diff --git a/src/main.ts b/src/main.ts index 4bac5e5..7d91043 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, @@ -68,22 +68,24 @@ export let test = false; export let pathpower = 0; export let owned = false; -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; @@ -140,7 +142,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)); @@ -158,15 +162,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 ff09c69..eadffb5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,6 @@ import { beretBuskingEffects, + booleanModifier, canEquip, Effect, getPower, @@ -25,7 +26,6 @@ import { have as have_, logger, maxBy, - NumericModifier, sum, } from "libram"; import { @@ -80,7 +80,7 @@ function multipliers(slot: Slot): number { export function printBuskResult( result: BuskResult | null, - modifiers: Modifier[], + modifiers: Map, desiredEffects: Effect[] = [] ): void { if (!result) { @@ -101,14 +101,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); } @@ -127,8 +129,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}:`); @@ -137,11 +139,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}`); } @@ -149,7 +151,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) @@ -175,14 +177,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) => { @@ -207,7 +209,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) => { @@ -215,10 +217,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)); }; } @@ -232,18 +231,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[]; @@ -324,7 +320,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 @@ -429,3 +425,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; +}