docs
This commit is contained in:
@@ -14,7 +14,11 @@ 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, CombatIntent } from './combat-engine.types';
|
||||
import {
|
||||
ActiveStatusEffect,
|
||||
CombatEngineState,
|
||||
CombatIntent,
|
||||
} from './combat-engine.types';
|
||||
import {
|
||||
characterNotFound,
|
||||
characterTooWounded,
|
||||
@@ -30,18 +34,30 @@ import {
|
||||
} from './combat.errors';
|
||||
import { CombatStatus } from './combat-status.enum';
|
||||
import { CombatEvent } from './entities/combat-event.entity';
|
||||
import { Combat, CombatPlayerState } from './entities/combat.entity';
|
||||
import {
|
||||
Combat,
|
||||
CombatMonsterState,
|
||||
CombatPlayerState,
|
||||
} from './entities/combat.entity';
|
||||
import { StatusEffectType } from './status-effect.enum';
|
||||
|
||||
// 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 CombatStatusEffectDto {
|
||||
type: StatusEffectType;
|
||||
remainingRounds: number;
|
||||
damagePerRound: number;
|
||||
}
|
||||
|
||||
export interface CombatPlayerDto {
|
||||
name: string;
|
||||
maxHp: number;
|
||||
currentHp: number;
|
||||
potionsRemaining: number;
|
||||
potionsMax: number;
|
||||
statusEffects: CombatStatusEffectDto[];
|
||||
}
|
||||
|
||||
export interface CombatMonsterDto {
|
||||
@@ -61,6 +77,7 @@ export interface CombatEventDto {
|
||||
source: string;
|
||||
target: string;
|
||||
amount?: number;
|
||||
statusEffect?: StatusEffectType;
|
||||
}
|
||||
|
||||
export interface CombatDto {
|
||||
@@ -163,7 +180,11 @@ export class CombatService {
|
||||
armor: playerStats.armor,
|
||||
potionsRemaining: STARTING_POTION_COUNT,
|
||||
},
|
||||
monsterState: { attack: monster.attack, armor: monster.armor },
|
||||
monsterState: {
|
||||
attack: monster.attack,
|
||||
armor: monster.armor,
|
||||
abilities: monster.abilities ?? {},
|
||||
},
|
||||
completedAt: null,
|
||||
});
|
||||
await combats.save(combat);
|
||||
@@ -223,11 +244,10 @@ export class CombatService {
|
||||
const combatEvents = manager.getRepository(CombatEvent);
|
||||
|
||||
// Lock the character before the combat row here, matching the order
|
||||
// startCombat already uses (character, then combat). grantVictoryRewards
|
||||
// locks the character again later in this same transaction, which is a
|
||||
// no-op re-lock — but locking it first here keeps both code paths
|
||||
// consistent and avoids a lock-order inversion that could deadlock two
|
||||
// concurrent requests against the same character. Do not reorder this.
|
||||
// startCombat already uses (character, then combat). Keeping both code
|
||||
// paths on the same order avoids a lock-order inversion that could
|
||||
// deadlock two concurrent requests against the same character. Do not
|
||||
// reorder this.
|
||||
const character = await this.lockCharacter(characters, characterId);
|
||||
|
||||
const combat = await combats.findOne({
|
||||
@@ -256,7 +276,7 @@ export class CombatService {
|
||||
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;
|
||||
combat.monsterState = result.state.monster.stats as CombatMonsterState;
|
||||
if (combat.status !== CombatStatus.ACTIVE) {
|
||||
combat.completedAt = new Date();
|
||||
this.characterVitals.resume(character, combat.playerCurrentHp);
|
||||
@@ -284,6 +304,7 @@ export class CombatService {
|
||||
source: event.source,
|
||||
target: event.target,
|
||||
amount: event.amount ?? null,
|
||||
statusEffect: event.statusEffect ?? null,
|
||||
});
|
||||
await combatEvents.save(entity);
|
||||
}
|
||||
@@ -308,7 +329,13 @@ export class CombatService {
|
||||
this.loadEvents(combat.id, combatEvents),
|
||||
]);
|
||||
|
||||
return this.toCombatDto(combat, reloadedCharacter.name, monster, events, rewards);
|
||||
return this.toCombatDto(
|
||||
combat,
|
||||
reloadedCharacter.name,
|
||||
monster,
|
||||
events,
|
||||
rewards,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -420,6 +447,9 @@ export class CombatService {
|
||||
currentHp: combat.playerCurrentHp,
|
||||
potionsRemaining: combat.playerState.potionsRemaining,
|
||||
potionsMax: STARTING_POTION_COUNT,
|
||||
statusEffects: this.toStatusEffectDtos(
|
||||
combat.playerState.statusEffects,
|
||||
),
|
||||
},
|
||||
monster: {
|
||||
key: monster.key,
|
||||
@@ -437,8 +467,19 @@ export class CombatService {
|
||||
source: event.source,
|
||||
target: event.target,
|
||||
amount: event.amount ?? undefined,
|
||||
statusEffect: event.statusEffect ?? undefined,
|
||||
})),
|
||||
rewards,
|
||||
};
|
||||
}
|
||||
|
||||
private toStatusEffectDtos(
|
||||
effects: ActiveStatusEffect[] | undefined,
|
||||
): CombatStatusEffectDto[] {
|
||||
return (effects ?? []).map((effect) => ({
|
||||
type: effect.type,
|
||||
remainingRounds: effect.remainingRounds,
|
||||
damagePerRound: effect.damagePerRound,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user