전투 시뮬레이터 구현

This commit is contained in:
2026-01-18 17:41:13 +00:00
parent 5b51aad6d0
commit 4647b67ba3
14 changed files with 2589 additions and 10 deletions
+27 -5
View File
@@ -3,6 +3,7 @@ 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,
@@ -73,11 +74,17 @@ const resolveCastleArmType = (
return crewTypes.find((crewType) => crewType.id === castleCrewTypeId)?.armType ?? 0;
};
export const buildBattleSimJobPayload = async (
export interface BattleSimEnvironment {
unitSetName: string;
unitSet: UnitSetDefinition;
config: WarEngineConfig;
startYear: number;
}
export const buildBattleSimEnvironment = async (
worldState: WorldStateRow,
request: BattleSimRequestPayload,
profileFallback: string
): Promise<BattleSimJobPayload> => {
): Promise<BattleSimEnvironment> => {
const unitSetName = resolveUnitSetName(worldState, profileFallback);
const unitSet = await loadUnitSetDefinitionByName(unitSetName);
@@ -105,13 +112,28 @@ export const buildBattleSimJobPayload = async (
};
return {
...request,
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: resolveStartYear(worldState),
startYear: environment.startYear,
},
};
};
+1
View File
@@ -133,6 +133,7 @@ const mapGeneralPayload = (payload: BattleSimJobPayload['attackerGeneral']): Gen
intelExp: payload.intel_exp,
strengthExp: payload.strength_exp,
leadershipExp: payload.leadership_exp,
defenceTrain: payload.defence_train,
rank_warnum: payload.warnum,
rank_killnum: payload.killnum,
rank_killcrew: payload.killcrew,
+3 -2
View File
@@ -28,7 +28,7 @@ export const zBattleSimGeneral = z.object({
experience: z.number().int().min(0),
dedication: z.number().int().min(0),
officer_level: z.number().int().min(1),
officer_city: z.number().int().positive(),
officer_city: z.number().int().min(0),
gold: z.number().int().min(0),
rice: z.number().int().min(0),
dex1: z.number().int().min(0),
@@ -36,6 +36,7 @@ export const zBattleSimGeneral = z.object({
dex3: z.number().int().min(0),
dex4: z.number().int().min(0),
dex5: z.number().int().min(0),
defence_train: z.number().int().min(0),
recent_war: z.string().nullable(),
warnum: z.number().int().min(0),
killnum: z.number().int().min(0),
@@ -70,7 +71,7 @@ export const zBattleSimCity = z.object({
export const zBattleSimNation = z.object({
type: z.string().min(1),
tech: z.number().min(0),
level: z.number().int().min(1),
level: z.number().int().min(0),
capital: z.number().int().min(0),
nation: z.number().int().min(0),
name: z.string().min(1),
@@ -0,0 +1,144 @@
import {
ITEM_KEYS,
loadItemModules,
loadNationTraitModules,
loadPersonalityTraitModules,
loadWarTraitModules,
NATION_TRAIT_KEYS,
PERSONALITY_TRAIT_KEYS,
WAR_TRAIT_KEYS,
type ItemModule,
type TraitModule,
} from '@sammo-ts/logic';
export type BattleSimTraitOption = {
key: string;
name: string;
info: string;
};
export type BattleSimItemOption = {
key: string;
name: string;
};
export type BattleSimItemOptions = {
horse: BattleSimItemOption[];
weapon: BattleSimItemOption[];
book: BattleSimItemOption[];
item: BattleSimItemOption[];
};
export type BattleSimDexLevel = {
value: number;
color: string;
label: string;
};
export const BATTLE_SIM_NATION_LEVELS: Array<{ level: number; name: string }> = [
{ level: 0, name: '방랑군' },
{ level: 1, name: '호족' },
{ level: 2, name: '군벌' },
{ level: 3, name: '주자사' },
{ level: 4, name: '주목' },
{ level: 5, name: '공' },
{ level: 6, name: '왕' },
{ level: 7, name: '황제' },
];
export const BATTLE_SIM_CITY_LEVELS: Array<{ level: number; name: string }> = [
{ level: 1, name: '수' },
{ level: 2, name: '진' },
{ level: 3, name: '관' },
{ level: 4, name: '이' },
{ level: 5, name: '소' },
{ level: 6, name: '중' },
{ level: 7, name: '대' },
{ level: 8, name: '특' },
];
export const BATTLE_SIM_DEX_LEVELS: BattleSimDexLevel[] = [
{ value: 0, color: 'navy', label: 'F-' },
{ value: 350, color: 'navy', label: 'F' },
{ value: 1375, color: 'navy', label: 'F+' },
{ value: 3500, color: 'skyblue', label: 'E-' },
{ value: 7125, color: 'skyblue', label: 'E' },
{ value: 12650, color: 'skyblue', label: 'E+' },
{ value: 20475, color: 'seagreen', label: 'D-' },
{ value: 31000, color: 'seagreen', label: 'D' },
{ value: 44625, color: 'seagreen', label: 'D+' },
{ value: 61750, color: 'teal', label: 'C-' },
{ value: 82775, color: 'teal', label: 'C' },
{ value: 108100, color: 'teal', label: 'C+' },
{ value: 138125, color: 'limegreen', label: 'B-' },
{ value: 173250, color: 'limegreen', label: 'B' },
{ value: 213875, color: 'limegreen', label: 'B+' },
{ value: 260400, color: 'darkorange', label: 'A-' },
{ value: 313225, color: 'darkorange', label: 'A' },
{ value: 372750, color: 'darkorange', label: 'A+' },
{ value: 439375, color: 'tomato', label: 'S-' },
{ value: 513500, color: 'tomato', label: 'S' },
{ value: 595525, color: 'tomato', label: 'S+' },
{ value: 685850, color: 'darkviolet', label: 'Z-' },
{ value: 784875, color: 'darkviolet', label: 'Z' },
{ value: 893000, color: 'darkviolet', label: 'Z+' },
{ value: 1010625, color: 'gold', label: 'EX-' },
{ value: 1138150, color: 'gold', label: 'EX' },
{ value: 1275975, color: 'white', label: 'EX+' },
];
const toTraitOption = (module: TraitModule): BattleSimTraitOption => ({
key: module.key,
name: module.name,
info: module.info,
});
let cachedTraitOptions: Promise<{
nationTypes: BattleSimTraitOption[];
warTraits: BattleSimTraitOption[];
personalities: BattleSimTraitOption[];
}> | null = null;
export const loadBattleSimTraitOptions = async (): Promise<{
nationTypes: BattleSimTraitOption[];
warTraits: BattleSimTraitOption[];
personalities: BattleSimTraitOption[];
}> => {
if (!cachedTraitOptions) {
cachedTraitOptions = Promise.all([
loadNationTraitModules([...NATION_TRAIT_KEYS]),
loadWarTraitModules([...WAR_TRAIT_KEYS]),
loadPersonalityTraitModules([...PERSONALITY_TRAIT_KEYS]),
]).then(([nationTraits, warTraits, personalities]) => ({
nationTypes: nationTraits.map(toTraitOption),
warTraits: warTraits.map(toTraitOption),
personalities: personalities.map(toTraitOption),
}));
}
return cachedTraitOptions;
};
let cachedItemOptions: Promise<BattleSimItemOptions> | null = null;
const toItemOption = (module: ItemModule): BattleSimItemOption => ({
key: module.key,
name: module.name,
});
export const loadBattleSimItemOptions = async (): Promise<BattleSimItemOptions> => {
if (!cachedItemOptions) {
cachedItemOptions = loadItemModules([...ITEM_KEYS]).then((modules) => {
const items: BattleSimItemOptions = {
horse: [],
weapon: [],
book: [],
item: [],
};
for (const module of modules) {
items[module.slot].push(toItemOption(module));
}
return items;
});
}
return cachedItemOptions;
};
+1
View File
@@ -38,6 +38,7 @@ export interface BattleSimGeneralPayload {
dex3: number;
dex4: number;
dex5: number;
defence_train: number;
recent_war: string | null;
warnum: number;
killnum: number;
+2 -1
View File
@@ -6,7 +6,8 @@ import { parseUnitSetDefinition, type UnitSetDefinition } from '@sammo-ts/logic'
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const DEFAULT_UNIT_SET_ROOT = path.resolve(__dirname, '..', '..', '..', 'game-engine', 'resources', 'unitset');
const REPO_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
const DEFAULT_UNIT_SET_ROOT = path.resolve(REPO_ROOT, 'resources', 'unitset');
export interface UnitSetLoaderOptions {
unitSetRoot?: string;