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:
216
apps/api/src/database/seeds/item-content.ts
Normal file
216
apps/api/src/database/seeds/item-content.ts
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
import { EquipmentSlot } from '../../items/equipment-slot.enum';
|
||||||
|
import { ItemRarity } from '../../items/item-rarity.enum';
|
||||||
|
import { ItemType } from '../../items/item-type.enum';
|
||||||
|
import {
|
||||||
|
ASH_RAT_LOOT_TABLE_ID,
|
||||||
|
ITEM_IDS,
|
||||||
|
ItemKey,
|
||||||
|
ROAD_BANDIT_LOOT_TABLE_ID,
|
||||||
|
} from './item.constants';
|
||||||
|
|
||||||
|
export interface SeedItemDefinition {
|
||||||
|
id: string;
|
||||||
|
key: ItemKey;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
type: ItemType;
|
||||||
|
equipmentSlot: EquipmentSlot | null;
|
||||||
|
rarity: ItemRarity;
|
||||||
|
tier: number;
|
||||||
|
requiredLevel: number;
|
||||||
|
weaponDamage: number;
|
||||||
|
bonusHp: number;
|
||||||
|
bonusAttack: number;
|
||||||
|
bonusArmor: number;
|
||||||
|
sellPrice: number;
|
||||||
|
iconPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function item(
|
||||||
|
key: ItemKey,
|
||||||
|
name: string,
|
||||||
|
description: string,
|
||||||
|
type: ItemType,
|
||||||
|
equipmentSlot: EquipmentSlot | null,
|
||||||
|
rarity: ItemRarity,
|
||||||
|
stats: Partial<Pick<SeedItemDefinition, 'weaponDamage' | 'bonusHp' | 'bonusAttack' | 'bonusArmor'>> = {},
|
||||||
|
): SeedItemDefinition {
|
||||||
|
return {
|
||||||
|
id: ITEM_IDS[key],
|
||||||
|
key,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
type,
|
||||||
|
equipmentSlot,
|
||||||
|
rarity,
|
||||||
|
tier: 1,
|
||||||
|
requiredLevel: 1,
|
||||||
|
weaponDamage: stats.weaponDamage ?? 0,
|
||||||
|
bonusHp: stats.bonusHp ?? 0,
|
||||||
|
bonusAttack: stats.bonusAttack ?? 0,
|
||||||
|
bonusArmor: stats.bonusArmor ?? 0,
|
||||||
|
// Always 0: no merchants exist in Slice 0.4, and the balancing doc's
|
||||||
|
// Grenzmarken table lists purchase prices, not sell prices.
|
||||||
|
sellPrice: 0,
|
||||||
|
iconPath: `/images/items/${key}.png`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats from docs/Ashen_Realms_Balancing_Items_Loot_Design_V1.md §19.
|
||||||
|
export const ITEM_DEFINITIONS: SeedItemDefinition[] = [
|
||||||
|
item(
|
||||||
|
'worn-short-sword',
|
||||||
|
'Abgenutztes Kurzschwert',
|
||||||
|
'Die Klinge eines Rekruten, öfter geschliffen als geführt.',
|
||||||
|
ItemType.WEAPON,
|
||||||
|
EquipmentSlot.WEAPON,
|
||||||
|
ItemRarity.COMMON,
|
||||||
|
{ weaponDamage: 8 },
|
||||||
|
),
|
||||||
|
item(
|
||||||
|
'bandit-blade',
|
||||||
|
'Räuberklinge',
|
||||||
|
'Eine grob gezahnte Klinge, geschmiedet für schnelle Überfälle.',
|
||||||
|
ItemType.WEAPON,
|
||||||
|
EquipmentSlot.WEAPON,
|
||||||
|
ItemRarity.COMMON,
|
||||||
|
{ weaponDamage: 11, bonusAttack: 1 },
|
||||||
|
),
|
||||||
|
item(
|
||||||
|
'ash-blade',
|
||||||
|
'Aschenklinge',
|
||||||
|
'In der Glut der Aschenfelder gehärtet; die Schneide glimmt noch.',
|
||||||
|
ItemType.WEAPON,
|
||||||
|
EquipmentSlot.WEAPON,
|
||||||
|
ItemRarity.RARE,
|
||||||
|
{ weaponDamage: 15, bonusAttack: 2 },
|
||||||
|
),
|
||||||
|
item(
|
||||||
|
'bandit-hood',
|
||||||
|
'Räuberhaube',
|
||||||
|
'Vernarbtes Leder, das Gesicht und Absicht des Trägers verbirgt.',
|
||||||
|
ItemType.ARMOR,
|
||||||
|
EquipmentSlot.HEAD,
|
||||||
|
ItemRarity.COMMON,
|
||||||
|
{ bonusArmor: 3, bonusHp: 5 },
|
||||||
|
),
|
||||||
|
item(
|
||||||
|
'reinforced-leather-jacket',
|
||||||
|
'Verstärkte Lederjacke',
|
||||||
|
'Mit Eisenplatten benähtes Leder, schwer und verlässlich.',
|
||||||
|
ItemType.ARMOR,
|
||||||
|
EquipmentSlot.CHEST,
|
||||||
|
ItemRarity.RARE,
|
||||||
|
{ bonusArmor: 7, bonusHp: 10 },
|
||||||
|
),
|
||||||
|
item(
|
||||||
|
'raider-gloves',
|
||||||
|
'Plündererhandschuhe',
|
||||||
|
'Beschlagene Handschuhe, abgegriffen von fremdem Gut.',
|
||||||
|
ItemType.ARMOR,
|
||||||
|
EquipmentSlot.HANDS,
|
||||||
|
ItemRarity.COMMON,
|
||||||
|
{ bonusArmor: 3, bonusAttack: 1 },
|
||||||
|
),
|
||||||
|
item(
|
||||||
|
'guardsman-legs',
|
||||||
|
'Wachmannsbeinkleid',
|
||||||
|
'Beinzeug der Grenzwacht, an den Knien geflickt.',
|
||||||
|
ItemType.ARMOR,
|
||||||
|
EquipmentSlot.LEGS,
|
||||||
|
ItemRarity.RARE,
|
||||||
|
{ bonusArmor: 5, bonusHp: 5 },
|
||||||
|
),
|
||||||
|
item(
|
||||||
|
'ash-boots',
|
||||||
|
'Aschenstiefel',
|
||||||
|
'Stiefel, die durch glimmende Felder getragen wurden und blieben.',
|
||||||
|
ItemType.ARMOR,
|
||||||
|
EquipmentSlot.FEET,
|
||||||
|
ItemRarity.RARE,
|
||||||
|
{ bonusArmor: 4, bonusHp: 5 },
|
||||||
|
),
|
||||||
|
item(
|
||||||
|
'borderwatch-sigil',
|
||||||
|
'Zeichen der Grenzwacht',
|
||||||
|
'Das Wappen eines Turms, den es nicht mehr gibt.',
|
||||||
|
ItemType.ARMOR,
|
||||||
|
EquipmentSlot.AMULET,
|
||||||
|
ItemRarity.RARE,
|
||||||
|
{ bonusAttack: 3, bonusHp: 10 },
|
||||||
|
),
|
||||||
|
item(
|
||||||
|
'burned-captain-pendant',
|
||||||
|
'Anhänger des verbrannten Hauptmanns',
|
||||||
|
'Ein Schädel aus Schlacke, in dem die Glut nie erlosch.',
|
||||||
|
ItemType.ARMOR,
|
||||||
|
EquipmentSlot.AMULET,
|
||||||
|
ItemRarity.EPIC,
|
||||||
|
{ bonusAttack: 3, bonusHp: 15, bonusArmor: 2 },
|
||||||
|
),
|
||||||
|
// Seeded as content only. Slice 0.4 implements no consumable use, and the
|
||||||
|
// Straßenräuber loot entry for it is deliberately deferred (spec §16).
|
||||||
|
item(
|
||||||
|
'small-healing-potion',
|
||||||
|
'Kleiner Heiltrank',
|
||||||
|
'Ein bitterer Sud, der Wunden für einen Atemzug vergessen lässt.',
|
||||||
|
ItemType.CONSUMABLE,
|
||||||
|
null,
|
||||||
|
ItemRarity.COMMON,
|
||||||
|
),
|
||||||
|
item(
|
||||||
|
'ash-pelt',
|
||||||
|
'Aschenfell',
|
||||||
|
'Versengtes Fell, zäh wie Leder und grau von Ascheflug.',
|
||||||
|
ItemType.MATERIAL,
|
||||||
|
null,
|
||||||
|
ItemRarity.COMMON,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
export const LOOT_TABLES = [
|
||||||
|
{ id: ASH_RAT_LOOT_TABLE_ID, key: 'ash-rat-loot', name: 'Aschenratte Beute' },
|
||||||
|
{ id: ROAD_BANDIT_LOOT_TABLE_ID, key: 'road-bandit-loot', name: 'Straßenräuber Beute' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface SeedLootTableEntry {
|
||||||
|
lootTableId: string;
|
||||||
|
itemDefinitionId: string;
|
||||||
|
position: number;
|
||||||
|
dropChance: string;
|
||||||
|
minQuantity: number;
|
||||||
|
maxQuantity: number;
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function entry(
|
||||||
|
lootTableId: string,
|
||||||
|
key: ItemKey,
|
||||||
|
position: number,
|
||||||
|
dropChance: string,
|
||||||
|
): SeedLootTableEntry {
|
||||||
|
return {
|
||||||
|
lootTableId,
|
||||||
|
itemDefinitionId: ITEM_IDS[key],
|
||||||
|
position,
|
||||||
|
dropChance,
|
||||||
|
minQuantity: 1,
|
||||||
|
maxQuantity: 1,
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop chances from docs/Ashen_Realms_Balancing_Items_Loot_Design_V1.md §27–28.
|
||||||
|
* Every entry is an independent roll (spec §17), rolled in `position` order.
|
||||||
|
*
|
||||||
|
* DEFERRED: the Straßenräuber table also lists 10 % Kleiner Heiltrank. It is
|
||||||
|
* omitted here because Slice 0.4 implements no consumables (spec §16).
|
||||||
|
*/
|
||||||
|
export const LOOT_TABLE_ENTRIES: SeedLootTableEntry[] = [
|
||||||
|
entry(ASH_RAT_LOOT_TABLE_ID, 'ash-pelt', 1, '0.6000'),
|
||||||
|
entry(ASH_RAT_LOOT_TABLE_ID, 'worn-short-sword', 2, '0.0800'),
|
||||||
|
entry(ROAD_BANDIT_LOOT_TABLE_ID, 'bandit-blade', 1, '0.1800'),
|
||||||
|
entry(ROAD_BANDIT_LOOT_TABLE_ID, 'bandit-hood', 2, '0.1200'),
|
||||||
|
entry(ROAD_BANDIT_LOOT_TABLE_ID, 'raider-gloves', 3, '0.0800'),
|
||||||
|
];
|
||||||
20
apps/api/src/database/seeds/item.constants.ts
Normal file
20
apps/api/src/database/seeds/item.constants.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Stable content ids, in art-sheet order. Aschenfell (no sheet entry) is last.
|
||||||
|
export const ITEM_IDS = {
|
||||||
|
'worn-short-sword': '50000000-0000-4000-8000-000000000001',
|
||||||
|
'bandit-blade': '50000000-0000-4000-8000-000000000002',
|
||||||
|
'ash-blade': '50000000-0000-4000-8000-000000000003',
|
||||||
|
'bandit-hood': '50000000-0000-4000-8000-000000000004',
|
||||||
|
'reinforced-leather-jacket': '50000000-0000-4000-8000-000000000005',
|
||||||
|
'raider-gloves': '50000000-0000-4000-8000-000000000006',
|
||||||
|
'guardsman-legs': '50000000-0000-4000-8000-000000000007',
|
||||||
|
'ash-boots': '50000000-0000-4000-8000-000000000008',
|
||||||
|
'borderwatch-sigil': '50000000-0000-4000-8000-000000000009',
|
||||||
|
'burned-captain-pendant': '50000000-0000-4000-8000-00000000000a',
|
||||||
|
'small-healing-potion': '50000000-0000-4000-8000-00000000000b',
|
||||||
|
'ash-pelt': '50000000-0000-4000-8000-00000000000c',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type ItemKey = keyof typeof ITEM_IDS;
|
||||||
|
|
||||||
|
export const ASH_RAT_LOOT_TABLE_ID = '60000000-0000-4000-8000-000000000001';
|
||||||
|
export const ROAD_BANDIT_LOOT_TABLE_ID = '60000000-0000-4000-8000-000000000002';
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import { Character } from '../../characters/entities/character.entity';
|
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 { LocationMonster } from '../../monsters/entities/location-monster.entity';
|
||||||
import { MonsterDefinition } from '../../monsters/entities/monster-definition.entity';
|
import { MonsterDefinition } from '../../monsters/entities/monster-definition.entity';
|
||||||
import { LocationConnection } from '../../world/entities/location-connection.entity';
|
import { LocationConnection } from '../../world/entities/location-connection.entity';
|
||||||
import { LocationDefinition } from '../../world/entities/location-definition.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';
|
import { seedVisibleVerticalSlice } from './vertical-slice.seed';
|
||||||
|
|
||||||
type Row = Record<string, unknown>;
|
type Row = Record<string, unknown>;
|
||||||
@@ -67,24 +71,20 @@ function createDataSource(
|
|||||||
characterRepository: InMemoryRepository,
|
characterRepository: InMemoryRepository,
|
||||||
monsterRepository: InMemoryRepository,
|
monsterRepository: InMemoryRepository,
|
||||||
locationMonsterRepository: InMemoryRepository,
|
locationMonsterRepository: InMemoryRepository,
|
||||||
|
itemRepository: InMemoryRepository = new InMemoryRepository(),
|
||||||
|
lootTableRepository: InMemoryRepository = new InMemoryRepository(),
|
||||||
|
lootEntryRepository: InMemoryRepository = new InMemoryRepository(),
|
||||||
): DataSource {
|
): DataSource {
|
||||||
return {
|
return {
|
||||||
getRepository: jest.fn((entity: unknown) => {
|
getRepository: jest.fn((entity: unknown) => {
|
||||||
if (entity === LocationDefinition) {
|
if (entity === LocationDefinition) return locationRepository;
|
||||||
return locationRepository;
|
if (entity === LocationConnection) return connectionRepository;
|
||||||
}
|
if (entity === Character) return characterRepository;
|
||||||
if (entity === LocationConnection) {
|
if (entity === MonsterDefinition) return monsterRepository;
|
||||||
return connectionRepository;
|
if (entity === LocationMonster) return locationMonsterRepository;
|
||||||
}
|
if (entity === ItemDefinition) return itemRepository;
|
||||||
if (entity === Character) {
|
if (entity === LootTable) return lootTableRepository;
|
||||||
return characterRepository;
|
if (entity === LootTableEntry) return lootEntryRepository;
|
||||||
}
|
|
||||||
if (entity === MonsterDefinition) {
|
|
||||||
return monsterRepository;
|
|
||||||
}
|
|
||||||
if (entity === LocationMonster) {
|
|
||||||
return locationMonsterRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Unexpected repository');
|
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 }),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import { DEMO_CHARACTER_ID } from '../../demo/demo-character.constants';
|
import { DEMO_CHARACTER_ID } from '../../demo/demo-character.constants';
|
||||||
import { Character } from '../../characters/entities/character.entity';
|
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 { EncounterType } from '../../monsters/entities/encounter-type.enum';
|
||||||
import { LocationMonster } from '../../monsters/entities/location-monster.entity';
|
import { LocationMonster } from '../../monsters/entities/location-monster.entity';
|
||||||
import { MonsterDefinition } from '../../monsters/entities/monster-definition.entity';
|
import { MonsterDefinition } from '../../monsters/entities/monster-definition.entity';
|
||||||
import { LocationConnection } from '../../world/entities/location-connection.entity';
|
import { LocationConnection } from '../../world/entities/location-connection.entity';
|
||||||
import { LocationDefinition } from '../../world/entities/location-definition.entity';
|
import { LocationDefinition } from '../../world/entities/location-definition.entity';
|
||||||
|
import {
|
||||||
|
ASH_RAT_LOOT_TABLE_ID,
|
||||||
|
ROAD_BANDIT_LOOT_TABLE_ID,
|
||||||
|
} from './item.constants';
|
||||||
|
import { ITEM_DEFINITIONS, LOOT_TABLES, LOOT_TABLE_ENTRIES } from './item-content';
|
||||||
import {
|
import {
|
||||||
ASH_RAT_MONSTER_ID,
|
ASH_RAT_MONSTER_ID,
|
||||||
BURNED_ROAD_ID,
|
BURNED_ROAD_ID,
|
||||||
@@ -21,6 +29,9 @@ export async function seedVisibleVerticalSlice(
|
|||||||
const characterRepository = dataSource.getRepository(Character);
|
const characterRepository = dataSource.getRepository(Character);
|
||||||
const monsterRepository = dataSource.getRepository(MonsterDefinition);
|
const monsterRepository = dataSource.getRepository(MonsterDefinition);
|
||||||
const locationMonsterRepository = dataSource.getRepository(LocationMonster);
|
const locationMonsterRepository = dataSource.getRepository(LocationMonster);
|
||||||
|
const itemRepository = dataSource.getRepository(ItemDefinition);
|
||||||
|
const lootTableRepository = dataSource.getRepository(LootTable);
|
||||||
|
const lootEntryRepository = dataSource.getRepository(LootTableEntry);
|
||||||
|
|
||||||
const locations = [
|
const locations = [
|
||||||
{
|
{
|
||||||
@@ -95,6 +106,15 @@ export async function seedVisibleVerticalSlice(
|
|||||||
['fromLocationId', 'toLocationId'],
|
['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 = [
|
const monsters = [
|
||||||
{
|
{
|
||||||
id: ASH_RAT_MONSTER_ID,
|
id: ASH_RAT_MONSTER_ID,
|
||||||
@@ -108,6 +128,7 @@ export async function seedVisibleVerticalSlice(
|
|||||||
silverMin: 4,
|
silverMin: 4,
|
||||||
silverMax: 7,
|
silverMax: 7,
|
||||||
artworkPath: '/images/monsters/ash-rat.png',
|
artworkPath: '/images/monsters/ash-rat.png',
|
||||||
|
lootTableId: ASH_RAT_LOOT_TABLE_ID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ROAD_BANDIT_MONSTER_ID,
|
id: ROAD_BANDIT_MONSTER_ID,
|
||||||
@@ -121,6 +142,7 @@ export async function seedVisibleVerticalSlice(
|
|||||||
silverMin: 9,
|
silverMin: 9,
|
||||||
silverMax: 15,
|
silverMax: 15,
|
||||||
artworkPath: '/images/monsters/road-bandit.png',
|
artworkPath: '/images/monsters/road-bandit.png',
|
||||||
|
lootTableId: ROAD_BANDIT_LOOT_TABLE_ID,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let ashRatId = ASH_RAT_MONSTER_ID;
|
let ashRatId = ASH_RAT_MONSTER_ID;
|
||||||
@@ -176,6 +198,7 @@ export async function seedVisibleVerticalSlice(
|
|||||||
name: 'Aric Duskwalker',
|
name: 'Aric Duskwalker',
|
||||||
level: 1,
|
level: 1,
|
||||||
experience: 0,
|
experience: 0,
|
||||||
|
silver: 0,
|
||||||
baseHp: 100,
|
baseHp: 100,
|
||||||
baseAttack: 6,
|
baseAttack: 6,
|
||||||
currentHp: 100,
|
currentHp: 100,
|
||||||
|
|||||||
Reference in New Issue
Block a user