feat(combat): validate potions, persist telegraph state, and expose both on the combat DTO
Also updates the pre-existing exact-equality playerState assertion in combat-equipment-integration.spec.ts, which broke from the new potionsRemaining field but wasn't listed in the task brief's file scope.
This commit is contained in:
@@ -13,12 +13,13 @@ import { TravelService } from '../travel/travel.service';
|
||||
import { TravelStatus } from '../travel/travel-status.enum';
|
||||
import { CombatAction } from './combat-action.enum';
|
||||
import { CombatEngineService } from './combat-engine.service';
|
||||
import { CombatEngineState } from './combat-engine.types';
|
||||
import { CombatEngineState, CombatIntent } from './combat-engine.types';
|
||||
import {
|
||||
characterNotFound,
|
||||
characterTravelling,
|
||||
combatAlreadyActive,
|
||||
combatAlreadyFinished,
|
||||
combatNoPotionsRemaining,
|
||||
combatNotFound,
|
||||
combatStateInvalid,
|
||||
huntEncounterAlreadyConsumed,
|
||||
@@ -27,12 +28,18 @@ import {
|
||||
} from './combat.errors';
|
||||
import { CombatStatus } from './combat-status.enum';
|
||||
import { CombatEvent } from './entities/combat-event.entity';
|
||||
import { Combat } from './entities/combat.entity';
|
||||
import { Combat, CombatPlayerState } from './entities/combat.entity';
|
||||
|
||||
// Playable Slice 0.6 spec §3: fixed at 2 for V1, not yet backed by the
|
||||
// persistent consumable inventory.
|
||||
const STARTING_POTION_COUNT = 2;
|
||||
|
||||
export interface CombatPlayerDto {
|
||||
name: string;
|
||||
maxHp: number;
|
||||
currentHp: number;
|
||||
potionsRemaining: number;
|
||||
potionsMax: number;
|
||||
}
|
||||
|
||||
export interface CombatMonsterDto {
|
||||
@@ -42,6 +49,7 @@ export interface CombatMonsterDto {
|
||||
maxHp: number;
|
||||
currentHp: number;
|
||||
artworkPath: string;
|
||||
pendingIntent: CombatIntent | null;
|
||||
}
|
||||
|
||||
export interface CombatEventDto {
|
||||
@@ -142,6 +150,7 @@ export class CombatService {
|
||||
attack: playerStats.attack,
|
||||
weaponDamage: playerStats.weaponDamage,
|
||||
armor: playerStats.armor,
|
||||
potionsRemaining: STARTING_POTION_COUNT,
|
||||
},
|
||||
monsterState: { attack: monster.attack, armor: monster.armor },
|
||||
completedAt: null,
|
||||
@@ -220,6 +229,9 @@ export class CombatService {
|
||||
if (combat.status !== CombatStatus.ACTIVE) {
|
||||
throw combatAlreadyFinished();
|
||||
}
|
||||
if (action === CombatAction.POTION && (combat.playerState.potionsRemaining ?? 0) <= 0) {
|
||||
throw combatNoPotionsRemaining();
|
||||
}
|
||||
|
||||
const actionRound = combat.round;
|
||||
const engineState = this.toEngineState(combat);
|
||||
@@ -229,6 +241,8 @@ export class CombatService {
|
||||
combat.status = result.state.status;
|
||||
combat.playerCurrentHp = result.state.player.currentHp;
|
||||
combat.monsterCurrentHp = result.state.monster.currentHp;
|
||||
combat.playerState = result.state.player.stats as CombatPlayerState;
|
||||
combat.monsterState = result.state.monster.stats;
|
||||
if (combat.status !== CombatStatus.ACTIVE) {
|
||||
combat.completedAt = new Date();
|
||||
await this.settleEncounter(
|
||||
@@ -386,6 +400,8 @@ export class CombatService {
|
||||
name: playerName,
|
||||
maxHp: combat.playerMaxHp,
|
||||
currentHp: combat.playerCurrentHp,
|
||||
potionsRemaining: combat.playerState.potionsRemaining,
|
||||
potionsMax: STARTING_POTION_COUNT,
|
||||
},
|
||||
monster: {
|
||||
key: monster.key,
|
||||
@@ -394,6 +410,7 @@ export class CombatService {
|
||||
maxHp: combat.monsterMaxHp,
|
||||
currentHp: combat.monsterCurrentHp,
|
||||
artworkPath: monster.artworkPath,
|
||||
pendingIntent: combat.monsterState.pendingAction ?? null,
|
||||
},
|
||||
events: events.map((event) => ({
|
||||
round: event.round,
|
||||
|
||||
Reference in New Issue
Block a user