65 lines
1.8 KiB
TypeScript
65 lines
1.8 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { DataSource } from 'typeorm';
|
|
import { CharacterEquipment } from '../equipment/entities/character-equipment.entity';
|
|
import { EquipmentSlot } from '../items/equipment-slot.enum';
|
|
import { Character } from './entities/character.entity';
|
|
|
|
export interface EffectiveCharacterStats {
|
|
maxHp: number;
|
|
currentHp: number;
|
|
attack: number;
|
|
weaponDamage: number;
|
|
armor: number;
|
|
combatPower: number;
|
|
}
|
|
|
|
type RepositoryScope = Pick<DataSource, 'getRepository'>;
|
|
|
|
/**
|
|
* Single authoritative source of effective character stats (spec §18).
|
|
* Replaces the Slice 0.3 `CharacterCombatStatsService` shortcut.
|
|
*/
|
|
@Injectable()
|
|
export class CharacterStatsService {
|
|
constructor(private readonly dataSource: DataSource) {}
|
|
|
|
async calculate(
|
|
character: Character,
|
|
scope?: RepositoryScope,
|
|
): Promise<EffectiveCharacterStats> {
|
|
const db = scope ?? this.dataSource;
|
|
const equipped = await db.getRepository(CharacterEquipment).find({
|
|
where: { characterId: character.id },
|
|
relations: { characterItem: { itemDefinition: true } },
|
|
});
|
|
|
|
let weaponDamage = 0;
|
|
let bonusHp = 0;
|
|
let bonusAttack = 0;
|
|
let bonusArmor = 0;
|
|
|
|
for (const slot of equipped) {
|
|
const definition = slot.characterItem.itemDefinition;
|
|
if (slot.slot === EquipmentSlot.WEAPON) {
|
|
weaponDamage = definition.weaponDamage;
|
|
}
|
|
bonusHp += definition.bonusHp;
|
|
bonusAttack += definition.bonusAttack;
|
|
bonusArmor += definition.bonusArmor;
|
|
}
|
|
|
|
const maxHp = character.baseHp + bonusHp;
|
|
const attack = character.baseAttack + bonusAttack;
|
|
const armor = bonusArmor;
|
|
|
|
return {
|
|
maxHp,
|
|
currentHp: character.currentHp,
|
|
attack,
|
|
weaponDamage,
|
|
armor,
|
|
combatPower: maxHp / 10 + attack * 2 + weaponDamage * 2 + armor * 1.5,
|
|
};
|
|
}
|
|
}
|