diff --git a/package.json b/package.json index 95177f1..93be206 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.28591.0", - "libram": "^0.11.14", + "kolmafia": "^5.29297.0", + "libram": "^0.11.24", "mafia-shared-relay": "0.0.7", "webpack": "^5.89.0" }, diff --git a/src/args.ts b/src/args.ts index ffdca95..99afac0 100644 --- a/src/args.ts +++ b/src/args.ts @@ -51,6 +51,11 @@ export const args = Args.create( default: false, setting: "", }), + sea: Args.flag({ + help: "Ascend into and run seapath", + default: false, + setting: "", + }), crimbo: Args.flag({ help: "Farm Crimbo instead of Garbo", default: false, @@ -152,7 +157,11 @@ export const args = Args.create( default: "", }), ih8uscript: Args.string({ - help: "The command that will do your You, Robot run for you. Include any arguments desired.", + help: "The command that will do your ih8u run for you. Include any arguments desired.", + default: "", + }), + seascript: Args.string({ + help: "The command that will do your sea for you. Include any arguments desired.", default: "", }), pulls: Args.items({ diff --git a/src/main.ts b/src/main.ts index 5664c25..663d340 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,5 @@ import { Args, getTasks } from "grimoire-kolmafia"; -import { gamedayToInt, getRelated, Item, mallPrice, print } from "kolmafia"; -import { $item } from "libram"; +import { gamedayToInt, print } from "kolmafia"; import { args } from "./args"; import { ProfitTrackingEngine } from "./engine/engine"; @@ -8,7 +7,7 @@ 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 { deleteJunkKmails, halloween, notifyVoters, realDay, realMonth, totalApronMeatDrop } from "./tasks/utils"; const version = "0.0.3"; @@ -22,34 +21,9 @@ export function main(command?: string): void { } 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)}%)`); + print(`Total meat drop from Apron ${totalApronMeatDrop()}`); + if(totalApronMeatDrop() > 100) { + throw `Apron meat seems pretty good, maybe solve that?`; } } @@ -63,7 +37,15 @@ export function main(command?: string): void { print(`Running: candyWrapper v${version}`); - if (!args.cs && !args.smol && !args.casual && !args.robot && !args.zooto && !args.ih8u) + if ( + !args.cs && + !args.smol && + !args.casual && + !args.robot && + !args.zooto && + !args.ih8u && + !args.sea + ) throw "Undefined runtype; please choose an acceptable path"; const tasks = getTasks([AftercoreQuest(), AscendQuest(), RunQuests(), PostRunQuests()]); diff --git a/src/tasks/1 aftercoreleg.ts b/src/tasks/1 aftercoreleg.ts index 3f1bfb8..cd2add1 100644 --- a/src/tasks/1 aftercoreleg.ts +++ b/src/tasks/1 aftercoreleg.ts @@ -50,9 +50,20 @@ import { Cycle, setConfiguration, Station } from "libram/dist/resources/2022/Tra import { args } from "../args"; import { garboValue } from "../engine/profits"; - -import { chrono, crimbo, garboWeen, noBarf, postRunQuests, preRunQuests } from "./repeatableTasks"; import { Quest } from "../structure"; + +import { + chrono, + crimbo, + cupOf13sDrink, + fixScreech, + postRunQuests, + pregamePasta, + preRunQuests, + sealClubberFishySetup, + seaPearls, + sWordSetup, +} from "./repeatableTasks"; import { maxBase, pvpCloset, stooperDrunk, totallyDrunk } from "./utils"; const RESOURCES = ["Spice", "Rum", "Anchor", "Mast", "Silk", "Gold"] as const; @@ -171,8 +182,14 @@ export function AftercoreQuest(): Quest { }, ...preRunQuests(), ...postRunQuests(), - ...noBarf(), - ...garboWeen(), + ...pregamePasta(), + ...cupOf13sDrink(), + ...sealClubberFishySetup(), + ...sWordSetup(), + ...seaPearls(), + ...fixScreech(), + //...noBarf(), + //...garboWeen(), ...crimbo(), ...chrono(), { @@ -255,6 +272,7 @@ export function AftercoreQuest(): Quest { }, tracking: "Organs", }, + ...seaPearls(), { name: "Nightcap (Marginal)", ready: () => have($item`Beach Comb`) || have($item`Map to Safety Shelter Grimace Prime`), diff --git a/src/tasks/2 ascend.ts b/src/tasks/2 ascend.ts index 9220d7e..b23b0cd 100644 --- a/src/tasks/2 ascend.ts +++ b/src/tasks/2 ascend.ts @@ -2,18 +2,32 @@ import { cliExecute, equip, handlingChoice, + mallPrice, myAdventures, myDaycount, + retrieveItem, runChoice, visitUrl, } from "kolmafia"; -import { $class, $item, $path, $skill, $skills, ascend, CursedMonkeyPaw, have } from "libram"; -import { availableCasts, castDownTo } from "libram/dist/resources/2025/BloodCubicZirconia"; +import { + $class, + $item, + $items, + $path, + $skill, + $skills, + $slot, + ascend, + BloodCubicZirconia, + CursedMonkeyPaw, + EternityCodpiece, + have, +} from "libram"; import { args } from "../args"; +import { Quest } from "../structure"; import { targetPerms } from "./perm"; -import { Quest } from "../structure"; import { toMoonSign, totallyDrunk } from "./utils"; const skipPizza = args.cs || args.smol ? false : true; @@ -28,17 +42,57 @@ export function AscendQuest(): Quest { 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, + BloodCubicZirconia.availableCasts($skill`BCZ: Prepare Spinal Tapas`, 0) === 0 && + BloodCubicZirconia.availableCasts($skill`BCZ: Craft a Pheromone Cocktail`, 0) === 0 && + BloodCubicZirconia.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), + (sk) => { + const casts = BloodCubicZirconia.availableCasts(sk, 0); + for (let index = 0; index < casts; index++) { + BloodCubicZirconia.cast(sk, BloodCubicZirconia.availableCasts(sk, 0)); + } + }, ); }, tracking: "Other", }, + { + name: "Do Pizza", + ready: () => args.cs || args.smol || args.sea, + completed: () => + have($item`Deep Dish of Legend`) && + have($item`Calzone of Legend`) && + have($item`Pizza of Legend`), + do: () => { + retrieveItem($item`Deep Dish of Legend`); + retrieveItem($item`Calzone of Legend`); + retrieveItem($item`Pizza of Legend`); + }, + tracking: "Other", + }, + { + name: "Smuggle Pearls", + ready: () => + (mallPrice($item`unblemished pearl`) <= 75_000 || have($item`unblemished pearl`, 5)) && + EternityCodpiece.have() && + args.sea, + completed: () => + EternityCodpiece.currentGems().join(",") === + $items`unblemished pearl, unblemished pearl, unblemished pearl, unblemished pearl, unblemished pearl`.join( + ",", + ), + do: () => { + retrieveItem($item`unblemished pearl`, 5); + equip($item`unblemished pearl`, $slot`codpiece1`); + equip($item`unblemished pearl`, $slot`codpiece2`); + equip($item`unblemished pearl`, $slot`codpiece3`); + equip($item`unblemished pearl`, $slot`codpiece4`); + equip($item`unblemished pearl`, $slot`codpiece5`); + }, + tracking: "Other", + }, { name: "Do the Ascension", ready: () => @@ -65,6 +119,8 @@ export function AscendQuest(): Quest { ? $path`Z is for Zootomist` : args.ih8u ? $path`11 Things I Hate About U` + : args.sea + ? $path`11,037 Leagues Under the Sea` : undefined; const lifestyle = args.casual ? 1 : 2; @@ -90,8 +146,9 @@ export function AscendQuest(): Quest { }); cliExecute("refresh all"); visitUrl("main.php"); - if (args.smol || args.robot) { + if (args.smol || args.robot || args.sea) { while (handlingChoice()) runChoice(1); + runChoice(1); } }, }, diff --git a/src/tasks/3 runleg.ts b/src/tasks/3 runleg.ts index 6a8cd7c..df968a4 100644 --- a/src/tasks/3 runleg.ts +++ b/src/tasks/3 runleg.ts @@ -17,9 +17,9 @@ import { import { $item, $skill, clamp, get, have } from "libram"; import { args } from "../args"; - -import { preRunQuests } from "./repeatableTasks"; import { Quest } from "../structure"; + +import { preRunQuests, seaPearls } from "./repeatableTasks"; import { shouldWeOverdrink } from "./utils"; const runType = () => @@ -33,6 +33,8 @@ const runType = () => ? args.robotscript : args.ih8u ? args.ih8uscript + : args.sea + ? args.seascript : "autoscend"; export function howManySausagesCouldIEat() { @@ -86,9 +88,7 @@ function doIh8uDrinks(): void { while ( canDrinkMore() && (have($item`mini kiwitini`) || - (have($item`mini kiwi`) && - (have($item`bottle of vodka`) || - have($item`bottle of gin`)))) + (have($item`mini kiwi`) && (have($item`bottle of vodka`) || have($item`bottle of gin`)))) ) { if (!have($item`mini kiwitini`)) retrieveItem($item`mini kiwitini`); useSkill($skill`The Ode to Booze`); @@ -117,6 +117,7 @@ export function RunQuests(): Quest { completed: () => get("kingLiberated"), tasks: [ ...preRunQuests(), + ...seaPearls(), { name: "Run", completed: () => (get("kingLiberated") && args.cs) || step("questL13Final") > 11, diff --git a/src/tasks/4 postrunleg.ts b/src/tasks/4 postrunleg.ts index de43eef..4901ee2 100644 --- a/src/tasks/4 postrunleg.ts +++ b/src/tasks/4 postrunleg.ts @@ -1,10 +1,8 @@ -import { CombatStrategy } from "grimoire-kolmafia"; import { buy, canAdventure, cliExecute, drink, - Effect, getWorkshed, hippyStoneBroken, inebrietyLimit, @@ -15,13 +13,10 @@ import { myDaycount, myInebriety, myLevel, - myMaxhp, mySign, numericModifier, print, pvpAttacksLeft, - restoreHp, - restoreMp, retrieveItem, runChoice, setProperty, @@ -30,7 +25,6 @@ import { useFamiliar, useSkill, visitUrl, - wait, } from "kolmafia"; import { $coinmaster, @@ -39,29 +33,30 @@ import { $familiar, $familiars, $item, - $items, $location, $skill, + BarrelShrine, 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"; + chrono, + crimbo, + cupOf13sDrink, + fixScreech, + pompeii, + postRunQuests, + pregamePasta, + sealClubberFishySetup, + seaPearls, + sWordSetup, +} from "./repeatableTasks"; +import { doneAdventuring, pvpCloset, stooperDrunk, totallyDrunk } from "./utils"; let pajamas = false; let smoke = 0; @@ -87,8 +82,9 @@ export function PostRunQuests(): Quest { { name: "Clear citizen", completed: () => - !get("_citizenZone", "").includes("castle") && - !get("_citizenZone", "").includes("Madness Bakery"), + !get("_citizenZone") || + get("_citizenZone") === $location`The Dire Warren` || + get("_citizenZone") === $location`Barf Mountain`, do: (): void => { uneffect($effect`Citizen of a Zone`); cliExecute(`set _citizenZone = ""`); @@ -206,148 +202,11 @@ export function PostRunQuests(): Quest { 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`), + ready: () => !args.cs, completed: () => have($skill`Liver of Steel`) || have($item`steel margarita`), - do: () => cliExecute("panda temple"), + do: () => cliExecute(`loopstar goal="organ"`), tracking: "Steel Organ", }, { @@ -376,9 +235,16 @@ export function PostRunQuests(): Quest { limit: { tries: 6 }, tracking: "Organs", }, + ...pregamePasta(), ...postRunQuests(), - ...noBarf(), - ...garboWeen(), + ...pompeii(), + ...sWordSetup(), + ...cupOf13sDrink(), + ...sealClubberFishySetup(), + ...seaPearls(), + ...fixScreech(), + //...noBarf(), + //...garboWeen(), ...crimbo(), ...chrono(), { @@ -468,7 +334,8 @@ export function PostRunQuests(): Quest { completed: () => !have($skill`Aug. 13th: Left/Off Hander's Day!`) || have($effect`Offhand Remarkable`) || - get("_aug13Cast", false), + get("_aug13Cast", false) || + (get("_augSkillsCast") >= 5 && get("_augTodayCast")), do: () => useSkill($skill`Aug. 13th: Left/Off Hander's Day!`), tracking: "Rollover Prep", }, @@ -510,6 +377,7 @@ export function PostRunQuests(): Quest { if (have($item`clockwork maid`)) { use($item`clockwork maid`); } + BarrelShrine.smashParty(); pajamas = true; }, outfit: () => ({ diff --git a/src/tasks/repeatableTasks.ts b/src/tasks/repeatableTasks.ts index 8bba2c1..8a56d08 100644 --- a/src/tasks/repeatableTasks.ts +++ b/src/tasks/repeatableTasks.ts @@ -1,21 +1,31 @@ import { CombatStrategy } from "grimoire-kolmafia"; import { + autosellPrice, availableAmount, canAdventure, + chew, cliExecute, + cupOf13sTier, + eatsilent, equip, Familiar, fullnessLimit, getCampground, getClanLounge, getClanName, + getWorkshed, guildStoreAvailable, handlingChoice, hippyStoneBroken, holiday, inebrietyLimit, + Item, + itemDropsArray, lastChoice, + Location, + mallPrice, maximize, + Monster, myAdventures, myClass, myDaycount, @@ -24,11 +34,15 @@ import { myInebriety, myLevel, myMaxhp, + myMp, myPrimestat, mySpleenUse, + print, restoreHp, + restoreMp, retrieveItem, runChoice, + Skill, spleenLimit, storageAmount, takeStorage, @@ -45,25 +59,34 @@ import { $items, $location, $monster, + $monsters, + $phylum, $skill, $skills, $stat, AprilingBandHelmet, + AsdonMartin, CombatLoversLocket, + CupOfThirteens, get, getTodaysHolidayWanderers, have, Macro, + maxBy, PocketProfessor, set, + Snapper, + sum, uneffect, } from "libram"; import { args } from "../args"; - import { Task } from "../structure"; + import { + cheapestItem, getGarden, + getNextFreeKill, maxBase, pantogram, pantogramReady, @@ -83,6 +106,9 @@ const bestFam = (): Familiar => $familiar`Grey Goose`, ].find(famCheck) ?? $familiar`Grey Goose`; +const bestSeaFam = + get("screechCombats") > 0 ? $familiar`Patriotic Eagle` : $familiar`Red-Nosed Snapper`; + const doSmol = args.smol ? true : false; export function postRunQuests(): Task[] { @@ -141,8 +167,7 @@ export function postRunQuests(): Task[] { choices: { 1494: 2, }, - do: () => - use($item`S.I.T. Course Completion Certificate`), + do: () => use($item`S.I.T. Course Completion Certificate`), }, { name: "Restore HP", @@ -499,12 +524,11 @@ export function crimbo(): Task[] { }, clear: "all", tracking: "Crimbo", - limit: { tries: 1 }, //this will run again after installing CMC, by magic + limit: { tries: 1 }, }, { name: "Super Nightcap", - ready: () => - have($item`Drunkula's wineglass`) && holiday().includes("Halloween") && myDaycount() > 1, + ready: () => have($item`Drunkula's wineglass`) && args.crimbo && myDaycount() > 1, completed: () => totallyDrunk(), do: () => cliExecute(`CONSUME NIGHTCAP`), tracking: "Organs", @@ -519,7 +543,347 @@ export function crimbo(): Task[] { }, clear: "all", tracking: "Crimbo", - limit: { tries: 1 }, //this will run again after installing CMC, by magic + limit: { tries: 1 }, + }, + ]; +} + +export function cupOf13sDrink(): Task[] { + return [ + { + name: "Drink Cup of 13s", + ready: () => get("_cupOf13sJewels", 0) >= 13 && myInebriety() < inebrietyLimit() && args.cs, + completed: () => get("_cupOf13sJewels", 0) < 13, + do: () => { + const combination = cheapestCupOf13sCombination(); + if (combination === null) { + throw "Cup of 13s died"; + } + const item1 = combination[0]; + const item2 = combination[1]; + const item3 = combination[2]; + + print(`Using items ${item1}, ${item2}, and ${item3} for cup of 13s`); + + combination.forEach((i) => retrieveItem(i, 2)); + CupOfThirteens.drink(item1, item2, item3); + }, }, ]; } + +export function sWordSetup(): Task[] { + if (!CombatLoversLocket.have() || !CombatLoversLocket.reminiscesLeft()) { + return []; + } else + return [ + { + name: "Set up S Word", + ready: () => have($familiar`Sword of S Words`), + completed: () => get("swordOfSWordsMonster", Monster.none) === getSWordMonster(), + do: () => CombatLoversLocket.reminisce(getSWordMonster()), + outfit: () => { + return { + pants: $item`Greatest American Pants`, + familiar: $familiar`Sword of S Words`, + }; + }, + combat: new CombatStrategy().macro( + Macro.trySkill($skill`%fn, kill a lot of these guys`).runaway(), + ), + }, + ]; +} + +function getSWordMonster(): Monster { + // eslint-disable-next-line libram/verify-constants + const goodMonsters = $monsters`giant flamingo statue, Sloppy Seconds Burger, strong wind`; + + return maxBy(goodMonsters, (monster) => + sum(itemDropsArray(monster), ({ drop, rate }) => (rate / 100) * mallPrice(drop)), + ); +} + +export function sealClubberFishySetup(): Task[] { + if (myClass() !== $class`Seal Clubber`) return []; + else + return [ + { + name: "Set up Screech", + ready: () => + get("screechCombats") <= 0 && + !get("banishedPhyla").includes("fish") && + get("beGregariousFightsLeft") <= 0, + completed: () => get("banishedPhyla").includes("fish"), + do: () => $location`The Briniest Deepests`, + outfit: () => { + return { + modifier: "cold res", + }; + }, + combat: new CombatStrategy().macro( + Macro.trySkill($skill`%fn, Release the Patriotic Screech!`) + .trySkillRepeat(combatSkill()) + .repeat(), + ), + }, + { + name: "Set up Greg", + ready: () => mySpleenUse() < spleenLimit() - 2, + prepare: () => { + retrieveItem($item`Extrovermectin™`); + chew($item`Extrovermectin™`); + }, + completed: () => + have($effect`Fishy`, 300) || + mySpleenUse() >= spleenLimit() || + get("beGregariousFightsLeft") > 0, + do: () => CombatLoversLocket.reminisce($monster`cockroach`), + combat: new CombatStrategy().macro( + Macro.trySkill($skill`Be Gregarious`) + .trySkillRepeat(combatSkill()) + .repeat(), + ), + }, + ]; +} + +export function fixScreech(): Task[] { + return [ + { + name: "Fix screech", + ready: () => + (get("banishedPhyla").includes("construct") || get("banishedPhyla").includes("fish")) && + get("screechCombats") <= 0 && + args.sea, + completed: () => + !get("banishedPhyla").includes("construct") && !get("banishedPhyla").includes("fish"), + do: $location`Pandamonium Slums`, + outfit: { + familiar: $familiar`Patriotic Eagle`, + pants: $item`Greatest American Pants`, + }, + combat: new CombatStrategy().macro( + Macro.trySkill($skill`%fn, Release the Patriotic Screech!`).runaway(), + ), + }, + ]; +} + +export function pompeii(): Task[] { + return [ + { + name: "Pompeii (bastille)", + ready: () => !get("_pompeiiRun", false), + completed: () => get("_pompeiiRun", false), + do: () => { + cliExecute("pompeii"); + set("_pompeiiRun", true); + }, + }, + ]; +} + +export function pregamePasta(): Task[] { + return [ + { + name: "Pregame Legendary Pasta", + ready: () => getWorkshed() === $item`portable Mayo Clinic`, + completed: () => true, + do: () => { + // eslint-disable-next-line libram/verify-constants + const legendaryPasta = $items`Pesto alla Marziano, Arrattabbattabiata, Frutti di Scatoletta, Orzo di Riso, Pasta Grimavera, Linguini Ubriacapa, Gnocci Domani, Formica e Pepe, Tubetto Gelatto`; + const cheapestPasta = cheapestItem(legendaryPasta); + const deepDish = $item`Deep Dish of Legend`; + retrieveItem($item`Mayoflex`); + use($item`Mayoflex`); + retrieveItem(cheapestPasta); + eatsilent(cheapestPasta, 1); + runChoice(5); + retrieveItem($item`Mayozapine`); + use($item`Mayozapine`); + retrieveItem(deepDish); + eatsilent(deepDish); + }, + }, + ]; +} + +export function seaPearls(): Task[] { + return SEA_PEARL_ZONES.map((zone) => ({ + name: zone.name, + ready: () => + canSeaPearl() && + !get(zone.prop) && + myAdventures() >= 10 - get(`${zone.prop}Progress`, 0) / 10, + completed: () => get(zone.prop, false), + prepare: () => seaPearlPrepare(), + do: () => zone.location, + outfit: () => getSeaPearlOutfit(zone.modifier), + combat: new CombatStrategy().macro(buildSeaPearlMacro()), + clear: "all", + tracking: "Sea Pearl", + limit: { turns: 10 }, + })); +} + +function canSeaPearl(): boolean { + return ( + get("isMerkinGladiatorChampion") && + (mallPrice($item`unblemished pearl`) / 10 >= get("valueOfAdventure") || + myInebriety() > inebrietyLimit() + 1) + ); +} + +function seaPearlPrepare(): void { + const goodphylum = $phylum`fish`; + if (Snapper.getTrackedPhylum() !== goodphylum) { + Snapper.trackPhylum(goodphylum); + } + if (mallPrice($item`shadow brick`) < get("valueOfAdventure")) { + retrieveItem($item`shadow brick`, 13); + } + if (!have($effect`Fishy`) && mallPrice($item`cuppa Gill tea`) < get("valueOfAdventure") * 30) { + retrieveItem($item`cuppa Gill tea`); + use($item`cuppa Gill tea`); + } + if (AsdonMartin.installed() && !have($effect`Driving Waterproofly`)) { + AsdonMartin.drive(AsdonMartin.Driving.Waterproofly); + } + if (!have($effect`Feeling Peaceful`) && get("_feelPeacefulUsed") < 3) { + useSkill($skill`Feel Peaceful`); + } + if (!have($effect`Astral Shell`)) { + useSkill($skill`Astral Shell`); + } + if (!have($effect`Elemental Saucesphere`)) { + useSkill($skill`Elemental Saucesphere`); + } + if (have($effect`The Colors...`)) { + uneffect($effect`The Colors...`); + } + if (myHp() < 1000 && myMaxhp() > 1000) { + restoreHp(1000); + } + if (myMp() < 50) { + restoreMp(50); + } +} + +function combatSkill(): Skill { + if (myPrimestat() === $stat`Muscle`) { + return $skill`Lunging Thrust-Smack`; + } else return $skill`Saucegeyser`; +} + +function cheapestCupOf13sCombination(): [Item, Item, Item] | null { + const items = Item.all().filter( + (item) => + !item.name.includes("Loathing Idol") && + cupOf13sTier(item) > 3 && + autosellPrice(item) < 250 && + mallPrice(item) <= 500, + ); + + let best: [Item, Item, Item] | null = null; + let bestPrice = Infinity; + + for (let i = 0; i < items.length; i++) { + const item1 = items[i]; + const tier1 = cupOf13sTier(item1); + + for (let j = i; j < items.length; j++) { + const item2 = items[j]; + const tier2 = cupOf13sTier(item2); + + for (let k = j; k < items.length; k++) { + const item3 = items[k]; + const tier3 = cupOf13sTier(item3); + + if (tier1 + tier2 + tier3 !== 13) continue; + + const price = mallPrice(item1) + mallPrice(item2) + mallPrice(item3); + + if (price < bestPrice) { + bestPrice = price; + best = [item1, item2, item3]; + } + } + } + } + + return best; +} + +function buildSeaPearlMacro(): Macro { + const macro = Macro.if_(`monsterphylum plant`, Macro.trySkill($skill`Tear Away your Pants!`)).if_( + `!monsterphylum ${$phylum`fish`}`, + Macro.trySkill($skill`Sea *dent: Talk to Some Fish`), + ); + const freeKill = getNextFreeKill(); + + if (freeKill) { + if (freeKill.do instanceof Skill) { + macro.trySkill(freeKill.do); + } else if (freeKill.do instanceof Item) { + macro.tryItem(freeKill.do); + } + } + + return macro.trySkillRepeat(combatSkill()).repeat(); +} + +function getSeaPearlOutfit(elementModifier: string) { + const needsScuba = !have($effect`Driving Waterproofly`); + const isUnderwaterFam = bestFam().underwater; + + return { + familiar: bestSeaFam, + modifier: elementModifier, + back: needsScuba ? $item`Elf Guard SCUBA tank` : undefined, + weapon: myClass() === $class`Seal Clubber` ? $item`Monodent of the Sea` : undefined, + famequip: !isUnderwaterFam && needsScuba ? $item`das boot` : undefined, + pants: $item`tearaway pants`, + ...getNextFreeKill()?.equip, // Dynamically equips the item needed for the available free kill + }; +} + +interface SeaPearlZone { + name: string; + prop: string; + location: Location; + modifier: string; +} + +const SEA_PEARL_ZONES: SeaPearlZone[] = [ + { + name: "Sea Pearl Mine", + prop: "_unblemishedPearlAnemoneMine", + location: $location`Anemone Mine`, + modifier: "spooky res", + }, + { + name: "Sea Pearl Bar", + prop: "_unblemishedPearlDiveBar", + location: $location`The Dive Bar`, + modifier: "sleaze res", + }, + { + name: "Sea Pearl Reef", + prop: "_unblemishedPearlMadnessReef", + location: $location`Madness Reef`, + modifier: "stench res", + }, + { + name: "Sea Pearl Trench", + prop: "_unblemishedPearlMarinaraTrench", + location: $location`The Marinara Trench`, + modifier: "hot res", + }, + { + name: "Sea Pearl Deepests", + prop: "_unblemishedPearlTheBriniestDeepests", + location: $location`The Briniest Deepests`, + modifier: "cold res", + }, +]; diff --git a/src/tasks/utils.ts b/src/tasks/utils.ts index 9bf9a9b..b541467 100644 --- a/src/tasks/utils.ts +++ b/src/tasks/utils.ts @@ -1,4 +1,5 @@ import { + blackAndWhiteApronKitContents, canEquip, chew, cliExecute, @@ -22,6 +23,7 @@ import { myFullness, myInebriety, myName, + myPath, myPrimestat, mySpleenUse, outfitPieces, @@ -29,6 +31,7 @@ import { print, putCloset, retrieveItem, + Skill, spleenLimit, storageAmount, toItem, @@ -37,19 +40,24 @@ import { visitUrl, } from "kolmafia"; import { + $effect, $familiar, $familiars, $item, $items, $location, + $monster, + $path, $phylum, $skill, Kmail as _Kmail, gameDay, get, getBanishedMonsters, + getModifier, have, maxBy, + MenuItem, Pantogram, set, Snapper, @@ -58,6 +66,7 @@ import { import { args } from "../args"; import { garboAverageValue, garboValue } from "../engine/profits"; +import { CombatResource as BaseCombatResource, OutfitSpec } from "grimoire-kolmafia"; export function haveAll(its: Item[]): boolean { return its.reduce((a, it) => a && have(it), true); @@ -474,3 +483,107 @@ export function pantogram(): boolean { ); return true; } + +export function garboTarget(): Monster { + const values = new Map([ + [$monster`cockroach`, 5_000], + [$monster`Witchess Bishop`, mallPrice($item`Sacramento wine`) * 0.9], + [$monster`Witchess Knight`, mallPrice($item`jumping horseradish`) * 0.9], + ]); + + return [...values.entries()].reduce((best, current) => + current[1] > best[1] ? current : best, + )[0]; +} + +export const cheapestItem: (items: Item[]) => Item = (items: Item[]) => + maxBy(items, MenuItem.defaultPriceFunction, true); + +const apron = () => blackAndWhiteApronKitContents(); + +export const totalApronMeatDrop = () => { + let total = getModifier("Meat Drop", apron().main_effect); + + for (let mealIndex = 0; mealIndex < 3; mealIndex++) { + for (let foodIndex = 0; foodIndex < 5; foodIndex++) { + total += getModifier( + "Meat Drop", + apron().meals[mealIndex][foodIndex].effect + ); + } + } + + return total; +}; + +export interface Resource { + name: string; + available: () => boolean; + prepare?: () => void; + equip?: Item | Familiar | OutfitSpec | OutfitSpec[]; + effect?: Effect; + chance?: () => number; +} + +export type CombatResource = Resource & BaseCombatResource; + +interface FreekillSource extends CombatResource { + do: Item | Skill; +} + +export const freekillSources: FreekillSource[] = [ + { + name: "Shadow Brick", + available: () => have($item`shadow brick`) && get("_shadowBricksUsed") < 13, + do: $item`shadow brick`, + }, + { + name: "Sweat Bullets", + available: () => + have($item`blood cubic zirconia`) && + get("_bczSweatBulletsCasts", 0) < 7 && + myPath() !== $path`Grey You`, + do: $skill`BCZ: Sweat Bullets`, + equip: $item`blood cubic zirconia`, + }, + { + name: "Gingerbread Mob Hit", + available: () => have($skill`Gingerbread Mob Hit`) && !get("_gingerbreadMobHitUsed"), + do: $skill`Gingerbread Mob Hit`, + }, + { + name: "Lil' Doctor™ bag", + available: () => have($item`Lil' Doctor™ bag`) && get("_chestXRayUsed") < 3, + do: $skill`Chest X-Ray`, + equip: $item`Lil' Doctor™ bag`, + }, + { + name: "Shattering Punch", + available: () => have($skill`Shattering Punch`) && get("_shatteringPunchUsed") < 3, + do: $skill`Shattering Punch`, + }, + { + name: "Replica bat-oomerang", + available: () => have($item`replica bat-oomerang`) && get("_usedReplicaBatoomerang") < 3, + do: $item`replica bat-oomerang`, + }, + { + name: "The Jokester's gun", + available: () => have($item`The Jokester's gun`) && !get("_firedJokestersGun"), + do: $skill`Fire the Jokester's Gun`, + equip: $item`The Jokester's gun`, + }, + { + name: "Jurassic Parka", + available: () => + have($skill`Torso Awareness`) && + have($item`Jurassic Parka`) && + !have($effect`Everything Looks Yellow`), + equip: { equip: $items`Jurassic Parka`, modes: { parka: "dilophosaur" } }, + do: $skill`Spit jurassic acid`, + }, +]; + +export function getNextFreeKill(): FreekillSource | undefined { + return freekillSources.find((source) => source.available()); +} diff --git a/webpack.config.js b/webpack.config.js index a4c52a9..8b6d9e2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -25,6 +25,9 @@ const shared = { { // Include ts, tsx, js, and jsx files. test: /\.(ts|js)x?$/, + resolve: { + fullySpecified: false, + }, // exclude: /node_modules/, loader: "babel-loader", }, diff --git a/yarn.lock b/yarn.lock index 3c86e25..18aa92f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2111,6 +2111,11 @@ core-js@^3.16.4: resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.41.0.tgz#57714dafb8c751a6095d028a7428f1fb5834a776" integrity sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA== +core-js@^3.47.0: + version "3.50.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.50.0.tgz#a18646fcb0097650189b4504c803e887bcae4c0a" + integrity sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw== + create-create-app@^7.3.0: version "7.3.0" resolved "https://registry.yarnpkg.com/create-create-app/-/create-create-app-7.3.0.tgz#d867edabc758167ba6ebca139d79484e8131c72c" @@ -2248,6 +2253,11 @@ enhanced-resolve@^5.15.0: graceful-fs "^4.2.4" tapable "^2.2.0" +entities@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.1.tgz#26e8a88889db63417dcb9a1e79a3f1bc92b5976b" + integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA== + envinfo@^7.7.3, envinfo@^7.8.1: version "7.10.0" resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.10.0.tgz#55146e3909cc5fe63c22da63fb15b05aeac35b13" @@ -3144,10 +3154,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.28591.0: - version "5.28725.0" - resolved "https://registry.yarnpkg.com/kolmafia/-/kolmafia-5.28725.0.tgz#b4699c3b1bdb790d15ef0f127370041c4b8feb65" - integrity sha512-dPkZ1D0OOK20cMwsIYDKHU1/3ADy89k4rNfQRfyu3WAib3N2SWxoEA5/ohjyN1WnZ1U8aZ08GjwCr/WFL3m9dg== +kolmafia@^5.29297.0: + version "5.29352.0" + resolved "https://registry.yarnpkg.com/kolmafia/-/kolmafia-5.29352.0.tgz#58b24740e4d6b12c56acd8641435a58d24cd2b5a" + integrity sha512-/tJN2boLu2rvKhHcz4FrwNwSkuhPUOiquNET2eh1v3+rUgHnVfUKCrEDl0LY8Qd0vjdw4Q61KWiwuXgAQI/kaQ== kuler@^2.0.0: version "2.0.0" @@ -3162,12 +3172,13 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -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== +libram@^0.11.24: + version "0.11.36" + resolved "https://registry.yarnpkg.com/libram/-/libram-0.11.36.tgz#dbd35de2ffb7eaf124c8d6720c442a97137b7805" + integrity sha512-oWFC9m6LFgc8BKyPTkslpJIjE6dAkrKcQoBH6UPRNe5vvJqPMnyjABolo90FelMGvuQQZVOgyrufSwdSLy5iHw== dependencies: - html-entities "^2.5.2" + core-js "^3.47.0" + entities "^7.0.0" license.js@^3.1.2: version "3.1.2"