Files
healer-man/src/game/dungeonEncounterTypes.ts
T
2026-09-09 11:21:33 -04:00

195 lines
8.4 KiB
TypeScript

import type { Vector3Tuple } from "./dungeonTypes";
// Version 2 preserves consumed action execution order so durable state can be
// reconstructed without reviving attempt-scoped object/flag overwrites.
export const DUNGEON_ENCOUNTER_SNAPSHOT_SCHEMA_VERSION = 2;
export type DungeonEncounterResetScope = "boss-wipe" | "checkpoint" | "dungeon";
export type DungeonEncounterValue = string | number | boolean;
export interface DungeonEncounterFacts {
readonly registeredEntityIds?: readonly string[];
readonly deadEntityIds?: readonly string[];
}
export type DungeonEncounterEvent =
| {
readonly kind: "tick";
readonly now: number;
readonly facts?: DungeonEncounterFacts;
}
| {
readonly kind: "signal";
readonly now: number;
readonly signalId: string;
readonly sourceEntityId?: string;
}
| { readonly kind: "entity-engaged"; readonly now: number; readonly entityId: string }
| { readonly kind: "entity-death"; readonly now: number; readonly entityId: string }
| {
readonly kind: "entity-health";
readonly now: number;
readonly entityId: string;
readonly healthPercent: number;
}
| {
readonly kind: "interact";
readonly now: number;
readonly objectId: string;
readonly actorId?: string;
};
export type DungeonEncounterTrigger =
| { readonly kind: "tick" }
| { readonly kind: "signal"; readonly signalId: string }
| { readonly kind: "entity-engaged"; readonly entityId: string }
| { readonly kind: "entity-death"; readonly entityId: string }
| {
readonly kind: "entity-health";
readonly entityId: string;
readonly atOrBelowPercent: number;
}
| { readonly kind: "interact"; readonly objectId: string }
| { readonly kind: "elapsed"; readonly afterMs: number }
| {
readonly kind: "entities-dead";
readonly entityIds: readonly string[];
/**
* Missing-actor recovery window. It may advance presentation-only rules
* when no listed target is registered alive, but can never award an
* objective, checkpoint, loot, or dungeon completion.
*/
readonly timeoutMs?: number;
};
export type DungeonEncounterGuard =
| { readonly kind: "phase"; readonly phases: readonly string[] }
| { readonly kind: "difficulty"; readonly difficultyIds: readonly string[] }
| { readonly kind: "flag"; readonly flagId: string; readonly equals: boolean }
| {
readonly kind: "counter";
readonly counterId: string;
readonly comparison: "equal" | "at-least" | "at-most";
readonly value: number;
}
| { readonly kind: "objective-complete"; readonly objectiveId: string; readonly equals: boolean }
| { readonly kind: "spawn-group-active"; readonly spawnGroupId: string; readonly equals: boolean }
| { readonly kind: "world-object-state"; readonly objectId: string; readonly states: readonly string[] };
export type DungeonEncounterActionPersistence = "attempt" | "checkpoint" | "run";
interface DungeonEncounterActionBase {
/** Stable, encounter-local id used to make rule execution idempotent. */
readonly id: string;
readonly persistence?: DungeonEncounterActionPersistence;
}
export type DungeonEncounterAction =
| (DungeonEncounterActionBase & { readonly kind: "set-phase"; readonly phase: string })
| (DungeonEncounterActionBase & { readonly kind: "set-flag"; readonly flagId: string; readonly value: boolean })
| (DungeonEncounterActionBase & { readonly kind: "set-counter"; readonly counterId: string; readonly value: number })
| (DungeonEncounterActionBase & { readonly kind: "increment-counter"; readonly counterId: string; readonly amount?: number })
| (DungeonEncounterActionBase & { readonly kind: "start-timer"; readonly timerId: string; readonly durationMs: number })
| (DungeonEncounterActionBase & { readonly kind: "clear-timer"; readonly timerId: string })
| (DungeonEncounterActionBase & { readonly kind: "activate-spawn-group"; readonly spawnGroupId: string })
| (DungeonEncounterActionBase & { readonly kind: "deactivate-spawn-group"; readonly spawnGroupId: string })
| (DungeonEncounterActionBase & { readonly kind: "set-world-object-state"; readonly objectId: string; readonly state: string })
| (DungeonEncounterActionBase & { readonly kind: "set-transition-active"; readonly transitionId: string; readonly active: boolean })
| (DungeonEncounterActionBase & { readonly kind: "complete-objective"; readonly objectiveId: string })
| (DungeonEncounterActionBase & { readonly kind: "set-checkpoint"; readonly checkpointId: string })
| (DungeonEncounterActionBase & { readonly kind: "teleport-party"; readonly destination: Vector3Tuple })
| (DungeonEncounterActionBase & { readonly kind: "grant-loot"; readonly lootTableId: string })
| (DungeonEncounterActionBase & { readonly kind: "emit-signal"; readonly signalId: string });
export interface DungeonEncounterRule {
readonly id: string;
readonly trigger: DungeonEncounterTrigger;
readonly guards?: readonly DungeonEncounterGuard[];
readonly actions: readonly DungeonEncounterAction[];
/** Defaults to true. Repeatable rules must remain safe when evaluated again. */
readonly once?: boolean;
readonly priority?: number;
}
export interface DungeonEncounterInitialState {
readonly flags?: Readonly<Record<string, boolean>>;
readonly counters?: Readonly<Record<string, number>>;
readonly worldObjectStates?: Readonly<Record<string, string>>;
readonly transitionStates?: Readonly<Record<string, boolean>>;
}
export interface DungeonEncounterResetPolicy {
readonly bossWipePhase?: string;
readonly checkpointPhase?: string;
}
export type DungeonEncounterFidelity =
| "source-exact"
| "source-backed-adapted"
| "healer-man-authored";
export type DungeonEncounterScope = "boss-progression" | "instance-orchestration";
/**
* Review record kept beside executable encounter data. Adapted behavior must
* remain explicit so a safe approximation is never mistaken for a finished
* port of the original instance script.
*/
export interface DungeonEncounterSourceReview {
readonly fidelity: DungeonEncounterFidelity;
readonly scope: DungeonEncounterScope;
readonly files: readonly string[];
readonly reviewedBehavior: string;
readonly adaptations: readonly string[];
readonly remainingGaps: readonly string[];
}
export interface DungeonEncounterDefinition {
readonly id: string;
readonly dungeonId: string;
readonly initialPhase: string;
readonly initialState?: DungeonEncounterInitialState;
readonly resetPolicy?: DungeonEncounterResetPolicy;
readonly source?: DungeonEncounterSourceReview;
readonly rules: readonly DungeonEncounterRule[];
}
/** JSON-safe source of truth; renderer objects and callbacks never enter this state. */
export interface DungeonEncounterSnapshot {
readonly schemaVersion: typeof DUNGEON_ENCOUNTER_SNAPSHOT_SCHEMA_VERSION;
readonly encounterId: string;
readonly phase: string;
readonly phaseStartedAt: number;
readonly flags: Readonly<Record<string, boolean>>;
readonly counters: Readonly<Record<string, number>>;
readonly timers: Readonly<Record<string, number>>;
readonly worldObjectStates: Readonly<Record<string, string>>;
readonly transitionStates: Readonly<Record<string, boolean>>;
readonly activeSpawnGroupIds: readonly string[];
readonly completedObjectiveIds: readonly string[];
readonly consumedActionIds: readonly string[];
readonly checkpointId: string | null;
readonly revision: number;
}
export type DungeonEncounterEffect =
| { readonly kind: "spawn-group"; readonly actionId: string; readonly spawnGroupId: string }
| { readonly kind: "despawn-group"; readonly actionId: string; readonly spawnGroupId: string }
| { readonly kind: "world-object-state"; readonly actionId: string; readonly objectId: string; readonly state: string }
| { readonly kind: "transition-state"; readonly actionId: string; readonly transitionId: string; readonly active: boolean }
| { readonly kind: "party-teleport"; readonly actionId: string; readonly destination: Vector3Tuple }
| { readonly kind: "loot-grant"; readonly actionId: string; readonly lootTableId: string }
| { readonly kind: "signal"; readonly actionId: string; readonly signalId: string };
export interface DungeonEncounterReduction {
readonly snapshot: DungeonEncounterSnapshot;
readonly effects: readonly DungeonEncounterEffect[];
readonly firedRuleIds: readonly string[];
}
export interface DungeonEncounterEffectEnvelope {
readonly dungeonId: string;
readonly encounterId: string;
readonly effect: DungeonEncounterEffect;
}