From 6bddbc3aa0502e92aa4fa90c7c14ffd05f7bd4d5 Mon Sep 17 00:00:00 2001 From: Hide_D Date: Mon, 5 Jan 2026 18:43:09 +0000 Subject: [PATCH] =?UTF-8?q?wip:=20=EB=B0=94=EB=B3=B4=20=EC=A0=AC3=ED=94=8C?= =?UTF-8?q?.=20=20type=20=EC=9E=AC=EC=A0=95=EC=9D=98=20=EC=A4=80=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/logic/src/items/base.ts | 24 ++--- packages/logic/src/triggers/general-action.ts | 102 +++++++++--------- .../logic/src/triggers/special/registry.ts | 70 +++++++----- packages/logic/src/triggers/special/types.ts | 41 ++++--- packages/logic/src/triggers/types.ts | 85 ++++++++++++--- packages/logic/src/war/actions.ts | 74 ++++++------- packages/logic/src/war/units.ts | 25 +++-- 7 files changed, 254 insertions(+), 167 deletions(-) diff --git a/packages/logic/src/items/base.ts b/packages/logic/src/items/base.ts index 2e97847..6837c67 100644 --- a/packages/logic/src/items/base.ts +++ b/packages/logic/src/items/base.ts @@ -34,31 +34,19 @@ export const createStatItemModule = (options: StatItemOptions): ItemModule => { const name = `${options.rawName}(+${options.statValue})`; const baseInfo = `${statLabel} +${options.statValue}`; const info = options.extraInfo ? `${baseInfo}
${options.extraInfo}` : baseInfo; - function onCalcStat( - _context: GeneralActionContext, - statName: GeneralStatName, - value: number, - _aux?: unknown - ): number; - function onCalcStat( - _context: WarActionContext, - statName: WarStatName, - value: number | [number, number], - _aux?: unknown - ): number | [number, number]; - function onCalcStat( + const onCalcStat = ( _context: GeneralActionContext | WarActionContext, statName: GeneralStatName | WarStatName, value: number | [number, number] - ): number | [number, number] { + ): number | [number, number] => { if (statName !== options.statName) { return value; } - if (Array.isArray(value)) { - return value; + if (typeof value === 'number') { + return value + options.statValue; } - return value + options.statValue; - } + return value; + }; return { key: options.key, diff --git a/packages/logic/src/triggers/general-action.ts b/packages/logic/src/triggers/general-action.ts index a77bb00..6fed42e 100644 --- a/packages/logic/src/triggers/general-action.ts +++ b/packages/logic/src/triggers/general-action.ts @@ -1,7 +1,9 @@ import type { GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js'; import { type GeneralActionContext, GeneralTriggerCaller } from './general.js'; import type { + GeneralStatBundleMap, GeneralStatName, + WarStatBundleMap, TriggerActionPhase, TriggerActionType, TriggerDomesticActionType, @@ -16,58 +18,58 @@ export interface GeneralActionModule string) | undefined; getPreTurnExecuteTriggerList?: - | ((context: GeneralActionContext) => GeneralTriggerCaller | null) - | undefined; + | ((context: GeneralActionContext) => GeneralTriggerCaller | null) + | undefined; onCalcDomestic?: - | (( - context: GeneralActionContext, - turnType: TriggerDomesticActionType, - varType: TriggerDomesticVarType, - value: number, - aux?: unknown - ) => number) - | undefined; + | (( + context: GeneralActionContext, + turnType: TriggerDomesticActionType, + varType: TriggerDomesticVarType, + value: number, + aux?: unknown + ) => number) + | undefined; onCalcStat?: - | (( - context: GeneralActionContext, - statName: GeneralStatName, - value: number, - aux?: unknown - ) => number) - | undefined; + | (( + context: GeneralActionContext, + statName: T, + value: GeneralStatBundleMap[T]['value'], + aux?: GeneralStatBundleMap[T]['aux'] + ) => GeneralStatBundleMap[T]['return']) + | undefined; onCalcOpposeStat?: - | (( - context: GeneralActionContext, - statName: GeneralStatName, - value: number, - aux?: unknown - ) => number) - | undefined; + | (( + context: GeneralActionContext, + statName: T, + value: GeneralStatBundleMap[T]['value'], + aux?: GeneralStatBundleMap[T]['aux'] + ) => GeneralStatBundleMap[T]['return']) + | undefined; onCalcStrategic?: - | (( - context: GeneralActionContext, - turnType: TriggerStrategicActionType, - varType: TriggerStrategicVarType, - value: number - ) => number) - | undefined; + | (( + context: GeneralActionContext, + turnType: TriggerStrategicActionType, + varType: TriggerStrategicVarType, + value: number + ) => number) + | undefined; onCalcNationalIncome?: - | ((context: GeneralActionContext, type: TriggerNationalIncomeType, amount: number) => number) - | undefined; + | ((context: GeneralActionContext, type: TriggerNationalIncomeType, amount: number) => number) + | undefined; onArbitraryAction?: - | (( - context: GeneralActionContext, - actionType: TriggerActionType, - phase?: TriggerActionPhase | null, - aux?: Record | null - ) => Record | null) - | undefined; + | (( + context: GeneralActionContext, + actionType: TriggerActionType, + phase?: TriggerActionPhase | null, + aux?: Record | null + ) => Record | null) + | undefined; } export class GeneralActionPipeline { @@ -107,12 +109,12 @@ export class GeneralActionPipeline( context: GeneralActionContext, - statName: GeneralStatName, - value: number, - aux?: unknown - ): number { + statName: T, + value: GeneralStatBundleMap[T]['value'], + aux?: GeneralStatBundleMap[T]['aux'] + ): GeneralStatBundleMap[T]['return'] { let current = value; for (const module of this.modules) { if (!module.onCalcStat) { @@ -123,12 +125,12 @@ export class GeneralActionPipeline( context: GeneralActionContext, - statName: GeneralStatName, - value: number, - aux?: unknown - ): number { + statName: T, + value: GeneralStatBundleMap[T]['value'], + aux?: GeneralStatBundleMap[T]['aux'] + ): GeneralStatBundleMap[T]['return'] { let current = value; for (const module of this.modules) { if (!module.onCalcOpposeStat) { diff --git a/packages/logic/src/triggers/special/registry.ts b/packages/logic/src/triggers/special/registry.ts index 44a84d4..1036242 100644 --- a/packages/logic/src/triggers/special/registry.ts +++ b/packages/logic/src/triggers/special/registry.ts @@ -2,6 +2,7 @@ import type { GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js'; import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js'; import type { GeneralActionModule } from '@sammo-ts/logic/triggers/general-action.js'; import type { + GeneralStatBundleMap, GeneralStatName, TriggerActionPhase, TriggerActionType, @@ -10,6 +11,7 @@ import type { TriggerNationalIncomeType, TriggerStrategicActionType, TriggerStrategicVarType, + WarStatBundleMap, WarStatName, } from '@sammo-ts/logic/triggers/types.js'; import type { WarActionContext, WarActionModule } from '@sammo-ts/logic/war/actions.js'; @@ -64,7 +66,7 @@ export class TraitGeneralActionRouter< constructor( private readonly kind: TraitKind, private readonly registry: TraitModuleRegistry - ) {} + ) { } private getModule(context: GeneralActionContext): TraitModule | null { const key = resolveTraitKey(context, this.kind); @@ -87,24 +89,32 @@ export class TraitGeneralActionRouter< return module?.onCalcDomestic?.(context, turnType, varType, value, aux) ?? value; } - onCalcStat( + onCalcStat( context: GeneralActionContext, - statName: GeneralStatName, - value: number, - aux?: unknown - ): number { + statName: T, + value: GeneralStatBundleMap[T]['value'], + aux?: GeneralStatBundleMap[T]['aux'] + ): GeneralStatBundleMap[T]['return'] { const module = this.getModule(context); - return module?.onCalcStat?.(context, statName, value, aux) ?? value; + const onCalcStat = module?.onCalcStat; + if (onCalcStat) { + return onCalcStat(context, statName, value as never, aux as never) as never; + } + return value; } - onCalcOpposeStat( + onCalcOpposeStat( context: GeneralActionContext, - statName: GeneralStatName, - value: number, - aux?: unknown - ): number { + statName: T, + value: GeneralStatBundleMap[T]['value'], + aux?: GeneralStatBundleMap[T]['aux'] + ): GeneralStatBundleMap[T]['return'] { const module = this.getModule(context); - return module?.onCalcOpposeStat?.(context, statName, value, aux) ?? value; + const onCalcOpposeStat = module?.onCalcOpposeStat; + if (onCalcOpposeStat) { + return onCalcOpposeStat(context, statName, value as never, aux as never) as never; + } + return value; } onCalcStrategic( @@ -145,7 +155,7 @@ export class TraitWarActionRouter< constructor( private readonly kind: TraitKind, private readonly registry: TraitModuleRegistry - ) {} + ) { } private getModule(context: WarActionContext): TraitModule | null { const key = resolveTraitKey(context, this.kind); @@ -162,24 +172,32 @@ export class TraitWarActionRouter< return module?.getBattlePhaseTriggerList?.(context) ?? null; } - onCalcStat( + onCalcStat( context: WarActionContext, - statName: WarStatName, - value: number | [number, number], - aux?: unknown - ): number | [number, number] { + statName: T, + value: WarStatBundleMap[T]['value'], + aux?: WarStatBundleMap[T]['aux'] + ): WarStatBundleMap[T]['return'] { const module = this.getModule(context); - return module?.onCalcStat?.(context, statName, value, aux) ?? value; + const onCalcStat = module?.onCalcStat; + if (onCalcStat) { + return onCalcStat(context, statName, value as never, aux as never) as never; + } + return value; } - onCalcOpposeStat( + onCalcOpposeStat( context: WarActionContext, - statName: WarStatName, - value: number | [number, number], - aux?: unknown - ): number | [number, number] { + statName: T, + value: WarStatBundleMap[T]['value'], + aux?: WarStatBundleMap[T]['aux'] + ): WarStatBundleMap[T]['return'] { const module = this.getModule(context); - return module?.onCalcOpposeStat?.(context, statName, value, aux) ?? value; + const onCalcOpposeStat = module?.onCalcOpposeStat; + if (onCalcOpposeStat) { + return onCalcOpposeStat(context, statName, value as never, aux as never) as never; + } + return value; } getWarPowerMultiplier( diff --git a/packages/logic/src/triggers/special/types.ts b/packages/logic/src/triggers/special/types.ts index 2cddcce..f7f7a36 100644 --- a/packages/logic/src/triggers/special/types.ts +++ b/packages/logic/src/triggers/special/types.ts @@ -13,24 +13,41 @@ export interface TraitSpec { kind: TraitKind; } +import type { + GeneralStatName, + GeneralStatBundleMap, + WarStatName, + WarStatBundleMap, +} from '../types.js'; + export interface TraitOnCalcStat { - (context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number; - ( + ( + context: GeneralActionContext, + statName: T, + value: GeneralStatBundleMap[T]['value'], + aux?: GeneralStatBundleMap[T]['aux'] + ): GeneralStatBundleMap[T]['return']; + ( context: WarActionContext, - statName: WarStatName, - value: number | [number, number], - aux?: unknown - ): number | [number, number]; + statName: T, + value: WarStatBundleMap[T]['value'], + aux?: WarStatBundleMap[T]['aux'] + ): WarStatBundleMap[T]['return']; } export interface TraitOnCalcOpposeStat { - (context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number; - ( + ( + context: GeneralActionContext, + statName: T, + value: GeneralStatBundleMap[T]['value'], + aux?: GeneralStatBundleMap[T]['aux'] + ): GeneralStatBundleMap[T]['return']; + ( context: WarActionContext, - statName: WarStatName, - value: number | [number, number], - aux?: unknown - ): number | [number, number]; + statName: T, + value: WarStatBundleMap[T]['value'], + aux?: WarStatBundleMap[T]['aux'] + ): WarStatBundleMap[T]['return']; } export type TraitModule = TraitSpec & diff --git a/packages/logic/src/triggers/types.ts b/packages/logic/src/triggers/types.ts index c355eaf..088d365 100644 --- a/packages/logic/src/triggers/types.ts +++ b/packages/logic/src/triggers/types.ts @@ -1,3 +1,6 @@ +import type { GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js'; +import type { GeneralActionContext } from './general.js'; +import type { WarActionContext } from '../war/actions.js'; export type TriggerActionType = '장비매매'; export type TriggerActionPhase = '판매' | '구매'; @@ -28,17 +31,71 @@ export type TriggerNationalIncomeType = 'gold' | 'rice'; export type GeneralStatName = 'leadership' | 'strength' | 'intelligence' | 'experience' | 'dedication'; -export type WarStatName = - | GeneralStatName - | 'cityBattleOrder' - | 'initWarPhase' - | 'bonusTrain' - | 'bonusAtmos' - | 'warCriticalRatio' - | 'warAvoidRatio' - | 'killRice' - | 'criticalDamageRange' - | 'warMagicSuccessDamage' - | 'warMagicTrialProb' - | 'warMagicSuccessProb' - | `dex${number}`; +export type GeneralStatBundle = + | { statName: 'leadership'; value: number; aux: undefined; return: number } + | { statName: 'strength'; value: number; aux: undefined; return: number } + | { statName: 'intelligence'; value: number; aux: undefined; return: number } + | { statName: 'experience'; value: number; aux: undefined; return: number } + | { statName: 'dedication'; value: number; aux: undefined; return: number }; + +export type GeneralStatBundleMap = { + [T in GeneralStatBundle as T['statName']]: T; +}; + +export type WarStatBundle = + | GeneralStatBundle + | { statName: 'cityBattleOrder'; value: number; aux: undefined; return: number } + | { statName: 'initWarPhase'; value: number; aux: { isAttacker: boolean }; return: number } + | { statName: 'bonusTrain'; value: number; aux: { isAttacker: boolean }; return: number } + | { statName: 'bonusAtmos'; value: number; aux: { isAttacker: boolean }; return: number } + | { statName: 'warCriticalRatio'; value: number; aux: { isAttacker: boolean }; return: number } + | { statName: 'warAvoidRatio'; value: number; aux: { isAttacker: boolean }; return: number } + | { statName: 'killRice'; value: number; aux: undefined; return: number } + | { statName: 'criticalDamageRange'; value: [number, number]; aux: undefined; return: [number, number] } + | { statName: 'warMagicSuccessDamage'; value: number; aux: undefined; return: number } + | { statName: 'warMagicTrialProb'; value: number; aux: undefined; return: number } + | { statName: 'warMagicSuccessProb'; value: number; aux: undefined; return: number } + | { + statName: `dex${number}`; + value: number; + aux: { isAttacker: boolean; opposeType: { armType: number } | null }; + return: number; + }; + +export type WarStatBundleMap = { + [T in WarStatBundle as T['statName']]: T; +}; + +export type WarStatName = WarStatBundle['statName']; + +export interface TraitOnCalcStat { + ( + context: GeneralActionContext, + statName: T, + value: GeneralStatBundleMap[T]['value'], + aux?: GeneralStatBundleMap[T]['aux'] + ): GeneralStatBundleMap[T]['return']; + ( + context: WarActionContext, + statName: T, + value: WarStatBundleMap[T]['value'], + aux?: WarStatBundleMap[T]['aux'] + ): WarStatBundleMap[T]['return']; +} + +export interface TraitOnCalcOpposeStat { + ( + context: GeneralActionContext, + statName: T, + value: GeneralStatBundleMap[T]['value'], + aux?: GeneralStatBundleMap[T]['aux'] + ): GeneralStatBundleMap[T]['return']; + ( + context: WarActionContext, + statName: T, + value: WarStatBundleMap[T]['value'], + aux?: WarStatBundleMap[T]['aux'] + ): WarStatBundleMap[T]['return']; +} + +export type TriggerActionType = '장비매매'; diff --git a/packages/logic/src/war/actions.ts b/packages/logic/src/war/actions.ts index 996537a..33a0ffd 100644 --- a/packages/logic/src/war/actions.ts +++ b/packages/logic/src/war/actions.ts @@ -2,7 +2,7 @@ import type { RandUtil } from '@sammo-ts/common'; import type { City, General, GeneralTriggerState, Nation } from '@sammo-ts/logic/domain/entities.js'; import type { ActionLogger } from '@sammo-ts/logic/logging/actionLogger.js'; -import type { WarStatName } from '@sammo-ts/logic/triggers/types.js'; +import type { WarStatBundleMap, WarStatName } from '@sammo-ts/logic/triggers/types.js'; import type { WarUnit } from './units.js'; import { WarTriggerCaller } from './triggers.js'; @@ -24,30 +24,30 @@ export interface WarActionModule) => WarTriggerCaller | null) | undefined; onCalcStat?: - | (( - context: WarActionContext, - statName: WarStatName, - value: number | [number, number], - aux?: unknown - ) => number | [number, number]) - | undefined; + | (( + context: WarActionContext, + statName: T, + value: WarStatBundleMap[T]['value'], + aux?: WarStatBundleMap[T]['aux'] + ) => WarStatBundleMap[T]['return']) + | undefined; onCalcOpposeStat?: - | (( - context: WarActionContext, - statName: WarStatName, - value: number | [number, number], - aux?: unknown - ) => number | [number, number]) - | undefined; + | (( + context: WarActionContext, + statName: T, + value: WarStatBundleMap[T]['value'], + aux?: WarStatBundleMap[T]['aux'] + ) => WarStatBundleMap[T]['return']) + | undefined; getWarPowerMultiplier?: - | (( - context: WarActionContext, - unit: WarUnit, - oppose: WarUnit - ) => [number, number]) - | undefined; + | (( + context: WarActionContext, + unit: WarUnit, + oppose: WarUnit + ) => [number, number]) + | undefined; } export class WarActionPipeline { @@ -80,36 +80,36 @@ export class WarActionPipeline( + onCalcStat( context: WarActionContext, - statName: WarStatName, - value: T, - aux?: unknown - ): T { - let current: number | [number, number] = value; + statName: T, + value: WarStatBundleMap[T]['value'], + aux?: WarStatBundleMap[T]['aux'] + ): WarStatBundleMap[T]['return'] { + let current = value; for (const module of this.modules) { if (!module.onCalcStat) { continue; } - current = module.onCalcStat(context, statName, current, aux); + current = module.onCalcStat(context, statName, current as never, aux as never) as never; } - return current as T; + return current; } - onCalcOpposeStat( + onCalcOpposeStat( context: WarActionContext, - statName: WarStatName, - value: T, - aux?: unknown - ): T { - let current: number | [number, number] = value; + statName: T, + value: WarStatBundleMap[T]['value'], + aux?: WarStatBundleMap[T]['aux'] + ): WarStatBundleMap[T]['return'] { + let current = value; for (const module of this.modules) { if (!module.onCalcOpposeStat) { continue; } - current = module.onCalcOpposeStat(context, statName, current, aux); + current = module.onCalcOpposeStat(context, statName, current as never, aux as never) as never; } - return current as T; + return current; } getWarPowerMultiplier( diff --git a/packages/logic/src/war/units.ts b/packages/logic/src/war/units.ts index 6e4e128..4b81451 100644 --- a/packages/logic/src/war/units.ts +++ b/packages/logic/src/war/units.ts @@ -466,8 +466,8 @@ export class WarUnitGeneral< const baseTurnTime = this.isAttacker() ? getMetaString(this.general.meta, META_TURN_TIME) : oppose instanceof WarUnitGeneral - ? getMetaString(oppose.general.meta, META_TURN_TIME) - : getMetaString(this.general.meta, META_TURN_TIME); + ? getMetaString(oppose.general.meta, META_TURN_TIME) + : getMetaString(this.general.meta, META_TURN_TIME); if (!baseTurnTime) { return; } @@ -480,9 +480,8 @@ export class WarUnitGeneral< public override getMaxPhase(): number { const base = this.getCrewType().speed; - const aux = { isAttacker: this.isAttacker() }; const context = this.getActionContext(); - const phase = this.actionPipeline.onCalcStat(context, 'initWarPhase', base, aux); + const phase = this.actionPipeline.onCalcStat(context, 'initWarPhase', base, { isAttacker: this.isAttacker() }); return phase + this.bonusPhase; } @@ -507,12 +506,16 @@ export class WarUnitGeneral< return strength; } - private resolveOpposeStatValue(statName: WarStatName, value: number, aux?: Record): number { + private resolveOpposeStatValue(statName: WarStatName, value: number, aux?: any): number { const oppose = this.getOppose(); if (!(oppose instanceof WarUnitGeneral)) { return value; } - return oppose.getActionPipeline().onCalcOpposeStat(this.getActionContext(), statName, value, aux); + const result = oppose.getActionPipeline().onCalcOpposeStat(this.getActionContext(), statName, value, aux); + if (typeof result === 'number') { + return result; + } + return value; } public override getDex(crewType: WarCrewType): number { @@ -527,8 +530,10 @@ export class WarUnitGeneral< }; const statName = toDexStatName(armType); let dex = this.actionPipeline.onCalcStat(this.getActionContext(), statName, base, aux); - dex = this.resolveOpposeStatValue(statName, dex, aux); - return dex; + if (typeof dex === 'number') { + dex = this.resolveOpposeStatValue(statName, dex, aux); + } + return typeof dex === 'number' ? dex : base; } public override getComputedAttack(): number { @@ -578,8 +583,8 @@ export class WarUnitGeneral< const mainStat = this.resolveMainStat(armType); const coef = armType === this.config.armTypes.wizard || - armType === this.config.armTypes.siege || - armType === this.config.armTypes.misc + armType === this.config.armTypes.siege || + armType === this.config.armTypes.misc ? 0.4 : 0.5;