141 lines
4.7 KiB
TypeScript
141 lines
4.7 KiB
TypeScript
import type { WorldStateRow } from '../context.js';
|
|
import type { BattleSimJobPayload, BattleSimRequestPayload } from './types.js';
|
|
import { loadUnitSetDefinitionByName } from './unitSetLoader.js';
|
|
import type { WarEngineConfig } from '@sammo-ts/logic';
|
|
import { asRecord } from '@sammo-ts/common';
|
|
import type { UnitSetDefinition } from '@sammo-ts/logic';
|
|
|
|
const DEFAULT_WAR_CONFIG = {
|
|
armPerPhase: 500,
|
|
maxTrainByCommand: 100,
|
|
maxAtmosByCommand: 100,
|
|
maxTrainByWar: 110,
|
|
maxAtmosByWar: 150,
|
|
};
|
|
|
|
const resolveNumber = (record: Record<string, unknown>, keys: string[], fallback: number): number => {
|
|
for (const key of keys) {
|
|
const value = record[key];
|
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
return value;
|
|
}
|
|
}
|
|
return fallback;
|
|
};
|
|
|
|
const resolveUnitSetName = (worldState: WorldStateRow, fallback: string): string => {
|
|
const config = asRecord(worldState.config);
|
|
const environment = asRecord(config.environment ?? config.map);
|
|
const unitSet = environment.unitSet;
|
|
if (typeof unitSet === 'string' && unitSet.trim().length > 0) {
|
|
return unitSet;
|
|
}
|
|
return fallback;
|
|
};
|
|
|
|
const resolveStartYear = (worldState: WorldStateRow): number => {
|
|
const meta = asRecord(worldState.meta);
|
|
const scenarioMeta = asRecord(meta.scenarioMeta);
|
|
const startYear = scenarioMeta.startYear;
|
|
if (typeof startYear === 'number' && Number.isFinite(startYear)) {
|
|
return startYear;
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
const resolveCastleCrewTypeId = (unitSet: {
|
|
crewTypes?: Array<{ id: number; name: string; requirements: Array<{ type: string }> }>;
|
|
defaultCrewTypeId?: number;
|
|
}): number => {
|
|
const crewTypes = unitSet.crewTypes ?? [];
|
|
const byName = crewTypes.find((crewType) => crewType.name.includes('성벽'));
|
|
if (byName) {
|
|
return byName.id;
|
|
}
|
|
const byRequirement = crewTypes.find((crewType) =>
|
|
crewType.requirements.some((requirement) => requirement.type === 'Impossible')
|
|
);
|
|
if (byRequirement) {
|
|
return byRequirement.id;
|
|
}
|
|
if (typeof unitSet.defaultCrewTypeId === 'number') {
|
|
return unitSet.defaultCrewTypeId;
|
|
}
|
|
return crewTypes[0]?.id ?? 0;
|
|
};
|
|
|
|
const resolveCastleArmType = (
|
|
unitSet: {
|
|
crewTypes?: Array<{ id: number; armType: number }>;
|
|
},
|
|
castleCrewTypeId: number
|
|
): number => {
|
|
const crewTypes = unitSet.crewTypes ?? [];
|
|
return crewTypes.find((crewType) => crewType.id === castleCrewTypeId)?.armType ?? 0;
|
|
};
|
|
|
|
export interface BattleSimEnvironment {
|
|
unitSetName: string;
|
|
unitSet: UnitSetDefinition;
|
|
config: WarEngineConfig;
|
|
startYear: number;
|
|
}
|
|
|
|
export const buildBattleSimEnvironment = async (
|
|
worldState: WorldStateRow,
|
|
profileFallback: string
|
|
): Promise<BattleSimEnvironment> => {
|
|
const unitSetName = resolveUnitSetName(worldState, profileFallback);
|
|
const unitSet = await loadUnitSetDefinitionByName(unitSetName);
|
|
|
|
const configRecord = asRecord(worldState.config);
|
|
const constValues = asRecord(configRecord.const ?? configRecord.consts);
|
|
const castleCrewTypeId = resolveNumber(constValues, ['castleCrewTypeId'], resolveCastleCrewTypeId(unitSet));
|
|
const castleArmType = resolveCastleArmType(unitSet, castleCrewTypeId);
|
|
|
|
const config: WarEngineConfig = {
|
|
armPerPhase: resolveNumber(constValues, ['armPerPhase', 'armperphase'], DEFAULT_WAR_CONFIG.armPerPhase),
|
|
maxTrainByCommand: resolveNumber(constValues, ['maxTrainByCommand'], DEFAULT_WAR_CONFIG.maxTrainByCommand),
|
|
maxAtmosByCommand: resolveNumber(constValues, ['maxAtmosByCommand'], DEFAULT_WAR_CONFIG.maxAtmosByCommand),
|
|
maxTrainByWar: resolveNumber(constValues, ['maxTrainByWar'], DEFAULT_WAR_CONFIG.maxTrainByWar),
|
|
maxAtmosByWar: resolveNumber(constValues, ['maxAtmosByWar'], DEFAULT_WAR_CONFIG.maxAtmosByWar),
|
|
maxGeneralStat: resolveNumber(constValues, ['maxLevel'], 255),
|
|
castleCrewTypeId,
|
|
armTypes: {
|
|
footman: 1,
|
|
archer: 2,
|
|
cavalry: 3,
|
|
wizard: 4,
|
|
siege: 5,
|
|
misc: 6,
|
|
castle: castleArmType,
|
|
},
|
|
};
|
|
|
|
return {
|
|
unitSetName,
|
|
unitSet,
|
|
config,
|
|
startYear: resolveStartYear(worldState),
|
|
};
|
|
};
|
|
|
|
export const buildBattleSimJobPayload = async (
|
|
worldState: WorldStateRow,
|
|
request: BattleSimRequestPayload,
|
|
profileFallback: string
|
|
): Promise<BattleSimJobPayload> => {
|
|
const environment = await buildBattleSimEnvironment(worldState, profileFallback);
|
|
|
|
return {
|
|
...request,
|
|
unitSet: environment.unitSet,
|
|
config: environment.config,
|
|
time: {
|
|
year: request.year,
|
|
month: request.month,
|
|
startYear: environment.startYear,
|
|
},
|
|
};
|
|
};
|