From 74cf5d96731d1a2a99bf3b7d67e9b26518756b60 Mon Sep 17 00:00:00 2001 From: nwjgit Date: Tue, 19 Mar 2024 19:58:29 -0500 Subject: [PATCH 1/3] fixes --- src/lib/constants.ts | 3 + .../functions/addSkillingClueToLoot.ts | 35 +- src/lib/types/minions.ts | 4 +- src/lib/util/repeatStoredTrip.ts | 4 +- src/mahoji/commands/chop.ts | 37 +- .../lib/abstracted_commands/statCommand.ts | 38 ++ src/tasks/minions/woodcuttingActivity.ts | 335 ++++++++++++++++-- 7 files changed, 425 insertions(+), 31 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 926c61ad08..27c19316b3 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -774,6 +774,9 @@ export type NMZStrategy = (typeof NMZ_STRATEGY)[number]; export const UNDERWATER_AGILITY_THIEVING_TRAINING_SKILL = ['agility', 'thieving', 'agility+thieving'] as const; export type UnderwaterAgilityThievingTrainingSkill = (typeof UNDERWATER_AGILITY_THIEVING_TRAINING_SKILL)[number]; +export const TWITCHERS_GLOVES = ['egg', 'ring', 'seed', 'clue'] as const; +export type TwitcherGloves = (typeof TWITCHERS_GLOVES)[number]; + export const busyImmuneCommands = ['admin', 'rp']; export const usernameCache = new Map(); export const badgesCache = new Map(); diff --git a/src/lib/minions/functions/addSkillingClueToLoot.ts b/src/lib/minions/functions/addSkillingClueToLoot.ts index d58d926ddd..771978f63d 100644 --- a/src/lib/minions/functions/addSkillingClueToLoot.ts +++ b/src/lib/minions/functions/addSkillingClueToLoot.ts @@ -1,7 +1,14 @@ -import { sumArr } from 'e'; +import { percentChance, sumArr } from 'e'; import { Bank } from 'oldschooljs'; -import { birdsNestID, nestTable, strungRabbitFootNestTable } from '../../simulation/birdsNest'; +import { + birdsNestID, + eggNest, + nestTable, + ringNests, + strungRabbitFootNestTable, + treeSeedsNest +} from '../../simulation/birdsNest'; import { SkillsEnum } from '../../skilling/types'; import { randFloat, roll } from '../../util'; import itemID from '../../util/itemID'; @@ -22,17 +29,37 @@ export default function addSkillingClueToLoot( loot: Bank, clueNestsOnly?: boolean, strungRabbitFoot?: boolean, + twitcherSetting?: string, wcCapeNestBoost?: boolean ) { const userLevel = typeof userOrLevel === 'number' ? userOrLevel : userOrLevel.skillLevel(skill); - const chance = Math.floor(clueChance / (100 + userLevel)); const nestChance = wcCapeNestBoost ? Math.floor(256 * 0.9) : 256; const cluesTotalWeight = sumArr(clues.map(c => c[1])); + let chance = Math.floor(clueChance / (100 + userLevel)); let nests = 0; + if (skill === SkillsEnum.Woodcutting && twitcherSetting === 'clue') { + chance = Math.floor((clueChance * 0.8) / (100 + userLevel)); + } + for (let i = 0; i < quantity; i++) { if (skill === SkillsEnum.Woodcutting && !clueNestsOnly && roll(nestChance)) { - if (strungRabbitFoot) { + if (twitcherSetting && percentChance(20)) { + switch (twitcherSetting) { + case 'egg': + loot.add(eggNest.roll()); + nests++; + continue; + case 'seed': + loot.add(treeSeedsNest.roll()); + nests++; + continue; + case 'ring': + loot.add(ringNests.roll()); + nests++; + continue; + } + } else if (strungRabbitFoot) { loot.add(strungRabbitFootNestTable.roll()); continue; } else { diff --git a/src/lib/types/minions.ts b/src/lib/types/minions.ts index 746156820d..e6c4850efb 100644 --- a/src/lib/types/minions.ts +++ b/src/lib/types/minions.ts @@ -1,7 +1,7 @@ import type { CropUpgradeType } from '@prisma/client'; import { BathhouseTierName } from '../baxtorianBathhouses'; -import { NMZStrategy, UnderwaterAgilityThievingTrainingSkill } from '../constants'; +import { NMZStrategy, TwitcherGloves, UnderwaterAgilityThievingTrainingSkill } from '../constants'; import { Kibble } from '../data/kibble'; import { IMaterialBank, MaterialType } from '../invention'; import type { IPatchData } from '../minions/farming/types'; @@ -211,6 +211,8 @@ export interface WoodcuttingActivityTaskOptions extends ActivityTaskOptions { fakeDurationMax: number; fakeDurationMin: number; powerchopping: boolean; + forestry?: boolean; + twitchers?: TwitcherGloves; logID: number; quantity: number; iQty?: number; diff --git a/src/lib/util/repeatStoredTrip.ts b/src/lib/util/repeatStoredTrip.ts index b24691c9ea..e6e7bb7d38 100644 --- a/src/lib/util/repeatStoredTrip.ts +++ b/src/lib/util/repeatStoredTrip.ts @@ -584,7 +584,9 @@ export const tripHandlers = { args: (data: WoodcuttingActivityTaskOptions) => ({ name: itemNameFromID(data.logID), quantity: data.iQty, - powerchop: data.powerchopping + powerchop: data.powerchopping, + forestry_events: data.forestry, + twitchers_gloves: data.twitchers }) }, [activity_type_enum.VasaMagus]: { diff --git a/src/mahoji/commands/chop.ts b/src/mahoji/commands/chop.ts index 3a8590d125..6def67225e 100644 --- a/src/mahoji/commands/chop.ts +++ b/src/mahoji/commands/chop.ts @@ -1,7 +1,7 @@ import { increaseNumByPercent, reduceNumByPercent } from 'e'; import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { IVY_MAX_TRIP_LENGTH_BOOST } from '../../lib/constants'; +import { IVY_MAX_TRIP_LENGTH_BOOST, TwitcherGloves, TWITCHERS_GLOVES } from '../../lib/constants'; import { InventionID, inventionItemBoost } from '../../lib/invention/inventions'; import { determineWoodcuttingTime } from '../../lib/skilling/functions/determineWoodcuttingTime'; import Woodcutting from '../../lib/skilling/skills/woodcutting'; @@ -106,6 +106,19 @@ export const chopCommand: OSBMahojiCommand = { name: 'powerchop', description: 'Set this to true to powerchop. Higher xp/hour, No loot (default false, optional).', required: false + }, + { + type: ApplicationCommandOptionType.Boolean, + name: 'forestry_events', + description: 'Set this to true to participate in forestry events. (default false, optional).', + required: false + }, + { + type: ApplicationCommandOptionType.String, + name: 'twitchers_gloves', + description: "Change the settings of your Twitcher's gloves. (default egg, optional)", + required: false, + choices: TWITCHERS_GLOVES.map(i => ({ name: `${i} nest`, value: i })) } ], run: async ({ @@ -116,6 +129,8 @@ export const chopCommand: OSBMahojiCommand = { name: string; quantity?: number; powerchop?: boolean; + forestry_events?: boolean; + twitchers_gloves?: TwitcherGloves; }>) => { const user = await mUserFetch(userID); const log = Woodcutting.Logs.find( @@ -127,7 +142,7 @@ export const chopCommand: OSBMahojiCommand = { if (!log) return "That's not a valid log to chop."; - let { quantity, powerchop } = options; + let { quantity, powerchop, forestry_events, twitchers_gloves } = options; const skills = user.skillsAsLevels; @@ -150,11 +165,19 @@ export const chopCommand: OSBMahojiCommand = { let wcLvl = skills.woodcutting; // Invisible wc boost for woodcutting guild, forestry events don't happen in woodcutting guild - if (resolveItems(['Redwood logs', 'Logs']).includes(log.id) || log.lootTable) { + if ( + !forestry_events || + resolveItems(['Redwood logs', 'Logs']).includes(log.id) || + log.lootTable || + log.name === 'Ivy' + ) { + forestry_events = false; if (skills.woodcutting >= 60 && log.wcGuild) { boosts.push('+7 invisible WC lvls at the Woodcutting guild'); wcLvl += 7; } + } else { + boosts.push('Participating in Forestry events'); } // Enable 1.5 tick teaks half way to 99 @@ -164,6 +187,7 @@ export const chopCommand: OSBMahojiCommand = { // Default bronze axe, last in the array let axeMultiplier = 1; + boosts.push(`**${axeMultiplier}x** success multiplier for Bronze axe`); if (user.hasEquippedOrInBank(['Drygore axe'])) { let [predeterminedTotalTime] = determineWoodcuttingTime({ @@ -181,9 +205,11 @@ export const chopCommand: OSBMahojiCommand = { }); if (boostRes.success) { axeMultiplier = 10; + boosts.pop(); boosts.push(`**10x** success multiplier for Drygore axe (${boostRes.messages})`); } else { axeMultiplier = 8; + boosts.pop(); boosts.push('**8x** success multiplier for Dwarven greataxe'); } } else { @@ -196,7 +222,8 @@ export const chopCommand: OSBMahojiCommand = { } } - if (log.name === 'Ivy') { + // Ivy choping + if (!forestry_events && log.name === 'Ivy') { boosts.push(`+${formatDuration(IVY_MAX_TRIP_LENGTH_BOOST, true)} max trip length for Ivy`); powerchop = false; } @@ -244,6 +271,8 @@ export const chopCommand: OSBMahojiCommand = { quantity: newQuantity, iQty: options.quantity ? options.quantity : undefined, powerchopping: powerchop, + forestry: forestry_events, + twitchers: twitchers_gloves, duration, fakeDurationMin, fakeDurationMax, diff --git a/src/mahoji/lib/abstracted_commands/statCommand.ts b/src/mahoji/lib/abstracted_commands/statCommand.ts index c185763bee..3863ed3e37 100644 --- a/src/mahoji/lib/abstracted_commands/statCommand.ts +++ b/src/mahoji/lib/abstracted_commands/statCommand.ts @@ -39,6 +39,7 @@ import { makeBankImage } from '../../../lib/util/makeBankImage'; import resolveItems from '../../../lib/util/resolveItems'; import { Cooldowns } from '../Cooldowns'; import { collectables } from './collectCommand'; +import { ForestryEvents } from '../../../tasks/minions/woodcuttingActivity'; interface DataPiece { name: string; @@ -1160,6 +1161,43 @@ GROUP BY "bankBackground";`); .join('\n')}`; } }, + { + name: 'Personal XP gained from Forestry events', + perkTierNeeded: PerkTier.Four, + run: async (user: MUser) => { + const result = await prisma.$queryRawUnsafe( + `SELECT skill, + SUM(xp)::int AS total_xp + FROM xp_gains + WHERE source = 'ForestryEvents' + AND user_id = ${BigInt(user.id)} + GROUP BY skill + ORDER BY CASE + WHEN skill = 'woodcutting' THEN 0 + ELSE 1 + END` + ); + + return `**Personal XP gained from Forestry events**\n${result + .map( + (i: any) => + `${skillEmoji[i.skill as keyof typeof skillEmoji] as keyof SkillsScore} ${toKMB(i.total_xp)}` + ) + .join('\n')}`; + } + }, + { + name: 'Forestry events completed', + perkTierNeeded: PerkTier.Four, + run: async (_, userStats) => { + let str = 'You have completed...\n\n'; + for (const event of ForestryEvents) { + const qty = (userStats.forestry_event_completions_bank as ItemBank)[event.id] ?? 0; + str += `${event.name}: ${qty}\n`; + } + return str; + } + }, { name: 'Bird Eggs Offered', perkTierNeeded: null, diff --git a/src/tasks/minions/woodcuttingActivity.ts b/src/tasks/minions/woodcuttingActivity.ts index 80481b5928..65aa7c43b6 100644 --- a/src/tasks/minions/woodcuttingActivity.ts +++ b/src/tasks/minions/woodcuttingActivity.ts @@ -1,35 +1,287 @@ -import { Time } from 'e'; -import { Bank } from 'oldschooljs'; +import { percentChance, randInt, Time } from 'e'; +import { Bank, LootTable } from 'oldschooljs'; -import { Emoji, Events, MAX_LEVEL, MIN_LENGTH_FOR_PET } from '../../lib/constants'; +import { Emoji, Events, MAX_LEVEL, MIN_LENGTH_FOR_PET, TwitcherGloves } from '../../lib/constants'; +import { MediumSeedPackTable } from '../../lib/data/seedPackTables'; import addSkillingClueToLoot from '../../lib/minions/functions/addSkillingClueToLoot'; +import { eggNest } from '../../lib/simulation/birdsNest'; import Firemaking from '../../lib/skilling/skills/firemaking'; import Woodcutting from '../../lib/skilling/skills/woodcutting'; import { SkillsEnum } from '../../lib/skilling/types'; import { WoodcuttingActivityTaskOptions } from '../../lib/types/minions'; -import { clAdjustedDroprate, itemID, roll, skillingPetDropRate } from '../../lib/util'; +import { clAdjustedDroprate, itemID, perTimeUnitChance, roll, skillingPetDropRate } from '../../lib/util'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; +import { userStatsBankUpdate } from '../../mahoji/mahojiSettings'; + +export interface ForestryEvent { + id: number; + name: string; + uniqueXP: SkillsEnum; +} + +export const ForestryEvents: ForestryEvent[] = [ + { + id: 1, + name: 'Rising Roots', + uniqueXP: SkillsEnum.Woodcutting + }, + { + id: 2, + name: 'Struggling Sapling', + uniqueXP: SkillsEnum.Farming + }, + { + id: 3, + name: 'Flowering Bush', + uniqueXP: SkillsEnum.Woodcutting + }, + { + id: 4, + name: 'Woodcutting Leprechaun', + uniqueXP: SkillsEnum.Woodcutting + }, + { + id: 5, + name: 'Beehive', + uniqueXP: SkillsEnum.Construction + }, + { + id: 6, + name: 'Friendly Ent', + uniqueXP: SkillsEnum.Fletching + }, + { + id: 7, + name: 'Poachers', + uniqueXP: SkillsEnum.Hunter + }, + { + id: 8, + name: 'Enchantment Ritual', + uniqueXP: SkillsEnum.Woodcutting + }, + { + id: 9, + name: 'Pheasant Control', + uniqueXP: SkillsEnum.Thieving + } +]; + +const LeafTable = new LootTable() + .add('Leaves', 20) + .add('Oak leaves', 20) + .add('Willow leaves', 20) + .add('Maple leaves', 20) + .add('Yew leaves', 20) + .add('Magic leaves', 20); + +async function handleForestry({ user, duration, loot }: { user: MUser; duration: number; loot: Bank }) { + let eventCounts: { [key: number]: number } = {}; + let eventXP = {} as { [key in SkillsEnum]: number }; + ForestryEvents.forEach(event => { + eventCounts[event.id] = 0; + eventXP[event.uniqueXP] = 0; + }); + + let strForestry = ''; + const userWcLevel = user.skillLevel(SkillsEnum.Woodcutting); + const chanceWcLevel = Math.min(userWcLevel, 99); + const eggChance = Math.ceil(2700 - ((chanceWcLevel - 1) * (2700 - 1350)) / 98); + const whistleChance = Math.ceil(90 - ((chanceWcLevel - 1) * (90 - 45)) / 98); + + perTimeUnitChance(duration, 20, Time.Minute, async () => { + const eventIndex = randInt(0, ForestryEvents.length - 1); + const event = ForestryEvents[eventIndex]; + let eventRounds = 0; + let eventInteraction = 0; + + switch (event.id) { + case 1: // Rising Roots + eventRounds = randInt(5, 7); // anima-infused roots spawned + for (let i = 0; i < eventRounds; i++) { + eventInteraction += randInt(5, 6); // anima-infused roots chopped + } + eventCounts[event.id]++; + eventXP[event.uniqueXP] += user.skillLevel(event.uniqueXP) * 1.4 * eventInteraction; + break; + case 2: // Struggling Sapling + eventInteraction = randInt(12, 15); // mulch added to sapling + loot.add(LeafTable.roll()); + eventCounts[event.id]++; + eventXP[event.uniqueXP] += eventInteraction * (user.skillLevel(event.uniqueXP) * 0.6); + eventXP[SkillsEnum.Woodcutting] += eventInteraction * (userWcLevel * 1.95) * 2; + break; + case 3: // Flowering Bush + eventRounds = randInt(5, 7); // bush pairs spawned + for (let i = 0; i < eventRounds; i++) { + eventInteraction += randInt(12, 20); // bushes pollinated + } + loot.add('Strange fruit', randInt(4, 8)).add(MediumSeedPackTable.roll()); + eventCounts[event.id]++; + eventXP[event.uniqueXP] += user.skillLevel(event.uniqueXP) * 0.25 * eventInteraction * 3; + break; + case 4: // Woodcutting Leprechaun + eventInteraction = randInt(6, 8); // rainbows entered + eventCounts[event.id]++; + eventXP[event.uniqueXP] += user.skillLevel(event.uniqueXP) * 2 * eventInteraction; + break; + case 5: // Beehive + eventRounds = randInt(5, 7); // beehives spawned + for (let i = 0; i < eventRounds; i++) { + if (percentChance(66)) { + loot.add('Sturdy beehive parts'); + } + eventInteraction += randInt(5, 10); // repairs per beehive + } + eventCounts[event.id]++; + eventXP[event.uniqueXP] += user.skillLevel(event.uniqueXP) * 0.3 * eventInteraction; + eventXP[SkillsEnum.Woodcutting] += + eventInteraction * (userWcLevel * 0.6) + userWcLevel * 3.8 * eventRounds; + break; + case 6: // Friendly Ent + eventInteraction = randInt(40, 60); // ents pruned + loot.add(LeafTable.roll()); + loot.add(eggNest.roll()); + eventCounts[event.id]++; + eventXP[event.uniqueXP] += user.skillLevel(event.uniqueXP) * 0.2 * eventInteraction; + eventXP[SkillsEnum.Woodcutting] += eventInteraction * (userWcLevel * 0.55); + break; + case 7: // Poachers + eventInteraction = randInt(12, 15); // traps disarmed + if (roll(whistleChance)) { + loot.add('Fox whistle'); + } + eventCounts[event.id]++; + eventXP[event.uniqueXP] += eventInteraction * (user.skillLevel(event.uniqueXP) / 2); + eventXP[SkillsEnum.Woodcutting] += eventInteraction * (userWcLevel * 1.35); + break; + case 8: // Enchantment Ritual + eventInteraction = randInt(6, 8); // ritual circles + if (roll(50)) { + loot.add('Petal garland'); + } + eventCounts[event.id]++; + eventXP[event.uniqueXP] += user.skillLevel(event.uniqueXP) * eventInteraction * 5.5; + break; + case 9: // Pheasant Control + eventInteraction = randInt(15, 45); // eggs delivered + for (let i = 0; i < eventInteraction; i++) { + if (percentChance(50)) { + loot.add('Pheasant tail feathers'); + } + if (roll(eggChance)) { + loot.add('Golden pheasant egg'); + } + } + eventCounts[event.id]++; + eventXP[event.uniqueXP] += eventInteraction * (user.skillLevel(event.uniqueXP) / 2); + eventXP[SkillsEnum.Woodcutting] += eventInteraction * (userWcLevel * 1.1); + break; + } + // Give user Anima-infused bark per event + loot.add('Anima-infused bark', randInt(250, 500)); + }); + + let totalEvents = 0; + for (const event in eventCounts) { + if (eventCounts.hasOwnProperty(event)) { + const count = eventCounts[event]; + totalEvents += count; + await userStatsBankUpdate( + user.id, + 'forestry_event_completions_bank', + new Bank().add(parseInt(event), count) + ); + } + } + + // Give user xp from events + let xpRes = ''; + for (const skill in eventXP) { + if (eventXP.hasOwnProperty(skill)) { + xpRes += await user.addXP({ + skillName: skill as SkillsEnum, + amount: Math.ceil(eventXP[skill as SkillsEnum]), + minimal: true, + source: 'ForestryEvents' + }); + } + } + + // Generate forestry message + const completedEvents = Object.entries(eventCounts) + .map(([eventId, count]) => { + const event = ForestryEvents.find(e => e.id === parseInt(eventId)); + return count > 0 ? `${count} ${event!.name}` : null; + }) + .filter(Boolean) + .join(' & '); + strForestry += `${ + totalEvents > 0 ? `Completed Forestry event${totalEvents > 1 ? 's:' : ':'} ${completedEvents}. ${xpRes}\n` : '' + }`; + strForestry += `${ + loot.has('Sturdy beehive parts') && !user.cl.has('Sturdy beehive parts') // only show this message once to reduce spam + ? '- The temporary beehive was made so well you could repurpose parts of it to build a permanent hive.\n' + : '' + }`; + strForestry += `${ + loot.has('Golden pheasant egg') + ? '- You feel a connection to the pheasants as if one wishes to travel with you...\n' + : '' + }`; + strForestry += `${ + loot.has('Fox whistle') ? '- You feel a connection to the fox as if it wishes to travel with you...\n' : '' + }`; + strForestry += `${loot.has('Petal garland') ? '- The Dryad also hands you a Petal garland.\n' : ''}`; + + return strForestry; +} export const woodcuttingTask: MinionTask = { type: 'Woodcutting', async run(data: WoodcuttingActivityTaskOptions) { - const { logID, quantity, userID, channelID, duration, powerchopping } = data; + const { logID, quantity, userID, channelID, duration, powerchopping, forestry, twitchers } = data; const user = await mUserFetch(userID); - - const log = Woodcutting.Logs.find(Log => Log.id === logID)!; - let clueChance = log.clueScrollChance; + let userWcLevel = user.skillLevel(SkillsEnum.Woodcutting); + const log = Woodcutting.Logs.find(i => i.id === logID)!; + const forestersRations = user.bank.amount("Forester's ration"); + const wcCapeNestBoost = + user.hasEquipped('Woodcutting cape') || + (user.hasEquipped('Forestry basket') && + user.bank.has(['Woodcutting cape', 'Cape pouch']) && + userWcLevel >= 99); let strungRabbitFoot = user.hasEquipped('Strung rabbit foot'); + let twitchersEquipped = user.hasEquipped("twitcher's gloves"); + let twitcherSetting: TwitcherGloves | undefined = 'egg'; let xpReceived = quantity * log.xp; + let bonusXP = 0; + let rationUsed = 0; + let lostLogs = 0; + let loot = new Bank(); + let itemsToRemove = new Bank(); + + // GMC for elder logs + let clueChance = log.clueScrollChance; if (logID === itemID('Elder logs')) { const userWcLevel = user.skillLevel(SkillsEnum.Woodcutting); // Bring it as close as possible to Rocktails if (userWcLevel >= MAX_LEVEL) clueChance = 13_011; } - let bonusXP = 0; - let lostLogs = 0; - let loot = new Bank(); - let itemsToRemove = new Bank(); + + // Felling axe +10% xp bonus & 20% logs lost + if (user.gear.skilling.hasEquipped('Bronze felling axe')) { + for (let i = 0; i < quantity && i < forestersRations; i++) { + rationUsed++; + if (percentChance(20)) { + lostLogs++; + } + } + const fellingXP = Math.floor(rationUsed * log.xp * 0.1); + xpReceived += fellingXP; + bonusXP += fellingXP; + itemsToRemove.add("Forester's ration", rationUsed); + } // If they have the entire lumberjack outfit, give an extra 0.5% xp bonus if ( @@ -52,6 +304,7 @@ export const woodcuttingTask: MinionTask = { } } + // Give the user xp let xpRes = await user.addXP({ skillName: SkillsEnum.Woodcutting, amount: Math.ceil(xpReceived), @@ -61,13 +314,13 @@ export const woodcuttingTask: MinionTask = { // Add Logs or loot if (!powerchopping) { if (log.lootTable) { - loot.add(log.lootTable.roll(quantity)); + loot.add(log.lootTable.roll(quantity - lostLogs)); } else if (!log.hasNoLoot) { - loot.add(log.id, quantity); + loot.add(log.id, quantity - lostLogs); const logItem = Firemaking.Burnables.find(i => i.inputLogs === log.id); if (user.hasEquipped('Inferno adze') && logItem) { - loot.remove(log.id, quantity); - loot.add('Ashes', quantity); + loot.remove(log.id, quantity - lostLogs); + loot.add('Ashes', quantity - lostLogs); xpRes += '\n'; xpRes += await user.addXP({ skillName: SkillsEnum.Firemaking, @@ -78,10 +331,29 @@ export const woodcuttingTask: MinionTask = { } } + // Add leaves + if (log.leaf && user.hasEquippedOrInBank('Forestry kit')) { + for (let i = 0; i < quantity; i++) { + if (percentChance(25)) { + loot.add(log.leaf, 1); + } + } + } + + // WC master cape perk if (user.hasEquippedOrInBank('Woodcutting master cape')) { loot.multiply(2); } + // Check for twitcher gloves + if (twitchersEquipped) { + if (twitchers !== undefined) { + twitcherSetting = twitchers; + } + } else { + twitcherSetting = undefined; + } + // Add clue scrolls if (clueChance) { addSkillingClueToLoot( @@ -91,7 +363,9 @@ export const woodcuttingTask: MinionTask = { clueChance, loot, log.clueNestsOnly, - strungRabbitFoot + strungRabbitFoot, + twitcherSetting, + wcCapeNestBoost ); } @@ -100,6 +374,27 @@ export const woodcuttingTask: MinionTask = { bonusXP > 0 ? ` **Bonus XP:** ${bonusXP.toLocaleString()}` : '' }\n`; + if (!log.clueNestsOnly) { + if (wcCapeNestBoost) { + str += `Your ${ + user.hasEquipped('Woodcutting cape') ? 'Woodcutting cape' : 'Forestry basket' + } increases the chance of receiving bird nests.\n`; + } + if (strungRabbitFoot) { + str += + 'Your Strung rabbit foot necklace increases the chance of receiving bird egg nests and ring nests.\n'; + } + if (twitcherSetting !== undefined) { + str += `Your Twitcher's gloves increases the chance of receiving ${twitcherSetting} nests.\n`; + } + } + + // Forestry events + if (forestry) { + str += await handleForestry({ user, duration, loot }); + } + + // Roll for Peky if (duration >= MIN_LENGTH_FOR_PET) { const minutes = duration / Time.Minute; const droprate = clAdjustedDroprate(user, 'Peky', Math.floor(4000 / minutes), 1.5); @@ -109,7 +404,8 @@ export const woodcuttingTask: MinionTask = { '\n<:peky:787028037031559168> A small pigeon has taken a liking to you, and hides itself in your bank.'; } } - // Roll for pet + + // Roll for OSB pet if (log.petChance) { const { petDropRate } = skillingPetDropRate(user, SkillsEnum.Woodcutting, log.petChance); if (roll(petDropRate / quantity)) { @@ -125,9 +421,6 @@ export const woodcuttingTask: MinionTask = { ); } } - if (bonusXP > 0) { - str += `. **Bonus XP:** ${bonusXP.toLocaleString()}`; - } // Loot received, items used, and logs/loot rolls lost message str += `\nYou received ${loot}. `; From 338283d2f6200ce5cd746dc76cc7f1de88c3367d Mon Sep 17 00:00:00 2001 From: nwjgit Date: Tue, 19 Mar 2024 20:05:48 -0500 Subject: [PATCH 2/3] import fixes --- src/mahoji/lib/abstracted_commands/statCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mahoji/lib/abstracted_commands/statCommand.ts b/src/mahoji/lib/abstracted_commands/statCommand.ts index 3863ed3e37..83b88814c5 100644 --- a/src/mahoji/lib/abstracted_commands/statCommand.ts +++ b/src/mahoji/lib/abstracted_commands/statCommand.ts @@ -37,9 +37,9 @@ import { barChart, lineChart, pieChart } from '../../../lib/util/chart'; import { getItem } from '../../../lib/util/getOSItem'; import { makeBankImage } from '../../../lib/util/makeBankImage'; import resolveItems from '../../../lib/util/resolveItems'; +import { ForestryEvents } from '../../../tasks/minions/woodcuttingActivity'; import { Cooldowns } from '../Cooldowns'; import { collectables } from './collectCommand'; -import { ForestryEvents } from '../../../tasks/minions/woodcuttingActivity'; interface DataPiece { name: string; From 2533bebc74cd5e70eb2d1b1858b9d656cbf19d8d Mon Sep 17 00:00:00 2001 From: nwjgit Date: Wed, 20 Mar 2024 23:03:01 -0500 Subject: [PATCH 3/3] fix variable --- src/tasks/minions/woodcuttingActivity.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tasks/minions/woodcuttingActivity.ts b/src/tasks/minions/woodcuttingActivity.ts index 65aa7c43b6..4d761a7e93 100644 --- a/src/tasks/minions/woodcuttingActivity.ts +++ b/src/tasks/minions/woodcuttingActivity.ts @@ -242,7 +242,7 @@ export const woodcuttingTask: MinionTask = { async run(data: WoodcuttingActivityTaskOptions) { const { logID, quantity, userID, channelID, duration, powerchopping, forestry, twitchers } = data; const user = await mUserFetch(userID); - let userWcLevel = user.skillLevel(SkillsEnum.Woodcutting); + const userWcLevel = user.skillLevel(SkillsEnum.Woodcutting); const log = Woodcutting.Logs.find(i => i.id === logID)!; const forestersRations = user.bank.amount("Forester's ration"); const wcCapeNestBoost = @@ -264,7 +264,6 @@ export const woodcuttingTask: MinionTask = { // GMC for elder logs let clueChance = log.clueScrollChance; if (logID === itemID('Elder logs')) { - const userWcLevel = user.skillLevel(SkillsEnum.Woodcutting); // Bring it as close as possible to Rocktails if (userWcLevel >= MAX_LEVEL) clueChance = 13_011; }