Refactor action effects to use direct state mutations and logging

- Removed the use of createGeneralPatchEffect and createLogEffect in various action definitions.
- Replaced effects with direct mutations of the state for general and city entities.
- Updated logging to use context.addLog for better clarity and consistency.
- Ensured that all actions now return an empty effects array after processing.
This commit is contained in:
2026-01-02 10:25:33 +00:00
parent 18247eeff4
commit f137c5be65
17 changed files with 281 additions and 562 deletions
+56 -144
View File
@@ -1,5 +1,5 @@
import type { RandomGenerator } from '@sammo-ts/common';
import { enablePatches, produceWithPatches, type Draft, castDraft } from 'immer';
import { enablePatches, produceWithPatches, castDraft } from 'immer';
import type {
City,
General,
@@ -34,8 +34,16 @@ export interface GeneralActionResolveContext<
rng: RandomGenerator;
city?: City;
nation?: Nation | null;
addLog(
message: string,
options?: Partial<Omit<LogEntryDraft, 'text'>>
): void;
}
export type GeneralActionResolveInputContext<
TriggerState extends GeneralTriggerState = GeneralTriggerState
> = Omit<GeneralActionResolveContext<TriggerState>, 'addLog'>;
export interface TurnScheduleContext {
now: Date;
schedule: TurnSchedule;
@@ -130,106 +138,6 @@ export interface GeneralActionResolution {
};
}
/**
* Immer Draft에 Effect를 적용한다.
* 기존 Effect 기반 코드를 유지하면서 Draft에 즉시 반영하기 위함.
*/
export const applyEffectToDraft = <
TriggerState extends GeneralTriggerState = GeneralTriggerState
>(
draft: Draft<WorldState<TriggerState>>,
effect: GeneralActionEffect<TriggerState>,
context: { generalId: GeneralId; cityId?: CityId; nationId?: NationId }
): void => {
const generalDraft = draft.general as any;
switch (effect.type) {
case 'general:patch':
if (
effect.targetId === undefined ||
effect.targetId === context.generalId
) {
Object.assign(generalDraft, effect.patch);
if (effect.patch.stats) {
generalDraft.stats = {
...generalDraft.stats,
...effect.patch.stats,
};
}
if (effect.patch.role) {
generalDraft.role = {
...generalDraft.role,
...effect.patch.role,
items: {
...generalDraft.role.items,
...(effect.patch.role.items ?? {}),
},
};
}
if (effect.patch.triggerState) {
generalDraft.triggerState = {
...generalDraft.triggerState,
...effect.patch.triggerState,
flags: {
...generalDraft.triggerState.flags,
...(effect.patch.triggerState.flags ?? {}),
},
counters: {
...generalDraft.triggerState.counters,
...(effect.patch.triggerState.counters ?? {}),
},
modifiers: {
...generalDraft.triggerState.modifiers,
...(effect.patch.triggerState.modifiers ?? {}),
},
meta: {
...generalDraft.triggerState.meta,
...(effect.patch.triggerState.meta ?? {}),
},
};
}
if (effect.patch.meta) {
generalDraft.meta = {
...generalDraft.meta,
...effect.patch.meta,
};
}
}
break;
case 'city:patch':
if (
draft.city &&
(effect.targetId === undefined ||
effect.targetId === context.cityId)
) {
Object.assign(draft.city, effect.patch);
if (effect.patch.meta) {
draft.city.meta = {
...draft.city.meta,
...effect.patch.meta,
};
}
}
break;
case 'nation:patch':
if (
draft.nation &&
(effect.targetId === undefined ||
effect.targetId === context.nationId)
) {
Object.assign(draft.nation, effect.patch);
if (effect.patch.meta) {
draft.nation.meta = {
...draft.nation.meta,
...effect.patch.meta,
};
}
}
break;
default:
break;
}
};
export const createGeneralPatchEffect = <
TriggerState extends GeneralTriggerState = GeneralTriggerState
>(
@@ -303,7 +211,7 @@ export const resolveGeneralAction = <
Args = unknown
>(
resolver: GeneralActionResolver<TriggerState, Args>,
context: GeneralActionResolveContext<TriggerState>,
context: GeneralActionResolveInputContext<TriggerState>,
scheduleContext: TurnScheduleContext,
args: Args
): GeneralActionResolution => {
@@ -323,12 +231,56 @@ export const resolveGeneralAction = <
nation: context.nation,
} as WorldState<TriggerState>,
(draft) => {
const addLog = (
message: string,
options: Partial<Omit<LogEntryDraft, 'text'>> = {}
) => {
const entry: LogEntryDraft = {
scope: options.scope ?? LogScope.GENERAL,
category: options.category ?? LogCategory.ACTION,
text: message,
format: options.format ?? LogFormat.MONTH,
...options,
};
switch (entry.scope) {
case LogScope.GENERAL:
logs.push({
...entry,
generalId: entry.generalId ?? context.general.id,
});
break;
case LogScope.NATION:
if (entry.nationId !== undefined) {
logs.push(entry);
break;
}
if (context.nation?.id !== undefined) {
logs.push({
...entry,
nationId: context.nation.id,
});
}
break;
case LogScope.USER:
if (entry.userId) {
logs.push(entry);
}
break;
case LogScope.SYSTEM:
default:
logs.push(entry);
break;
}
};
const outcome = resolver.resolve(
{
...context,
general: castDraft(draft.general),
city: castDraft(draft.city),
nation: castDraft(draft.nation),
addLog,
} as GeneralActionResolveContext<TriggerState>,
args
);
@@ -336,38 +288,7 @@ export const resolveGeneralAction = <
for (const effect of outcome.effects) {
switch (effect.type) {
case 'log':
// 로그 대상이 비어 있으면 현재 장수/국가 기준으로 보정한다.
switch (effect.entry.scope) {
case LogScope.GENERAL:
logs.push({
...effect.entry,
generalId:
effect.entry.generalId ??
context.general.id,
});
break;
case LogScope.NATION:
if (effect.entry.nationId !== undefined) {
logs.push(effect.entry);
break;
}
if (context.nation?.id !== undefined) {
logs.push({
...effect.entry,
nationId: context.nation.id,
});
}
break;
case LogScope.USER:
if (effect.entry.userId) {
logs.push(effect.entry);
}
break;
case LogScope.SYSTEM:
default:
logs.push(effect.entry);
break;
}
addLog(effect.entry.text, effect.entry);
break;
case 'schedule:override':
nextTurnAtOverride = effect.nextTurnAt;
@@ -378,16 +299,7 @@ export const resolveGeneralAction = <
case 'general:patch':
case 'city:patch':
case 'nation:patch':
applyEffectToDraft(draft, effect, {
generalId: context.general.id,
...(context.city?.id !== undefined
? { cityId: context.city.id }
: {}),
...(context.nation?.id !== undefined
? { nationId: context.nation.id }
: {}),
});
// 타겟이 다른 경우 patches에 추가 (applyEffectToDraft에서 처리되지 않은 경우)
// 타겟이 다른 경우 patches에 추가
if (
effect.type === 'general:patch' &&
effect.targetId !== undefined &&