diff --git a/.github/workflows/codequality.yml b/.github/workflows/codequality.yml deleted file mode 100644 index dbc51150ef..0000000000 --- a/.github/workflows/codequality.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Code Quality - -on: - push: - branches: - - master - - bso - pull_request: - -jobs: - ESLint: - name: ESLint - runs-on: ubuntu-latest - steps: - - name: Checkout Project - uses: actions/checkout@v3 - - name: Use Node.js 18.12.0 - uses: actions/setup-node@v3 - with: - node-version: 18.12.0 - cache: yarn - - name: Restore CI Cache - uses: actions/cache@v3 - with: - path: node_modules - key: ${{ runner.os }}-18-${{ hashFiles('**/yarn.lock') }} - - name: Install Dependencies - run: yarn --frozen-lockfile - - name: Generate Prisma Client - run: yarn gen - - name: Run ESLint on changed files - uses: tj-actions/eslint-changed-files@v21 - with: - skip_annotations: true - config_path: ".eslintrc.json" - ignore_path: ".eslintignore" - file_extensions: | - **/*.ts - **/*.tsx - - Typescript: - name: Typescript - runs-on: ubuntu-latest - steps: - - name: Checkout Project - uses: actions/checkout@v3 - - name: Use Node.js 18.12.0 - uses: actions/setup-node@v3 - with: - node-version: 18.12.0 - cache: yarn - - name: Restore CI Cache - uses: actions/cache@v3 - with: - path: node_modules - key: ${{ runner.os }}-18-${{ hashFiles('**/yarn.lock') }} - - name: Install Dependencies - run: yarn --frozen-lockfile - - name: Copy Configuration - run: | - pushd src && - cp config.example.ts config.ts && - popd - - name: Copy env - run: cp .env.example .env - - name: Generate Prisma Client - run: yarn gen - - name: Build code - run: yarn build diff --git a/.github/workflows/inttests.yml b/.github/workflows/integration_tests.yml similarity index 100% rename from .github/workflows/inttests.yml rename to .github/workflows/integration_tests.yml diff --git a/.github/workflows/test.yml b/.github/workflows/unit_tests.yml similarity index 80% rename from .github/workflows/test.yml rename to .github/workflows/unit_tests.yml index 82a0ad2867..bdfa62cdbc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/unit_tests.yml @@ -40,6 +40,15 @@ jobs: run: cp .env.example .env - name: Generate Prisma Client run: yarn gen + - name: Run ESLint on changed files + uses: tj-actions/eslint-changed-files@v21 + with: + skip_annotations: true + config_path: ".eslintrc.json" + ignore_path: ".eslintignore" + file_extensions: | + **/*.ts + **/*.tsx - name: Build run: yarn build - name: Test diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 0cb13883c3..b47ed3808d 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -295,6 +295,8 @@ export enum BitField { DisabledFarmingReminders = 37, DisableClueButtons = 38, DisableAutoSlayButton = 39, + DisableHighPeakTimeWarning = 40, + HasGivenBirthdayPack = 200, HasPermanentSpawnLamp = 201, HasScrollOfFarming = 202, @@ -521,6 +523,11 @@ export const BitFieldData: Record = { name: 'Disable Eagle Tame Opening Caskets', protected: false, userConfigurable: true + }, + [BitField.DisableHighPeakTimeWarning]: { + name: 'Disable Wilderness High Peak Time Warning', + protected: false, + userConfigurable: true } } as const; diff --git a/src/lib/data/Collections.ts b/src/lib/data/Collections.ts index 53e8a66f81..1f54e2796c 100644 --- a/src/lib/data/Collections.ts +++ b/src/lib/data/Collections.ts @@ -1100,7 +1100,7 @@ export const allCollectionLogs: ICollection = { "Shades of Mort'ton": { items: shadesOfMorttonCL, isActivity: true, - fmtProg: () => '0 KC' + fmtProg: mgProg('shades_of_morton') }, 'Soul Wars': { alias: ['soul wars', 'sw'], diff --git a/src/lib/data/cox.ts b/src/lib/data/cox.ts index 443bea8237..217e11296c 100644 --- a/src/lib/data/cox.ts +++ b/src/lib/data/cox.ts @@ -265,10 +265,10 @@ export async function checkCoxTeam(users: MUser[], cm: boolean, quantity: number for (const user of users) { const { total } = calculateUserGearPercents(user); if (total < 20) { - return "Your gear is terrible! You do not stand a chance in the Chamber's of Xeric."; + return 'Your gear is terrible! You do not stand a chance in the Chambers of Xeric.'; } if (!hasMinRaidsRequirements(user)) { - return `${user.usernameOrMention} doesn't meet the stat requirements to do the Chamber's of Xeric.`; + return `${user.usernameOrMention} doesn't meet the stat requirements to do the Chambers of Xeric.`; } if (cm) { if (users.length === 1 && !user.hasEquippedOrInBank('Twisted bow')) { diff --git a/src/lib/degradeableItems.ts b/src/lib/degradeableItems.ts index 136d4bbe05..4146e60dbc 100644 --- a/src/lib/degradeableItems.ts +++ b/src/lib/degradeableItems.ts @@ -1,4 +1,4 @@ -import { Time } from 'e'; +import { percentChance, Time } from 'e'; import { Bank } from 'oldschooljs'; import { Item } from 'oldschooljs/dist/meta/types'; import Monster from 'oldschooljs/dist/structures/Monster'; @@ -227,7 +227,7 @@ export const degradeableItems: DegradeableItem[] = [ setup: 'melee', aliases: ['scythe of vitur'], chargeInput: { - cost: new Bank().add('Blood rune', 300).add('Vial of blood').freeze(), + cost: new Bank().add('Blood rune', 200).add('Vial of blood').freeze(), charges: 100 }, unchargedItem: getOSItem('Scythe of vitur (uncharged)'), @@ -329,6 +329,17 @@ export async function degradeItem({ const degItem = degradeableItems.find(i => i.item === item); if (!degItem) throw new Error('Invalid degradeable item'); + // 5% chance to not consume a charge when Ghommal's lucky penny is equipped + let pennyReduction = 0; + if (user.hasEquipped("Ghommal's lucky penny")) { + for (let i = 0; i < chargesToDegrade; i++) { + if (percentChance(5)) { + pennyReduction++; + } + } + } + chargesToDegrade -= pennyReduction; + const currentCharges = user.user[degItem.settingsKey]; assert(typeof currentCharges === 'number'); const newCharges = Math.floor(currentCharges - chargesToDegrade); @@ -385,7 +396,11 @@ export async function degradeItem({ const chargesAfter = user.user[degItem.settingsKey]; assert(typeof chargesAfter === 'number' && chargesAfter > 0); return { - userMessage: `Your ${item.name} degraded by ${chargesToDegrade} charges, and now has ${chargesAfter} remaining.` + userMessage: `Your ${ + item.name + } degraded by ${chargesToDegrade} charges, and now has ${chargesAfter} remaining.${ + pennyReduction > 0 ? ` Your Ghommal's lucky penny saved ${pennyReduction} charges` : '' + }` }; } diff --git a/src/lib/randomEvents.ts b/src/lib/randomEvents.ts index b8bd5818d6..5b836dedd0 100644 --- a/src/lib/randomEvents.ts +++ b/src/lib/randomEvents.ts @@ -39,18 +39,36 @@ export const zombieOutfit = resolveItems([ export const mimeOutfit = resolveItems(['Mime mask', 'Mime top', 'Mime legs', 'Mime gloves', 'Mime boots']); +// Used by Mysterious Old man, Pillory, Rick Turpentine +const randomEventTable = new LootTable() + .add('Uncut sapphire', 1, 32) + .add('Uncut emerald', 1, 16) + .add('Coins', 80, 10) + .add('Coins', 160, 10) + .add('Coins', 320, 10) + .add('Coins', 480, 10) + .add('Coins', 640, 10) + .add('Uncut ruby', 1, 8) + .add('Coins', 240, 6) + .add('Cosmic talisman', 1, 4) + .add('Uncut diamond', 2, 2) + .add('Tooth half of key', 1, 1) + .add('Tooth half of key', 1, 1); + +// https://oldschool.runescape.wiki/w/Random_events#List_of_random_events +// Missing: Evil Bob, Jekyll and Hyde, Maze, Prison Pete export const RandomEvents: RandomEvent[] = [ { id: 1, name: 'Bee keeper', outfit: beekeeperOutfit, - loot: new LootTable().add('Coins', [20, 60]).add('Flax', [1, 27]) + loot: new LootTable().add('Coins', [16, 36]).add('Flax', [1, 27]) }, { id: 2, name: 'Drill Demon', outfit: camoOutfit, - loot: new LootTable().every('Coins', 500) + loot: new LootTable().every('Genie lamp') }, { id: 3, @@ -60,8 +78,8 @@ export const RandomEvents: RandomEvent[] = [ { id: 4, name: 'Freaky Forester', - loot: new LootTable().every('Coins', 500), - outfit: lederhosenOutfit + outfit: lederhosenOutfit, + loot: new LootTable().every('Genie lamp') }, { id: 5, @@ -71,8 +89,8 @@ export const RandomEvents: RandomEvent[] = [ { id: 6, name: 'Gravedigger', - loot: new LootTable().every('Coins', 500), - outfit: zombieOutfit + outfit: zombieOutfit, + loot: new LootTable().every('Genie lamp') }, { id: 7, @@ -82,8 +100,8 @@ export const RandomEvents: RandomEvent[] = [ { id: 8, name: 'Mime', - loot: new LootTable().every('Coins', 500), - outfit: mimeOutfit + outfit: mimeOutfit, + loot: new LootTable().every('Genie lamp') }, { id: 9, @@ -129,6 +147,45 @@ export const RandomEvents: RandomEvent[] = [ .add('Spinach roll') .add('Tooth half of key') .add('Loop half of key') + }, + { + id: 14, + name: 'Count Check', + loot: new LootTable().every('Genie lamp') + }, + { + id: 15, + name: 'Evil twin', + loot: new LootTable() + .add('Uncut sapphire', [2, 4]) + .add('Uncut emerald', [2, 4]) + .add('Uncut ruby', [2, 4]) + .add('Uncut diamond', [2, 4]) + }, + { + id: 16, + name: 'Mysterious Old Man', + loot: randomEventTable.add('Kebab', 1, 16).add('Spinach roll', 1, 14) + }, + { + id: 17, + name: 'Pillory', + loot: randomEventTable + }, + { + id: 18, + name: 'Pinball', + loot: new LootTable().add('Sapphire', 5, 3).add('Emerald', 5, 3).add('Ruby', 5, 3).add('Diamond', 2, 1) + }, + { + id: 19, + name: 'Rick Turpentine', + loot: randomEventTable.add('Kebab', 1, 16).add('Spinach roll', 1, 14) + }, + { + id: 20, + name: 'Strange plant', + loot: new LootTable().every('Strange fruit') } ]; diff --git a/src/lib/settings/minigames.ts b/src/lib/settings/minigames.ts index 5300b3b0a6..62fd5451a9 100644 --- a/src/lib/settings/minigames.ts +++ b/src/lib/settings/minigames.ts @@ -97,12 +97,12 @@ export const Minigames: readonly BotMinigame[] = [ column: 'castle_wars' }, { - name: "Chamber's of Xeric", + name: 'Chambers of Xeric', aliases: ['cox', 'raid1', 'raids1', 'chambers', 'xeric'], column: 'raids' }, { - name: "Chamber's of Xeric - Challenge Mode", + name: 'Chambers of Xeric - Challenge Mode', aliases: ['coxcm', 'raid1cm', 'raids1cm', 'chamberscm', 'xericcm'], column: 'raids_challenge_mode' }, diff --git a/src/lib/simulation/tob.ts b/src/lib/simulation/tob.ts index 1c789f1fd1..6993e2a0da 100644 --- a/src/lib/simulation/tob.ts +++ b/src/lib/simulation/tob.ts @@ -4,6 +4,7 @@ import { Bank, LootTable } from 'oldschooljs'; import { LootBank } from 'oldschooljs/dist/meta/types'; import { convertLootBanksToItemBanks, JSONClone } from 'oldschooljs/dist/util'; +import { BOT_TYPE } from '../constants'; import { TOBRooms } from '../data/tob'; import { assert } from '../util/logError'; @@ -50,7 +51,6 @@ const HardModeUniqueTable = new LootTable() .add('Avernic defender hilt', 1, 7); const NonUniqueTable = new LootTable() - .tertiary(25, 'Clue scroll (elite)') .add('Vial of blood', [50, 60], 2) .add('Death rune', [500, 600]) .add('Blood rune', [500, 600]) @@ -93,6 +93,10 @@ export class TheatreOfBloodClass { if (deaths.length === TOBRooms.length) { return new Bank().add('Cabbage'); } + + if (BOT_TYPE === 'BSO') { + NonUniqueTable.tertiary(25, 'Clue scroll (elite)'); + } const loot = new Bank(); for (let i = 0; i < 3; i++) { loot.add(NonUniqueTable.roll()); @@ -106,6 +110,13 @@ export class TheatreOfBloodClass { // Add HM Tertiary drops: dust / kits loot.add(HardModeExtraTable.roll()); } + + if (BOT_TYPE === 'OSB') { + if (roll(25)) { + loot.add('Clue scroll (elite)'); + } + } + let petChance = isHardMode ? 500 : 650; if (member.numDeaths > 0) { petChance *= member.numDeaths; diff --git a/src/lib/slayer/tasks/chaeldarTasks.ts b/src/lib/slayer/tasks/chaeldarTasks.ts index 17de6e428e..95dc46c321 100644 --- a/src/lib/slayer/tasks/chaeldarTasks.ts +++ b/src/lib/slayer/tasks/chaeldarTasks.ts @@ -442,20 +442,6 @@ export const chaeldarTasks: AssignableSlayerTask[] = [ unlocked: true, dontAssign: true }, - { - monster: Monsters.SpiritualRanger, - amount: [110, 170], - - weight: 12, - monsters: [Monsters.SpiritualRanger.id, Monsters.SpiritualWarrior.id, Monsters.SpiritualMage.id], - levelRequirements: { - slayer: 60 - }, - combatLevel: 60, - slayerLevel: 63, - questPoints: 3, - unlocked: true - }, { monster: Monsters.MountainTroll, amount: [110, 170], diff --git a/src/lib/slayer/tasks/duradelTasks.ts b/src/lib/slayer/tasks/duradelTasks.ts index 421bce2851..72c24bb45f 100644 --- a/src/lib/slayer/tasks/duradelTasks.ts +++ b/src/lib/slayer/tasks/duradelTasks.ts @@ -385,7 +385,7 @@ export const duradelTasks: AssignableSlayerTask[] = [ monster: Monsters.SpiritualMage, amount: [110, 170], - weight: 12, + weight: 7, monsters: [Monsters.SpiritualRanger.id, Monsters.SpiritualWarrior.id, Monsters.SpiritualMage.id], extendedAmount: [180, 250], extendedUnlockId: SlayerTaskUnlocksEnum.SpiritualFervour, @@ -398,21 +398,6 @@ export const duradelTasks: AssignableSlayerTask[] = [ unlocked: true, dontAssign: true }, - { - monster: Monsters.SpiritualRanger, - amount: [130, 200], - weight: 7, - monsters: [Monsters.SpiritualRanger.id, Monsters.SpiritualWarrior.id, Monsters.SpiritualMage.id], - extendedAmount: [180, 250], - extendedUnlockId: SlayerTaskUnlocksEnum.SpiritualFervour, - levelRequirements: { - slayer: 60 - }, - combatLevel: 60, - slayerLevel: 63, - questPoints: 3, - unlocked: true - }, { monster: Monsters.SteelDragon, amount: [10, 20], diff --git a/src/lib/slayer/tasks/index.ts b/src/lib/slayer/tasks/index.ts index a514c6c844..b33c3dccc0 100644 --- a/src/lib/slayer/tasks/index.ts +++ b/src/lib/slayer/tasks/index.ts @@ -18,3 +18,5 @@ export const allSlayerTasks: AssignableSlayerTask[] = [ ...vannakaTasks, ...duradelTasks ]; + +export const allSlayerMonsters = allSlayerTasks.map(m => m.monster); diff --git a/src/lib/slayer/tasks/konarTasks.ts b/src/lib/slayer/tasks/konarTasks.ts index 4061b8b059..f754314bc5 100644 --- a/src/lib/slayer/tasks/konarTasks.ts +++ b/src/lib/slayer/tasks/konarTasks.ts @@ -115,7 +115,7 @@ export const konarTasks: AssignableSlayerTask[] = [ { monster: Monsters.BlueDragon, amount: [120, 170], - weight: 8, + weight: 4, monsters: [Monsters.BlueDragon.id, Monsters.BabyBlueDragon.id, Monsters.BrutalBlueDragon.id], combatLevel: 65, questPoints: 34, @@ -214,7 +214,7 @@ export const konarTasks: AssignableSlayerTask[] = [ monster: Monsters.FossilIslandWyvernSpitting, amount: [15, 30], - weight: 9, + weight: 5, monsters: [ Monsters.FossilIslandWyvernAncient.id, Monsters.FossilIslandWyvernLongTailed.id, @@ -392,7 +392,7 @@ export const konarTasks: AssignableSlayerTask[] = [ { monster: Monsters.SteelDragon, amount: [30, 50], - weight: 7, + weight: 5, monsters: [Monsters.SteelDragon.id], levelRequirements: killableMonsters.find(k => k.id === Monsters.SteelDragon.id)!.levelRequirements, extendedAmount: [40, 60], diff --git a/src/lib/slayer/tasks/mazchnaTasks.ts b/src/lib/slayer/tasks/mazchnaTasks.ts index 480d301439..3a9385395d 100644 --- a/src/lib/slayer/tasks/mazchnaTasks.ts +++ b/src/lib/slayer/tasks/mazchnaTasks.ts @@ -90,14 +90,6 @@ export const mazchnaTasks: AssignableSlayerTask[] = [ questPoints: 1, unlocked: true }, - { - monster: Monsters.Lizard, - amount: [40, 70], - weight: 8, - monsters: [Monsters.Lizard.id, Monsters.SmallLizard.id, Monsters.DesertLizard.id, Monsters.SulphurLizard.id], - slayerLevel: 22, - unlocked: true - }, { monster: Monsters.GuardDog, amount: [40, 70], @@ -188,6 +180,14 @@ export const mazchnaTasks: AssignableSlayerTask[] = [ questPoints: 4, unlocked: true }, + { + monster: Monsters.Lizard, + amount: [40, 70], + weight: 8, + monsters: [Monsters.Lizard.id, Monsters.SmallLizard.id, Monsters.DesertLizard.id, Monsters.SulphurLizard.id], + slayerLevel: 22, + unlocked: true + }, { monster: Monsters.Mogre, amount: [40, 70], diff --git a/src/lib/slayer/tasks/nieveTasks.ts b/src/lib/slayer/tasks/nieveTasks.ts index ea5c082f1b..7e2f3d0881 100644 --- a/src/lib/slayer/tasks/nieveTasks.ts +++ b/src/lib/slayer/tasks/nieveTasks.ts @@ -365,7 +365,7 @@ export const nieveTasks: AssignableSlayerTask[] = [ monster: Monsters.SpiritualMage, amount: [110, 170], - weight: 12, + weight: 6, monsters: [Monsters.SpiritualRanger.id, Monsters.SpiritualWarrior.id, Monsters.SpiritualMage.id], levelRequirements: { slayer: 60 @@ -376,19 +376,6 @@ export const nieveTasks: AssignableSlayerTask[] = [ unlocked: true, dontAssign: true }, - { - monster: Monsters.SpiritualRanger, - amount: [120, 185], - weight: 6, - monsters: [Monsters.SpiritualRanger.id, Monsters.SpiritualWarrior.id, Monsters.SpiritualMage.id], - levelRequirements: { - slayer: 60 - }, - combatLevel: 60, - slayerLevel: 63, - questPoints: 3, - unlocked: true - }, { monster: Monsters.SteelDragon, amount: [30, 60], diff --git a/src/lib/slayer/tasks/turaelTasks.ts b/src/lib/slayer/tasks/turaelTasks.ts index e53aa500fd..1a87e15b75 100644 --- a/src/lib/slayer/tasks/turaelTasks.ts +++ b/src/lib/slayer/tasks/turaelTasks.ts @@ -22,6 +22,20 @@ export const turaelTasks: AssignableSlayerTask[] = [ combatLevel: 5, unlocked: true }, + { + monster: Monsters.BlackBear, + amount: [15, 50], + weight: 7, + monsters: [ + Monsters.BlackBear.id, + Monsters.GrizzlyBearCub.id, + Monsters.BearCub.id, + Monsters.GrizzlyBear.id, + Monsters.Callisto.id + ], + combatLevel: 13, + unlocked: true + }, { monster: Monsters.Bird, amount: [15, 50], @@ -39,20 +53,6 @@ export const turaelTasks: AssignableSlayerTask[] = [ ], unlocked: true }, - { - monster: Monsters.BlackBear, - amount: [15, 50], - weight: 7, - monsters: [ - Monsters.BlackBear.id, - Monsters.GrizzlyBearCub.id, - Monsters.BearCub.id, - Monsters.GrizzlyBear.id, - Monsters.Callisto.id - ], - combatLevel: 13, - unlocked: true - }, { monster: Monsters.CaveBug, amount: [10, 20], diff --git a/src/lib/slayer/tasks/vannakaTasks.ts b/src/lib/slayer/tasks/vannakaTasks.ts index 790bfc251e..416ca8e5f0 100644 --- a/src/lib/slayer/tasks/vannakaTasks.ts +++ b/src/lib/slayer/tasks/vannakaTasks.ts @@ -484,7 +484,7 @@ export const vannakaTasks: AssignableSlayerTask[] = [ monster: Monsters.SpiritualMage, amount: [110, 170], - weight: 12, + weight: 8, monsters: [Monsters.SpiritualRanger.id, Monsters.SpiritualWarrior.id, Monsters.SpiritualMage.id], levelRequirements: { slayer: 60 @@ -495,19 +495,6 @@ export const vannakaTasks: AssignableSlayerTask[] = [ unlocked: true, dontAssign: true }, - { - monster: Monsters.SpiritualRanger, - amount: [60, 120], - weight: 8, - monsters: [Monsters.SpiritualRanger.id, Monsters.SpiritualWarrior.id, Monsters.SpiritualMage.id], - levelRequirements: { - slayer: 60 - }, - combatLevel: 60, - slayerLevel: 63, - questPoints: 3, - unlocked: true - }, { monster: Monsters.TerrorDog, amount: [20, 45], diff --git a/src/lib/util/minionStatus.ts b/src/lib/util/minionStatus.ts index 5459a5db92..c4bd59cfb9 100644 --- a/src/lib/util/minionStatus.ts +++ b/src/lib/util/minionStatus.ts @@ -532,7 +532,7 @@ export function minionStatus(user: MUser) { case 'Raids': { const data = currentTask as RaidsOptions; - return `${name} is currently doing the Chamber's of Xeric${ + return `${name} is currently doing the Chambers of Xeric${ data.challengeMode ? ' in Challenge Mode' : '' }, ${ data.users.length === 1 ? 'as a solo.' : `with a team of ${data.users.length} minions.` diff --git a/src/lib/util/repeatStoredTrip.ts b/src/lib/util/repeatStoredTrip.ts index ea3ed00b84..6d27d9243a 100644 --- a/src/lib/util/repeatStoredTrip.ts +++ b/src/lib/util/repeatStoredTrip.ts @@ -88,7 +88,9 @@ export const taskCanBeRepeated = (activity: Activity) => { activity_type_enum.Birdhouse, activity_type_enum.HalloweenMiniMinigame, activity_type_enum.BalthazarsBigBonanza, - activity_type_enum.GuthixianCache + activity_type_enum.GuthixianCache, + activity_type_enum.Birdhouse, + activity_type_enum.StrongholdOfSecurity ] as activity_type_enum[] ).includes(activity.type); }; diff --git a/src/lib/util/smallUtils.ts b/src/lib/util/smallUtils.ts index dfaac34f54..7f6cd87102 100644 --- a/src/lib/util/smallUtils.ts +++ b/src/lib/util/smallUtils.ts @@ -371,3 +371,10 @@ export function calculateAverageTimeForSuccess(probabilityPercent: number, timeF let averageTimeUntilSuccessMilliseconds = timeFrameMilliseconds / probabilityOfSuccess; return averageTimeUntilSuccessMilliseconds; } + +export function ellipsize(str: string, maxLen: number = 2000) { + if (str.length > maxLen) { + return `${str.substring(0, maxLen - 3)}...`; + } + return str; +} diff --git a/src/mahoji/commands/ca.ts b/src/mahoji/commands/ca.ts index 4ad29b3f41..eb1c298a1c 100644 --- a/src/mahoji/commands/ca.ts +++ b/src/mahoji/commands/ca.ts @@ -140,7 +140,7 @@ export const caCommand: OSBMahojiCommand = { if (selectedMonster) { const tasksForSelectedMonster = allCombatAchievementTasks.filter( - task => task.monster === selectedMonster + task => task.monster.toLowerCase() === selectedMonster!.toLowerCase() ); if (tasksForSelectedMonster.length === 0) diff --git a/src/mahoji/commands/cl.ts b/src/mahoji/commands/cl.ts index 1745a29b3f..0271c0b396 100644 --- a/src/mahoji/commands/cl.ts +++ b/src/mahoji/commands/cl.ts @@ -40,7 +40,7 @@ export const collectionLogCommand: OSBMahojiCommand = { ]; }) .flat(3) - ].filter(i => (!value ? true : i.name.toLowerCase().includes(value))); + ].filter(i => (!value ? true : i.name.toLowerCase().includes(value.toLowerCase()))); } }, { diff --git a/src/mahoji/commands/config.ts b/src/mahoji/commands/config.ts index ddf98f0e05..1754c92eed 100644 --- a/src/mahoji/commands/config.ts +++ b/src/mahoji/commands/config.ts @@ -146,6 +146,10 @@ const toggles: UserConfigToggle[] = [ { name: 'Disable Clue Buttons', bit: BitField.DisableClueButtons + }, + { + name: 'Disable wilderness high peak time warning', + bit: BitField.DisableHighPeakTimeWarning } ]; diff --git a/src/mahoji/commands/drop.ts b/src/mahoji/commands/drop.ts index 7a1b9529ff..7821288b85 100644 --- a/src/mahoji/commands/drop.ts +++ b/src/mahoji/commands/drop.ts @@ -1,7 +1,7 @@ import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { ClueTiers } from '../../lib/clues/clueTiers'; -import { itemNameFromID } from '../../lib/util'; +import { ellipsize, itemNameFromID, returnStringOrFile } from '../../lib/util'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { parseBank } from '../../lib/util/parseStringBank'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; @@ -70,23 +70,25 @@ export const dropCommand: OSBMahojiCommand = { ].flat(1); const doubleCheckItems = itemsToDoubleCheck.filter(f => bank.has(f)); + await handleMahojiConfirmation( + interaction, + `${user}, are you sure you want to drop ${ellipsize( + bank.toString(), + 1800 + )}? This is irreversible, and you will lose the items permanently.` + ); if (doubleCheckItems.length > 0) { await handleMahojiConfirmation( interaction, - `${user}, some of the items you are dropping look valuable, are you *really* sure you want to drop them? **${doubleCheckItems + `${user}, some of the items you are dropping are on your **favorites** or look valuable, are you *really* sure you want to drop them?\n**${doubleCheckItems .map(itemNameFromID) - .join(', ')}**` - ); - } else { - await handleMahojiConfirmation( - interaction, - `${user}, are you sure you want to drop ${bank}? This is irreversible, and you will lose the items permanently.` + .join(', ')}**\n\nDropping: ${ellipsize(bank.toString(), 1000)}` ); } await user.removeItemsFromBank(bank); updateBankSetting('dropped_items', bank); - return `Dropped ${bank}.`; + return returnStringOrFile(`Dropped ${bank}.`); } }; diff --git a/src/mahoji/commands/ge.ts b/src/mahoji/commands/ge.ts index 60483c9aec..0cc8c203e3 100644 --- a/src/mahoji/commands/ge.ts +++ b/src/mahoji/commands/ge.ts @@ -392,11 +392,11 @@ The next buy limit reset is at: ${GrandExchange.getInterval().nextResetStr}, it return patronMsg(PerkTier.Four); } let result = await prisma.$queryRawUnsafe< - { quantity_bought: number; price_per_item_before_tax: number; created_at: Date }[] + { total_quantity_bought: number; average_price_per_item_before_tax: number; week: Date }[] >(`SELECT - sellTransactions.created_at, - sellTransactions.price_per_item_before_tax, - sellTransactions.quantity_bought + DATE_TRUNC('week', sellTransactions.created_at) AS week, + AVG(sellTransactions.price_per_item_before_tax) AS average_price_per_item_before_tax, + SUM(sellTransactions.quantity_bought) AS total_quantity_bought FROM ge_listing INNER JOIN @@ -407,15 +407,18 @@ AND ge_listing.cancelled_at IS NULL AND ge_listing.fulfilled_at IS NOT NULL +GROUP BY + week ORDER BY - sellTransactions.created_at ASC;`); - if (result.length < 2) return 'No price history found for that item.'; - if (result[0].price_per_item_before_tax <= 1_000_000) { - result = result.filter(i => i.quantity_bought > 1); + week ASC; +`); + if (result.length < 1) return 'No price history found for that item.'; + if (result[0].average_price_per_item_before_tax <= 1_000_000) { + result = result.filter(i => i.total_quantity_bought > 1); } const buffer = await lineChart( `Price History for ${item.name}`, - result.map(i => [new Date(i.created_at).toDateString(), i.price_per_item_before_tax]), + result.map(i => [new Date(i.week).toDateString(), i.average_price_per_item_before_tax]), val => val.toString(), val => val, false diff --git a/src/mahoji/commands/mix.ts b/src/mahoji/commands/mix.ts index e53e68b3fe..2ff20229d3 100644 --- a/src/mahoji/commands/mix.ts +++ b/src/mahoji/commands/mix.ts @@ -93,8 +93,10 @@ export const mixCommand: OSBMahojiCommand = { if ((zahur && mixableZahur) || (wesley && mixableWesley)) { timeToMixSingleItem = 0.000_001; - requiredItems.add('Coins', wesley ? 50 : 200); - cost = `decided to pay ${wesley ? 'Wesley 50' : 'Zahur 200'} gp for each item so they don't have to go`; + requiredItems.add('Coins', mixableWesley ? 50 : 200); + cost = `decided to pay ${ + mixableWesley ? 'Wesley 50' : 'Zahur 200' + } gp for each item so they don't have to go.`; } const boosts: string[] = []; diff --git a/src/mahoji/commands/rp.ts b/src/mahoji/commands/rp.ts index a233022d06..d99c0574d1 100644 --- a/src/mahoji/commands/rp.ts +++ b/src/mahoji/commands/rp.ts @@ -22,7 +22,7 @@ import { allPerkBitfields } from '../../lib/perkTiers'; import { prisma } from '../../lib/settings/prisma'; import { TeamLoot } from '../../lib/simulation/TeamLoot'; import { ItemBank } from '../../lib/types'; -import { dateFm, formatDuration } from '../../lib/util'; +import { dateFm, formatDuration, returnStringOrFile } from '../../lib/util'; import getOSItem from '../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { deferInteraction } from '../../lib/util/interactionReply'; @@ -79,6 +79,12 @@ export const rpCommand: OSBMahojiCommand = { name: 'force_comp_update', description: 'Force the top 100 completionist users to update their completion percentage.', options: [] + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'view_all_items', + description: 'View all item IDs present in banks/cls.', + options: [] } ] }, @@ -323,6 +329,7 @@ export const rpCommand: OSBMahojiCommand = { validate_ge?: {}; patreon_reset?: {}; force_comp_update?: {}; + view_all_items?: {}; }; player?: { givetgb?: { user: MahojiUserOption }; @@ -401,6 +408,18 @@ export const rpCommand: OSBMahojiCommand = { return 'Done.'; } + if (options.action?.view_all_items) { + const result = await prisma.$queryRawUnsafe< + { item_id: number }[] + >(`SELECT DISTINCT json_object_keys(bank)::int AS item_id +FROM users +UNION +SELECT DISTINCT jsonb_object_keys("collectionLogBank")::int AS item_id +FROM users +ORDER BY item_id ASC;`); + return returnStringOrFile(`[${result.map(i => i.item_id).join(',')}]`); + } + if (options.action?.patreon_reset) { const bitfieldsToRemove = [ BitField.IsPatronTier1, diff --git a/src/mahoji/commands/sell.ts b/src/mahoji/commands/sell.ts index 383b4d1b33..9e57ab8ebc 100644 --- a/src/mahoji/commands/sell.ts +++ b/src/mahoji/commands/sell.ts @@ -8,7 +8,7 @@ import { MAX_INT_JAVA } from '../../lib/constants'; import { customPrices } from '../../lib/customItems/util'; import { prisma } from '../../lib/settings/prisma'; import { NestBoxesTable } from '../../lib/simulation/misc'; -import { itemID, toKMB } from '../../lib/util'; +import { itemID, returnStringOrFile, toKMB } from '../../lib/util'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { parseBank } from '../../lib/util/parseStringBank'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; @@ -244,12 +244,14 @@ export const sellCommand: OSBMahojiCommand = { if (user.isIronman) { return `Sold ${bankToSell} for **${totalPrice.toLocaleString()}gp (${toKMB(totalPrice)})**`; } - return `Sold ${bankToSell} for **${totalPrice.toLocaleString()}gp (${toKMB( - totalPrice - )})** (${taxRatePercent}% below market price). ${ - hasSkipper - ? '\n\n<:skipper:755853421801766912> Skipper has negotiated with the bank and you were charged less tax on the sale!' - : '' - }`; + return returnStringOrFile( + `Sold ${bankToSell} for **${totalPrice.toLocaleString()}gp (${toKMB( + totalPrice + )})** (${taxRatePercent}% below market price). ${ + hasSkipper + ? '\n\n<:skipper:755853421801766912> Skipper has negotiated with the bank and you were charged less tax on the sale!' + : '' + }` + ); } }; diff --git a/src/mahoji/commands/testpotato.ts b/src/mahoji/commands/testpotato.ts index 137d39d218..1c67bbfb0b 100644 --- a/src/mahoji/commands/testpotato.ts +++ b/src/mahoji/commands/testpotato.ts @@ -39,6 +39,10 @@ import { prisma } from '../../lib/settings/prisma'; import { getFarmingInfo } from '../../lib/skilling/functions/getFarmingInfo'; import Skills from '../../lib/skilling/skills'; import Farming from '../../lib/skilling/skills/farming'; +import { slayerMasterChoices } from '../../lib/slayer/constants'; +import { slayerMasters } from '../../lib/slayer/slayerMasters'; +import { getUsersCurrentSlayerInfo } from '../../lib/slayer/slayerUtil'; +import { allSlayerMonsters } from '../../lib/slayer/tasks'; import { tameSpecies } from '../../lib/tames'; import { stringMatches } from '../../lib/util'; import { calcDropRatesFromBankWithoutUniques } from '../../lib/util/calcDropRatesFromBank'; @@ -684,6 +688,46 @@ export const testPotatoCommand: OSBMahojiCommand | null = production } } ] + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'setslayertask', + description: 'Set slayer task.', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'master', + description: 'The master you wish to set your task.', + required: true, + choices: slayerMasterChoices + }, + { + type: ApplicationCommandOptionType.String, + name: 'monster', + description: 'The monster you want to set your task as.', + required: true, + autocomplete: async value => { + const filteredMonsters = [...new Set(allSlayerMonsters)].filter(monster => { + if (!value) return true; + return [monster.name.toLowerCase(), ...monster.aliases].some(aliases => + aliases.includes(value.toLowerCase()) + ); + }); + return filteredMonsters.map(monster => ({ + name: monster.name, + value: monster.name + })); + } + }, + { + type: ApplicationCommandOptionType.Integer, + name: 'quantity', + description: 'The task quantity you want to assign.', + required: false, + min_value: 0, + max_value: 1000 + } + ] } ], run: async ({ @@ -718,6 +762,7 @@ export const testPotatoCommand: OSBMahojiCommand | null = production set?: { qp?: number; all_ca_tasks?: boolean }; check?: { monster_droprates?: string }; bingo_tools?: { start_bingo: string }; + setslayertask?: { master: string; monster: string; quantity: number }; }>) => { await deferInteraction(interaction); if (production) { @@ -1103,6 +1148,61 @@ ${droprates.join('\n')}`), return userGrowingProgressStr((await getFarmingInfo(userID)).patchesDetailed); } + if (options.setslayertask) { + const user = await mUserFetch(userID); + const usersTask = await getUsersCurrentSlayerInfo(user.id); + + const { monster, master } = options.setslayertask; + + const selectedMonster = allSlayerMonsters.find(m => stringMatches(m.name, monster)); + const selectedMaster = slayerMasters.find( + sm => stringMatches(master, sm.name) || sm.aliases.some(alias => stringMatches(master, alias)) + ); + + // Set quantity to 50 if user doesn't assign a quantity + const quantity = options.setslayertask?.quantity ?? 50; + + const assignedTask = selectedMaster!.tasks.find(m => m.monster.id === selectedMonster?.id)!; + + if (!selectedMaster) return 'Invalid slayer master.'; + if (!selectedMonster) return 'Invalid monster.'; + if (!assignedTask) return `${selectedMaster.name} can not assign ${selectedMonster.name}.`; + + // Update an existing slayer task for the user + if (usersTask.currentTask?.id) { + await prisma.slayerTask.update({ + where: { + id: usersTask.currentTask?.id + }, + data: { + quantity, + quantity_remaining: quantity, + slayer_master_id: selectedMaster.id, + monster_id: selectedMonster.id, + skipped: false + } + }); + } else { + // Create a new slayer task for the user + await prisma.slayerTask.create({ + data: { + user_id: user.id, + quantity, + quantity_remaining: quantity, + slayer_master_id: selectedMaster.id, + monster_id: selectedMonster.id, + skipped: false + } + }); + } + + await user.update({ + slayer_last_task: selectedMonster.id + }); + + return `You set your slayer task to ${selectedMonster.name} using ${selectedMaster.name}.`; + } + return 'Nothin!'; } }; diff --git a/src/mahoji/lib/abstracted_commands/fightCavesCommand.ts b/src/mahoji/lib/abstracted_commands/fightCavesCommand.ts index cd51377051..5b9a959d28 100644 --- a/src/mahoji/lib/abstracted_commands/fightCavesCommand.ts +++ b/src/mahoji/lib/abstracted_commands/fightCavesCommand.ts @@ -4,6 +4,7 @@ import { Bank, Monsters } from 'oldschooljs'; import TzTokJad from 'oldschooljs/dist/simulation/monsters/special/TzTokJad'; import { itemID } from 'oldschooljs/dist/util'; +import { getMinigameScore } from '../../../lib/settings/minigames'; import { getUsersCurrentSlayerInfo } from '../../../lib/slayer/slayerUtil'; import { FightCavesActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration } from '../../../lib/util'; @@ -24,7 +25,9 @@ async function determineDuration(user: MUser): Promise<[number, string]> { // Reduce time based on KC const jadKC = await user.getKC(TzTokJad.id); - const percentIncreaseFromKC = Math.min(50, jadKC); + const zukKC = await getMinigameScore(user.id, 'inferno'); + const experienceKC = jadKC + zukKC * 3; + const percentIncreaseFromKC = Math.min(50, experienceKC); baseTime = reduceNumByPercent(baseTime, percentIncreaseFromKC); debugStr += `${percentIncreaseFromKC}% from KC`; @@ -43,9 +46,12 @@ async function determineDuration(user: MUser): Promise<[number, string]> { return [baseTime, debugStr]; } -function determineChanceOfDeathPreJad(user: MUser, attempts: number) { +function determineChanceOfDeathPreJad(user: MUser, attempts: number, hasInfernoKC: boolean) { let deathChance = Math.max(14 - attempts * 2, 5); + // If user has killed inferno, give them the lowest chance of death pre Jad. + if (hasInfernoKC) deathChance = 5; + // -4% Chance of dying before Jad if you have SGS. if (user.hasEquippedOrInBank(itemID('Saradomin godsword'))) { deathChance -= 4; @@ -54,8 +60,12 @@ function determineChanceOfDeathPreJad(user: MUser, attempts: number) { return deathChance; } -function determineChanceOfDeathInJad(attempts: number) { - const chance = Math.floor(100 - (Math.log(attempts) / Math.log(Math.sqrt(15))) * 50); +function determineChanceOfDeathInJad(attempts: number, hasInfernoKC: boolean) { + let chance = Math.floor(100 - (Math.log(attempts) / Math.log(Math.sqrt(15))) * 50); + + if (hasInfernoKC) { + chance /= 1.5; + } // Chance of death cannot be 100% or <5%. return Math.max(Math.min(chance, 99), 5); @@ -98,11 +108,14 @@ export async function fightCavesCommand(user: MUser, channelID: string): Command const { fight_caves_attempts: attempts } = await user.fetchStats({ fight_caves_attempts: true }); - const jadDeathChance = determineChanceOfDeathInJad(attempts); - let preJadDeathChance = determineChanceOfDeathPreJad(user, attempts); + const jadKC = await user.getKC(TzTokJad.id); + const zukKC = await getMinigameScore(user.id, 'inferno'); + const hasInfernoKC = zukKC > 0; + + const jadDeathChance = determineChanceOfDeathInJad(attempts, hasInfernoKC); + let preJadDeathChance = determineChanceOfDeathPreJad(user, attempts, hasInfernoKC); const usersRangeStats = user.gear.range.stats; - const jadKC = await user.getKC(TzTokJad.id); duration += (randInt(1, 5) * duration) / 100; @@ -154,6 +167,7 @@ export async function fightCavesCommand(user: MUser, channelID: string): Command **Boosts:** ${debugStr} **Range Attack Bonus:** ${usersRangeStats.attack_ranged} **Jad KC:** ${jadKC} +**Zuk KC:** ${zukKC} **Attempts:** ${attempts} **Removed from your bank:** ${fightCavesCost} diff --git a/src/mahoji/lib/abstracted_commands/minionKill.ts b/src/mahoji/lib/abstracted_commands/minionKill.ts index 553908f851..d129c93b6c 100644 --- a/src/mahoji/lib/abstracted_commands/minionKill.ts +++ b/src/mahoji/lib/abstracted_commands/minionKill.ts @@ -935,7 +935,7 @@ export async function minionKillCommand( break; } } - if (wildyPeak?.peakTier === PeakTier.High) { + if (wildyPeak?.peakTier === PeakTier.High && !user.bitfield.includes(BitField.DisableHighPeakTimeWarning)) { if (interaction) { await handleMahojiConfirmation( interaction, diff --git a/src/mahoji/lib/abstracted_commands/openCommand.ts b/src/mahoji/lib/abstracted_commands/openCommand.ts index bccfddcdda..29290939fe 100644 --- a/src/mahoji/lib/abstracted_commands/openCommand.ts +++ b/src/mahoji/lib/abstracted_commands/openCommand.ts @@ -200,7 +200,8 @@ async function finalizeOpening({ ? `Loot from ${cost.amount(openables[0].openedItem.id)}x ${openables[0].name}` : 'Loot From Opening', user, - previousCL + previousCL, + mahojiFlags: ['show_names'] }); if (loot.has('Coins')) { diff --git a/src/mahoji/lib/abstracted_commands/statCommand.ts b/src/mahoji/lib/abstracted_commands/statCommand.ts index 75cc5d0053..676b042ada 100644 --- a/src/mahoji/lib/abstracted_commands/statCommand.ts +++ b/src/mahoji/lib/abstracted_commands/statCommand.ts @@ -5,7 +5,7 @@ import { sumArr, Time } from 'e'; import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; import { Bank, Monsters } from 'oldschooljs'; import { SkillsEnum } from 'oldschooljs/dist/constants'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import { ItemBank, SkillsScore } from 'oldschooljs/dist/meta/types'; import { TOBRooms } from 'oldschooljs/dist/simulation/misc/TheatreOfBlood'; import { toKMB } from 'oldschooljs/dist/util'; @@ -13,6 +13,7 @@ import { ClueTiers } from '../../../lib/clues/clueTiers'; import { getClueScoresFromOpenables } from '../../../lib/clues/clueUtils'; import { Emoji, PerkTier } from '../../../lib/constants'; import { calcCLDetails, isCLItem } from '../../../lib/data/Collections'; +import { skillEmoji } from '../../../lib/data/emojis'; import { slayerMaskHelms } from '../../../lib/data/slayerMaskHelms'; import { calculateAllFletchedItems, @@ -1138,6 +1139,27 @@ GROUP BY "bankBackground";`); return makeResponseForBank(new Bank(stats.ic_loot_bank as ItemBank), 'Item Contract Loot'); } }, + { + name: 'Personal XP gained from Tears of Guthix', + perkTierNeeded: PerkTier.Four, + run: async (user: MUser) => { + const result = await prisma.$queryRawUnsafe( + `SELECT skill, + SUM(xp) AS total_xp + FROM xp_gains + WHERE source = 'TearsOfGuthix' + AND user_id = ${BigInt(user.id)} + GROUP BY skill` + ); + + return `**Personal XP gained from Tears of Guthix**\n${result + .map( + (i: any) => + `${skillEmoji[i.skill as keyof typeof skillEmoji] as keyof SkillsScore} ${toKMB(i.total_xp)}` + ) + .join('\n')}`; + } + }, { name: 'Bird Eggs Offered', perkTierNeeded: null, diff --git a/src/tasks/minions/minigames/tobActivity.ts b/src/tasks/minions/minigames/tobActivity.ts index d4c0423407..277d638388 100644 --- a/src/tasks/minions/minigames/tobActivity.ts +++ b/src/tasks/minions/minigames/tobActivity.ts @@ -3,7 +3,7 @@ import { randArrItem, roll, shuffleArr } from 'e'; import { Bank } from 'oldschooljs'; import { drawChestLootImage } from '../../../lib/bankImage'; -import { CHINCANNON_MESSAGES, Emoji, Events } from '../../../lib/constants'; +import { BOT_TYPE, CHINCANNON_MESSAGES, Emoji, Events } from '../../../lib/constants'; import { tobMetamorphPets } from '../../../lib/data/CollectionsExport'; import { TOBRooms, TOBUniques, TOBUniquesToAnnounce } from '../../../lib/data/tob'; import { trackLoot } from '../../../lib/lootTrack'; @@ -156,6 +156,13 @@ export const tobTask: MinionTask = { // Refund initial 100k entry cost userLoot.add('Coins', 100_000); + // Remove elite clue scroll if OSB & user has one in bank + if (BOT_TYPE === 'OSB') { + if (user.owns('Clue scroll (elite)')) { + userLoot.remove('Clue scroll (elite)', 1); + } + } + // Add this raids loot to the raid's total loot: totalLoot.add(userLoot);