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 { HP_REGEN_PER_SECOND } from './character-vitals.constants'; import { CharacterVitalsService } from './character-vitals.service'; import { Character } from './entities/character.entity'; export interface EffectiveCharacterStats { maxHp: number; currentHp: number; attack: number; weaponDamage: number; armor: number; combatPower: number; hpRegenPerSecond: number; hpRegenSince: Date | null; } type RepositoryScope = Pick; /** * 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, private readonly characterVitals: CharacterVitalsService, ) {} async calculate( character: Character, scope?: RepositoryScope, ): Promise { 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: this.characterVitals.effectiveHp(character, maxHp), attack, weaponDamage, armor, combatPower: maxHp / 10 + attack * 2 + weaponDamage * 2 + armor * 1.5, hpRegenPerSecond: HP_REGEN_PER_SECOND, hpRegenSince: character.hpRegenSince, }; } }