diff --git a/web/src/content/overrides.ts b/web/src/content/overrides.ts index d87d4e0..65b2e61 100644 --- a/web/src/content/overrides.ts +++ b/web/src/content/overrides.ts @@ -86,6 +86,21 @@ const RULE_NAMES: Record = { * that will not withdraw fights a different fight from one that will, and * that is the player's business before they commit an attempt. */ +/** + * The five 0-10 dials a MegaMek behaviour is made of. + * + * Everything else in a `bot:` block - the retreat switches, the edge to run + * to - sits on top of a behaviour rather than being part of one, which is why + * a preset is matched on these alone. + */ +const DIALS = new Set([ + "hyperaggression", + "selfpreservation", + "herdmentality", + "bravery", + "fallshame", +]); + const AI_NAMES: Record = { hyperaggression: "Aggression", selfpreservation: "Self-preservation", @@ -147,10 +162,17 @@ export function allOverrides(raw: RawOverrides | undefined): Override[] { // bot to compare it against says nothing. const ai = (raw.ai ?? []).filter(({ knob }) => knob.toLowerCase() !== "type"); if (ai.length > 0) { + // The five dials are what a behaviour *is*; the retreat settings are + // separate switches on top of one. A scenario that flies MegaMek's Sniper + // but refuses to withdraw is still flying Sniper, and saying so is far + // more use to a player than five numbers and a flag - so the preset is + // matched on the dials alone and the flags are what is printed beside it. + const dials = ai.filter(({ knob }) => DIALS.has(knob.toLowerCase())); + const flags = ai.filter(({ knob }) => !DIALS.has(knob.toLowerCase())); const knobs = Object.fromEntries( - ai.map((a) => [a.knob.toLowerCase(), a.value]), + dials.map((a) => [a.knob.toLowerCase(), a.value]), ); - const named = presetNamed(knobs); + const named = dials.length > 0 ? presetNamed(knobs) : null; // "Forced withdrawal off", not "Forced withdrawal false". Every other // knob is a 0-10 dial and reads as one; the two flags are switches. const spelled = ai @@ -161,12 +183,21 @@ export function allOverrides(raw: RawOverrides | undefined): Override[] { return `${name} ${shown}`; }) .join(" · "); + const spelledFlags = flags + .map(({ knob, value }) => { + const name = AI_NAMES[knob.toLowerCase()] ?? knob; + const shown = + value === "true" ? "on" : value === "false" ? "off" : value; + return `${name} ${shown}`; + }) + .join(" · "); out.push({ kind: "ai", label: named ?? "Custom behaviour", - // A named behaviour needs no value beside it: the name is the whole - // fact. An unnamed one is spelled out instead. - value: named ? "" : spelled, + // A named behaviour carries only what it was changed by: the name is + // the rest of the fact, and the dials behind it are on hover. An + // unnamed one is spelled out in full. + value: named ? spelledFlags : spelled, unofficial: false, // Always worth saying: how the opposition plays is the single thing a // player most needs to know before committing an attempt.