Merge branch 'master' into worktree-local-location-view

Brings in the First Loot slice. Resolved additively:

- MonsterDefinition keeps both the new iconPath and master's lootTableId.
- The seed keeps the four-monster pool and the local view content, and
  gives the two new monsters existing loot tables — the road dog shares
  the beast table, the charred looter the raider table.
- The local location view migration moves to 1788700000000 so it orders
  deterministically after the loot migration, which claimed the same
  timestamp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-20 10:38:18 +02:00
77 changed files with 8315 additions and 32 deletions

View File

@@ -1,11 +1,19 @@
import { DataSource } from 'typeorm';
import { DEMO_CHARACTER_ID } from '../../demo/demo-character.constants';
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 { 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 { ITEM_DEFINITIONS, LOOT_TABLES, LOOT_TABLE_ENTRIES } from './item-content';
import {
ASH_RAT_LOOT_TABLE_ID,
ROAD_BANDIT_LOOT_TABLE_ID,
} from './item.constants';
import {
BURNED_ROAD_LOCAL_CONTENT,
SOUTH_GATE_LOCAL_CONTENT,
@@ -27,6 +35,9 @@ export async function seedVisibleVerticalSlice(
const characterRepository = dataSource.getRepository(Character);
const monsterRepository = dataSource.getRepository(MonsterDefinition);
const locationMonsterRepository = dataSource.getRepository(LocationMonster);
const itemRepository = dataSource.getRepository(ItemDefinition);
const lootTableRepository = dataSource.getRepository(LootTable);
const lootEntryRepository = dataSource.getRepository(LootTableEntry);
const locations = [
{
@@ -98,6 +109,15 @@ export async function seedVisibleVerticalSlice(
['fromLocationId', 'toLocationId'],
);
// Content is upserted by its stable key so re-running never duplicates rows
// and never touches player-owned character_items or combat_rewards.
await itemRepository.upsert(ITEM_DEFINITIONS, ['key']);
await lootTableRepository.upsert(LOOT_TABLES, ['key']);
await lootEntryRepository.upsert(LOOT_TABLE_ENTRIES, [
'lootTableId',
'itemDefinitionId',
]);
const monsters = [
{
id: ASH_RAT_MONSTER_ID,
@@ -112,6 +132,7 @@ export async function seedVisibleVerticalSlice(
silverMax: 7,
artworkPath: '/images/monsters/ash-rat.png',
iconPath: '/images/monsters/icons/ash-rat-128.png',
lootTableId: ASH_RAT_LOOT_TABLE_ID,
},
{
id: WILD_ROAD_DOG_MONSTER_ID,
@@ -126,6 +147,9 @@ export async function seedVisibleVerticalSlice(
silverMax: 9,
artworkPath: '/images/monsters/wild-road-dog.png',
iconPath: '/images/monsters/icons/wild-road-dog-128.png',
// Shares the beast table: both are scorched road animals that leave a
// pelt behind. A table of its own waits for content that differs.
lootTableId: ASH_RAT_LOOT_TABLE_ID,
},
{
id: ROAD_BANDIT_MONSTER_ID,
@@ -140,6 +164,7 @@ export async function seedVisibleVerticalSlice(
silverMax: 15,
artworkPath: '/images/monsters/road-bandit.png',
iconPath: '/images/monsters/icons/road-bandit-128.png',
lootTableId: ROAD_BANDIT_LOOT_TABLE_ID,
},
{
id: CHARRED_LOOTER_MONSTER_ID,
@@ -154,6 +179,8 @@ export async function seedVisibleVerticalSlice(
silverMax: 19,
artworkPath: '/images/monsters/charred-looter.png',
iconPath: '/images/monsters/icons/charred-looter-128.png',
// Shares the raider table: same gear, taken from the same caravans.
lootTableId: ROAD_BANDIT_LOOT_TABLE_ID,
},
];
@@ -204,6 +231,7 @@ export async function seedVisibleVerticalSlice(
name: 'Aric Duskwalker',
level: 1,
experience: 0,
silver: 0,
baseHp: 100,
baseAttack: 6,
currentHp: 100,