feat: Introduce action context builders for various turn actions
- Added `actionContextBuilder` to multiple turn action files to utilize the default action context builder. - Implemented specific context builders for actions requiring additional context, such as war configurations, world summaries, and general statistics. - Created new `actionContext.ts` and `actionContextHelpers.ts` files to define interfaces and helper functions for managing action contexts. - Enhanced context handling for nation and general actions, ensuring proper data injection for turn execution.
This commit is contained in:
@@ -1,437 +1,19 @@
|
|||||||
import type {
|
import type {
|
||||||
City,
|
ActionContextBase,
|
||||||
General,
|
ActionContextBuilder,
|
||||||
MapDefinition,
|
ActionContextOptions,
|
||||||
Nation,
|
ActionResolveContext,
|
||||||
ScenarioConfig,
|
|
||||||
ScenarioMeta,
|
|
||||||
WarAftermathConfig,
|
|
||||||
WarEngineConfig,
|
|
||||||
WarTimeContext,
|
|
||||||
UnitSetDefinition,
|
|
||||||
} from '@sammo-ts/logic';
|
} from '@sammo-ts/logic';
|
||||||
|
|
||||||
import type { InMemoryTurnWorld } from './inMemoryWorld.js';
|
export type ActionContextBuilderMap = Map<string, ActionContextBuilder>;
|
||||||
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<string, unknown>;
|
|
||||||
|
|
||||||
export interface ActionContextOptions {
|
|
||||||
world: TurnWorldState;
|
|
||||||
scenarioConfig: ScenarioConfig;
|
|
||||||
scenarioMeta?: ScenarioMeta;
|
|
||||||
map?: MapDefinition;
|
|
||||||
unitSet?: UnitSetDefinition;
|
|
||||||
worldRef: InMemoryTurnWorld | null;
|
|
||||||
actionArgs: Record<string, unknown>;
|
|
||||||
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<string, unknown> =>
|
|
||||||
value && typeof value === 'object' && !Array.isArray(value)
|
|
||||||
? (value as Record<string, unknown>)
|
|
||||||
: {};
|
|
||||||
|
|
||||||
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 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<string, ActionContextBuilder> = {
|
|
||||||
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 const buildActionContext = (
|
export const buildActionContext = (
|
||||||
key: string,
|
key: string,
|
||||||
base: ActionContextBase,
|
base: ActionContextBase,
|
||||||
options: ActionContextOptions
|
options: ActionContextOptions,
|
||||||
|
builders?: ActionContextBuilderMap
|
||||||
): ActionResolveContext | null => {
|
): ActionResolveContext | null => {
|
||||||
const builder = ACTION_CONTEXT_BUILDERS[key];
|
const builder = builders?.get(key);
|
||||||
return builder ? builder(base, options) : base;
|
return builder ? builder(base, options) : base;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import type {
|
import type {
|
||||||
|
ActionContextBase,
|
||||||
|
ActionContextBuilder,
|
||||||
City,
|
City,
|
||||||
GeneralActionDefinition,
|
GeneralActionDefinition,
|
||||||
LogEntryDraft,
|
LogEntryDraft,
|
||||||
@@ -12,6 +14,9 @@ import type {
|
|||||||
} from '@sammo-ts/logic';
|
} from '@sammo-ts/logic';
|
||||||
import {
|
import {
|
||||||
DEFAULT_TURN_COMMAND_PROFILE,
|
DEFAULT_TURN_COMMAND_PROFILE,
|
||||||
|
GeneralTurnCommandLoader,
|
||||||
|
NationTurnCommandLoader,
|
||||||
|
defaultActionContextBuilder,
|
||||||
evaluateConstraints,
|
evaluateConstraints,
|
||||||
resolveGeneralAction,
|
resolveGeneralAction,
|
||||||
} from '@sammo-ts/logic';
|
} from '@sammo-ts/logic';
|
||||||
@@ -29,7 +34,6 @@ import {
|
|||||||
buildCommandEnv,
|
buildCommandEnv,
|
||||||
buildReservedTurnDefinitions,
|
buildReservedTurnDefinitions,
|
||||||
} from './reservedTurnCommands.js';
|
} from './reservedTurnCommands.js';
|
||||||
import type { ActionContextBase } from './reservedTurnActionContext.js';
|
|
||||||
import { buildActionContext } from './reservedTurnActionContext.js';
|
import { buildActionContext } from './reservedTurnActionContext.js';
|
||||||
|
|
||||||
const DEFAULT_ACTION = '휴식';
|
const DEFAULT_ACTION = '휴식';
|
||||||
@@ -219,6 +223,41 @@ export const createReservedTurnHandler = async (options: {
|
|||||||
const generalFallback = generalDefinitions.get(DEFAULT_ACTION)!;
|
const generalFallback = generalDefinitions.get(DEFAULT_ACTION)!;
|
||||||
const nationFallback = nationDefinitions.get(DEFAULT_ACTION)!;
|
const nationFallback = nationDefinitions.get(DEFAULT_ACTION)!;
|
||||||
|
|
||||||
|
const actionContextBuilders = new Map<string, ActionContextBuilder>();
|
||||||
|
const seenActionKeys = new Set<string>();
|
||||||
|
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;
|
let nextGeneralId: number | null = null;
|
||||||
const createGeneralId = (): number => {
|
const createGeneralId = (): number => {
|
||||||
if (nextGeneralId === null) {
|
if (nextGeneralId === null) {
|
||||||
@@ -344,7 +383,7 @@ export const createReservedTurnHandler = async (options: {
|
|||||||
actionArgs: actionArgsRecord,
|
actionArgs: actionArgsRecord,
|
||||||
createGeneralId,
|
createGeneralId,
|
||||||
seedBase,
|
seedBase,
|
||||||
});
|
}, actionContextBuilders);
|
||||||
if (!specificContext && actionKey !== fallbackDefinition.key) {
|
if (!specificContext && actionKey !== fallbackDefinition.key) {
|
||||||
definition = fallbackDefinition;
|
definition = fallbackDefinition;
|
||||||
actionArgs = definition.parseArgs({}) ?? {};
|
actionArgs = definition.parseArgs({}) ?? {};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export * from './definition.js';
|
export * from './definition.js';
|
||||||
export * from './engine.js';
|
export * from './engine.js';
|
||||||
export * from './turn/commandEnv.js';
|
export * from './turn/commandEnv.js';
|
||||||
|
export * from './turn/actionContext.js';
|
||||||
export * from './turn/commandModule.js';
|
export * from './turn/commandModule.js';
|
||||||
export * from './turn/commandProfile.js';
|
export * from './turn/commandProfile.js';
|
||||||
export * from './turn/general/index.js';
|
export * from './turn/general/index.js';
|
||||||
|
|||||||
@@ -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<string, unknown>;
|
||||||
|
|
||||||
|
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<string, unknown>;
|
||||||
|
createGeneralId: () => number;
|
||||||
|
seedBase: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 예약 턴 처리에서 커맨드별로 필요한 컨텍스트를 확장한다.
|
||||||
|
export type ActionContextBuilder = (
|
||||||
|
base: ActionContextBase,
|
||||||
|
options: ActionContextOptions
|
||||||
|
) => ActionResolveContext | null;
|
||||||
|
|
||||||
|
export const defaultActionContextBuilder: ActionContextBuilder = (base) => base;
|
||||||
@@ -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<string, unknown> =>
|
||||||
|
value && typeof value === 'object' && !Array.isArray(value)
|
||||||
|
? (value as Record<string, unknown>)
|
||||||
|
: {};
|
||||||
|
|
||||||
|
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 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),
|
||||||
|
});
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { GeneralActionDefinition } from '../definition.js';
|
import type { GeneralActionDefinition } from '../definition.js';
|
||||||
import type { GeneralActionResolver } from '../engine.js';
|
import type { GeneralActionResolver } from '../engine.js';
|
||||||
|
import type { ActionContextBuilder } from './actionContext.js';
|
||||||
import type { TurnCommandEnv } from './commandEnv.js';
|
import type { TurnCommandEnv } from './commandEnv.js';
|
||||||
|
|
||||||
export interface TurnCommandSpecBase<TKey extends string = string> {
|
export interface TurnCommandSpecBase<TKey extends string = string> {
|
||||||
@@ -15,4 +16,5 @@ export interface TurnCommandModule<TSpec extends TurnCommandSpecBase = TurnComma
|
|||||||
ActionDefinition: new (...args: any[]) => GeneralActionDefinition;
|
ActionDefinition: new (...args: any[]) => GeneralActionDefinition;
|
||||||
ActionResolver?: new (...args: any[]) => GeneralActionResolver;
|
ActionResolver?: new (...args: any[]) => GeneralActionResolver;
|
||||||
CommandResolver?: new (...args: any[]) => unknown;
|
CommandResolver?: new (...args: any[]) => unknown;
|
||||||
|
actionContextBuilder?: ActionContextBuilder;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
} from '../../engine.js';
|
} from '../../engine.js';
|
||||||
import { LogCategory, LogFormat } from '../../../logging/types.js';
|
import { LogCategory, LogFormat } from '../../../logging/types.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export interface UprisingArgs {}
|
export interface UprisingArgs {}
|
||||||
@@ -50,6 +51,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_거병',
|
key: 'che_거병',
|
||||||
category: '전략',
|
category: '전략',
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
} from '../../engine.js';
|
} from '../../engine.js';
|
||||||
import { LogCategory, LogFormat } from '../../../logging/types.js';
|
import { LogCategory, LogFormat } from '../../../logging/types.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export interface FoundingArgs {}
|
export interface FoundingArgs {}
|
||||||
@@ -50,6 +51,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_건국',
|
key: 'che_건국',
|
||||||
category: '전략',
|
category: '전략',
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import type {
|
|||||||
GeneralActionResolveContext,
|
GeneralActionResolveContext,
|
||||||
} from '../../engine.js';
|
} from '../../engine.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export interface TechResearchArgs {}
|
export interface TechResearchArgs {}
|
||||||
@@ -104,6 +105,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_기술연구',
|
key: 'che_기술연구',
|
||||||
category: '내정',
|
category: '내정',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { GeneralTriggerState } from '../../../domain/entities.js';
|
import type { GeneralTriggerState } from '../../../domain/entities.js';
|
||||||
import { CityDevelopmentActionDefinition } from './cityDevelopment.js';
|
import { CityDevelopmentActionDefinition } from './cityDevelopment.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export class ActionDefinition<
|
export class ActionDefinition<
|
||||||
@@ -21,6 +22,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_농지개간',
|
key: 'che_농지개간',
|
||||||
category: '내정',
|
category: '내정',
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type {
|
|||||||
GeneralActionResolveContext,
|
GeneralActionResolveContext,
|
||||||
} from '../../engine.js';
|
} from '../../engine.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export interface BoostMoraleArgs {}
|
export interface BoostMoraleArgs {}
|
||||||
@@ -82,6 +83,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_사기진작',
|
key: 'che_사기진작',
|
||||||
category: '군사',
|
category: '군사',
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import type {
|
|||||||
GeneralActionResolveContext,
|
GeneralActionResolveContext,
|
||||||
} from '../../engine.js';
|
} from '../../engine.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export type DomesticCriticalPick = 'fail' | 'normal' | 'success';
|
export type DomesticCriticalPick = 'fail' | 'normal' | 'success';
|
||||||
@@ -422,6 +423,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_상업투자',
|
key: 'che_상업투자',
|
||||||
category: '내정',
|
category: '내정',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { GeneralTriggerState } from '../../../domain/entities.js';
|
import type { GeneralTriggerState } from '../../../domain/entities.js';
|
||||||
import { CityDevelopmentActionDefinition } from './cityDevelopment.js';
|
import { CityDevelopmentActionDefinition } from './cityDevelopment.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export class ActionDefinition<
|
export class ActionDefinition<
|
||||||
@@ -21,6 +22,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_성벽보수',
|
key: 'che_성벽보수',
|
||||||
category: '내정',
|
category: '내정',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { GeneralTriggerState } from '../../../domain/entities.js';
|
import type { GeneralTriggerState } from '../../../domain/entities.js';
|
||||||
import { CityDevelopmentActionDefinition } from './cityDevelopment.js';
|
import { CityDevelopmentActionDefinition } from './cityDevelopment.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export class ActionDefinition<
|
export class ActionDefinition<
|
||||||
@@ -21,6 +22,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_수비강화',
|
key: 'che_수비강화',
|
||||||
category: '내정',
|
category: '내정',
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type {
|
|||||||
GeneralActionResolveContext,
|
GeneralActionResolveContext,
|
||||||
} from '../../engine.js';
|
} from '../../engine.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export interface RecoveryArgs {}
|
export interface RecoveryArgs {}
|
||||||
@@ -70,6 +71,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_요양',
|
key: 'che_요양',
|
||||||
category: '개인',
|
category: '개인',
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ import {
|
|||||||
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
||||||
import { buildRecruitmentGeneral } from './recruitment.js';
|
import { buildRecruitmentGeneral } from './recruitment.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
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 { TurnCommandEnv } from '../commandEnv.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.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 = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_인재탐색',
|
key: 'che_인재탐색',
|
||||||
category: '인사',
|
category: '인사',
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
} from '../../engine.js';
|
} from '../../engine.js';
|
||||||
import { LogCategory, LogFormat } from '../../../logging/types.js';
|
import { LogCategory, LogFormat } from '../../../logging/types.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export interface AppointmentArgs {
|
export interface AppointmentArgs {
|
||||||
@@ -60,6 +61,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_임관',
|
key: 'che_임관',
|
||||||
category: '전략',
|
category: '전략',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { GeneralTriggerState } from '../../../domain/entities.js';
|
import type { GeneralTriggerState } from '../../../domain/entities.js';
|
||||||
import { CityDevelopmentActionDefinition } from './cityDevelopment.js';
|
import { CityDevelopmentActionDefinition } from './cityDevelopment.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export class ActionDefinition<
|
export class ActionDefinition<
|
||||||
@@ -21,6 +22,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_주민선정',
|
key: 'che_주민선정',
|
||||||
category: '내정',
|
category: '내정',
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import type {
|
|||||||
GeneralActionResolver,
|
GeneralActionResolver,
|
||||||
} from '../../engine.js';
|
} from '../../engine.js';
|
||||||
import type { MapDefinition, UnitSetDefinition } from '../../../world/types.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 { TurnCommandEnv } from '../commandEnv.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
import {
|
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 = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_징병',
|
key: 'che_징병',
|
||||||
category: '내정',
|
category: '내정',
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ import type {
|
|||||||
MapDefinition,
|
MapDefinition,
|
||||||
UnitSetDefinition,
|
UnitSetDefinition,
|
||||||
} from '../../../world/types.js';
|
} from '../../../world/types.js';
|
||||||
|
import type { ActionContextBuilder } from '../actionContext.js';
|
||||||
|
import {
|
||||||
|
buildWarAftermathConfig,
|
||||||
|
buildWarConfig,
|
||||||
|
buildWarTime,
|
||||||
|
} from '../actionContextHelpers.js';
|
||||||
|
|
||||||
export interface DispatchArgs {
|
export interface DispatchArgs {
|
||||||
destCityId: number;
|
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 = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_출병',
|
key: 'che_출병',
|
||||||
category: '군사',
|
category: '군사',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { GeneralTriggerState } from '../../../domain/entities.js';
|
import type { GeneralTriggerState } from '../../../domain/entities.js';
|
||||||
import { CityDevelopmentActionDefinition } from './cityDevelopment.js';
|
import { CityDevelopmentActionDefinition } from './cityDevelopment.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export class ActionDefinition<
|
export class ActionDefinition<
|
||||||
@@ -21,6 +22,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_치안강화',
|
key: 'che_치안강화',
|
||||||
category: '내정',
|
category: '내정',
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import {
|
|||||||
} from '../../engine.js';
|
} from '../../engine.js';
|
||||||
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export interface FireAttackArgs {
|
export interface FireAttackArgs {
|
||||||
@@ -495,6 +496,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_화계',
|
key: 'che_화계',
|
||||||
category: '계략',
|
category: '계략',
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type {
|
|||||||
GeneralActionResolveContext,
|
GeneralActionResolveContext,
|
||||||
} from '../../engine.js';
|
} from '../../engine.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export interface TrainingArgs {}
|
export interface TrainingArgs {}
|
||||||
@@ -82,6 +83,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: 'che_훈련',
|
key: 'che_훈련',
|
||||||
category: '군사',
|
category: '군사',
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type {
|
|||||||
} from '../../engine.js';
|
} from '../../engine.js';
|
||||||
import { LogCategory, LogFormat } from '../../../logging/types.js';
|
import { LogCategory, LogFormat } from '../../../logging/types.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export interface RestArgs {}
|
export interface RestArgs {}
|
||||||
@@ -65,6 +66,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: GeneralTurnCommandSpec = {
|
export const commandSpec: GeneralTurnCommandSpec = {
|
||||||
key: '휴식',
|
key: '휴식',
|
||||||
category: '개인',
|
category: '개인',
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import type {
|
|||||||
import { createGeneralPatchEffect, createLogEffect } from '../../engine.js';
|
import { createGeneralPatchEffect, createLogEffect } from '../../engine.js';
|
||||||
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
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 { TurnCommandEnv } from '../commandEnv.js';
|
||||||
import type { NationTurnCommandSpec } from './index.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 = {
|
export const commandSpec: NationTurnCommandSpec = {
|
||||||
key: 'che_발령',
|
key: 'che_발령',
|
||||||
category: '인사',
|
category: '인사',
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import type {
|
|||||||
} from '../../engine.js';
|
} from '../../engine.js';
|
||||||
import { createLogEffect } from '../../engine.js';
|
import { createLogEffect } from '../../engine.js';
|
||||||
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
||||||
|
import type { ActionContextBuilder } from '../actionContext.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
import type { NationTurnCommandSpec } from './index.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 = {
|
export const commandSpec: NationTurnCommandSpec = {
|
||||||
key: 'che_불가침제의',
|
key: 'che_불가침제의',
|
||||||
category: '외교',
|
category: '외교',
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import type {
|
|||||||
import { createLogEffect } from '../../engine.js';
|
import { createLogEffect } from '../../engine.js';
|
||||||
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export interface NonAggressionCancelProposalArgs {
|
export interface NonAggressionCancelProposalArgs {
|
||||||
@@ -86,6 +87,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: NationTurnCommandSpec = {
|
export const commandSpec: NationTurnCommandSpec = {
|
||||||
key: 'che_불가침파기제의',
|
key: 'che_불가침파기제의',
|
||||||
category: '외교',
|
category: '외교',
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import type {
|
|||||||
import { createDiplomacyPatchEffect, createLogEffect } from '../../engine.js';
|
import { createDiplomacyPatchEffect, createLogEffect } from '../../engine.js';
|
||||||
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export interface DeclareWarArgs {
|
export interface DeclareWarArgs {
|
||||||
@@ -106,6 +107,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: NationTurnCommandSpec = {
|
export const commandSpec: NationTurnCommandSpec = {
|
||||||
key: 'che_선전포고',
|
key: 'che_선전포고',
|
||||||
category: '외교',
|
category: '외교',
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ import {
|
|||||||
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
||||||
import { buildRecruitmentGeneral } from '../general/recruitment.js';
|
import { buildRecruitmentGeneral } from '../general/recruitment.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
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 { TurnCommandEnv } from '../commandEnv.js';
|
||||||
import type { NationTurnCommandSpec } from './index.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 = {
|
export const commandSpec: NationTurnCommandSpec = {
|
||||||
key: 'che_의병모집',
|
key: 'che_의병모집',
|
||||||
category: '전략',
|
category: '전략',
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
|
|||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
|
import type { ActionContextBuilder } from '../actionContext.js';
|
||||||
|
|
||||||
export interface AwardArgs {
|
export interface AwardArgs {
|
||||||
isGold: boolean;
|
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 = {
|
export const commandSpec: NationTurnCommandSpec = {
|
||||||
key: 'che_포상',
|
key: 'che_포상',
|
||||||
category: '인사',
|
category: '인사',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type {
|
|||||||
GeneralActionResolver,
|
GeneralActionResolver,
|
||||||
} from '../../engine.js';
|
} from '../../engine.js';
|
||||||
import type { TurnCommandEnv } from '../commandEnv.js';
|
import type { TurnCommandEnv } from '../commandEnv.js';
|
||||||
|
import { defaultActionContextBuilder } from '../actionContext.js';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
|
||||||
export interface NationRestArgs {}
|
export interface NationRestArgs {}
|
||||||
@@ -62,6 +63,9 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 턴 실행은 기본 컨텍스트만 사용한다.
|
||||||
|
export const actionContextBuilder = defaultActionContextBuilder;
|
||||||
|
|
||||||
export const commandSpec: NationTurnCommandSpec = {
|
export const commandSpec: NationTurnCommandSpec = {
|
||||||
key: '휴식',
|
key: '휴식',
|
||||||
category: '휴식',
|
category: '휴식',
|
||||||
|
|||||||
Reference in New Issue
Block a user