From 882f61e0d92902c3b1776dfdc086983c74a077ec Mon Sep 17 00:00:00 2001 From: TastyPumPum <79149170+TastyPumPum@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:05:36 +0100 Subject: [PATCH] Add Crystal Impling Hunting (#5771) --- src/lib/skilling/functions/calcsHunter.ts | 26 ++++++++++++- .../hunter/creatures/butterflyNetting.ts | 12 ++++++ src/lib/util/activityInArea.ts | 7 ++++ src/mahoji/commands/hunt.ts | 37 ++++++++++++++---- .../minions/HunterActivity/hunterActivity.ts | 38 ++++++++++++++----- 5 files changed, 102 insertions(+), 18 deletions(-) diff --git a/src/lib/skilling/functions/calcsHunter.ts b/src/lib/skilling/functions/calcsHunter.ts index 5aa0acb99a..a1bc41fc86 100644 --- a/src/lib/skilling/functions/calcsHunter.ts +++ b/src/lib/skilling/functions/calcsHunter.ts @@ -1,3 +1,4 @@ +import { Time } from 'e'; import LootTable from 'oldschooljs/dist/structures/LootTable'; import { percentChance } from '../../util'; @@ -6,12 +7,33 @@ import { Creature } from '../types'; export function calcLootXPHunting( currentLevel: number, creature: Creature, - quantity: number + quantity: number, + usingStaminaPotion: boolean, + graceful: boolean, + experienceScore: number ): [number, number, number] { let xpReceived = 0; let successful = 0; - const chanceOfSuccess = creature.slope * currentLevel + creature.intercept; + let chanceOfSuccess = creature.slope * currentLevel + creature.intercept; + + if (creature.name === 'Crystal impling') { + chanceOfSuccess = 20; + if (graceful) { + chanceOfSuccess *= 1.05; + } + if (usingStaminaPotion) { + chanceOfSuccess *= 1.2; + } + + const timeInSeconds = creature.catchTime * Time.Second; + const experienceFactor = experienceScore / (Time.Hour / timeInSeconds); + + const maxPercentIncrease = 10; + let percentIncrease = Math.min(Math.floor(experienceFactor), maxPercentIncrease); + + chanceOfSuccess += chanceOfSuccess * (percentIncrease / 100); + } for (let i = 0; i < quantity; i++) { if (!percentChance(chanceOfSuccess)) { diff --git a/src/lib/skilling/skills/hunter/creatures/butterflyNetting.ts b/src/lib/skilling/skills/hunter/creatures/butterflyNetting.ts index 7aa6195fe0..db80eac199 100644 --- a/src/lib/skilling/skills/hunter/creatures/butterflyNetting.ts +++ b/src/lib/skilling/skills/hunter/creatures/butterflyNetting.ts @@ -55,6 +55,18 @@ const butterflyNettingCreatures: Creature[] = [ catchTime: 5, slope: 1.35, intercept: 35 + }, + { + name: 'Crystal impling', + id: 37, + aliases: ['cimp', 'crystal imp', 'c imp', 'crystal impling'], + level: 80, + hunterXP: 280, + table: new LootTable().every('Crystal impling jar'), + huntTechnique: HunterTechniqueEnum.ButterflyNetting, + catchTime: 180, + slope: 0, + intercept: 0 } ]; diff --git a/src/lib/util/activityInArea.ts b/src/lib/util/activityInArea.ts index f7f85db4b3..a22948ce69 100644 --- a/src/lib/util/activityInArea.ts +++ b/src/lib/util/activityInArea.ts @@ -4,6 +4,7 @@ import { soteSkillRequirements } from '../skilling/functions/questRequirements'; import { ActivityTaskData, AgilityActivityTaskOptions, + HunterActivityTaskOptions, MonsterActivityTaskOptions, PickpocketActivityTaskOptions, WoodcuttingActivityTaskOptions @@ -51,6 +52,12 @@ const WorldLocationsChecker = [ return true; } } + if ( + activity.type === 'Hunter' && + (activity as HunterActivityTaskOptions).creatureName === 'Crystal impling' + ) { + return true; + } return false; } diff --git a/src/mahoji/commands/hunt.ts b/src/mahoji/commands/hunt.ts index bf58e3836b..497ef474d3 100644 --- a/src/mahoji/commands/hunt.ts +++ b/src/mahoji/commands/hunt.ts @@ -6,12 +6,13 @@ import { Bank } from 'oldschooljs'; import { HERBIBOAR_ID, RAZOR_KEBBIT_ID } from '../../lib/constants'; import { hasWildyHuntGearEquipped } from '../../lib/gear/functions/hasWildyHuntGearEquipped'; import { trackLoot } from '../../lib/lootTrack'; +import { soteSkillRequirements } from '../../lib/skilling/functions/questRequirements'; import creatures from '../../lib/skilling/skills/hunter/creatures'; import Hunter from '../../lib/skilling/skills/hunter/hunter'; import { HunterTechniqueEnum, SkillsEnum } from '../../lib/skilling/types'; import { Peak } from '../../lib/tickers'; import { HunterActivityTaskOptions } from '../../lib/types/minions'; -import { formatDuration, itemID } from '../../lib/util'; +import { formatDuration, hasSkillReqs, itemID } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; @@ -118,6 +119,18 @@ export const huntCommand: OSBMahojiCommand = { } } + let crystalImpling = creature.name === 'Crystal impling'; + + if (crystalImpling) { + const [hasReqs, reason] = hasSkillReqs(user, soteSkillRequirements); + if (!hasReqs) { + return `To hunt ${creature.name}, you need: ${reason}.`; + } + if (user.QP < 150) { + return `To hunt ${creature.name}, you need 150 QP.`; + } + } + // Reduce time if user is experienced hunting the creature, every hour become 1% better to a cap of 10% or 20% if tracking technique. let [percentReduced, catchTime] = [ Math.min( @@ -158,10 +171,20 @@ export const huntCommand: OSBMahojiCommand = { const maxTripLength = calcMaxTripLength(user, 'Hunter'); let { quantity } = options; - if (!quantity) quantity = Math.floor(maxTripLength / ((catchTime * Time.Second) / traps)); + if (!quantity) { + if (crystalImpling) { + quantity = Math.floor(maxTripLength / Time.Minute); + } else { + quantity = Math.floor(maxTripLength / ((catchTime * Time.Second) / traps)); + } + } let duration = Math.floor(((quantity * catchTime) / traps) * Time.Second); + if (crystalImpling) { + duration = Math.floor(quantity * Time.Minute); + } + if (duration > maxTripLength) { return `${user.minionName} can't go on trips longer than ${formatDuration( maxTripLength @@ -188,9 +211,9 @@ export const huntCommand: OSBMahojiCommand = { // If creatures Herbiboar or Razor-backed kebbit use Stamina potion(4) if (usingStaminaPotion) { - if (creature.id === HERBIBOAR_ID || creature.id === RAZOR_KEBBIT_ID) { + if (creature.id === HERBIBOAR_ID || creature.id === RAZOR_KEBBIT_ID || crystalImpling) { let staminaPotionQuantity = - creature.id === HERBIBOAR_ID + creature.id === HERBIBOAR_ID || crystalImpling ? Math.round(duration / (9 * Time.Minute)) : Math.round(duration / (18 * Time.Minute)); @@ -256,9 +279,9 @@ export const huntCommand: OSBMahojiCommand = { type: 'Hunter' }); - let response = `${user.minionName} is now ${creature.huntTechnique} ${quantity}x ${ - creature.name - }, it'll take around ${formatDuration(duration)} to finish.`; + let response = `${user.minionName} is now ${crystalImpling ? 'hunting' : `${creature.huntTechnique}`} ${ + crystalImpling ? '' : ` ${quantity}x ` + }${creature.name}, it'll take around ${formatDuration(duration)} to finish.`; if (boosts.length > 0) { response += `\n\n**Boosts:** ${boosts.join(', ')}.`; diff --git a/src/tasks/minions/HunterActivity/hunterActivity.ts b/src/tasks/minions/HunterActivity/hunterActivity.ts index fb70b8191b..12351538e0 100644 --- a/src/tasks/minions/HunterActivity/hunterActivity.ts +++ b/src/tasks/minions/HunterActivity/hunterActivity.ts @@ -14,6 +14,7 @@ import { roll, skillingPetDropRate, stringMatches } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import itemID from '../../../lib/util/itemID'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; +import { userHasGracefulEquipped } from '../../../mahoji/mahojiSettings'; import { BLACK_CHIN_ID, HERBIBOAR_ID } from './../../../lib/constants'; const riskDeathNumbers = [ @@ -34,7 +35,8 @@ const riskDeathNumbers = [ export const hunterTask: MinionTask = { type: 'Hunter', async run(data: HunterActivityTaskOptions) { - const { creatureName, quantity, userID, channelID, usingHuntPotion, wildyPeak, duration } = data; + const { creatureName, quantity, userID, channelID, usingHuntPotion, wildyPeak, duration, usingStaminaPotion } = + data; const user = await mUserFetch(userID); const userBank = user.bank; const currentLevel = user.skillLevel(SkillsEnum.Hunter); @@ -53,12 +55,29 @@ export const hunterTask: MinionTask = { if (!creature) return; + let crystalImpling = creature.name === 'Crystal impling'; + + let graceful = false; + if (userHasGracefulEquipped(user)) { + graceful = true; + } + + const experienceScore = await user.getCreatureScore(creature.id); + let [successfulQuantity, xpReceived] = calcLootXPHunting( Math.min(Math.floor(currentLevel + (usingHuntPotion ? 2 : 0)), MAX_LEVEL), creature, - quantity + quantity, + usingStaminaPotion, + graceful, + experienceScore ); + if (crystalImpling) { + // Limit it to a max of 22 crystal implings per hour + successfulQuantity = Math.min(successfulQuantity, Math.round((21 / 60) * duration) + 1); + } + if (creature.wildy) { let riskPkChance = creature.id === BLACK_CHIN_ID ? 100 : 200; riskPkChance += @@ -136,6 +155,7 @@ export const hunterTask: MinionTask = { }); } } + const loot = new Bank(); for (let i = 0; i < successfulQuantity - pkedQuantity; i++) { loot.add(creatureTable.roll()); @@ -156,13 +176,13 @@ export const hunterTask: MinionTask = { duration }); - let str = `${user}, ${user.minionName} finished hunting ${ - creature.name - } ${quantity}x times, due to clever creatures you missed out on ${ - quantity - successfulQuantity - }x catches. ${xpStr}`; - - str += `\n\nYou received: ${loot}.${magicSecStr.length > 1 ? magicSecStr : ''}`; + let str = `${user}, ${user.minionName} finished hunting ${creature.name}${ + crystalImpling + ? '.' + : `${quantity}x times, due to clever creatures you missed out on ${ + quantity - successfulQuantity + }x catches. ` + }${xpStr}\n\nYou received: ${loot}.${magicSecStr.length > 1 ? magicSecStr : ''}`; if (gotPked && !died) { str += `\n${pkStr}`;