refactor: adjust implementation details for combat

This commit is contained in:
hypercross 2026-04-16 21:52:28 +08:00
parent f7b59a1790
commit e3014e47a8
5 changed files with 78 additions and 98 deletions

View File

@ -375,7 +375,7 @@ function applyItemBuff(
sourceItemId: card.sourceItemId, sourceItemId: card.sourceItemId,
targetItemId: card.sourceItemId, targetItemId: card.sourceItemId,
}; };
state.itemBuffs.push(itemBuff); state.player.itemBuffs.push(itemBuff);
} }
function removeWoundCards(deck: PlayerDeck, count: number): void { function removeWoundCards(deck: PlayerDeck, count: number): void {
@ -436,9 +436,9 @@ export function getModifiedAttackDamage(
): number { ): number {
let damage = baseDamage; let damage = baseDamage;
const attackBuff = state.itemBuffs const attackBuff = state.player.itemBuffs
.filter(b => b.effectId === "attackBuff" || b.effectId === "attackBuffUntilPlay") .filter((b) => b.effectId === "attackBuff" || b.effectId === "attackBuffUntilPlay")
.filter(b => { .filter((b) => {
const card = state.player.deck.cards[cardId]; const card = state.player.deck.cards[cardId];
return card && card.sourceItemId === b.targetItemId; return card && card.sourceItemId === b.targetItemId;
}) })
@ -455,9 +455,9 @@ export function getModifiedDefendAmount(
): number { ): number {
let defend = baseDefend; let defend = baseDefend;
const defendBuff = state.itemBuffs const defendBuff = state.player.itemBuffs
.filter(b => b.effectId === "defendBuff" || b.effectId === "defendBuffUntilPlay") .filter((b) => b.effectId === "defendBuff" || b.effectId === "defendBuffUntilPlay")
.filter(b => { .filter((b) => {
const card = state.player.deck.cards[cardId]; const card = state.player.deck.cards[cardId];
return card && card.sourceItemId === b.targetItemId; return card && card.sourceItemId === b.targetItemId;
}) })
@ -521,7 +521,7 @@ function expireItemBuffsOnCardPlayed(state: CombatState, cardId: string): void {
const card = state.player.deck.cards[cardId]; const card = state.player.deck.cards[cardId];
if (!card || !card.sourceItemId) return; if (!card || !card.sourceItemId) return;
state.itemBuffs = state.itemBuffs.filter(buff => { state.player.itemBuffs = state.player.itemBuffs.filter((buff) => {
if (buff.timing === "itemUntilPlayed" && buff.sourceItemId === card.sourceItemId) { if (buff.timing === "itemUntilPlayed" && buff.sourceItemId === card.sourceItemId) {
return false; return false;
} }

View File

@ -1,7 +1,7 @@
import type { IGameContext } from "@/core/game"; import type { IGameContext } from "@/core/game";
import type { CombatState, CombatResult, CombatGameContext, CombatEffectEntry } from "./types"; import type { CombatState, CombatResult, CombatGameContext, CombatEffectEntry } from "./types";
import type { CombatTriggerRegistry, TriggerContext } from "./triggers"; import type { CombatTriggerRegistry, TriggerContext } from "./triggers";
import { createCombatTriggerRegistry, dispatchTrigger, dispatchShuffleTrigger, dispatchOutgoingDamageTrigger, dispatchIncomingDamageTrigger, dispatchDamageTrigger } from "./triggers"; import { createCombatTriggerRegistry, dispatchTrigger, dispatchShuffleTrigger, dispatchOutgoingDamageTrigger, dispatchIncomingDamageTrigger, dispatchDamageTrigger, dispatchCardDrawnTrigger } from "./triggers";
import { prompts } from "./prompts"; import { prompts } from "./prompts";
import { import {
drawCardsToHand, drawCardsToHand,
@ -30,8 +30,7 @@ export async function runCombat(
): Promise<CombatResult> { ): Promise<CombatResult> {
const triggerRegistry = createCombatTriggerRegistry(); const triggerRegistry = createCombatTriggerRegistry();
// TODO prefer await game.produceAsync over game.produce since produceAsync can await ui animations await game.produceAsync(async (state) => {
game.produce(state => {
state.phase = "playerTurn"; state.phase = "playerTurn";
state.player.energy = state.player.maxEnergy; state.player.energy = state.player.maxEnergy;
state.player.damageTakenThisTurn = 0; state.player.damageTakenThisTurn = 0;
@ -55,7 +54,7 @@ export async function runCombat(
} }
if (isPlayerDead(game.value)) { if (isPlayerDead(game.value)) {
game.produce(state => { await game.produceAsync((state) => {
state.result = "defeat"; state.result = "defeat";
state.phase = "combatEnd"; state.phase = "combatEnd";
}); });
@ -63,20 +62,13 @@ export async function runCombat(
} }
if (areAllEnemiesDead(game.value)) { if (areAllEnemiesDead(game.value)) {
game.produce(state => { await game.produceAsync((state) => {
state.result = "victory"; state.result = "victory";
state.phase = "combatEnd"; state.phase = "combatEnd";
state.loot = generateLoot(state); state.loot = generateLoot(state);
}); });
return "victory"; return "victory";
} }
if (game.value.result === "fled") {
game.produce(state => {
state.phase = "combatEnd";
});
return "fled";
}
} }
return game.value.result ?? "defeat"; return game.value.result ?? "defeat";
@ -88,26 +80,15 @@ async function runPlayerTurn(
): Promise<void> { ): Promise<void> {
const triggerCtx = createTriggerContext(game); const triggerCtx = createTriggerContext(game);
game.produce(state => { await game.produceAsync(async (state) => {
updateBuffs(state.player.buffs); updateBuffs(state.player.buffs);
state.player.damageTakenThisTurn = 0; state.player.damageTakenThisTurn = 0;
state.player.damagedThisTurn = false; state.player.damagedThisTurn = false;
state.player.cardsDiscardedThisTurn = 0; state.player.cardsDiscardedThisTurn = 0;
// TODO avoid hardcoding here, prefer handling these in either the onTurnStart trigger or the onBuffUpdate trigger dispatchTrigger(triggerCtx, "onTurnStart", "player", triggerRegistry);
if (state.player.buffs["energyNext"]) {
state.player.energy += state.player.buffs["energyNext"];
}
if (state.player.buffs["drawNext"]) {
drawCardsToHand(state.player.deck, state.player.buffs["drawNext"]);
}
if (state.player.buffs["defendNext"]) {
applyDefend(state.player.buffs, state.player.buffs["defendNext"]);
}
}); });
dispatchTrigger(triggerCtx, "onTurnStart", "player", triggerRegistry);
while (game.value.phase === "playerTurn") { while (game.value.phase === "playerTurn") {
const action = await game.prompt<{ action: "play" | "end"; cardId?: string; targetId?: string }>( const action = await game.prompt<{ action: "play" | "end"; cardId?: string; targetId?: string }>(
prompts.playCard, prompts.playCard,
@ -120,7 +101,7 @@ async function runPlayerTurn(
const card = state.player.deck.cards[cardId]; const card = state.player.deck.cards[cardId];
if (card?.itemData?.targetType === "single") { if (card?.itemData?.targetType === "single") {
const aliveEnemies = state.enemyOrder.filter(id => state.enemies[id].isAlive); const aliveEnemies = state.enemyOrder.filter((id) => state.enemies[id].isAlive);
if (!targetId && aliveEnemies.length > 0) { if (!targetId && aliveEnemies.length > 0) {
throw "请指定目标"; throw "请指定目标";
} }
@ -136,7 +117,7 @@ async function runPlayerTurn(
if (action.action === "play" && action.cardId) { if (action.action === "play" && action.cardId) {
const ctx = createEffectContext(game); const ctx = createEffectContext(game);
game.produce(state => { await game.produceAsync(async (state) => {
playCard({ state, rng: game._rng }, action.cardId!, action.targetId); playCard({ state, rng: game._rng }, action.cardId!, action.targetId);
}); });
@ -152,8 +133,7 @@ async function runPlayerTurn(
break; break;
} }
// TODO end is an alternative action to be taken by the player. await game.prompt<{ action: "end" }>(
const endAction = await game.prompt<{ action: "end" }>(
prompts.endTurn, prompts.endTurn,
() => { () => {
return { action: "end" as const }; return { action: "end" as const };
@ -163,23 +143,26 @@ async function runPlayerTurn(
dispatchTrigger(createTriggerContext(game), "onTurnEnd", "player", triggerRegistry); dispatchTrigger(createTriggerContext(game), "onTurnEnd", "player", triggerRegistry);
game.produce(state => { await game.produceAsync(async (state) => {
for (const cardId of [...state.player.deck.hand]) { for (const cardId of [...state.player.deck.hand]) {
state.player.cardsDiscardedThisTurn++; state.player.cardsDiscardedThisTurn++;
} }
discardHand(state.player.deck); discardHand(state.player.deck);
}); });
game.produce(state => { await game.produceAsync(async (state) => {
if (state.player.deck.drawPile.length === 0) { if (state.player.deck.drawPile.length === 0) {
reshuffleWithFatigue(state); reshuffleWithFatigue(state);
dispatchShuffleTrigger(createTriggerContext(game), triggerRegistry); dispatchShuffleTrigger(createTriggerContext(game), triggerRegistry);
} }
drawCardsToHand(state.player.deck, 5); const drawn = drawCardsToHand(state.player.deck, 5);
for (const cardId of drawn) {
dispatchCardDrawnTrigger(createTriggerContext(game), cardId, triggerRegistry);
}
state.player.energy = state.player.maxEnergy; state.player.energy = state.player.maxEnergy;
}); });
game.produce(state => { await game.produceAsync(async (state) => {
state.phase = "enemyTurn"; state.phase = "enemyTurn";
}); });
} }
@ -190,7 +173,7 @@ async function runEnemyTurn(
): Promise<void> { ): Promise<void> {
const state = game.value; const state = game.value;
game.produce(state => { await game.produceAsync(async (state) => {
for (const enemyId of state.enemyOrder) { for (const enemyId of state.enemyOrder) {
const enemy = state.enemies[enemyId]; const enemy = state.enemies[enemyId];
if (!enemy.isAlive) continue; if (!enemy.isAlive) continue;
@ -205,7 +188,7 @@ async function runEnemyTurn(
dispatchTrigger(triggerCtx, "onTurnStart", enemyId, triggerRegistry); dispatchTrigger(triggerCtx, "onTurnStart", enemyId, triggerRegistry);
} }
game.produce(state => { await game.produceAsync(async (state) => {
for (const enemyId of state.enemyOrder) { for (const enemyId of state.enemyOrder) {
const enemy = state.enemies[enemyId]; const enemy = state.enemies[enemyId];
if (!enemy.isAlive) continue; if (!enemy.isAlive) continue;
@ -245,7 +228,7 @@ async function runEnemyTurn(
dispatchTrigger(createTriggerContext(game), "onTurnEnd", enemyId, triggerRegistry); dispatchTrigger(createTriggerContext(game), "onTurnEnd", enemyId, triggerRegistry);
} }
game.produce(state => { await game.produceAsync(async (state) => {
state.phase = "playerTurn"; state.phase = "playerTurn";
state.turnNumber++; state.turnNumber++;
}); });
@ -289,8 +272,8 @@ function reshuffleWithFatigue(state: CombatState): void {
state.player.deck.drawPile.push(...state.player.deck.discardPile); state.player.deck.drawPile.push(...state.player.deck.discardPile);
state.player.deck.discardPile = []; state.player.deck.discardPile = [];
addFatigueCards(state.player.deck, FATIGUE_CARDS_PER_SHUFFLE, { value: state.fatigueAddedCount }); addFatigueCards(state.player.deck, FATIGUE_CARDS_PER_SHUFFLE, { value: state.player.fatigueAddedCount });
state.fatigueAddedCount += FATIGUE_CARDS_PER_SHUFFLE; state.player.fatigueAddedCount += FATIGUE_CARDS_PER_SHUFFLE;
for (let i = state.player.deck.drawPile.length - 1; i > 0; i--) { for (let i = state.player.deck.drawPile.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); const j = Math.floor(Math.random() * (i + 1));

View File

@ -5,7 +5,8 @@ import type { EnemyDesert } from "../data/enemyDesert.csv";
import type { EffectDesert } from "../data/effectDesert.csv"; import type { EffectDesert } from "../data/effectDesert.csv";
import type { EncounterDesert } from "../data/encounterDesert.csv"; import type { EncounterDesert } from "../data/encounterDesert.csv";
import { generateDeckFromInventory, createStatusCard } from "../deck/factory"; import { generateDeckFromInventory, createStatusCard } from "../deck/factory";
import { enemyDesertData, effectDesertData } from "../data"; import { enemyDesertData, effectDesertData, cardDesertData } from "../data";
import { createRNG } from "@/utils/rng";
import type { import type {
BuffTable, BuffTable,
CombatState, CombatState,
@ -86,6 +87,8 @@ export function createPlayerCombatState(
damageTakenThisTurn: 0, damageTakenThisTurn: 0,
damagedThisTurn: false, damagedThisTurn: false,
cardsDiscardedThisTurn: 0, cardsDiscardedThisTurn: 0,
itemBuffs: [],
fatigueAddedCount: 0,
}; };
} }
@ -101,9 +104,10 @@ export function createCombatState(
const enemyOrder: string[] = []; const enemyOrder: string[] = [];
const enemyTemplateData: Record<string, EnemyDesert> = {}; const enemyTemplateData: Record<string, EnemyDesert> = {};
for (const [enemyId, hp, bonusHp] of encounter.enemies) { for (const enemyEntry of encounter.enemies as unknown as [string, number, number][]) {
const [enemyId, hp, bonusHp] = enemyEntry;
// Find initBuffs from enemyDesert (first row for this enemy type) // Find initBuffs from enemyDesert (first row for this enemy type)
const enemyRow = enemyDesertData.find(e => e.enemy === enemyId); const enemyRow = enemyDesertData.find((e) => e.enemy === enemyId);
const initBuffs: [EffectDesert, number][] = []; const initBuffs: [EffectDesert, number][] = [];
if (enemyRow) { if (enemyRow) {
for (const [effect, stacks] of enemyRow.initBuffs) { for (const [effect, stacks] of enemyRow.initBuffs) {
@ -123,7 +127,7 @@ export function createCombatState(
enemyTemplateData[enemyInstance.templateId] = enemyRow!; enemyTemplateData[enemyInstance.templateId] = enemyRow!;
} }
shuffleDeck(player.deck.drawPile, buildSimpleRNG(0)); shuffleDeck(player.deck.drawPile, createRNG(0));
drawCardsToHand(player.deck, INITIAL_HAND_SIZE); drawCardsToHand(player.deck, INITIAL_HAND_SIZE);
@ -135,8 +139,6 @@ export function createCombatState(
turnNumber: 1, turnNumber: 1,
result: null, result: null,
loot: [], loot: [],
itemBuffs: [],
fatigueAddedCount: 0,
enemyTemplateData, enemyTemplateData,
}; };
} }
@ -145,7 +147,6 @@ export function drawCardsToHand(deck: PlayerDeck, count: number): string[] {
const drawn: string[] = []; const drawn: string[] = [];
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
if (deck.drawPile.length === 0) { if (deck.drawPile.length === 0) {
// TODO think we should shuffle Fatigue into the deck here.
reshuffleDiscardIntoDraw(deck); reshuffleDiscardIntoDraw(deck);
} }
if (deck.drawPile.length === 0) break; if (deck.drawPile.length === 0) break;
@ -171,13 +172,15 @@ export function reshuffleDiscardIntoDraw(deck: PlayerDeck): void {
export function addFatigueCards(deck: PlayerDeck, count: number, fatigueCounter: { value: number }): number { export function addFatigueCards(deck: PlayerDeck, count: number, fatigueCounter: { value: number }): number {
let added = 0; let added = 0;
const fatigueDef = cardDesertData.find((c) => c.id === "fatigue");
if (!fatigueDef) return 0;
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
fatigueCounter.value++; fatigueCounter.value++;
// TODO avoid hard coding, expect a fatigue card to be in the CSV with a specific id.
const card = createStatusCard( const card = createStatusCard(
`fatigue-${fatigueCounter.value}`, `fatigue-${fatigueCounter.value}`,
"疲劳", fatigueDef.name,
"1费/消耗", fatigueDef.desc,
); );
deck.cards[card.id] = card; deck.cards[card.id] = card;
deck.drawPile.push(card.id); deck.drawPile.push(card.id);
@ -239,19 +242,6 @@ function shuffleDeck(drawPile: string[], rng: { nextInt: (n: number) => number }
} }
} }
// TODO why? use @/utils/rng.ts instead?
function buildSimpleRNG(seed: number) {
let s = seed;
return {
nextInt(max: number) {
s = (s + 0x6d2b79f5) | 0;
let t = Math.imul(s ^ (s >>> 15), s | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return Math.floor(((t ^ (t >>> 14)) >>> 0) / 4294967296 * max);
},
};
}
export function getEffectTiming(effectId: string): EffectDesert["timing"] | undefined { export function getEffectTiming(effectId: string): EffectDesert["timing"] | undefined {
const effect = effectDesertData.find(e => e.id === effectId); const effect = effectDesertData.find(e => e.id === effectId);
return effect?.timing; return effect?.timing;

View File

@ -9,9 +9,6 @@ export type TriggerContext = {
rng: { nextInt: (n: number) => number }; rng: { nextInt: (n: number) => number };
}; };
// TODO add an onCardDrawn trigger
// TODO refactor this to NOT implicitly correspond to an effect type, but generic event handler
// TODO also, refactor this to be async to support prompts and produceAsync
export type BuffTriggerBehavior = { export type BuffTriggerBehavior = {
onTurnStart?: (ctx: TriggerContext, entityKey: "player" | string, stacks: number) => void; onTurnStart?: (ctx: TriggerContext, entityKey: "player" | string, stacks: number) => void;
onTurnEnd?: (ctx: TriggerContext, entityKey: "player" | string, stacks: number) => void; onTurnEnd?: (ctx: TriggerContext, entityKey: "player" | string, stacks: number) => void;
@ -22,9 +19,21 @@ export type BuffTriggerBehavior = {
onShuffle?: (ctx: TriggerContext, stacks: number) => void; onShuffle?: (ctx: TriggerContext, stacks: number) => void;
onCardPlayed?: (ctx: TriggerContext, cardId: string, stacks: number) => void; onCardPlayed?: (ctx: TriggerContext, cardId: string, stacks: number) => void;
onCardDiscarded?: (ctx: TriggerContext, cardId: string, stacks: number) => void; onCardDiscarded?: (ctx: TriggerContext, cardId: string, stacks: number) => void;
onCardDrawn?: (ctx: TriggerContext, cardId: string, stacks: number) => void;
}; };
// TODO refactor this to be keyed by event type export type TriggerEvent =
| "onTurnStart"
| "onTurnEnd"
| "onAttacked"
| "onDamage"
| "modifyOutgoingDamage"
| "modifyIncomingDamage"
| "onShuffle"
| "onCardPlayed"
| "onCardDiscarded"
| "onCardDrawn";
export type CombatTriggerRegistry = Record<string, BuffTriggerBehavior>; export type CombatTriggerRegistry = Record<string, BuffTriggerBehavior>;
export function createCombatTriggerRegistry(): CombatTriggerRegistry { export function createCombatTriggerRegistry(): CombatTriggerRegistry {
@ -121,7 +130,6 @@ export function createCombatTriggerRegistry(): CombatTriggerRegistry {
const moltStacks = enemy.buffs["molt"] ?? 0; const moltStacks = enemy.buffs["molt"] ?? 0;
if (moltStacks >= enemy.maxHp) { if (moltStacks >= enemy.maxHp) {
enemy.isAlive = false; enemy.isAlive = false;
state.result = "fled";
} }
} }
} }
@ -141,7 +149,7 @@ export function createCombatTriggerRegistry(): CombatTriggerRegistry {
const { state } = ctx; const { state } = ctx;
if (targetKey === "player" && damage > 0) { if (targetKey === "player" && damage > 0) {
const vultureEnemies = state.enemyOrder.filter( const vultureEnemies = state.enemyOrder.filter(
id => state.enemies[id].isAlive && state.enemies[id].buffs["vultureEye"] && state.enemies[id].templateId === "秃鹫" (id) => state.enemies[id].isAlive && state.enemies[id].buffs["vultureEye"] && state.enemies[id].templateId === "秃鹫"
); );
if (vultureEnemies.length > 0) { if (vultureEnemies.length > 0) {
for (const vultureId of vultureEnemies) { for (const vultureId of vultureEnemies) {
@ -164,7 +172,7 @@ export function createCombatTriggerRegistry(): CombatTriggerRegistry {
onCardDiscarded(ctx, cardId, stacks) { onCardDiscarded(ctx, cardId, stacks) {
const { state } = ctx; const { state } = ctx;
state.player.cardsDiscardedThisTurn++; state.player.cardsDiscardedThisTurn++;
const venomCards = state.player.deck.hand.filter(id => { const venomCards = state.player.deck.hand.filter((id) => {
const card = state.player.deck.cards[id]; const card = state.player.deck.cards[id];
return card && card.itemData === null && card.displayName === "蛇毒"; return card && card.itemData === null && card.displayName === "蛇毒";
}); });
@ -197,7 +205,7 @@ export function createCombatTriggerRegistry(): CombatTriggerRegistry {
} }
function addStatusCardToHand(state: CombatState, effectId: string, count: number): void { function addStatusCardToHand(state: CombatState, effectId: string, count: number): void {
const cardDef = cardDesertData.find(c => c.id === effectId); const cardDef = cardDesertData.find((c) => c.id === effectId);
if (!cardDef) return; if (!cardDef) return;
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
@ -208,20 +216,9 @@ function addStatusCardToHand(state: CombatState, effectId: string, count: number
} }
} }
export type TriggerEvent =
| "onTurnStart"
| "onTurnEnd"
| "onAttacked"
| "onDamage"
| "modifyOutgoingDamage"
| "modifyIncomingDamage"
| "onShuffle"
| "onCardPlayed"
| "onCardDiscarded";
export function dispatchTrigger( export function dispatchTrigger(
ctx: TriggerContext, ctx: TriggerContext,
event: "onTurnStart" | "onTurnEnd", event: TriggerEvent,
entityKey: "player" | string, entityKey: "player" | string,
registry: CombatTriggerRegistry, registry: CombatTriggerRegistry,
): void { ): void {
@ -333,3 +330,18 @@ export function dispatchShuffleTrigger(
} }
} }
} }
export function dispatchCardDrawnTrigger(
ctx: TriggerContext,
cardId: string,
registry: CombatTriggerRegistry,
): void {
const buffs = ctx.state.player.buffs;
if (!buffs) return;
for (const [buffId, stacks] of Object.entries(buffs)) {
const behavior = registry[buffId];
if (!behavior?.onCardDrawn) continue;
behavior.onCardDrawn(ctx, cardId, stacks);
}
}

View File

@ -1,4 +1,3 @@
// TODO shouldn't rely on csv types. Use interfaces and expect csv types to match.
import type { EnemyDesert } from "../data/enemyDesert.csv"; import type { EnemyDesert } from "../data/enemyDesert.csv";
import type { EffectDesert } from "../data/effectDesert.csv"; import type { EffectDesert } from "../data/effectDesert.csv";
import type { PlayerDeck, GameCard } from "../deck/types"; import type { PlayerDeck, GameCard } from "../deck/types";
@ -6,7 +5,7 @@ import type { PlayerState } from "../progress/types";
export type BuffTable = Record<string, number>; export type BuffTable = Record<string, number>;
// TODO rename this to "lifecycle". Should use lifecycle in csv as well. /** Lifecycle timing for effects - matches CSV timing column */
export type EffectTiming = EffectDesert["timing"]; export type EffectTiming = EffectDesert["timing"];
export type EffectTarget = "self" | "target" | "all" | "random" | "player" | "team"; export type EffectTarget = "self" | "target" | "all" | "random" | "player" | "team";
@ -41,11 +40,13 @@ export type PlayerCombatState = {
damageTakenThisTurn: number; damageTakenThisTurn: number;
damagedThisTurn: boolean; damagedThisTurn: boolean;
cardsDiscardedThisTurn: number; cardsDiscardedThisTurn: number;
itemBuffs: ItemBuff[];
fatigueAddedCount: number;
}; };
export type CombatPhase = "playerTurn" | "enemyTurn" | "combatEnd"; export type CombatPhase = "playerTurn" | "enemyTurn" | "combatEnd";
export type CombatResult = "victory" | "defeat" | "fled"; export type CombatResult = "victory" | "defeat";
export type LootEntry = { export type LootEntry = {
type: "gold" | "item" | "relic"; type: "gold" | "item" | "relic";
@ -59,14 +60,8 @@ export type CombatState = {
player: PlayerCombatState; player: PlayerCombatState;
phase: CombatPhase; phase: CombatPhase;
turnNumber: number; turnNumber: number;
// TODO: "fled" is a per-enemy state. Should remove it here and expand the isAlive property on enemy instead.
// TODO: CombatResult should just be "victory" "defeat" or null.
result: CombatResult | null; result: CombatResult | null;
loot: LootEntry[]; loot: LootEntry[];
// TODO: I think this belongs to the player combat state.
itemBuffs: ItemBuff[];
// TODO: Also belongs to the player combat state.
fatigueAddedCount: number;
enemyTemplateData: Record<string, EnemyDesert>; enemyTemplateData: Record<string, EnemyDesert>;
}; };