diff --git a/app/game-engine/src/turn/reservedTurnActionContext.ts b/app/game-engine/src/turn/reservedTurnActionContext.ts index 08176f3..6869971 100644 --- a/app/game-engine/src/turn/reservedTurnActionContext.ts +++ b/app/game-engine/src/turn/reservedTurnActionContext.ts @@ -1,437 +1,19 @@ import type { - City, - General, - MapDefinition, - Nation, - ScenarioConfig, - ScenarioMeta, - WarAftermathConfig, - WarEngineConfig, - WarTimeContext, - UnitSetDefinition, + ActionContextBase, + ActionContextBuilder, + ActionContextOptions, + ActionResolveContext, } from '@sammo-ts/logic'; -import type { InMemoryTurnWorld } from './inMemoryWorld.js'; -import type { TurnGeneral, TurnWorldState } from './types.js'; - -interface WorldSummary { - totalGeneralCount: number; - totalNpcCount: number; - averageStats?: General['stats']; -} - -interface NationSummary { - averageStats?: General['stats']; - averageExperience?: number; - averageDedication?: number; -} - -export interface ActionRandomSource { - nextFloat(): number; - nextBool(probability: number): boolean; - nextInt(minInclusive: number, maxExclusive: number): number; -} - -export type ActionContextBase = { - general: TurnGeneral; - city?: City; - nation?: Nation | null; - rng: ActionRandomSource; -}; - -export type ActionResolveContext = ActionContextBase & Record; - -export interface ActionContextOptions { - world: TurnWorldState; - scenarioConfig: ScenarioConfig; - scenarioMeta?: ScenarioMeta; - map?: MapDefinition; - unitSet?: UnitSetDefinition; - worldRef: InMemoryTurnWorld | null; - actionArgs: Record; - createGeneralId: () => number; - seedBase: string; -} - -type ActionContextBuilder = ( - base: ActionContextBase, - options: ActionContextOptions -) => ActionResolveContext | null; - -const buildWorldSummary = (world: InMemoryTurnWorld | null): WorldSummary => { - if (!world) { - return { totalGeneralCount: 0, totalNpcCount: 0 }; - } - const generals = world.listGenerals(); - if (generals.length === 0) { - return { totalGeneralCount: 0, totalNpcCount: 0 }; - } - const total = generals.length; - const npcCount = generals.filter((general) => general.npcState > 0).length; - const statSum = generals.reduce( - (acc, general) => ({ - leadership: acc.leadership + general.stats.leadership, - strength: acc.strength + general.stats.strength, - intelligence: acc.intelligence + general.stats.intelligence, - }), - { leadership: 0, strength: 0, intelligence: 0 } - ); - return { - totalGeneralCount: total, - totalNpcCount: npcCount, - averageStats: { - leadership: statSum.leadership / total, - strength: statSum.strength / total, - intelligence: statSum.intelligence / total, - }, - }; -}; - -const buildNationSummary = ( - world: InMemoryTurnWorld | null, - nationId: number -): NationSummary => { - if (!world || nationId <= 0) { - return {}; - } - const generals = world.listGenerals().filter( - (general) => general.nationId === nationId - ); - if (generals.length === 0) { - return {}; - } - const total = generals.length; - const statSum = generals.reduce( - (acc, general) => ({ - leadership: acc.leadership + general.stats.leadership, - strength: acc.strength + general.stats.strength, - intelligence: acc.intelligence + general.stats.intelligence, - }), - { leadership: 0, strength: 0, intelligence: 0 } - ); - const expSum = generals.reduce((acc, general) => acc + general.experience, 0); - const dedSum = generals.reduce((acc, general) => acc + general.dedication, 0); - return { - averageStats: { - leadership: statSum.leadership / total, - strength: statSum.strength / total, - intelligence: statSum.intelligence / total, - }, - averageExperience: expSum / total, - averageDedication: dedSum / total, - }; -}; - -const buildAverageNationGeneralCount = (world: InMemoryTurnWorld | null): number => { - if (!world) { - return 0; - } - const generals = world.listGenerals(); - const nations = world.listNations(); - if (nations.length === 0) { - return generals.length; - } - return generals.length / nations.length; -}; - -const resolveStartYear = ( - world: TurnWorldState, - scenarioMeta?: ScenarioMeta -): number => { - if (typeof scenarioMeta?.startYear === 'number') { - return scenarioMeta.startYear; - } - return world.currentYear; -}; - -const resolveTurnTermMinutes = (world: TurnWorldState): number => - Math.max(1, Math.round(world.tickSeconds / 60)); - -const DEFAULT_WAR_CONFIG = { - armPerPhase: 500, - maxTrainByCommand: 100, - maxAtmosByCommand: 100, - maxTrainByWar: 110, - maxAtmosByWar: 150, -}; - -const DEFAULT_AFTER_CONFIG = { - techLevelIncYear: 5, - initialAllowedTechLevel: 1, - defaultCityWall: 1000, -}; - -const asRecord = (value: unknown): Record => - value && typeof value === 'object' && !Array.isArray(value) - ? (value as Record) - : {}; - -const resolveNumber = ( - record: Record, - 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 resolveCastleCrewTypeId = ( - unitSet: UnitSetDefinition, - fallback: 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 ?? fallback; -}; - -const resolveCastleArmType = ( - unitSet: UnitSetDefinition, - castleCrewTypeId: number -): number => { - const crewTypes = unitSet.crewTypes ?? []; - return ( - crewTypes.find((crewType) => crewType.id === castleCrewTypeId)?.armType ?? - 0 - ); -}; - -const buildWarConfig = ( - scenarioConfig: ScenarioConfig, - unitSet: UnitSetDefinition -): WarEngineConfig => { - const constValues = asRecord(scenarioConfig.const); - const castleCrewTypeId = resolveNumber( - constValues, - ['castleCrewTypeId'], - resolveCastleCrewTypeId(unitSet, 0) - ); - const castleArmType = resolveCastleArmType(unitSet, castleCrewTypeId); - - return { - 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 - ), - castleCrewTypeId, - armTypes: { - footman: 1, - archer: 2, - cavalry: 3, - wizard: 4, - siege: 5, - misc: 6, - castle: castleArmType, - }, - }; -}; - -const buildWarAftermathConfig = ( - scenarioConfig: ScenarioConfig, - castleCrewTypeId: number -): WarAftermathConfig => { - const constValues = asRecord(scenarioConfig.const); - return { - initialNationGenLimit: resolveNumber( - constValues, - ['initialNationGenLimit'], - 0 - ), - techLevelIncYear: resolveNumber( - constValues, - ['techLevelIncYear'], - DEFAULT_AFTER_CONFIG.techLevelIncYear - ), - initialAllowedTechLevel: resolveNumber( - constValues, - ['initialAllowedTechLevel'], - DEFAULT_AFTER_CONFIG.initialAllowedTechLevel - ), - maxTechLevel: resolveNumber(constValues, ['maxTechLevel'], 0), - defaultCityWall: resolveNumber( - constValues, - ['defaultCityWall'], - DEFAULT_AFTER_CONFIG.defaultCityWall - ), - baseGold: resolveNumber(constValues, ['baseGold', 'basegold'], 0), - baseRice: resolveNumber(constValues, ['baseRice', 'baserice'], 0), - castleCrewTypeId, - }; -}; - -const buildWarTime = ( - world: TurnWorldState, - scenarioMeta?: ScenarioMeta -): WarTimeContext => ({ - year: world.currentYear, - month: world.currentMonth, - startYear: resolveStartYear(world, scenarioMeta), -}); - -// 커맨드별로 필요한 컨텍스트 확장 데이터를 구성한다. -const ACTION_CONTEXT_BUILDERS: Record = { - che_인재탐색: (base, options) => ({ - ...base, - currentYear: options.world.currentYear, - worldSummary: buildWorldSummary(options.worldRef), - createGeneralId: options.createGeneralId, - }), - che_의병모집: (base, options) => { - const nationSummary = buildNationSummary( - options.worldRef, - base.general.nationId - ); - return { - ...base, - currentYear: options.world.currentYear, - startYear: resolveStartYear(options.world, options.scenarioMeta), - averageNationGeneralCount: buildAverageNationGeneralCount( - options.worldRef - ), - nationAverageStats: nationSummary.averageStats, - nationAverageExperience: nationSummary.averageExperience, - nationAverageDedication: nationSummary.averageDedication, - createGeneralId: options.createGeneralId, - }; - }, - che_포상: (base, options) => { - const destGeneralId = options.actionArgs.destGeneralId; - if (typeof destGeneralId !== 'number') { - return null; - } - const destGeneral = options.worldRef?.getGeneralById(destGeneralId); - if (!destGeneral) { - return null; - } - return { - ...base, - destGeneral, - }; - }, - che_발령: (base, options) => { - const destGeneralId = options.actionArgs.destGeneralId; - const destCityId = options.actionArgs.destCityId; - if (typeof destGeneralId !== 'number' || typeof destCityId !== 'number') { - return null; - } - const destGeneral = options.worldRef?.getGeneralById(destGeneralId); - const destCity = options.worldRef?.getCityById(destCityId); - if (!destGeneral || !destCity) { - return null; - } - return { - ...base, - destGeneral, - destCity, - currentYear: options.world.currentYear, - currentMonth: options.world.currentMonth, - turnTermMinutes: resolveTurnTermMinutes(options.world), - generalTurnTime: base.general.turnTime, - destGeneralTurnTime: destGeneral.turnTime, - }; - }, - che_징병: (base, options) => { - if (!options.map || !options.unitSet) { - return null; - } - return { - ...base, - map: options.map, - unitSet: options.unitSet, - cities: options.worldRef?.listCities() ?? [], - currentYear: options.world.currentYear, - startYear: resolveStartYear(options.world, options.scenarioMeta), - }; - }, - che_불가침제의: (base, options) => ({ - ...base, - currentYear: options.world.currentYear, - currentMonth: options.world.currentMonth, - }), - che_출병: (base, options) => { - if (!options.unitSet || !options.worldRef) { - return null; - } - const destCityId = options.actionArgs.destCityId; - if (typeof destCityId !== 'number') { - return null; - } - const destCity = options.worldRef.getCityById(destCityId); - if (!destCity) { - return null; - } - const destNation = - destCity.nationId > 0 - ? options.worldRef.getNationById(destCity.nationId) - : null; - const diplomacy = options.worldRef.listDiplomacy(); - const warConfig = buildWarConfig(options.scenarioConfig, options.unitSet); - const aftermathConfig = buildWarAftermathConfig( - options.scenarioConfig, - warConfig.castleCrewTypeId - ); - return { - ...base, - destCity, - destNation, - cities: options.worldRef.listCities(), - nations: options.worldRef.listNations(), - generals: options.worldRef.listGenerals(), - unitSet: options.unitSet, - map: options.map, - diplomacy, - time: buildWarTime(options.world, options.scenarioMeta), - seedBase: options.seedBase, - warConfig, - aftermathConfig, - }; - }, -}; +export type ActionContextBuilderMap = Map; +// 커맨드 모듈에서 제공한 컨텍스트 빌더로 확장한다. export const buildActionContext = ( key: string, base: ActionContextBase, - options: ActionContextOptions + options: ActionContextOptions, + builders?: ActionContextBuilderMap ): ActionResolveContext | null => { - const builder = ACTION_CONTEXT_BUILDERS[key]; + const builder = builders?.get(key); return builder ? builder(base, options) : base; }; diff --git a/app/game-engine/src/turn/reservedTurnHandler.ts b/app/game-engine/src/turn/reservedTurnHandler.ts index 70a55a7..531b3c7 100644 --- a/app/game-engine/src/turn/reservedTurnHandler.ts +++ b/app/game-engine/src/turn/reservedTurnHandler.ts @@ -1,4 +1,6 @@ import type { + ActionContextBase, + ActionContextBuilder, City, GeneralActionDefinition, LogEntryDraft, @@ -12,6 +14,9 @@ import type { } from '@sammo-ts/logic'; import { DEFAULT_TURN_COMMAND_PROFILE, + GeneralTurnCommandLoader, + NationTurnCommandLoader, + defaultActionContextBuilder, evaluateConstraints, resolveGeneralAction, } from '@sammo-ts/logic'; @@ -29,7 +34,6 @@ import { buildCommandEnv, buildReservedTurnDefinitions, } from './reservedTurnCommands.js'; -import type { ActionContextBase } from './reservedTurnActionContext.js'; import { buildActionContext } from './reservedTurnActionContext.js'; const DEFAULT_ACTION = '휴식'; @@ -219,6 +223,41 @@ export const createReservedTurnHandler = async (options: { const generalFallback = generalDefinitions.get(DEFAULT_ACTION)!; const nationFallback = nationDefinitions.get(DEFAULT_ACTION)!; + const actionContextBuilders = new Map(); + const seenActionKeys = new Set(); + const applyActionContextBuilder = (module: { + commandSpec: { key: string }; + actionContextBuilder?: ActionContextBuilder; + }): void => { + actionContextBuilders.set( + module.commandSpec.key, + module.actionContextBuilder ?? defaultActionContextBuilder + ); + }; + const generalModuleLoader = new GeneralTurnCommandLoader(); + const nationModuleLoader = new NationTurnCommandLoader(); + for (const key of commandProfile.general) { + if (seenActionKeys.has(key)) { + continue; + } + seenActionKeys.add(key); + const module = await generalModuleLoader.load(key); + applyActionContextBuilder(module); + } + for (const key of commandProfile.nation) { + if (seenActionKeys.has(key)) { + continue; + } + seenActionKeys.add(key); + const module = await nationModuleLoader.load(key); + applyActionContextBuilder(module); + } + if (!actionContextBuilders.has(DEFAULT_ACTION)) { + applyActionContextBuilder( + await generalModuleLoader.load(DEFAULT_ACTION) + ); + } + let nextGeneralId: number | null = null; const createGeneralId = (): number => { if (nextGeneralId === null) { @@ -344,7 +383,7 @@ export const createReservedTurnHandler = async (options: { actionArgs: actionArgsRecord, createGeneralId, seedBase, - }); + }, actionContextBuilders); if (!specificContext && actionKey !== fallbackDefinition.key) { definition = fallbackDefinition; actionArgs = definition.parseArgs({}) ?? {}; diff --git a/packages/logic/src/actions/index.ts b/packages/logic/src/actions/index.ts index 6aa8939..5fe49da 100644 --- a/packages/logic/src/actions/index.ts +++ b/packages/logic/src/actions/index.ts @@ -1,6 +1,7 @@ export * from './definition.js'; export * from './engine.js'; export * from './turn/commandEnv.js'; +export * from './turn/actionContext.js'; export * from './turn/commandModule.js'; export * from './turn/commandProfile.js'; export * from './turn/general/index.js'; diff --git a/packages/logic/src/actions/turn/actionContext.ts b/packages/logic/src/actions/turn/actionContext.ts new file mode 100644 index 0000000..e45668c --- /dev/null +++ b/packages/logic/src/actions/turn/actionContext.ts @@ -0,0 +1,63 @@ +import type { City, General, Nation } from '../../domain/entities.js'; +import type { ScenarioConfig } from '../../scenario/types.js'; +import type { ScenarioMeta } from '../../world/types.js'; +import type { MapDefinition, UnitSetDefinition } from '../../world/types.js'; + +export interface ActionRandomSource { + nextFloat(): number; + nextBool(probability: number): boolean; + nextInt(minInclusive: number, maxExclusive: number): number; +} + +export interface ActionContextGeneral extends General { + turnTime: Date; +} + +export type ActionContextBase = { + general: ActionContextGeneral; + city?: City; + nation?: Nation | null; + rng: ActionRandomSource; +}; + +export type ActionResolveContext = ActionContextBase & Record; + +export interface ActionContextWorldState { + currentYear: number; + currentMonth: number; + tickSeconds: number; +} + +export interface ActionContextWorldRef { + listGenerals(): ActionContextGeneral[]; + listCities(): City[]; + listNations(): Nation[]; + listDiplomacy(): Array<{ + fromNationId: number; + toNationId: number; + state: number; + }>; + getGeneralById(id: number): ActionContextGeneral | null; + getCityById(id: number): City | null; + getNationById(id: number): Nation | null; +} + +export interface ActionContextOptions { + world: ActionContextWorldState; + scenarioConfig: ScenarioConfig; + scenarioMeta?: ScenarioMeta; + map?: MapDefinition; + unitSet?: UnitSetDefinition; + worldRef: ActionContextWorldRef | null; + actionArgs: Record; + createGeneralId: () => number; + seedBase: string; +} + +// 예약 턴 처리에서 커맨드별로 필요한 컨텍스트를 확장한다. +export type ActionContextBuilder = ( + base: ActionContextBase, + options: ActionContextOptions +) => ActionResolveContext | null; + +export const defaultActionContextBuilder: ActionContextBuilder = (base) => base; diff --git a/packages/logic/src/actions/turn/actionContextHelpers.ts b/packages/logic/src/actions/turn/actionContextHelpers.ts new file mode 100644 index 0000000..3a5439a --- /dev/null +++ b/packages/logic/src/actions/turn/actionContextHelpers.ts @@ -0,0 +1,275 @@ +import type { General } from '../../domain/entities.js'; +import type { ScenarioConfig } from '../../scenario/types.js'; +import type { ScenarioMeta } from '../../world/types.js'; +import type { WarAftermathConfig, WarEngineConfig, WarTimeContext } from '../../war/types.js'; +import type { UnitSetDefinition } from '../../world/types.js'; +import type { + ActionContextWorldRef, + ActionContextWorldState, +} from './actionContext.js'; + +export interface WorldSummary { + totalGeneralCount: number; + totalNpcCount: number; + averageStats?: General['stats']; +} + +export interface NationSummary { + averageStats?: General['stats']; + averageExperience?: number; + averageDedication?: number; +} + +export const buildWorldSummary = ( + world: ActionContextWorldRef | null +): WorldSummary => { + if (!world) { + return { totalGeneralCount: 0, totalNpcCount: 0 }; + } + const generals = world.listGenerals(); + if (generals.length === 0) { + return { totalGeneralCount: 0, totalNpcCount: 0 }; + } + const total = generals.length; + const npcCount = generals.filter((general) => general.npcState > 0).length; + const statSum = generals.reduce( + (acc, general) => ({ + leadership: acc.leadership + general.stats.leadership, + strength: acc.strength + general.stats.strength, + intelligence: acc.intelligence + general.stats.intelligence, + }), + { leadership: 0, strength: 0, intelligence: 0 } + ); + return { + totalGeneralCount: total, + totalNpcCount: npcCount, + averageStats: { + leadership: statSum.leadership / total, + strength: statSum.strength / total, + intelligence: statSum.intelligence / total, + }, + }; +}; + +export const buildNationSummary = ( + world: ActionContextWorldRef | null, + nationId: number +): NationSummary => { + if (!world || nationId <= 0) { + return {}; + } + const generals = world.listGenerals().filter( + (general) => general.nationId === nationId + ); + if (generals.length === 0) { + return {}; + } + const total = generals.length; + const statSum = generals.reduce( + (acc, general) => ({ + leadership: acc.leadership + general.stats.leadership, + strength: acc.strength + general.stats.strength, + intelligence: acc.intelligence + general.stats.intelligence, + }), + { leadership: 0, strength: 0, intelligence: 0 } + ); + const expSum = generals.reduce((acc, general) => acc + general.experience, 0); + const dedSum = generals.reduce((acc, general) => acc + general.dedication, 0); + return { + averageStats: { + leadership: statSum.leadership / total, + strength: statSum.strength / total, + intelligence: statSum.intelligence / total, + }, + averageExperience: expSum / total, + averageDedication: dedSum / total, + }; +}; + +export const buildAverageNationGeneralCount = ( + world: ActionContextWorldRef | null +): number => { + if (!world) { + return 0; + } + const generals = world.listGenerals(); + const nations = world.listNations(); + if (nations.length === 0) { + return generals.length; + } + return generals.length / nations.length; +}; + +export const resolveStartYear = ( + world: ActionContextWorldState, + scenarioMeta?: ScenarioMeta +): number => { + if (typeof scenarioMeta?.startYear === 'number') { + return scenarioMeta.startYear; + } + return world.currentYear; +}; + +export const resolveTurnTermMinutes = (world: ActionContextWorldState): number => + Math.max(1, Math.round(world.tickSeconds / 60)); + +const DEFAULT_WAR_CONFIG = { + armPerPhase: 500, + maxTrainByCommand: 100, + maxAtmosByCommand: 100, + maxTrainByWar: 110, + maxAtmosByWar: 150, +}; + +const DEFAULT_AFTER_CONFIG = { + techLevelIncYear: 5, + initialAllowedTechLevel: 1, + defaultCityWall: 1000, +}; + +const asRecord = (value: unknown): Record => + value && typeof value === 'object' && !Array.isArray(value) + ? (value as Record) + : {}; + +const resolveNumber = ( + record: Record, + 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 resolveCastleCrewTypeId = ( + unitSet: UnitSetDefinition, + fallback: 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 ?? fallback; +}; + +const resolveCastleArmType = ( + unitSet: UnitSetDefinition, + castleCrewTypeId: number +): number => { + const crewTypes = unitSet.crewTypes ?? []; + return ( + crewTypes.find((crewType) => crewType.id === castleCrewTypeId)?.armType ?? + 0 + ); +}; + +export const buildWarConfig = ( + scenarioConfig: ScenarioConfig, + unitSet: UnitSetDefinition +): WarEngineConfig => { + const constValues = asRecord(scenarioConfig.const); + const castleCrewTypeId = resolveNumber( + constValues, + ['castleCrewTypeId'], + resolveCastleCrewTypeId(unitSet, 0) + ); + const castleArmType = resolveCastleArmType(unitSet, castleCrewTypeId); + + return { + 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 + ), + castleCrewTypeId, + armTypes: { + footman: 1, + archer: 2, + cavalry: 3, + wizard: 4, + siege: 5, + misc: 6, + castle: castleArmType, + }, + }; +}; + +export const buildWarAftermathConfig = ( + scenarioConfig: ScenarioConfig, + castleCrewTypeId: number +): WarAftermathConfig => { + const constValues = asRecord(scenarioConfig.const); + return { + initialNationGenLimit: resolveNumber( + constValues, + ['initialNationGenLimit'], + 0 + ), + techLevelIncYear: resolveNumber( + constValues, + ['techLevelIncYear'], + DEFAULT_AFTER_CONFIG.techLevelIncYear + ), + initialAllowedTechLevel: resolveNumber( + constValues, + ['initialAllowedTechLevel'], + DEFAULT_AFTER_CONFIG.initialAllowedTechLevel + ), + maxTechLevel: resolveNumber(constValues, ['maxTechLevel'], 0), + defaultCityWall: resolveNumber( + constValues, + ['defaultCityWall'], + DEFAULT_AFTER_CONFIG.defaultCityWall + ), + baseGold: resolveNumber(constValues, ['baseGold', 'basegold'], 0), + baseRice: resolveNumber(constValues, ['baseRice', 'baserice'], 0), + castleCrewTypeId, + }; +}; + +export const buildWarTime = ( + world: ActionContextWorldState, + scenarioMeta?: ScenarioMeta +): WarTimeContext => ({ + year: world.currentYear, + month: world.currentMonth, + startYear: resolveStartYear(world, scenarioMeta), +}); diff --git a/packages/logic/src/actions/turn/commandModule.ts b/packages/logic/src/actions/turn/commandModule.ts index a36b389..459e32d 100644 --- a/packages/logic/src/actions/turn/commandModule.ts +++ b/packages/logic/src/actions/turn/commandModule.ts @@ -1,5 +1,6 @@ import type { GeneralActionDefinition } from '../definition.js'; import type { GeneralActionResolver } from '../engine.js'; +import type { ActionContextBuilder } from './actionContext.js'; import type { TurnCommandEnv } from './commandEnv.js'; export interface TurnCommandSpecBase { @@ -15,4 +16,5 @@ export interface TurnCommandModule GeneralActionDefinition; ActionResolver?: new (...args: any[]) => GeneralActionResolver; CommandResolver?: new (...args: any[]) => unknown; + actionContextBuilder?: ActionContextBuilder; } diff --git a/packages/logic/src/actions/turn/general/che_거병.ts b/packages/logic/src/actions/turn/general/che_거병.ts index b37059c..8247f08 100644 --- a/packages/logic/src/actions/turn/general/che_거병.ts +++ b/packages/logic/src/actions/turn/general/che_거병.ts @@ -8,6 +8,7 @@ import type { } from '../../engine.js'; import { LogCategory, LogFormat } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface UprisingArgs {} @@ -50,6 +51,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_거병', category: '전략', diff --git a/packages/logic/src/actions/turn/general/che_건국.ts b/packages/logic/src/actions/turn/general/che_건국.ts index e9585cf..c3f6e15 100644 --- a/packages/logic/src/actions/turn/general/che_건국.ts +++ b/packages/logic/src/actions/turn/general/che_건국.ts @@ -8,6 +8,7 @@ import type { } from '../../engine.js'; import { LogCategory, LogFormat } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface FoundingArgs {} @@ -50,6 +51,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_건국', category: '전략', diff --git a/packages/logic/src/actions/turn/general/che_기술연구.ts b/packages/logic/src/actions/turn/general/che_기술연구.ts index 344acc5..1e78867 100644 --- a/packages/logic/src/actions/turn/general/che_기술연구.ts +++ b/packages/logic/src/actions/turn/general/che_기술연구.ts @@ -20,6 +20,7 @@ import type { GeneralActionResolveContext, } from '../../engine.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface TechResearchArgs {} @@ -104,6 +105,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_기술연구', category: '내정', diff --git a/packages/logic/src/actions/turn/general/che_농지개간.ts b/packages/logic/src/actions/turn/general/che_농지개간.ts index 5b50927..56fc440 100644 --- a/packages/logic/src/actions/turn/general/che_농지개간.ts +++ b/packages/logic/src/actions/turn/general/che_농지개간.ts @@ -1,6 +1,7 @@ import type { GeneralTriggerState } from '../../../domain/entities.js'; import { CityDevelopmentActionDefinition } from './cityDevelopment.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export class ActionDefinition< @@ -21,6 +22,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_농지개간', category: '내정', diff --git a/packages/logic/src/actions/turn/general/che_사기진작.ts b/packages/logic/src/actions/turn/general/che_사기진작.ts index 9ab3178..a9b9cd9 100644 --- a/packages/logic/src/actions/turn/general/che_사기진작.ts +++ b/packages/logic/src/actions/turn/general/che_사기진작.ts @@ -13,6 +13,7 @@ import type { GeneralActionResolveContext, } from '../../engine.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface BoostMoraleArgs {} @@ -82,6 +83,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_사기진작', category: '군사', diff --git a/packages/logic/src/actions/turn/general/che_상업투자.ts b/packages/logic/src/actions/turn/general/che_상업투자.ts index 2e406b9..8f25058 100644 --- a/packages/logic/src/actions/turn/general/che_상업투자.ts +++ b/packages/logic/src/actions/turn/general/che_상업투자.ts @@ -32,6 +32,7 @@ import type { GeneralActionResolveContext, } from '../../engine.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export type DomesticCriticalPick = 'fail' | 'normal' | 'success'; @@ -422,6 +423,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_상업투자', category: '내정', diff --git a/packages/logic/src/actions/turn/general/che_성벽보수.ts b/packages/logic/src/actions/turn/general/che_성벽보수.ts index ef7e6c2..517d5e7 100644 --- a/packages/logic/src/actions/turn/general/che_성벽보수.ts +++ b/packages/logic/src/actions/turn/general/che_성벽보수.ts @@ -1,6 +1,7 @@ import type { GeneralTriggerState } from '../../../domain/entities.js'; import { CityDevelopmentActionDefinition } from './cityDevelopment.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export class ActionDefinition< @@ -21,6 +22,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_성벽보수', category: '내정', diff --git a/packages/logic/src/actions/turn/general/che_수비강화.ts b/packages/logic/src/actions/turn/general/che_수비강화.ts index 070c33f..4794969 100644 --- a/packages/logic/src/actions/turn/general/che_수비강화.ts +++ b/packages/logic/src/actions/turn/general/che_수비강화.ts @@ -1,6 +1,7 @@ import type { GeneralTriggerState } from '../../../domain/entities.js'; import { CityDevelopmentActionDefinition } from './cityDevelopment.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export class ActionDefinition< @@ -21,6 +22,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_수비강화', category: '내정', diff --git a/packages/logic/src/actions/turn/general/che_요양.ts b/packages/logic/src/actions/turn/general/che_요양.ts index fb6ece4..06a15e8 100644 --- a/packages/logic/src/actions/turn/general/che_요양.ts +++ b/packages/logic/src/actions/turn/general/che_요양.ts @@ -13,6 +13,7 @@ import type { GeneralActionResolveContext, } from '../../engine.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface RecoveryArgs {} @@ -70,6 +71,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_요양', category: '개인', diff --git a/packages/logic/src/actions/turn/general/che_인재탐색.ts b/packages/logic/src/actions/turn/general/che_인재탐색.ts index bdde687..89575cf 100644 --- a/packages/logic/src/actions/turn/general/che_인재탐색.ts +++ b/packages/logic/src/actions/turn/general/che_인재탐색.ts @@ -30,6 +30,8 @@ import { import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; import { buildRecruitmentGeneral } from './recruitment.js'; import { JosaUtil } from '@sammo-ts/common'; +import type { ActionContextBuilder } from '../actionContext.js'; +import { buildWorldSummary } from '../actionContextHelpers.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; @@ -458,6 +460,14 @@ export class ActionDefinition< } } +// 예약 턴 실행에 필요한 월드 요약/생성기를 주입한다. +export const actionContextBuilder: ActionContextBuilder = (base, options) => ({ + ...base, + currentYear: options.world.currentYear, + worldSummary: buildWorldSummary(options.worldRef), + createGeneralId: options.createGeneralId, +}); + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_인재탐색', category: '인사', diff --git a/packages/logic/src/actions/turn/general/che_임관.ts b/packages/logic/src/actions/turn/general/che_임관.ts index 3055893..2f3b608 100644 --- a/packages/logic/src/actions/turn/general/che_임관.ts +++ b/packages/logic/src/actions/turn/general/che_임관.ts @@ -11,6 +11,7 @@ import type { } from '../../engine.js'; import { LogCategory, LogFormat } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface AppointmentArgs { @@ -60,6 +61,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_임관', category: '전략', diff --git a/packages/logic/src/actions/turn/general/che_주민선정.ts b/packages/logic/src/actions/turn/general/che_주민선정.ts index 09c6726..127fe75 100644 --- a/packages/logic/src/actions/turn/general/che_주민선정.ts +++ b/packages/logic/src/actions/turn/general/che_주민선정.ts @@ -1,6 +1,7 @@ import type { GeneralTriggerState } from '../../../domain/entities.js'; import { CityDevelopmentActionDefinition } from './cityDevelopment.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export class ActionDefinition< @@ -21,6 +22,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_주민선정', category: '내정', diff --git a/packages/logic/src/actions/turn/general/che_징병.ts b/packages/logic/src/actions/turn/general/che_징병.ts index 788a059..e0679c6 100644 --- a/packages/logic/src/actions/turn/general/che_징병.ts +++ b/packages/logic/src/actions/turn/general/che_징병.ts @@ -31,6 +31,8 @@ import type { GeneralActionResolver, } from '../../engine.js'; import type { MapDefinition, UnitSetDefinition } from '../../../world/types.js'; +import type { ActionContextBuilder } from '../actionContext.js'; +import { resolveStartYear } from '../actionContextHelpers.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { @@ -592,6 +594,21 @@ export class ActionDefinition< } } +// 예약 턴 실행에 필요한 지도/연도 컨텍스트를 구성한다. +export const actionContextBuilder: ActionContextBuilder = (base, options) => { + if (!options.map || !options.unitSet) { + return null; + } + return { + ...base, + map: options.map, + unitSet: options.unitSet, + cities: options.worldRef?.listCities() ?? [], + currentYear: options.world.currentYear, + startYear: resolveStartYear(options.world, options.scenarioMeta), + }; +}; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_징병', category: '내정', diff --git a/packages/logic/src/actions/turn/general/che_출병.ts b/packages/logic/src/actions/turn/general/che_출병.ts index 5c2325d..c4fb37b 100644 --- a/packages/logic/src/actions/turn/general/che_출병.ts +++ b/packages/logic/src/actions/turn/general/che_출병.ts @@ -52,6 +52,12 @@ import type { MapDefinition, UnitSetDefinition, } from '../../../world/types.js'; +import type { ActionContextBuilder } from '../actionContext.js'; +import { + buildWarAftermathConfig, + buildWarConfig, + buildWarTime, +} from '../actionContextHelpers.js'; export interface DispatchArgs { destCityId: number; @@ -529,6 +535,46 @@ export class ActionDefinition< } } +// 예약 턴 실행에 필요한 전투 컨텍스트를 구성한다. +export const actionContextBuilder: ActionContextBuilder = (base, options) => { + if (!options.unitSet || !options.worldRef) { + return null; + } + const destCityId = options.actionArgs.destCityId; + if (typeof destCityId !== 'number') { + return null; + } + const destCity = options.worldRef.getCityById(destCityId); + if (!destCity) { + return null; + } + const destNation = + destCity.nationId > 0 + ? options.worldRef.getNationById(destCity.nationId) + : null; + const diplomacy = options.worldRef.listDiplomacy(); + const warConfig = buildWarConfig(options.scenarioConfig, options.unitSet); + const aftermathConfig = buildWarAftermathConfig( + options.scenarioConfig, + warConfig.castleCrewTypeId + ); + return { + ...base, + destCity, + destNation, + cities: options.worldRef.listCities(), + nations: options.worldRef.listNations(), + generals: options.worldRef.listGenerals(), + unitSet: options.unitSet, + map: options.map, + diplomacy, + time: buildWarTime(options.world, options.scenarioMeta), + seedBase: options.seedBase, + warConfig, + aftermathConfig, + }; +}; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_출병', category: '군사', diff --git a/packages/logic/src/actions/turn/general/che_치안강화.ts b/packages/logic/src/actions/turn/general/che_치안강화.ts index 1a3cabe..5af3673 100644 --- a/packages/logic/src/actions/turn/general/che_치안강화.ts +++ b/packages/logic/src/actions/turn/general/che_치안강화.ts @@ -1,6 +1,7 @@ import type { GeneralTriggerState } from '../../../domain/entities.js'; import { CityDevelopmentActionDefinition } from './cityDevelopment.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export class ActionDefinition< @@ -21,6 +22,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_치안강화', category: '내정', diff --git a/packages/logic/src/actions/turn/general/che_화계.ts b/packages/logic/src/actions/turn/general/che_화계.ts index c89b5d9..87400d6 100644 --- a/packages/logic/src/actions/turn/general/che_화계.ts +++ b/packages/logic/src/actions/turn/general/che_화계.ts @@ -36,6 +36,7 @@ import { } from '../../engine.js'; import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface FireAttackArgs { @@ -495,6 +496,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_화계', category: '계략', diff --git a/packages/logic/src/actions/turn/general/che_훈련.ts b/packages/logic/src/actions/turn/general/che_훈련.ts index 96aaf9a..f712438 100644 --- a/packages/logic/src/actions/turn/general/che_훈련.ts +++ b/packages/logic/src/actions/turn/general/che_훈련.ts @@ -13,6 +13,7 @@ import type { GeneralActionResolveContext, } from '../../engine.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface TrainingArgs {} @@ -82,6 +83,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_훈련', category: '군사', diff --git a/packages/logic/src/actions/turn/general/휴식.ts b/packages/logic/src/actions/turn/general/휴식.ts index ecf2c22..3a7d188 100644 --- a/packages/logic/src/actions/turn/general/휴식.ts +++ b/packages/logic/src/actions/turn/general/휴식.ts @@ -13,6 +13,7 @@ import type { } from '../../engine.js'; import { LogCategory, LogFormat } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; export interface RestArgs {} @@ -65,6 +66,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: GeneralTurnCommandSpec = { key: '휴식', category: '개인', diff --git a/packages/logic/src/actions/turn/nation/che_발령.ts b/packages/logic/src/actions/turn/nation/che_발령.ts index d0f7115..81a3eb3 100644 --- a/packages/logic/src/actions/turn/nation/che_발령.ts +++ b/packages/logic/src/actions/turn/nation/che_발령.ts @@ -29,6 +29,8 @@ import type { import { createGeneralPatchEffect, createLogEffect } from '../../engine.js'; import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; import { JosaUtil } from '@sammo-ts/common'; +import type { ActionContextBuilder } from '../actionContext.js'; +import { resolveTurnTermMinutes } from '../actionContextHelpers.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { NationTurnCommandSpec } from './index.js'; @@ -211,6 +213,30 @@ export class ActionDefinition< } } +// 예약 턴 실행에 필요한 대상 장수/도시 컨텍스트를 구성한다. +export const actionContextBuilder: ActionContextBuilder = (base, options) => { + const destGeneralId = options.actionArgs.destGeneralId; + const destCityId = options.actionArgs.destCityId; + if (typeof destGeneralId !== 'number' || typeof destCityId !== 'number') { + return null; + } + const destGeneral = options.worldRef?.getGeneralById(destGeneralId); + const destCity = options.worldRef?.getCityById(destCityId); + if (!destGeneral || !destCity) { + return null; + } + return { + ...base, + destGeneral, + destCity, + currentYear: options.world.currentYear, + currentMonth: options.world.currentMonth, + turnTermMinutes: resolveTurnTermMinutes(options.world), + generalTurnTime: base.general.turnTime, + destGeneralTurnTime: destGeneral.turnTime, + }; +}; + export const commandSpec: NationTurnCommandSpec = { key: 'che_발령', category: '인사', diff --git a/packages/logic/src/actions/turn/nation/che_불가침제의.ts b/packages/logic/src/actions/turn/nation/che_불가침제의.ts index 20089bb..f0802c0 100644 --- a/packages/logic/src/actions/turn/nation/che_불가침제의.ts +++ b/packages/logic/src/actions/turn/nation/che_불가침제의.ts @@ -15,6 +15,7 @@ import type { } from '../../engine.js'; import { createLogEffect } from '../../engine.js'; import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; +import type { ActionContextBuilder } from '../actionContext.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { NationTurnCommandSpec } from './index.js'; @@ -161,6 +162,13 @@ export class ActionDefinition< } } +// 예약 턴 실행에 필요한 날짜 정보를 제공한다. +export const actionContextBuilder: ActionContextBuilder = (base, options) => ({ + ...base, + currentYear: options.world.currentYear, + currentMonth: options.world.currentMonth, +}); + export const commandSpec: NationTurnCommandSpec = { key: 'che_불가침제의', category: '외교', diff --git a/packages/logic/src/actions/turn/nation/che_불가침파기제의.ts b/packages/logic/src/actions/turn/nation/che_불가침파기제의.ts index 73e9a92..e66e08f 100644 --- a/packages/logic/src/actions/turn/nation/che_불가침파기제의.ts +++ b/packages/logic/src/actions/turn/nation/che_불가침파기제의.ts @@ -16,6 +16,7 @@ import type { import { createLogEffect } from '../../engine.js'; import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { NationTurnCommandSpec } from './index.js'; export interface NonAggressionCancelProposalArgs { @@ -86,6 +87,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: NationTurnCommandSpec = { key: 'che_불가침파기제의', category: '외교', diff --git a/packages/logic/src/actions/turn/nation/che_선전포고.ts b/packages/logic/src/actions/turn/nation/che_선전포고.ts index c755f1d..2dc57b1 100644 --- a/packages/logic/src/actions/turn/nation/che_선전포고.ts +++ b/packages/logic/src/actions/turn/nation/che_선전포고.ts @@ -16,6 +16,7 @@ import type { import { createDiplomacyPatchEffect, createLogEffect } from '../../engine.js'; import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { NationTurnCommandSpec } from './index.js'; export interface DeclareWarArgs { @@ -106,6 +107,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: NationTurnCommandSpec = { key: 'che_선전포고', category: '외교', diff --git a/packages/logic/src/actions/turn/nation/che_의병모집.ts b/packages/logic/src/actions/turn/nation/che_의병모집.ts index 3aa048d..7bb1aa0 100644 --- a/packages/logic/src/actions/turn/nation/che_의병모집.ts +++ b/packages/logic/src/actions/turn/nation/che_의병모집.ts @@ -32,6 +32,12 @@ import { import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; import { buildRecruitmentGeneral } from '../general/recruitment.js'; import { JosaUtil } from '@sammo-ts/common'; +import type { ActionContextBuilder } from '../actionContext.js'; +import { + buildAverageNationGeneralCount, + buildNationSummary, + resolveStartYear, +} from '../actionContextHelpers.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { NationTurnCommandSpec } from './index.js'; @@ -419,6 +425,26 @@ export class ActionDefinition< } } +// 예약 턴 실행에 필요한 국가 평균 정보를 구성한다. +export const actionContextBuilder: ActionContextBuilder = (base, options) => { + const nationSummary = buildNationSummary( + options.worldRef, + base.general.nationId + ); + return { + ...base, + currentYear: options.world.currentYear, + startYear: resolveStartYear(options.world, options.scenarioMeta), + averageNationGeneralCount: buildAverageNationGeneralCount( + options.worldRef + ), + nationAverageStats: nationSummary.averageStats, + nationAverageExperience: nationSummary.averageExperience, + nationAverageDedication: nationSummary.averageDedication, + createGeneralId: options.createGeneralId, + }; +}; + export const commandSpec: NationTurnCommandSpec = { key: 'che_의병모집', category: '전략', diff --git a/packages/logic/src/actions/turn/nation/che_포상.ts b/packages/logic/src/actions/turn/nation/che_포상.ts index 35f4f6f..381e3dd 100644 --- a/packages/logic/src/actions/turn/nation/che_포상.ts +++ b/packages/logic/src/actions/turn/nation/che_포상.ts @@ -36,6 +36,7 @@ import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { NationTurnCommandSpec } from './index.js'; import { JosaUtil } from '@sammo-ts/common'; +import type { ActionContextBuilder } from '../actionContext.js'; export interface AwardArgs { isGold: boolean; @@ -271,6 +272,22 @@ export class ActionDefinition< } } +// 예약 턴 실행에 필요한 대상 장수를 주입한다. +export const actionContextBuilder: ActionContextBuilder = (base, options) => { + const destGeneralId = options.actionArgs.destGeneralId; + if (typeof destGeneralId !== 'number') { + return null; + } + const destGeneral = options.worldRef?.getGeneralById(destGeneralId); + if (!destGeneral) { + return null; + } + return { + ...base, + destGeneral, + }; +}; + export const commandSpec: NationTurnCommandSpec = { key: 'che_포상', category: '인사', diff --git a/packages/logic/src/actions/turn/nation/휴식.ts b/packages/logic/src/actions/turn/nation/휴식.ts index 10890f5..34e73a0 100644 --- a/packages/logic/src/actions/turn/nation/휴식.ts +++ b/packages/logic/src/actions/turn/nation/휴식.ts @@ -12,6 +12,7 @@ import type { GeneralActionResolver, } from '../../engine.js'; import type { TurnCommandEnv } from '../commandEnv.js'; +import { defaultActionContextBuilder } from '../actionContext.js'; import type { NationTurnCommandSpec } from './index.js'; export interface NationRestArgs {} @@ -62,6 +63,9 @@ export class ActionDefinition< } } +// 예약 턴 실행은 기본 컨텍스트만 사용한다. +export const actionContextBuilder = defaultActionContextBuilder; + export const commandSpec: NationTurnCommandSpec = { key: '휴식', category: '휴식',