diff --git a/app/game-engine/src/turn/reservedTurnHandler.ts b/app/game-engine/src/turn/reservedTurnHandler.ts index 0d4d210..bf83f89 100644 --- a/app/game-engine/src/turn/reservedTurnHandler.ts +++ b/app/game-engine/src/turn/reservedTurnHandler.ts @@ -369,7 +369,7 @@ export const createReservedTurnHandler = async (options: { const resolution = resolveGeneralAction( definition, - actionContext as GeneralActionResolveContext, + actionContext, { now: currentGeneral.turnTime, schedule: context.schedule, diff --git a/packages/logic/src/actions/engine.ts b/packages/logic/src/actions/engine.ts index ed7e13a..f4f4df3 100644 --- a/packages/logic/src/actions/engine.ts +++ b/packages/logic/src/actions/engine.ts @@ -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> + ): void; } +export type GeneralActionResolveInputContext< + TriggerState extends GeneralTriggerState = GeneralTriggerState +> = Omit, '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>, - effect: GeneralActionEffect, - 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, - context: GeneralActionResolveContext, + context: GeneralActionResolveInputContext, scheduleContext: TurnScheduleContext, args: Args ): GeneralActionResolution => { @@ -323,12 +231,56 @@ export const resolveGeneralAction = < nation: context.nation, } as WorldState, (draft) => { + const addLog = ( + message: string, + options: Partial> = {} + ) => { + 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, 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 && diff --git a/packages/logic/src/actions/turn/general/che_거병.ts b/packages/logic/src/actions/turn/general/che_거병.ts index bd64de1..b37059c 100644 --- a/packages/logic/src/actions/turn/general/che_거병.ts +++ b/packages/logic/src/actions/turn/general/che_거병.ts @@ -6,8 +6,7 @@ import type { GeneralActionOutcome, GeneralActionResolveContext, } from '../../engine.js'; -import { createGeneralPatchEffect, createLogEffect } from '../../engine.js'; -import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; +import { LogCategory, LogFormat } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; @@ -35,24 +34,19 @@ export class ActionDefinition< _args: UprisingArgs ): GeneralActionOutcome { const general = context.general; - const meta = { - ...general.meta, + + // 직접 수정 (Immer Draft) + general.meta = { + ...general.meta as object, uprising: true as TriggerValue, }; - return { - effects: [ - createGeneralPatchEffect( - { meta } as Partial, - general.id - ), - createLogEffect(`${ACTION_NAME}을 준비했습니다.`, { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }), - ], - }; + context.addLog(`${ACTION_NAME}을 준비했습니다.`, { + category: LogCategory.ACTION, + format: LogFormat.MONTH, + }); + + return { effects: [] }; } } diff --git a/packages/logic/src/actions/turn/general/che_건국.ts b/packages/logic/src/actions/turn/general/che_건국.ts index ac62300..e9585cf 100644 --- a/packages/logic/src/actions/turn/general/che_건국.ts +++ b/packages/logic/src/actions/turn/general/che_건국.ts @@ -6,8 +6,7 @@ import type { GeneralActionOutcome, GeneralActionResolveContext, } from '../../engine.js'; -import { createGeneralPatchEffect, createLogEffect } from '../../engine.js'; -import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; +import { LogCategory, LogFormat } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; @@ -35,24 +34,19 @@ export class ActionDefinition< _args: FoundingArgs ): GeneralActionOutcome { const general = context.general; - const meta = { - ...general.meta, + + // 직접 수정 (Immer Draft) + general.meta = { + ...general.meta as object, founding: true as TriggerValue, }; - return { - effects: [ - createGeneralPatchEffect( - { meta } as Partial, - general.id - ), - createLogEffect(`${ACTION_NAME}을 준비했습니다.`, { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }), - ], - }; + context.addLog(`${ACTION_NAME}을 준비했습니다.`, { + category: LogCategory.ACTION, + format: LogFormat.MONTH, + }); + + return { effects: [] }; } } diff --git a/packages/logic/src/actions/turn/general/che_기술연구.ts b/packages/logic/src/actions/turn/general/che_기술연구.ts index bdd969f..344acc5 100644 --- a/packages/logic/src/actions/turn/general/che_기술연구.ts +++ b/packages/logic/src/actions/turn/general/che_기술연구.ts @@ -1,5 +1,4 @@ import type { - General, GeneralTriggerState, Nation, } from '../../../domain/entities.js'; @@ -20,12 +19,6 @@ import type { GeneralActionOutcome, GeneralActionResolveContext, } from '../../engine.js'; -import { - createGeneralPatchEffect, - createLogEffect, - createNationPatchEffect, -} from '../../engine.js'; -import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; @@ -84,17 +77,10 @@ export class ActionDefinition< _args: TechResearchArgs ): GeneralActionOutcome { const general = context.general; - const nation = context.nation ?? null; + const nation = context.nation; if (!nation) { - return { - effects: [ - createLogEffect('국가 정보를 찾지 못했습니다.', { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }), - ], - }; + context.addLog('국가 정보를 찾지 못했습니다.'); + return { effects: [] }; } const delta = this.env.techDelta ?? DEFAULT_TECH_DELTA; @@ -107,26 +93,14 @@ export class ActionDefinition< const nextTech = Math.min(currentTech + delta, maxTech); const applied = nextTech - currentTech; const costGold = this.env.costGold ?? 0; - const generalPatch: Partial> = { - gold: Math.max(0, general.gold - costGold), - }; - return { - effects: [ - createNationPatchEffect( - { - meta: { ...nation.meta, tech: nextTech }, - } as Partial, - nation.id - ), - createGeneralPatchEffect(generalPatch, general.id), - createLogEffect(`${ACTION_NAME}로 기술이 ${applied} 상승했습니다.`, { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }), - ], - }; + // 직접 수정 (Immer Draft) + nation.meta = { ...nation.meta, tech: nextTech }; + general.gold = Math.max(0, general.gold - costGold); + + context.addLog(`${ACTION_NAME}로 기술이 ${applied} 상승했습니다.`); + + return { effects: [] }; } } diff --git a/packages/logic/src/actions/turn/general/che_사기진작.ts b/packages/logic/src/actions/turn/general/che_사기진작.ts index 47a2316..9ab3178 100644 --- a/packages/logic/src/actions/turn/general/che_사기진작.ts +++ b/packages/logic/src/actions/turn/general/che_사기진작.ts @@ -1,5 +1,4 @@ import type { - General, GeneralTriggerState, } from '../../../domain/entities.js'; import type { @@ -13,8 +12,6 @@ import type { GeneralActionOutcome, GeneralActionResolveContext, } from '../../engine.js'; -import { createGeneralPatchEffect, createLogEffect } from '../../engine.js'; -import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; @@ -74,21 +71,14 @@ export class ActionDefinition< const nextAtmos = clamp(general.atmos + delta, 0, maxAtmos); const applied = nextAtmos - general.atmos; const costGold = this.env.costGold ?? 0; - const patch: Partial> = { - atmos: nextAtmos, - gold: Math.max(0, general.gold - costGold), - }; - return { - effects: [ - createGeneralPatchEffect(patch, general.id), - createLogEffect(`${ACTION_NAME}로 사기가 ${applied} 증가했습니다.`, { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }), - ], - }; + // 직접 수정 (Immer Draft) + general.atmos = nextAtmos; + general.gold = Math.max(0, general.gold - costGold); + + context.addLog(`${ACTION_NAME}로 사기가 ${applied} 증가했습니다.`); + + return { effects: [] }; } } diff --git a/packages/logic/src/actions/turn/general/che_상업투자.ts b/packages/logic/src/actions/turn/general/che_상업투자.ts index 7d4db38..2e406b9 100644 --- a/packages/logic/src/actions/turn/general/che_상업투자.ts +++ b/packages/logic/src/actions/turn/general/che_상업투자.ts @@ -30,12 +30,6 @@ import type { GeneralActionOutcome, GeneralActionResolver, GeneralActionResolveContext, - GeneralActionEffect, -} from '../../engine.js'; -import { - createCityPatchEffect, - createGeneralPatchEffect, - createLogEffect, } from '../../engine.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; @@ -335,36 +329,24 @@ export class ActionResolver< context.rng ); - const updatedCommerce = clamp( + // 직접 수정 (Immer Draft) + city.commerce = clamp( city.commerce + result.score, 0, city.commerceMax ); - const nextGold = Math.max(0, general.gold - result.costGold); - const nextRice = Math.max(0, general.rice - result.costRice); - const nextExperience = general.experience + result.exp; - const nextDedication = general.dedication + result.dedication; + general.gold = Math.max(0, general.gold - result.costGold); + general.rice = Math.max(0, general.rice - result.costRice); + general.experience += result.exp; + general.dedication += result.dedication; const metaWithStatExp = addMetaNumber(general.meta, STAT_EXP_KEY, 1); - const metaUpdated = + general.meta = result.pick === 'success' ? { ...metaWithStatExp, max_domestic_critical: result.score } : { ...metaWithStatExp, max_domestic_critical: 0 }; - const effects: Array> = [ - createCityPatchEffect({ - [CITY_KEY]: updatedCommerce, - } as Partial), - createGeneralPatchEffect({ - gold: nextGold, - rice: nextRice, - experience: nextExperience, - dedication: nextDedication, - meta: metaUpdated, - }), - ]; - const pickLabel = result.pick === 'success' ? '성공' @@ -372,9 +354,9 @@ export class ActionResolver< ? '실패' : '완료'; const logMessage = `${ACTION_NAME} ${pickLabel}: +${Math.round(result.score)}`; - effects.push(createLogEffect(logMessage)); + context.addLog(logMessage); - return { effects }; + return { effects: [] }; } } diff --git a/packages/logic/src/actions/turn/general/che_요양.ts b/packages/logic/src/actions/turn/general/che_요양.ts index 13f2cad..fb6ece4 100644 --- a/packages/logic/src/actions/turn/general/che_요양.ts +++ b/packages/logic/src/actions/turn/general/che_요양.ts @@ -1,5 +1,4 @@ import type { - General, GeneralTriggerState, } from '../../../domain/entities.js'; import type { @@ -13,8 +12,6 @@ import type { GeneralActionOutcome, GeneralActionResolveContext, } from '../../engine.js'; -import { createGeneralPatchEffect, createLogEffect } from '../../engine.js'; -import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; @@ -62,21 +59,14 @@ export class ActionDefinition< const nextInjury = Math.max(0, general.injury - delta); const applied = general.injury - nextInjury; const costGold = this.env.costGold ?? 0; - const patch: Partial> = { - injury: nextInjury, - gold: Math.max(0, general.gold - costGold), - }; - return { - effects: [ - createGeneralPatchEffect(patch, general.id), - createLogEffect(`${ACTION_NAME}으로 부상이 ${applied} 회복되었습니다.`, { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }), - ], - }; + // 직접 수정 (Immer Draft) + general.injury = nextInjury; + general.gold = Math.max(0, general.gold - costGold); + + context.addLog(`${ACTION_NAME}으로 부상이 ${applied} 회복되었습니다.`); + + return { effects: [] }; } } diff --git a/packages/logic/src/actions/turn/general/che_의병모집.ts b/packages/logic/src/actions/turn/general/che_의병모집.ts index d78eb3b..b324ff5 100644 --- a/packages/logic/src/actions/turn/general/che_의병모집.ts +++ b/packages/logic/src/actions/turn/general/che_의병모집.ts @@ -27,9 +27,6 @@ import type { } from '../../engine.js'; import { createGeneralAddEffect, - createGeneralPatchEffect, - createLogEffect, - createNationPatchEffect, } from '../../engine.js'; import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; import { buildRecruitmentGeneral } from './recruitment.js'; @@ -250,47 +247,34 @@ export class ActionResolver< _args: VolunteerRecruitArgs ): GeneralActionOutcome { void _args; - const effects: Array> = []; - const nation = context.nation ?? null; + const general = context.general; + const nation = context.nation; const expGain = 5 * (DEFAULT_PRE_TURN + 1); const dedGain = 5 * (DEFAULT_PRE_TURN + 1); - effects.push( - createGeneralPatchEffect({ - experience: context.general.experience + expGain, - dedication: context.general.dedication + dedGain, - }) - ); - effects.push( - createLogEffect(`${ACTION_NAME} 발동!`, { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }) - ); - effects.push( - createLogEffect(`${ACTION_NAME} 발동`, { - scope: LogScope.GENERAL, - category: LogCategory.HISTORY, - format: LogFormat.YEAR_MONTH, - }) - ); + // 직접 수정 (Immer Draft) + general.experience += expGain; + general.dedication += dedGain; - if (nation?.id) { - const generalName = context.general.name; + context.addLog(`${ACTION_NAME} 발동!`); + context.addLog(`${ACTION_NAME} 발동`, { + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + }); + + if (nation) { + const generalName = general.name; const generalJosa = JosaUtil.pick(generalName, '이'); const actionJosa = JosaUtil.pick(ACTION_NAME, '을'); - effects.push( - createLogEffect( - `${generalName}${generalJosa} ${ACTION_NAME}${actionJosa} 발동했습니다.`, - { - scope: LogScope.NATION, - category: LogCategory.HISTORY, - nationId: nation.id, - format: LogFormat.YEAR_MONTH, - } - ) + context.addLog( + `${generalName}${generalJosa} ${ACTION_NAME}${actionJosa} 발동했습니다.`, + { + scope: LogScope.NATION, + category: LogCategory.HISTORY, + nationId: nation.id, + format: LogFormat.YEAR_MONTH, + } ); } @@ -310,16 +294,15 @@ export class ActionResolver< const globalDelay = this.command.getGlobalDelay(context); if (nation) { - effects.push( - createNationPatchEffect({ - meta: { - gennum: nextGennum, - strategic_cmd_limit: globalDelay, - }, - }, nation.id) - ); + nation.meta = { + ...nation.meta as object, + gennum: nextGennum, + strategic_cmd_limit: globalDelay, + }; } + const effects: Array> = []; + const baseAge = this.env.npcAge ?? DEFAULT_NPC_AGE; const deathYears = this.env.npcDeathYears ?? DEFAULT_NPC_DEATH_YEARS; const killTurnMin = this.env.killTurnMin ?? DEFAULT_KILLTURN_MIN; diff --git a/packages/logic/src/actions/turn/general/che_인재탐색.ts b/packages/logic/src/actions/turn/general/che_인재탐색.ts index f336efa..03c954e 100644 --- a/packages/logic/src/actions/turn/general/che_인재탐색.ts +++ b/packages/logic/src/actions/turn/general/che_인재탐색.ts @@ -20,14 +20,11 @@ import { } from '../../../triggers/general-action.js'; import type { GeneralActionDefinition } from '../../definition.js'; import type { - GeneralActionEffect, GeneralActionOutcome, GeneralActionResolveContext, } from '../../engine.js'; import { createGeneralAddEffect, - createGeneralPatchEffect, - createLogEffect, } from '../../engine.js'; import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; import { buildRecruitmentGeneral } from './recruitment.js'; @@ -301,40 +298,35 @@ export class ActionResolver< _args: TalentScoutArgs ): GeneralActionOutcome { void _args; + const general = context.general; const { gold: reqGold, rice: reqRice } = this.command.getCost(); const prop = this.command.calcFoundProp(context); const found = context.rng.nextBool(prop); - const statKey = pickStatExpKey(context.rng, context.general); + const statKey = pickStatExpKey(context.rng, general); const metaAfter = found - ? addMetaNumber(context.general.meta, statKey, 3) - : addMetaNumber(context.general.meta, statKey, 1); + ? addMetaNumber(general.meta, statKey, 3) + : addMetaNumber(general.meta, statKey, 1); - const nextGold = Math.max(0, context.general.gold - reqGold); - const nextRice = Math.max(0, context.general.rice - reqRice); + const nextGold = Math.max(0, general.gold - reqGold); + const nextRice = Math.max(0, general.rice - reqRice); const expGain = found ? 200 : 100; const dedGain = found ? 300 : 70; - const effects: Array> = [ - createGeneralPatchEffect({ - gold: nextGold, - rice: nextRice, - experience: context.general.experience + expGain, - dedication: context.general.dedication + dedGain, - meta: metaAfter, - }), - ]; + // 직접 수정 (Immer Draft) + general.gold = nextGold; + general.rice = nextRice; + general.experience += expGain; + general.dedication += dedGain; + general.meta = metaAfter; if (!found) { - effects.push( - createLogEffect('인재를 찾을 수 없었습니다.', { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }) - ); - return { effects }; + context.addLog('인재를 찾을 수 없었습니다.', { + category: LogCategory.ACTION, + format: LogFormat.MONTH, + }); + return { effects: [] }; } const candidate = resolveCandidate(context, context.rng, this.env); @@ -396,32 +388,25 @@ export class ActionResolver< meta, }); - effects.push(createGeneralAddEffect(newGeneral)); const nameObjJosa = JosaUtil.pick(name, '을'); const nameSubjJosa = JosaUtil.pick(name, '이'); - effects.push( - createLogEffect(`인재 ${name}${nameObjJosa} 발견했습니다.`, { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }) - ); - effects.push( - createLogEffect(`인재 ${name}${nameSubjJosa} 등장했습니다.`, { - scope: LogScope.SYSTEM, - category: LogCategory.SUMMARY, - format: LogFormat.MONTH, - }) - ); - effects.push( - createLogEffect(`인재 ${name}${nameObjJosa} 발견했습니다.`, { - scope: LogScope.GENERAL, - category: LogCategory.HISTORY, - format: LogFormat.YEAR_MONTH, - }) - ); + context.addLog(`인재 ${name}${nameObjJosa} 발견했습니다.`, { + category: LogCategory.ACTION, + format: LogFormat.MONTH, + }); + context.addLog(`인재 ${name}${nameSubjJosa} 등장했습니다.`, { + scope: LogScope.SYSTEM, + category: LogCategory.SUMMARY, + format: LogFormat.MONTH, + }); + context.addLog(`인재 ${name}${nameObjJosa} 발견했습니다.`, { + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + }); - return { effects }; + return { + effects: [createGeneralAddEffect(newGeneral)], + }; } } diff --git a/packages/logic/src/actions/turn/general/che_임관.ts b/packages/logic/src/actions/turn/general/che_임관.ts index c460683..3055893 100644 --- a/packages/logic/src/actions/turn/general/che_임관.ts +++ b/packages/logic/src/actions/turn/general/che_임관.ts @@ -9,8 +9,7 @@ import type { GeneralActionOutcome, GeneralActionResolveContext, } from '../../engine.js'; -import { createLogEffect } from '../../engine.js'; -import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; +import { LogCategory, LogFormat } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; @@ -47,22 +46,17 @@ export class ActionDefinition< } resolve( - _context: GeneralActionResolveContext, + context: GeneralActionResolveContext, args: AppointmentArgs ): GeneralActionOutcome { - void _context; - return { - effects: [ - createLogEffect( - `${ACTION_NAME}을 신청했습니다. (국가 ${args.destNationId})`, - { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - } - ), - ], - }; + context.addLog( + `${ACTION_NAME}을 신청했습니다. (국가 ${args.destNationId})`, + { + category: LogCategory.ACTION, + format: LogFormat.MONTH, + } + ); + return { effects: [] }; } } diff --git a/packages/logic/src/actions/turn/general/che_징병.ts b/packages/logic/src/actions/turn/general/che_징병.ts index 219a817..2237919 100644 --- a/packages/logic/src/actions/turn/general/che_징병.ts +++ b/packages/logic/src/actions/turn/general/che_징병.ts @@ -26,15 +26,9 @@ import { } from '../../../triggers/general-action.js'; import type { GeneralActionDefinition } from '../../definition.js'; import type { - GeneralActionEffect, GeneralActionOutcome, GeneralActionResolveContext, } from '../../engine.js'; -import { - createCityPatchEffect, - createGeneralPatchEffect, - createLogEffect, -} from '../../engine.js'; import type { MapDefinition, UnitSetDefinition } from '../../../world/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; @@ -351,16 +345,14 @@ export class ActionResolver< ): GeneralActionOutcome { const { general, city } = context; if (!city) { - return { - effects: [createLogEffect('도시 정보가 없습니다.')], - }; + context.addLog('도시 정보가 없습니다.'); + return { effects: [] }; } const crewType = findCrewTypeById(context.unitSet, args.crewType); if (!crewType) { - return { - effects: [createLogEffect('병종 정보가 없습니다.')], - }; + context.addLog('병종 정보가 없습니다.'); + return { effects: [] }; } const availabilityContext: CrewTypeAvailabilityContext = { @@ -376,9 +368,8 @@ export class ActionResolver< availabilityContext.startYear = context.startYear; } if (!isCrewTypeAvailable(context.unitSet, crewType.id, availabilityContext)) { - return { - effects: [createLogEffect('현재 선택할 수 없는 병종입니다.')], - }; + context.addLog('현재 선택할 수 없는 병종입니다.'); + return { effects: [] }; } const plan = this.command.getCost( @@ -435,30 +426,26 @@ export class ActionResolver< const expGain = Math.round(appliedCrew / 100); const dedGain = Math.round(appliedCrew / 100); - const metaUpdated = addMetaNumber(general.meta, 'leadership_exp', 1); + // 직접 수정 (Immer Draft) + city.population = nextPopulation; + city.meta = { + ...city.meta as object, + trust: nextTrust, + }; - const effects: Array> = [ - createCityPatchEffect({ - population: nextPopulation, - meta: { - trust: nextTrust, - }, - }), - createGeneralPatchEffect({ - crewTypeId: nextCrewTypeId, - crew: nextCrew, - train: nextTrain, - atmos: nextAtmos, - gold: nextGold, - rice: nextRice, - experience: general.experience + expGain, - dedication: general.dedication + dedGain, - meta: metaUpdated, - }), - createLogEffect(logMessage), - ]; + general.crewTypeId = nextCrewTypeId; + general.crew = nextCrew; + general.train = nextTrain; + general.atmos = nextAtmos; + general.gold = nextGold; + general.rice = nextRice; + general.experience += expGain; + general.dedication += dedGain; + general.meta = addMetaNumber(general.meta, 'leadership_exp', 1); - return { effects }; + context.addLog(logMessage); + + return { effects: [] }; } } diff --git a/packages/logic/src/actions/turn/general/che_출병.ts b/packages/logic/src/actions/turn/general/che_출병.ts index 8c66d71..f93eba3 100644 --- a/packages/logic/src/actions/turn/general/che_출병.ts +++ b/packages/logic/src/actions/turn/general/che_출병.ts @@ -12,8 +12,7 @@ import type { GeneralActionOutcome, GeneralActionResolveContext, } from '../../engine.js'; -import { createLogEffect } from '../../engine.js'; -import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; +import { LogCategory, LogFormat } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; @@ -56,19 +55,14 @@ export class ActionDefinition< } resolve( - _context: GeneralActionResolveContext, + context: GeneralActionResolveContext, args: DispatchArgs ): GeneralActionOutcome { - void _context; - return { - effects: [ - createLogEffect(`${ACTION_NAME}을 준비했습니다. (목표 도시 ${args.destCityId})`, { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }), - ], - }; + context.addLog(`${ACTION_NAME}을 준비했습니다. (목표 도시 ${args.destCityId})`, { + category: LogCategory.ACTION, + format: LogFormat.MONTH, + }); + return { effects: [] }; } } diff --git a/packages/logic/src/actions/turn/general/che_화계.ts b/packages/logic/src/actions/turn/general/che_화계.ts index c1271b2..c89b5d9 100644 --- a/packages/logic/src/actions/turn/general/che_화계.ts +++ b/packages/logic/src/actions/turn/general/che_화계.ts @@ -33,7 +33,6 @@ import type { import { createCityPatchEffect, createGeneralPatchEffect, - createLogEffect, } from '../../engine.js'; import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; @@ -332,6 +331,7 @@ export class ActionResolver< _args: FireAttackArgs ): GeneralActionOutcome { void _args; + const general = context.general; const city = context.city; if (!city) { throw new Error('Fire attack requires a city context.'); @@ -351,13 +351,13 @@ export class ActionResolver< const effects: Array> = []; - const nextGold = Math.max(0, context.general.gold - result.costGold); - const nextRice = Math.max(0, context.general.rice - result.costRice); - const nextExperience = context.general.experience + result.exp; - const nextDedication = context.general.dedication + result.dedication; + const nextGold = Math.max(0, general.gold - result.costGold); + const nextRice = Math.max(0, general.rice - result.costRice); + const nextExperience = general.experience + result.exp; + const nextDedication = general.dedication + result.dedication; const metaWithStatExp = addMetaNumber( - context.general.meta, + general.meta, STAT_EXP_KEY, 1 ); @@ -365,26 +365,21 @@ export class ActionResolver< ? addMetaNumber(metaWithStatExp, 'firenum', 1) : metaWithStatExp; - effects.push( - createGeneralPatchEffect({ - gold: nextGold, - rice: nextRice, - experience: nextExperience, - dedication: nextDedication, - meta: metaUpdated, - }) - ); + // 직접 수정 (Immer Draft) + general.gold = nextGold; + general.rice = nextRice; + general.experience = nextExperience; + general.dedication = nextDedication; + general.meta = metaUpdated; if (!result.success) { - effects.push( - createLogEffect( - `${context.destCity.name}에 ${ACTION_NAME} 실패했습니다.`, - { - format: LogFormat.MONTH, - } - ) + context.addLog( + `${context.destCity.name}에 ${ACTION_NAME} 실패했습니다.`, + { + format: LogFormat.MONTH, + } ); - return { effects }; + return { effects: [] }; } const updatedCityMeta: Record = { @@ -392,6 +387,7 @@ export class ActionResolver< state: CITY_STATE_BURNING, }; + // 타겟 도시는 Draft가 아니므로 Effect 반환 effects.push( createCityPatchEffect( { @@ -403,45 +399,38 @@ export class ActionResolver< ) ); - effects.push( - createLogEffect( - `${context.destCity.name}이 불타고 있습니다.`, - { - scope: LogScope.SYSTEM, - category: LogCategory.SUMMARY, - format: LogFormat.MONTH, - } - ) + context.addLog( + `${context.destCity.name}이 불타고 있습니다.`, + { + scope: LogScope.SYSTEM, + category: LogCategory.SUMMARY, + format: LogFormat.MONTH, + } ); - effects.push( - createLogEffect( - `${context.destCity.name}에 ${ACTION_NAME} 성공했습니다.`, - { - format: LogFormat.MONTH, - } - ) + context.addLog( + `${context.destCity.name}에 ${ACTION_NAME} 성공했습니다.`, + { + format: LogFormat.MONTH, + } ); - effects.push( - createLogEffect( - `도시의 농업이 ${result.agriDamage}, 상업이 ${result.commDamage}만큼 감소하고, 장수 ${result.injuryCount}명이 부상 당했습니다.`, - { - format: LogFormat.PLAIN, - } - ) + context.addLog( + `도시의 농업이 ${result.agriDamage}, 상업이 ${result.commDamage}만큼 감소하고, 장수 ${result.injuryCount}명이 부상 당했습니다.`, + { + format: LogFormat.PLAIN, + } ); for (const injured of result.injuredGenerals) { + // 타겟 장수는 Draft가 아니므로 Effect 반환 effects.push( createGeneralPatchEffect(injured.patch, injured.id) ); - effects.push( - createLogEffect( - `${ACTION_KEY}로 인해 부상을 당했습니다.`, - { - generalId: injured.id, - format: LogFormat.MONTH, - } - ) + context.addLog( + `${ACTION_KEY}로 인해 부상을 당했습니다.`, + { + generalId: injured.id, + format: LogFormat.MONTH, + } ); } diff --git a/packages/logic/src/actions/turn/general/che_훈련.ts b/packages/logic/src/actions/turn/general/che_훈련.ts index 460f10e..96aaf9a 100644 --- a/packages/logic/src/actions/turn/general/che_훈련.ts +++ b/packages/logic/src/actions/turn/general/che_훈련.ts @@ -1,5 +1,4 @@ import type { - General, GeneralTriggerState, } from '../../../domain/entities.js'; import type { @@ -13,8 +12,6 @@ import type { GeneralActionOutcome, GeneralActionResolveContext, } from '../../engine.js'; -import { createGeneralPatchEffect, createLogEffect } from '../../engine.js'; -import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; @@ -74,21 +71,14 @@ export class ActionDefinition< const nextTrain = clamp(general.train + delta, 0, maxTrain); const applied = nextTrain - general.train; const costGold = this.env.costGold ?? 0; - const patch: Partial> = { - train: nextTrain, - gold: Math.max(0, general.gold - costGold), - }; - return { - effects: [ - createGeneralPatchEffect(patch, general.id), - createLogEffect(`${ACTION_NAME}을 통해 훈련도가 ${applied} 증가했습니다.`, { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }), - ], - }; + // 직접 수정 (Immer Draft) + general.train = nextTrain; + general.gold = Math.max(0, general.gold - costGold); + + context.addLog(`${ACTION_NAME}을 통해 훈련도가 ${applied} 증가했습니다.`); + + return { effects: [] }; } } diff --git a/packages/logic/src/actions/turn/general/cityDevelopment.ts b/packages/logic/src/actions/turn/general/cityDevelopment.ts index 5c69b8d..fdbfa81 100644 --- a/packages/logic/src/actions/turn/general/cityDevelopment.ts +++ b/packages/logic/src/actions/turn/general/cityDevelopment.ts @@ -1,6 +1,5 @@ import type { City, - General, GeneralTriggerState, } from '../../../domain/entities.js'; import type { @@ -21,12 +20,6 @@ import type { GeneralActionOutcome, GeneralActionResolveContext, } from '../../engine.js'; -import { - createCityPatchEffect, - createGeneralPatchEffect, - createLogEffect, -} from '../../engine.js'; -import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; export interface CityDevelopmentArgs {} @@ -98,53 +91,28 @@ export class CityDevelopmentActionDefinition< const general = context.general; const city = context.city; if (!city) { - return { - effects: [ - createLogEffect('도시 정보를 찾지 못했습니다.', { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }), - ], - }; + context.addLog('도시 정보를 찾지 못했습니다.'); + return { effects: [] }; } const baseAmount = this.env.amount ?? this.config.baseAmount; const current = readNumber(city[this.config.statKey]); const max = readNumber(city[this.config.maxKey]); if (current === null || max === null) { - return { - effects: [ - createLogEffect('도시 정보를 찾지 못했습니다.', { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }), - ], - }; + context.addLog('도시 정보를 찾지 못했습니다.'); + return { effects: [] }; } const nextValue = clamp(current + baseAmount, 0, max); const costGold = this.env.develCost ?? 0; - const generalPatch: Partial> = { - gold: Math.max(0, general.gold - costGold), - }; + + // 직접 수정 (Immer Draft) + (city as any)[this.config.statKey] = nextValue; + general.gold = Math.max(0, general.gold - costGold); const logMessage = `${this.config.label}이 ${nextValue - current} 증가했습니다.`; + context.addLog(logMessage); - return { - effects: [ - createCityPatchEffect( - { [this.config.statKey]: nextValue } as Partial, - city.id - ), - createGeneralPatchEffect(generalPatch, general.id), - createLogEffect(logMessage, { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }), - ], - }; + return { effects: [] }; } } diff --git a/packages/logic/src/actions/turn/general/휴식.ts b/packages/logic/src/actions/turn/general/휴식.ts index 1e7b35d..821f733 100644 --- a/packages/logic/src/actions/turn/general/휴식.ts +++ b/packages/logic/src/actions/turn/general/휴식.ts @@ -10,8 +10,7 @@ import type { GeneralActionOutcome, GeneralActionResolveContext, } from '../../engine.js'; -import { createLogEffect } from '../../engine.js'; -import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js'; +import { LogCategory, LogFormat } from '../../../logging/types.js'; import type { TurnCommandEnv } from '../commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; @@ -23,20 +22,14 @@ export class ActionResolver< TriggerState extends GeneralTriggerState = GeneralTriggerState > { resolve( - _context: GeneralActionResolveContext, + context: GeneralActionResolveContext, _args: RestArgs ): GeneralActionOutcome { - void _context; - void _args; - return { - effects: [ - createLogEffect('아무것도 실행하지 않았습니다.', { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - format: LogFormat.MONTH, - }), - ], - }; + context.addLog('아무것도 실행하지 않았습니다.', { + category: LogCategory.ACTION, + format: LogFormat.MONTH, + }); + return { effects: [] }; } }