diff --git a/prisma/schema.prisma b/prisma/schema.prisma index cf9252ba7c..cd4fded9ae 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1135,6 +1135,17 @@ model UserEvent { @@map("user_event") } +model DroppedClueScroll { + id String @id @default(uuid()) @db.Uuid + user_id String @db.Text + date_received DateTime @db.Timestamp(6) + used Boolean @default(false) + + item_id Int + + @@map("dropped_clue_scroll") +} + enum command_name_enum { testpotato achievementdiary diff --git a/src/lib/MUser.ts b/src/lib/MUser.ts index 6a9c8c8577..918a11b767 100644 --- a/src/lib/MUser.ts +++ b/src/lib/MUser.ts @@ -44,6 +44,7 @@ import { Gear, defaultGear } from './structures/Gear'; import { GearBank } from './structures/GearBank'; import type { XPBank } from './structures/XPBank'; import type { ItemBank, Skills } from './types'; +import type { ActivityTaskOptions } from './types/minions'; import { addItemToBank, convertXPtoLVL, fullGearToBank, hasSkillReqsRaw, itemNameFromID } from './util'; import { determineRunes } from './util/determineRunes'; import { getKCByName } from './util/getKCByName'; @@ -340,13 +341,15 @@ GROUP BY data->>'ci';`); collectionLog = false, filterLoot = true, dontAddToTempCL = false, - neverUpdateHistory = false + neverUpdateHistory = false, + tripOptions }: { items: ItemBank | Bank; collectionLog?: boolean; filterLoot?: boolean; dontAddToTempCL?: boolean; neverUpdateHistory?: boolean; + tripOptions?: ActivityTaskOptions; }) { const res = await transactItems({ collectionLog, @@ -354,7 +357,8 @@ GROUP BY data->>'ci';`); filterLoot, dontAddToTempCL, userID: this.id, - neverUpdateHistory + neverUpdateHistory, + tripOptions }); this.user = res.newUser; this.updateProperties(); diff --git a/src/lib/clues/clueUtils.ts b/src/lib/clues/clueUtils.ts index 2e34c305cb..7289d00f73 100644 --- a/src/lib/clues/clueUtils.ts +++ b/src/lib/clues/clueUtils.ts @@ -8,22 +8,6 @@ export function getClueScoresFromOpenables(openableScores: Bank) { return openableScores.filter(item => Boolean(ClueTiers.find(ct => ct.id === item.id))); } -/** - * Removes extra clue scrolls from loot, if they got more than 1 or if they already own 1. - */ -export function deduplicateClueScrolls({ loot, currentBank }: { loot: Bank; currentBank: Bank }) { - const newLoot = loot.clone(); - for (const { scrollID } of ClueTiers) { - if (!newLoot.has(scrollID)) continue; - if (currentBank.has(scrollID)) { - newLoot.remove(scrollID, newLoot.amount(scrollID)); - } else { - newLoot.set(scrollID, 1); - } - } - return newLoot; -} - export function buildClueButtons(loot: Bank | null, perkTier: number, user: MUser) { const components: ButtonBuilder[] = []; if (loot && perkTier > 1 && !user.bitfield.includes(BitField.DisableClueButtons)) { diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 2c24ad8b62..1198197e0d 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -6,6 +6,7 @@ import * as dotenv from 'dotenv'; import { getItemOrThrow, resolveItems } from 'oldschooljs'; import { z } from 'zod'; +import { Time } from 'e'; import { DISCORD_SETTINGS, production } from '../config'; import type { AbstractCommand } from '../mahoji/lib/inhibitors'; import { SkillsEnum } from './skilling/types'; @@ -582,3 +583,5 @@ if (!process.env.TEST && isMainThread) { `Starting... Git[${gitHash}] ClientID[${globalConfig.clientID}] Production[${globalConfig.isProduction}]` ); } + +export const CLUE_DROP_DESPAWN_TIME = Time.Minute * 61; diff --git a/src/lib/structures/UpdateBank.ts b/src/lib/structures/UpdateBank.ts index 0e3f2e5889..14fb3c5329 100644 --- a/src/lib/structures/UpdateBank.ts +++ b/src/lib/structures/UpdateBank.ts @@ -8,6 +8,7 @@ import type { MUserClass } from '../MUser'; import { degradeChargeBank } from '../degradeableItems'; import type { GearSetup } from '../gear/types'; import type { ItemBank } from '../types'; +import type { ActivityTaskOptions } from '../types/minions'; import { type JsonKeys, objHasAnyPropInCommon } from '../util'; import { ChargeBank, XPBank } from './Bank'; import { KCBank } from './KCBank'; @@ -54,7 +55,12 @@ export class UpdateBank { this.userUpdates = mergeDeep(this.userUpdates, other.userUpdates); } - async transact(user: MUser, { isInWilderness }: { isInWilderness?: boolean } = { isInWilderness: false }) { + async transact( + user: MUser, + { isInWilderness, tripOptions }: { isInWilderness?: boolean; tripOptions?: ActivityTaskOptions } = { + isInWilderness: false + } + ) { // Check everything first if (this.chargeBank.length() > 0) { const charges = user.hasCharges(this.chargeBank); @@ -86,7 +92,12 @@ export class UpdateBank { } let itemTransactionResult: Awaited> | null = null; if (this.itemLootBank.length > 0) { - itemTransactionResult = await user.addItemsToBank({ items: this.itemLootBank, collectionLog: true }); + itemTransactionResult = await user.addItemsToBank({ + items: this.itemLootBank, + collectionLog: true, + tripOptions + }); + results.push(...itemTransactionResult.messages); } // XP diff --git a/src/lib/util.ts b/src/lib/util.ts index fb7370da81..2c5144f6aa 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -435,3 +435,12 @@ export function joinStrings(itemList: any[], end?: string) { return itemList.join(', '); } } + +export function fetchUsersDroppedClues() { + return prisma.droppedClueScroll.findMany({ + select: { + user_id: true, + item_id: true + } + }); +} diff --git a/src/lib/util/migrateUser.ts b/src/lib/util/migrateUser.ts index 488b999f10..f1c55309ed 100644 --- a/src/lib/util/migrateUser.ts +++ b/src/lib/util/migrateUser.ts @@ -96,6 +96,9 @@ export async function migrateUser(_source: string | MUser, _dest: string | MUser transactions.push( prisma.reclaimableItem.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) ); + transactions.push( + prisma.droppedClueScroll.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) + ); transactions.push( prisma.activity.updateMany({ diff --git a/src/lib/util/smallUtils.ts b/src/lib/util/smallUtils.ts index 9699518863..5d6aafed17 100644 --- a/src/lib/util/smallUtils.ts +++ b/src/lib/util/smallUtils.ts @@ -1,5 +1,5 @@ import { type CommandResponse, deepMerge, miniID, stripEmojis, toTitleCase } from '@oldschoolgg/toolkit/util'; -import type { Prisma } from '@prisma/client'; +import type { DroppedClueScroll, Prisma } from '@prisma/client'; import { AlignmentEnum, AsciiTable3 } from 'ascii-table3'; import { ButtonBuilder, ButtonStyle, type InteractionReplyOptions } from 'discord.js'; import { clamp, objectEntries } from 'e'; @@ -7,6 +7,7 @@ import { type ArrayItemsResolved, Bank, type ItemBank, Items, getItemOrThrow } f import { MersenneTwister19937, shuffle } from 'random-js'; import z from 'zod'; +import { CLUE_DROP_DESPAWN_TIME } from '../constants'; import { skillEmoji } from '../data/emojis'; import type { UserFullGearSetup } from '../gear/types'; import type { Skills } from '../types'; @@ -255,3 +256,12 @@ export function isValidNickname(str?: string) { stripEmojis(str).length === str.length ); } + +export function convertDroppedCluesToBank(droppedClues: Pick[]) { + const bank = new Bank(); + for (const clue of droppedClues) { + if (clue.date_received.getTime() < Date.now() - CLUE_DROP_DESPAWN_TIME) continue; + bank.add(clue.item_id, 1); + } + return bank; +} diff --git a/src/lib/util/transactItemsFromBank.ts b/src/lib/util/transactItemsFromBank.ts index d1c38919b2..29ecf2b3dc 100644 --- a/src/lib/util/transactItemsFromBank.ts +++ b/src/lib/util/transactItemsFromBank.ts @@ -1,15 +1,60 @@ import type { Prisma } from '@prisma/client'; import { Bank } from 'oldschooljs'; +import { randomInteger } from 'remeda'; import { findBingosWithUserParticipating } from '../../mahoji/lib/bingo/BingoManager'; import { mahojiUserSettingsUpdate } from '../MUser'; -import { deduplicateClueScrolls } from '../clues/clueUtils'; +import { ClueTiers } from '../clues/clueTiers'; import { handleNewCLItems } from '../handleNewCLItems'; import { filterLootReplace } from '../slayer/slayerUtil'; import type { ItemBank } from '../types'; +import type { ActivityTaskOptions } from '../types/minions'; import { logError } from './logError'; import { userQueueFn } from './userQueues'; +async function deduplicateClueScrolls({ + userID, + loot, + currentBank, + tripOptions +}: { userID: string; loot: Bank; currentBank: Bank; tripOptions: ActivityTaskOptions | undefined }): Promise<{ + newLoot: Bank; + droppedClues: Prisma.DroppedClueScrollCreateInput[]; +}> { + const droppedClues: Prisma.DroppedClueScrollCreateInput[] = []; + const newLoot = loot.clone(); + for (const { scrollID } of ClueTiers) { + if (!newLoot.has(scrollID)) continue; + const amountOwned = currentBank.amount(scrollID); + const amountInLoot = newLoot.amount(scrollID); + + newLoot.set(scrollID, amountOwned > 0 ? 0 : 1); + + const leftOvers = amountInLoot - amountOwned; + if (tripOptions) { + for (let i = 0; i < leftOvers; i++) { + const dateReceived = new Date( + randomInteger(tripOptions.finishDate - tripOptions.duration, tripOptions.finishDate) + ); + droppedClues.push({ + user_id: userID, + item_id: scrollID, + date_received: dateReceived + }); + } + } + } + if (droppedClues.length > 0) { + await prisma.droppedClueScroll.createMany({ + data: droppedClues + }); + } + return { + newLoot, + droppedClues + }; +} + export interface TransactItemsArgs { userID: string; itemsToAdd?: Bank; @@ -19,6 +64,7 @@ export interface TransactItemsArgs { dontAddToTempCL?: boolean; neverUpdateHistory?: boolean; otherUpdates?: Prisma.UserUpdateArgs['data']; + tripOptions?: ActivityTaskOptions; } declare global { @@ -31,6 +77,7 @@ async function transactItemsFromBank({ collectionLog = false, filterLoot = true, dontAddToTempCL = false, + tripOptions, ...options }: TransactItemsArgs) { let itemsToAdd = options.itemsToAdd ? options.itemsToAdd.clone() : undefined; @@ -38,6 +85,7 @@ async function transactItemsFromBank({ return userQueueFn(userID, async function transactItemsInner() { const settings = await mUserFetch(userID); + const messages: string[] = []; const gpToRemove = (itemsToRemove?.amount('Coins') ?? 0) - (itemsToAdd?.amount('Coins') ?? 0); if (itemsToRemove && settings.GP < gpToRemove) { @@ -58,10 +106,14 @@ async function transactItemsFromBank({ let clUpdates: Prisma.UserUpdateArgs['data'] = {}; if (itemsToAdd) { - itemsToAdd = deduplicateClueScrolls({ + const dedupeResult = await deduplicateClueScrolls({ loot: itemsToAdd.clone(), - currentBank: currentBank.clone().remove(itemsToRemove ?? {}) + currentBank: currentBank.clone().remove(itemsToRemove ?? {}), + userID, + tripOptions }); + itemsToAdd = dedupeResult.newLoot; + messages.push(`Dropped ${dedupeResult.droppedClues.length} clue scrolls`); const { bankLoot, clLoot } = filterLoot ? filterLootReplace(settings.allItemsOwned, itemsToAdd) : { bankLoot: itemsToAdd, clLoot: itemsToAdd }; @@ -151,7 +203,8 @@ async function transactItemsFromBank({ itemsRemoved: itemsToRemove, newBank: new Bank(newUser.bank as ItemBank), newCL, - newUser + newUser, + messages }; }); } diff --git a/src/mahoji/commands/clue.ts b/src/mahoji/commands/clue.ts index e421e6d0fb..3d21bc78b2 100644 --- a/src/mahoji/commands/clue.ts +++ b/src/mahoji/commands/clue.ts @@ -1,16 +1,16 @@ import type { CommandResponse, CommandRunOptions } from '@oldschoolgg/toolkit/util'; import { ApplicationCommandOptionType } from 'discord.js'; -import { Time, notEmpty, randInt } from 'e'; +import { Time, clamp, notEmpty, randInt, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; import type { ClueTier } from '../../lib/clues/clueTiers'; import { ClueTiers } from '../../lib/clues/clueTiers'; -import { BitField } from '../../lib/constants'; +import { BitField, CLUE_DROP_DESPAWN_TIME } from '../../lib/constants'; import { allOpenables, getOpenableLoot } from '../../lib/openables'; import { getPOHObject } from '../../lib/poh'; import type { ClueActivityTaskOptions } from '../../lib/types/minions'; -import { formatDuration, isWeekend, stringMatches } from '../../lib/util'; +import { convertDroppedCluesToBank, formatDuration, isWeekend, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import getOSItem, { getItem } from '../../lib/util/getOSItem'; @@ -19,14 +19,125 @@ import { getPOH } from '../lib/abstracted_commands/pohCommand'; import type { OSBMahojiCommand } from '../lib/util'; import { addToOpenablesScores, getMahojiBank, mahojiUsersSettingsFetch } from '../mahojiSettings'; -function reducedClueTime(clueTier: ClueTier, score: number) { - // Every 3 hours become 1% better to a cap of 10% - const percentReduced = Math.min(Math.floor(score / ((Time.Hour * 3) / clueTier.timeToFinish)), 10); - const amountReduced = (clueTier.timeToFinish * percentReduced) / 100; - const reducedTime = clueTier.timeToFinish - amountReduced; - - return [reducedTime, percentReduced]; -} +const clueTierBoosts: Record = { + Beginner: [ + { + item: getOSItem('Ring of the elements'), + boost: '10% for Ring of the elements', + durationMultiplier: 0.9 + } + ], + Easy: [ + { + item: getOSItem('Achievement diary cape'), + boost: '10% for Achievement diary cape', + durationMultiplier: 0.9 + }, + { + item: getOSItem('Ring of the elements'), + boost: '6% for Ring of the elements', + durationMultiplier: 0.94 + } + ], + Medium: [ + { + item: getOSItem('Ring of the elements'), + boost: '8% for Ring of the elements', + durationMultiplier: 0.92 + } + ], + Hard: [ + { + item: getOSItem('Achievement diary cape'), + boost: '10% for Achievement diary cape', + durationMultiplier: 0.9 + }, + { + item: getOSItem('Wilderness sword 3'), + boost: '8% for Wilderness sword 3', + durationMultiplier: 0.92 + }, + { + item: getOSItem('Royal seed pod'), + boost: '6% for Royal seed pod', + durationMultiplier: 0.94 + }, + { + item: getOSItem('Eternal teleport crystal'), + boost: '4% for Eternal teleport crystal', + durationMultiplier: 0.96 + }, + { + item: getOSItem("Pharaoh's sceptre"), + boost: "4% for Pharaoh's sceptre", + durationMultiplier: 0.96 + }, + { + item: getOSItem('Toxic blowpipe'), + boost: '4% for Toxic blowpipe', + durationMultiplier: 0.96 + } + ], + Elite: [ + { + item: getOSItem('Achievement diary cape'), + boost: '10% for Achievement diary cape', + durationMultiplier: 0.9 + }, + { + item: getOSItem('Kandarin headgear 4'), + boost: '7% for Kandarin headgear 4', + durationMultiplier: 0.93 + }, + { + item: getOSItem('Fremennik sea boots 4'), + boost: '3% for Fremennik sea boots 4', + durationMultiplier: 0.97 + }, + { + item: getOSItem("Pharaoh's sceptre"), + boost: "4% for Pharaoh's sceptre", + durationMultiplier: 0.96 + }, + { + item: getOSItem('Toxic blowpipe'), + boost: '4% for Toxic blowpipe', + durationMultiplier: 0.96 + } + ], + Master: [ + { + item: getOSItem('Achievement diary cape'), + boost: '10% for Achievement diary cape', + durationMultiplier: 0.9 + }, + { + item: getOSItem('Kandarin headgear 4'), + boost: '6% for Kandarin headgear 4', + durationMultiplier: 0.94 + }, + { + item: getOSItem('Music cape'), + boost: '5% for Music cape', + durationMultiplier: 0.95 + }, + { + item: getOSItem('Eternal teleport crystal'), + boost: '3% for Eternal teleport crystal', + durationMultiplier: 0.97 + }, + { + item: getOSItem('Toxic blowpipe'), + boost: '2% for Toxic blowpipe', + durationMultiplier: 0.98 + }, + { + item: getOSItem('Dragon claws'), + boost: '1% for Dragon claws', + durationMultiplier: 0.99 + } + ] +}; function shouldApplyBoost(clueTier: ClueTier, item: string, hasAchievementDiaryCape: boolean) { switch (clueTier.name) { @@ -47,22 +158,6 @@ interface ClueBoost { durationMultiplier: number; } -function applyClueBoosts(user: MUser, boostList: ClueBoost[], boosts: string[], duration: number, clueTier: ClueTier) { - let hasAchievementDiaryCape = false; - for (const boost of boostList) { - if (user.hasEquippedOrInBank(boost.item.name)) { - if (shouldApplyBoost(clueTier, boost.item.name, hasAchievementDiaryCape)) { - boosts.push(boost.boost); - duration *= boost.durationMultiplier; - } - if (boost.item.name === 'Achievement diary cape') { - hasAchievementDiaryCape = true; - } - } - } - return { duration, boosts }; -} - export const clueCommand: OSBMahojiCommand = { name: 'clue', description: 'Send your minion to complete clue scrolls.', @@ -78,9 +173,33 @@ export const clueCommand: OSBMahojiCommand = { description: 'The clue you want to do.', required: true, autocomplete: async (value, user) => { - const bank = getMahojiBank(await mahojiUsersSettingsFetch(user.id, { bank: true })); + const [droppedClues, mUser] = await prisma.$transaction([ + prisma.droppedClueScroll.findMany({ + where: { + user_id: user.id, + date_received: { + gte: new Date(Date.now() - CLUE_DROP_DESPAWN_TIME) + }, + used: false + }, + select: { + item_id: true, + date_received: true + } + }), + prisma.user.findFirst({ + where: { + id: user.id + }, + select: { + bank: true + } + }) + ]); + const bank = new Bank(mUser?.bank as ItemBank); + const dropped = convertDroppedCluesToBank(droppedClues); return ClueTiers.map(i => ({ - name: `${i.name} (${bank.amount(i.scrollID)}x Owned)`, + name: `${i.name} (${bank.amount(i.scrollID)}x Owned${dropped.amount(i.scrollID) > 0 ? `, ${dropped.amount(i.scrollID)}x Dropped` : ''})`, value: i.name })).filter(i => !value || i.value.toLowerCase().includes(value)); } @@ -107,7 +226,6 @@ export const clueCommand: OSBMahojiCommand = { ], run: async ({ options, userID, channelID }: CommandRunOptions<{ tier: string; implings?: string }>) => { const user = await mUserFetch(userID); - let quantity = 1; const clueTier = ClueTiers.find( tier => stringMatches(tier.id.toString(), options.tier) || stringMatches(tier.name, options.tier) @@ -126,37 +244,33 @@ export const clueCommand: OSBMahojiCommand = { if (!clueTier.implings?.includes(clueImpling.id)) return `These clues aren't found in ${clueImpling.name}s`; } - const boosts = []; - + const maxTripLength = calcMaxTripLength(user, 'ClueCompletion'); const stats = await user.fetchStats({ openable_scores: true }); + const score = (stats.openable_scores as ItemBank)[clueTier.id] ?? 1; - let [timeToFinish, percentReduced] = reducedClueTime( - clueTier, - (stats.openable_scores as ItemBank)[clueTier.id] ?? 1 - ); - - if (percentReduced >= 1) boosts.push(`${percentReduced}% for Clue score`); + const learningBoostPercent = Math.min(Math.floor(score / ((Time.Hour * 3) / clueTier.timeToFinish)), 10); + let timeToFinish = reduceNumByPercent(clueTier.timeToFinish, learningBoostPercent); - let duration = timeToFinish * quantity; - - const maxTripLength = calcMaxTripLength(user, 'ClueCompletion'); - - if (duration > maxTripLength) { - return `${user.minionName} can't go on Clue trips longer than ${formatDuration( - maxTripLength - )}, try a lower quantity. The highest amount you can do for ${clueTier.name} is ${Math.floor( - maxTripLength / timeToFinish - )}.`; + // START Boosts + const boosts = []; + const boostList = clueTierBoosts[clueTier.name]; + let hasAchievementDiaryCape = false; + for (const boost of boostList) { + if (user.hasEquippedOrInBank(boost.item.name)) { + if (shouldApplyBoost(clueTier, boost.item.name, hasAchievementDiaryCape)) { + boosts.push(boost.boost); + timeToFinish *= boost.durationMultiplier; + } + if (boost.item.name === 'Achievement diary cape') { + hasAchievementDiaryCape = true; + } + } } - - const randomAddedDuration = randInt(1, 20); - duration += (randomAddedDuration * duration) / 100; const poh = await getPOH(user.id); const hasOrnateJewelleryBox = poh.jewellery_box === getPOHObject('Ornate jewellery box').id; const hasJewelleryBox = poh.jewellery_box !== null; const hasXericTalisman = poh.amulet === getPOHObject("Mounted xeric's talisman").id; - // Global Boosts const globalBoosts = [ { condition: isWeekend, @@ -188,151 +302,76 @@ export const clueCommand: OSBMahojiCommand = { for (const { condition, boost, durationMultiplier } of globalBoosts) { if (condition()) { boosts.push(boost); - duration *= durationMultiplier; + timeToFinish *= durationMultiplier; } } - // Xeric's Talisman boost if (clueTier.name === 'Medium' && hasXericTalisman) { boosts.push("2% for Mounted Xeric's Talisman"); - duration *= 0.98; + timeToFinish *= 0.98; } + // END Boosts + + const droppedClues = options.implings + ? [] + : await prisma.droppedClueScroll.findMany({ + where: { + user_id: user.id, + item_id: clueTier.scrollID, + used: false, + date_received: { + gte: new Date(Date.now() - CLUE_DROP_DESPAWN_TIME) + } + } + }); + const totalDropped = droppedClues.length; + const totalInBank = user.bank.amount(clueTier.scrollID); + const totalOwned = totalDropped + totalInBank; + if (totalOwned === 0) return `You don't have any ${clueTier.name} clues.`; + let quantity = clamp(totalOwned, 1, Math.floor(maxTripLength / timeToFinish)); + + if (learningBoostPercent >= 1) boosts.push(`${learningBoostPercent}% for Clue score`); - // Specific boosts - const clueTierBoosts: Record = { - Beginner: [ - { - item: getOSItem('Ring of the elements'), - boost: '10% for Ring of the elements', - durationMultiplier: 0.9 - } - ], - Easy: [ - { - item: getOSItem('Achievement diary cape'), - boost: '10% for Achievement diary cape', - durationMultiplier: 0.9 - }, - { - item: getOSItem('Ring of the elements'), - boost: '6% for Ring of the elements', - durationMultiplier: 0.94 - } - ], - Medium: [ - { - item: getOSItem('Ring of the elements'), - boost: '8% for Ring of the elements', - durationMultiplier: 0.92 - } - ], - Hard: [ - { - item: getOSItem('Achievement diary cape'), - boost: '10% for Achievement diary cape', - durationMultiplier: 0.9 - }, - { - item: getOSItem('Wilderness sword 3'), - boost: '8% for Wilderness sword 3', - durationMultiplier: 0.92 - }, - { - item: getOSItem('Royal seed pod'), - boost: '6% for Royal seed pod', - durationMultiplier: 0.94 - }, - { - item: getOSItem('Eternal teleport crystal'), - boost: '4% for Eternal teleport crystal', - durationMultiplier: 0.96 - }, - { - item: getOSItem("Pharaoh's sceptre"), - boost: "4% for Pharaoh's sceptre", - durationMultiplier: 0.96 - }, - { - item: getOSItem('Toxic blowpipe'), - boost: '4% for Toxic blowpipe', - durationMultiplier: 0.96 - } - ], - Elite: [ - { - item: getOSItem('Achievement diary cape'), - boost: '10% for Achievement diary cape', - durationMultiplier: 0.9 - }, - { - item: getOSItem('Kandarin headgear 4'), - boost: '7% for Kandarin headgear 4', - durationMultiplier: 0.93 - }, - { - item: getOSItem('Fremennik sea boots 4'), - boost: '3% for Fremennik sea boots 4', - durationMultiplier: 0.97 - }, - { - item: getOSItem("Pharaoh's sceptre"), - boost: "4% for Pharaoh's sceptre", - durationMultiplier: 0.96 - }, - { - item: getOSItem('Toxic blowpipe'), - boost: '4% for Toxic blowpipe', - durationMultiplier: 0.96 - } - ], - Master: [ - { - item: getOSItem('Achievement diary cape'), - boost: '10% for Achievement diary cape', - durationMultiplier: 0.9 - }, - { - item: getOSItem('Kandarin headgear 4'), - boost: '6% for Kandarin headgear 4', - durationMultiplier: 0.94 - }, - { - item: getOSItem('Music cape'), - boost: '5% for Music cape', - durationMultiplier: 0.95 - }, - { - item: getOSItem('Eternal teleport crystal'), - boost: '3% for Eternal teleport crystal', - durationMultiplier: 0.97 - }, - { - item: getOSItem('Toxic blowpipe'), - boost: '2% for Toxic blowpipe', - durationMultiplier: 0.98 - }, - { - item: getOSItem('Dragon claws'), - boost: '1% for Dragon claws', - durationMultiplier: 0.99 - } - ] - }; + let duration = timeToFinish * quantity; - const clueTierName = clueTier.name; - const boostList = clueTierBoosts[clueTierName]; - const result = applyClueBoosts(user, boostList, boosts, duration, clueTier); + if (duration > maxTripLength) { + return `${user.minionName} can't go on Clue trips longer than ${formatDuration( + maxTripLength + )}, try a lower quantity. The highest amount you can do for ${clueTier.name} is ${Math.floor( + maxTripLength / timeToFinish + )}.`; + } - timeToFinish = result.duration; + const randomAddedDuration = randInt(1, 20); + duration += (randomAddedDuration * duration) / 100; const response: Awaited = {}; let implingLootString = ''; let implingClues = 0; if (!clueImpling) { - const cost = new Bank().add(clueTier.scrollID, quantity); - if (!user.owns(cost)) return `You don't own ${cost}.`; - await user.removeItemsFromBank(new Bank().add(clueTier.scrollID, quantity)); + const droppedCluesToRemove = quantity - totalInBank; + const realCluesToRemove = quantity - droppedCluesToRemove; + if (droppedCluesToRemove > 0) { + const updated = await prisma.droppedClueScroll.updateMany({ + where: { + id: { + in: droppedClues.slice(0, droppedCluesToRemove).map(c => c.id) + } + }, + data: { + used: true + } + }); + if (updated.count !== droppedCluesToRemove) { + console.error( + `User[${user.id}] didn't have expected amount of dropped clues to remove. Expected: ${droppedCluesToRemove}, Removed: ${updated.count}` + ); + } + } + if (realCluesToRemove > 0) { + await user.removeItemsFromBank(new Bank().add(clueTier.scrollID, realCluesToRemove)); + } } else { const implingJarOpenable = allOpenables.find(o => o.aliases.some(a => stringMatches(a, clueImpling.name))); // If this triggers, it means OSJS probably broke / is missing an alias for an impling jar: @@ -381,6 +420,7 @@ export const clueCommand: OSBMahojiCommand = { } from ${openedImplings}x ${clueImpling.name}s.`; } + console.log({ timeToFinish: formatDuration(timeToFinish), duration: formatDuration(duration), quantity }); duration = timeToFinish * quantity; await addSubTaskToActivityTask({ diff --git a/src/mahoji/lib/abstracted_commands/ironmanCommand.ts b/src/mahoji/lib/abstracted_commands/ironmanCommand.ts index 2c9b0687a7..82ff8d2d3d 100644 --- a/src/mahoji/lib/abstracted_commands/ironmanCommand.ts +++ b/src/mahoji/lib/abstracted_commands/ironmanCommand.ts @@ -165,6 +165,7 @@ After becoming an ironman: await prisma.newUser.deleteMany({ where: { id: user.id } }); await prisma.activity.deleteMany({ where: { user_id: BigInt(user.id) } }); await prisma.stashUnit.deleteMany({ where: { user_id: BigInt(user.id) } }); + await prisma.droppedClueScroll.deleteMany({ where: { user_id: user.id } }); await prisma.userEvent.deleteMany({ where: { user_id: user.id } }); await prisma.userStats.deleteMany({ where: { user_id: BigInt(user.id) } }); await prisma.buyCommandTransaction.deleteMany({ where: { user_id: BigInt(user.id) } }); diff --git a/src/tasks/minions/monsterActivity.ts b/src/tasks/minions/monsterActivity.ts index 6bc5748106..a0e7b8420a 100644 --- a/src/tasks/minions/monsterActivity.ts +++ b/src/tasks/minions/monsterActivity.ts @@ -503,7 +503,10 @@ export const monsterTask: MinionTask = { ] }); - const resultOrError = await updateBank.transact(user, { isInWilderness: data.isInWilderness }); + const resultOrError = await updateBank.transact(user, { + isInWilderness: data.isInWilderness, + tripOptions: data + }); if (typeof resultOrError === 'string') { return resultOrError; }