diff --git a/apps/api/src/equipment/equipment.service.spec.ts b/apps/api/src/equipment/equipment.service.spec.ts index 5536a9d..5e96426 100644 --- a/apps/api/src/equipment/equipment.service.spec.ts +++ b/apps/api/src/equipment/equipment.service.spec.ts @@ -1,5 +1,6 @@ import { DataSource, EntityManager, EntityTarget } from 'typeorm'; import { CharacterStatsService } from '../characters/character-stats.service'; +import { CharacterVitalsService } from '../characters/character-vitals.service'; import { Character } from '../characters/entities/character.entity'; import { CombatStatus } from '../combat/combat-status.enum'; import { Combat } from '../combat/entities/combat.entity'; @@ -164,6 +165,7 @@ function character(overrides: Partial = {}): Character { baseHp: 100, baseAttack: 6, currentHp: 100, + hpRegenSince: null, ...overrides, } as Character; } @@ -178,8 +180,18 @@ function createHarness(state: Partial = {}) { ...state, }; const dataSource = new FakeDataSource(fullState); - const characterStats = new CharacterStatsService(dataSource as unknown as DataSource); - const service = new EquipmentService(dataSource as unknown as DataSource, characterStats); + const characterVitals = new CharacterVitalsService({ + now: () => new Date('2026-08-18T09:00:00.000Z'), + }); + const characterStats = new CharacterStatsService( + dataSource as unknown as DataSource, + characterVitals, + ); + const service = new EquipmentService( + dataSource as unknown as DataSource, + characterStats, + characterVitals, + ); return { state: fullState, service }; } @@ -408,6 +420,33 @@ describe('EquipmentService', () => { 'CHARACTER_IN_COMBAT', ); }); + + it('re-anchors HP regeneration so a later max-HP increase does not gift accumulated overflow', async () => { + const bonusHpHelm = itemDefinition({ + id: 'def-bonus-hp-helm', + key: 'bonus-hp-helm', + name: 'Gepolsterter Helm', + equipmentSlot: EquipmentSlot.HEAD, + bonusHp: 20, + weaponDamage: 0, + }); + const { state, service } = createHarness({ + characters: [character({ currentHp: 100, hpRegenSince: null })], + itemDefinitions: [bonusHpHelm], + characterItems: [ + { + id: BANDIT_HOOD_ITEM_ID, + characterId: CHARACTER_ID, + itemDefinitionId: bonusHpHelm.id, + quantity: 1, + } as CharacterItem, + ], + }); + + await service.equip(CHARACTER_ID, BANDIT_HOOD_ITEM_ID); + + expect(state.characters[0].currentHp).toBe(100); + }); }); describe('getEquipment', () => { diff --git a/apps/api/src/equipment/equipment.service.ts b/apps/api/src/equipment/equipment.service.ts index 5fd094a..b0d92c5 100644 --- a/apps/api/src/equipment/equipment.service.ts +++ b/apps/api/src/equipment/equipment.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { CharacterStatsService } from '../characters/character-stats.service'; +import { CharacterVitalsService } from '../characters/character-vitals.service'; import { Character } from '../characters/entities/character.entity'; import { CombatStatus } from '../combat/combat-status.enum'; import { Combat } from '../combat/entities/combat.entity'; @@ -48,6 +49,7 @@ export class EquipmentService { constructor( private readonly dataSource: DataSource, private readonly characterStats: CharacterStatsService, + private readonly characterVitals: CharacterVitalsService, ) {} async getEquipment(characterId: string): Promise { @@ -106,6 +108,10 @@ export class EquipmentService { throw itemLevelRequirementNotMet(); } + const statsBeforeChange = await this.characterStats.calculate(character, manager); + this.characterVitals.settle(character, statsBeforeChange.maxHp); + await characters.save(character); + const existing = await equipmentRepo.findOne({ where: { characterId, slot: definition.equipmentSlot }, lock: { mode: 'pessimistic_write' },