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 { EncounterData, EnemyData, EffectData, IntentData } from '../types';
import type { RunState, GameItemMeta } from './types';
import type { GridInventory } from '../grid-inventory/types';
import type { RunState } from './types';
import { generateDeckFromInventory } from '../deck/factory';
import { ReadonlyRNG } from '@/utils/rng';
import { createRegion } from '@/core/region';
// -- Encounter assignment to nodes --
@ -77,10 +75,8 @@ export function assignAllEncounters(
export function buildCombatState(
encounter: EncounterData,
runState: RunState,
intentPool: IntentData[] = []
): CombatState {
const intentIndex = buildIntentIndex(intentPool);
const enemies = createEnemyEntities(encounter, intentIndex);
const enemies = createEnemyEntities(encounter);
const deck = generateDeckFromInventory(runState.inventory);
const player = createPlayerEntity(runState, deck);
@ -95,48 +91,34 @@ 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.
* 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(
encounter: EncounterData,
intentIndex: Map<string, IntentData>
): EnemyEntity[] {
const enemies: EnemyEntity[] = [];
let instanceCounter = 0;
for (const [enemyData, count, encounterBuffs] of encounter.enemies) {
for (let i = 0; i < count; i++) {
for (const [enemyData, hp, encounterBuffs] of encounter.enemies) {
const instanceId = `${enemyData.id}-${instanceCounter++}`;
const intents = buildIntentMap(enemyData, intentIndex);
const initialIntentId = findInitialIntent(enemyData, intentIndex);
const intents = buildIntentMap(enemyData);
const initialIntent = findInitialIntent(enemyData);
const effects = buildEffectTable(encounterBuffs);
const entity: EnemyEntity = {
id: instanceId,
enemy: enemyData,
hp: enemyData.hp,
maxHp: enemyData.hp,
hp,
maxHp: hp,
isAlive: true,
effects,
intents,
currentIntentId: initialIntentId,
currentIntent: initialIntent,
};
enemies.push(entity);
}
}
return enemies;
}
@ -146,14 +128,10 @@ export function createEnemyEntities(
*/
function buildIntentMap(
enemy: EnemyData,
intentIndex: Map<string, IntentData>
): Record<string, IntentData> {
const intents: Record<string, IntentData> = {};
for (const intentId of enemy.intentIds) {
const intent = intentIndex.get(intentId);
if (intent) {
intents[intentId] = intent;
}
for (const intent of enemy.intents) {
intents[intent.id] = intent;
}
return intents;
}
@ -161,18 +139,16 @@ function buildIntentMap(
/**
* Finds the initial intent ID for an enemy.
*/
function findInitialIntent(
enemy: EnemyData,
intentIndex: Map<string, IntentData>
): string {
for (const intentId of enemy.intentIds) {
const intent = intentIndex.get(intentId);
if (intent?.initialIntent) {
return intentId;
function findInitialIntent(enemy: EnemyData): IntentData {
for (const intent of enemy.intents) {
if (intent.initialIntent) {
return intent;
}
}
// Fallback: first intent
return enemy.intentIds[0] ?? '';
if (enemy.intents.length === 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 {
return {
id: "player",
hp: runState.player.currentHp,
maxHp: runState.player.maxHp,
isAlive: runState.player.currentHp > 0,
@ -224,13 +201,13 @@ export function isCombatEncounter(runState: RunState): boolean {
* Starts the encounter at the current node.
* 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);
if (!encounter || encounter.enemies.length === 0) {
return null;
}
return buildCombatState(encounter, runState, intentPool);
return buildCombatState(encounter, runState);
}
/**