feat(loot): seed tier-1 items and the ash rat and road bandit loot tables

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-19 23:59:08 +02:00
parent 35559e3d2b
commit c58a46b7d9
4 changed files with 354 additions and 15 deletions

View File

@@ -1,9 +1,13 @@
import { DataSource } from 'typeorm';
import { Character } from '../../characters/entities/character.entity';
import { ItemDefinition } from '../../items/entities/item-definition.entity';
import { LootTableEntry } from '../../loot/entities/loot-table-entry.entity';
import { LootTable } from '../../loot/entities/loot-table.entity';
import { LocationMonster } from '../../monsters/entities/location-monster.entity';
import { MonsterDefinition } from '../../monsters/entities/monster-definition.entity';
import { LocationConnection } from '../../world/entities/location-connection.entity';
import { LocationDefinition } from '../../world/entities/location-definition.entity';
import { ASH_RAT_LOOT_TABLE_ID, ITEM_IDS, ROAD_BANDIT_LOOT_TABLE_ID } from './item.constants';
import { seedVisibleVerticalSlice } from './vertical-slice.seed';
type Row = Record<string, unknown>;
@@ -67,24 +71,20 @@ function createDataSource(
characterRepository: InMemoryRepository,
monsterRepository: InMemoryRepository,
locationMonsterRepository: InMemoryRepository,
itemRepository: InMemoryRepository = new InMemoryRepository(),
lootTableRepository: InMemoryRepository = new InMemoryRepository(),
lootEntryRepository: InMemoryRepository = new InMemoryRepository(),
): DataSource {
return {
getRepository: jest.fn((entity: unknown) => {
if (entity === LocationDefinition) {
return locationRepository;
}
if (entity === LocationConnection) {
return connectionRepository;
}
if (entity === Character) {
return characterRepository;
}
if (entity === MonsterDefinition) {
return monsterRepository;
}
if (entity === LocationMonster) {
return locationMonsterRepository;
}
if (entity === LocationDefinition) return locationRepository;
if (entity === LocationConnection) return connectionRepository;
if (entity === Character) return characterRepository;
if (entity === MonsterDefinition) return monsterRepository;
if (entity === LocationMonster) return locationMonsterRepository;
if (entity === ItemDefinition) return itemRepository;
if (entity === LootTable) return lootTableRepository;
if (entity === LootTableEntry) return lootEntryRepository;
throw new Error('Unexpected repository');
}),
@@ -256,4 +256,84 @@ describe('seedVisibleVerticalSlice', () => {
}),
]);
});
it('seeds the tier-1 items and both loot tables idempotently and wires them to the monsters', async () => {
const locationRepository = new InMemoryRepository();
const connectionRepository = new InMemoryRepository();
const characterRepository = new InMemoryRepository();
const monsterRepository = new InMemoryRepository();
const locationMonsterRepository = new InMemoryRepository();
const itemRepository = new InMemoryRepository();
const lootTableRepository = new InMemoryRepository();
const lootEntryRepository = new InMemoryRepository();
const dataSource = createDataSource(
locationRepository,
connectionRepository,
characterRepository,
monsterRepository,
locationMonsterRepository,
itemRepository,
lootTableRepository,
lootEntryRepository,
);
await seedVisibleVerticalSlice(dataSource);
await seedVisibleVerticalSlice(dataSource);
expect(itemRepository.rows).toHaveLength(12);
expect(itemRepository.rows).toEqual(
expect.arrayContaining([
expect.objectContaining({
key: 'bandit-blade',
name: 'Räuberklinge',
type: 'WEAPON',
equipmentSlot: 'WEAPON',
rarity: 'COMMON',
weaponDamage: 11,
bonusAttack: 1,
sellPrice: 0,
iconPath: '/images/items/bandit-blade.png',
}),
expect.objectContaining({
key: 'ash-pelt',
name: 'Aschenfell',
type: 'MATERIAL',
equipmentSlot: null,
}),
]),
);
expect(lootTableRepository.rows).toHaveLength(2);
expect(lootEntryRepository.rows).toHaveLength(5);
expect(lootEntryRepository.rows).toEqual(
expect.arrayContaining([
expect.objectContaining({
lootTableId: ASH_RAT_LOOT_TABLE_ID,
itemDefinitionId: ITEM_IDS['ash-pelt'],
position: 1,
dropChance: '0.6000',
}),
expect.objectContaining({
lootTableId: ROAD_BANDIT_LOOT_TABLE_ID,
itemDefinitionId: ITEM_IDS['bandit-blade'],
position: 1,
dropChance: '0.1800',
}),
]),
);
// The Kleiner Heiltrank entry is deliberately deferred (spec §16).
expect(
lootEntryRepository.rows.some(
(row) => row.itemDefinitionId === ITEM_IDS['small-healing-potion'],
),
).toBe(false);
expect(monsterRepository.rows).toEqual(
expect.arrayContaining([
expect.objectContaining({ key: 'ash-rat', lootTableId: ASH_RAT_LOOT_TABLE_ID }),
expect.objectContaining({ key: 'road-bandit', lootTableId: ROAD_BANDIT_LOOT_TABLE_ID }),
]),
);
});
});