Files
core2026/app/game-engine/src/scenario/scenarioSeeder.ts
T
Hide_D 124b070dc9 feat: add initial Pokémon map data and loader
- Introduced a new JSON file `map_pokemon_v1.json` containing detailed city data for the Pokémon map, including attributes like population, agriculture, commerce, and connections.
- Implemented a map loader in `mapLoader.ts` to read and parse map definitions from JSON files, allowing for dynamic loading of map data.
- Created a script `generate-map-data.mjs` to convert legacy PHP map data into the new JSON format, ensuring compatibility with the updated map system.
2025-12-29 05:59:15 +00:00

269 lines
9.0 KiB
TypeScript

import type { Prisma } from '@prisma/client';
import { createPostgresConnector } from '@sammo-ts/infra';
import {
buildScenarioBootstrap,
type ScenarioBootstrapWarning,
type WorldSeedPayload,
} from '@sammo-ts/logic';
import type { MapLoaderOptions } from './mapLoader.js';
import { loadMapDefinitionByName } from './mapLoader.js';
import type { ScenarioLoaderOptions } from './scenarioLoader.js';
import { loadScenarioDefinitionById } from './scenarioLoader.js';
const DEFAULT_TICK_SECONDS = 120 * 60;
const DEFAULT_GENERAL_GOLD = 1000;
const DEFAULT_GENERAL_RICE = 1000;
export interface ScenarioSeedOptions {
scenarioId: number;
databaseUrl: string;
scenarioOptions?: ScenarioLoaderOptions;
mapOptions?: MapLoaderOptions;
resetTables?: boolean;
now?: Date;
tickSeconds?: number;
includeNeutralNationInSeed?: boolean;
defaultGeneralGold?: number;
defaultGeneralRice?: number;
}
export interface ScenarioSeedResult {
seed: WorldSeedPayload;
warnings: ScenarioBootstrapWarning[];
}
const asJson = (value: unknown): Prisma.InputJsonValue =>
value as Prisma.InputJsonValue;
const resolveGeneralAge = (
startYear: number | null,
birthYear: number
): number => {
if (startYear === null || birthYear <= 0) {
return 20;
}
return Math.max(startYear - birthYear, 0);
};
const buildEventRows = (
rows: unknown[],
targetOverride?: string
): Prisma.EventCreateManyInput[] => {
const result: Prisma.EventCreateManyInput[] = [];
for (const row of rows) {
if (!Array.isArray(row)) {
continue;
}
if (targetOverride) {
const [condition, ...actions] = row;
result.push({
targetCode: targetOverride,
priority: 0,
condition: asJson(condition ?? null),
action: asJson(actions),
meta: asJson({ source: targetOverride }),
});
continue;
}
const [target, priority, condition, ...actions] = row;
if (typeof target !== 'string' || typeof priority !== 'number') {
continue;
}
result.push({
targetCode: target,
priority,
condition: asJson(condition ?? null),
action: asJson(actions),
meta: asJson({ source: 'scenario' }),
});
}
return result;
};
// 시나리오 초기 데이터를 로드해 DB에 저장한다.
export const seedScenarioToDatabase = async (
options: ScenarioSeedOptions
): Promise<ScenarioSeedResult> => {
const scenario = await loadScenarioDefinitionById(
options.scenarioId,
options.scenarioOptions
);
const map = await loadMapDefinitionByName(
scenario.config.environment.mapName,
options.mapOptions
);
const { seed, warnings } = buildScenarioBootstrap({
scenario,
map,
options: {
includeNeutralNationInSeed:
options.includeNeutralNationInSeed ?? true,
},
});
const connector = createPostgresConnector({ url: options.databaseUrl });
const now = options.now ?? new Date();
const tickSeconds = options.tickSeconds ?? DEFAULT_TICK_SECONDS;
const generalGold = options.defaultGeneralGold ?? DEFAULT_GENERAL_GOLD;
const generalRice = options.defaultGeneralRice ?? DEFAULT_GENERAL_RICE;
await connector.connect();
try {
const prisma = connector.prisma;
if (options.resetTables ?? true) {
await prisma.event.deleteMany();
await prisma.diplomacy.deleteMany();
await prisma.general.deleteMany();
await prisma.city.deleteMany();
await prisma.nation.deleteMany();
await prisma.worldState.deleteMany();
}
await prisma.worldState.create({
data: {
scenarioCode: String(options.scenarioId),
currentYear: scenario.startYear ?? 0,
currentMonth: 1,
tickSeconds,
config: asJson(seed.scenarioConfig),
meta: asJson({
scenarioId: options.scenarioId,
scenarioMeta: seed.scenarioMeta,
}),
},
});
if (seed.nations.length > 0) {
await prisma.nation.createMany({
data: seed.nations.map((nation) => ({
id: nation.id,
name: nation.name,
color: nation.color,
capitalCityId: nation.capitalCityId ?? null,
gold: nation.gold,
rice: nation.rice,
tech: nation.tech,
level: nation.level,
typeCode: nation.typeCode,
meta: asJson({
infoText: nation.infoText,
cityIds: nation.cityIds,
}),
})),
});
}
if (seed.cities.length > 0) {
await prisma.city.createMany({
data: seed.cities.map((city) => ({
id: city.id,
name: city.name,
level: city.level,
nationId: city.nationId,
supplyState: city.supplyState,
frontState: city.frontState,
population: city.population,
populationMax: city.populationMax,
agriculture: city.agriculture,
agricultureMax: city.agricultureMax,
commerce: city.commerce,
commerceMax: city.commerceMax,
security: city.security,
securityMax: city.securityMax,
trust: city.trust,
trade: city.trade,
defence: city.defence,
defenceMax: city.defenceMax,
wall: city.wall,
wallMax: city.wallMax,
region: city.region,
conflict: asJson({}),
meta: asJson({
position: city.position,
connections: city.connections,
...city.meta,
}),
})),
});
}
if (seed.generals.length > 0) {
await prisma.general.createMany({
data: seed.generals.map((general) => ({
id: general.id,
name: general.name,
nationId: general.nationId,
cityId: general.cityId,
npcState: general.npcType,
affinity: general.affinity,
bornYear: general.birthYear,
deadYear: general.deathYear,
picture:
general.picture === null
? null
: String(general.picture),
leadership: general.stats.leadership,
strength: general.stats.strength,
intel: general.stats.intelligence,
officerLevel: general.officerLevel,
gold: generalGold,
rice: generalRice,
crewTypeId: general.crewTypeId,
horseCode: general.horse ?? 'None',
weaponCode: general.weapon ?? 'None',
bookCode: general.book ?? 'None',
itemCode: general.item ?? 'None',
turnTime: now,
age: resolveGeneralAge(
scenario.startYear ?? null,
general.birthYear
),
personalCode: general.personality ?? 'None',
specialCode: general.special ?? 'None',
special2Code: general.specialWar ?? 'None',
lastTurn: asJson({}),
meta: asJson({
npcType: general.npcType,
crewTypeId: general.crewTypeId,
...general.meta,
}),
penalty: asJson({}),
})),
});
}
if (seed.diplomacy.length > 0) {
await prisma.diplomacy.createMany({
data: seed.diplomacy.map((row) => ({
srcNationId: row.fromNationId,
destNationId: row.toNationId,
stateCode: row.state,
term: row.durationMonths,
meta: asJson({}),
})),
});
}
const eventRows = [
...buildEventRows(seed.events),
...buildEventRows(seed.initialEvents, 'initial'),
];
if (eventRows.length > 0) {
await prisma.event.createMany({
data: eventRows,
});
}
} finally {
await connector.disconnect();
}
return { seed, warnings };
};