diff --git a/src/lib/types/minions.ts b/src/lib/types/minions.ts index 583d39b8cf..46fbd2b585 100644 --- a/src/lib/types/minions.ts +++ b/src/lib/types/minions.ts @@ -150,6 +150,7 @@ export interface FishingActivityTaskOptions extends ActivityTaskOptions { type: 'Fishing'; fishID: number; quantity: number; + flakesQuantity?: number; iQty?: number; } diff --git a/src/lib/util/repeatStoredTrip.ts b/src/lib/util/repeatStoredTrip.ts index 25549dad68..179270c099 100644 --- a/src/lib/util/repeatStoredTrip.ts +++ b/src/lib/util/repeatStoredTrip.ts @@ -306,7 +306,11 @@ const tripHandlers = { }, [activity_type_enum.Fishing]: { commandName: 'fish', - args: (data: FishingActivityTaskOptions) => ({ name: data.fishID, quantity: data.iQty }) + args: (data: FishingActivityTaskOptions) => ({ + name: data.fishID, + quantity: data.iQty, + flakes: data.flakesQuantity !== undefined + }) }, [activity_type_enum.FishingTrawler]: { commandName: 'minigames', diff --git a/src/lib/util/smallUtils.ts b/src/lib/util/smallUtils.ts index 82229775b6..71d9e86234 100644 --- a/src/lib/util/smallUtils.ts +++ b/src/lib/util/smallUtils.ts @@ -70,6 +70,12 @@ export function pluraliseItemName(name: string): string { return name + (name.endsWith('s') ? '' : 's'); } +export function pluraliseItemNameWithQuantity(name: string, quantity: number): string { + const result = `${quantity} ${name}`; + if (quantity === 1) return result; + return pluraliseItemName(result); +} + export function shuffleRandom(input: number, arr: readonly T[]): T[] { const engine = MersenneTwister19937.seed(input); return shuffle(engine, [...arr]); diff --git a/src/mahoji/commands/fish.ts b/src/mahoji/commands/fish.ts index 8e585bb919..ddbf3e2a9e 100644 --- a/src/mahoji/commands/fish.ts +++ b/src/mahoji/commands/fish.ts @@ -8,7 +8,7 @@ import TzTokJad from 'oldschooljs/dist/simulation/monsters/special/TzTokJad'; import Fishing from '../../lib/skilling/skills/fishing'; import { SkillsEnum } from '../../lib/skilling/types'; import type { FishingActivityTaskOptions } from '../../lib/types/minions'; -import { formatDuration, itemID, itemNameFromID } from '../../lib/util'; +import { formatDuration, itemID, itemNameFromID, pluraliseItemNameWithQuantity } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import type { OSBMahojiCommand } from '../lib/util'; @@ -42,9 +42,19 @@ export const fishCommand: OSBMahojiCommand = { description: 'The quantity you want to fish (optional).', required: false, min_value: 1 + }, + { + type: ApplicationCommandOptionType.Boolean, + name: 'flakes', + description: 'Use spirit flakes?', + required: false } ], - run: async ({ options, userID, channelID }: CommandRunOptions<{ name: string; quantity?: number }>) => { + run: async ({ + options, + userID, + channelID + }: CommandRunOptions<{ name: string; quantity?: number; flakes?: boolean }>) => { const user = await mUserFetch(userID); const fish = Fishing.Fishes.find( fish => @@ -132,8 +142,24 @@ export const fishCommand: OSBMahojiCommand = { const maxTripLength = calcMaxTripLength(user, 'Fishing'); - let { quantity } = options; - if (!quantity) quantity = Math.floor(maxTripLength / scaledTimePerFish); + let { quantity, flakes } = options; + if (!quantity) { + quantity = Math.floor(maxTripLength / scaledTimePerFish); + } + let shouldRemoveFromBank = false; + let flakesQuantity: number | undefined; + const cost = new Bank(); + + if (flakes) { + if (!user.bank.has('Spirit flakes')) { + return 'You need to have at least one spirit flake!'; + } + + flakesQuantity = Math.min(user.bank.amount('Spirit flakes'), quantity); + boosts.push(`More fish from using ${pluraliseItemNameWithQuantity('spirit flake', flakesQuantity)}`); + shouldRemoveFromBank = true; + cost.add('Spirit flakes', flakesQuantity); + } if (fish.bait) { const baseCost = new Bank().add(fish.bait); @@ -146,11 +172,13 @@ export const fishCommand: OSBMahojiCommand = { quantity = maxCanDo; } - const cost = new Bank(); - cost.add(baseCost.multiply(quantity)); + shouldRemoveFromBank = true; + cost.add(fish.bait, quantity); + } - // Remove the bait from their bank. - await user.removeItemsFromBank(new Bank().add(fish.bait, quantity)); + if (shouldRemoveFromBank) { + // Remove the bait and/or spirit flakes from their bank. + await user.removeItemsFromBank(cost); } let duration = quantity * scaledTimePerFish; @@ -173,7 +201,8 @@ export const fishCommand: OSBMahojiCommand = { quantity, iQty: options.quantity ? options.quantity : undefined, duration, - type: 'Fishing' + type: 'Fishing', + flakesQuantity }); let response = `${user.minionName} is now fishing ${quantity}x ${fish.name}, it'll take around ${formatDuration( diff --git a/src/tasks/minions/fishingActivity.ts b/src/tasks/minions/fishingActivity.ts index 1eb322259e..6e51ef4e63 100644 --- a/src/tasks/minions/fishingActivity.ts +++ b/src/tasks/minions/fishingActivity.ts @@ -41,6 +41,7 @@ export const fishingTask: MinionTask = { }), async run(data: FishingActivityTaskOptions) { const { fishID, quantity, userID, channelID, duration } = data; + let { flakesQuantity } = data; const user = await mUserFetch(userID); const currentLevel = user.skillLevel(SkillsEnum.Fishing); const { blessingEquipped, blessingChance } = radasBlessing(user); @@ -160,6 +161,11 @@ export const fishingTask: MinionTask = { } else { lootQuantity += blessingEquipped && percentChance(blessingChance) ? 2 : 1; } + + if (flakesQuantity && flakesQuantity > 0) { + lootQuantity += percentChance(50) ? 1 : 0; + flakesQuantity--; + } } const loot = new Bank({ diff --git a/tests/unit/commands/fish.test.ts b/tests/unit/commands/fish.test.ts index 469a6720fe..f447d05138 100644 --- a/tests/unit/commands/fish.test.ts +++ b/tests/unit/commands/fish.test.ts @@ -92,4 +92,41 @@ describe('Fish Command', () => { **Boosts:** +9 trip minutes for having a Fish sack barrel.` }); }); + + it('should handle using flakes without flakes in bank', () => { + testRunCmd({ + cmd: fishCommand, + opts: { name: 'shrimps', flakes: true }, + user: { + skills_fishing: 999_999 + }, + result: 'You need to have at least one spirit flake!' + }); + }); + + it('should fish with flakes', () => { + testRunCmd({ + cmd: fishCommand, + opts: { name: 'shrimps', flakes: true }, + user: { + bank: new Bank({ 'Spirit flakes': 10000 }) + }, + result: `<:minion:778418736180494347> Your minion is now fishing 251x Shrimps, it'll take around 29 minutes, 58 seconds to finish. + +**Boosts:** More fish from using 251 spirit flakes.` + }); + }); + + it('should still use flakes if bank contains fewer flakes than fish quantity', () => { + testRunCmd({ + cmd: fishCommand, + opts: { name: 'shrimps', flakes: true }, + user: { + bank: new Bank({ 'Spirit flakes': 100 }) + }, + result: `<:minion:778418736180494347> Your minion is now fishing 251x Shrimps, it'll take around 29 minutes, 58 seconds to finish. + +**Boosts:** More fish from using 100 spirit flakes.` + }); + }); }); diff --git a/tests/unit/util.test.ts b/tests/unit/util.test.ts index e2a3e0110d..c37d7098a6 100644 --- a/tests/unit/util.test.ts +++ b/tests/unit/util.test.ts @@ -8,7 +8,13 @@ import { baseModifyBusyCounter } from '../../src/lib/busyCounterCache'; import { deduplicateClueScrolls } from '../../src/lib/clues/clueUtils'; import getUserFoodFromBank from '../../src/lib/minions/functions/getUserFoodFromBank'; import { SkillsEnum } from '../../src/lib/skilling/types'; -import { pluraliseItemName, sanitizeBank, skillingPetDropRate, stripEmojis } from '../../src/lib/util'; +import { + pluraliseItemName, + pluraliseItemNameWithQuantity, + sanitizeBank, + skillingPetDropRate, + stripEmojis +} from '../../src/lib/util'; import getOSItem from '../../src/lib/util/getOSItem'; import { sellPriceOfItem, sellStorePriceOfItem } from '../../src/mahoji/commands/sell'; import { mockMUser } from './utils'; @@ -142,4 +148,12 @@ describe('util', () => { expect(pluraliseItemName('Steel Arrowtips')).toEqual('Steel Arrowtips'); expect(pluraliseItemName('Adamantite nails')).toEqual('Adamantite nails'); }); + + test('pluraliseItemWithQuantity correctly pluralises items', async () => { + expect(pluraliseItemNameWithQuantity('Shrimp', 0)).toEqual('0 Shrimps'); + expect(pluraliseItemNameWithQuantity('Twisted bow', 999)).toEqual('999 Twisted bows'); + expect(pluraliseItemNameWithQuantity('Bronze Arrowtips', 0)).toEqual('0 Bronze Arrowtips'); + expect(pluraliseItemNameWithQuantity('Spirit flakes', 10)).toEqual('10 Spirit flakes'); + expect(pluraliseItemNameWithQuantity('Yellow square', 1)).toEqual('1 Yellow square'); + }); });