feat(equipment): remove the requiredLevel equipment gate - owned items are always equippable
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
|||||||
Reference in New Issue
Block a user