Feature: enhance encounter resolution with creature names and result labels

This commit is contained in:
Keith Solomon
2026-03-15 13:11:28 -05:00
parent 1ff20723ec
commit 39703ce6b0
5 changed files with 196 additions and 1 deletions

110
src/rules/encounters.ts Normal file
View File

@@ -0,0 +1,110 @@
import { findTableByCode } from "@/data/contentHelpers";
import type { ContentPack } from "@/types/content";
import type { EncounterState, RoomState } from "@/types/state";
import { lookupTable, type TableLookupResult } from "./tables";
import type { DiceRoller } from "./dice";
export type EncounterResolutionResult = {
room: RoomState;
encounter?: EncounterState;
lookup?: TableLookupResult;
};
const ROOM_HINT_TO_TABLE: Array<{ pattern: RegExp; tableCode: string }> = [
{ pattern: /\bdogs?\b/i, tableCode: "L1D" },
{ pattern: /\bguards?\b/i, tableCode: "L1G" },
{ pattern: /\bmartial\b/i, tableCode: "L1M" },
{ pattern: /\bworkers?\b/i, tableCode: "L1W" },
{ pattern: /\blabourers?\b/i, tableCode: "L1P" },
{ pattern: /\bpreacher\b/i, tableCode: "L1P" },
{ pattern: /\banimals?\b/i, tableCode: "L1A" },
{ pattern: /\bsnakes?\b/i, tableCode: "L1A" },
{ pattern: /\bfungal\b/i, tableCode: "L1F" },
{ pattern: /\bslimy\b/i, tableCode: "L1F" },
{ pattern: /\bcrate\b/i, tableCode: "L1CE" },
];
function splitEncounterLabel(label: string) {
return label
.split(/\sand\s/i)
.map((part) => part.trim())
.filter(Boolean);
}
function inferEncounterTableCode(room: RoomState) {
const noteText = room.notes.join(" ");
const flagText = room.flags.join(" ");
const combinedText = `${noteText} ${flagText}`;
for (const mapping of ROOM_HINT_TO_TABLE) {
if (mapping.pattern.test(combinedText)) {
return mapping.tableCode;
}
}
return undefined;
}
function isNonEncounterRoom(room: RoomState) {
const noteText = room.notes.join(" ").toLowerCase();
return noteText.includes("nothing here") || noteText.includes("no encounter");
}
export function resolveRoomEncounter(
content: ContentPack,
room: RoomState,
roller?: DiceRoller,
): EncounterResolutionResult {
if (room.encounter?.resolved) {
return {
room,
encounter: room.encounter,
};
}
if (isNonEncounterRoom(room)) {
const emptyEncounter: EncounterState = {
id: `${room.id}.encounter`,
creatureIds: [],
creatureNames: [],
resultLabel: "No encounter",
resolved: true,
};
return {
room: {
...room,
encounter: emptyEncounter,
},
encounter: emptyEncounter,
};
}
const tableCode = inferEncounterTableCode(room);
if (!tableCode) {
return { room };
}
const table = findTableByCode(content, tableCode);
const lookup = lookupTable(table, { roller });
const creatureNames = splitEncounterLabel(lookup.entry.label);
const encounter: EncounterState = {
id: `${room.id}.encounter`,
sourceTableCode: tableCode,
creatureIds: creatureNames.map((_, index) => `${room.id}.creature.${index + 1}`),
creatureNames,
resultLabel: lookup.entry.label,
resolved: true,
};
return {
room: {
...room,
encounter,
},
encounter,
lookup,
};
}