feat: seed Aschenratte and Straßenräuber for Verbrannte Straße

Adds the two starter monster definitions and their location-monster
mapping (weights 70/30) to the vertical-slice seed, following the
same findOneBy/update-or-insert pattern used for locations, plus an
upsert on (locationId, monsterId) for the mapping. Copies the source
artwork into the served images/monsters directory and extends the
seed spec harness to cover both idempotent inserts.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-19 12:08:26 +02:00
parent e04827cd5a
commit 9423c28bd8
5 changed files with 154 additions and 2 deletions

View File

@@ -1,9 +1,17 @@
import { DataSource } from 'typeorm';
import { DEMO_CHARACTER_ID } from '../../demo/demo-character.constants';
import { Character } from '../../characters/entities/character.entity';
import { EncounterType } from '../../monsters/entities/encounter-type.enum';
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 { BURNED_ROAD_ID, SOUTH_GATE_ID } from './vertical-slice.constants';
import {
ASH_RAT_MONSTER_ID,
BURNED_ROAD_ID,
ROAD_BANDIT_MONSTER_ID,
SOUTH_GATE_ID,
} from './vertical-slice.constants';
export async function seedVisibleVerticalSlice(
dataSource: DataSource,
@@ -11,6 +19,9 @@ export async function seedVisibleVerticalSlice(
const locationRepository = dataSource.getRepository(LocationDefinition);
const connectionRepository = dataSource.getRepository(LocationConnection);
const characterRepository = dataSource.getRepository(Character);
const monsterRepository = dataSource.getRepository(MonsterDefinition);
const locationMonsterRepository =
dataSource.getRepository(LocationMonster);
const locations = [
{
@@ -85,6 +96,77 @@ export async function seedVisibleVerticalSlice(
['fromLocationId', 'toLocationId'],
);
const monsters = [
{
id: ASH_RAT_MONSTER_ID,
key: 'ash-rat',
name: 'Aschenratte',
level: 1,
maxHp: 45,
attack: 5,
armor: 0,
experienceReward: 8,
silverMin: 4,
silverMax: 7,
artworkPath: '/images/monsters/ash-rat.png',
},
{
id: ROAD_BANDIT_MONSTER_ID,
key: 'road-bandit',
name: 'Straßenräuber',
level: 2,
maxHp: 75,
attack: 9,
armor: 5,
experienceReward: 16,
silverMin: 9,
silverMax: 15,
artworkPath: '/images/monsters/road-bandit.png',
},
];
let ashRatId = ASH_RAT_MONSTER_ID;
let roadBanditId = ROAD_BANDIT_MONSTER_ID;
for (const monster of monsters) {
const existingMonster = await monsterRepository.findOneBy({
key: monster.key,
});
const { id, key, ...definition } = monster;
const persistedId = existingMonster?.id ?? id;
if (existingMonster) {
await monsterRepository.update(existingMonster.id, definition);
} else {
await monsterRepository.insert(monster);
}
if (key === 'ash-rat') {
ashRatId = persistedId;
} else {
roadBanditId = persistedId;
}
}
await locationMonsterRepository.upsert(
[
{
locationId: burnedRoadId,
monsterId: ashRatId,
weight: 70,
encounterType: EncounterType.NORMAL,
enabled: true,
},
{
locationId: burnedRoadId,
monsterId: roadBanditId,
weight: 30,
encounterType: EncounterType.NORMAL,
enabled: true,
},
],
['locationId', 'monsterId'],
);
const existing = await characterRepository.findOneBy({
id: DEMO_CHARACTER_ID,
});