fix: encounter generation

This commit is contained in:
hypercross 2026-04-17 15:45:52 +08:00
parent af0906561c
commit a80852bc59
1 changed files with 33 additions and 56 deletions

View File

@ -1,11 +1,9 @@
import type { PointCrawlMap, MapNode } from '../map/types'; import type { PointCrawlMap } from '../map/types';
import type { CombatState, EnemyEntity, PlayerEntity, EffectTable } from '../combat/types'; import type { CombatState, EnemyEntity, PlayerEntity, EffectTable } from '../combat/types';
import type { EncounterData, EnemyData, EffectData, IntentData } from '../types'; import type { EncounterData, EnemyData, EffectData, IntentData } from '../types';
import type { RunState, GameItemMeta } from './types'; import type { RunState } from './types';
import type { GridInventory } from '../grid-inventory/types';
import { generateDeckFromInventory } from '../deck/factory'; import { generateDeckFromInventory } from '../deck/factory';
import { ReadonlyRNG } from '@/utils/rng'; import { ReadonlyRNG } from '@/utils/rng';
import { createRegion } from '@/core/region';
// -- Encounter assignment to nodes -- // -- Encounter assignment to nodes --
@ -77,10 +75,8 @@ export function assignAllEncounters(
export function buildCombatState( export function buildCombatState(
encounter: EncounterData, encounter: EncounterData,
runState: RunState, runState: RunState,
intentPool: IntentData[] = []
): CombatState { ): CombatState {
const intentIndex = buildIntentIndex(intentPool); const enemies = createEnemyEntities(encounter);
const enemies = createEnemyEntities(encounter, intentIndex);
const deck = generateDeckFromInventory(runState.inventory); const deck = generateDeckFromInventory(runState.inventory);
const player = createPlayerEntity(runState, deck); const player = createPlayerEntity(runState, deck);
@ -95,47 +91,33 @@ export function buildCombatState(
}; };
} }
/**
* Builds an index of intents by their ID for fast lookup.
*/
function buildIntentIndex(intentPool: IntentData[]): Map<string, IntentData> {
const index = new Map<string, IntentData>();
for (const intent of intentPool) {
index.set(intent.id, intent);
}
return index;
}
/** /**
* Creates EnemyEntity instances from encounter enemy definitions. * Creates EnemyEntity instances from encounter enemy definitions.
* Each enemy gets: initial HP from enemy data, initial buffs from encounter, intents from enemy definition. * Each enemy gets: HP from encounter tuple, initial buffs from encounter, intents from enemy definition.
*/ */
export function createEnemyEntities( export function createEnemyEntities(
encounter: EncounterData, encounter: EncounterData,
intentIndex: Map<string, IntentData>
): EnemyEntity[] { ): EnemyEntity[] {
const enemies: EnemyEntity[] = []; const enemies: EnemyEntity[] = [];
let instanceCounter = 0; let instanceCounter = 0;
for (const [enemyData, count, encounterBuffs] of encounter.enemies) { for (const [enemyData, hp, encounterBuffs] of encounter.enemies) {
for (let i = 0; i < count; i++) { const instanceId = `${enemyData.id}-${instanceCounter++}`;
const instanceId = `${enemyData.id}-${instanceCounter++}`; const intents = buildIntentMap(enemyData);
const intents = buildIntentMap(enemyData, intentIndex); const initialIntent = findInitialIntent(enemyData);
const initialIntentId = findInitialIntent(enemyData, intentIndex); const effects = buildEffectTable(encounterBuffs);
const effects = buildEffectTable(encounterBuffs);
const entity: EnemyEntity = { const entity: EnemyEntity = {
id: instanceId, id: instanceId,
enemy: enemyData, enemy: enemyData,
hp: enemyData.hp, hp,
maxHp: enemyData.hp, maxHp: hp,
isAlive: true, isAlive: true,
effects, effects,
intents, intents,
currentIntentId: initialIntentId, currentIntent: initialIntent,
}; };
enemies.push(entity); enemies.push(entity);
}
} }
return enemies; return enemies;
@ -146,14 +128,10 @@ export function createEnemyEntities(
*/ */
function buildIntentMap( function buildIntentMap(
enemy: EnemyData, enemy: EnemyData,
intentIndex: Map<string, IntentData>
): Record<string, IntentData> { ): Record<string, IntentData> {
const intents: Record<string, IntentData> = {}; const intents: Record<string, IntentData> = {};
for (const intentId of enemy.intentIds) { for (const intent of enemy.intents) {
const intent = intentIndex.get(intentId); intents[intent.id] = intent;
if (intent) {
intents[intentId] = intent;
}
} }
return intents; return intents;
} }
@ -161,18 +139,16 @@ function buildIntentMap(
/** /**
* Finds the initial intent ID for an enemy. * Finds the initial intent ID for an enemy.
*/ */
function findInitialIntent( function findInitialIntent(enemy: EnemyData): IntentData {
enemy: EnemyData, for (const intent of enemy.intents) {
intentIndex: Map<string, IntentData> if (intent.initialIntent) {
): string { return intent;
for (const intentId of enemy.intentIds) {
const intent = intentIndex.get(intentId);
if (intent?.initialIntent) {
return intentId;
} }
} }
// Fallback: first intent if (enemy.intents.length === 0) {
return enemy.intentIds[0] ?? ''; throw new Error(`Enemy "${enemy.id}" has no intents`);
}
return enemy.intents[0];
} }
/** /**
@ -191,6 +167,7 @@ function buildEffectTable(buffs: readonly [EffectData, number][]): EffectTable {
*/ */
function createPlayerEntity(runState: RunState, deck: ReturnType<typeof generateDeckFromInventory>): PlayerEntity { function createPlayerEntity(runState: RunState, deck: ReturnType<typeof generateDeckFromInventory>): PlayerEntity {
return { return {
id: "player",
hp: runState.player.currentHp, hp: runState.player.currentHp,
maxHp: runState.player.maxHp, maxHp: runState.player.maxHp,
isAlive: runState.player.currentHp > 0, isAlive: runState.player.currentHp > 0,
@ -224,13 +201,13 @@ export function isCombatEncounter(runState: RunState): boolean {
* Starts the encounter at the current node. * Starts the encounter at the current node.
* Returns the constructed CombatState, or null if no combat encounter. * Returns the constructed CombatState, or null if no combat encounter.
*/ */
export function startEncounter(runState: RunState, intentPool: IntentData[] = []): CombatState | null { export function startEncounter(runState: RunState): CombatState | null {
const encounter = getCurrentEncounterData(runState); const encounter = getCurrentEncounterData(runState);
if (!encounter || encounter.enemies.length === 0) { if (!encounter || encounter.enemies.length === 0) {
return null; return null;
} }
return buildCombatState(encounter, runState, intentPool); return buildCombatState(encounter, runState);
} }
/** /**