diff --git a/src/familiar/freeFightFamiliar.ts b/src/familiar/freeFightFamiliar.ts index 322b35ac5..9a96da43a 100644 --- a/src/familiar/freeFightFamiliar.ts +++ b/src/familiar/freeFightFamiliar.ts @@ -1,5 +1,15 @@ -import { Familiar, familiarWeight, inebrietyLimit, Location, myInebriety } from "kolmafia"; -import { $familiar, $item, $location, clamp, findLeprechaunMultiplier, get, have } from "libram"; +import { Familiar, familiarWeight, inebrietyLimit, Location, Monster, myInebriety } from "kolmafia"; +import { + $familiar, + $item, + $location, + $phylum, + clamp, + findLeprechaunMultiplier, + get, + have, + Snapper, +} from "libram"; import { canOpenRedPresent } from "."; import { garboValue } from "../value"; import getConstantValueFamiliars from "./constantValueFamiliars"; @@ -7,6 +17,7 @@ import getDropFamiliars from "./dropFamiliars"; import getExperienceFamiliars from "./experienceFamiliars"; import { GeneralFamiliar, timeToMeatify } from "./lib"; import { meatFamiliar } from "./meatFamiliar"; +import { barfEncounterRate } from "../lib"; type MenuOptions = { canChooseMacro?: boolean; @@ -69,6 +80,25 @@ export function menu(options: MenuOptions = {}): GeneralFamiliar[] { limit: "special", }); } + if ( + location === $location`Barf Mountain` && + Snapper.have() && + Snapper.getTrackedPhylum() === $phylum`dude` + ) { + const encounterRate = barfEncounterRate({ snapper: true, snapperPhylum: $phylum`dude` }); + const dudeRate = [...encounterRate.entries()].reduce( + (acc: number, entry: [Monster, number]) => + entry[0].phylum === $phylum`dude` ? entry[1] + acc : acc, + 0, + ); + + familiarMenu.push({ + familiar: $familiar`Red-Nosed Snapper`, + expectedValue: (dudeRate * garboValue($item`human musk`)) / 11, + leprechaunMultiplier: 0, + limit: "none", + }); + } } const meatFam = meatFamiliar(); diff --git a/src/fights.ts b/src/fights.ts index 73c965adb..a6ef1f25e 100644 --- a/src/fights.ts +++ b/src/fights.ts @@ -106,6 +106,7 @@ import { Requirement, Robortender, set, + Snapper, SourceTerminal, sum, tryFindFreeRun, @@ -997,7 +998,9 @@ const freeFightSources = [ new FreeFight( () => (wantPills() ? 5 - get("_saberForceUses") : 0), () => { - if (have($familiar`Red-Nosed Snapper`)) cliExecute(`snapper ${$phylum`dude`}`); + if (Snapper.have() && Snapper.getTrackedPhylum() !== $phylum`dude`) { + Snapper.trackPhylum($phylum`dude`); + } setChoice(1387, 3); if ( have($skill`Comprehensive Cartography`) && diff --git a/src/lib.ts b/src/lib.ts index b64e0b280..cbc692030 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -34,6 +34,7 @@ import { mySoulsauce, myTurncount, numericModifier, + Phylum, print, printHtml, restoreHp, @@ -60,6 +61,8 @@ import { $item, $location, $monster, + $monsters, + $phylum, $skill, $slot, ActionSource, @@ -80,6 +83,7 @@ import { PropertiesManager, property, set, + Snapper, SongBoom, sum, uneffect, @@ -684,4 +688,81 @@ export function printEventLog(): void { } } +const rateCache = new Map>(); + +export function barfEncounterRate(options: { + snapper?: boolean; + snapperPhylum?: Phylum; + olfact?: Monster; + longCon?: Monster; + motif?: Monster; + turtle?: Monster; + monkeyPoint?: Monster; + humanity?: boolean; +}): Map { + const olfact = options.olfact ?? get("olfactedMonster"); + const longCon = options.longCon ?? get("longConMonster"); + const motif = options.motif ?? get("motifMonster"); + const turtle = options.turtle ?? get("_gallapagosMonster"); + const monkeyPoint = options.monkeyPoint ?? get("monkeyPointMonster"); + const snapper = options.snapper ?? myFamiliar() === $familiar`Red-Nosed Snapper`; + const snapperPhylum = options.snapperPhylum ?? Snapper.getTrackedPhylum(); + const humanity = options.humanity ?? have($effect`Ew, The Humanity`); + + const zoneMonsters = $monsters`garbage tourist, angry tourist, horrible tourist family`; + const encounterQueue = zoneMonsters.filter((m) => + $location`Barf Mountain`.combatQueue.includes(`${m}`), + ); + + const cacheKey = [ + olfact, + longCon, + motif, + turtle, + monkeyPoint, + snapper, + snapperPhylum, + humanity, + ...encounterQueue, + ] + .map((v) => `${v}`) + .join(":"); + + const cachedValue = rateCache.get(cacheKey); + if (cachedValue) { + return cachedValue; + } + + const copies = (target: Monster | null, n: number): Monster[] => + n === 0 ? [] : [...zoneMonsters.filter((m) => m === target), ...copies(target, n - 1)]; + + const monsterQueue = [ + ...zoneMonsters, + ...copies(olfact, 3), + ...copies(longCon, 3), + ...copies(motif, 2), + ...copies(turtle, 1), + ...copies(monkeyPoint, 2), + ...zoneMonsters + .filter((m) => snapper && m.phylum === snapperPhylum) + .flatMap((m) => copies(m, 2)), + ...zoneMonsters + .filter((m) => humanity && m.phylum === $phylum`dude`) + .flatMap((m) => copies(m, 2)), + ]; + + const encounters = monsterQueue.flatMap((m) => + monsterQueue.map((n) => + // olfaction, longcon, and motif caus that monster to ignore queue rejection + olfact === m || longCon === m || motif === m || !encounterQueue.includes(m) ? m : n, + ), + ); + + const encounterRate = new Map( + zoneMonsters.map((m) => [m, encounters.filter((n) => n === m).length / encounters.length]), + ); + rateCache.set(cacheKey, encounterRate); + return encounterRate; +} + export const TREASURE_HOUSE_FAT_LOOT_TOKEN_COST = 20000; diff --git a/src/tasks/dailyFamiliars.ts b/src/tasks/dailyFamiliars.ts index 793cfdde4..1c5a49c3c 100644 --- a/src/tasks/dailyFamiliars.ts +++ b/src/tasks/dailyFamiliars.ts @@ -18,11 +18,13 @@ import { $familiars, $item, $items, + $phylum, CrimboShrub, get, have, Robortender, set, + Snapper, sum, withProperty, } from "libram"; @@ -227,6 +229,12 @@ const DailyFamiliarTasks: GarboTask[] = [ !!get("garbo_felizValue", 0) || today - get("garbo_felizValueDate", 0) < 24 * 60 * 60 * 1000, do: () => felizValue, }, + { + name: "Snapper Hunts Dudes", + ready: () => Snapper.have(), + completed: () => Snapper.getTrackedPhylum() === $phylum`dude`, + do: () => Snapper.trackPhylum($phylum`dude`), + }, ]; export const DailyFamiliarsQuest: Quest = {