diff --git a/package.json b/package.json index fe609ea..95177f1 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,8 @@ "babel-loader": "^9.1.3", "build": "^0.1.4", "grimoire-kolmafia": "^0.3.25", - "kolmafia": "^5.27815.0", - "libram": "^0.9.2", + "kolmafia": "^5.28591.0", + "libram": "^0.11.14", "mafia-shared-relay": "0.0.7", "webpack": "^5.89.0" }, @@ -37,7 +37,7 @@ "esbuild-plugin-babel": "^0.2.3", "eslint": "^8.45.0", "eslint-config-prettier": "^8.8.0", - "eslint-plugin-libram": "^0.4.15", + "eslint-plugin-libram": "^0.4.34", "eslint-plugin-prettier": "^5.0.0", "prettier": "^3.0.0", "typescript": "^5.1.6", @@ -51,5 +51,6 @@ "TS" ], "repository": "https://github.com/Ignose/candywrapper.git", - "private": false + "private": false, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/args.ts b/src/args.ts index 2d123ee..1b3a492 100644 --- a/src/args.ts +++ b/src/args.ts @@ -23,18 +23,47 @@ export const args = Args.create( }), cs: Args.flag({ help: "Ascend into and run CS.", + default: false, setting: "", }), smol: Args.flag({ help: "Ascend into and run smol.", + default: false, setting: "", }), casual: Args.flag({ help: "Ascend into and run casual.", + default: false, setting: "", }), robot: Args.flag({ help: "Ascend into and run You, Robot.", + default: false, + setting: "", + }), + zooto: Args.flag({ + help: "Ascend into and run Zootomist", + default: false, + setting: "", + }), + ih8u: Args.flag({ + help: "Ascend into and run 11 Things I Hate About U", + default: false, + setting: "", + }), + crimbo: Args.flag({ + help: "Farm Crimbo instead of Garbo", + default: false, + setting: "", + }), + chrono: Args.flag({ + help: "Farm TTT instead of Garbo", + default: false, + setting: "", + }), + ween: Args.flag({ + help: "Run Halloween instead of Garbo", + default: false, setting: "", }), @@ -122,6 +151,10 @@ export const args = Args.create( help: "The command that will do your You, Robot run for you. Include any arguments desired.", default: "", }), + ih8uscript: Args.string({ + help: "The command that will do your You, Robot run for you. Include any arguments desired.", + default: "", + }), pulls: Args.items({ help: "A list of items to pull at the start of the smol run.", default: [ @@ -139,6 +172,14 @@ export const args = Args.create( help: `The command that will be used to diet and use all your adventures in Day 2 aftercore.`, default: "garbo ascend", }), + crimboscript: Args.string({ + help: `The command that will be used to run a crimbo farming script - note that if no script is provided, we will default to garbo`, + default: "", + }), + chronoscript: Args.string({ + help: `The command that will be used to run a TTT farming script - note that if no script is provided, we will default to garbo`, + default: "", + }), itemcleanup: Args.string({ help: `The script that will be used to mallsale items after a run`, default: "", @@ -151,5 +192,9 @@ export const args = Args.create( help: "Define a specific monster for Garbo to target; note that the wrapper will find something useful for you!", default: "", }), + test: Args.flag({ + help: "test", + default: false + }) }, ); diff --git a/src/beret.ts b/src/beret.ts new file mode 100644 index 0000000..c6213f1 --- /dev/null +++ b/src/beret.ts @@ -0,0 +1,371 @@ +import { beretBuskingEffects, buy, canEquip, Effect, equip, equippedItem, getPower, Item, myFamiliar, myMeat, npcPrice, numericModifier, print, toEffect, toSlot, useFamiliar, useSkill } from "kolmafia"; +import { $effect, $familiar, $item, $skill, $slot, $slots, get, have as have_, logger, maxBy, NumericModifier, sum, unequip } from "libram"; + +// eslint-disable-next-line libram/verify-constants +const beret = $item`prismatic beret`; + +export type EffectValuer = + | Partial> + | ((effect: Effect, duration: number) => number) + | Effect[]; +const valueEffect = (effect: Effect, duration: number, valuer: EffectValuer) => + typeof valuer === "function" + ? valuer(effect, duration) + : Array.isArray(valuer) + ? Number(valuer.includes(effect)) * duration + : sum( + Object.entries(valuer), + ([modifier, weight]) => weight * numericModifier(effect, modifier), + ); + +/** + * @returns Whether or not you have the prismatic beret + */ +export function have(): boolean { + return have_(beret); +} + +function getUseableClothes(buyItem = true): { + useableHats: Item[]; + useablePants: Item[]; + useableShirts: Item[]; +} { + const availableItems = Item.all().filter( + (i) => canEquip(i) && (have_(i) || (buyItem && npcPrice(i) > 0)), + ); + const useableHats = have_($familiar`Mad Hatrack`) + ? [...availableItems.filter((i) => toSlot(i) === $slot`hat`), $item.none] + : [beret]; + const useablePants = [ + ...availableItems.filter((i) => toSlot(i) === $slot`pants`), + $item.none, + ]; + const useableShirts = [ + ...availableItems.filter((i) => toSlot(i) === $slot`shirt`), + $item.none, + ]; + return { useableHats, useablePants, useableShirts }; +} + +function availablePowersums(buyItem: boolean): number[] { + const taoMultiplier = have_($skill`Tao of the Terrapin`) ? 2 : 1; + + const { useableHats, useablePants, useableShirts } = + getUseableClothes(buyItem); + + const hatPowers = [ + ...new Set(useableHats.map((i) => taoMultiplier * getPower(i))), + ]; + const pantPowers = [ + ...new Set(useablePants.map((i) => taoMultiplier * getPower(i))), + ]; + const shirtPowers = [...new Set(useableShirts.map((i) => getPower(i)))]; + + return [ + ...new Set( + hatPowers.flatMap((hat) => + pantPowers.flatMap((pant) => + shirtPowers.flatMap((shirt) => hat + pant + shirt), + ), + ), + ), + ]; +} + +function scoreBusk( + effects: [Effect, number][], + effectValuer: EffectValuer, + uselessEffects: Set, +): number { + const usefulEffects = effects.filter( + ([effect]) => !uselessEffects.has(effect), + ); + return sum(usefulEffects, ([effect, duration]) => + valueEffect(effect, duration, effectValuer), + ); +} + +/** + * Calculate the optimal power-sum at which to busk, given a weighted set of modifiers. + * @param wantedEffects An array of Effects we care about; maximizes the number of those effects we end up with + * @param buskUses How many busks should we assume we've cast? Defaults to the current number. + * @param uselessEffects An array (defaults to empty) of effects not to consider for the purposes of busk valuation + * @param buyItem Whether or not we should consider purchasing items from NPC stores; defaults to true + * @returns The power-sum at which you'll find the optimal busk for this situation. + */ +export function findOptimalOutfitPower( + wantedEffects: Effect[], + buskUses?: number, + uselessEffects?: Effect[], + buyItem?: boolean, +): number; +/** + * Calculate the optimal power-sum at which to busk, given a weighted set of modifiers. + * @param weightedModifiers An object keyed by Numeric Modifiers, with their values representing weights + * @param buskUses How many busks should we assume we've cast? Defaults to the current number. + * @param uselessEffects An array (defaults to empty) of effects not to consider for the purposes of busk valuation + * @param buyItem Whether or not we should consider purchasing items from NPC stores; defaults to true + * @returns The power-sum at which you'll find the optimal busk for this situation. + */ +export function findOptimalOutfitPower( + weightedModifiers: Partial>, + buskUses?: number, + uselessEffects?: Effect[], + buyItem?: boolean, +): number; +/** + * Calculate the optimal power-sum at which to busk, given a weighted set of modifiers. + * @param valueFunction A function that maps effects to values + * @param buskUses How many busks should we assume we've cast? Defaults to the current number. + * @param uselessEffects An array (defaults to empty) of effects not to consider for the purposes of busk valuation + * @param buyItem Whether or not we should consider purchasing items from NPC stores; defaults to true + * @returns The power-sum at which you'll find the optimal busk for this situation. + */ +export function findOptimalOutfitPower( + valueFunction: (effect: Effect, duration: number) => number, + buskUses?: number, + uselessEffects?: Effect[], + buyItem?: boolean, +): number; +/** + * Calculate the optimal power-sum at which to busk, given a weighted set of modifiers. + * @param effectValuer Either a function that maps effect-duration pairs to values, or an object keyed by numeric modifiers with weights as values, or an array of desired effects + * @param buskUses How many busks should we assume we've cast? Defaults to the current number. + * @param uselessEffects An array (defaults to empty) of effects not to consider for the purposes of busk valuation + * @param buyItem Whether or not we should consider purchasing items from NPC stores; defaults to true + * @returns The power-sum at which you'll find the optimal busk for this situation. + */ +export function findOptimalOutfitPower( + effectValuer: EffectValuer, + buskUses?: number, + uselessEffects?: Effect[], + buyItem?: boolean, +): number; +/** + * Calculate the optimal power-sum at which to busk, given a weighted set of modifiers. + * @param effectValuer Either a function that maps effect-duration pairs to values, or an object keyed by numeric modifiers with weights as values, or an array of desired effects + * @param buskUses How many busks should we assume we've cast? Defaults to the current number. + * @param uselessEffects An array (defaults to empty) of effects not to consider for the purposes of busk valuation + * @param buyItem Whether or not we should consider purchasing items from NPC stores; defaults to true + * @returns The power-sum at which you'll find the optimal busk for this situation. + */ +export function findOptimalOutfitPower( + effectValuer: EffectValuer, + buskUses = get("_beretBuskingUses",0), + uselessEffects: Effect[] = [], + buyItem = true, +): number { + const uselessEffectSet = new Set(uselessEffects); + const powersums = availablePowersums(buyItem); + if (!powersums.length) return 0; + return maxBy(powersums, (power) => + scoreBusk( + Object.entries(beretBuskingEffects(power, buskUses)) + .map(([effect, duration]): [Effect, number] => [ + toEffect(effect), + duration, + ]) + .filter(([e]) => e !== $effect.none), + effectValuer, + uselessEffectSet, + ), + ); +} + +const populateMap = (arr: Item[], max: number, double: boolean) => { + const map = new Map(); + for (const it of arr) { + const power = getPower(it) * (double ? 2 : 1); + if (power > max) continue; + + const existing = map.get(power); + if ( + !existing || + (!have_(existing) && (have_(it) || npcPrice(it) < npcPrice(existing))) + ) { + map.set(power, it); + } + } + return map; +}; +const relevantSlots = ["hat", "pants", "shirt"] as const; + +function functionalPrice(item: Item): number { + const price = have_(item) || Item.none ? 0 : npcPrice(item); + return price; +} + +function outfitPrice(outfit: { hat: Item; pants: Item; shirt: Item }): number { + const thing = sum(relevantSlots, (slot) => functionalPrice(outfit[slot])); + return thing; +} + + +function findOutfit(power: number, buyItem: boolean) { + const { useableHats, useablePants, useableShirts } = + getUseableClothes(buyItem); + const hatPowers = populateMap( + useableHats, + power, + have_($skill`Tao of the Terrapin`), + ); + const pantsPowers = populateMap( + useablePants, + power, + have_($skill`Tao of the Terrapin`), + ); + const shirtPowers = populateMap(useableShirts, power, false); + + const outfits = [...hatPowers].flatMap(([hatPower, hat]) => + [...pantsPowers].flatMap(([pantsPower, pants]) => + [...shirtPowers].flatMap(([shirtPower, shirt]) => + hatPower + pantsPower + shirtPower === power + ? { hat, pants, shirt } + : [], + ), + ), + ); + if (!outfits.length) return null; + const outfit = maxBy(outfits, outfitPrice, true); + logger.debug(`Chose outfit ${outfit.hat} ${outfit.shirt} ${outfit.pants}`); + if (outfitPrice(outfit) > myMeat()) return null; + + for (const slot of relevantSlots) { + const item = outfit[slot]; + if (have_(item) || item === Item.none) { + print(`Have ${item}!`); + continue; + } + if (!buy(item)) { + logger.debug(`Failed to purchase ${item}`); + return null; + } + } + return outfit; +} + +/** + * Attempt to busk at a particular power + * @param power The power in question + * @param buyItem Whether to buy items from NPC shops to create an outfit + * @returns If we successfully busked at that power + */ +export function buskAt(power: number, buyItem = true): boolean { + if (!have()) return false; + const initialUses = get("_beretBuskingUses",0); + if (initialUses >= 5) return false; + const outfit = findOutfit(power, buyItem); + if (!outfit) return false; + const initialEquips = $slots`hat, shirt, pants`.map((slot) => + equippedItem(slot), + ); + const initialFamiliar = myFamiliar(); + const initialFamequip = equippedItem($slot`familiar`); + const { hat, pants, shirt } = outfit; + equip($slot`hat`, hat); + if (hat !== beret) { + useFamiliar($familiar`Mad Hatrack`); + equip($slot`familiar`, beret); + } + equip($slot`shirt`, shirt); + equip($slot`pants`, pants); + const taoMultiplier = have_($skill`Tao of the Terrapin`) ? 2 : 1; + try { + if ( + taoMultiplier * + (getPower(equippedItem($slot`hat`)) + + getPower(equippedItem($slot`pants`))) + + getPower(equippedItem($slot`shirt`)) !== + power + ) { + return false; + } + // eslint-disable-next-line libram/verify-constants + useSkill($skill`Beret Busking`); + return initialUses !== get("_beretBuskingUses",0); + } finally { + $slots`hat, shirt, pants`.forEach((slot, index) => + equip(slot, initialEquips[index]), + ); + if(initialFamiliar !== $familiar`Mad Hatrack` && myFamiliar() === $familiar`Mad Hatrack`) { + unequip($slot`familiar`); + } + useFamiliar(initialFamiliar); + equip($slot`familiar`, initialFamequip); + } +} + +export function buskFor( + weightedModifiers: Partial>, + buyItem?: boolean, + uselessEffects?: Effect[], +): boolean; +export function buskFor( + effects: Effect[], + buyItem?: boolean, + uselessEffects?: Effect[], +): boolean; +export function buskFor( + valueFunction: (effect: Effect, duration: number) => number, + buyItem?: boolean, + uselessEffects?: Effect[], +): boolean; +/** + * Calculate the best outfit-power you can achieve for a given busk valuation, and then busks. + * @param effectValuer Either a function that maps effect-duration pairs to values, or an object keyed by numeric modifiers with weights as values, or an array of desired effects + * @param buyItem Whether or not we should consider purchasing items from NPC stores; defaults to true + * @param uselessEffects An array (defaults to empty) of effects not to consider for the purposes of busk valuation + * @returns Whether we were successful in our endeavor + */ +export function buskFor( + effectValuer: EffectValuer, + buyItem = true, + uselessEffects: Effect[] = [], +): boolean { + const outfitPower = findOptimalOutfitPower( + effectValuer, + get("_beretBuskingUses",0), + uselessEffects, + buyItem, + ); + return buskAt(outfitPower, buyItem); +} + +function multipliers(): [number, number] { + const taoHatMultiplier = have_($skill`Tao of the Terrapin`) ? 2 : 1; + const taoPantsMultiplier = have_($skill`Tao of the Terrapin`) ? 1 : 0; + const hammerTimeMultiplier = have_($effect`Hammertime`) ? 3 : 0; + const totalPantsMultiplier = 1 + hammerTimeMultiplier + taoPantsMultiplier; + + return [taoHatMultiplier, totalPantsMultiplier]; +} + +export function reconstructOutfit(daRaw: number): { hat: Item, pants: Item, shirt: Item } { + const allItems = Item.all().filter((i) => have_(i) && canEquip(i)); + const shopItems = Item.all().filter((i) => npcPrice(i) > 0 && canEquip(i)); + allItems.push(...shopItems); + const allHats = () => have_($familiar`Mad Hatrack`) + ? allItems.filter((i) => toSlot(i) === $slot`hat`) + : [beret]; + const allPants = allItems.filter((i) => toSlot(i) === $slot`pants`); + const allShirts = allItems.filter((i) => toSlot(i) === $slot`shirt`); + + for (const hat of allHats()) { + const hatPower = multipliers()[0] * getPower(hat); + for (const shirt of allShirts) { + const shirtPower = getPower(shirt); + for (const pants of allPants) { + const pantsPower = multipliers()[1] * getPower(pants); + if (shirtPower + hatPower + pantsPower === daRaw) { + return { hat, pants, shirt }; + } + } + } + } + + return { + hat: new Item, + pants: new Item, + shirt: new Item +}; +} diff --git a/src/engine/engine.ts b/src/engine/engine.ts index 50f861c..13e1422 100644 --- a/src/engine/engine.ts +++ b/src/engine/engine.ts @@ -1,7 +1,9 @@ import { CombatResources, CombatStrategy, Engine } from "grimoire-kolmafia"; -import { cliExecute, Location, logprint, setAutoAttack, writeCcs } from "kolmafia"; -import { clearMaximizerCache } from "libram"; +import { cliExecute, equippedAmount, Location, logprint, setAutoAttack, writeCcs } from "kolmafia"; +import { $item, clearMaximizerCache, get, JuneCleaver, PropertiesManager } from "libram"; + import { getCurrentLeg, Task } from "../tasks/structure"; + import { printProfits, ProfitTracker } from "./profits"; const grimoireCCS = "grimoire_macro"; @@ -14,16 +16,30 @@ export class ProfitTrackingEngine extends Engine { this.profits = new ProfitTracker(key); } + setChoices(task: Task, manager: PropertiesManager): void { + super.setChoices(task, manager); + if (equippedAmount($item`June cleaver`) > 0) { + this.propertyManager.setChoices( + Object.fromEntries( + JuneCleaver.choices.map((choice) => [ + choice, + shouldSkip(choice) ? 4 : bestJuneCleaverOption(choice), + ]), + ), + ); + } + } + setCombat( task: Task, task_combat: CombatStrategy, - task_resources: CombatResources + task_resources: CombatResources, ): void { // Save regular combat macro const macro = task_combat.compile( task_resources, this.options?.combat_defaults, - task.do instanceof Location ? task.do : undefined + task.do instanceof Location ? task.do : undefined, ); if (macro.toString().length > 1) { macro.save(); @@ -80,3 +96,23 @@ export class ProfitTrackingEngine extends Engine { printProfits(this.profits.all()); } } +function shouldSkip(choice: number): boolean { + const skip = [1468, 1470, 1472, 1473, 1474]; + return skip.includes(choice) && get("_juneCleaverSkips") < 5; +} + +function bestJuneCleaverOption(choice: number): number { + const choiceTable: { [key: number]: number } = { + 1467: 3, // Poetic Justice + 1468: 2, // Aunts not Ants + 1469: 3, // Beware of Alligator + 1470: 2, // Teacher's Pet + 1471: 1, // Lost and Found + 1472: 1, // Summer Days + 1473: 1, // Bath Time + 1474: 2, // Delicious Sprouts + 1475: 1, // Hypnotic Master + }; + + return choiceTable[choice] ?? 0; +} diff --git a/src/engine/leprecondo.ts b/src/engine/leprecondo.ts new file mode 100644 index 0000000..1f83597 --- /dev/null +++ b/src/engine/leprecondo.ts @@ -0,0 +1,125 @@ +import { Effect, Item } from "kolmafia"; +import { $effect, $item, arrayEquals, get, Leprecondo, maxBy, sum, Tuple } from "libram"; +import { garboAverageValue, garboValue } from "./profits"; +import { Task } from "../tasks/structure"; + +function resultValue(result: Leprecondo.Result): number { + if (result instanceof Item) return garboValue(result); + if (Array.isArray(result)) return garboAverageValue(...result); + if (result instanceof Effect) { + if (result === $effect`Your Days Are Numbed` || result === $effect`Counter Intelligence`) return 825; + } + return 0; +} + +/* + * @returns Whether `a` is strictly better than `b` + */ +function strictlyBetterThan( + a: Leprecondo.FurnitureStat, + b: Leprecondo.FurnitureStat, +): boolean { + return Leprecondo.NEEDS.every((need) => { + const result = b[need]; + if (!result) return true; + const other = a[need]; + if (!other) return false; + if (resultValue(other) <= resultValue(result)) return false; + return true; + }); +} + +function getStat( + furniture: Leprecondo.FurniturePiece, +): Leprecondo.FurnitureStat { + return Leprecondo.Furniture[furniture]; +} + +function getCoveredNeeds( + furniture: Leprecondo.FurniturePiece, +): Leprecondo.Need[] { + return Object.keys(getStat(furniture)) as Leprecondo.Need[]; +} + +function viableFurniture(): Leprecondo.FurniturePiece[] { + const discovered = Leprecondo.discoveredFurniture(); + return [ + "empty", + ...discovered.filter( + (f, index) => + !discovered + .slice(index) + .some((futureFurniture) => + strictlyBetterThan(getStat(futureFurniture), getStat(f)), + ), + ), + ]; +} + +type Combination = Tuple; + +function valueCombination(combo: Combination): number { + const total = Leprecondo.furnitureBonuses(combo); + return sum(Leprecondo.NEEDS, (need) => + resultValue(total[need] ?? $item.none), + ); +} + +function buildCombination( + combinations: Tuple[], + furniture: Leprecondo.FurniturePiece[], +): [...Tuple, Leprecondo.FurniturePiece][] { + return combinations.flatMap((combination) => { + const coveredNeeds = new Set(combination.flatMap(getCoveredNeeds)); + const plausibleFurniture = furniture.filter((f) => + getCoveredNeeds(f).some((need) => !coveredNeeds.has(need)), + ); // Only furniture that cover at least one presently-uncovered need need apply + return ( + plausibleFurniture.length ? plausibleFurniture : (["empty"] as const) + ).map( + ( + furniture, + ): [ + ...Tuple, + Leprecondo.FurniturePiece, + ] => [...combination, furniture], + ); + }); +} + +function getViableCombinations(): Combination[] { + const furniture = viableFurniture(); + return Array(4) + .fill(null) + .reduce( + (acc) => buildCombination(acc, furniture), + [[]], + ) as Combination[]; +} + +function findBestCombination(): Combination { + return maxBy(getViableCombinations(), valueCombination); +} + +let bestCombination: Combination; +let unlocked: string; +function getBestLeprecondoCombination(): Combination { + if (unlocked !== get("leprecondoDiscovered")) { + unlocked = get("leprecondoDiscovered"); + bestCombination = findBestCombination(); + } + return bestCombination; +} + +export function leprecondoTask(): Task { + return { + name: "Configure Leprecondo", + ready: () => Leprecondo.have() && Leprecondo.rearrangesRemaining() > 0, + completed: () => + arrayEquals( + Leprecondo.installedFurniture(), + getBestLeprecondoCombination(), + ), + do: () => Leprecondo.setFurniture(...getBestLeprecondoCombination()), + }; +} diff --git a/src/main.ts b/src/main.ts index ebfc459..99b448c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,16 +1,14 @@ import { Args, getTasks } from "grimoire-kolmafia"; -import { gamedayToInt, print } from "kolmafia"; +import { gamedayToInt, getRelated, Item, mallPrice, print } from "kolmafia"; import { args } from "./args"; import { ProfitTrackingEngine } from "./engine/engine"; -import { GarboWeenQuest } from "./tasks/Garboween"; -import { AftercoreQuest } from "./tasks/aftercoreleg"; -import { AscendQuest } from "./tasks/ascend"; -import { CasualQuests } from "./tasks/casualrunleg"; -import { CSQuests } from "./tasks/csrunleg"; -import { RobotQuests } from "./tasks/robotrunleg"; -import { SmolQuests } from "./tasks/smolrunleg"; -import { deleteJunkKmails, halloween, realDay, realMonth } from "./tasks/utils"; +import { AftercoreQuest } from "./tasks/1 aftercoreleg"; +import { AscendQuest } from "./tasks/2 ascend"; +import { RunQuests } from "./tasks/3 runleg"; +import { PostRunQuests } from "./tasks/4 postrunleg"; +import { deleteJunkKmails, halloween, notifyVoters, realDay, realMonth } from "./tasks/utils"; +import { $item } from "libram"; const version = "0.0.3"; @@ -23,6 +21,40 @@ export function main(command?: string): void { return; } + if (args.test) { + const coldWadPrice = mallPrice($item`stench wad`); + const twinklyWadPrice = mallPrice($item`twinkly wad`); + + const results = Item.all() + .map((item) => { + const p = getRelated(item, "pulverize"); + + const value = + // cold + (p?.["stench wad"] ?? 0) * coldWadPrice / 1_000_000 + + (p?.["stench nuggets"] ?? 0) * coldWadPrice / 5 / 1_000_000 + + (p?.["stench powder"] ?? 0) * coldWadPrice / 25 / 1_000_000 + + + // twinkly + (p?.["twinkly wad"] ?? 0) * twinklyWadPrice / 1_000_000 + + (p?.["twinkly nuggets"] ?? 0) * twinklyWadPrice / 5 / 1_000_000 + + (p?.["twinkly powder"] ?? 0) * twinklyWadPrice / 25 / 1_000_000; + + const price = mallPrice(item) > 0 ? mallPrice(item) : Infinity; + const net = value - price; + const roi = net / price; + + return { item, value, price, net, roi }; + }) + .filter(o => o.net > 0) + .sort((a, b) => b.net - a.net); + + for (const { item, net, roi } of results) { + print(`${item}: net ${net.toFixed(0)} meat (ROI ${(roi * 100).toFixed(1)}%)`); + } + + } + if (dontCS && args.halloween && args.cs) { throw `Tomorrow is halloween, run something that lets you get steel organs!`; } @@ -31,29 +63,14 @@ export function main(command?: string): void { throw `Today is halloween, run CS for more organ space!`; } - /*if (args.profits) { - printProfits(this.profits.all()); - return; - };*/ - print(`Running: candyWrapper v${version}`); - const runQuest = args.cs - ? CSQuests() - : args.smol - ? SmolQuests() - : args.casual - ? CasualQuests() - : args.robot - ? RobotQuests() - : undefined; - if (runQuest === undefined) throw "Undefined runtype; please choose either cs or smol"; + if (!args.cs && !args.smol && !args.casual && !args.robot && !args.zooto && !args.ih8u) + throw "Undefined runtype; please choose an acceptable path"; - const tasks = halloween - ? getTasks([GarboWeenQuest(), AscendQuest(), ...runQuest]) - : getTasks([AftercoreQuest(), AscendQuest(), ...runQuest]); + const tasks = getTasks([AftercoreQuest(), AscendQuest(), RunQuests(), PostRunQuests()]); - if (tasks === undefined) throw "Undefined runtype; please choose either cs or smol"; + if (tasks === undefined) throw "Undefined runtype; please choose a valid run type"; if (args.abort) { const to_abort = tasks.find((task) => task.name === args.abort); @@ -75,4 +92,5 @@ export function main(command?: string): void { engine.destruct(); } deleteJunkKmails(); + notifyVoters(); } diff --git a/src/relay.ts b/src/relay.ts index 4c50dc8..7a279c4 100644 --- a/src/relay.ts +++ b/src/relay.ts @@ -7,6 +7,7 @@ import { handleApiRequest, RelayPage, } from "mafia-shared-relay"; + import { args } from "./args"; function convertArgsToHtml(): RelayPage[] { @@ -40,7 +41,7 @@ function convertArgsToHtml(): RelayPage[] { }, (group, name: string) => { pages.push({ page: name, components: [] }); - } + }, ); pages @@ -48,9 +49,7 @@ function convertArgsToHtml(): RelayPage[] { .forEach((p) => { const html: ComponentHtml = { type: "html", - data: `

CandyWrapper ${ - p.page - }`, + data: `

CandyWrapper ${p.page}`, }; p.components.splice(0, 0, html); }); @@ -61,7 +60,5 @@ function convertArgsToHtml(): RelayPage[] { export function main() { if (handleApiRequest()) return; - write( - generateHTML(convertArgsToHtml()) - ); + write(generateHTML(convertArgsToHtml())); } diff --git a/src/tasks/aftercoreleg.ts b/src/tasks/1 aftercoreleg.ts similarity index 51% rename from src/tasks/aftercoreleg.ts rename to src/tasks/1 aftercoreleg.ts index 391391d..b6ef990 100644 --- a/src/tasks/aftercoreleg.ts +++ b/src/tasks/1 aftercoreleg.ts @@ -1,25 +1,18 @@ import { CombatStrategy } from "grimoire-kolmafia"; import { - availableAmount, buy, cliExecute, - getCampground, - getClanName, + daycount, getWorkshed, - guildStoreAvailable, - handlingChoice, - haveEffect, haveEquipped, hippyStoneBroken, inebrietyLimit, + Item, itemAmount, mallPrice, myAdventures, - myClass, myDaycount, - myHp, myInebriety, - myLevel, myMaxhp, myPrimestat, print, @@ -27,13 +20,13 @@ import { restoreHp, restoreMp, retrieveItem, + runChoice, toBoolean, use, useFamiliar, visitUrl, } from "kolmafia"; import { - $class, $coinmaster, $effect, $effects, @@ -41,34 +34,90 @@ import { $item, $items, $location, - $phylum, $skill, - $stat, - AprilingBandHelmet, - AsdonMartin, - DNALab, + byStat, get, getTodaysHolidayWanderers, have, Macro, - set, + maxBy, + TakerSpace, TrainSet, + Tuple, uneffect, } from "libram"; import { Cycle, setConfiguration, Station } from "libram/dist/resources/2022/TrainSet"; import { args } from "../args"; +import { chrono, crimbo, garboWeen, noBarf, postRunQuests, preRunQuests } from "./repeatableTasks"; import { Quest } from "./structure"; -import { - bestFam, - getGarden, - isGoodGarboScript, - maxBase, - pvpCloset, - stooperDrunk, - totallyDrunk, -} from "./utils"; +import { maxBase, pvpCloset, stooperDrunk, totallyDrunk } from "./utils"; +import { garboValue } from "../engine/profits"; + +const RESOURCES = ["Spice", "Rum", "Anchor", "Mast", "Silk", "Gold"] as const; +export type Resource = (typeof RESOURCES)[number]; + +export type Recipe = Tuple; +const RECIPES = new Map([ + [$item`deft pirate hook`, [0, 0, 1, 1, 0, 1]], + [$item`iron tricorn hat`, [0, 0, 2, 1, 0, 0]], + [$item`jolly roger flag`, [0, 1, 0, 1, 1, 0]], + [$item`sleeping profane parrot`, [15, 3, 0, 0, 2, 1]], + [$item`pirrrate's currrse`, [2, 2, 0, 0, 0, 0]], + [$item`tankard of spiced rum`, [1, 2, 0, 0, 0, 0]], + [$item`packaged luxury garment`, [0, 0, 0, 0, 3, 2]], + [$item`harpoon`, [0, 0, 0, 2, 0, 0]], + [$item`chili powder cutlass`, [5, 0, 1, 0, 0, 0]], + [$item`cursed Aztec tamale`, [2, 0, 0, 0, 0, 0]], + [$item`jolly roger tattoo kit`, [0, 6, 1, 1, 0, 6]], + [$item`golden pet rock`, [0, 0, 0, 0, 0, 7]], + [$item`groggles`, [0, 6, 0, 0, 0, 0]], + [$item`pirate dinghy`, [0, 0, 1, 1, 1, 0]], + [$item`anchor bomb`, [0, 1, 3, 1, 0, 1]], + [$item`silky pirate drawers`, [0, 0, 0, 0, 2, 0]], + [$item`spices`, [1, 0, 0, 0, 0, 0]], +]); + +/** + * @returns A copy of our map of all recipes + */ +export function allRecipes(): Map { + return new Map( + [...RECIPES.entries()].map(([item, recipe]) => [item, [...recipe]]), + ); +} + +export function affordableRecipes(): Item[] { + const recipes = allRecipes(); // Get all recipes + print(`Recipes Map: ${recipes}`); + + const result = [...recipes.keys()].filter((item) => TakerSpace.canMake(item)); + print(`Final affordable items array: ${result}`); + + return result; +} + +function takerSpaceOptimizer(): boolean { + let recipes = affordableRecipes(); + print(`Recipes after affordableRecipes: ${recipes}`); + + while (recipes.length > 0) { + print(`Current recipe list: ${recipes}`); + + // Find the best item to craft based on garboValue + const bestRecipe = maxBy(recipes, garboValue); + print(`Best recipe to craft: ${bestRecipe}`); + + // Craft the selected recipe + TakerSpace.make(bestRecipe); + + // Recompute the list of craftable recipes after crafting + recipes = affordableRecipes(); + } + + return true; +} const doSmol = args.smol ? true : false; const doCS = args.cs ? true : false; @@ -95,6 +144,7 @@ const stations = [ export function AftercoreQuest(): Quest { return { name: "Aftercore", + ready: () => myDaycount() >= 2 && get("kingLiberated"), completed: () => (myAdventures() === 0 && totallyDrunk() && @@ -104,9 +154,15 @@ export function AftercoreQuest(): Quest { myDaycount() === 1, tasks: [ { - name: "Whitelist VIP Clan", - completed: () => !args.clan || getClanName().toLowerCase() === args.clan.toLowerCase(), - do: () => cliExecute(`/whitelist ${args.clan}`), + name: "Takerspace", + ready: () => getWorkshed() === $item`TakerSpace letter of Marque` && !get("_workshedItemUsed"), + completed: () => getWorkshed() === $item`model train set`, + do: () => { + visitUrl("campground.php?action=workshed"); + takerSpaceOptimizer() + use($item`model train set`); + }, + tracking: "Workshed" }, { name: "Pre-Run Photobooth", @@ -124,255 +180,12 @@ export function AftercoreQuest(): Quest { completed: () => toBoolean(get("_safetyCloset1")), do: () => pvpCloset(1), }, - { - name: "Get Floundry item", - ready: () => have($item`Clan VIP Lounge key`) && !args.carpe, - completed: () => get("_floundryItemCreated"), - do: (): void => { - retrieveItem($item`carpe`); - }, - limit: { tries: 1 }, - }, - { - name: "Prep Fireworks Shop", - completed: () => !have($item`Clan VIP Lounge key`) || get("_goorboFireworksPrepped", false), - do: () => { - visitUrl("clan_viplounge.php?action=fwshop&whichfloor=2"); - set("_goorboFireworksPrepped", true); - }, - }, - { - name: "Breakfast", - completed: () => get("breakfastCompleted"), - do: () => cliExecute("breakfast"), - }, - { - name: "Apriling", - ready: () => AprilingBandHelmet.canChangeSong(), - completed: () => have($effect`Apriling Band Celebration Bop`), - do: (): void => { - AprilingBandHelmet.conduct($effect`Apriling Band Celebration Bop`); - }, - limit: { tries: 1 }, - }, - { - name: "Harvest Garden", - completed: () => - getGarden() === $item`none` || - getGarden() === $item`packet of mushroom spores` || - getCampground()[getGarden().name] === 0, - do: () => cliExecute("garden pick"), - tracking: "Dailies", - limit: { tries: 3 }, - }, - { - name: "Plant Grass", - completed: () => - !have($item`packet of tall grass seeds`) || - getGarden() === $item`packet of tall grass seeds`, - do: () => use($item`packet of tall grass seeds`), - }, - { - name: "SIT Course", - // eslint-disable-next-line libram/verify-constants - ready: () => have($item`S.I.T. Course Completion Certificate`), - completed: () => get("_sitCourseCompleted", false), - choices: { - 1494: 2, - }, - do: () => - // eslint-disable-next-line libram/verify-constants - use($item`S.I.T. Course Completion Certificate`), - }, - { - name: "Drive Observantly", - completed: () => - get("dailyDungeonDone") || - getWorkshed() !== $item`Asdon Martin keyfob (on ring)` || - haveEffect($effect`Driving Observantly`) >= - (totallyDrunk() || !have($item`Drunkula's wineglass`) - ? myAdventures() - : myAdventures() + 60), - do: () => - AsdonMartin.drive( - $effect`Driving Observantly`, - totallyDrunk() || !have($item`Drunkula's wineglass`) - ? myAdventures() - : myAdventures() + 60, - false, - ), - limit: { tries: 5 }, - }, - { - name: "Sample Constellation DNA", - ready: () => have($item`DNA extraction syringe`), - completed: () => - !DNALab.installed() || - DNALab.isHybridized($phylum`Constellation`) || - get("dnaSyringe") === $phylum`Constellation`, - outfit: { - familiar: bestFam(), - modifier: `${maxBase()}`, - }, - do: $location`The Hole in the Sky`, - combat: new CombatStrategy() - .macro(Macro.skill($skill`Curse of Weaksauce`), getTodaysHolidayWanderers()) - .macro(Macro.tryItem($item`DNA extraction syringe`)) - .macro( - Macro.tryItem($item`train whistle`) - .tryItem($item`porquoise-handled sixgun`) - .trySkill($skill`Sing Along`) - .attack() - .repeat(), - ), - }, - { - name: "Hybridize Constellation", - ready: () => get("dnaSyringe") === $phylum`Constellation`, - completed: () => !DNALab.installed() || DNALab.isHybridized($phylum`Constellation`), - do: () => { - DNALab.makeTonic(3); - DNALab.hybridize(); - }, - }, - { - name: "LGR Seed", - ready: () => - //best guess if we're going to Dinseylandfill later in the day - isGoodGarboScript(args.garboascend) || - args.pulls.includes($item`one-day ticket to Dinseylandfill`), - completed: () => - !have($item`lucky gold ring`) || get("_stenchAirportToday") || get("stenchAirportAlways"), - do: () => use($item`one-day ticket to Dinseylandfill`), - }, - { - name: "June Cleaver", - completed: () => - !have($item`June cleaver`) || get("_juneCleaverFightsLeft") > 0 || myAdventures() === 0, - choices: { - 1467: 3, //Poetic Justice - 1468: get("_juneCleaverSkips") < 5 ? 4 : 2, //Aunts not Ants - 1469: 3, //Beware of Aligator - 1470: get("_juneCleaverSkips") < 5 ? 4 : 2, //Teacher's Pet - 1471: 1, //Lost and Found - 1472: get("_juneCleaverSkips") < 5 ? 4 : 1, //Summer Days - 1473: get("_juneCleaverSkips") < 5 ? 4 : 1, //Bath Time - 1474: get("_juneCleaverSkips") < 5 ? 4 : 2, //Delicious Sprouts - 1475: 1, //Hypnotic Master - }, - do: $location`Noob Cave`, - post: () => { - if (handlingChoice()) visitUrl("main.php"); - if (have($effect`Beaten Up`)) uneffect($effect`Beaten Up`); - }, - outfit: () => ({ equip: $items`June cleaver` }), - limit: undefined, - }, - { - name: "Restore HP", - completed: () => myHp() > 0.5 * myMaxhp(), - do: () => restoreHp(0.95 * myMaxhp()), - }, - { - name: "Implement Glitch", - ready: () => have($item`[glitch season reward name]`), - completed: () => get("_glitchItemImplemented"), - do: () => use($item`[glitch season reward name]`), - }, - { - name: "Unlock Guild", - ready: () => - myClass() === $class`Seal Clubber` && - Math.min( - ...$items`figurine of a wretched-looking seal, seal-blubber candle`.map((it) => - availableAmount(it), - ), - ) < 20 && - doSmol, - completed: () => guildStoreAvailable() || myAdventures() === 0 || stooperDrunk(), - do: () => cliExecute("guild"), - choices: { - //sleazy back alley - 108: 4, //craps: skip - 109: 1, //drunken hobo: fight - 110: 4, //entertainer: skip - 112: 2, //harold's hammer: skip - 21: 2, //under the knife: skip - //haunted pantry - 115: 1, //drunken hobo: fight - 116: 4, //singing tree: skip - 117: 1, //knob goblin chef: fight - 114: 2, //birthday cake: skip - //outskirts of cobb's knob - 113: 2, //knob goblin chef: fight - 111: 3, //chain gang: fight - 118: 2, //medicine quest: skip - }, - outfit: () => ({ - familiar: bestFam(), - modifier: `${maxBase()}, ${ - myPrimestat() === $stat`Muscle` ? "100 combat rate 20 max" : "-100 combat rate" - }, 250 bonus carnivorous potted plant`, - }), - combat: new CombatStrategy() - .macro( - () => - Macro.step("pickpocket") - .externalIf( - have($skill`Curse of Weaksauce`), - Macro.trySkill($skill`Curse of Weaksauce`), - Macro.tryItem($item`electronics kit`), - ) - .tryItem($item`porquoise-handled sixgun`) - .trySkill($skill`Sing Along`) - .attack() - .repeat(), - getTodaysHolidayWanderers(), - ) - .macro(() => - Macro.step("pickpocket") - .trySkill($skill`Sing Along`) - .tryItem($item`porquoise-handled sixgun`) - .attack() - .repeat(), - ), - }, - { - name: "Stock Up on MMJs", - ready: () => - guildStoreAvailable() && - (myClass().primestat === $stat`Mysticality` || - (myClass() === $class`Accordion Thief` && myLevel() >= 9)), - completed: () => availableAmount($item`magical mystery juice`) >= 500, - acquire: [ - { - item: $item`magical mystery juice`, - num: 500, - }, - ], - do: () => false, - }, - { - name: "Buy Seal Summoning Supplies", - ready: () => myClass() === $class`Seal Clubber` && guildStoreAvailable(), - completed: () => - Math.min( - ...$items`figurine of a wretched-looking seal, seal-blubber candle`.map((it) => - availableAmount(it), - ), - ) >= 40, - acquire: $items`figurine of a wretched-looking seal, seal-blubber candle`.map((it) => ({ - item: it, - num: 500, - })), - do: () => false, - }, - { - name: "PvP Closet Safety 2", - ready: () => args.pvp && get("autoSatisfyWithCloset") && !args.safepvp, - completed: () => toBoolean(get("_safetyCloset2")), - do: () => pvpCloset(2), - }, + ...preRunQuests(), + ...postRunQuests(), + ...noBarf(), + ...garboWeen(), + ...crimbo(), + ...chrono(), { name: "Garbo", completed: () => stooperDrunk() || myAdventures() === 0, @@ -399,12 +212,13 @@ export function AftercoreQuest(): Quest { useFamiliar($familiar`Stooper`); cliExecute("drink stillsuit distillate"); }, + tracking: "Organs", }, { name: "Barfing Drunk with Stooper", ready: () => stooperDrunk() && have($familiar`Stooper`) && !have($item`Drunkula's wineglass`), - completed: () => myAdventures() === 0 || totallyDrunk(), + completed: () => myAdventures() === 0 || totallyDrunk() || daycount() === 1, acquire: [{ item: $item`seal tooth` }], outfit: () => ({ familiar: $familiar`Stooper`, @@ -435,18 +249,29 @@ export function AftercoreQuest(): Quest { .repeat(), ), limit: { tries: 30 }, + tracking: "Garbo" }, { name: "Nightcap (Wine Glass)", ready: () => have($item`Drunkula's wineglass`), completed: () => totallyDrunk(), - do: () => cliExecute(`CONSUME NIGHTCAP VALUE ${get("valueOfAdventure") - 1000}`), + do: () => { + if($familiar`Cooler Yeti`.experience >= 400) { + useFamiliar($familiar`Cooler Yeti`); + visitUrl("main.php?talktoyeti=1", false); + runChoice(2); + useFamiliar($familiar`Stooper`); + } + cliExecute(`CONSUME NIGHTCAP VALUE ${get("valueOfAdventure") - 1000}`); + }, + tracking: "Organs" }, { name: "Nightcap (Marginal)", ready: () => have($item`Beach Comb`) || have($item`Map to Safety Shelter Grimace Prime`), completed: () => totallyDrunk(), do: () => cliExecute(`CONSUME NIGHTCAP VALUE 500`), + tracking: "Organs" }, { name: "Grimace Maps", @@ -454,13 +279,35 @@ export function AftercoreQuest(): Quest { completed: () => myAdventures() === 0 || !have($item`Map to Safety Shelter Grimace Prime`), do: () => cliExecute("grimace maps"), limit: { tries: 30 }, + tracking: "Bonus" + }, + { + name: "Trip Scrip", + ready: () => args.ih8u || args.smol || args.casual, + completed: () => have($item`Shore Inc. Ship Trip Scrip`) || myInebriety() > inebrietyLimit(), + do: $location`The Shore, Inc. Travel Agency`, + outfit: () => { + if (!get("candyCaneSwordShore")) return { equip: $items`candy cane sword cane` }; + else return {}; + }, + choices: () => { + const swordReady = haveEquipped($item`candy cane sword cane`) && !get("candyCaneSwordShore"); + const statChoice = byStat({ + Muscle: 1, + Mysticality: 2, + Moxie: 3, + }); + return { 793: swordReady ? 5 : statChoice }; + }, + limit: { tries: 1 }, + tracking: "Trip Scrip" }, { name: "Garbo (Drunk)", ready: () => have($item`Drunkula's wineglass`), prepare: () => uneffect($effect`Beaten Up`), completed: () => myAdventures() === 0, - do: () => cliExecute("garbo ascend"), + do: () => cliExecute(`${args.garboascend}`), post: () => $effects`Power Ballad of the Arrowsmith, Stevedave's Shanty of Superiority, The Moxious Madrigal, The Magical Mojomuscular Melody, Aloysius' Antiphon of Aptitude, Ur-Kel's Aria of Annoyance` .filter((ef) => have(ef)) @@ -469,7 +316,7 @@ export function AftercoreQuest(): Quest { tracking: "Garbo", }, { - name: "PvP Closet Safety 3", + name: "PvP Closet Safety 2", ready: () => args.pvp && get("autoSatisfyWithCloset") && !args.safepvp, completed: () => toBoolean(get("_safetyCloset3")), do: () => pvpCloset(3), @@ -479,6 +326,7 @@ export function AftercoreQuest(): Quest { ready: () => have($item`Beach Comb`), completed: () => myAdventures() === 0, do: () => cliExecute(`combo ${11 - get("_freeBeachWalksUsed") + myAdventures()}`), + tracking: "Combo" }, { name: "Turn in FunFunds", @@ -522,6 +370,7 @@ export function AftercoreQuest(): Quest { "acquire Pizza of Legend; acquire Frosty's frosty mug; acquire Ol' Scratch's salad fork", ); }, + tracking: "Ascension Prep" }, { name: "Marble Soda!", @@ -535,9 +384,11 @@ export function AftercoreQuest(): Quest { skipSoda = true; } }, + tracking: "Ascension Prep" }, { name: "Prepare for LoopCS", + ready: () => doCS, completed: () => have($item`Pizza of Legend`) && have($item`Deep Dish of Legend`) && @@ -548,6 +399,23 @@ export function AftercoreQuest(): Quest { !have($item`Calzone of Legend`) ? retrieveItem($item`Calzone of Legend`) : undefined; !have($item`borrowed time`) ? retrieveItem($item`borrowed time`) : undefined; }, + tracking: "Ascension Prep" + }, + { + name: "Prepare for IH8U", + ready: () => args.ih8u, + completed: () => + have($item`mini kiwi invisible dirigible`) && + have($item`mini kiwi digitized cookies`) && + have($item`mini kiwi intoxicating spirits`) && + have($item`incredible mini-pizza`), + do: (): void => { + retrieveItem($item`mini kiwi invisible dirigible`); + retrieveItem($item`mini kiwi digitized cookies`); + retrieveItem($item`mini kiwi intoxicating spirits`); + retrieveItem($item`incredible mini-pizza`); + }, + tracking: "Ascension Prep" }, { name: "Let's do the trainset again", diff --git a/src/tasks/ascend.ts b/src/tasks/2 ascend.ts similarity index 64% rename from src/tasks/ascend.ts rename to src/tasks/2 ascend.ts index a0e4d79..815116c 100644 --- a/src/tasks/ascend.ts +++ b/src/tasks/2 ascend.ts @@ -1,18 +1,21 @@ import { cliExecute, + equip, handlingChoice, myAdventures, myDaycount, runChoice, visitUrl, } from "kolmafia"; -import { $class, $item, $path, ascend, CursedMonkeyPaw, have } from "libram"; +import { $class, $item, $path, $skill, $skills, ascend, CursedMonkeyPaw, have } from "libram"; import { args } from "../args"; import { targetPerms } from "./perm"; import { Quest } from "./structure"; -import { toMoonSign, totallyDrunk } from "./utils"; +import { availableCasts, castDownTo, toMoonSign, totallyDrunk } from "./utils"; + +const skipPizza = args.cs || args.smol ? false : true export function AscendQuest(): Quest { return { @@ -20,12 +23,25 @@ export function AscendQuest(): Quest { ready: () => myAdventures() === 0 && totallyDrunk(), completed: () => myDaycount() === 1, tasks: [ + { + name: "Spend them stats grrrrl", + ready: () => have($item`blood cubic zirconia`), + completed: () => availableCasts($skill`BCZ: Prepare Spinal Tapas`,0) === 0 && + availableCasts($skill`BCZ: Craft a Pheromone Cocktail`,0) === 0 && + availableCasts($skill`BCZ: Create Blood Thinner`,0) === 0, + do: () => { + equip($item`blood cubic zirconia`); + $skills`BCZ: Prepare Spinal Tapas, BCZ: Craft a Pheromone Cocktail, BCZ: Create Blood Thinner`.forEach((sk) => castDownTo(sk, 0)); + }, + tracking: "Other" + }, { name: "Do the Ascension", ready: () => - have($item`Pizza of Legend`) && + (have($item`Pizza of Legend`) && have($item`Deep Dish of Legend`) && - have($item`Calzone of Legend`), + have($item`Calzone of Legend`)) || + skipPizza, completed: () => myDaycount() === 1, //Change this do: (): void => { const [skills, permLifestyle] = targetPerms(); @@ -41,6 +57,10 @@ export function AscendQuest(): Quest { ? $path.none : args.robot ? $path`You, Robot` + : args.zooto + ? $path`Z is for Zootomist` + : args.ih8u + ? $path`11 Things I Hate About U` : undefined; const lifestyle = args.casual ? 1 : 2; @@ -57,7 +77,7 @@ export function AscendQuest(): Quest { ascend({ path: path, - playerClass: myClass, + playerClass: args.zooto ? $class`Zootomist` : myClass, lifestyle: lifestyle, moon: moonsign, consumable: $item`astral six-pack`, diff --git a/src/tasks/3 runleg.ts b/src/tasks/3 runleg.ts new file mode 100644 index 0000000..04737e9 --- /dev/null +++ b/src/tasks/3 runleg.ts @@ -0,0 +1,140 @@ +import { step } from "grimoire-kolmafia"; +import { + cliExecute, + drink, + drinksilent, + eat, + equip, + fullnessLimit, + inebrietyLimit, + itemAmount, + myFullness, + myInebriety, + retrieveItem, + useSkill, + visitUrl, +} from "kolmafia"; +import { $item, $skill, clamp, get, have } from "libram"; + +import { args } from "../args"; + +import { preRunQuests } from "./repeatableTasks"; +import { Quest } from "./structure"; +import { shouldWeOverdrink } from "./utils"; + +const runType = () => + args.smol + ? args.smolscript + : args.cs + ? args.csscript + : args.casual + ? args.casualscript + : args.robot + ? args.robotscript + : args.ih8u + ? args.ih8uscript + : "autoscend"; + +export function howManySausagesCouldIEat() { + if (!have($item`Kramco Sausage-o-Matic™`)) return 0; + // You may be full but you can't be overfull + if (myFullness() > fullnessLimit()) return 0; + + return clamp( + 23 - get("_sausagesEaten"), + 0, + itemAmount($item`magical sausage`) + itemAmount($item`magical sausage casing`), + ); +} + +function ih8uDrink(): boolean { + if(myInebriety() >= inebrietyLimit()) + return false; + if (have($item`splendid martini`)) return true; + if(!have($item`Kremlin's Greatest Briefcase`) && !(have($item`mini kiwi`) && (have($item`bottle of vodka`) || have($item`bottle of gin`)))) + return false; + if (have($item`Kremlin's Greatest Briefcase`) && get("_kgbDispenserUses") < 3) { + cliExecute("briefcase unlock"); + cliExecute("briefcase collect"); + } + if(have($item`mini kiwi`) && (have($item`bottle of vodka`) || have($item`bottle of gin`))) { + retrieveItem($item`mini kiwitini`) + } + if(have($item`mini kiwitini`)) return true; + return false; +} + +export function RunQuests(): Quest { + return { + name: "Ascension Run", + completed: () => get("kingLiberated"), + tasks: [ + ...preRunQuests(), + { + name: "Run", + completed: () => (get("kingLiberated") && args.cs) || step("questL13Final") > 11, + do: () => { + if (runType() === undefined || runType() === null) throw "no runtime defined"; + else cliExecute(runType()); + }, + clear: "all", + tracking: args.cs ? "Ignore" : "Run", + }, + { + name: "drink ih8u", + ready: () => myInebriety() < inebrietyLimit() && args.ih8u, + completed: () => myInebriety() === inebrietyLimit() || !ih8uDrink(), + do: (): void => { + if (have($item`Kremlin's Greatest Briefcase`) && get("_kgbDispenserUses") < 3) { + cliExecute("briefcase unlock"); + cliExecute("briefcase collect"); + } + while(myInebriety() < inebrietyLimit() && have($item`splendid martini`)) { + equip($item`tuxedo shirt`); + useSkill($skill`The Ode to Booze`); + if(have($item`splendid martini`)) { + drinksilent($item`splendid martini`); + } + } + while(myFullness < fullnessLimit && have($item`mini kiwi`,3)) { + retrieveItem($item`mini kiwi digitized cookies`); + eat($item`mini kiwi digitized cookies`) + } + }, + clear: "all", + tracking: "Organs", + }, + { + name: "drink", + ready: () => shouldWeOverdrink() && args.smol, + completed: () => myInebriety() >= 2, + do: (): void => { + if (have($skill`The Ode to Booze`)) useSkill($skill`The Ode to Booze`); + drink($item`astral pilsner`, 1); + }, + clear: "all", + tracking: "Organs", + }, + { + name: "Sausages", + ready: () => args.smol, + completed: () => howManySausagesCouldIEat() === 0, + do: (): void => { + eat($item`magical sausage`, howManySausagesCouldIEat()); + }, + clear: "all", + tracking: "Run", + }, + { + name: "Free King", + ready: () => step("questL13Final") > 11 && !args.cs, + completed: () => get("kingLiberated", false), + do: (): void => { + visitUrl("place.php?whichplace=nstower&action=ns_11_prism"); + }, + clear: "all", + tracking: "ignore", + }, + ], + }; +} diff --git a/src/tasks/4 postrunleg.ts b/src/tasks/4 postrunleg.ts new file mode 100644 index 0000000..31355c0 --- /dev/null +++ b/src/tasks/4 postrunleg.ts @@ -0,0 +1,536 @@ +import { CombatStrategy } from "grimoire-kolmafia"; +import { + buy, + canAdventure, + cliExecute, + drink, + Effect, + getWorkshed, + hippyStoneBroken, + inebrietyLimit, + itemAmount, + mallPrice, + myAdventures, + myAscensions, + myDaycount, + myInebriety, + myLevel, + myMaxhp, + mySign, + numericModifier, + print, + pvpAttacksLeft, + restoreHp, + restoreMp, + retrieveItem, + runChoice, + setProperty, + toBoolean, + use, + useFamiliar, + useSkill, + visitUrl, + wait, +} from "kolmafia"; +import { + $coinmaster, + $effect, + $effects, + $familiar, + $familiars, + $item, + $items, + $location, + $skill, + get, + have, + Macro, + uneffect, +} from "libram"; + +import { args } from "../args"; + +import { chrono, crimbo, garboWeen, noBarf, postRunQuests } from "./repeatableTasks"; +import { Quest } from "./structure"; +import { + backstageItemsDone, + bestFam, + doneAdventuring, + haveAll, + maxBase, + pvpCloset, + stooperDrunk, + totallyDrunk, +} from "./utils"; + +let pajamas = false; +let smoke = 0; +const sasqBonus = (0.5 * 30 * 1000) / get("valueOfAdventure"); +const ratskinBonus = (0.3 * 40 * 1000) / get("valueOfAdventure"); + +const checkMelange = () => + get("valueOfAdventure") * 45 > mallPrice($item`spice melange`) && + !have($item`designer sweatpants`); + +export function PostRunQuests(): Quest { + return { + name: "Post-Run Aftercore", + ready: () => (myDaycount() === 1 || (myDaycount() === 2 && args.zooto)) && get("kingLiberated", false), + completed: () => totallyDrunk() && pajamas, + tasks: [ + { + name: "Pull All", + completed: () => get("lastEmptiedStorage") === myAscensions(), + do: () => cliExecute("pull all; refresh all"), + }, + { + name: "Clear citizen", + completed: () => + !get("_citizenZone", "").includes("castle") && + !get("_citizenZone", "").includes("Madness Bakery"), + do: (): void => { + uneffect($effect`Citizen of a Zone`); + cliExecute(`set _citizenZone = ""`); + }, + tracking: "Other" + }, + { + name: "Ensure prefs reset", + completed: () => !get("_folgerInitialConfig", false), + do: () => cliExecute("set _folgerInitialConfig = false"), + }, + { + name: "But dad I don't want to feel lost", + completed: () => !have($effect`Feeling Lost`), + do: () => uneffect($effect`Feeling Lost`), + tracking: "Other" + }, + { + name: "Hey kids, let's take a trip to the beach", + completed: () => have($item`bitchin' meatcar`) || have($item`Desert Bus pass`), + do: () => cliExecute("acquire bitchin"), + tracking: "Other" + }, + { + name: "Sober Up", + ready: () => args.smol, + completed: () => myInebriety() <= inebrietyLimit() || get("_mimeArmyShotglassUsed"), + do: (): void => { + if (checkMelange()) { + cliExecute("acquire spice melange; use spice melange"); + } + while (get("_sweatOutSomeBoozeUsed", 0) < 3) { + useSkill($skill`Sweat Out Some Booze`); + } + + use($item`synthetic dog hair pill`); + + if (!get("_sobrieTeaUsed", false)) { + retrieveItem($item`cuppa Sobrie tea`); + use($item`cuppa Sobrie tea`); + } + }, + tracking: "Organs" + }, + { + name: "PvP Closet Safety 1", + ready: () => args.pvp && get("autoSatisfyWithCloset") && !args.safepvp, + completed: () => toBoolean(get("_safetyCloset1")), + do: () => pvpCloset(1), + }, + { + name: "Drink Pre-Tune", + ready: () => + mySign().toLowerCase() === "blender" && + myLevel() >= 7 && + have($item`mime army shotglass`) && + (have($item`astral pilsner`) || have($item`astral six-pack`)), + completed: () => + get("_mimeArmyShotglassUsed") || !have($item`hewn moon-rune spoon`) || get("moonTuned"), + prepare: () => { + if (have($item`astral six-pack`)) use($item`astral six-pack`); + }, + do: () => drink(1, $item`astral pilsner`), + tracking: "Organs" + }, + { + name: "Moon Spoon", + completed: () => + !have($item`hewn moon-rune spoon`) || + get("moonTuned") || + mySign().toLowerCase() === "wombat", + do: () => cliExecute("spoon wombat"), + }, + { + name: "Gold Wedding Ring", + ready: () => canAdventure($location`A-Boo Peak`), + completed: () => + !have($skill`Comprehensive Cartography`) || + myAscensions() === get("lastCartographyBooPeak"), + choices: { 1430: 3, 606: 4, 610: 1, 1056: 1 }, + do: $location`A-Boo Peak`, + outfit: { modifier: "initiative 40 min 40 max, -tie" }, + tracking: "Other" + }, + { + name: "Emergency Drink", + ready: () => myAdventures() < 25, + completed: () => get("_mimeArmyShotglassUsed") || !have($item`mime army shotglass`), + prepare: () => { + if (have($item`astral six-pack`)) use($item`astral six-pack`); + }, + do: () => { + while (myAdventures() < 25) { + drink(1, $item`astral pilsner`); + } + }, + tracking: "Organs" + }, + { + name: "Emergency Drink Part 2", + ready: () => myAdventures() === 0 && myInebriety() < 11, + completed: () => myAdventures() > 0 || myInebriety() >= 11 || (!have($item`astral pilsner`) && !have($item`astral six-pack`)), + prepare: () => { + if (have($item`astral six-pack`)) use($item`astral six-pack`); + }, + do: () => { + while (myAdventures() < 25) { + useSkill($skill`The Ode to Booze`); + drink(1, $item`astral pilsner`); + } + }, + limit: { tries: 6 }, + tracking: "Organs" + }, + { + name: "Laugh Floor", + ready: () => !args.cs, + completed: () => + have($skill`Liver of Steel`) || + have($item`steel margarita`) || + have($item`Azazel's lollipop`) || + have($item`observational glasses`), + effects: () => [ + ...(have($skill`Musk of the Moose`) ? $effects`Musk of the Moose` : []), + ...(have($skill`Carlweather's Cantata of Confrontation`) + ? $effects`Carlweather's Cantata of Confrontation` + : []), + ], + prepare: (): void => { + if (!have($effect`Carlweather's Cantata of Confrontation`)) { + cliExecute("kmail to Buffy || 10 Cantata of Confrontation"); + wait(15); + cliExecute("refresh effects"); + } + $effects`Smooth Movements, The Sonata of Sneakiness, Darkened Photons, Shifted Phase`.forEach( + (ef: Effect) => cliExecute(`uneffect ${ef}`), + ); + restoreHp(0.75 * myMaxhp()); + restoreMp(20); + }, + do: $location`The Laugh Floor`, + outfit: () => ({ + familiar: bestFam(), + modifier: `${maxBase()}, 100 combat rate, 3 item, 250 bonus carnivorous potted plant`, + }), + combat: new CombatStrategy().macro( + Macro.trySkill($skill`Curse of Weaksauce`) + .tryItem($item`train whistle`) + .tryItem($item`porquoise-handled sixgun`) + .attack() + .repeat(), + ), + limit: { tries: 15 }, + tracking: "Steel Organ" + }, + { + name: "Infernal Rackets Backstage", + ready: () => !args.cs, + completed: () => + have($skill`Liver of Steel`) || + have($item`steel margarita`) || + have($item`Azazel's unicorn`) || + backstageItemsDone(), + effects: () => [ + ...(have($skill`Smooth Movement`) ? $effects`Smooth Movements` : []), + ...(have($skill`The Sonata of Sneakiness`) ? $effects`The Sonata of Sneakiness` : []), + ], + prepare: (): void => { + if (!have($effect`The Sonata of Sneakiness`)) { + cliExecute("kmail to Buffy || 10 Sonata of Sneakiness"); + wait(15); + cliExecute("refresh effects"); + } + $effects`Musk of the Moose, Carlweather's Cantata of Confrontation, Hooooooooonk!`.forEach( + (ef: Effect) => cliExecute(`uneffect ${ef}`), + ); + restoreHp(0.75 * myMaxhp()); + restoreMp(20); + }, + do: $location`Infernal Rackets Backstage`, + outfit: () => ({ + familiar: bestFam(), + modifier: `${maxBase()}, -100 combat rate, 3 item, 250 bonus carnivorous potted plant`, + }), + combat: new CombatStrategy().macro( + Macro.trySkill($skill`Curse of Weaksauce`) + .tryItem($item`train whistle`) + .tryItem($item`porquoise-handled sixgun`) + .attack() + .repeat(), + ), + limit: { tries: 15 }, + tracking: "Steel Organ" + }, + { + name: "Mourn", + ready: () => have($item`observational glasses`) && !args.cs, + completed: () => + have($skill`Liver of Steel`) || + have($item`steel margarita`) || + have($item`Azazel's lollipop`), + outfit: { + equip: $items`hilarious comedy prop, observational glasses, Victor\, the Insult Comic Hellhound Puppet`, + }, + do: () => cliExecute("panda comedy insult; panda comedy observe"), + tracking: "Steel Organ" + }, + { + name: "Sven Golly", + ready: () => backstageItemsDone() && !args.cs, + completed: () => + have($skill`Liver of Steel`) || + have($item`steel margarita`) || + have($item`Azazel's unicorn`), + do: (): void => { + cliExecute( + `panda arena Bognort ${$items`giant marshmallow, gin-soaked blotter paper`.find((a) => + have(a), + )}`, + ); + cliExecute( + `panda arena Stinkface ${$items`beer-scented teddy bear, gin-soaked blotter paper`.find( + (a) => have(a), + )}`, + ); + cliExecute( + `panda arena Flargwurm ${$items`booze-soaked cherry, sponge cake`.find((a) => + have(a), + )}`, + ); + cliExecute(`panda arena Jim ${$items`comfy pillow, sponge cake`.find((a) => have(a))}`); + }, + tracking: "Steel Organ" + }, + { + name: "Moaning Panda", + ready: () => haveAll($items`Azazel's lollipop, Azazel's unicorn`) && !args.cs, + completed: () => + have($skill`Liver of Steel`) || + have($item`steel margarita`) || + have($item`Azazel's tutu`), + acquire: () => + $items`bus pass, imp air`.map((it) => ({ + item: it, + num: 5, + price: get("valueOfAdventure"), + })), + do: () => cliExecute("panda moan"), + limit: { tries: 3 }, + tracking: "Steel Organ" + }, + { + name: "Steel Margarita", + ready: () => haveAll($items`Azazel's tutu, Azazel's lollipop, Azazel's unicorn`), + completed: () => have($skill`Liver of Steel`) || have($item`steel margarita`), + do: () => cliExecute("panda temple"), + tracking: "Steel Organ" + }, + { + name: "Liver of Steel", + ready: () => have($item`steel margarita`), + completed: () => have($skill`Liver of Steel`), + do: () => drink(1, $item`steel margarita`), + tracking: "Steel Organ" + }, + { + name: "Emergency Drink Part 3", + ready: () => myAdventures() < 40 && myInebriety() < 11, + completed: () => myAdventures() > 40 || myInebriety() >= 11|| (!have($item`astral pilsner`) && !have($item`astral six-pack`)), + prepare: () => { + if (have($item`astral six-pack`)) use($item`astral six-pack`); + }, + do: () => { + while (myAdventures() < 80 && have($item`astral pilsner`)) { + useSkill($skill`The Ode to Booze`); + drink(1, $item`astral pilsner`); + } + }, + limit: { tries: 6 }, + tracking: "Organs" + }, + ...postRunQuests(), + ...noBarf(), + ...garboWeen(), + ...crimbo(), + ...chrono(), + { + name: "Garbo", + completed: () => myAdventures() === 0 || stooperDrunk(), + prepare: () => uneffect($effect`Beaten Up`), + do: () => cliExecute(`${args.garbo}`), + post: () => + $effects`Power Ballad of the Arrowsmith, Stevedave's Shanty of Superiority, The Moxious Madrigal, The Magical Mojomuscular Melody, Aloysius' Antiphon of Aptitude, Ur-Kel's Aria of Annoyance` + .filter((ef) => have(ef)) + .forEach((ef) => uneffect(ef)), + clear: "all", + tracking: "Garbo", + }, + { + name: "Turn in FunFunds", + ready: () => get("_stenchAirportToday") && itemAmount($item`FunFunds™`) >= 20, + completed: () => have($item`one-day ticket to Dinseylandfill`), + do: () => + buy($coinmaster`The Dinsey Company Store`, 1, $item`one-day ticket to Dinseylandfill`), + tracking: "Garbo", + }, + { + name: "PvP Closet Safety 2", + ready: () => args.pvp && get("autoSatisfyWithCloset") && !args.safepvp, + completed: () => toBoolean(get("_safetyCloset2")), + do: () => pvpCloset(2), + }, + { + name: "PvP", + ready: () => doneAdventuring(), + completed: () => pvpAttacksLeft() === 0 || !hippyStoneBroken(), + do: (): void => { + cliExecute("unequip"); + cliExecute("UberPvPOptimizer"); + cliExecute(`PVP_MAB target=${args.pvpTarget}`); + }, + }, + { + name: "Stooper", + ready: () => + myInebriety() === inebrietyLimit() && + have($item`tiny stillsuit`) && + get("familiarSweat") >= 300, + completed: () => !have($familiar`Stooper`) || stooperDrunk(), + do: () => { + useFamiliar($familiar`Stooper`); + cliExecute("drink stillsuit distillate"); + }, + tracking: "Rollover Prep" + }, + { + name: "Nightcap", + ready: () => doneAdventuring(), + completed: () => totallyDrunk(), + do: () => { + if($familiar`Cooler Yeti`.experience >= 400) { + useFamiliar($familiar`Cooler Yeti`); + visitUrl("main.php?talktoyeti=1", false); + runChoice(2); + useFamiliar($familiar`Stooper`); + } + cliExecute("CONSUME NIGHTCAP"); + }, + tracking: "Rollover Prep" + }, + { + name: "Smoke em if you got em", + ready: () => get("getawayCampsiteUnlocked"), + completed: () => !have($item`stick of firewood`), + do: (): void => { + while (have($item`stick of firewood`)) { + setProperty( + "choiceAdventure1394", + `1&message=${smoke} Thanks Seraphiii for writing Candywrapper!`, + ); + use(1, $item`campfire smoke`); + print(`Smoked ${smoke} firewoods!`); + smoke = smoke + 1; + } + }, + tracking: "Community Service" + }, + { + name: "Offhand Remarkable", + ready: () => have($item`august scepter`), + completed: () => + !have($skill`Aug. 13th: Left/Off Hander's Day!`) || + have($effect`Offhand Remarkable`) || + get("_aug13Cast", false), + do: () => useSkill($skill`Aug. 13th: Left/Off Hander's Day!`), + tracking: "Rollover Prep" + }, + { + name: "PvP Closet Safety 3", + ready: () => args.pvp && get("autoSatisfyWithCloset") && !args.safepvp, + completed: () => toBoolean(get("_safetyCloset3")), + do: () => pvpCloset(3), + }, + { + name: "Item Cleanup", + // eslint-disable-next-line libram/verify-constants + completed: () => get("_cleanupToday", false) || args.itemcleanup === "", + do: (): void => { + cliExecute(`${args.itemcleanup}`); + cliExecute("set _cleanupToday = true"); + }, + clear: "all", + tracking: "Item Cleanup", + }, + { + name: "Takerspace", + ready: () => getWorkshed() === $item`model train set` && !get("_workshedItemUsed"), + completed: () => getWorkshed() === $item`TakerSpace letter of Marque`, + do: () => { + use($item`TakerSpace letter of Marque`); + visitUrl("campground.php?action=workshed"); + }, + tracking: "Workshed" + }, + { + name: "Pajamas", + completed: () => have($item`burning cape`), + acquire: [ + { item: $item`clockwork maid`, price: 7 * get("valueOfAdventure"), optional: true }, + { item: $item`burning cape` }, + ], + do: (): void => { + if (have($item`clockwork maid`)) { + use($item`clockwork maid`); + } + pajamas = true; + }, + outfit: () => ({ + familiar: + $familiars`Trick-or-Treating Tot, Left-Hand Man, Disembodied Hand, Grey Goose`.find( + (fam) => have(fam), + ), + modifier: `adventures ${sasqBonus} bonus Sasq™ watch, ${ratskinBonus} bonus ratskin pajama pants ${ + args.pvp ? ", 0.3 fites" : "" + }`, + }), + tracking: "Rollover Prep" + }, + { + name: "Alert-No Nightcap", + ready: () => !doneAdventuring(), + completed: () => stooperDrunk(), + do: (): void => { + const targetAdvs = 100 - numericModifier("adventures"); + print("robot completed, but did not overdrink.", "red"); + if (targetAdvs < myAdventures() && targetAdvs > 0) + print( + `Rerun with fewer than ${targetAdvs} adventures for smol to handle your diet`, + "red", + ); + else print("Something went wrong.", "red"); + }, + }, + ], + }; +} diff --git a/src/tasks/Garboween.ts b/src/tasks/Garboween.ts deleted file mode 100644 index 987496d..0000000 --- a/src/tasks/Garboween.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { CombatStrategy } from "grimoire-kolmafia"; -import { - availableAmount, - buy, - cliExecute, - fullnessLimit, - getCampground, - getClanName, - getWorkshed, - guildStoreAvailable, - haveEffect, - hippyStoneBroken, - holiday, - inebrietyLimit, - itemAmount, - myAdventures, - myClass, - myFullness, - myHp, - myInebriety, - myMaxhp, - mySpleenUse, - pvpAttacksLeft, - restoreHp, - retrieveItem, - spleenLimit, - use, - useFamiliar, -} from "kolmafia"; -import { - $class, - $coinmaster, - $effect, - $familiar, - $item, - $items, - $location, - $phylum, - $skill, - AsdonMartin, - DNALab, - get, - getTodaysHolidayWanderers, - have, - Macro, - uneffect, -} from "libram"; - -import { args } from "../args"; - -import { Quest } from "./structure"; -import { bestFam, getGarden, maxBase, stooperDrunk, totallyDrunk } from "./utils"; - -let garboDone = false; - -export function GarboWeenQuest(): Quest { - return { - name: "Garboween", - completed: () => myAdventures() === 0 && totallyDrunk(), - tasks: [ - { - name: "Whitelist VIP Clan", - completed: () => !args.clan || getClanName().toLowerCase() === args.clan.toLowerCase(), - do: () => cliExecute(`/whitelist ${args.clan}`), - }, - { - name: "LGR Seed", - ready: () => have($item`lucky gold ring`) && have($item`one-day ticket to Dinseylandfill`), - completed: () => get("_stenchAirportToday") || get("stenchAirportAlways"), - do: () => use($item`one-day ticket to Dinseylandfill`), - tracking: "Garbo", - }, - { - name: "Breakfast", - completed: () => get("breakfastCompleted"), - do: () => cliExecute("breakfast"), - }, - { - name: "Harvest Garden", - completed: () => - getGarden() === $item`none` || - getGarden() === $item`packet of mushroom spores` || - getCampground()[getGarden().name] === 0, - do: () => cliExecute("garden pick"), - tracking: "Dailies", - limit: { tries: 3 }, - }, - { - name: "Plant Grass", - completed: () => - !have($item`packet of tall grass seeds`) || - getGarden() === $item`packet of tall grass seeds`, - do: () => use($item`packet of tall grass seeds`), - }, - { - name: "SIT Course", - ready: () => have($item`S.I.T. Course Completion Certificate`), - completed: () => get("_sitCourseCompleted", false), - choices: { - 1494: 2, - }, - do: () => use($item`S.I.T. Course Completion Certificate`), - }, - { - name: "Drive Observantly", - completed: () => - getWorkshed() !== $item`Asdon Martin keyfob (on ring)` || - haveEffect($effect`Driving Observantly`) >= - (totallyDrunk() || !have($item`Drunkula's wineglass`) - ? myAdventures() - : myAdventures() + 60), - do: () => - AsdonMartin.drive( - $effect`Driving Observantly`, - totallyDrunk() || !have($item`Drunkula's wineglass`) - ? myAdventures() - : myAdventures() + 60, - false, - ), - limit: { tries: 5 }, - }, - { - name: "Sample Constellation DNA", - ready: () => have($item`DNA extraction syringe`), - completed: () => - !DNALab.installed() || - DNALab.isHybridized($phylum`Constellation`) || - get("dnaSyringe") === $phylum`Constellation`, - outfit: { - familiar: bestFam(), - modifier: `${maxBase()}`, - }, - do: $location`The Hole in the Sky`, - combat: new CombatStrategy() - .macro(Macro.skill($skill`Curse of Weaksauce`), getTodaysHolidayWanderers()) - .macro(Macro.tryItem($item`DNA extraction syringe`)) - .macro( - Macro.tryItem($item`train whistle`) - .tryItem($item`porquoise-handled sixgun`) - .trySkill($skill`Sing Along`) - .attack() - .repeat(), - ), - }, - { - name: "Hybridize Constellation", - ready: () => get("dnaSyringe") === $phylum`Constellation`, - completed: () => !DNALab.installed() || DNALab.isHybridized($phylum`Constellation`), - do: () => { - DNALab.makeTonic(3); - DNALab.hybridize(); - }, - }, - { - name: "Restore HP", - completed: () => myHp() > 0.5 * myMaxhp(), - do: () => restoreHp(0.95 * myMaxhp()), - }, - { - name: "Implement Glitch", - ready: () => have($item`[glitch season reward name]`), - completed: () => get("_glitchItemImplemented"), - do: () => use($item`[glitch season reward name]`), - }, - { - name: "Buy Seal Summoning Supplies", - ready: () => myClass() === $class`Seal Clubber` && guildStoreAvailable(), - completed: () => - Math.min( - ...$items`figurine of a wretched-looking seal, seal-blubber candle`.map((it) => - availableAmount(it), - ), - ) >= 40, - acquire: $items`figurine of a wretched-looking seal, seal-blubber candle`.map((it) => ({ - item: it, - num: 500, - })), - do: () => false, - }, - { - name: "CONSUME ALL", - completed: () => - myFullness() >= fullnessLimit() && - mySpleenUse() >= spleenLimit() && - myInebriety() >= inebrietyLimit(), - do: () => cliExecute("consume ALL"), - }, - { - name: "Garbo Nobarf", - completed: () => garboDone, - do: (): void => { - cliExecute(`${args.garboascend} nodiet nobarf target="witchess knight"`); - garboDone = true; - }, - }, - { - name: "Freecandy time", - ready: () => holiday().includes("Halloween"), - completed: () => myAdventures() / 5 < 1, - prepare: () => uneffect($effect`Beaten Up`), - do: (): void => { - if (have($familiar`Trick-or-Treating Tot`)) cliExecute("familiar Trick-or-Treating Tot"); - else if (have($familiar`Red-Nosed Snapper`)) cliExecute("familiar snapper"); - cliExecute(`freecandy ${myAdventures()}`); - }, - clear: "all", - tracking: "Freecandy", - limit: { tries: 1 }, //this will run again after installing CMC, by magic - }, - { - name: "Do Pizza", - completed: () => - have($item`Pizza of Legend`) && - have($item`Deep Dish of Legend`) && - have($item`Calzone of Legend`), - do: (): void => { - !have($item`Pizza of Legend`) ? retrieveItem($item`Pizza of Legend`) : undefined; - !have($item`Deep Dish of Legend`) ? retrieveItem($item`Deep Dish of Legend`) : undefined; - !have($item`Calzone of Legend`) ? retrieveItem($item`Calzone of Legend`) : undefined; - }, - }, - { - name: "Stooper", - ready: () => - myInebriety() === inebrietyLimit() && - have($item`tiny stillsuit`) && - get("familiarSweat") >= 300, - completed: () => !have($familiar`Stooper`) || stooperDrunk(), - do: () => { - useFamiliar($familiar`Stooper`); - cliExecute("drink stillsuit distillate"); - }, - }, - { - name: "Super Nightcap", - ready: () => have($item`Drunkula's wineglass`), - completed: () => totallyDrunk(), - do: () => cliExecute(`CONSUME NIGHTCAP`), - }, - { - name: "Freecandy Drunk", - ready: () => holiday().includes("Halloween"), - completed: () => Math.floor(myAdventures() / 5) === 0, - prepare: () => uneffect($effect`Beaten Up`), - do: (): void => { - useFamiliar($familiar`Red-Nosed Snapper`); - cliExecute(`freeCandy ${myAdventures()}`); - }, - clear: "all", - tracking: "Freecandy", - limit: { tries: 1 }, //this will run again after installing CMC, by magic - }, - { - name: "Grimace Maps", - ready: () => have($item`Map to Safety Shelter Grimace Prime`) && totallyDrunk(), - completed: () => !have($item`Map to Safety Shelter Grimace Prime`) || myAdventures() === 0, - do: () => cliExecute("grimace maps"), - }, - { - name: "Comb Beach", - ready: () => have($item`Beach Comb`) && totallyDrunk(), - completed: () => myAdventures() === 0, - do: () => cliExecute(`combo ${11 - get("_freeBeachWalksUsed") + myAdventures()}`), - }, - { - name: "Turn in FunFunds", - ready: () => get("_stenchAirportToday") && itemAmount($item`FunFunds™`) >= 20, - completed: () => have($item`one-day ticket to Dinseylandfill`), - do: () => - buy($coinmaster`The Dinsey Company Store`, 1, $item`one-day ticket to Dinseylandfill`), - tracking: "Garbo", - }, - { - name: "PvP", - completed: () => pvpAttacksLeft() === 0 || !hippyStoneBroken(), - do: (): void => { - cliExecute("unequip"); - cliExecute("UberPvPOptimizer"); - cliExecute(`PVP_MAB target=${args.pvpTarget}`); - }, - }, - ], - }; -} diff --git a/src/tasks/casualrunleg.ts b/src/tasks/casualrunleg.ts deleted file mode 100644 index 58e1dbf..0000000 --- a/src/tasks/casualrunleg.ts +++ /dev/null @@ -1,598 +0,0 @@ -import { CombatStrategy, step } from "grimoire-kolmafia"; -import { - buy, - cliExecute, - drink, - Effect, - equip, - familiarWeight, - fullnessLimit, - getClanName, - getWorkshed, - hippyStoneBroken, - inebrietyLimit, - itemAmount, - myAdventures, - myAscensions, - myDaycount, - myFullness, - myInebriety, - myLevel, - myMaxhp, - mySign, - numericModifier, - print, - pvpAttacksLeft, - restoreHp, - restoreMp, - retrieveItem, - setProperty, - storageAmount, - use, - useFamiliar, - useSkill, - visitUrl, - wait, -} from "kolmafia"; -import { - $coinmaster, - $effect, - $effects, - $familiar, - $familiars, - $item, - $items, - $location, - $skill, - AprilingBandHelmet, - clamp, - get, - have, - Macro, - set, - uneffect, -} from "libram"; - -import { args } from "../args"; - -import { getCurrentLeg, Leg, Quest } from "./structure"; -import { - backstageItemsDone, - bestFam, - doneAdventuring, - haveAll, - maxBase, - stooperDrunk, - totallyDrunk, -} from "./utils"; - -let pajamas = false; -let smoke = 1; - -export function howManySausagesCouldIEat() { - if (!have($item`Kramco Sausage-o-Matic™`)) return 0; - // You may be full but you can't be overfull - if (myFullness() > fullnessLimit()) return 0; - - return clamp( - 23 - get("_sausagesEaten"), - 0, - itemAmount($item`magical sausage`) + itemAmount($item`magical sausage casing`), - ); -} - -function firstWorkshed() { - return ( - $items`model train set, Asdon Martin keyfob (on ring), cold medicine cabinet, Little Geneticist DNA-Splicing Lab, portable Mayo Clinic`.find( - (it) => have(it) || getWorkshed() === it || storageAmount(it) > 0, - ) || $item`none` - ); -} -function altWorkshed() { - const ws = getWorkshed(); - switch (ws) { - case $item`model train set`: - return ( - $items`cold medicine cabinet, Asdon Martin keyfob (on ring), Little Geneticist DNA-Splicing Lab, portable Mayo Clinic`.find( - (it) => have(it) || getWorkshed() === it || storageAmount(it) > 0, - ) || ws - ); - case $item`Asdon Martin keyfob (on ring)`: - return ( - $items`cold medicine cabinet, model train set, Little Geneticist DNA-Splicing Lab, portable Mayo Clinic`.find( - (it) => have(it) || getWorkshed() === it || storageAmount(it) > 0, - ) || ws - ); - case $item`cold medicine cabinet`: - return ( - $items`Asdon Martin keyfob (on ring), model train set, Little Geneticist DNA-Splicing Lab, portable Mayo Clinic, warbear induction oven, snow machine`.find( - (it) => have(it) || getWorkshed() === it || storageAmount(it) > 0, - ) || ws - ); - case $item`Little Geneticist DNA-Splicing Lab`: - return ( - $items`cold medicine cabinet, Asdon Martin keyfob (on ring), model train set, portable Mayo Clinic`.find( - (it) => have(it) || getWorkshed() === it || storageAmount(it) > 0, - ) || ws - ); - case $item`portable Mayo Clinic`: - return ( - $items`cold medicine cabinet, model train set, Asdon Martin keyfob (on ring), Little Geneticist DNA-Splicing Lab`.find( - (it) => have(it) || getWorkshed() === it || storageAmount(it) > 0, - ) || ws - ); - default: - return $item`none`; - } -} - -export function CasualQuests(): Quest[] { - return [ - { - name: "Casual Run", - completed: () => getCurrentLeg() !== Leg.Run || get("kingLiberated", false), - tasks: [ - { - name: "Whitelist VIP Clan", - completed: () => !args.clan || getClanName().toLowerCase() === args.clan.toLowerCase(), - do: () => cliExecute(`/whitelist ${args.clan}`), - choices: { - 1507: 1, - }, - }, - { - name: "Prep Fireworks Shop", - completed: () => - !have($item`Clan VIP Lounge key`) || get("_goorboFireworksPrepped", false), - do: () => { - visitUrl("clan_viplounge.php?action=fwshop&whichfloor=2"); - set("_goorboFireworksPrepped", true); - }, - }, - { - name: "LGR Seed", - ready: () => - have($item`lucky gold ring`) && have($item`one-day ticket to Dinseylandfill`), - completed: () => get("_stenchAirportToday") || get("stenchAirportAlways"), - do: () => use($item`one-day ticket to Dinseylandfill`), - tracking: "Garbo", - }, - { - name: "Install First Workshed", - ready: () => have(firstWorkshed()), - completed: () => - firstWorkshed() === $item`none` || - get("_workshedItemUsed") || - getWorkshed() !== $item`none`, - do: () => use(firstWorkshed()), - }, - { - name: "SIT Course", - // eslint-disable-next-line libram/verify-constants - ready: () => have($item`S.I.T. Course Completion Certificate`), - completed: () => get("_sitCourseCompleted", false), - choices: { - 1494: 2, - }, - do: () => - // eslint-disable-next-line libram/verify-constants - use($item`S.I.T. Course Completion Certificate`), - }, - { - name: "Break Stone", - completed: () => hippyStoneBroken() || !args.pvp, - do: (): void => { - visitUrl("peevpee.php?action=smashstone&pwd&confirm=on", true); - visitUrl("peevpee.php?place=fight"); - }, - }, - { - name: "Prepare Empathy", - completed: () => get("_empathyReady", false), - do: (): void => { - cliExecute("maximize MP; set _empathyReady = true"); - }, - }, - { - name: "Stillsuit Prep", - completed: () => itemAmount($item`tiny stillsuit`) === 0, - do: () => - equip( - $item`tiny stillsuit`, - get( - "stillsuitFamiliar", - $familiars`Gelatinous Cubeling, Levitating Potato, Mosquito`.find((fam) => - have(fam), - ) || $familiar`none`, - ), - ), - }, - { - name: "Run", - completed: () => step("questL13Final") > 11, - do: () => cliExecute(args.casualscript), - clear: "all", - tracking: "Run", - }, - { - name: "Free King", - ready: () => step("questL13Final") > 11, - completed: () => get("kingLiberated", false), - do: (): void => { - visitUrl("place.php?whichplace=nstower&action=ns_11_prism"); - }, - clear: "all", - }, - ], - }, - { - name: "Post-casual Aftercore", - ready: () => myDaycount() === 1 && get("kingLiberated", false), - completed: () => totallyDrunk() && pajamas, - tasks: [ - { - name: "Unlock Garbage Mountain", - completed: () => get("_stenchAirportToday") || get("stenchAirportAlways"), - do: (): void => { - retrieveItem($item`one-day ticket to Dinseylandfill`); - use($item`one-day ticket to Dinseylandfill`); - }, - tracking: "Garbo", - }, - { - name: "Wardrobe-o-matic", - ready: () => myLevel() >= 15 && have($item`wardrobe-o-matic`), - completed: () => get("_wardrobeUsed", false), - do: (): void => { - use($item`wardrobe-o-matic`); - cliExecute("set _wardrobeUsed = true"); - }, - limit: { tries: 1 }, - }, - { - name: "Apriling Part 1", - ready: () => AprilingBandHelmet.canChangeSong(), - completed: () => have($effect`Apriling Band Celebration Bop`), - do: (): void => { - AprilingBandHelmet.conduct($effect`Apriling Band Celebration Bop`); - }, - limit: { tries: 1 }, - }, - { - name: "Drink Pre-Tune", - ready: () => - mySign().toLowerCase() === "blender" && - myLevel() >= 7 && - have($item`mime army shotglass`) && - (have($item`astral pilsner`) || have($item`astral six-pack`)), - completed: () => - get("_mimeArmyShotglassUsed") || !have($item`hewn moon-rune spoon`) || get("moonTuned"), - prepare: () => { - if (have($item`astral six-pack`)) use($item`astral six-pack`); - }, - do: () => drink(1, $item`astral pilsner`), - }, - { - name: "Moon Spoon", - completed: () => - !have($item`hewn moon-rune spoon`) || - get("moonTuned") || - mySign().toLowerCase() === "wombat", - do: () => cliExecute("spoon wombat"), - }, - { - name: "Install Alternate Workshed", - ready: () => have(altWorkshed()), - completed: () => - altWorkshed() === $item`none` || - get("_workshedItemUsed") || - getWorkshed() === altWorkshed(), - do: () => use(altWorkshed()), - }, - { - name: "Gold Wedding Ring", - completed: () => - !have($skill`Comprehensive Cartography`) || - myAscensions() === get("lastCartographyBooPeak"), - choices: { 1430: 3, 606: 4, 610: 1, 1056: 1 }, - do: $location`A-Boo Peak`, - outfit: { modifier: "initiative 40 min 40 max, -tie" }, - }, - { - name: "Breakfast", - completed: () => get("breakfastCompleted"), - do: () => cliExecute("breakfast"), - }, - { - name: "Laugh Floor", - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's lollipop`) || - have($item`observational glasses`), - effects: () => [ - ...(have($skill`Musk of the Moose`) ? $effects`Musk of the Moose` : []), - ...(have($skill`Carlweather's Cantata of Confrontation`) - ? $effects`Carlweather's Cantata of Confrontation` - : []), - ], - prepare: (): void => { - if (!have($effect`Carlweather's Cantata of Confrontation`)) { - cliExecute("kmail to Buffy || 10 Cantata of Confrontation"); - wait(15); - cliExecute("refresh effects"); - } - $effects`Smooth Movements, The Sonata of Sneakiness, Darkened Photons, Shifted Phase`.forEach( - (ef: Effect) => cliExecute(`uneffect ${ef}`), - ); - restoreHp(0.75 * myMaxhp()); - restoreMp(20); - }, - do: $location`The Laugh Floor`, - outfit: () => ({ - familiar: bestFam(), - modifier: `${maxBase()}, 100 combat rate, 3 item, 250 bonus carnivorous potted plant`, - }), - combat: new CombatStrategy().macro( - Macro.trySkill($skill`Curse of Weaksauce`) - .tryItem($item`train whistle`) - .tryItem($item`porquoise-handled sixgun`) - .attack() - .repeat(), - ), - limit: { tries: 15 }, - }, - { - name: "Infernal Rackets Backstage", - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's unicorn`) || - backstageItemsDone(), - effects: () => [ - ...(have($skill`Smooth Movement`) ? $effects`Smooth Movements` : []), - ...(have($skill`The Sonata of Sneakiness`) ? $effects`The Sonata of Sneakiness` : []), - ], - prepare: (): void => { - if (!have($effect`The Sonata of Sneakiness`)) { - cliExecute("kmail to Buffy || 10 Sonata of Sneakiness"); - wait(15); - cliExecute("refresh effects"); - } - $effects`Musk of the Moose, Carlweather's Cantata of Confrontation, Hooooooooonk!`.forEach( - (ef: Effect) => cliExecute(`uneffect ${ef}`), - ); - restoreHp(0.75 * myMaxhp()); - restoreMp(20); - }, - do: $location`Infernal Rackets Backstage`, - outfit: () => ({ - familiar: bestFam(), - modifier: `${maxBase()}, -100 combat rate, 3 item, 250 bonus carnivorous potted plant`, - }), - combat: new CombatStrategy().macro( - Macro.trySkill($skill`Curse of Weaksauce`) - .tryItem($item`train whistle`) - .tryItem($item`porquoise-handled sixgun`) - .attack() - .repeat(), - ), - limit: { tries: 15 }, - }, - { - name: "Mourn", - ready: () => have($item`observational glasses`), - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's lollipop`), - outfit: { - equip: $items`hilarious comedy prop, observational glasses, Victor\, the Insult Comic Hellhound Puppet`, - }, - do: () => cliExecute("panda comedy insult; panda comedy observe"), - }, - { - name: "Sven Golly", - ready: () => backstageItemsDone(), - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's unicorn`), - do: (): void => { - cliExecute( - `panda arena Bognort ${$items`giant marshmallow, gin-soaked blotter paper`.find((a) => - have(a), - )}`, - ); - cliExecute( - `panda arena Stinkface ${$items`beer-scented teddy bear, gin-soaked blotter paper`.find( - (a) => have(a), - )}`, - ); - cliExecute( - `panda arena Flargwurm ${$items`booze-soaked cherry, sponge cake`.find((a) => - have(a), - )}`, - ); - cliExecute(`panda arena Jim ${$items`comfy pillow, sponge cake`.find((a) => have(a))}`); - }, - }, - { - name: "Moaning Panda", - ready: () => haveAll($items`Azazel's lollipop, Azazel's unicorn`), - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's tutu`), - acquire: () => - $items`bus pass, imp air`.map((it) => ({ - item: it, - num: 5, - price: get("valueOfAdventure"), - })), - do: () => cliExecute("panda moan"), - limit: { tries: 3 }, - }, - { - name: "Steel Margarita", - ready: () => haveAll($items`Azazel's tutu, Azazel's lollipop, Azazel's unicorn`), - completed: () => have($skill`Liver of Steel`) || have($item`steel margarita`), - do: () => cliExecute("panda temple"), - }, - { - name: "Liver of Steel", - ready: () => have($item`steel margarita`), - completed: () => have($skill`Liver of Steel`), - do: () => drink(1, $item`steel margarita`), - }, - { - name: "Garbo", - ready: () => get("_stenchAirportToday") || get("stenchAirportAlways"), - completed: () => myAdventures() === 0 || stooperDrunk(), - prepare: () => uneffect($effect`Beaten Up`), - do: () => cliExecute(args.garbo), - post: () => - $effects`Power Ballad of the Arrowsmith, Stevedave's Shanty of Superiority, The Moxious Madrigal, The Magical Mojomuscular Melody, Aloysius' Antiphon of Aptitude, Ur-Kel's Aria of Annoyance` - .filter((ef) => have(ef)) - .forEach((ef) => uneffect(ef)), - clear: "all", - tracking: "Garbo", - }, - { - name: "Turn in FunFunds", - ready: () => get("_stenchAirportToday") && itemAmount($item`FunFunds™`) >= 20, - completed: () => have($item`one-day ticket to Dinseylandfill`), - do: () => - buy($coinmaster`The Dinsey Company Store`, 1, $item`one-day ticket to Dinseylandfill`), - tracking: "Garbo", - }, - { - name: "PvP", - ready: () => doneAdventuring(), - completed: () => pvpAttacksLeft() === 0 || !hippyStoneBroken(), - do: (): void => { - cliExecute("unequip"); - cliExecute("UberPvPOptimizer"); - cliExecute(`PVP_MAB target=${args.pvpTarget}`); - }, - }, - { - name: "Stooper", - ready: () => - myInebriety() === inebrietyLimit() && - have($item`tiny stillsuit`) && - get("familiarSweat") >= 300, - completed: () => !have($familiar`Stooper`) || stooperDrunk(), - do: () => { - useFamiliar($familiar`Stooper`); - cliExecute("drink stillsuit distillate"); - }, - }, - { - name: "Nightcap", - ready: () => doneAdventuring(), - completed: () => totallyDrunk(), - do: () => cliExecute("CONSUME NIGHTCAP"), - }, - { - name: "Smoke em if you got em", - ready: () => get("getawayCampsiteUnlocked"), - completed: () => !have($item`stick of firewood`), - do: (): void => { - while (have($item`stick of firewood`)) { - setProperty( - "choiceAdventure1394", - `1&message=${smoke} Thanks Seraphiii for writing Candywrapper!`, - ); - use(1, $item`campfire smoke`); - print(`Smoked ${smoke} firewoods!`); - smoke = smoke + 1; - } - }, - }, - { - name: "Offhand Remarkable", - ready: () => have($item`august scepter`), - completed: () => - !have($skill`Aug. 13th: Left/Off Hander's Day!`) || - have($effect`Offhand Remarkable`) || - get("_aug13Cast", false), - do: () => useSkill($skill`Aug. 13th: Left/Off Hander's Day!`), - }, - { - name: "Item Cleanup", - // eslint-disable-next-line libram/verify-constants - completed: () => get("_cleanupToday", false) || args.itemcleanup === "", - do: (): void => { - cliExecute(`${args.itemcleanup}`); - cliExecute("set _cleanupToday = true"); - }, - clear: "all", - tracking: "Item Cleanup", - }, - { - name: "Apriling Part 2", - ready: () => AprilingBandHelmet.canJoinSection(), - completed: () => !AprilingBandHelmet.canPlay($item`Apriling band piccolo`), - do: (): void => { - AprilingBandHelmet.joinSection($item`Apriling band piccolo`); - if (AprilingBandHelmet.canJoinSection()) { - AprilingBandHelmet.joinSection($item`Apriling band saxophone`); - AprilingBandHelmet.play($item`Apriling band saxophone`); - } - if (have($familiar`Grey Goose`)) useFamiliar($familiar`Grey Goose`); - else if (have($familiar`Chest Mimic`)) useFamiliar($familiar`Chest Mimic`); - else if ( - have($familiar`Pocket Professor`) && - familiarWeight($familiar`Pocket Professor`) < 20 - ) - useFamiliar($familiar`Pocket Professor`); - else if (have($familiar`Comma Chameleon`)) useFamiliar($familiar`Comma Chameleon`); - while ( - $item`Apriling band piccolo`.dailyusesleft > 0 && - have($item`Apriling band piccolo`) - ) - AprilingBandHelmet.play($item`Apriling band piccolo`); - }, - limit: { tries: 1 }, - }, - { - name: "Pajamas", - completed: () => have($item`burning cape`), - acquire: [ - { item: $item`clockwork maid`, price: 7 * get("valueOfAdventure"), optional: true }, - { item: $item`burning cape` }, - ], - do: (): void => { - if (have($item`clockwork maid`)) { - use($item`clockwork maid`); - } - pajamas = true; - }, - outfit: () => ({ - familiar: - $familiars`Trick-or-Treating Tot, Left-Hand Man, Disembodied Hand, Grey Goose`.find( - (fam) => have(fam), - ), - modifier: `adventures${args.pvp ? ", 0.3 fites" : ""}`, - }), - }, - { - name: "Alert-No Nightcap", - ready: () => !doneAdventuring(), - completed: () => stooperDrunk(), - do: (): void => { - const targetAdvs = 100 - numericModifier("adventures"); - print("smol completed, but did not overdrink.", "red"); - if (targetAdvs < myAdventures() && targetAdvs > 0) - print( - `Rerun with fewer than ${targetAdvs} adventures for smol to handle your diet`, - "red", - ); - else print("Something went wrong.", "red"); - }, - }, - ], - }, - ]; -} diff --git a/src/tasks/cluedin.ts b/src/tasks/cluedin.ts new file mode 100644 index 0000000..815a03b --- /dev/null +++ b/src/tasks/cluedin.ts @@ -0,0 +1,169 @@ +import { Item, mallPrice, print } from "kolmafia"; +import { $item, have } from "libram"; + + +const mrStoreItems: [Item, number][] = [ + [$item`Apathargic Bandersnatch`, 0], + [$item`fairy-worn boots`, 0], + [$item`Source terminal`, 0], + [$item`Kramco Industries packing carton`, 0], + [$item`baby camelCalf`, 0], + [$item`emotion chip`, 0], + [$item`packaged backup camera`, 0], + [$item`packaged cold medicine cabinet`, 0], + [$item`packaged Jurassic Parka`, 0], + [$item`book of facts`, 0], + [$item`boxed bat wings`, 0], + [$item`Tome of Clip Art`, 1], + [$item`Pack of Every Card`, 1], + [$item`LI-11 Motor Pool voucher`, 1], + [$item`corked genie bottle`, 1], + [$item`Pocket Meteor Guide`, 1], + [$item`space planula`, 1], + [$item`January's Garbage Tote (unopened)`, 1], + [$item`Fourth of May Cosplay Saber kit`, 1], + [$item`packaged Pocket Professor`, 1], + [$item`mint-in-box Powerful Glove`, 1], + [$item`Comprehensive Cartographic Compendium`, 1], + [$item`packaged miniature crystal ball`, 1], + [$item`packaged industrial fire extinguisher`, 1], + [$item`grey gosling`, 1], + [$item`undrilled cosmic bowling ball`, 1], + [$item`boxed autumn-aton`, 1], + [$item`sleeping patriotic eagle`, 1], + [$item`closed-circuit pay phone`, 1], + [$item`cursed monkey glove`, 1], + [$item`shrink-wrapped Cincho de Mayo`, 1], + [$item`shrink-wrapped 2002 Mr. Store Catalog`, 1], + [$item`wrapped candy cane sword cane`, 1], + [$item`baby chest mimic`, 1], + [$item`in-the-box spring shoes`, 1], + [$item`packaged Everfull Dart Holster`, 1], + [$item`boxed Apriling band helmet`, 1], + [$item`packaged Roman Candelabra`, 1], + [$item`boxed Sept-Ember Censer`, 1], + [$item`McHugeLarge deluxe ski set`, 1], + [$item`Clan VIP Lounge invitation`, 2], + [$item`Schmalz's First Prize Beer`, 2], + [$item`Moping Artistic Goth Kid`, 2], + [$item`The Smith's Tome`, 2], + [$item`Granny Tood's Thanksgarden Catalog`, 2], + [$item`Witchess Set`, 2], + [$item`heart-shaped crate`, 2], + [$item`suspicious package`, 2], + [$item`xo-skeleton-in-a-box`, 2], + [$item`Neverending Party invitation envelope`, 2], + [$item`latte lovers club card`, 2], + [$item`voter registration form`, 2], + [$item`mint condition Lil' Doctor™ bag`, 2], + [$item`unopened diabolic pizza cube box`, 2], + [$item`Unopened Eight Days a Week Pill Keeper`, 2], + [$item`combat lover's locket lockbox`, 2], + [$item`packaged model train set`, 2], + [$item`boxed august scepter`, 2], + [$item`boxed Mayam Calendar`, 2], + [$item`Sealed TakerSpace letter of Marque`, 2], + [$item`Libram of Candy Heart Summoning`, 3], + [$item`Libram of BRICKOs`, 3], + [$item`Order of the Green Thumb Order Form`, 3], + [$item`bottle of lovebug pheromones`, 3], + [$item`Chateau Mantegna room key`, 3], + [$item`yellow puck`, 3], + [$item`yellow puck with a bow on it`, 3], + [$item`machine elf capsule`, 3], + [$item`disconnected intergnat`, 3], + [$item`DIY protonic accelerator kit`, 3], + [$item`li'l orphan tot`, 3], + [$item`potted tea tree`, 3], + [$item`X-32-F snowman crate`, 3], + [$item`kitten burglar`, 3], + [$item`red-spotted snapper roe`, 3], + [$item`bagged Cargo Cultist Shorts`, 3], + [$item`packaged familiar scrapbook`, 3], + [$item`power seed`, 3], + [$item`packaged Daylight Shavings Helmet`, 3], + [$item`bottled Vampire Vintner`, 3], + [$item`mint condition magnifying glass`, 3], + [$item`undamaged Unbreakable Umbrella`, 3], + [$item`packaged June cleaver`, 3], + [$item`unopened tiny stillsuit`, 3], + [$item`mummified entombed cookbookbat`, 3], + [$item`Rock Garden Guide`, 3], + [$item`peace turkey outline`, 3], + [$item`miniscule temporal rip`, 4], + [$item`Tome of Snowcone Summoning`, 4], + [$item`navel ring of navel gazing`, 4], + [$item`Libram of Divine Favors`, 4], + [$item`sane hatrack`, 4], + [$item`spooky rattling cigar box`, 4], + [$item`container of Spooky Putty`, 4], + [$item`floaty stone sphere`, 4], + [$item`Libram of Love Songs`, 4], + [$item`panicked kernel`, 4], + [$item`Crown of Thrones`, 4], + [$item`Greatest American Pants`, 4], + [$item`a cute angel`, 4], + [$item`Mint Salton Pepper's Peppermint Seed Catalog`, 4], + [$item`mysterious chest`, 4], + [$item`Camp Scout backpack`, 4], + [$item`can of Rain-Doh`, 4], + [$item`Libram of Resolutions`, 4], + [$item`Unagnimated Gnome`, 4], + [$item`avatar of the Unconscious Collective`, 4], + [$item`deanimated reanimator's coffin`, 4], + [$item`Libram of Pulled Taffy`, 4], + [$item`Buddy Bjorn`, 4], + [$item`fist turkey outline`, 4], + [$item`Little Geneticist DNA-Splicing Lab`, 4], + [$item`still grill`, 4], + [$item`shrine to the Barrel god`, 4], + [$item`haunted doghouse`, 4], + [$item`portable Mayo Clinic`, 4], + [$item`Dear Past Self Package`, 4], + [$item`LT&T telegraph office deed`, 4], + [$item`New-You Club Membership Form`, 4], + [$item`pantogram`, 4], + [$item`unpowered Robortender`, 4], + [$item`Bastille Battalion control rig crate`, 4], + [$item`Boxing Day care package`, 4], + [$item`SongBoom™ BoomBox Box`, 4], + [$item`Beach Comb Box`, 4], + [$item`rune-strewn spoon cocoon`, 4], + [$item`vampyric cloake pattern`, 4], + [$item`packaged knock-off retro superhero cape`, 4], + [$item`unopened Bird-a-Day calendar`, 4], + [$item`shortest-order cook`, 4], + [$item`designer sweatpants (new old stock)`, 4], + [$item`S.I.T. Course Completion Certificate`, 4], + [$item`Dark Jill-of-All-Trades`, 4], + [$item`Black and White Apron Enrollment Form`, 4], + [$item`CyberRealm keycode`, 4], +]; + +const tierValues = new Map([ + [0, 20], + [1, 11], + [2, 6], + [3, 3], + [4, 1], +]); + +export function itemPriceValue() { + // Filter out items the player already owns + const filteredItems = mrStoreItems + .filter(([item]) => !have(item)) + .map(([item, tier]) => { + const turnsSaved = tierValues.get(tier) ?? Infinity; + const price = mallPrice(item); + const pricePerTurn = turnsSaved !== Infinity ? price / turnsSaved : Infinity; + + return { item, tier, pricePerTurn }; + }) + .filter(({ pricePerTurn }) => pricePerTurn !== Infinity) // Remove invalid entries + .sort((a, b) => a.pricePerTurn - b.pricePerTurn); // Sort by pricePerTurn (ascending) + + // Print the sorted list + filteredItems.forEach(({ item, tier, pricePerTurn }) => { + print(`Item: ${item}, Tier: ${tier}, Price per turn saved: ${pricePerTurn.toFixed(2)}`); + }); +} diff --git a/src/tasks/csrunleg.ts b/src/tasks/csrunleg.ts deleted file mode 100644 index 7dee2b6..0000000 --- a/src/tasks/csrunleg.ts +++ /dev/null @@ -1,431 +0,0 @@ -import { - buy, - cliExecute, - drink, - fullnessLimit, - getClanName, - getWorkshed, - haveEffect, - hippyStoneBroken, - holiday, - inebrietyLimit, - itemAmount, - mallPrice, - myAdventures, - myAscensions, - myFullness, - myInebriety, - myLevel, - mySign, - mySpleenUse, - numericModifier, - print, - pvpAttacksLeft, - retrieveItem, - setProperty, - spleenLimit, - toBoolean, - use, - useFamiliar, - useSkill, - visitUrl, -} from "kolmafia"; -import { - $coinmaster, - $effect, - $effects, - $familiar, - $item, - $items, - $skill, - AsdonMartin, - gameDay, - get, - have, - set, - uneffect, -} from "libram"; - -import { args } from "../args"; - -import { getCurrentLeg, Leg, Quest } from "./structure"; -import { - canDiet, - doneAdventuring, - getGarden, - pvpCloset, - stooperDrunk, - totallyDrunk, -} from "./utils"; - -let pajamas = false; -let smoke = 1; -const offhandWorth = have($familiar`Left-Hand Man`); -let garboDone = false; - -export function CSQuests(): Quest[] { - return [ - { - name: "Community Service Run", - completed: () => getCurrentLeg() !== Leg.Run || get("kingLiberated"), - tasks: [ - { - name: "Whitelist VIP Clan", - completed: () => !args.clan || getClanName().toLowerCase() === args.clan.toLowerCase(), - do: () => cliExecute(`/whitelist ${args.clan}`), - }, - { - name: "Break Stone", - ready: () => !args.safepvp, - completed: () => hippyStoneBroken() || !args.pvp, - do: (): void => { - visitUrl("peevpee.php?action=smashstone&pwd&confirm=on", true); - visitUrl("peevpee.php?place=fight"); - }, - }, - { - name: "Prep Fireworks Shop", - completed: () => - !have($item`Clan VIP Lounge key`) || get("_goorboFireworksPrepped", false), - do: () => { - visitUrl("clan_viplounge.php?action=fwshop&whichfloor=2"); - set("_goorboFireworksPrepped", true); - }, - tracking: "Run", - }, - { - name: "Run", - completed: () => get("kingLiberated"), - do: () => cliExecute(args.csscript), - tracking: "Run", - }, - ], - }, - { - name: "Post-Community Service Aftercore", - ready: () => getCurrentLeg() === Leg.Run && get("kingLiberated", false), - completed: () => totallyDrunk() && pajamas, - tasks: [ - { - name: "Pull All", - completed: () => get("lastEmptiedStorage") === myAscensions(), - do: () => cliExecute("pull all; refresh all"), - }, - { - name: "PvP Closet Safety 1", - ready: () => args.pvp && get("autoSatisfyWithCloset") && !args.safepvp, - completed: () => toBoolean(get("_safetyCloset1")), - do: () => pvpCloset(1), - }, - { - name: "Ensure prefs reset", - completed: () => !get("_folgerInitialConfig", false), - do: () => cliExecute("set _folgerInitialConfig = false"), - }, - { - name: "But dad I don't want to feel lost", - completed: () => !have($effect`Feeling Lost`), - do: () => uneffect($effect`Feeling Lost`), - }, - { - name: "Clear citizen", - completed: () => get("_citizenZone", "") !== "Madness Bakery", - do: (): void => { - uneffect($effect`Citizen of a Zone`); - cliExecute(`set _citizenZone = ""`); - }, - }, - { - name: "Wardrobe-o-matic", - ready: () => myLevel() >= 15 && have($item`wardrobe-o-matic`), - completed: () => get("_wardrobeUsed", false), - do: (): void => { - use($item`wardrobe-o-matic`); - cliExecute("set _wardrobeUsed = true"); - }, - limit: { tries: 1 }, - }, - { - name: "Smoke em if you got em", - ready: () => get("getawayCampsiteUnlocked"), - completed: () => !have($item`stick of firewood`) || smoke >= 10, - do: (): void => { - if (mallPrice($item`stick of firewood`) <= 200) buy($item`stick of firewood`, 10); - while (have($item`stick of firewood`)) { - setProperty( - "choiceAdventure1394", - `1&message=${smoke} Thanks Seraphiii for writing Candywrapper!`, - ); - use(1, $item`campfire smoke`); - print(`Smoked ${smoke} firewoods!`); - smoke = smoke + 1; - } - if (mallPrice($item`stick of firewood`) <= 200) buy($item`stick of firewood`, 1); - }, - }, - { - name: "Acquire Carpe", - completed: () => !args.carpe || have($item`carpe`), - do: () => cliExecute("acquire carpe"), - }, - { - name: "Unlock Desert", - completed: () => have($item`bitchin' meatcar`), - do: () => cliExecute("acquire bitchin"), - }, - { - name: "Drink Pre-Tune", - ready: () => - mySign().toLowerCase() === "blender" && - myLevel() >= 7 && - have($item`mime army shotglass`) && - (have($item`astral pilsner`) || have($item`astral six-pack`)), - completed: () => - get("_mimeArmyShotglassUsed") || !have($item`hewn moon-rune spoon`) || get("moonTuned"), - prepare: () => { - if (have($item`astral six-pack`)) use($item`astral six-pack`); - }, - do: () => drink(1, $item`astral pilsner`), - }, - { - name: "Moon Spoon", - completed: () => - !have($item`hewn moon-rune spoon`) || - get("moonTuned") || - mySign().toLowerCase() === "wombat", - do: () => cliExecute("spoon wombat"), - }, - { - name: "Drive Observantly", - completed: () => - getWorkshed() !== $item`Asdon Martin keyfob (on ring)` || - haveEffect($effect`Driving Observantly`) >= - (totallyDrunk() || !have($item`Drunkula's wineglass`) - ? myAdventures() - : myAdventures() + 60), - do: () => - AsdonMartin.drive( - $effect`Driving Observantly`, - totallyDrunk() || !have($item`Drunkula's wineglass`) - ? myAdventures() - : myAdventures() + 60, - false, - ), - limit: { tries: 5 }, - }, - { - name: "Breakfast", - completed: () => get("breakfastCompleted"), - do: () => cliExecute("breakfast"), - }, - { - name: "Garbo", - ready: () => !holiday().includes("Halloween"), - completed: () => (myAdventures() === 0 && !canDiet()) || stooperDrunk(), - prepare: () => uneffect($effect`Beaten Up`), - do: () => cliExecute(`${args.garbo}`), - post: () => - $effects`Power Ballad of the Arrowsmith, Stevedave's Shanty of Superiority, The Moxious Madrigal, The Magical Mojomuscular Melody, Aloysius' Antiphon of Aptitude, Ur-Kel's Aria of Annoyance` - .filter((ef) => have(ef)) - .forEach((ef) => uneffect(ef)), - clear: "all", - tracking: "Garbo", - }, - { - name: "CONSUME ALL", - ready: () => holiday().includes("Halloween"), - completed: () => - myFullness() >= fullnessLimit() && - mySpleenUse() >= spleenLimit() && - myInebriety() >= inebrietyLimit(), - do: () => cliExecute("consume ALL"), - }, - { - name: "Garbo Nobarf", - ready: () => holiday().includes("Halloween"), - completed: () => garboDone, - do: (): void => { - cliExecute(`${args.garbo} nodiet nobarf target="witchess knight"`); - garboDone = true; - }, - }, - { - name: "Garboween", - ready: () => holiday().includes("Halloween"), - completed: () => Math.floor(myAdventures() / 5) < 1, - prepare: () => uneffect($effect`Beaten Up`), - do: (): void => { - if (have($familiar`Trick-or-Treating Tot`)) - cliExecute("familiar Trick-or-Treating Tot"); - else if (have($familiar`Red-Nosed Snapper`)) cliExecute("familiar snapper"); - cliExecute(`freeCandy ${myAdventures()}`); - }, - post: () => { - if (myAdventures() === 0) - $effects`Power Ballad of the Arrowsmith, Stevedave's Shanty of Superiority, The Moxious Madrigal, The Magical Mojomuscular Melody, Aloysius' Antiphon of Aptitude, Ur-Kel's Aria of Annoyance` - .filter((ef) => have(ef)) - .forEach((ef) => uneffect(ef)); - }, - clear: "all", - tracking: "Garbo", - }, - { - name: "PvP Closet Safety 2", - ready: () => args.pvp && get("autoSatisfyWithCloset") && !args.safepvp, - completed: () => toBoolean(get("_safetyCloset2")), - do: () => pvpCloset(2), - }, - { - name: "Turn in FunFunds", - ready: () => get("_stenchAirportToday") && itemAmount($item`FunFunds™`) >= 20, - completed: () => have($item`one-day ticket to Dinseylandfill`), - do: () => - buy($coinmaster`The Dinsey Company Store`, 1, $item`one-day ticket to Dinseylandfill`), - tracking: "Garbo", - }, - { - name: "PvP", - ready: () => doneAdventuring() && !args.safepvp, - completed: () => pvpAttacksLeft() === 0 || !hippyStoneBroken(), - do: (): void => { - cliExecute("unequip"); - cliExecute("UberPvPOptimizer"); - cliExecute(`PVP_MAB target=${args.pvpTarget}`); - }, - }, - { - name: "Stooper", - ready: () => - myInebriety() === inebrietyLimit() && - have($item`tiny stillsuit`) && - get("familiarSweat") >= 300, - completed: () => !have($familiar`Stooper`) || stooperDrunk(), - do: () => { - useFamiliar($familiar`Stooper`); - cliExecute("drink stillsuit distillate"); - }, - }, - { - name: "Nightcap", - ready: () => doneAdventuring(), - completed: () => totallyDrunk(), - do: () => cliExecute("CONSUME NIGHTCAP"), - }, - { - name: "Do Pizza", - ready: () => doneAdventuring(), - completed: () => - have($item`Pizza of Legend`) && - have($item`Deep Dish of Legend`) && - have($item`Calzone of Legend`), - do: (): void => { - !have($item`Pizza of Legend`) ? retrieveItem($item`Pizza of Legend`) : undefined; - !have($item`Deep Dish of Legend`) - ? retrieveItem($item`Deep Dish of Legend`) - : undefined; - !have($item`Calzone of Legend`) ? retrieveItem($item`Calzone of Legend`) : undefined; - }, - }, - { - name: "Plant Garden", - ready: () => - doneAdventuring() && - !!$items`packet of rock seeds, packet of thanksgarden seeds, Peppermint Pip Packet, packet of winter seeds, packet of beer seeds, packet of pumpkin seeds, packet of dragon's teeth`.find( - (it) => have(it), - ), - completed: () => getGarden() !== $item`packet of tall grass seeds`, - do: () => { - use( - $items`packet of rock seeds, packet of thanksgarden seeds, Peppermint Pip Packet, packet of winter seeds, packet of beer seeds, packet of pumpkin seeds, packet of dragon's teeth`.find( - (it) => have(it), - ) || $item`none`, - ); - cliExecute("garden pick"); - }, - }, - { - name: "Freecandy Drunk", - ready: () => holiday().includes("Halloween"), - completed: () => stooperDrunk() || (!canDiet() && Math.floor(myAdventures() / 5) === 0), - prepare: () => uneffect($effect`Beaten Up`), - do: (): void => { - cliExecute(`freeCandy ${myAdventures()}`); - }, - post: () => { - if (myAdventures() === 0) - $effects`Power Ballad of the Arrowsmith, Stevedave's Shanty of Superiority, The Moxious Madrigal, The Magical Mojomuscular Melody, Aloysius' Antiphon of Aptitude, Ur-Kel's Aria of Annoyance` - .filter((ef) => have(ef)) - .forEach((ef) => uneffect(ef)); - }, - clear: "all", - tracking: "Garbo", - limit: { tries: 1 }, //this will run again after installing CMC, by magic - }, - { - name: "Offhand Remarkable", - ready: () => have($item`august scepter`), - completed: () => - have($effect`Offhand Remarkable`) || - get("_aug13Cast", false) || - (get("_augSkillsCast", 0) >= 5 && gameDay().getDate() !== 13), - do: () => useSkill($skill`Aug. 13th: Left/Off Hander's Day!`), - }, - { - name: "Alternative Offhand Remarkable", - ready: () => offhandWorth, - completed: () => have($effect`Offhand Remarkable`), - do: (): void => { - retrieveItem($item`pocket wish`); - cliExecute("genie effect Aug. 13th: Left/Off Hander's Day!"); - }, - }, - { - name: "Item Cleanup", - // eslint-disable-next-line libram/verify-constants - completed: () => get("_cleanupToday", false) || args.itemcleanup === "", - do: (): void => { - cliExecute(`${args.itemcleanup}`); - cliExecute("set _cleanupToday = true"); - }, - tracking: "Item Cleanup", - }, - { - name: "PvP Closet Safety 3", - ready: () => args.pvp && get("autoSatisfyWithCloset") && !args.safepvp, - completed: () => toBoolean(get("_safetyCloset3")), - do: () => pvpCloset(3), - }, - { - name: "Pajamas", - completed: () => have($item`burning cape`), - acquire: [ - { item: $item`clockwork maid`, price: 7 * get("valueOfAdventure"), optional: true }, - { item: $item`burning cape` }, - ], - do: (): void => { - if (have($item`clockwork maid`)) { - use($item`clockwork maid`); - } - cliExecute("maximize adv, switch disembodied hand, -switch left-hand man"); - pajamas = true; - }, - }, - { - name: "Alert-No Nightcap", - ready: () => !doneAdventuring(), - completed: () => stooperDrunk(), - do: (): void => { - const targetAdvs = 100 - numericModifier("adventures"); - print("candyWrapper completed, but did not overdrink.", "red"); - if (targetAdvs < myAdventures() && targetAdvs > 0) - print( - `Rerun with fewer than ${targetAdvs} adventures for candyWrapper to handle your diet`, - "red", - ); - else print("Something went wrong.", "red"); - }, - }, - ], - }, - ]; -} diff --git a/src/tasks/repeatableTasks.ts b/src/tasks/repeatableTasks.ts new file mode 100644 index 0000000..19efc2b --- /dev/null +++ b/src/tasks/repeatableTasks.ts @@ -0,0 +1,599 @@ +import { CombatStrategy } from "grimoire-kolmafia"; +import { + availableAmount, + canAdventure, + cliExecute, + equip, + Familiar, + fullnessLimit, + getCampground, + getClanLounge, + getClanName, + guildStoreAvailable, + handlingChoice, + hippyStoneBroken, + holiday, + inebrietyLimit, + lastChoice, + mallPrice, + maximize, + myAdventures, + myClass, + myDaycount, + myFullness, + myHp, + myInebriety, + myLevel, + myMaxhp, + myPrimestat, + mySpleenUse, + print, + restoreHp, + retrieveItem, + runChoice, + spleenLimit, + storageAmount, + takeStorage, + toInt, + toItem, + toSkill, + use, + useFamiliar, + useSkill, + visitUrl, +} from "kolmafia"; +import { + $class, + $effect, + $effects, + $familiar, + $item, + $items, + $location, + $monster, + $skill, + $skills, + $stat, + AprilingBandHelmet, + CombatLoversLocket, + get, + getTodaysHolidayWanderers, + have, + Macro, + PocketProfessor, + set, + uneffect, +} from "libram"; + +import { args } from "../args"; + +import { Task } from "./structure"; +import { getGarden, maxBase, nextCyberZone, pantogram, pantogramReady, stooperDrunk, totallyDrunk } from "./utils"; +import { buskAt, findOptimalOutfitPower, reconstructOutfit } from "../beret"; + +const bestFam = () => + famCheck($familiar`Pocket Professor`) + ? $familiar`Pocket Professor` + : famCheck($familiar`Chest Mimic`) + ? $familiar`Chest Mimic` + : famCheck($familiar`Grey Goose`) + ? $familiar`Grey Goose` + : $familiar`Grey Goose`; + +function famCheck(fam: Familiar): boolean { + return have(fam) && fam.experience < 400; +} + +const doSmol = args.smol ? true : false; + +export function postRunQuests(): Task[] { + return [ + { + name: "Whitelist VIP Clan", + completed: () => !args.clan || getClanName().toLowerCase() === args.clan.toLowerCase(), + do: () => cliExecute(`/whitelist ${args.clan}`), + }, + { + name: "Set Garbo Pref", + completed: () => get("_garbo_beSelfish",false), + do: () => set("_garbo_beSelfish",true), + }, + { + name: "Breakfast", + completed: () => get("breakfastCompleted"), + do: () => cliExecute("breakfast"), + tracking: "Breakfast" + }, + { + name: "Radio", + // eslint-disable-next-line libram/verify-constants + ready: () => have($item`Allied Radio Backpack`) && get("_alliedRadioDropsUsed", 0) < 3, + // eslint-disable-next-line libram/verify-constants + completed: () => get("_alliedRadioDropsUsed", 0) >= 3, + do: () => { + const visitRadio = () => visitUrl(`inventory.php?action=requestdrop&pwd`); + visitRadio(); + if (!handlingChoice() || lastChoice() !== 1563) visitRadio(); + runChoice(1, `request=radio`); + }, + limit: { tries: 3 }, + }, + { + name: "Harvest Garden", + completed: () => + getGarden() === $item`none` || + getGarden() === $item`packet of mushroom spores` || + getCampground()[getGarden().name] === 0, + do: () => cliExecute("garden pick"), + tracking: "Dailies", + limit: { tries: 3 }, + }, + { + name: "Apriling", + ready: () => AprilingBandHelmet.canChangeSong(), + completed: () => have($effect`Apriling Band Celebration Bop`), + do: (): void => { + AprilingBandHelmet.conduct($effect`Apriling Band Celebration Bop`); + }, + limit: { tries: 1 }, + }, + { + name: "SIT Course", + // eslint-disable-next-line libram/verify-constants + ready: () => have($item`S.I.T. Course Completion Certificate`), + completed: () => get("_sitCourseCompleted", false), + choices: { + 1494: 2, + }, + do: () => + // eslint-disable-next-line libram/verify-constants + use($item`S.I.T. Course Completion Certificate`), + }, + { + name: "Restore HP", + completed: () => myHp() > 0.5 * myMaxhp(), + do: () => restoreHp(0.95 * myMaxhp()), + tracking: "Other" + }, + { + name: "Implement Glitch", + ready: () => have($item`[glitch season reward name]`), + completed: () => get("_glitchItemImplemented"), + do: () => use($item`[glitch season reward name]`), + }, + { + name: "Unlock Guild", + ready: () => + myClass() === $class`Seal Clubber` && + Math.min( + ...$items`figurine of a wretched-looking seal, seal-blubber candle`.map((it) => + availableAmount(it), + ), + ) < 20 && + doSmol, + completed: () => guildStoreAvailable() || myAdventures() === 0 || stooperDrunk(), + do: () => cliExecute("guild"), + choices: { + //sleazy back alley + 108: 4, //craps: skip + 109: 1, //drunken hobo: fight + 110: 4, //entertainer: skip + 112: 2, //harold's hammer: skip + 21: 2, //under the knife: skip + //haunted pantry + 115: 1, //drunken hobo: fight + 116: 4, //singing tree: skip + 117: 1, //knob goblin chef: fight + 114: 2, //birthday cake: skip + //outskirts of cobb's knob + 113: 2, //knob goblin chef: fight + 111: 3, //chain gang: fight + 118: 2, //medicine quest: skip + }, + outfit: () => ({ + familiar: bestFam(), + modifier: `${maxBase()}, ${ + myPrimestat() === $stat`Muscle` ? "100 combat rate 20 max" : "-100 combat rate" + }, 250 bonus carnivorous potted plant`, + }), + combat: new CombatStrategy() + .macro( + () => + Macro.step("pickpocket") + .externalIf( + have($skill`Curse of Weaksauce`), + Macro.trySkill($skill`Curse of Weaksauce`), + Macro.tryItem($item`electronics kit`), + ) + .tryItem($item`porquoise-handled sixgun`) + .trySkill($skill`Sing Along`) + .attack() + .repeat(), + getTodaysHolidayWanderers(), + ) + .macro(() => + Macro.step("pickpocket") + .trySkill($skill`Sing Along`) + .tryItem($item`porquoise-handled sixgun`) + .attack() + .repeat(), + ), + tracking: "Other" + }, + { + name: "Stock Up on MMJs", + ready: () => + guildStoreAvailable() && + (myClass().primestat === $stat`Mysticality` || + (myClass() === $class`Accordion Thief` && myLevel() >= 9)), + completed: () => availableAmount($item`magical mystery juice`) >= 500, + acquire: [ + { + item: $item`magical mystery juice`, + num: 500, + }, + ], + do: () => false, + tracking: "Other" + }, + { + name: "Buy Seal Summoning Supplies", + ready: () => myClass() === $class`Seal Clubber` && guildStoreAvailable(), + completed: () => + Math.min( + ...$items`figurine of a wretched-looking seal, seal-blubber candle`.map((it) => + availableAmount(it), + ), + ) >= 40, + acquire: $items`figurine of a wretched-looking seal, seal-blubber candle`.map((it) => ({ + item: it, + num: 500, + })), + do: () => false, + tracking: "Other" + }, + { + name: "Run CyberRealm", + ready: () => mallPrice($item`1`) > 3_000 && myAdventures() > 60 && myInebriety() < inebrietyLimit(), + prepare: () => { + $effects`Astral Shell, Elemental Saucesphere, Scarysauce`.forEach((ef) => { + if (!have(ef)) useSkill(toSkill(ef)); + }); + }, + completed: () => nextCyberZone() === $location`none`, // $location`Cyberzone 1`.turnsSpent >= 19 * myDaycount(), + choices: { 1545: 1, 1546: 1, 1547: 1, 1548: 1, 1549: 1, 1550: 1 }, + do: () => nextCyberZone(), + outfit: { + hat: $item`Crown of Thrones`, + back: $item`unwrapped knock-off retro superhero cape`, + shirt: $item`zero-trust tanktop`, + weapon: $item`June cleaver`, + offhand: $item`visual packet sniffer`, + pants: $item`digibritches`, + acc1: $item`retro floppy disk`, + acc2: $item`retro floppy disk`, + acc3: $item`retro floppy disk`, + famequip: $item`familiar-in-the-middle wrapper`, + modes: { retrocape: ["vampire", "hold"] }, + riders: { "crown-of-thrones": $familiar`Mini Kiwi` }, + }, + combat: new CombatStrategy().macro(() => + Macro.if_( + "!monsterphylum construct", + Macro.trySkill($skill`Sing Along`) + .trySkill($skill`Micrometeorite`) + .trySkill($skill`Saucestorm`) + .trySkill($skill`Saucestorm`) + .trySkill($skill`Saucestorm`) + .trySkill($skill`Saucestorm`) + .attack() + .repeat(), + ) + .skill($skill`Throw Cyber Rock`) + .repeat(), + ), + limit: { skip: 60 }, + tracking: "Cyber Realm" + }, + { + name: "Wardrobe-o-matic", + ready: () => myLevel() >= 15 && have($item`wardrobe-o-matic`), + completed: () => get("_wardrobeUsed", false), + do: (): void => { + use($item`wardrobe-o-matic`); + cliExecute("set _wardrobeUsed = true"); + }, + limit: { tries: 1 }, + }, + { + name: "Beret? Beret.", + ready: () => have(toItem(11919)), + completed: () => get("_beretBuskingUses",0) >= 5, + do: () => { + const uselessEffects = $effects`How to Scam Tourists, Empathy`; + let busk = get("_beretBuskingUses",0); + print(`Busking starting at ${busk} uses.`) + for(; busk <5; busk++) { + const best = findOptimalOutfitPower( + { + "Familiar Weight": 10, + }, busk, uselessEffects, true + ); + const outfit = reconstructOutfit(best); + print(`Outfit is: ${outfit?.hat}, ${outfit?.pants}, ${outfit?.shirt}`); + print(`Busking at ${best} power.`); + buskAt(best, true); + } + }, + limit: { tries: 5 }, + }, + { + name: "Candy Deviler", + // eslint-disable-next-line libram/verify-constants + ready: () => have($item`candy egg deviler`), + completed: () => toInt(get("_candyEggsDeviled")) >= 3, + do: () => { + visitUrl(`inventory.php?action=eggdevil&pwd`); + visitUrl("choice.php?a=3054&whichchoice=1544&option=1&pwd"); + visitUrl("choice.php?a=3054&whichchoice=1544&option=1&pwd"); + visitUrl("choice.php?a=3054&whichchoice=1544&option=1&pwd"); + }, + tracking: "Garbo" + }, + ]; +} + +let duffo = false; +const loungeItems = getClanLounge(); +const hasClanFloundry = loungeItems["Clan Floundry"] === 1; +const hasCarpe = loungeItems["carpe"] !== undefined && loungeItems["carpe"] >= 1; + +export function preRunQuests(): Task[] { + return [ + { + name: "Whitelist VIP Clan", + completed: () => !args.clan || getClanName().toLowerCase() === args.clan.toLowerCase(), + do: () => cliExecute(`/whitelist ${args.clan}`), + choices: { + 1507: 1, + }, + }, + { + name: "Get Floundry item", + ready: () => have($item`Clan VIP Lounge key`) && !args.carpe && hasClanFloundry && hasCarpe, + completed: () => get("_floundryItemCreated"), + do: (): void => { + if(getClanLounge()) + retrieveItem($item`carpe`); + }, + limit: { tries: 1 }, + }, + { + name: "Unpack Duffel Bag", + completed: () => duffo, + do: () => { + visitUrl("inventory.php?action=skiduffel&pwd"); + duffo = true; + }, + }, + { + name: "Pantogramming", + ready: () => pantogramReady() && args.casual, + completed: () => pantogram(), + do: () => pantogram(), + tracking: "Farming Prep" + }, + { + name: "Trip Scrip", + ready: () => args.ih8u || args.smol || args.robot, + completed: () => get("_roninStoragePulls").includes(`${$item`Shore Inc. Ship Trip Scrip`.id}`) || storageAmount($item`Shore Inc. Ship Trip Scrip`) === 0, + do: () => takeStorage($item`Shore Inc. Ship Trip Scrip`,1), + tracking: "Farming Prep" + }, + { + name: "LGR Seed", + ready: () => + have($item`lucky gold ring`) && + have($item`one-day ticket to Dinseylandfill`) && + !args.garboascend.includes("penguin") && + !(args.cs || args.zooto), + completed: () => get("_stenchAirportToday") || get("stenchAirportAlways"), + do: () => use($item`one-day ticket to Dinseylandfill`), + tracking: "Farming Prep", + }, + { + name: "Break Stone", + ready: () => !args.safepvp, + completed: () => hippyStoneBroken() || !args.pvp, + do: (): void => { + visitUrl("peevpee.php?action=smashstone&pwd&confirm=on", true); + visitUrl("peevpee.php?place=fight"); + }, + }, + ]; +} + +export function noBarf(): Task[] { + return [ + { + name: "CONSUME ALL", + ready: () => holiday().includes("Halloween") || args.crimbo || args.chrono, + completed: () => + myFullness() >= fullnessLimit() && + mySpleenUse() >= spleenLimit() && + myInebriety() >= inebrietyLimit(), + do: () => cliExecute("consume ALL"), + tracking: "Organs" + }, + { + name: "PProf Penguin Chain", + ready: () => ((args.garbo.includes("penguin") && args.garbo.includes(`target="black crayon penguin"`) && myDaycount() === 0) + || (args.garboascend.includes("penguin") && args.garboascend.includes(`target="black crayon penguin"`) && myDaycount() > 0)) && + CombatLoversLocket.canReminisce($monster`Black Crayon Flower`) && + PocketProfessor.have() && + PocketProfessor.lecturesDelivered() < 3, + prepare: () => { + if (!have($item`Pocket Professor memory chip`)) { + retrieveItem(1, $item`Pocket Professor memory chip`) + } + + useFamiliar($familiar`Pocket Professor`); + maximize(`10 familiar weight, -tie, 5.25 Meat Drop, -"equip Amulet of Perpetual Darkness", -"equip Buddy Bjorn", -"equip Roman Candelabra", -"equip Spooky Putty ball", -"equip Spooky Putty leotard", -"equip Spooky Putty mitre", -"equip Spooky Putty snake", -"equip broken champagne bottle", -"equip cheap sunglasses", -"equip dice-shaped backpack", -"equip papier-masque", -"equip papier-mitre", -"equip smoke ball", -"equip stinky fannypack", 100 "bonus pantogram pants", 124.26 "bonus June cleaver", 135 "bonus Crown of Thrones", 180 "bonus Mr. Screege's spectacles", 222.92 "bonus mafia thumb ring", 253.61 "bonus can of mixed everything", 284 "bonus lucky gold ring", 6.25 "bonus Powerful Glove", 700 "bonus mafia pointer finger ring"`, false); + $skills`Empathy of the Newt, Leash of Linguini`.forEach((sk) => useSkill(sk)); + $items`Pocket Professor memory chip, tearaway pants`.forEach((it) => equip(it)); + }, + completed: () => PocketProfessor.currentlyAvailableLectures() === 0, + do: () => CombatLoversLocket.reminisce($monster`Black Crayon Flower`,""), + combat: new CombatStrategy().macro( + Macro.externalIf(PocketProfessor.currentlyAvailableLectures() > 0, + Macro.trySkill($skill`lecture on relativity`) + .trySkill($skill`Sing Along`) + .trySkill($skill`Bowl Straight Up`) + .trySkill($skill`Tear Away your Pants!`) + .trySkillRepeat($skill`Saucestorm`), + Macro.trySkill($skill`Sing Along`) + .trySkill($skill`Bowl Straight Up`) + .trySkill($skill`Tear Away your Pants!`) + .trySkillRepeat($skill`Saucestorm`) + ) + ), + tracking: "Garbo" + }, + { + name: "Pantogramming", + ready: () => pantogramReady(), + completed: () => pantogram(), + do: () => pantogram(), + tracking: "Farming Prep" + }, + { + name: "Garbo Nobarf", + ready: () => holiday().includes("Halloween") || args.crimbo || args.chrono, + completed: () => ((myInebriety() > inebrietyLimit()) || (get("_monsterHabitatsRecalled") >=3 && get("_macrometeoriteUses") >= 10)), + do: (): void => { + cliExecute(`garbo nodiet nobarf target="sausage goblin"`); + }, + tracking: "Garbo" + }, + ]; +} + +export function garboWeen(): Task[] { + return [ + { + name: "Freecandy time", + ready: () => holiday().includes("Halloween"), + completed: () => myAdventures() / 5 < 1, + prepare: () => uneffect($effect`Beaten Up`), + do: (): void => { + if (have($familiar`Trick-or-Treating Tot`)) cliExecute("familiar Trick-or-Treating Tot"); + else if (have($familiar`Red-Nosed Snapper`)) cliExecute("familiar snapper"); + cliExecute(`freecandy ${myAdventures()}`); + }, + clear: "all", + tracking: "Freecandy", + limit: { tries: 1 }, //this will run again after installing CMC, by magic + }, + { + name: "Super Nightcap", + ready: () => have($item`Drunkula's wineglass`) && holiday().includes("Halloween"), + completed: () => totallyDrunk(), + do: () => cliExecute(`CONSUME NIGHTCAP`), + tracking: "Organs" + }, + { + name: "Freecandy Drunk", + ready: () => holiday().includes("Halloween"), + completed: () => Math.floor(myAdventures() / 5) === 0, + prepare: () => uneffect($effect`Beaten Up`), + do: (): void => { + useFamiliar($familiar`Red-Nosed Snapper`); + cliExecute(`freeCandy ${myAdventures()}`); + }, + clear: "all", + tracking: "Freecandy", + limit: { tries: 1 }, //this will run again after installing CMC, by magic + }, + ]; +} + +export function chrono(): Task[] { + return [ + { + name: "Chrono", + ready: () => args.chrono && canAdventure($location`The Primordial Stew`), + completed: () => myAdventures() === 0 && myInebriety() >= inebrietyLimit(), + prepare: () => uneffect($effect`Beaten Up`), + do: (): void => { + cliExecute(`${args.chronoscript}`); + }, + clear: "all", + tracking: "Chrono", + limit: { tries: 1 }, //this will run again after installing CMC, by magic + }, + { + name: "Super Nightcap", + ready: () => + have($item`Drunkula's wineglass`) && + args.chrono && + canAdventure($location`The Primordial Stew`) && + myDaycount() > 1, + completed: () => totallyDrunk(), + do: () => cliExecute(`CONSUME NIGHTCAP`), + tracking: "Organs" + }, + { + name: "Chrono Drunk", + ready: () => args.chrono && canAdventure($location`The Primordial Stew`) && myDaycount() > 1, + completed: () => myAdventures() === 0, + prepare: () => uneffect($effect`Beaten Up`), + do: (): void => { + cliExecute(`${args.chronoscript}`); + }, + clear: "all", + tracking: "Chrono", + limit: { tries: 1 }, //this will run again after installing CMC, by magic + }, + ]; +} + +export function crimbo(): Task[] { + return [ + { + name: "Crimbo Time", + ready: () => args.crimbo, + completed: () => { + if (myDaycount() === 1) + return myAdventures() === 0 || myInebriety() > inebrietyLimit() + else return myAdventures() === 0 + }, + prepare: () => uneffect($effect`Beaten Up`), + do: (): void => { + cliExecute(`${args.crimboscript}`); + }, + clear: "all", + tracking: "Crimbo", + limit: { tries: 1 }, //this will run again after installing CMC, by magic + }, + { + name: "Super Nightcap", + ready: () => + have($item`Drunkula's wineglass`) && holiday().includes("Halloween") && myDaycount() > 1, + completed: () => totallyDrunk(), + do: () => cliExecute(`CONSUME NIGHTCAP`), + tracking: "Organs" + }, + { + name: "Crimbo Drunk", + ready: () => args.crimbo && myDaycount() > 1, + completed: () => myAdventures() === 0 || myDaycount() === 1, + prepare: () => uneffect($effect`Beaten Up`), + do: (): void => { + cliExecute(`${args.crimboscript}`); + }, + clear: "all", + tracking: "Crimbo", + limit: { tries: 1 }, //this will run again after installing CMC, by magic + }, + ]; +} diff --git a/src/tasks/robotrunleg.ts b/src/tasks/robotrunleg.ts deleted file mode 100644 index c3ed8bb..0000000 --- a/src/tasks/robotrunleg.ts +++ /dev/null @@ -1,578 +0,0 @@ -import { CombatStrategy, step } from "grimoire-kolmafia"; -import { - buy, - buyUsingStorage, - cliExecute, - drink, - Effect, - getClanName, - getWorkshed, - hippyStoneBroken, - inebrietyLimit, - itemAmount, - myAdventures, - myAscensions, - myDaycount, - myInebriety, - myLevel, - myMaxhp, - mySign, - numericModifier, - print, - pullsRemaining, - pvpAttacksLeft, - restoreHp, - restoreMp, - retrieveItem, - setProperty, - storageAmount, - toBoolean, - toInt, - use, - useFamiliar, - useSkill, - visitUrl, - wait, -} from "kolmafia"; -import { - $coinmaster, - $effect, - $effects, - $familiar, - $familiars, - $item, - $items, - $location, - $skill, - AprilingBandHelmet, - get, - have, - Macro, - set, - uneffect, -} from "libram"; - -import { args } from "../args"; - -import { GarboWeenQuest } from "./Garboween"; -import { getCurrentLeg, Leg, Quest } from "./structure"; -import { - backstageItemsDone, - bestFam, - doneAdventuring, - halloween, - haveAll, - maxBase, - pvpCloset, - stooperDrunk, - totallyDrunk, -} from "./utils"; - -let pajamas = false; -let smoke = 1; - -function firstWorkshed() { - return ( - $items`model train set, Asdon Martin keyfob (on ring), cold medicine cabinet, Little Geneticist DNA-Splicing Lab, portable Mayo Clinic`.find( - (it) => have(it) || getWorkshed() === it || storageAmount(it) > 0, - ) || $item`none` - ); -} -const sasqBonus = (0.5 * 30 * 1000) / get("valueOfAdventure"); -const ratskinBonus = (0.3 * 40 * 1000) / get("valueOfAdventure"); - -export function RobotQuests(): Quest[] { - return [ - { - name: "Robot Run", - completed: () => getCurrentLeg() !== Leg.Run || get("kingLiberated", false), - tasks: [ - { - name: "Whitelist VIP Clan", - completed: () => !args.clan || getClanName().toLowerCase() === args.clan.toLowerCase(), - do: () => cliExecute(`/whitelist ${args.clan}`), - choices: { - 1507: 1, - }, - }, - { - name: "Get Floundry item", - ready: () => have($item`Clan VIP Lounge key`) && !args.carpe, - completed: () => get("_floundryItemCreated"), - do: (): void => { - retrieveItem($item`carpe`); - }, - limit: { tries: 1 }, - }, - { - name: "Prep Fireworks Shop", - completed: () => - !have($item`Clan VIP Lounge key`) || get("_goorboFireworksPrepped", false), - do: () => { - visitUrl("clan_viplounge.php?action=fwshop&whichfloor=2"); - set("_goorboFireworksPrepped", true); - }, - }, - { - name: "Pre-Pulls", - completed: () => - pullsRemaining() === 0 || - !args.pulls.find( - (it) => !have(it) && !get("_roninStoragePulls").includes(toInt(it).toString()), - ), //can't find a pull that (we dont have and it hasn't been pulled today) - do: () => - args.pulls.forEach((it) => { - if (!have(it) && !get("_roninStoragePulls").includes(toInt(it).toString())) { - if (storageAmount(it) === 0) buyUsingStorage(it); //should respect autoBuyPriceLimit - cliExecute(`pull ${it}`); - } - }), - }, - { - name: "LGR Seed", - ready: () => - have($item`lucky gold ring`) && have($item`one-day ticket to Dinseylandfill`), - completed: () => get("_stenchAirportToday") || get("stenchAirportAlways"), - do: () => use($item`one-day ticket to Dinseylandfill`), - tracking: "Garbo", - }, - { - name: "Break Stone", - ready: () => !args.safepvp, - completed: () => hippyStoneBroken() || !args.pvp, - do: (): void => { - visitUrl("peevpee.php?action=smashstone&pwd&confirm=on", true); - visitUrl("peevpee.php?place=fight"); - }, - }, - { - name: "Run", - completed: () => step("questL13Final") > 11, - do: () => cliExecute(args.robotscript), - clear: "all", - tracking: "Run", - }, - { - name: "Free King", - ready: () => step("questL13Final") > 11, - completed: () => get("kingLiberated", false), - do: (): void => { - visitUrl("place.php?whichplace=nstower&action=ns_11_prism"); - }, - clear: "all", - tracking: "Ignore", - }, - ], - }, - { - name: "Post-Robot Aftercore", - ready: () => myDaycount() === 1 && get("kingLiberated", false), - completed: () => totallyDrunk() && pajamas, - tasks: [ - { - name: "Pull All", - completed: () => get("lastEmptiedStorage") === myAscensions(), - do: () => cliExecute("pull all; refresh all"), - }, - { - name: "Clear citizen", - completed: () => !get("_citizenZone", "").includes("castle"), - do: (): void => { - uneffect($effect`Citizen of a Zone`); - cliExecute(`set _citizenZone = ""`); - }, - }, - { - name: "PvP Closet Safety 1", - ready: () => args.pvp && get("autoSatisfyWithCloset") && !args.safepvp, - completed: () => toBoolean(get("_safetyCloset1")), - do: () => pvpCloset(1), - }, - { - name: "Install First Workshed", - ready: () => have(firstWorkshed()), - completed: () => - firstWorkshed() === $item`none` || - get("_workshedItemUsed") || - getWorkshed() !== $item`none`, - do: () => use(firstWorkshed()), - }, - { - name: "Unlock Garbage Mountain", - completed: () => get("_stenchAirportToday") || get("stenchAirportAlways"), - do: (): void => { - retrieveItem($item`one-day ticket to Dinseylandfill`); - use($item`one-day ticket to Dinseylandfill`); - }, - tracking: "Garbo", - }, - { - name: "Wardrobe-o-matic", - ready: () => myLevel() >= 15 && have($item`wardrobe-o-matic`), - completed: () => get("_wardrobeUsed", false), - do: (): void => { - use($item`wardrobe-o-matic`); - cliExecute("set _wardrobeUsed = true"); - }, - limit: { tries: 1 }, - }, - { - name: "Apriling Part 1", - ready: () => AprilingBandHelmet.canChangeSong(), - completed: () => have($effect`Apriling Band Celebration Bop`), - do: (): void => { - AprilingBandHelmet.conduct($effect`Apriling Band Celebration Bop`); - }, - limit: { tries: 1 }, - }, - { - name: "Drink Pre-Tune", - ready: () => - mySign().toLowerCase() === "blender" && - myLevel() >= 7 && - have($item`mime army shotglass`) && - (have($item`astral pilsner`) || have($item`astral six-pack`)), - completed: () => - get("_mimeArmyShotglassUsed") || !have($item`hewn moon-rune spoon`) || get("moonTuned"), - prepare: () => { - if (have($item`astral six-pack`)) use($item`astral six-pack`); - }, - do: () => drink(1, $item`astral pilsner`), - }, - { - name: "Moon Spoon", - completed: () => - !have($item`hewn moon-rune spoon`) || - get("moonTuned") || - mySign().toLowerCase() === "wombat", - do: () => cliExecute("spoon wombat"), - }, - { - name: "Gold Wedding Ring", - completed: () => - !have($skill`Comprehensive Cartography`) || - myAscensions() === get("lastCartographyBooPeak"), - choices: { 1430: 3, 606: 4, 610: 1, 1056: 1 }, - do: $location`A-Boo Peak`, - outfit: { modifier: "initiative 40 min 40 max, -tie" }, - }, - { - name: "Breakfast", - completed: () => get("breakfastCompleted"), - do: () => cliExecute("breakfast"), - }, - { - name: "Emergency Drink", - ready: () => myAdventures() < 25, - completed: () => get("_mimeArmyShotglassUsed") || !have($item`mime army shotglass`), - prepare: () => { - if (have($item`astral six-pack`)) use($item`astral six-pack`); - }, - do: () => { - while (myAdventures() < 25) { - drink(1, $item`astral pilsner`); - } - }, - }, - { - name: "Emergency Drink Part 2", - ready: () => myAdventures() === 0 && myInebriety() < 11, - completed: () => myAdventures() > 0 || myInebriety() >= 11, - prepare: () => { - if (have($item`astral six-pack`)) use($item`astral six-pack`); - }, - do: () => { - while (myAdventures() < 25) { - useSkill($skill`The Ode to Booze`); - drink(1, $item`astral pilsner`); - } - }, - limit: { tries: 6 }, - }, - { - name: "Laugh Floor", - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's lollipop`) || - have($item`observational glasses`), - effects: () => [ - ...(have($skill`Musk of the Moose`) ? $effects`Musk of the Moose` : []), - ...(have($skill`Carlweather's Cantata of Confrontation`) - ? $effects`Carlweather's Cantata of Confrontation` - : []), - ], - prepare: (): void => { - if (!have($effect`Carlweather's Cantata of Confrontation`)) { - cliExecute("kmail to Buffy || 10 Cantata of Confrontation"); - wait(15); - cliExecute("refresh effects"); - } - $effects`Smooth Movements, The Sonata of Sneakiness, Darkened Photons, Shifted Phase`.forEach( - (ef: Effect) => cliExecute(`uneffect ${ef}`), - ); - restoreHp(0.75 * myMaxhp()); - restoreMp(20); - }, - do: $location`The Laugh Floor`, - outfit: () => ({ - familiar: bestFam(), - modifier: `${maxBase()}, 100 combat rate, 3 item, 250 bonus carnivorous potted plant`, - }), - combat: new CombatStrategy().macro( - Macro.trySkill($skill`Curse of Weaksauce`) - .tryItem($item`train whistle`) - .tryItem($item`porquoise-handled sixgun`) - .attack() - .repeat(), - ), - limit: { tries: 15 }, - }, - { - name: "Infernal Rackets Backstage", - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's unicorn`) || - backstageItemsDone(), - effects: () => [ - ...(have($skill`Smooth Movement`) ? $effects`Smooth Movements` : []), - ...(have($skill`The Sonata of Sneakiness`) ? $effects`The Sonata of Sneakiness` : []), - ], - prepare: (): void => { - if (!have($effect`The Sonata of Sneakiness`)) { - cliExecute("kmail to Buffy || 10 Sonata of Sneakiness"); - wait(15); - cliExecute("refresh effects"); - } - $effects`Musk of the Moose, Carlweather's Cantata of Confrontation, Hooooooooonk!`.forEach( - (ef: Effect) => cliExecute(`uneffect ${ef}`), - ); - restoreHp(0.75 * myMaxhp()); - restoreMp(20); - }, - do: $location`Infernal Rackets Backstage`, - outfit: () => ({ - familiar: bestFam(), - modifier: `${maxBase()}, -100 combat rate, 3 item, 250 bonus carnivorous potted plant`, - }), - combat: new CombatStrategy().macro( - Macro.trySkill($skill`Curse of Weaksauce`) - .tryItem($item`train whistle`) - .tryItem($item`porquoise-handled sixgun`) - .attack() - .repeat(), - ), - limit: { tries: 15 }, - }, - { - name: "Mourn", - ready: () => have($item`observational glasses`), - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's lollipop`), - outfit: { - equip: $items`hilarious comedy prop, observational glasses, Victor\, the Insult Comic Hellhound Puppet`, - }, - do: () => cliExecute("panda comedy insult; panda comedy observe"), - }, - { - name: "Sven Golly", - ready: () => backstageItemsDone(), - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's unicorn`), - do: (): void => { - cliExecute( - `panda arena Bognort ${$items`giant marshmallow, gin-soaked blotter paper`.find((a) => - have(a), - )}`, - ); - cliExecute( - `panda arena Stinkface ${$items`beer-scented teddy bear, gin-soaked blotter paper`.find( - (a) => have(a), - )}`, - ); - cliExecute( - `panda arena Flargwurm ${$items`booze-soaked cherry, sponge cake`.find((a) => - have(a), - )}`, - ); - cliExecute(`panda arena Jim ${$items`comfy pillow, sponge cake`.find((a) => have(a))}`); - }, - }, - { - name: "Moaning Panda", - ready: () => haveAll($items`Azazel's lollipop, Azazel's unicorn`), - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's tutu`), - acquire: () => - $items`bus pass, imp air`.map((it) => ({ - item: it, - num: 5, - price: get("valueOfAdventure"), - })), - do: () => cliExecute("panda moan"), - limit: { tries: 3 }, - }, - { - name: "Steel Margarita", - ready: () => haveAll($items`Azazel's tutu, Azazel's lollipop, Azazel's unicorn`), - completed: () => have($skill`Liver of Steel`) || have($item`steel margarita`), - do: () => cliExecute("panda temple"), - }, - { - name: "Liver of Steel", - ready: () => have($item`steel margarita`), - completed: () => have($skill`Liver of Steel`), - do: () => drink(1, $item`steel margarita`), - }, - { - name: "GarboWeen", - ready: () => halloween, - completed: () => myAdventures() === 0 || stooperDrunk(), - do: () => GarboWeenQuest(), - }, - { - name: "Garbo", - ready: () => get("_stenchAirportToday") || get("stenchAirportAlways"), - completed: () => myAdventures() === 0 || stooperDrunk(), - prepare: () => uneffect($effect`Beaten Up`), - do: () => cliExecute(`${args.garbo}`), - post: () => - $effects`Power Ballad of the Arrowsmith, Stevedave's Shanty of Superiority, The Moxious Madrigal, The Magical Mojomuscular Melody, Aloysius' Antiphon of Aptitude, Ur-Kel's Aria of Annoyance` - .filter((ef) => have(ef)) - .forEach((ef) => uneffect(ef)), - clear: "all", - tracking: "Garbo", - }, - { - name: "Turn in FunFunds", - ready: () => get("_stenchAirportToday") && itemAmount($item`FunFunds™`) >= 20, - completed: () => have($item`one-day ticket to Dinseylandfill`), - do: () => - buy($coinmaster`The Dinsey Company Store`, 1, $item`one-day ticket to Dinseylandfill`), - tracking: "Garbo", - }, - { - name: "PvP Closet Safety 2", - ready: () => args.pvp && get("autoSatisfyWithCloset") && !args.safepvp, - completed: () => toBoolean(get("_safetyCloset2")), - do: () => pvpCloset(2), - }, - { - name: "PvP", - ready: () => doneAdventuring(), - completed: () => pvpAttacksLeft() === 0 || !hippyStoneBroken(), - do: (): void => { - cliExecute("unequip"); - cliExecute("UberPvPOptimizer"); - cliExecute(`PVP_MAB target=${args.pvpTarget}`); - }, - }, - { - name: "Stooper", - ready: () => - myInebriety() === inebrietyLimit() && - have($item`tiny stillsuit`) && - get("familiarSweat") >= 300, - completed: () => !have($familiar`Stooper`) || stooperDrunk(), - do: () => { - useFamiliar($familiar`Stooper`); - cliExecute("drink stillsuit distillate"); - }, - }, - { - name: "Nightcap", - ready: () => doneAdventuring(), - completed: () => totallyDrunk(), - do: () => cliExecute("CONSUME NIGHTCAP"), - }, - { - name: "Smoke em if you got em", - ready: () => get("getawayCampsiteUnlocked"), - completed: () => !have($item`stick of firewood`), - do: (): void => { - while (have($item`stick of firewood`)) { - setProperty( - "choiceAdventure1394", - `1&message=${smoke} Thanks Seraphiii for writing Candywrapper!`, - ); - use(1, $item`campfire smoke`); - print(`Smoked ${smoke} firewoods!`); - smoke = smoke + 1; - } - }, - }, - { - name: "Offhand Remarkable", - ready: () => have($item`august scepter`), - completed: () => - !have($skill`Aug. 13th: Left/Off Hander's Day!`) || - have($effect`Offhand Remarkable`) || - get("_aug13Cast", false), - do: () => useSkill($skill`Aug. 13th: Left/Off Hander's Day!`), - }, - { - name: "PvP Closet Safety 3", - ready: () => args.pvp && get("autoSatisfyWithCloset") && !args.safepvp, - completed: () => toBoolean(get("_safetyCloset3")), - do: () => pvpCloset(3), - }, - { - name: "Item Cleanup", - // eslint-disable-next-line libram/verify-constants - completed: () => get("_cleanupToday", false) || args.itemcleanup === "", - do: (): void => { - cliExecute(`${args.itemcleanup}`); - cliExecute("set _cleanupToday = true"); - }, - clear: "all", - tracking: "Item Cleanup", - }, - { - name: "Pajamas", - completed: () => have($item`burning cape`), - acquire: [ - { item: $item`clockwork maid`, price: 7 * get("valueOfAdventure"), optional: true }, - { item: $item`burning cape` }, - ], - do: (): void => { - if (have($item`clockwork maid`)) { - use($item`clockwork maid`); - } - pajamas = true; - }, - outfit: () => ({ - familiar: - $familiars`Trick-or-Treating Tot, Left-Hand Man, Disembodied Hand, Grey Goose`.find( - (fam) => have(fam), - ), - modifier: `adventures ${sasqBonus} bonus Sasq™ watch, ${ratskinBonus} bonus ratskin pajama pants ${ - args.pvp ? ", 0.3 fites" : "" - }`, - }), - }, - { - name: "Alert-No Nightcap", - ready: () => !doneAdventuring(), - completed: () => stooperDrunk(), - do: (): void => { - const targetAdvs = 100 - numericModifier("adventures"); - print("robot completed, but did not overdrink.", "red"); - if (targetAdvs < myAdventures() && targetAdvs > 0) - print( - `Rerun with fewer than ${targetAdvs} adventures for smol to handle your diet`, - "red", - ); - else print("Something went wrong.", "red"); - }, - }, - ], - }, - ]; -} diff --git a/src/tasks/smolrunleg.ts b/src/tasks/smolrunleg.ts deleted file mode 100644 index 1481c12..0000000 --- a/src/tasks/smolrunleg.ts +++ /dev/null @@ -1,693 +0,0 @@ -import { CombatStrategy, step } from "grimoire-kolmafia"; -import { - buy, - buyUsingStorage, - cliExecute, - drink, - eat, - Effect, - equip, - familiarWeight, - fullnessLimit, - getClanName, - getWorkshed, - hippyStoneBroken, - inebrietyLimit, - itemAmount, - mallPrice, - myAdventures, - myAscensions, - myDaycount, - myFullness, - myInebriety, - myLevel, - myMaxhp, - mySign, - numericModifier, - print, - pullsRemaining, - pvpAttacksLeft, - restoreHp, - restoreMp, - retrieveItem, - setProperty, - storageAmount, - toBoolean, - toInt, - use, - useFamiliar, - useSkill, - visitUrl, - wait, -} from "kolmafia"; -import { - $coinmaster, - $effect, - $effects, - $familiar, - $familiars, - $item, - $items, - $location, - $skill, - AprilingBandHelmet, - clamp, - get, - have, - Macro, - set, - uneffect, -} from "libram"; - -import { args } from "../args"; - -import { getCurrentLeg, Leg, Quest } from "./structure"; -import { - backstageItemsDone, - bestFam, - doneAdventuring, - haveAll, - maxBase, - pvpCloset, - stooperDrunk, - totallyDrunk, -} from "./utils"; - -let pajamas = false; -let smoke = 1; - -const checkMelange = () => - get("valueOfAdventure") * 45 > mallPrice($item`spice melange`) && - !have($item`designer sweatpants`); - -export function howManySausagesCouldIEat() { - if (!have($item`Kramco Sausage-o-Matic™`)) return 0; - // You may be full but you can't be overfull - if (myFullness() > fullnessLimit()) return 0; - - return clamp( - 23 - get("_sausagesEaten"), - 0, - itemAmount($item`magical sausage`) + itemAmount($item`magical sausage casing`), - ); -} - -function firstWorkshed() { - return ( - $items`model train set, Asdon Martin keyfob (on ring), cold medicine cabinet, Little Geneticist DNA-Splicing Lab, portable Mayo Clinic`.find( - (it) => have(it) || getWorkshed() === it || storageAmount(it) > 0, - ) || $item`none` - ); -} -function altWorkshed() { - const ws = getWorkshed(); - switch (ws) { - case $item`model train set`: - return ( - $items`cold medicine cabinet, Asdon Martin keyfob (on ring), Little Geneticist DNA-Splicing Lab, portable Mayo Clinic`.find( - (it) => have(it) || getWorkshed() === it || storageAmount(it) > 0, - ) || ws - ); - case $item`Asdon Martin keyfob (on ring)`: - return ( - $items`cold medicine cabinet, model train set, Little Geneticist DNA-Splicing Lab, portable Mayo Clinic`.find( - (it) => have(it) || getWorkshed() === it || storageAmount(it) > 0, - ) || ws - ); - case $item`cold medicine cabinet`: - return ( - $items`Asdon Martin keyfob (on ring), model train set, Little Geneticist DNA-Splicing Lab, portable Mayo Clinic, warbear induction oven, snow machine`.find( - (it) => have(it) || getWorkshed() === it || storageAmount(it) > 0, - ) || ws - ); - case $item`Little Geneticist DNA-Splicing Lab`: - return ( - $items`cold medicine cabinet, Asdon Martin keyfob (on ring), model train set, portable Mayo Clinic`.find( - (it) => have(it) || getWorkshed() === it || storageAmount(it) > 0, - ) || ws - ); - case $item`portable Mayo Clinic`: - return ( - $items`cold medicine cabinet, model train set, Asdon Martin keyfob (on ring), Little Geneticist DNA-Splicing Lab`.find( - (it) => have(it) || getWorkshed() === it || storageAmount(it) > 0, - ) || ws - ); - default: - return $item`none`; - } -} - -export function SmolQuests(): Quest[] { - return [ - { - name: "Smol Run", - completed: () => getCurrentLeg() !== Leg.Run || get("kingLiberated", false), - tasks: [ - { - name: "Whitelist VIP Clan", - completed: () => !args.clan || getClanName().toLowerCase() === args.clan.toLowerCase(), - do: () => cliExecute(`/whitelist ${args.clan}`), - choices: { - 1507: 1, - }, - }, - { - name: "Prep Fireworks Shop", - completed: () => - !have($item`Clan VIP Lounge key`) || get("_goorboFireworksPrepped", false), - do: () => { - visitUrl("clan_viplounge.php?action=fwshop&whichfloor=2"); - set("_goorboFireworksPrepped", true); - }, - }, - { - name: "Pre-Pulls", - completed: () => - pullsRemaining() === 0 || - !args.pulls.find( - (it) => !have(it) && !get("_roninStoragePulls").includes(toInt(it).toString()), - ), //can't find a pull that (we dont have and it hasn't been pulled today) - do: () => - args.pulls.forEach((it) => { - if (!have(it) && !get("_roninStoragePulls").includes(toInt(it).toString())) { - if (storageAmount(it) === 0) buyUsingStorage(it); //should respect autoBuyPriceLimit - cliExecute(`pull ${it}`); - } - }), - }, - { - name: "LGR Seed", - ready: () => - have($item`lucky gold ring`) && have($item`one-day ticket to Dinseylandfill`), - completed: () => get("_stenchAirportToday") || get("stenchAirportAlways"), - do: () => use($item`one-day ticket to Dinseylandfill`), - tracking: "Garbo", - }, - { - name: "Install First Workshed", - ready: () => have(firstWorkshed()), - completed: () => - firstWorkshed() === $item`none` || - get("_workshedItemUsed") || - getWorkshed() !== $item`none`, - do: () => use(firstWorkshed()), - }, - { - name: "SIT Course", - // eslint-disable-next-line libram/verify-constants - ready: () => have($item`S.I.T. Course Completion Certificate`), - completed: () => get("_sitCourseCompleted", false), - choices: { - 1494: 2, - }, - do: () => - // eslint-disable-next-line libram/verify-constants - use($item`S.I.T. Course Completion Certificate`), - }, - { - name: "Break Stone", - ready: () => !args.safepvp, - completed: () => hippyStoneBroken() || !args.pvp, - do: (): void => { - visitUrl("peevpee.php?action=smashstone&pwd&confirm=on", true); - visitUrl("peevpee.php?place=fight"); - }, - }, - { - name: "Prepare Empathy", - completed: () => get("_empathyReady", false), - do: (): void => { - cliExecute("maximize MP; set _empathyReady = true"); - }, - }, - { - name: "Stillsuit Prep", - completed: () => itemAmount($item`tiny stillsuit`) === 0, - do: () => - equip( - $item`tiny stillsuit`, - get( - "stillsuitFamiliar", - $familiars`Gelatinous Cubeling, Levitating Potato, Mosquito`.find((fam) => - have(fam), - ) || $familiar`none`, - ), - ), - }, - { - name: "Run", - completed: () => step("questL13Final") > 11, - do: () => cliExecute(args.smolscript), - clear: "all", - tracking: "Run", - }, - { - name: "drink", - ready: () => - step("questL13Final") > 11 && - (have($item`designer sweatpants`) || checkMelange()) && - have($skill`Drinking to Drink`) && - storageAmount($item`synthetic dog hair pill`) >= 1, - completed: () => myInebriety() >= 2, - do: (): void => { - if (have($skill`The Ode to Booze`)) useSkill($skill`The Ode to Booze`); - drink($item`astral pilsner`, 1); - }, - clear: "all", - tracking: "Run", - }, - { - name: "Sausages", - completed: () => howManySausagesCouldIEat() === 0, - do: (): void => { - eat($item`magical sausage`, howManySausagesCouldIEat()); - }, - clear: "all", - tracking: "Run", - }, - { - name: "Free King", - ready: () => step("questL13Final") > 11, - completed: () => get("kingLiberated", false), - do: (): void => { - visitUrl("place.php?whichplace=nstower&action=ns_11_prism"); - }, - clear: "all", - tracking: "Ignore", - }, - ], - }, - { - name: "Post-SMOL Aftercore", - ready: () => myDaycount() === 1 && get("kingLiberated", false), - completed: () => totallyDrunk() && pajamas, - tasks: [ - { - name: "Pull All", - completed: () => get("lastEmptiedStorage") === myAscensions(), - do: () => cliExecute("pull all; refresh all"), - }, - { - name: "PvP Closet Safety 1", - ready: () => args.pvp && get("autoSatisfyWithCloset"), - completed: () => toBoolean(get("_safetyCloset1")), - do: () => pvpCloset(1), - }, - { - name: "Unlock Garbage Mountain", - completed: () => get("_stenchAirportToday") || get("stenchAirportAlways"), - do: (): void => { - retrieveItem($item`one-day ticket to Dinseylandfill`); - use($item`one-day ticket to Dinseylandfill`); - }, - tracking: "Garbo", - }, - { - name: "Sober Up", - completed: () => - myInebriety() <= 15 || - get("_mimeArmyShotglassUsed") || - get("_sweatOutSomeBoozeUsed", 0) === 3, - do: (): void => { - if (checkMelange()) { - cliExecute("acquire spice melange; use spice melange"); - } - while (get("_sweatOutSomeBoozeUsed", 0) < 3) { - useSkill($skill`Sweat Out Some Booze`); - } - if (!get("_sobrieTeaUsed", false)) { - retrieveItem($item`cuppa Sobrie tea`); - use($item`cuppa Sobrie tea`); - } - use($item`synthetic dog hair pill`); - }, - }, - { - name: "Wardrobe-o-matic", - ready: () => myLevel() >= 15 && have($item`wardrobe-o-matic`), - completed: () => get("_wardrobeUsed", false), - do: (): void => { - use($item`wardrobe-o-matic`); - cliExecute("set _wardrobeUsed = true"); - }, - limit: { tries: 1 }, - }, - { - name: "Apriling Part 1", - ready: () => AprilingBandHelmet.canChangeSong(), - completed: () => have($effect`Apriling Band Celebration Bop`), - do: (): void => { - AprilingBandHelmet.conduct($effect`Apriling Band Celebration Bop`); - }, - limit: { tries: 1 }, - }, - { - name: "Drink Pre-Tune", - ready: () => - mySign().toLowerCase() === "blender" && - myLevel() >= 7 && - have($item`mime army shotglass`) && - (have($item`astral pilsner`) || have($item`astral six-pack`)), - completed: () => - get("_mimeArmyShotglassUsed") || !have($item`hewn moon-rune spoon`) || get("moonTuned"), - prepare: () => { - if (have($item`astral six-pack`)) use($item`astral six-pack`); - }, - do: () => drink(1, $item`astral pilsner`), - }, - { - name: "Moon Spoon", - completed: () => - !have($item`hewn moon-rune spoon`) || - get("moonTuned") || - mySign().toLowerCase() === "wombat", - do: () => cliExecute("spoon wombat"), - }, - { - name: "Install Alternate Workshed", - ready: () => have(altWorkshed()), - completed: () => - altWorkshed() === $item`none` || - get("_workshedItemUsed") || - getWorkshed() === altWorkshed(), - do: () => use(altWorkshed()), - }, - { - name: "Gold Wedding Ring", - completed: () => - !have($skill`Comprehensive Cartography`) || - myAscensions() === get("lastCartographyBooPeak"), - choices: { 1430: 3, 606: 4, 610: 1, 1056: 1 }, - do: $location`A-Boo Peak`, - outfit: { modifier: "initiative 40 min 40 max, -tie" }, - }, - { - name: "Breakfast", - completed: () => get("breakfastCompleted"), - do: () => cliExecute("breakfast"), - }, - { - name: "Laugh Floor", - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's lollipop`) || - have($item`observational glasses`), - effects: () => [ - ...(have($skill`Musk of the Moose`) ? $effects`Musk of the Moose` : []), - ...(have($skill`Carlweather's Cantata of Confrontation`) - ? $effects`Carlweather's Cantata of Confrontation` - : []), - ], - prepare: (): void => { - if (!have($effect`Carlweather's Cantata of Confrontation`)) { - cliExecute("kmail to Buffy || 10 Cantata of Confrontation"); - wait(15); - cliExecute("refresh effects"); - } - $effects`Smooth Movements, The Sonata of Sneakiness, Darkened Photons, Shifted Phase`.forEach( - (ef: Effect) => cliExecute(`uneffect ${ef}`), - ); - restoreHp(0.75 * myMaxhp()); - restoreMp(20); - }, - do: $location`The Laugh Floor`, - outfit: () => ({ - familiar: bestFam(), - modifier: `${maxBase()}, 100 combat rate, 3 item, 250 bonus carnivorous potted plant`, - }), - combat: new CombatStrategy().macro( - Macro.trySkill($skill`Curse of Weaksauce`) - .tryItem($item`train whistle`) - .tryItem($item`porquoise-handled sixgun`) - .attack() - .repeat(), - ), - limit: { tries: 15 }, - }, - { - name: "Infernal Rackets Backstage", - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's unicorn`) || - backstageItemsDone(), - effects: () => [ - ...(have($skill`Smooth Movement`) ? $effects`Smooth Movements` : []), - ...(have($skill`The Sonata of Sneakiness`) ? $effects`The Sonata of Sneakiness` : []), - ], - prepare: (): void => { - if (!have($effect`The Sonata of Sneakiness`)) { - cliExecute("kmail to Buffy || 10 Sonata of Sneakiness"); - wait(15); - cliExecute("refresh effects"); - } - $effects`Musk of the Moose, Carlweather's Cantata of Confrontation, Hooooooooonk!`.forEach( - (ef: Effect) => cliExecute(`uneffect ${ef}`), - ); - restoreHp(0.75 * myMaxhp()); - restoreMp(20); - }, - do: $location`Infernal Rackets Backstage`, - outfit: () => ({ - familiar: bestFam(), - modifier: `${maxBase()}, -100 combat rate, 3 item, 250 bonus carnivorous potted plant`, - }), - combat: new CombatStrategy().macro( - Macro.trySkill($skill`Curse of Weaksauce`) - .tryItem($item`train whistle`) - .tryItem($item`porquoise-handled sixgun`) - .attack() - .repeat(), - ), - limit: { tries: 15 }, - }, - { - name: "Mourn", - ready: () => have($item`observational glasses`), - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's lollipop`), - outfit: { - equip: $items`hilarious comedy prop, observational glasses, Victor\, the Insult Comic Hellhound Puppet`, - }, - do: () => cliExecute("panda comedy insult; panda comedy observe"), - }, - { - name: "Sven Golly", - ready: () => backstageItemsDone(), - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's unicorn`), - do: (): void => { - cliExecute( - `panda arena Bognort ${$items`giant marshmallow, gin-soaked blotter paper`.find((a) => - have(a), - )}`, - ); - cliExecute( - `panda arena Stinkface ${$items`beer-scented teddy bear, gin-soaked blotter paper`.find( - (a) => have(a), - )}`, - ); - cliExecute( - `panda arena Flargwurm ${$items`booze-soaked cherry, sponge cake`.find((a) => - have(a), - )}`, - ); - cliExecute(`panda arena Jim ${$items`comfy pillow, sponge cake`.find((a) => have(a))}`); - }, - }, - { - name: "Moaning Panda", - ready: () => haveAll($items`Azazel's lollipop, Azazel's unicorn`), - completed: () => - have($skill`Liver of Steel`) || - have($item`steel margarita`) || - have($item`Azazel's tutu`), - acquire: () => - $items`bus pass, imp air`.map((it) => ({ - item: it, - num: 5, - price: get("valueOfAdventure"), - })), - do: () => cliExecute("panda moan"), - limit: { tries: 3 }, - }, - { - name: "Steel Margarita", - ready: () => haveAll($items`Azazel's tutu, Azazel's lollipop, Azazel's unicorn`), - completed: () => have($skill`Liver of Steel`) || have($item`steel margarita`), - do: () => cliExecute("panda temple"), - }, - { - name: "Liver of Steel", - ready: () => have($item`steel margarita`), - completed: () => have($skill`Liver of Steel`), - do: () => drink(1, $item`steel margarita`), - }, - { - name: "PvP Closet Safety 2", - ready: () => args.pvp && get("autoSatisfyWithCloset"), - completed: () => toBoolean(get("_safetyCloset2")), - do: () => pvpCloset(2), - }, - { - name: "Garbo", - ready: () => get("_stenchAirportToday") || get("stenchAirportAlways"), - completed: () => myAdventures() === 0 || stooperDrunk(), - prepare: () => uneffect($effect`Beaten Up`), - do: () => cliExecute(`${args.garbo}`), - post: () => - $effects`Power Ballad of the Arrowsmith, Stevedave's Shanty of Superiority, The Moxious Madrigal, The Magical Mojomuscular Melody, Aloysius' Antiphon of Aptitude, Ur-Kel's Aria of Annoyance` - .filter((ef) => have(ef)) - .forEach((ef) => uneffect(ef)), - clear: "all", - tracking: "Garbo", - }, - { - name: "Turn in FunFunds", - ready: () => get("_stenchAirportToday") && itemAmount($item`FunFunds™`) >= 20, - completed: () => have($item`one-day ticket to Dinseylandfill`), - do: () => - buy($coinmaster`The Dinsey Company Store`, 1, $item`one-day ticket to Dinseylandfill`), - tracking: "Garbo", - }, - { - name: "PvP", - ready: () => doneAdventuring() && !args.safepvp, - completed: () => pvpAttacksLeft() === 0 || !hippyStoneBroken(), - do: (): void => { - cliExecute("unequip"); - cliExecute("UberPvPOptimizer"); - cliExecute(`PVP_MAB target=${args.pvpTarget}`); - }, - }, - { - name: "Stooper", - ready: () => - myInebriety() === inebrietyLimit() && - have($item`tiny stillsuit`) && - get("familiarSweat") >= 300, - completed: () => !have($familiar`Stooper`) || stooperDrunk(), - do: () => { - useFamiliar($familiar`Stooper`); - cliExecute("drink stillsuit distillate"); - }, - }, - { - name: "Nightcap", - ready: () => doneAdventuring(), - completed: () => totallyDrunk(), - do: () => cliExecute("CONSUME NIGHTCAP"), - }, - { - name: "Smoke em if you got em", - ready: () => get("getawayCampsiteUnlocked"), - completed: () => !have($item`stick of firewood`), - do: (): void => { - while (have($item`stick of firewood`)) { - setProperty( - "choiceAdventure1394", - `1&message=${smoke} Thanks Seraphiii for writing Candywrapper!`, - ); - use(1, $item`campfire smoke`); - print(`Smoked ${smoke} firewoods!`); - smoke = smoke + 1; - } - }, - }, - { - name: "Offhand Remarkable", - ready: () => have($item`august scepter`), - completed: () => - !have($skill`Aug. 13th: Left/Off Hander's Day!`) || - have($effect`Offhand Remarkable`) || - get("_aug13Cast", false), - do: () => useSkill($skill`Aug. 13th: Left/Off Hander's Day!`), - }, - { - name: "PvP Closet Safety 3", - ready: () => args.pvp && get("autoSatisfyWithCloset"), - completed: () => toBoolean(get("_safetyCloset3")), - do: () => pvpCloset(3), - }, - { - name: "Item Cleanup", - // eslint-disable-next-line libram/verify-constants - completed: () => get("_cleanupToday", false) || args.itemcleanup === "", - do: (): void => { - cliExecute(`${args.itemcleanup}`); - cliExecute("set _cleanupToday = true"); - }, - clear: "all", - tracking: "Item Cleanup", - }, - { - name: "Apriling Part 2", - ready: () => AprilingBandHelmet.canJoinSection(), - completed: () => !AprilingBandHelmet.canPlay($item`Apriling band piccolo`), - do: (): void => { - AprilingBandHelmet.joinSection($item`Apriling band piccolo`); - if (AprilingBandHelmet.canJoinSection()) { - AprilingBandHelmet.joinSection($item`Apriling band saxophone`); - AprilingBandHelmet.play($item`Apriling band saxophone`); - } - if (have($familiar`Grey Goose`)) useFamiliar($familiar`Grey Goose`); - else if (have($familiar`Chest Mimic`)) useFamiliar($familiar`Chest Mimic`); - else if ( - have($familiar`Pocket Professor`) && - familiarWeight($familiar`Pocket Professor`) < 20 - ) - useFamiliar($familiar`Pocket Professor`); - else if (have($familiar`Comma Chameleon`)) useFamiliar($familiar`Comma Chameleon`); - while ( - $item`Apriling band piccolo`.dailyusesleft > 0 && - have($item`Apriling band piccolo`) - ) - AprilingBandHelmet.play($item`Apriling band piccolo`); - }, - limit: { tries: 1 }, - }, - { - name: "Pajamas", - completed: () => have($item`burning cape`), - acquire: [ - { item: $item`clockwork maid`, price: 7 * get("valueOfAdventure"), optional: true }, - { item: $item`burning cape` }, - ], - do: (): void => { - if (have($item`clockwork maid`)) { - use($item`clockwork maid`); - } - pajamas = true; - }, - outfit: () => ({ - familiar: - $familiars`Trick-or-Treating Tot, Left-Hand Man, Disembodied Hand, Grey Goose`.find( - (fam) => have(fam), - ), - modifier: `adventures${args.pvp ? ", 0.3 fites" : ""}`, - }), - }, - { - name: "Alert-No Nightcap", - ready: () => !doneAdventuring(), - completed: () => stooperDrunk(), - do: (): void => { - const targetAdvs = 100 - numericModifier("adventures"); - print("smol completed, but did not overdrink.", "red"); - if (targetAdvs < myAdventures() && targetAdvs > 0) - print( - `Rerun with fewer than ${targetAdvs} adventures for smol to handle your diet`, - "red", - ); - else print("Something went wrong.", "red"); - }, - }, - ], - }, - ]; -} diff --git a/src/tasks/structure.ts b/src/tasks/structure.ts index f7091b4..dc032ca 100644 --- a/src/tasks/structure.ts +++ b/src/tasks/structure.ts @@ -11,11 +11,10 @@ export type Quest = BaseQuest; // eslint-disable-next-line no-restricted-syntax export enum Leg { Aftercore = 0, - Run = 1 + Run = 1, } export function getCurrentLeg(): number { - if (myDaycount() === 1) - return Leg.Run; + if (myDaycount() === 1) return Leg.Run; return Leg.Aftercore; } diff --git a/src/tasks/utils.ts b/src/tasks/utils.ts index d6b62b9..c5dc18d 100644 --- a/src/tasks/utils.ts +++ b/src/tasks/utils.ts @@ -1,4 +1,5 @@ import { + canEquip, chew, cliExecute, Effect, @@ -6,23 +7,36 @@ import { fullnessLimit, gamedayToInt, getCampground, + getOutfits, holiday, inebrietyLimit, Item, itemAmount, + lastChoice, + Location, mallPrice, Monster, myAdventures, + myBasestat, + myDaycount, myFamiliar, myFullness, myInebriety, + myPrimestat, mySpleenUse, + outfitPieces, + outfitTreats, print, putCloset, retrieveItem, + Skill, spleenLimit, + Stat, + storageAmount, + toItem, urlEncode, use, + useSkill, visitUrl, } from "kolmafia"; import { @@ -30,13 +44,20 @@ import { $familiars, $item, $items, + $location, $phylum, + $skill, + $stat, + Kmail as _Kmail, gameDay, get, getBanishedMonsters, have, + maxBy, + Pantogram, set, Snapper, + sum, } from "libram"; import { args } from "../args"; @@ -144,6 +165,32 @@ export function bestFam(mob?: Monster) { return fams.find((fam) => have(fam)); } +export function nextCyberZone(): Location { + if (lastChoice() === 1546) { + set("_cyberRealm1Done", true); + } + if (lastChoice() === 1548) { + set("_cyberRealm2Done", true); + } + if (lastChoice() === 1550) { + set("_cyberRealm3Done", true); + } + const realmChoice = () => + !get("_cyberRealm1Done").includes("true") + ? $location`Cyberzone 1` + : !get("_cyberRealm2Done").includes("true") + ? $location`Cyberzone 2` + : !get("_cyberRealm3Done").includes("true") + ? $location`Cyberzone 3` + : $location`none`; + print( + `Choosing ${realmChoice()} because turns spent ${ + realmChoice().turnsSpent - 19 * (myDaycount() - 1) + }`, + ); + return realmChoice(); +} + export function canDiet(): boolean { return ( myFullness() < fullnessLimit() || @@ -231,6 +278,20 @@ export interface Kmail { delete(): boolean; } +export function notifyVoters(): void { + if (get("_kmailSentToday").includes("true")) return; + + const recipients = ["Datris", "ange1ade", "miroto1998", "tissen", "nannachi", "Mandoline", "Lucasyeo"]; + + const message = `Voter Monster Today is ${get("_voteMonster")}`; + + recipients.forEach((recipient) => { + _Kmail.send(recipient, message); + }); + + set("_kmailSentToday", true); +} + export function getKmails(caller: string = "GreyDay"): Kmail[] { const buffer = visitUrl(`api.php?pwd&what=kmail&count=100&for=${urlEncode(caller)}`); @@ -289,7 +350,10 @@ export function deleteJunkKmails() { export const realMonth = gameDay().getMonth(); export const realDay = gameDay().getDate(); export const halloween = - gamedayToInt() === 79 || (realMonth === 10 && realDay === 31) || holiday().includes("halloween"); + (gamedayToInt() === 79 || + (realMonth === 10 && realDay === 31) || + holiday().includes("halloween")) && + isHalloweenWorthDoing(); export function pvpCloset(num: number) { const threshold = 10000; @@ -309,3 +373,221 @@ export function pvpCloset(num: number) { }); set(`_safetyCloset${num}`, true); } + +function treatValue(outfit: string): number { + return sum( + Object.entries(outfitTreats(outfit)), + ([candyName, probability]) => probability * garboValue(toItem(candyName)), + ); +} + +export function getTreatOutfit(): string { + let outfit = get("freecandy_treatOutfit"); + const availableOutfits = getOutfits().filter((name) => + outfitPieces(name).every((piece) => canEquip(piece)), + ); + + if (!availableOutfits.length) { + return "seal-clubbing club"; + } + + outfit = maxBy(availableOutfits, treatValue); + + return outfit; +} + +let _baseAdventureValue: number; +function baseAdventureValue(): number { + if (!_baseAdventureValue) { + const outfitCandyValue = treatValue(getTreatOutfit()); + const totOutfitCandyMultiplier = have($familiar`Trick-or-Treating Tot`) ? 1.6 : 1; + const bowlValue = (1 / 5) * garboValue($item`huge bowl of candy`); + const prunetsValue = have($familiar`Trick-or-Treating Tot`) + ? 4 * 0.2 * garboValue($item`Prunets`) + : 0; + + const outfitCandyTotal = 3 * outfitCandyValue * totOutfitCandyMultiplier; + _baseAdventureValue = (1 / 5) * (outfitCandyTotal + bowlValue + prunetsValue); + } + return _baseAdventureValue; +} + +function isHalloweenWorthDoing(): boolean { + const freeFightValue = have($familiar`Red-Nosed Snapper`) ? 2000 : 1100; + return baseAdventureValue() + freeFightValue > get("valueOfAdventure"); +} + +export function shouldWeOverdrink(): boolean { + const overdrinkValue = + get("valueOfAdventure") * (110 - 5.5 * 5) + mallPrice($item`Sacramento wine`) * 5; + const numToCleanse = 5; + const sweatpants = have($item`designer sweatpants`) ? 3 : 0; + const drinking = have($skill`Drinking to Drink`) ? 1 : 0; + const doghair = storageAmount($item`synthetic dog hair pill`) >= 1 ? 1 : 0; + const checkMelange = () => + get("valueOfAdventure") * 45 > mallPrice($item`spice melange`) && sweatpants < 3; + const melange = checkMelange() ? 3 : 0; + const melangeCost = () => (checkMelange() ? mallPrice($item`spice melange`) : 0); + + if (sweatpants + drinking + doghair + melange >= numToCleanse && overdrinkValue > 0) { + return true; + } else { + const sobrie = 1; + if ( + sweatpants + drinking + doghair + sobrie + melange >= numToCleanse && + overdrinkValue - mallPrice($item`cuppa Sobrie tea`) - melangeCost() > 0 + ) { + return true; + } else return false; + } +} + +export function pantogramReady(): boolean { + if (!Pantogram.have() || Pantogram.havePants()) return false; + const pantogramValue = 100 * myAdventures(); + + const cloverPrice = Math.min( + ...$items`ten-leaf clover, disassembled clover`.map((item) => + mallPrice(item), + ), + ); + if (cloverPrice + mallPrice($item`porquoise`) > pantogramValue) { + return false; + } + retrieveItem($item`porquoise`, 1); + if (!have($item`porquoise`)) return false; + return true; +} + +export function pantogram(): boolean { + if (!Pantogram.have() || Pantogram.havePants()) return true; + retrieveItem($item`ten-leaf clover`); + retrieveItem($item`bubblin' crude`); + Pantogram.makePants( + myPrimestat().toString(), + "Sleaze Resistance: 2", + "MP Regen Max: 15", + "Drops Items: true", + "Meat Drop: 60", + ); + return true; +} + +/** + * @returns Whether or not you have the blood cubic zirconia + */ +export function have_(): boolean { + return have($item`blood cubic zirconia`); +} + +/** + * @param skill The BCZ skill to check. + * @returns The subtat cost to cast the skill. + */ +export function skillCost(skill: Skill): number { + const castNumber = timesCast(skill); + if (castNumber <= 11) { + const cycle = Math.floor(castNumber / 3); + const position = castNumber % 3; + return [11, 23, 37][position] * 10 ** cycle; + } else if (castNumber === 12) { + return 420_000; + } else { + const cycle = Math.floor((castNumber - 13) / 3); + const position = (castNumber + 2) % 3; + return [11, 23, 37][position] * 10 ** (cycle + 5); + } +} + +const COSTS = new Map([ + [$skill`BCZ: Blood Geyser`, $stat`SubMuscle`], + [$skill`BCZ: Refracted Gaze`, $stat`SubMysticality`], + [$skill`BCZ: Sweat Bullets`, $stat`SubMoxie`], + [$skill`BCZ: Blood Bath`, $stat`SubMuscle`], + [$skill`BCZ: Craft a Pheromone Cocktail`, $stat`SubMoxie`], + [$skill`BCZ: Create Blood Thinner`, $stat`SubMuscle`], + [$skill`BCZ: Dial it up to 11`, $stat`SubMysticality`], + [$skill`BCZ: Prepare Spinal Tapas`, $stat`SubMysticality`], + [$skill`BCZ: Sweat Equity`, $stat`SubMoxie`], +]); + +export const numericProperties = ["coinMasterIndex", "dailyDeedsVersion", "defaultDropdown1", "defaultDropdown2", "defaultDropdownSplit", "defaultLimit", "fixedThreadPoolSize", "itemManagerIndex", "lastBuffRequestType", "lastGlobalCounterDay", "lastImageCacheClear", "pingDefaultTestPings", "pingLoginCount", "pingLoginGoal", "pingLoginThreshold", "pingTestPings", "previousUpdateRevision", "relayDelayForSVN", "relaySkillButtonCount", "scriptButtonPosition", "statusDropdown", "svnThreadPoolSize", "toolbarPosition", "_beachTides", "_g9Effect", "8BitBonusTurns", "8BitScore", "addingScrolls", "affirmationCookiesEaten", "aminoAcidsUsed", "antagonisticSnowmanKitCost", "ascensionsToday", "asolDeferredPoints", "asolPointsPigSkinner", "asolPointsCheeseWizard", "asolPointsJazzAgent", "autoAbortThreshold", "autoAntidote", "autoBuyPriceLimit", "autopsyTweezersUsed", "autumnatonQuestTurn", "availableCandyCredits", "availableDimes", "availableFunPoints", "availableMrStore2002Credits", "availableQuarters", "availableSeptEmbers", "availableStoreCredits", "availableSwagger", "avantGuardPoints", "averageSwagger", "awolMedicine", "awolPointsBeanslinger", "awolPointsCowpuncher", "awolPointsSnakeoiler", "awolDeferredPointsBeanslinger", "awolDeferredPointsCowpuncher", "awolDeferredPointsSnakeoiler", "awolVenom", "bagOTricksCharges", "ballpitBonus", "bankedKarma", "bartenderTurnsUsed", "basementMallPrices", "basementSafetyMargin", "batmanFundsAvailable", "batmanBonusInitialFunds", "batmanTimeLeft", "bearSwagger", "beeCounter", "beGregariousCharges", "beGregariousFightsLeft", "birdformCold", "birdformHot", "birdformRoc", "birdformSleaze", "birdformSpooky", "birdformStench", "blackBartsBootyCost", "blackPuddingsDefeated", "blackForestProgress", "blankOutUsed", "bloodweiserDrunk", "bodyguardCharge", "bondPoints", "bondVillainsDefeated", "boneAbacusVictories", "bookOfFactsGummi", "bookOfFactsPinata", "bookOfIronyCost", "booPeakProgress", "borisPoints", "breakableHandling", "breakableHandling1964", "breakableHandling9691", "breakableHandling9692", "breakableHandling9699", "breathitinCharges", "brodenBacteria", "brodenSprinkles", "buffBotMessageDisposal", "buffBotPhilanthropyType", "buffJimmyIngredients", "burnoutsDefeated", "burrowgrubSummonsRemaining", "bwApronMealsEaten", "camelSpit", "camerasUsed", "campAwayDecoration", "candyWitchTurnsUsed", "candyWitchCandyTotal", "carboLoading", "catBurglarBankHeists", "cellarLayout", "charitableDonations", "chasmBridgeProgress", "chefTurnsUsed", "chessboardsCleared", "chibiAlignment", "chibiBirthday", "chibiFitness", "chibiIntelligence", "chibiLastVisit", "chibiSocialization", "chilledToTheBone", "cinchoSaltAndLime", "cinderellaMinutesToMidnight", "cinderellaScore", "cocktailSummons", "commerceGhostCombats", "cookbookbatIngredientsCharge", "controlPanelOmega", "cornucopiasOpened", "cosmicBowlingBallReturnCombats", "cozyCounter6332", "cozyCounter6333", "cozyCounter6334", "craftingClay", "craftingLeather", "craftingPlansCharges", "craftingStraw", "crimbo16BeardChakraCleanliness", "crimbo16BootsChakraCleanliness", "crimbo16BungChakraCleanliness", "crimbo16CrimboHatChakraCleanliness", "crimbo16GutsChakraCleanliness", "crimbo16HatChakraCleanliness", "crimbo16JellyChakraCleanliness", "crimbo16LiverChakraCleanliness", "crimbo16NippleChakraCleanliness", "crimbo16NoseChakraCleanliness", "crimbo16ReindeerChakraCleanliness", "crimbo16SackChakraCleanliness", "crimboTrainingSkill", "crimboTreeDays", "cubelingProgress", "cupidBowFights", "currentExtremity", "currentHedgeMazeRoom", "currentMojoFilters", "currentNunneryMeat", "currentPortalEnergy", "currentReplicaStoreYear", "cursedMagnifyingGlassCount", "cyrptAlcoveEvilness", "cyrptCrannyEvilness", "cyrptNicheEvilness", "cyrptNookEvilness", "cyrptTotalEvilness", "darkGyfftePoints", "dartsThrown", "daycareEquipment", "daycareInstructorItemQuantity", "daycareInstructors", "daycareLastScavenge", "daycareToddlers", "dbNemesisSkill1", "dbNemesisSkill2", "dbNemesisSkill3", "desertExploration", "desktopHeight", "desktopWidth", "dinseyFilthLevel", "dinseyFunProgress", "dinseyNastyBearsDefeated", "dinseySocialJusticeIProgress", "dinseySocialJusticeIIProgress", "dinseyTouristsFed", "dinseyToxicMultiplier", "doctorBagQuestLights", "doctorBagUpgrades", "dreadScroll1", "dreadScroll2", "dreadScroll3", "dreadScroll4", "dreadScroll5", "dreadScroll6", "dreadScroll7", "dreadScroll8", "dripAdventuresSinceAscension", "drippingHallAdventuresSinceAscension", "drippingTreesAdventuresSinceAscension", "drippyBatsUnlocked", "drippyJuice", "drippyOrbsClaimed", "droneSelfDestructChipsUsed", "drunkenSwagger", "edDefeatAbort", "edPoints", "eldritchTentaclesFought", "electricKoolAidEaten", "elfGratitude", "encountersUntilDMTChoice", "encountersUntilYachtzeeChoice", "encountersUntilNEPChoice", "encountersUntilSRChoice", "ensorceleeLevel", "entauntaunedColdRes", "essenceOfAnnoyanceCost", "essenceOfBearCost", "extraRolloverAdventures", "falloutShelterLevel", "familiarSweat", "fingernailsClipped", "fistSkillsKnown", "flyeredML", "fossilB", "fossilD", "fossilN", "fossilP", "fossilS", "fossilW", "fratboysDefeated", "frenchGuardTurtlesFreed", "funGuyMansionKills", "garbageChampagneCharge", "garbageFireProgress", "garbageShirtCharge", "garbageTreeCharge", "garlandUpgrades", "getsYouDrunkTurnsLeft", "ghostPepperTurnsLeft", "gingerDigCount", "gingerLawChoice", "gingerMuscleChoice", "gingerTrainScheduleStudies", "gladiatorBallMovesKnown", "gladiatorBladeMovesKnown", "gladiatorNetMovesKnown", "glitchItemCost", "glitchItemImplementationCount", "glitchItemImplementationLevel", "glitchSwagger", "gloverPoints", "gnasirProgress", "goldenMrAccessories", "gongPath", "gooseDronesRemaining", "goreCollected", "gourdItemCount", "greyYouPoints", "grimoire1Summons", "grimoire2Summons", "grimoire3Summons", "grimstoneCharge", "guardTurtlesFreed", "guideToSafariCost", "guyMadeOfBeesCount", "guzzlrBronzeDeliveries", "guzzlrDeliveryProgress", "guzzlrGoldDeliveries", "guzzlrPlatinumDeliveries", "haciendaLayout", "hallowiener8BitRealm", "hallowienerCoinspiracy", "hareMillisecondsSaved", "hareTurnsUsed", "heavyRainsStartingThunder", "heavyRainsStartingRain", "heavyRainsStartingLightning", "heroDonationBoris", "heroDonationJarlsberg", "heroDonationSneakyPete", "hiddenApartmentProgress", "hiddenBowlingAlleyProgress", "hiddenHospitalProgress", "hiddenOfficeProgress", "hiddenTavernUnlock", "highTopPumped", "hippiesDefeated", "holidayHalsBookCost", "holidaySwagger", "homemadeRobotUpgrades", "homebodylCharges", "hpAutoRecovery", "hpAutoRecoveryTarget", "iceSwagger", "ironicSwagger", "jarlsbergPoints", "juicyGarbageUsed", "jungCharge", "junglePuns", "knownAscensions", "kolhsTotalSchoolSpirited", "lassoTrainingCount", "lastAnticheeseDay", "lastArcadeAscension", "lastBadMoonReset", "lastBangPotionReset", "lastBattlefieldReset", "lastBeardBuff", "lastBreakfast", "lastCartographyBooPeak", "lastCartographyCastleTop", "lastCartographyDarkNeck", "lastCartographyDefiledNook", "lastCartographyFratHouse", "lastCartographyFratHouseVerge", "lastCartographyGuanoJunction", "lastCartographyHauntedBilliards", "lastCartographyHippyCampVerge", "lastCartographyZeppelinProtesters", "lastCastleGroundUnlock", "lastCastleTopUnlock", "lastCellarReset", "lastChanceThreshold", "lastChasmReset", "lastColosseumRoundWon", "lastCouncilVisit", "lastCounterDay", "lastDesertUnlock", "lastDispensaryOpen", "lastDMTDuplication", "lastDwarfFactoryReset", "lastEVHelmetValue", "lastEVHelmetReset", "lastEmptiedStorage", "lastFilthClearance", "lastGoofballBuy", "lastGuildStoreOpen", "lastGuyMadeOfBeesReset", "lastFratboyCall", "lastFriarCeremonyAscension", "lastFriarsElbowNC", "lastFriarsHeartNC", "lastFriarsNeckNC", "lastHippyCall", "lastIslandUnlock", "lastKeyotronUse", "lastKingLiberation", "lastLightsOutTurn", "lastMushroomPlot", "lastMiningReset", "lastNemesisReset", "lastPaperStripReset", "lastPirateEphemeraReset", "lastPirateInsultReset", "lastPlusSignUnlock", "lastQuartetAscension", "lastQuartetRequest", "lastSecondFloorUnlock", "lastShadowForgeUnlockAdventure", "lastSkateParkReset", "lastStillBeatingSpleen", "lastTavernAscension", "lastTavernSquare", "lastTelescopeReset", "lastTempleAdventures", "lastTempleButtonsUnlock", "lastTempleUnlock", "lastThingWithNoNameDefeated", "lastTowelAscension", "lastTr4pz0rQuest", "lastTrainsetConfiguration", "lastVioletFogMap", "lastVoteMonsterTurn", "lastWartDinseyDefeated", "lastWuTangDefeated", "lastYearbookCameraAscension", "lastZapperWand", "lastZapperWandExplosionDay", "lawOfAveragesCost", "legacyPoints", "leprecondoLastNeedChange", "libramSummons", "lightsOutAutomation", "louvreDesiredGoal", "louvreGoal", "lovebugsAridDesert", "lovebugsBeachBuck", "lovebugsBooze", "lovebugsChroner", "lovebugsCoinspiracy", "lovebugsCyrpt", "lovebugsFreddy", "lovebugsFunFunds", "lovebugsHoboNickel", "lovebugsItemDrop", "lovebugsMeat", "lovebugsMeatDrop", "lovebugsMoxie", "lovebugsMuscle", "lovebugsMysticality", "lovebugsOilPeak", "lovebugsOrcChasm", "lovebugsPowder", "lovebugsWalmart", "lttQuestDifficulty", "lttQuestStageCount", "manaBurnSummonThreshold", "manaBurningThreshold", "manaBurningTrigger", "manorDrawerCount", "manualOfNumberologyCost", "mapToKokomoCost", "markYourTerritoryCharges", "masksUnlocked", "maximizerMRUSize", "maximizerCombinationLimit", "maximizerEquipmentLevel", "maximizerEquipmentScope", "maximizerMaxPrice", "maximizerPriceLevel", "maxManaBurn", "mayflyExperience", "mayoLevel", "meansuckerPrice", "merkinVocabularyMastery", "miniAdvClass", "miniKiwiAiolisUsed", "miniMartinisDrunk", "mixedBerryJellyUses", "moleTunnelLevel", "momSeaMonkeeProgress", "mothershipProgress", "mpAutoRecovery", "mpAutoRecoveryTarget", "munchiesPillsUsed", "mushroomGardenCropLevel", "nanopolymerSpiderWebsUsed", "nextAprilBandTurn", "nextParanormalActivity", "nextQuantumFamiliarOwnerId", "nextQuantumFamiliarTurn", "noobPoints", "noobDeferredPoints", "noodleSummons", "nsContestants1", "nsContestants2", "nsContestants3", "nuclearAutumnPoints", "numericSwagger", "nunsVisits", "oilPeakProgress", "optimalSwagger", "optimisticCandleProgress", "palindomeDudesDefeated", "parasolUsed", "peaceTurkeyIndex", "pendingMapReflections", "phosphorTracesUses", "pingpongSkill", "pirateRealmPlasticPiratesDefeated", "pirateRealmShipsDestroyed", "pirateRealmStormsEscaped", "pirateSwagger", "plantingDay", "plumberBadgeCost", "plumberCostumeCost", "plumberPoints", "pokefamPoints", "poolSharkCount", "poolSkill", "powerPillProgress", "preworkoutPowderUses", "primaryLabGooIntensity", "prismaticSummons", "procrastinatorLanguageFluency", "promptAboutCrafting", "puzzleChampBonus", "pyramidPosition", "quantumPoints", "reagentSummons", "reanimatorArms", "reanimatorLegs", "reanimatorSkulls", "reanimatorWeirdParts", "reanimatorWings", "recentLocations", "redSnapperProgress", "relayPort", "relocatePygmyJanitor", "relocatePygmyLawyer", "rockinRobinProgress", "romanCandelabraRedCasts", "romanCandelabraBlueCasts", "romanCandelabraYellowCasts", "romanCandelabraGreenCasts", "romanCandelabraPurpleCasts", "ROMOfOptimalityCost", "rumpelstiltskinKidsRescued", "rumpelstiltskinTurnsUsed", "rwbMonsterCount", "safariSwagger", "sausageGrinderUnits", "schoolOfHardKnocksDiplomaCost", "schoolSwagger", "scrapbookCharges", "screechCombats", "scriptMRULength", "seadentConstructKills", "seadentLevel", "seaodesFound", "seaPoints", "SeasoningSwagger", "sexChanges", "shenInitiationDay", "shockingLickCharges", "singleFamiliarRun", "skillBurn3", "skillBurn90", "skillBurn153", "skillBurn154", "skillBurn155", "skillBurn236", "skillBurn237", "skillBurn1019", "skillBurn5017", "skillBurn6014", "skillBurn6015", "skillBurn6016", "skillBurn6020", "skillBurn6021", "skillBurn6022", "skillBurn6023", "skillBurn6024", "skillBurn6026", "skillBurn6028", "skillBurn7323", "skillBurn14008", "skillBurn14028", "skillBurn14038", "skillBurn15011", "skillBurn15028", "skillBurn17005", "skillBurn22034", "skillBurn22035", "skillBurn23301", "skillBurn23302", "skillBurn23303", "skillBurn23304", "skillBurn23305", "skillBurn23306", "skillLevel46", "skillLevel47", "skillLevel48", "skillLevel117", "skillLevel118", "skillLevel121", "skillLevel128", "skillLevel134", "skillLevel135", "skillLevel144", "skillLevel180", "skillLevel188", "skillLevel227", "skillLevel245", "skillLevel7254", "slimelingFullness", "slimelingStacksDropped", "slimelingStacksDue", "smoresEaten", "smutOrcNoncombatProgress", "sneakyPetePoints", "snojoMoxieWins", "snojoMuscleWins", "snojoMysticalityWins", "sourceAgentsDefeated", "sourceEnlightenment", "sourceInterval", "sourcePoints", "sourceTerminalGram", "sourceTerminalPram", "sourceTerminalSpam", "spaceBabyLanguageFluency", "spacePirateLanguageFluency", "spelunkyNextNoncombat", "spelunkySacrifices", "spelunkyWinCount", "spookyPuttyCopiesMade", "spookyVHSTapeMonsterTurn", "statbotUses", "stockCertificateTurn", "sugarCounter4178", "sugarCounter4179", "sugarCounter4180", "sugarCounter4181", "sugarCounter4182", "sugarCounter4183", "sugarCounter4191", "summonAnnoyanceCost", "sweat", "tacoDanCocktailSauce", "tacoDanFishMeat", "takerSpaceAnchor", "takerSpaceGold", "takerSpaceMast", "takerSpaceRum", "takerSpaceSilk", "takerSpaceSpice", "tavernLayout", "telescopeUpgrades", "tempuraSummons", "timeposedTopHats", "timeSpinnerMedals", "timesRested", "tomeSummons", "totalCharitableDonations", "trainsetPosition", "turtleBlessingTurns", "twinPeakProgress", "twoCRSPoints", "unicornHornInflation", "universalSeasoningCost", "usable1HWeapons", "usable1xAccs", "usable2HWeapons", "usable3HWeapons", "usableAccessories", "usableHats", "usableOffhands", "usableOther", "usablePants", "usableShirts", "valueOfAdventure", "valueOfInventory", "valueOfStill", "valueOfTome", "vintnerCharge", "vintnerWineLevel", "violetFogGoal", "walfordBucketProgress", "warehouseProgress", "welcomeBackAdv", "wereProfessorBite", "wereProfessorKick", "wereProfessorLiver", "wereProfessorPoints", "wereProfessorRend", "wereProfessorResearchPoints", "wereProfessorStomach", "wereProfessorTransformTurns", "whetstonesUsed", "wolfPigsEvicted", "wolfTurnsUsed", "writingDesksDefeated", "xoSkeleltonXProgress", "xoSkeleltonOProgress", "yearbookCameraAscensions", "yearbookCameraUpgrades", "youRobotBody", "youRobotBottom", "youRobotLeft", "youRobotPoints", "youRobotRight", "youRobotTop", "zeppelinProgress", "zeppelinProtestors", "zigguratLianas", "zombiePoints", "zootSpecimensPrepared", "zootomistPoints", "_absintheDrops", "_abstractionDropsCrown", "_aguaDrops", "_xenomorphCharge", "_alliedRadioDropsUsed", "_ancestralRecallCasts", "_antihangoverBonus", "_aprilShowerDiscoNap", "_aprilBandInstruments", "_aprilBandSaxophoneUses", "_aprilBandTomUses", "_aprilBandTubaUses", "_aprilBandStaffUses", "_aprilBandPiccoloUses", "_astralDrops", "_augSkillsCast", "_assertYourAuthorityCast", "_automatedFutureManufactures", "_autumnatonQuests", "_backUpUses", "_badlyRomanticArrows", "_badgerCharge", "_balefulHowlUses", "_banderRunaways", "_bastilleCheese", "_bastilleGames", "_bastilleGameTurn", "_bastilleLastCheese", "_batWingsCauldronUsed", "_batWingsFreeFights", "_batWingsRestUsed", "_batWingsSwoopUsed", "_bczBloodGeyserCasts", "_bczRefractedGazeCasts", "_bczSweatBulletsCasts", "_bczBloodBathCasts", "_bczDialitupCasts", "_bczSweatEquityCasts", "_bczBloodThinnerCasts", "_bczSpinalTapasCasts", "_bczPheromoneCocktailCasts", "_beanCannonUses", "_bearHugs", "_beerLensDrops", "_bellydancerPickpockets", "_benettonsCasts", "_beretBlastUses", "_beretBoastUses", "_beretBuskingUses", "_birdsSoughtToday", "_bookOfFactsWishes", "_bookOfFactsTatters", "_boomBoxFights", "_boomBoxSongsLeft", "_bootStomps", "_boxingGloveArrows", "_brickoEyeSummons", "_brickoFights", "_campAwayCloudBuffs", "_campAwaySmileBuffs", "_candyEggsDeviled", "_candySummons", "_captainHagnkUsed", "_carnieCandyDrops", "_carnivorousPottedPlantWins", "_carrotNoseDrops", "_catBurglarCharge", "_catBurglarHeistsComplete", "_cheerleaderSteam", "_chestXRayUsed", "_chibiAdventures", "_chipBags", "_chocolateCigarsUsed", "_chocolateCoveredPingPongBallsUsed", "_chocolateSculpturesUsed", "_chocolatesUsed", "_chronolithActivations", "_chronolithNextCost", "_cinchUsed", "_cinchoRests", "_circadianRhythmsAdventures", "_clanFortuneConsultUses", "_clipartSummons", "_clocksUsed", "_cloversPurchased", "_coldMedicineConsults", "_coldMedicineEquipmentTaken", "_companionshipCasts", "_concoctionDatabaseRefreshes", "_cookbookbatCrafting", "_cookbookbatCombatsUntilNewQuest", "_cosmicBowlingSkillsUsed", "_crimbo21ColdResistance", "_cyberFreeFights", "_cyberZone1Turns", "_cyberZone2Turns", "_cyberZone3Turns", "_dailySpecialPrice", "_dartsLeft", "_daycareGymScavenges", "_daycareRecruits", "_deckCardsDrawn", "_deluxeKlawSummons", "_demandSandwich", "_detectiveCasesCompleted", "_disavowed", "_dnaPotionsMade", "_donhosCasts", "_douseFoeUses", "_dreamJarDrops", "_drunkPygmyBanishes", "_durableDolphinWhistleUsed", "_edDefeats", "_edLashCount", "_eldritchTentaclesFoughtToday", "_elfGuardCookingUsed", "_elronsCasts", "_enamorangs", "_energyCollected", "_expertCornerCutterUsed", "_extraTimeUsed", "_favorRareSummons", "_feastUsed", "_feelinTheRhythm", "_feelPrideUsed", "_feelExcitementUsed", "_feelHatredUsed", "_feelLonelyUsed", "_feelNervousUsed", "_feelEnvyUsed", "_feelDisappointedUsed", "_feelSuperiorUsed", "_feelLostUsed", "_feelNostalgicUsed", "_feelPeacefulUsed", "_fingertrapArrows", "_fireExtinguisherCharge", "_fragrantHerbsUsed", "_freeBeachWalksUsed", "_frButtonsPressed", "_fudgeWaspFights", "_gapBuffs", "_garbageFireDrops", "_garbageFireDropsCrown", "_generateIronyUsed", "_genieFightsUsed", "_genieWishesUsed", "_gibbererAdv", "_gibbererCharge", "_gingerbreadCityTurns", "_glarkCableUses", "_glitchMonsterFights", "_gnomeAdv", "_godLobsterFights", "_goldenMoneyCharge", "_gongDrops", "_gothKidCharge", "_gothKidFights", "_greyYouAdventures", "_grimBrotherCharge", "_grimFairyTaleDrops", "_grimFairyTaleDropsCrown", "_grimoireConfiscatorSummons", "_grimoireGeekySummons", "_grimstoneMaskDrops", "_grimstoneMaskDropsCrown", "_grooseCharge", "_grooseDrops", "_grubbyWoolDrops", "_guzzlrDeliveries", "_guzzlrGoldDeliveries", "_guzzlrPlatinumDeliveries", "_hareAdv", "_hareCharge", "_highTopPumps", "_hipsterAdv", "_hoardedCandyDropsCrown", "_hoboUnderlingSummons", "_holidayMultitaskingUsed", "_holoWristDrops", "_holoWristProgress", "_hotAshesDrops", "_hotJellyUses", "_hotTubSoaks", "_humanMuskUses", "_iceballUses", "_inigosCasts", "_ironTricornHeadbuttUsed", "_jerksHealthMagazinesUsed", "_jiggleCheese", "_jiggleCream", "_jiggleLife", "_jiggleSteak", "_jitbCharge", "_juneCleaverAdvs", "_juneCleaverFightsLeft", "_juneCleaverEncounters", "_juneCleaverStench", "_juneCleaverSpooky", "_juneCleaverSleaze", "_juneCleaverHot", "_juneCleaverCold", "_juneCleaverSkips", "_jungDrops", "_kgbClicksUsed", "_kgbDispenserUses", "_kgbTranquilizerDartUses", "_klawSummons", "_kloopCharge", "_kloopDrops", "_kolhsAdventures", "_kolhsSavedByTheBell", "_lastDailyDungeonRoom", "_lastFitzsimmonsHatch", "_lastMobiusStripTurn", "_lastSausageMonsterTurn", "_lastZomboEye", "_latteRefillsUsed", "_lawOfAveragesUsed", "_leafblowerML", "_leafLassosCrafted", "_leafMonstersFought", "_leavesBurned", "_legionJackhammerCrafting", "_leprecondoRearrangements", "_leprecondoFurniture", "_llamaCharge", "_longConUsed", "_lovebugsBeachBuck", "_lovebugsChroner", "_lovebugsCoinspiracy", "_lovebugsFreddy", "_lovebugsFunFunds", "_lovebugsHoboNickel", "_lovebugsWalmart", "_loveChocolatesUsed", "_lynyrdSnareUses", "_machineTunnelsAdv", "_macrometeoriteUses", "_mafiaThumbRingAdvs", "_mapToACandyRichBlockDrops", "_mayamRests", "_mayflowerDrops", "_mayflySummons", "_mcHugeLargeAvalancheUses", "_mcHugeLargeSkiPlowUses", "_mcHugeLargeSlashUses", "_mediumSiphons", "_meteoriteAdesUsed", "_meteorShowerUses", "_micrometeoriteUses", "_mildEvilPerpetrated", "_mimicEggsDonated", "_mimicEggsObtained", "_miniKiwiDrops", "_miniMartiniDrops", "_mobiusStripEncounters", "_monkeyPawWishesUsed", "_monsterHabitatsFightsLeft", "_monsterHabitatsRecalled", "_monstersMapped", "_mushroomGardenFights", "_nanorhinoCharge", "_navelRunaways", "_neverendingPartyFreeTurns", "_newYouQuestSharpensDone", "_newYouQuestSharpensToDo", "_nextColdMedicineConsult", "_nextQuantumAlignment", "_nightmareFuelCharges", "_noobSkillCount", "_nuclearStockpileUsed", "_oilExtracted", "_oldSchoolCocktailCraftingUsed", "_olfactionsUsed", "_optimisticCandleDropsCrown", "_oreDropsCrown", "_otoscopeUsed", "_oysterEggsFound", "_pantsgivingBanish", "_pantsgivingCount", "_pantsgivingCrumbs", "_pantsgivingFullness", "_pasteDrops", "_perilsForeseen", "_peteJukeboxFixed", "_peteJumpedShark", "_petePeeledOut", "_photoBoothEffects", "_photoBoothEquipment", "_pieDrops", "_piePartsCount", "_pirateRealmGold", "_pirateRealmGlue", "_pirateRealmGrog", "_pirateRealmGrub", "_pirateRealmGuns", "_pirateRealmIslandMonstersDefeated", "_pirateRealmSailingTurns", "_pirateRealmShipSpeed", "_pixieCharge", "_pocketProfessorLectures", "_poisonArrows", "_pokeGrowFertilizerDrops", "_poolGames", "_powderedGoldDrops", "_powderedMadnessUses", "_powerfulGloveBatteryPowerUsed", "_powerPillDrops", "_powerPillUses", "_precisionCasts", "_questPartyFairItemsOpened", "_radlibSummons", "_raindohCopiesMade", "_rapidPrototypingUsed", "_raveStealCount", "_reflexHammerUsed", "_resolutionAdv", "_resolutionRareSummons", "_riftletAdv", "_robinEggDrops", "_roboDrops", "_rogueProgramCharge", "_romanticFightsLeft", "_saberForceMonsterCount", "_saberForceUses", "_saberMod", "_saltGrainsConsumed", "_sandwormCharge", "_saplingsPlanted", "_sausageFights", "_sausagesEaten", "_sausagesMade", "_seadentLightningUsed", "_sealFigurineUses", "_sealScreeches", "_sealsSummoned", "_shadowBricksUsed", "_shadowRiftCombats", "_shatteringPunchUsed", "_shortOrderCookCharge", "_shrubCharge", "_slimeVialsHarvested", "_sloppyDinerBeachBucks", "_smilesOfMrA", "_smithsnessSummons", "_smoochArmyHQCombats", "_snojoFreeFights", "_snojoParts", "_snokebombUsed", "_snowconeSummons", "_snowglobeDrops", "_snowmanHatPlaceUsed", "_snowSuitCount", "_sourceTerminalDigitizeMonsterCount", "_sourceTerminalDigitizeUses", "_sourceTerminalDuplicateUses", "_sourceTerminalEnhanceUses", "_sourceTerminalExtrudes", "_sourceTerminalPortscanUses", "_spaceFurDropsCrown", "_spacegatePlanetIndex", "_spacegateTurnsLeft", "_spaceJellyfishDrops", "_speakeasyDrinksDrunk", "_speakeasyFreeFights", "_spelunkerCharges", "_spelunkingTalesDrops", "_spikolodonSpikeUses", "_spiritOfTheMountainsAdvs", "_spookyJellyUses", "_stackLumpsUses", "_steamCardDrops", "_stickerSummons", "_stinkyCheeseCount", "_stressBallSqueezes", "_sugarSummons", "_summonResortPassesUsed", "_surprisinglySweetSlashUsed", "_surprisinglySweetStabUsed", "_sweatOutSomeBoozeUsed", "_taffyRareSummons", "_taffyYellowSummons", "_tearawayPantsAdvs", "_thanksgettingFoodsEaten", "_thingfinderCasts", "_thinknerdPackageDrops", "_thorsPliersCrafting", "_timeHelmetAdv", "_timeCopsFoughtToday", "_timeSpinnerMinutesUsed", "_tokenDrops", "_transponderDrops", "_turkeyBlastersUsed", "_turkeyBooze", "_turkeyMuscle", "_turkeyMyst", "_turkeyMoxie", "_unaccompaniedMinerUsed", "_unblemishedPearlAnemoneMineProgress", "_unblemishedPearlDiveBarProgress", "_unblemishedPearlMadnessReefProgress", "_unblemishedPearlMarinaraTrenchProgress", "_unblemishedPearlTheBriniestDeepestsProgress", "_unconsciousCollectiveCharge", "_universalSeasoningsUsed", "_universeCalculated", "_universeImploded", "_usedReplicaBatoomerang", "_vampyreCloakeFormUses", "_villainLairProgress", "_vitachocCapsulesUsed", "_vmaskAdv", "_voidFreeFights", "_volcanoItem1", "_volcanoItem2", "_volcanoItem3", "_volcanoItemCount1", "_volcanoItemCount2", "_volcanoItemCount3", "_voteFreeFights", "_VYKEACompanionLevel", "_warbearAutoAnvilCrafting", "_waxGlobDrops", "_whiteRiceDrops", "_witchessFights", "_xoHugsUsed", "_yellowPixelDropsCrown", "_zapCount", "_zombieSmashPocketsUsed", "lastNoncombat15", "lastNoncombat257", "lastNoncombat270", "lastNoncombat273", "lastNoncombat280", "lastNoncombat283", "lastNoncombat297", "lastNoncombat322", "lastNoncombat323", "lastNoncombat324", "lastNoncombat341", "lastNoncombat343", "lastNoncombat384", "lastNoncombat386", "lastNoncombat391", "lastNoncombat392", "lastNoncombat394", "lastNoncombat405", "lastNoncombat406", "lastNoncombat408", "lastNoncombat439", "lastNoncombat440", "lastNoncombat441", "lastNoncombat450", "lastNoncombat528", "lastNoncombat533", "lastNoncombat539", "lastNoncombat540", "lastNoncombat541", "lastNoncombat588", "lastNoncombat589", "lastNoncombat590", "lastNoncombat591", "lastNoncombat592"] as const; +export type NumericProperty = typeof numericProperties[number]; + +const PREFS = new Map([ + [$skill`BCZ: Blood Geyser`, "_bczBloodGeyserCasts"], + [$skill`BCZ: Refracted Gaze`, "_bczRefractedGazeCasts"], + [$skill`BCZ: Sweat Bullets`, "_bczSweatBulletsCasts"], + [$skill`BCZ: Blood Bath`, "_bczBloodBathCasts"], + [$skill`BCZ: Dial it up to 11`, "_bczDialitupCasts"], + [$skill`BCZ: Sweat Equity`, "_bczSweatEquityCasts"], + [$skill`BCZ: Create Blood Thinner`, "_bczBloodThinnerCasts"], + [$skill`BCZ: Prepare Spinal Tapas`, "_bczSpinalTapasCasts"], + [$skill`BCZ: Craft a Pheromone Cocktail`, "_bczPheromoneCocktailCasts"], +]); + +/** + * @param skill The BCZ skill to check. + * @returns The number of casts of the skill already used, parsing the pref. + */ +export function timesCast(skill: Skill): number { + const pref = PREFS.get(skill); + if (!pref) return 0; + return get(pref, 0); +} + +/** + * @param skill The BCZ skill to check. + * @returns The substat used to cast the skill. + */ +export function substatUsed(skill: Skill): Stat | null { + const cost = COSTS.get(skill); + if (!cost) return null; + return cost; +} + +/** + * @param skill The BCZ skill to check. + * @param statFloor Minimum base stat you want to keep. + * @returns The number of casts you can achieve of the selected skill before hitting the given stat floor. + */ +export function availableCasts(skill: Skill, statFloor: number): number { + if (!have_()) return 0; + + const stat = substatUsed(skill); + if (!stat) return 0; + + // const currentStat = myBasestat(stat); + const currentStat = myBasestat(stat); + const subStatFloor = statFloor ** 2; + + let casts = 0; + let remainingStat = currentStat; + + for (let i = timesCast(skill); i < 25; i++) { + // 25 is abritrary + const nextCost = skillCost(skill); + if (remainingStat - nextCost < subStatFloor) break; + remainingStat -= nextCost; + casts++; + } + + return casts; +} + +/** + * @param skill The BCZ skill to cast. + * @param statFloor Minimum base stat you want to keep. + * @returns Whether you successfully cast the spell. + */ +export function castDownTo(skill: Skill, statFloor: number): boolean { + if (!have_() || !COSTS.get(skill)) return false; + let casts = availableCasts(skill, statFloor); + while (casts) { + if (!useSkill(skill, casts)) return false; + casts = availableCasts(skill, statFloor); + } + + return true; +} + diff --git a/yarn.lock b/yarn.lock index e97cebe..3c86e25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2107,9 +2107,9 @@ core-js@^2.4.0: integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== core-js@^3.16.4: - version "3.33.2" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.33.2.tgz#312bbf6996a3a517c04c99b9909cdd27138d1ceb" - integrity sha512-XeBzWI6QL3nJQiHmdzbAOiMYqjrb7hwU7A39Qhvd/POSa/t9E1AeZyEZx3fNvp/vtM8zXwhoL0FsiS0hD0pruQ== + version "3.41.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.41.0.tgz#57714dafb8c751a6095d028a7428f1fb5834a776" + integrity sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA== create-create-app@^7.3.0: version "7.3.0" @@ -2357,10 +2357,10 @@ eslint-config-prettier@^8.8.0: resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348" integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== -eslint-plugin-libram@^0.4.15: - version "0.4.15" - resolved "https://registry.yarnpkg.com/eslint-plugin-libram/-/eslint-plugin-libram-0.4.15.tgz#ea6cf0a1f9941cf0e16927a8d688d8daa6567433" - integrity sha512-QW+CwY9ocoSBr5xHM7KnlC/YcjmbYMUFSM33SmqoBrliheo9oJErdmO5PVYBrjZ4bNjqh2Ea4xW7OEzbNXjT7g== +eslint-plugin-libram@^0.4.34: + version "0.4.34" + resolved "https://registry.yarnpkg.com/eslint-plugin-libram/-/eslint-plugin-libram-0.4.34.tgz#d5be47303b16825a86eb5ec457de1c62a83bdc1a" + integrity sha512-XgGsaZvsDAl0uYvxYGa/KYYUWQbnEZoxHNZQuwc6PFMH96AR8c64lWMJnHPTrsoVInGGS24cfdPbd9ZkDAZsHg== dependencies: html-entities "^2.5.2" requireindex "~1.2.0" @@ -2800,9 +2800,9 @@ graphemer@^1.4.0: integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== grimoire-kolmafia@^0.3.25: - version "0.3.25" - resolved "https://registry.yarnpkg.com/grimoire-kolmafia/-/grimoire-kolmafia-0.3.25.tgz#be46c7b8ee0e550e6346ea66d5fe171d06035889" - integrity sha512-LSnNURUnH8rHrHOgSd1RtTo1geuOU8CfM2B1fAXcHXViicQB0ZxvNYObXG+RqzjFBp+ud0fEFFI2VfRW8kyW9A== + version "0.3.29" + resolved "https://registry.yarnpkg.com/grimoire-kolmafia/-/grimoire-kolmafia-0.3.29.tgz#9f9f923b891a570365136177700f6c7614506969" + integrity sha512-EiFV38tuG7wEHhinBVepUqEDDmEyOo4vZjSRSiKEe13GV0+wgL8Q3ycArSAuE4125KPPFZ8EKXDmTU79xtPP7g== dependencies: core-js "^3.16.4" @@ -2850,9 +2850,9 @@ hasown@^2.0.0: function-bind "^1.1.2" html-entities@^2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f" - integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA== + version "2.6.0" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.6.0.tgz#7c64f1ea3b36818ccae3d3fb48b6974208e984f8" + integrity sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ== human-signals@^2.1.0: version "2.1.0" @@ -3144,10 +3144,10 @@ kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -kolmafia@^5.27815.0: - version "5.27815.0" - resolved "https://registry.yarnpkg.com/kolmafia/-/kolmafia-5.27815.0.tgz#d591a9a76ae9fd5ae469647e6606d55ca2ba0e95" - integrity sha512-M175RtbW2t5waudPQPXU8ypyzsme9ghi7/jY9tnCJmIrnKrvVBRcKrwCWdAs6E5WVz6DeE2YKmbTHJ2tDqqtxQ== +kolmafia@^5.28591.0: + version "5.28725.0" + resolved "https://registry.yarnpkg.com/kolmafia/-/kolmafia-5.28725.0.tgz#b4699c3b1bdb790d15ef0f127370041c4b8feb65" + integrity sha512-dPkZ1D0OOK20cMwsIYDKHU1/3ADy89k4rNfQRfyu3WAib3N2SWxoEA5/ohjyN1WnZ1U8aZ08GjwCr/WFL3m9dg== kuler@^2.0.0: version "2.0.0" @@ -3162,10 +3162,10 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -libram@^0.9.2: - version "0.9.2" - resolved "https://registry.yarnpkg.com/libram/-/libram-0.9.2.tgz#2cd174109226c08bb4b9982272d44bfa6eb74b22" - integrity sha512-RZiGUnvcD4L7zsRvwjtmmmGaDQvxfldXgx4Z7jP3Ly9zDufHg+mIxQIhhwfekYWLuvGrNWxnNmrIxUyIyBp31A== +libram@^0.11.14: + version "0.11.14" + resolved "https://registry.yarnpkg.com/libram/-/libram-0.11.14.tgz#011527aba45e3225809e9f0decaf9b83fcce8325" + integrity sha512-EzuyU8fru9rYWnQkaWGcknZJ6JgwLIH+GDI8NbCKgVsA7MuSKE6CU/Do/I18vm/dmgjqSbivzE8wcjKxUDNTAQ== dependencies: html-entities "^2.5.2"