feat(equipment): remove the requiredLevel equipment gate - owned items are always equippable

This commit is contained in:
Bastian Wagner
2026-08-21 13:34:10 +02:00
parent 83eef5d9e5
commit 413fafd574
3 changed files with 8 additions and 24 deletions

View File

@@ -4,7 +4,6 @@ export type EquipmentErrorCode =
| 'CHARACTER_ITEM_NOT_FOUND' | 'CHARACTER_ITEM_NOT_FOUND'
| 'ITEM_NOT_OWNED' | 'ITEM_NOT_OWNED'
| 'ITEM_NOT_EQUIPPABLE' | 'ITEM_NOT_EQUIPPABLE'
| 'ITEM_LEVEL_REQUIREMENT_NOT_MET'
| 'INVALID_EQUIPMENT_SLOT' | 'INVALID_EQUIPMENT_SLOT'
| 'CHARACTER_IN_COMBAT'; | 'CHARACTER_IN_COMBAT';
@@ -42,14 +41,6 @@ export function itemNotEquippable(): EquipmentDomainError {
); );
} }
export function itemLevelRequirementNotMet(): EquipmentDomainError {
return new EquipmentDomainError(
'ITEM_LEVEL_REQUIREMENT_NOT_MET',
HttpStatus.BAD_REQUEST,
"The character does not meet this item's level requirement.",
);
}
// Defensive: slot is always derived from the item definition server-side, so // Defensive: slot is always derived from the item definition server-side, so
// this is unreachable in practice (spec §27 still names it explicitly). // this is unreachable in practice (spec §27 still names it explicitly).
export function invalidEquipmentSlot(): EquipmentDomainError { export function invalidEquipmentSlot(): EquipmentDomainError {

View File

@@ -143,7 +143,6 @@ function itemDefinition(overrides: Partial<ItemDefinition> = {}): ItemDefinition
equipmentSlot: EquipmentSlot.WEAPON, equipmentSlot: EquipmentSlot.WEAPON,
rarity: ItemRarity.COMMON, rarity: ItemRarity.COMMON,
tier: 1, tier: 1,
requiredLevel: 1,
weaponDamage: 8, weaponDamage: 8,
bonusHp: 0, bonusHp: 0,
bonusAttack: 0, bonusAttack: 0,
@@ -160,7 +159,6 @@ function character(overrides: Partial<Character> = {}): Character {
return { return {
id: CHARACTER_ID, id: CHARACTER_ID,
name: 'Aric Duskwalker', name: 'Aric Duskwalker',
level: 1,
baseHp: 100, baseHp: 100,
baseAttack: 6, baseAttack: 6,
currentHp: 100, currentHp: 100,
@@ -300,29 +298,28 @@ describe('EquipmentService', () => {
); );
}); });
it('rejects equipping an item above the character level', async () => { it('equips an owned item regardless of the item content that used to carry a level requirement', async () => {
const highLevelHelm = itemDefinition({ const highTierHelm = itemDefinition({
id: BANDIT_HOOD_ITEM_ID, id: BANDIT_HOOD_ITEM_ID,
key: 'bandit-hood', key: 'bandit-hood',
equipmentSlot: EquipmentSlot.HEAD, equipmentSlot: EquipmentSlot.HEAD,
requiredLevel: 5, tier: 5,
}); });
const { service } = createHarness({ const { service } = createHarness({
itemDefinitions: [highLevelHelm], itemDefinitions: [highTierHelm],
characterItems: [ characterItems: [
{ {
id: BANDIT_HOOD_ITEM_ID, id: BANDIT_HOOD_ITEM_ID,
characterId: CHARACTER_ID, characterId: CHARACTER_ID,
itemDefinitionId: highLevelHelm.id, itemDefinitionId: highTierHelm.id,
quantity: 1, quantity: 1,
} as CharacterItem, } as CharacterItem,
], ],
}); });
await expectEquipmentDomainError( const result = await service.equip(CHARACTER_ID, BANDIT_HOOD_ITEM_ID);
service.equip(CHARACTER_ID, BANDIT_HOOD_ITEM_ID),
'ITEM_LEVEL_REQUIREMENT_NOT_MET', expect(result.slots.HEAD?.characterItemId).toBe(BANDIT_HOOD_ITEM_ID);
);
}); });
it('rejects equipping a non-equippable item', async () => { it('rejects equipping a non-equippable item', async () => {

View File

@@ -12,7 +12,6 @@ import {
characterInCombat, characterInCombat,
characterItemNotFound, characterItemNotFound,
characterNotFound, characterNotFound,
itemLevelRequirementNotMet,
itemNotEquippable, itemNotEquippable,
itemNotOwned, itemNotOwned,
} from './equipment.errors'; } from './equipment.errors';
@@ -102,9 +101,6 @@ export class EquipmentService {
if (!definition.equipmentSlot) { if (!definition.equipmentSlot) {
throw itemNotEquippable(); throw itemNotEquippable();
} }
if (definition.requiredLevel > character.level) {
throw itemLevelRequirementNotMet();
}
const existing = await equipmentRepo.findOne({ const existing = await equipmentRepo.findOne({
where: { characterId, slot: definition.equipmentSlot }, where: { characterId, slot: definition.equipmentSlot },