feat: DomesticCommandLoader 및 GeneralAction 관련 타입 개선

This commit is contained in:
2025-12-28 17:27:49 +00:00
parent bfe28773e9
commit 87327cb5b5
3 changed files with 66 additions and 28 deletions
+7 -1
View File
@@ -1,6 +1,9 @@
import type { CommandResolver as CommandResolverType } from './che_상업투자.js';
export type DomesticCommandKey = 'che_상업투자';
type CommandResolverCtor = typeof CommandResolverType;
type CommandResolverArgs = ConstructorParameters<CommandResolverCtor>;
type CommandResolverInstance = InstanceType<CommandResolverCtor>;
export type DomesticCommandImporter = () => Promise<{
CommandResolver: typeof CommandResolverType;
@@ -28,7 +31,10 @@ export class DomesticCommandLoader {
return importer();
}
async create(key: DomesticCommandKey, ...args: unknown[]): Promise<unknown> {
async create(
key: DomesticCommandKey,
...args: CommandResolverArgs
): Promise<CommandResolverInstance> {
const { CommandResolver } = await this.load(key);
return new CommandResolver(...args);
}
+29 -14
View File
@@ -23,9 +23,11 @@ export interface TurnScheduleContext {
schedule: TurnSchedule;
}
export interface GeneralPatchEffect {
export interface GeneralPatchEffect<
TriggerState extends GeneralTriggerState = GeneralTriggerState
> {
type: 'general:patch';
patch: Partial<General>;
patch: Partial<General<TriggerState>>;
}
export interface CityPatchEffect {
@@ -48,22 +50,28 @@ export interface NextTurnOverrideEffect {
nextTurnAt: Date;
}
export type GeneralActionEffect =
| GeneralPatchEffect
export type GeneralActionEffect<
TriggerState extends GeneralTriggerState = GeneralTriggerState
> =
| GeneralPatchEffect<TriggerState>
| CityPatchEffect
| NationPatchEffect
| LogEffect
| NextTurnOverrideEffect;
export interface GeneralActionOutcome {
effects: GeneralActionEffect[];
export interface GeneralActionOutcome<
TriggerState extends GeneralTriggerState = GeneralTriggerState
> {
effects: GeneralActionEffect<TriggerState>[];
}
export interface GeneralActionResolver<
TriggerState extends GeneralTriggerState = GeneralTriggerState
> {
key: string;
resolve(context: GeneralActionResolveContext<TriggerState>): GeneralActionOutcome;
resolve(
context: GeneralActionResolveContext<TriggerState>
): GeneralActionOutcome<TriggerState>;
}
export interface GeneralActionResolution {
@@ -93,10 +101,10 @@ const mergeRole = (
},
});
const mergeTriggerState = (
base: GeneralTriggerState,
patch: Partial<GeneralTriggerState>
): GeneralTriggerState => ({
const mergeTriggerState = <TriggerState extends GeneralTriggerState>(
base: TriggerState,
patch: Partial<TriggerState>
): TriggerState => ({
...base,
...patch,
flags: { ...base.flags, ...(patch.flags ?? {}) },
@@ -105,7 +113,10 @@ const mergeTriggerState = (
meta: { ...base.meta, ...(patch.meta ?? {}) },
});
const applyGeneralPatch = (base: General, patch: Partial<General>): General => ({
const applyGeneralPatch = <TriggerState extends GeneralTriggerState>(
base: General<TriggerState>,
patch: Partial<General<TriggerState>>
): General<TriggerState> => ({
...base,
...patch,
stats: patch.stats ? mergeStats(base.stats, patch.stats) : base.stats,
@@ -173,12 +184,16 @@ export const resolveGeneralAction = <
nextTurnAtOverride ??
getNextTurnAt(scheduleContext.now, scheduleContext.schedule);
return {
const resolution: GeneralActionResolution = {
general: nextGeneral,
city: nextCity,
nation: nextNation,
nextTurnAt,
logs,
effects: outcome.effects,
};
if (nextCity) {
resolution.city = nextCity;
}
return resolution;
};
+30 -13
View File
@@ -3,8 +3,10 @@ export interface TurnScheduleEntry {
tickMinutes: number;
}
export type TurnScheduleEntries = [TurnScheduleEntry, ...TurnScheduleEntry[]];
export interface TurnSchedule {
entries: TurnScheduleEntry[];
entries: TurnScheduleEntries;
}
const MINUTES_PER_DAY = 24 * 60;
@@ -26,7 +28,7 @@ const toLocalDateAtMinute = (date: Date, minuteOfDay: number, dayOffset = 0): Da
);
};
const normalizeEntries = (entries: TurnScheduleEntry[]): TurnScheduleEntry[] => {
const normalizeEntries = (entries: TurnScheduleEntries): TurnScheduleEntries => {
const normalized = entries
.map((entry) => ({
startMinute: Math.max(0, Math.min(MINUTES_PER_DAY - 1, entry.startMinute)),
@@ -34,27 +36,33 @@ const normalizeEntries = (entries: TurnScheduleEntry[]): TurnScheduleEntry[] =>
}))
.sort((a, b) => a.startMinute - b.startMinute);
if (normalized.length === 0) {
throw new Error('Turn schedule needs at least one entry.');
}
return normalized;
return normalized as TurnScheduleEntries;
};
const findCurrentEntryIndex = (minuteOfDay: number, entries: TurnScheduleEntry[]): number => {
const findCurrentEntryIndex = (
minuteOfDay: number,
entries: TurnScheduleEntries
): number => {
for (let i = entries.length - 1; i >= 0; i -= 1) {
if (entries[i].startMinute <= minuteOfDay) {
const entry = entries[i];
if (entry && entry.startMinute <= minuteOfDay) {
return i;
}
}
return -1;
};
const getEntryAt = (
entries: TurnScheduleEntries,
index: number
): TurnScheduleEntry =>
entries[Math.max(0, Math.min(entries.length - 1, index))] ?? entries[0];
export const getTickMinutesAt = (date: Date, schedule: TurnSchedule): number => {
const entries = normalizeEntries(schedule.entries);
const minuteOfDay = toMinuteOfDay(date);
const index = findCurrentEntryIndex(minuteOfDay, entries);
const entry = index >= 0 ? entries[index] : entries[entries.length - 1];
const entry = getEntryAt(entries, index >= 0 ? index : entries.length - 1);
return entry.tickMinutes;
};
@@ -67,9 +75,18 @@ export const getNextTurnAt = (date: Date, schedule: TurnSchedule): Date => {
const nextIndex = (currentIndex + 1) % entries.length;
const nextDayOffset = startDayOffset + (nextIndex > currentIndex ? 0 : 1);
const currentEntry = entries[currentIndex];
const segmentStart = toLocalDateAtMinute(date, currentEntry.startMinute, startDayOffset);
const segmentEnd = toLocalDateAtMinute(date, entries[nextIndex].startMinute, nextDayOffset);
const currentEntry = getEntryAt(entries, currentIndex);
const nextEntry = getEntryAt(entries, nextIndex);
const segmentStart = toLocalDateAtMinute(
date,
currentEntry.startMinute,
startDayOffset
);
const segmentEnd = toLocalDateAtMinute(
date,
nextEntry.startMinute,
nextDayOffset
);
const elapsedMinutes = (date.getTime() - segmentStart.getTime()) / 60000;
const nextStep = Math.floor(elapsedMinutes / currentEntry.tickMinutes) + 1;