diff --git a/src/engine.ts b/src/engine.ts index 30e6ecf..d15e7f4 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -88,7 +88,6 @@ export class ChronerEngine extends Engine { ), ); } - this.propertyManager.setChoices({ 955: 2 }); } shouldRepeatAdv(task: ChronerTask): boolean { diff --git a/src/lib.ts b/src/lib.ts index fc84878..b2477d1 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -4,14 +4,23 @@ import { inebrietyLimit, isDarkMode, Item, + mpCost, myAdventures, myFamiliar, + myHp, myInebriety, + myMaxhp, + myMaxmp, + myMp, print, runChoice, + Skill, + totalFreeRests, + use, + useSkill, visitUrl, } from "kolmafia"; -import { $familiar, get, SourceTerminal } from "libram"; +import { $familiar, $item, get, have, SourceTerminal } from "libram"; /** * Find the best element of an array, where "best" is defined by some given criteria. @@ -90,6 +99,7 @@ export const args = Args.create("chrono", "A script for farming chroner", { ["rose", "Farm Roses from The Main Stage"], ["capsule", "Farm Time Capsules from the Cave Before Time"], ["future", "Farm... something from the Automated Future"], + ["rock", "Get Caveman Dan's Favorite Rock - duped as much as possible"], ["soup", "Farm soup ingredients from the Primordial Stew"], ], default: "rose", @@ -146,3 +156,35 @@ export function realmAvailable(identifier: RealmType): boolean { get(`${identifier}AirportAlways`, false) ); } + +export function freeRest(): boolean { + if (get("timesRested") >= totalFreeRests()) return false; + + if (myHp() >= myMaxhp() && myMp() >= myMaxmp()) { + if (have($item`awful poetry journal`)) { + use($item`awful poetry journal`); + } else { + // burn some mp so that we can rest + const bestSkill = maxBy( + Skill.all().filter((sk) => have(sk) && mpCost(sk) >= 1), + (sk) => -mpCost(sk), + ); // are there any other skills that cost mana which we should blacklist? + // Facial expressions? But this usually won't be an issue since all *NORMAL* classes have access to a level1 1mp skill + useSkill(bestSkill); + } + } + + if (get("chateauAvailable")) { + visitUrl("place.php?whichplace=chateau&action=chateau_restlabelfree"); + } else if (get("getawayCampsiteUnlocked")) { + visitUrl("place.php?whichplace=campaway&action=campaway_tentclick"); + } else { + visitUrl("campground.php?action=rest"); + } + + return true; +} + +export function freeRestsLeft(): boolean { + return get("timesRested") >= totalFreeRests(); +} diff --git a/src/macro.ts b/src/macro.ts index 8d9fb5d..ce6e850 100644 --- a/src/macro.ts +++ b/src/macro.ts @@ -1,4 +1,4 @@ -import { Item, Monster, myFamiliar, Skill } from "kolmafia"; +import { haveEquipped, Item, Monster, myFamiliar, Skill } from "kolmafia"; import { $familiar, $item, @@ -84,6 +84,29 @@ export default class Macro extends StrictMacro { return this.step(steps); } + getRocks(): this { + return this.externalIf( + myFamiliar() === $familiar`Grey Goose` && + $familiar`Grey Goose`.experience >= 36, + Macro.trySkill($skill`Emit Matter Duplicating Drones`), + ).externalIf( + haveEquipped($item`pro skateboard`), + Macro.trySkill($skill`Do an epic McTwist!`), + ); + } + + static getRocks(): Macro { + return new Macro().getRocks(); + } + + spikes(): this { + return this.trySkill($skill`Launch spikolodon spikes`); + } + + static spikes(): Macro { + return new Macro().spikes(); + } + standardCombat(): this { return this.externalIf( canOpenRedPresent() && myFamiliar() === $familiar`Crimbo Shrub`, diff --git a/src/main.ts b/src/main.ts index ecca838..167c887 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,6 +4,7 @@ import { canAdventure, cliExecute, myAdventures, + myAscensions, myClass, myTurncount, print, @@ -48,6 +49,7 @@ import { getBestAutomatedFutureSide, } from "./future"; import { setup } from "./setup"; +import { bigRock } from "./rocks"; const completed = () => { const turncount = myTurncount(); @@ -64,6 +66,8 @@ function getQuest(): ChronerQuest { return { ...rose, completed: completed() }; case "future": return { ...future, completed: completed() }; + case "rock": + return { ...bigRock, completed: completed() }; case "soup": return { ...soup, completed: completed() }; default: @@ -124,7 +128,10 @@ export function main(command?: string) { }, { name: "Clara's Bell", - completed: () => !have($item`Clara's bell`) || get("_claraBellUsed"), + completed: () => + !have($item`Clara's bell`) || + get("_claraBellUsed") || + get("noncombatForcerActive"), do: () => { use($item`Clara's bell`); }, @@ -255,6 +262,10 @@ export function main(command?: string) { }, { name: "Time Capsule", + ready: () => + args.mode !== "rock" || + get("_questCaveDan", 0) > 4 || + get("lastCaveDanDefeat", 0) >= myAscensions(), do: () => { const turns = totalTurnsPlayed(); adv1($location`The Cave Before Time`, 0, ""); @@ -268,6 +279,7 @@ export function main(command?: string) { forced: true, sobriety: "either", completed: () => false, + choices: { 955: 2 }, combat: new ChronerStrategy(() => Macro.standardCombat()), }, { @@ -284,18 +296,17 @@ export function main(command?: string) { }, ), do: quest.location, - completed: () => false, + completed: () => get("noncombatForcerActive"), prepare: () => cliExecute("parka spikolodon"), - combat: new ChronerStrategy(() => - Macro.trySkill($skill`Launch spikolodon spikes`).standardCombat(), - ), + combat: new ChronerStrategy(() => Macro.spikes().standardCombat()), sobriety: "sober", }, { name: "Bowling Ball Run", ready: () => get("cosmicBowlingBallReturnCombats") < 1 && - get("hasCosmicBowlingBall"), + get("hasCosmicBowlingBall") && + !get("noncombatForcerActive"), do: $location`The Cave Before Time`, sobriety: "sober", completed: () => false, @@ -311,7 +322,7 @@ export function main(command?: string) { }, { name: "Asdon Bumper", - ready: () => AsdonMartin.installed(), + ready: () => AsdonMartin.installed() && !get("noncombatForcerActive"), completed: () => get("banishedMonsters").includes("Spring-Loaded Front Bumper"), sobriety: "sober", @@ -328,7 +339,7 @@ export function main(command?: string) { }, { name: "Asdon Missile", - ready: () => AsdonMartin.installed(), + ready: () => AsdonMartin.installed() && !get("noncombatForcerActive"), completed: () => get("_missileLauncherUsed"), combat: new ChronerStrategy(() => { const romance = get("romanticTarget"); @@ -348,7 +359,9 @@ export function main(command?: string) { name: "Spit Jurassic Acid", completed: () => have($effect`Everything Looks Yellow`), ready: () => - have($item`Jurassic Parka`) && have($skill`Torso Awareness`), + have($item`Jurassic Parka`) && + have($skill`Torso Awareness`) && + !get("noncombatForcerActive"), outfit: () => chooseQuestOutfit( { location: yrTarget, isFree: true }, diff --git a/src/outfit.ts b/src/outfit.ts index 9614dfc..e119fa3 100644 --- a/src/outfit.ts +++ b/src/outfit.ts @@ -1,3 +1,6 @@ +import { freeFightFamiliar, MenuOptions } from "./familiar"; +import { garboAverageValue, garboValue } from "./garboValue"; +import { args, maxBy, realmAvailable, sober } from "./lib"; import { OutfitSlot, OutfitSpec } from "grimoire-kolmafia"; import { canEquip, @@ -19,10 +22,6 @@ import { sumNumbers, } from "libram"; -import { freeFightFamiliar, MenuOptions } from "./familiar"; -import { garboAverageValue, garboValue } from "./garboValue"; -import { maxBy, realmAvailable, sober } from "./lib"; - export function ifHave( slot: OutfitSlot, item: Item, @@ -48,7 +47,8 @@ export function chooseQuestOutfit( { location, isFree }: TaskOptions, ...outfits: OutfitSpec[] ): OutfitSpec { - const familiar = chooseFamiliar({ location }); + const mergedInputSpec = mergeSpecs(...outfits); + const familiar = mergedInputSpec.familiar ?? chooseFamiliar({ location }); const famEquip = equipmentFamiliars.get(familiar) ?? (familiar.elementalDamage || familiar.physicalDamage @@ -179,6 +179,10 @@ const accessories = new Map number>([ [$item`lucky gold ring`, luckyGoldRing], [$item`Mr. Screege's spectacles`, () => 180], [$item`Mr. Cheeng's spectacles`, () => 220], + [ + $item`pro skateboard`, + () => (args.mode === "rock" && get("_questCaveDan", 0) === 5 ? 10000 : 0), + ], ]); function getBestAccessories(isFree?: boolean) { diff --git a/src/rocks.ts b/src/rocks.ts new file mode 100644 index 0000000..67f31e4 --- /dev/null +++ b/src/rocks.ts @@ -0,0 +1,213 @@ +import { ChronerQuest, ChronerStrategy } from "./engine"; +import Macro from "./macro"; +import { chooseQuestOutfit, ifHave } from "./outfit"; +import { + familiarWeight, + handlingChoice, + myAscensions, + runChoice, + toUrl, + use, + visitUrl, +} from "kolmafia"; +import { + $familiar, + $item, + $items, + $location, + get, + getKramcoWandererChance, + set, +} from "libram"; + +const location = $location`The Cave Before Time`; +export const bigRock: ChronerQuest = { + name: "Get Rock", + location, + tasks: [ + { + name: "Set properties after ascension", + ready: () => get("lastCaveDanPropertyReset", 0) !== myAscensions(), + completed: () => + myAscensions() <= get("lastCaveDanDefeat", 0) || + get("questCaveDan", 0) === 0, + do: () => { + set("questCaveDan", 0); + set("lastCaveDanPropertyReset", myAscensions()); + }, + sobriety: "either", + }, + { + name: "Inital Visit to Dan's Cave", + completed: () => + get("questCaveDan", 0) > 0 || + get("lastCaveDanDefeat", 0) >= myAscensions(), + do: () => { + visitUrl("place.php?whichplace=twitch&action=twitch_dancave1"); + set("questCaveDan", 1); + }, + sobriety: "sober", + }, + { + name: "Play Rock^3 with Ook the Mook", + after: ["Inital Visit to Dan's Cave"], + completed: () => + get("questCaveDan", 0) > 1 || + get("lastCaveDanDefeat", 0) >= myAscensions(), + do: () => { + visitUrl(toUrl(location)); + for (const choiceValue of [3, 1, 2, 1, 2]) { + runChoice(choiceValue); + } + if (get("lastEncounter") === "Ook the Mook") { + set("questCaveDan", 2); + } + }, + outfit: () => { + const sausageSpec = + getKramcoWandererChance() >= 1 + ? ifHave("offhand", $item`Kramco Sausage-o-Matic™`) + : {}; + return chooseQuestOutfit( + { location, isFree: getKramcoWandererChance() >= 1 }, + sausageSpec, + ); + }, + combat: new ChronerStrategy(() => Macro.standardCombat()), + forced: true, + sobriety: "sober", + }, + { + name: "Teach Ook about Paper", + after: ["Play Rock^3 with Ook the Mook"], + completed: () => + get("questCaveDan", 0) > 2 || + get("lastCaveDanDefeat", 0) >= myAscensions(), + do: () => { + visitUrl(toUrl(location)); + for (const choiceValue of [3, 1, 1, 2]) { + runChoice(choiceValue); + } + /* if (handlingChoice()) { + runChoice(3); // 955 - Go towards noise + runChoice(1); // 954 - Ask About Password + runChoice(1); // 954 - Offer to Trade + runChoice(2); // 954 - Teach Secret of Paper + } */ + if (get("lastEncounter") === "Ook the Mook") { + set("questCaveDan", 3); + } + }, + outfit: () => { + const sausageSpec = + getKramcoWandererChance() >= 1 + ? ifHave("offhand", $item`Kramco Sausage-o-Matic™`) + : {}; + return chooseQuestOutfit( + { location, isFree: getKramcoWandererChance() >= 1 }, + sausageSpec, + ); + }, + combat: new ChronerStrategy(() => Macro.standardCombat()), + forced: true, + sobriety: "sober", + }, + { + name: "Teach Ook about Scissors", + after: ["Teach Ook about Paper"], + completed: () => + get("questCaveDan", 0) > 3 || + get("lastCaveDanDefeat", 0) >= myAscensions(), + do: () => { + visitUrl(toUrl(location)); + for (const choiceValue of [3, 1, 1, 2]) { + runChoice(choiceValue); + } + if (get("lastEncounter") === "Ook the Mook") { + set("questCaveDan", 4); + } + }, + outfit: () => { + const sausageSpec = + getKramcoWandererChance() >= 1 + ? ifHave("offhand", $item`Kramco Sausage-o-Matic™`) + : {}; + return chooseQuestOutfit( + { location, isFree: getKramcoWandererChance() >= 1 }, + sausageSpec, + ); + }, + combat: new ChronerStrategy(() => Macro.standardCombat()), + forced: true, + sobriety: "sober", + }, + { + name: "Play RoShamBo to obtain Password", + after: ["Teach Ook about Scissors"], + completed: () => + get("questCaveDan", 0) > 4 || + get("lastCaveDanDefeat", 0) >= myAscensions(), + do: () => { + visitUrl(toUrl(location)); + if (handlingChoice()) { + runChoice(3); // 955 - Go towards noise + runChoice(1); // 954 - Ask About Password + runChoice(2); // 954 - Game of Chance + while (handlingChoice()) { + runChoice(1); // 954 - Alternating between picking Rock and continuing to play until you win. + } + } + if (get("lastEncounter") === "Ook the Mook") { + set("questCaveDan", 5); + } + }, + outfit: () => { + const sausageSpec = + getKramcoWandererChance() >= 1 + ? ifHave("offhand", $item`Kramco Sausage-o-Matic™`) + : {}; + return chooseQuestOutfit( + { location, isFree: getKramcoWandererChance() >= 1 }, + sausageSpec, + ); + }, + combat: new ChronerStrategy(() => Macro.standardCombat()), + forced: true, + sobriety: "sober", + }, + { + name: "Charge Goose", + after: ["Play RoShamBo to obtain Password"], + completed: () => + familiarWeight($familiar`Grey Goose`) >= 7 || + get("questCaveDan", 0) > 5 || + get("lastCaveDanDefeat", 0) >= myAscensions(), + do: () => { + use($item`Ghost Dog Chow`); + }, + outfit: { familiar: $familiar`Grey Goose` }, + sobriety: "sober", + limit: { tries: 5 }, + }, + { + name: "Fight CaveDan", + after: ["Charge Goose", "Play RoShamBo to obtain Password"], + completed: () => + get("questCaveDan", 0) > 5 || + get("lastCaveDanDefeat", 0) >= myAscensions(), + do: () => { + visitUrl("place.php?whichplace=twitch&action=twitch_dancave3"); + set("questCaveDan", 6); + set("lastCaveDanDefeat", myAscensions()); + }, + outfit: () => { + return chooseQuestOutfit( + { location }, + { familiar: $familiar`Grey Goose`, acc1: $items`pro skateboard` }, + ); + }, + combat: new ChronerStrategy(() => Macro.getRocks().standardCombat()), + sobriety: "sober", + }, + ], +};