Reconciles Slice 0.6.5 (Renown & Reputation Foundation) against master's persistent-HP-and-regeneration slice, which landed independently and touches several of the same files (Character entity, CombatService, EquipmentService, the inventory detail panel). Conflict resolutions: - CharacterStatsService/EquipmentService constructor wiring: kept master's CharacterVitalsService injection, which this branch's version of the same files didn't have yet. - CombatService.performAction: kept master's HP-guard logic (characterTooWounded, vitals pause-on-enter) alongside this branch's multi-line calculate() call style. - Inventory detail panel (.html/.ts/.scss/.spec.ts): master had redesigned the panel (wrapping section, rarity styling, flavour text, a shared inventory.labels.ts) on top of the OLD level-gated component, since this branch's removal of the level gate (R4, Task 8/14) hadn't reached master yet. Kept master's visual redesign in full, but with the level-gate concept removed throughout: no requiredLevel stat block, no meetsLevelRequirement() branch in the equip button, no now-dead .detail__value--unmet SCSS rule. Kept both branches' independent tests (non-equippable-item, flavour-text). - inventory-page.component.ts: dropped master's dead characterLevel computed (nothing in the template read it, and the level concept is gone); kept its independent bagCells/bagUsed/bagCapacity grid feature, which has nothing to do with renown or level. Post-merge fixture repairs (three files failed the Angular bundle compile because they predate master's hpRegenPerSecond/hpRegenSince fields or master's item description field, neither conflict-marked since git considered them non-overlapping edits): - app.spec.ts: a 'renders loaded character values' test added on master after this branch forked still used the abolished level/ experience fields on its decoy fixture -- retargeted to renown. - inventory-detail-panel.component.spec.ts: the ashPelt fixture added by this branch's final-review follow-up predates master's required description field. - top-bar.component.spec.ts: this branch's fixture predates master's required hpRegenPerSecond/hpRegenSince fields. No database migration touches the same column: master's 1792000000000-AddHpRegeneration only adds characters.hp_regen_since, independent of this slice's 1791000000000-CreateRenownAndReputation. Timestamp ordering between the two was already correct with no rename needed. Verified: API 288/288 (267 from this slice + 21 from master), API build zero errors, web 237/237 (230 from this slice + 7 from master). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
72 lines
2.3 KiB
TypeScript
72 lines
2.3 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
import { CharacterEquipment } from '../equipment/entities/character-equipment.entity';
|
|
import { CharacterItem } from '../items/entities/character-item.entity';
|
|
import { EquipmentSlot } from '../items/equipment-slot.enum';
|
|
import { ItemRarity } from '../items/item-rarity.enum';
|
|
|
|
export interface InventoryItemDto {
|
|
id: string;
|
|
quantity: number;
|
|
equipped: boolean;
|
|
item: {
|
|
key: string;
|
|
name: string;
|
|
description: string;
|
|
rarity: ItemRarity;
|
|
equipmentSlot: EquipmentSlot | null;
|
|
weaponDamage: number;
|
|
bonusAttack: number;
|
|
bonusHp: number;
|
|
bonusArmor: number;
|
|
iconPath: string;
|
|
};
|
|
}
|
|
|
|
export interface InventoryResponseDto {
|
|
items: InventoryItemDto[];
|
|
}
|
|
|
|
@Injectable()
|
|
export class InventoryService {
|
|
constructor(
|
|
@InjectRepository(CharacterItem)
|
|
private readonly characterItems: Repository<CharacterItem>,
|
|
@InjectRepository(CharacterEquipment)
|
|
private readonly equipment: Repository<CharacterEquipment>,
|
|
) {}
|
|
|
|
async getInventory(characterId: string): Promise<InventoryResponseDto> {
|
|
const [items, equipped] = await Promise.all([
|
|
this.characterItems.find({
|
|
where: { characterId },
|
|
relations: { itemDefinition: true },
|
|
order: { createdAt: 'ASC' },
|
|
}),
|
|
this.equipment.find({ where: { characterId } }),
|
|
]);
|
|
const equippedIds = new Set(equipped.map((row) => row.characterItemId));
|
|
|
|
return {
|
|
items: items.map((characterItem) => ({
|
|
id: characterItem.id,
|
|
quantity: characterItem.quantity,
|
|
equipped: equippedIds.has(characterItem.id),
|
|
item: {
|
|
key: characterItem.itemDefinition.key,
|
|
name: characterItem.itemDefinition.name,
|
|
description: characterItem.itemDefinition.description,
|
|
rarity: characterItem.itemDefinition.rarity,
|
|
equipmentSlot: characterItem.itemDefinition.equipmentSlot,
|
|
weaponDamage: characterItem.itemDefinition.weaponDamage,
|
|
bonusAttack: characterItem.itemDefinition.bonusAttack,
|
|
bonusHp: characterItem.itemDefinition.bonusHp,
|
|
bonusArmor: characterItem.itemDefinition.bonusArmor,
|
|
iconPath: characterItem.itemDefinition.iconPath,
|
|
},
|
|
})),
|
|
};
|
|
}
|
|
}
|