diff --git a/src/main.ts b/src/main.ts index 2efc055..2c20ff9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ import { Args } from "grimoire-kolmafia"; import { Effect, Modifier, myPath, print, toEffect, toModifier } from "kolmafia"; -import { $effects, $path, get, have, NumericModifier, sinceKolmafiaRevision } from "libram"; +import { $effects, $path, get, have, sinceKolmafiaRevision } from "libram"; import { findOptimalOutfitPower, hybridEffectValuer, @@ -63,22 +63,24 @@ export let hammertime = false; export let test = false; export let pathpower = 0; -function parseWeightedModifiers(input: string): Partial> { - if (!input.trim()) return {}; +function parseWeightedModifiers(input: string): Map { + if (!input.trim()) return new Map(); - const result: Partial> = {}; + const result = new Map(); const parts = input.split(",").map((s) => s.trim()); for (const part of parts) { // Try to match weighted, e.g. "5 Meat Drop" - const weightedMatch = part.match(/^(\d+)\s+(.+)$/); + const weightedMatch = part.match(/^(-)?(\d+)?\s*(.+)$/); if (weightedMatch) { - const weight = Number(weightedMatch[1]); - const modifierName = weightedMatch[2].trim() as NumericModifier; - result[modifierName] = weight; - } else { - // Default weight 1 for singular modifier e.g. "Meat Drop" - result[part as NumericModifier] = 1; + const sign = weightedMatch[1] === "-" ? -1 : 1; + const weight = weightedMatch[2] === undefined ? 1 : Number(weightedMatch[2]); + const modifier = toModifier(weightedMatch[3].trim()); + if (modifier.type === "numeric" || modifier.type === "boolean") { + result.set(modifier, sign * weight); + } else { + print(`Error: modifier '${weightedMatch[3]}' not numeric or boolean`); + } } } return result; @@ -131,7 +133,9 @@ export function main(command?: string): void { if (args.effects !== Effect.none.name || args.modifiers !== Modifier.none.name) { const desiredEffects = args.effects !== Effect.none.name ? parseEffects(args.effects) : []; const weightedModifiers = - args.modifiers !== Modifier.none.name ? parseWeightedModifiers(args.modifiers) : {}; + args.modifiers !== Modifier.none.name + ? parseWeightedModifiers(args.modifiers) + : new Map(); const valuerFn = normalizeEffectValuer(hybridEffectValuer(desiredEffects, weightedModifiers)); @@ -149,15 +153,11 @@ export function main(command?: string): void { print( `Hybrid strategy: prioritizing effects [${desiredEffects .map((e) => e.name) - .join(", ")}], fallback modifiers [${Object.entries(weightedModifiers) + .join(", ")}], fallback modifiers [${[...weightedModifiers.entries()] .map(([m, w]) => `${w}×${m}`) .join(", ")}]` ); - printBuskResult( - result, - Object.keys(weightedModifiers).map((mod) => toModifier(mod)), - desiredEffects - ); + printBuskResult(result, weightedModifiers, desiredEffects); } } diff --git a/src/utils.ts b/src/utils.ts index ff71887..56914c1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,6 @@ import { beretBuskingEffects, + booleanModifier, buy, canEquip, Effect, @@ -26,7 +27,6 @@ import { have as have_, logger, maxBy, - NumericModifier, sum, } from "libram"; import { checkhatrack, hammertime, inHatPath, othermodifiers, pathpower, test } from "./main"; @@ -73,7 +73,7 @@ function multipliers(slot: Slot): number { export function printBuskResult( result: BuskResult | null, - modifiers: Modifier[], + modifiers: Map, desiredEffects: Effect[] = [] ): void { if (!result) { @@ -94,14 +94,16 @@ export function printBuskResult( $modifier`Familiar Experience`, ]; + const modKeys = [...modifiers.keys()]; + for (const { effects, daRaw, buskIndex } of bestBusks) { const desiredMatches = effects.filter((e) => desiredEffects.includes(e)); print(`Power ${daRaw} Busk ${buskIndex + 1}`); // Calculate total buff per weighted modifier const weightedTotals = new Map(); - for (const mod of modifiers) { - const total = sum(effects, (ef) => numericModifier(ef, mod)); + for (const mod of modKeys) { + const total = sum(effects, (ef) => modifier(ef, mod)); weightedTotals.set(mod, total); } @@ -120,8 +122,8 @@ export function printBuskResult( print(`Total buffs: ${totalBuffsStr}`); // For each weighted modifier, print contributing effects - for (const mod of modifiers) { - const contributingEffects = effects.filter((e) => numericModifier(e, mod) > 0); + for (const mod of modKeys) { + const contributingEffects = effects.filter((e) => modifier(e, mod) * modifiers.get(mod)! > 0); if (contributingEffects.length === 0) continue; print(`${mod.name}:`); @@ -130,11 +132,11 @@ export function printBuskResult( // Optionally print other useful modifiers if args.othermodifiers is true if (othermodifiers) { - const otherMods = otherModifiersList.filter((mod) => !modifiers.includes(mod)); + const otherMods = otherModifiersList.filter((mod) => !modKeys.includes(mod)); if (otherMods.length > 0) { print(`Other Useful Modifiers:`); for (const mod of otherMods) { - const total = sum(effects, (ef) => numericModifier(ef, mod)); + const total = sum(effects, (ef) => modifier(ef, mod)); if (total > 0) { print(` ${mod.name}: ${total}`); } @@ -142,7 +144,7 @@ export function printBuskResult( } } const usefulEffects = effects.filter((e) => - modifiers.some((mod) => numericModifier(e, mod) > 0) + modKeys.some((mod) => modifier(e, mod) * modifiers.get(mod)! > 0) ); const otherEffects = effects.filter( (e) => !desiredEffects.includes(e) && !usefulEffects.includes(e) @@ -168,14 +170,14 @@ export function printBuskResult( export function makeBuskResultFromPowers( powers: number[], - weightedModifiers: Partial>, + weightedModifiers: Map, uselessEffects: Effect[], buskStartIndex = get("_beretBuskingUses", 0) ): BuskResult { // eslint-disable-next-line @typescript-eslint/no-unused-vars const effectValuer = (effect: Effect, _duration: number) => - Object.entries(weightedModifiers) - .map(([mod, weight]) => (weight ?? 0) * numericModifier(effect, mod)) + [...weightedModifiers.entries()] + .map(([mod, weight]) => (weight ?? 0) * modifier(effect, mod)) .reduce((a, b) => a + b, 0); const busks: Busk[] = powers.map((power, index) => { @@ -200,7 +202,7 @@ export function makeBuskResultFromPowers( export function hybridEffectValuer( desiredEffects: Effect[], - weightedModifiers: Partial> + weightedModifiers: Map ): (effect: Effect, duration: number, all?: [Effect, number][]) => number { const wantedSet = new Set(desiredEffects); return (effect, duration) => { @@ -208,10 +210,7 @@ export function hybridEffectValuer( // Strongly prioritize desired effects return 1e6 + duration; // Big constant ensures it's preferred } - return sum( - Object.entries(weightedModifiers), - ([mod, weight]) => weight * numericModifier(effect, mod) - ); + return sum([...weightedModifiers.entries()], ([mod, weight]) => weight * modifier(effect, mod)); }; } @@ -225,18 +224,15 @@ export function normalizeEffectValuer( const set = new Set(valuer); return (effect, duration) => (set.has(effect) ? duration : 0); } else { - // valuer is a weighted modifier object + // valuer is a map // eslint-disable-next-line @typescript-eslint/no-unused-vars return (effect, _duration) => - sum( - Object.entries(valuer), - ([modifier, weight]) => weight * numericModifier(effect, modifier) - ); + sum([...valuer.entries()], ([mod, weight]) => weight * modifier(effect, mod)); } } export type EffectValuer = - | Partial> + | Map | ((effect: Effect, duration: number) => number) | Effect[]; @@ -317,7 +313,7 @@ export function findOptimalOutfitPower( * @returns The power-sum at which you'll find the optimal busk for this situation. */ export function findOptimalOutfitPower( - weightedModifiers: Partial>, + weightedModifiers: Map, buskUses?: number, uselessEffects?: Effect[], buyItem?: boolean @@ -426,3 +422,13 @@ export function findOutfit(power: number, buyItem: boolean) { } return outfit; } + +function modifier(effect: Effect, mod: Modifier): number { + if (mod.type === "numeric") { + return numericModifier(effect, mod); + } else if (mod.type === "boolean") { + return booleanModifier(effect, mod) ? 1 : 0; + } + // we limit to numeric or boolean modifiers when parsing + return 0; +}