diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 6bd833ee5d..926c61ad08 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -325,7 +325,8 @@ export enum BitField { HasIncandescentBoon = 224, HasVibrantBoon = 225, HasAncientBoon = 226, - DisabledTameClueOpening = 227 + DisabledTameClueOpening = 227, + HasMoondashCharm = 228 } interface BitFieldData { @@ -528,6 +529,11 @@ export const BitFieldData: Record = { name: 'Disable Wilderness High Peak Time Warning', protected: false, userConfigurable: true + }, + [BitField.HasMoondashCharm]: { + name: 'Used Moondash Charm', + protected: false, + userConfigurable: true } } as const; diff --git a/src/lib/customItems/customItems.ts b/src/lib/customItems/customItems.ts index 870a6fe545..70e23c0095 100644 --- a/src/lib/customItems/customItems.ts +++ b/src/lib/customItems/customItems.ts @@ -11186,17 +11186,7 @@ setCustomItem( 1 ); -setCustomItem( - 73_077, - 'Moonlight mutator', - 'Coal', - { - customItemData: { - cantDropFromMysteryBoxes: true - } - }, - 1 -); +// 73_077 Moonlight mutator setCustomItem( 73_078, @@ -11533,3 +11523,63 @@ setCustomItem( }, 1 ); + +setCustomItem( + 73_106, + 'Herbal zygomite spores', + 'Coal', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 1 +); + +setCustomItem( + 73_107, + 'Barky zygomite spores', + 'Coal', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 1 +); + +setCustomItem( + 73_109, + 'Fruity zygomite spores', + 'Coal', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 1 +); + +setCustomItem( + 73_110, + 'Moonlight essence', + 'Coal', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 1 +); + +setCustomItem( + 73_111, + 'Fungo', + 'Herbi', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 1 +); diff --git a/src/lib/customItems/invention/inventions.ts b/src/lib/customItems/invention/inventions.ts index 59f8a2239e..258c8bc292 100644 --- a/src/lib/customItems/invention/inventions.ts +++ b/src/lib/customItems/invention/inventions.ts @@ -33,3 +33,4 @@ addInvention(63_326, 'RoboFlappy'); addInvention(73_039, 'Wisp-buster', 'Bronze dagger'); addInvention(73_042, 'Divine hand', 'Bronze dagger'); addInvention(73_055, 'Drygore axe', 'Bronze dagger'); +addInvention(73_077, 'Moonlight mutator'); diff --git a/src/lib/invention/inventions.ts b/src/lib/invention/inventions.ts index e8f1a983f9..38e00dfac5 100644 --- a/src/lib/invention/inventions.ts +++ b/src/lib/invention/inventions.ts @@ -38,7 +38,8 @@ export enum InventionID { ChinCannon = 16, WispBuster = 17, DivineHand = 18, - DrygoreAxe = 19 + DrygoreAxe = 19, + MoonlightMutator = 20 } export type Invention = Readonly<{ @@ -452,6 +453,20 @@ export const Inventions: readonly Invention[] = [ flags: ['equipped'], inventionLevelNeeded: 100, usageCostMultiplier: 0.65 + }, + { + id: InventionID.MoonlightMutator, + name: 'Moonlight mutator', + description: 'Mutates seeds from your bank into zygomite spores.', + item: getOSItem('Moonlight mutator'), + materialTypeBank: new MaterialBank({ + organic: 5, + magic: 5 + }), + itemCost: new Bank().add('Moonlight essence').add('Lunite', 250), + flags: ['bank'], + inventionLevelNeeded: 100, + usageCostMultiplier: 0.1 } ] as const; diff --git a/src/lib/minions/data/killableMonsters/custom/SunMoon.ts b/src/lib/minions/data/killableMonsters/custom/SunMoon.ts index 42a91b5d49..58dff549db 100644 --- a/src/lib/minions/data/killableMonsters/custom/SunMoon.ts +++ b/src/lib/minions/data/killableMonsters/custom/SunMoon.ts @@ -3,6 +3,7 @@ import { Bank, LootTable, Monsters } from 'oldschooljs'; import { GearStat } from '../../../../gear'; import { addStatsOfItemsTogether, Gear } from '../../../../structures/Gear'; +import itemID from '../../../../util/itemID'; import resolveItems from '../../../../util/resolveItems'; import { CustomMonster } from './customMonsters'; @@ -18,6 +19,17 @@ solisMinGear.equip('Ignis ring(i)'); solisMinGear.equip('Drygore rapier'); solisMinGear.equip('Offhand dragon claw'); +const celesMinGear = new Gear(); +celesMinGear.equip('Gorajan warrior helmet'); +celesMinGear.equip('Gorajan warrior top'); +celesMinGear.equip('Gorajan warrior legs'); +celesMinGear.equip('Gorajan warrior gloves'); +celesMinGear.equip('Gorajan warrior boots'); +celesMinGear.equip('TzKal cape'); +celesMinGear.equip("Brawler's hook necklace"); +celesMinGear.equip('Ignis ring(i)'); +celesMinGear.equip('Soulreaper axe'); + export const Solis: CustomMonster = { id: 129_124, baseMonster: Monsters.AbyssalSire, @@ -83,22 +95,22 @@ export const Celestara: CustomMonster = { baseMonster: Monsters.AbyssalSire, name: 'Celestara', aliases: ['celestara'], - timeToFinish: Time.Minute * 120, + timeToFinish: Time.Minute * 100, hp: 3330, - table: new LootTable().every('Lunite', [10, 60]).tertiary(300, 'Noom'), + table: new LootTable().every('Lunite', [10, 60]).tertiary(300, 'Moonlight essence').tertiary(300, 'Noom'), difficultyRating: 5, - qpRequired: 2500, - healAmountNeeded: 350 * 200, + qpRequired: 1500, + healAmountNeeded: 250 * 200, attackStyleToUse: GearStat.AttackStab, attackStylesUsed: [GearStat.AttackStab], levelRequirements: { - hitpoints: 120, - attack: 110, - strength: 110, - defence: 110, - magic: 110, - ranged: 110, - slayer: 110 + hitpoints: 100, + attack: 100, + strength: 100, + defence: 100, + magic: 100, + ranged: 100, + slayer: 100 }, pohBoosts: { pool: { @@ -121,7 +133,7 @@ export const Celestara: CustomMonster = { } }, minimumWeaponShieldStats: { - melee: addStatsOfItemsTogether(resolveItems(['Offhand dragon claw', 'Drygore rapier']), [GearStat.AttackStab]) + melee: addStatsOfItemsTogether(resolveItems(['Soulreaper axe']), [GearStat.AttackSlash]) }, itemCost: { itemCost: new Bank().add('Super combat potion(4)').add('Heat res. brew', 3).add('Heat res. restore'), @@ -133,11 +145,23 @@ export const Celestara: CustomMonster = { const tames = await user.fetchTames(); const hasMaxedIgne = tames.some(tame => tame.isMaxedIgneTame()); if (hasMaxedIgne) return null; - return 'You need to have a maxed Igne Tame (best gear, all fed items) to fight Solis.'; + return 'You need to have a maxed Igne Tame (best gear, all fed items) to fight Celestara.'; }, - setupsUsed: ['melee'] + setupsUsed: ['melee'], + equippedItemBoosts: [ + { + gearSetup: 'melee', + items: [ + { + boostPercent: 25, + itemID: itemID('Axe of the high sungod') + } + ] + } + ] }; export const SunMoonMonsters = { - Solis + Solis, + Celestara }; diff --git a/src/lib/simulation/simulatedKillables.ts b/src/lib/simulation/simulatedKillables.ts index ae982f14a1..8b72ffa493 100644 --- a/src/lib/simulation/simulatedKillables.ts +++ b/src/lib/simulation/simulatedKillables.ts @@ -6,6 +6,7 @@ import { nexUniqueDrops } from '../data/CollectionsExport'; import { chanceOfDOAUnique, pickUniqueToGiveUser } from '../depthsOfAtlantis'; import { MoktangLootTable } from '../minions/data/killableMonsters/custom/bosses/Moktang'; import { NEX_UNIQUE_DROPRATE, nexLootTable } from '../nex'; +import { zygomiteFarmingSource } from '../skilling/skills/farming/zygomites'; import { roll } from '../util'; import { WintertodtCrate } from './wintertodt'; @@ -102,5 +103,16 @@ export const simulatedKillables: SimulatedKillable[] = [ loot: (quantity: number) => { return MoktangLootTable.roll(quantity); } - } + }, + ...zygomiteFarmingSource.map(src => ({ + name: src.name, + isCustom: true, + loot: (quantity: number) => { + let loot = new Bank(); + for (let i = 0; i < quantity; i++) { + loot.add(src.lootTable.roll()); + } + return loot; + } + })) ]; diff --git a/src/lib/skilling/skills/farming/index.ts b/src/lib/skilling/skills/farming/index.ts index 5fce8dd6e4..2ae9252800 100644 --- a/src/lib/skilling/skills/farming/index.ts +++ b/src/lib/skilling/skills/farming/index.ts @@ -12,6 +12,7 @@ import herbPlants from './herbPlants'; import hopsPlants from './hops'; import specialPlants from './specialPlants'; import trees from './trees'; +import { zygomitePlants } from './zygomites'; export const plants: Plant[] = [ ...herbPlants, @@ -20,7 +21,8 @@ export const plants: Plant[] = [ ...fruitTrees, ...hopsPlants, ...specialPlants, - ...bushes + ...bushes, + ...zygomitePlants ]; const maleFarmerItems: { [key: number]: number } = { diff --git a/src/lib/skilling/skills/farming/zygomites.ts b/src/lib/skilling/skills/farming/zygomites.ts new file mode 100644 index 0000000000..e0ca673b39 --- /dev/null +++ b/src/lib/skilling/skills/farming/zygomites.ts @@ -0,0 +1,84 @@ +import { roll } from 'e'; +import { Bank, LootTable } from 'oldschooljs'; + +import { MysteryBoxes } from '../../../bsoOpenables'; +import getOSItem from '../../../util/getOSItem'; +import resolveItems from '../../../util/resolveItems'; +import { Plant } from '../../types'; + +export const zygomiteFarmingSource = [ + { + name: 'Herbal zygomite', + mutatedFromItems: resolveItems(['Torstol seed', 'Dwarf weed seed', 'Lantadyme seed']), + seedItem: getOSItem('Herbal zygomite spores'), + lootTable: new LootTable() + .every( + new LootTable().add('Mushroom spore').add('Mort myre fungus', [40, 100]).add('Mushroom', [20, 50]), + 5 + ) + .every(new LootTable().add('Torstol').add('Dwarf weed').add('Cadantine').add('Kwuarm'), [40, 100]) + .every(MysteryBoxes) + }, + { + name: 'Barky zygomite', + mutatedFromItems: resolveItems(['Magic seed', 'Redwood tree seed']), + seedItem: getOSItem('Barky zygomite spores'), + lootTable: new LootTable() + .every( + new LootTable().add('Mushroom spore').add('Mort myre fungus', [40, 100]).add('Mushroom', [20, 50]), + 5 + ) + .every(new LootTable().add('Elder logs').add('Mahogany logs'), [50, 100]) + .every(MysteryBoxes) + }, + { + name: 'Fruity zygomite', + mutatedFromItems: resolveItems(['Dragonfruit tree seed', 'Palm tree seed', 'Papaya tree seed']), + seedItem: getOSItem('Fruity zygomite spores'), + lootTable: new LootTable() + .every( + new LootTable().add('Mushroom spore').add('Mort myre fungus', [40, 100]).add('Mushroom', [20, 50]), + 5 + ) + .every(new LootTable().add('Avocado').add('Mango').add('Papaya fruit').add('Lychee'), [50, 150]) + .every(MysteryBoxes) + } +]; + +export const zygomitePlants: Plant[] = zygomiteFarmingSource.map(src => ({ + id: src.seedItem.id, + level: 105, + plantXp: 181.5, + checkXp: 0, + harvestXp: 927.7, + inputItems: new Bank().add(src.seedItem.id), + name: src.name, + aliases: [src.name.toLowerCase()], + petChance: 7500, + seedType: 'mushroom' as const, + growthTime: 240, + numOfStages: 6, + chance1: 0, + chance99: 0, + chanceOfDeath: 5, + needsChopForHarvest: false, + fixedOutput: true, + fixedOutputAmount: 6, + givesLogs: false, + givesCrops: true, + defaultNumOfPatches: 0, + canPayFarmer: false, + canCompostPatch: true, + canCompostandPay: false, + additionalPatchesByQP: [[1, 1]], + additionalPatchesByFarmLvl: [], + additionalPatchesByFarmGuildAndLvl: [], + timePerPatchTravel: 10, + timePerHarvest: 5, + onHarvest: ({ loot }) => { + loot.add(src.lootTable.roll()); + if (roll(1000)) { + loot.add('Fungo'); + } + } +})); diff --git a/src/lib/skilling/types.ts b/src/lib/skilling/types.ts index 410ae82748..b51cb9da45 100644 --- a/src/lib/skilling/types.ts +++ b/src/lib/skilling/types.ts @@ -277,6 +277,7 @@ export interface Plant { additionalPatchesByFarmGuildAndLvl: number[][]; timePerPatchTravel: number; timePerHarvest: number; + onHarvest?: (options: { user: MUser; loot: Bank }) => unknown; } export enum HunterTechniqueEnum { diff --git a/src/lib/util/handleTripFinish.ts b/src/lib/util/handleTripFinish.ts index c8abebad65..87219eab8e 100644 --- a/src/lib/util/handleTripFinish.ts +++ b/src/lib/util/handleTripFinish.ts @@ -1,7 +1,7 @@ import { mentionCommand } from '@oldschoolgg/toolkit'; import { activity_type_enum } from '@prisma/client'; import { AttachmentBuilder, bold, ButtonBuilder, MessageCollector, MessageCreateOptions } from 'discord.js'; -import { notEmpty, randArrItem, randInt, reduceNumByPercent, roll, Time } from 'e'; +import { notEmpty, randArrItem, randInt, reduceNumByPercent, roll, shuffleArr, Time } from 'e'; import { Bank } from 'oldschooljs'; import { alching } from '../../mahoji/commands/laps'; @@ -23,6 +23,7 @@ import { mysteriousStepData } from '../mysteryTrail'; import { triggerRandomEvent } from '../randomEvents'; import { RuneTable, WilvusTable, WoodTable } from '../simulation/seedTable'; import { DougTable, PekyTable } from '../simulation/sharedTables'; +import { zygomiteFarmingSource } from '../skilling/skills/farming/zygomites'; import { SkillsEnum } from '../skilling/types'; import { getUsersCurrentSlayerInfo } from '../slayer/slayerUtil'; import { ActivityTaskData } from '../types/minions'; @@ -426,6 +427,46 @@ const tripFinishEffects: TripFinishEffect[] = [ ); } } + }, + { + name: 'Moonlight mutator', + fn: async ({ data, user, messages }) => { + if (!user.bank.has('Moonlight mutator')) return; + if (user.user.disabled_inventions.includes(InventionID.MoonlightMutator)) return; + const randomZyg = randArrItem(zygomiteFarmingSource); + const loot = new Bank(); + const cost = new Bank(); + + const minutes = Math.floor(data.duration / Time.Minute); + if (minutes < 1) return; + for (let i = 0; i < minutes; i++) { + if (roll(766)) { + loot.add(randomZyg.seedItem); + } + if (roll(10)) { + const ownedSeed = shuffleArr(randomZyg.mutatedFromItems).find(seed => user.bank.has(seed)); + cost.add(ownedSeed); + } + } + + if (cost.length > 0 || loot.length > 0) { + if (cost.length > 0 && !user.bank.has(cost)) { + console.error(`User ${user.id} doesn't ML ${cost.toString()}`); + return; + } + await user.transactItems({ + itemsToRemove: cost, + itemsToAdd: loot, + collectionLog: true + }); + + if (cost.length > 0 && loot.length === 0) { + messages.push(`<:moonlightMutator:1212761364372783176> Mutated ${cost} seeds, but all died`); + } else if (loot.length > 0) { + messages.push(`<:moonlightMutator:1212761364372783176> Mutated ${cost} seeds, ${loot} survived`); + } + } + } } ]; diff --git a/src/mahoji/lib/abstracted_commands/farmingCommand.ts b/src/mahoji/lib/abstracted_commands/farmingCommand.ts index 6028517e6f..3331e9cee8 100644 --- a/src/mahoji/lib/abstracted_commands/farmingCommand.ts +++ b/src/mahoji/lib/abstracted_commands/farmingCommand.ts @@ -1,9 +1,10 @@ import { CropUpgradeType } from '@prisma/client'; import { ChatInputCommandInteraction } from 'discord.js'; -import { percentChance, Time } from 'e'; +import { percentChance, reduceNumByPercent, Time } from 'e'; import { Bank } from 'oldschooljs'; import { Item } from 'oldschooljs/dist/meta/types'; +import { BitField } from '../../../lib/constants'; import { superCompostables } from '../../../lib/data/filterables'; import { ArdougneDiary, userhasDiaryTier } from '../../../lib/diaries'; import { prisma } from '../../../lib/settings/prisma'; @@ -84,6 +85,11 @@ export async function harvestCommand({ duration *= 0.9; } + if (user.bitfield.includes(BitField.HasMoondashCharm)) { + boostStr.push('25% faster for Moondash charm'); + duration = reduceNumByPercent(duration, 25); + } + const maxTripLength = calcMaxTripLength(user, 'Farming'); if (duration > maxTripLength) { @@ -220,6 +226,11 @@ export async function farmingPlantCommand({ duration *= 0.9; } + if (user.bitfield.includes(BitField.HasMoondashCharm)) { + boostStr.push('25% faster for Moondash charm'); + duration = reduceNumByPercent(duration, 25); + } + for (const [diary, tier] of [[ArdougneDiary, ArdougneDiary.elite]] as const) { const [has] = await userhasDiaryTier(user, tier); if (has) { diff --git a/src/mahoji/lib/abstracted_commands/useCommand.ts b/src/mahoji/lib/abstracted_commands/useCommand.ts index 5a3f508310..f2307a99e5 100644 --- a/src/mahoji/lib/abstracted_commands/useCommand.ts +++ b/src/mahoji/lib/abstracted_commands/useCommand.ts @@ -133,6 +133,11 @@ const usableUnlocks: UsableUnlock[] = [ bitfield: BitField.UsedStrangledTablet, resultMessage: 'You used your Strangled tablet.' }, + { + item: getOSItem('Moondash charm'), + bitfield: BitField.HasMoondashCharm, + resultMessage: 'You used your Moondash charm, the power of the moon now boosts your farming trips.' + }, ...divinationEnergies .filter(e => e.boonBitfield !== null) .map(e => ({ diff --git a/src/tasks/minions/farmingActivity.ts b/src/tasks/minions/farmingActivity.ts index 59b8ce2d21..552402f0cf 100644 --- a/src/tasks/minions/farmingActivity.ts +++ b/src/tasks/minions/farmingActivity.ts @@ -218,6 +218,9 @@ export const farmingTask: MinionTask = { })}`; await farmingLootBoosts(user, 'plant', plant, loot, infoStr); + if ('onHarvest' in plant && plant.onHarvest) { + plant.onHarvest({ user, loot }); + } if (loot.has('Plopper')) { loot.bank[itemID('Plopper')] = 1; @@ -569,6 +572,9 @@ export const farmingTask: MinionTask = { } await farmingLootBoosts(user, 'harvest', plantToHarvest, loot, infoStr); + if ('onHarvest' in plant && plant.onHarvest) { + plant.onHarvest({ user, loot }); + } if (plantToHarvest.name === 'Mysterious tree') { if (loot.has('Seed Pack')) {