feat(combat): add deterministic combat engine for ATTACK resolution
This commit is contained in:
83
apps/api/src/combat/combat-engine.service.ts
Normal file
83
apps/api/src/combat/combat-engine.service.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CombatAction } from './combat-action.enum';
|
||||
import { calculateDamage } from './combat-damage';
|
||||
import { Combatant } from './combatant.enum';
|
||||
import {
|
||||
CombatActionInput,
|
||||
CombatEngineEvent,
|
||||
CombatEngineResult,
|
||||
CombatEngineState,
|
||||
} from './combat-engine.types';
|
||||
import { CombatEventType } from './combat-event-type.enum';
|
||||
import { CombatStatus } from './combat-status.enum';
|
||||
|
||||
export class UnsupportedCombatActionError extends Error {
|
||||
constructor(action: string) {
|
||||
super(`Unsupported combat action: ${action}`);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class CombatEngineService {
|
||||
resolveAction(state: CombatEngineState, input: CombatActionInput): CombatEngineResult {
|
||||
switch (input.action) {
|
||||
case CombatAction.ATTACK:
|
||||
return this.resolveAttack(state);
|
||||
default:
|
||||
throw new UnsupportedCombatActionError(input.action);
|
||||
}
|
||||
}
|
||||
|
||||
private resolveAttack(state: CombatEngineState): CombatEngineResult {
|
||||
const events: CombatEngineEvent[] = [];
|
||||
const player = { ...state.player };
|
||||
const monster = { ...state.monster };
|
||||
|
||||
const playerDamage = calculateDamage(player.stats, monster.stats.armor);
|
||||
monster.currentHp = Math.max(0, monster.currentHp - playerDamage);
|
||||
events.push({
|
||||
source: Combatant.PLAYER,
|
||||
target: Combatant.MONSTER,
|
||||
type: CombatEventType.DAMAGE,
|
||||
amount: playerDamage,
|
||||
});
|
||||
|
||||
if (monster.currentHp <= 0) {
|
||||
events.push({
|
||||
source: Combatant.PLAYER,
|
||||
target: Combatant.MONSTER,
|
||||
type: CombatEventType.COMBAT_WON,
|
||||
});
|
||||
return {
|
||||
state: { ...state, player, monster, status: CombatStatus.WON },
|
||||
events,
|
||||
};
|
||||
}
|
||||
|
||||
const monsterDamage = calculateDamage(monster.stats, player.stats.armor);
|
||||
player.currentHp = Math.max(0, player.currentHp - monsterDamage);
|
||||
events.push({
|
||||
source: Combatant.MONSTER,
|
||||
target: Combatant.PLAYER,
|
||||
type: CombatEventType.DAMAGE,
|
||||
amount: monsterDamage,
|
||||
});
|
||||
|
||||
if (player.currentHp <= 0) {
|
||||
events.push({
|
||||
source: Combatant.MONSTER,
|
||||
target: Combatant.PLAYER,
|
||||
type: CombatEventType.COMBAT_LOST,
|
||||
});
|
||||
return {
|
||||
state: { ...state, player, monster, status: CombatStatus.LOST },
|
||||
events,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
state: { ...state, player, monster, status: CombatStatus.ACTIVE, round: state.round + 1 },
|
||||
events,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user