refactor: improve war module type safety and reserved turns test data handling
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
|
||||
import type { GeneralActionResolver } from '@sammo-ts/logic/actions/engine.js';
|
||||
import type { ActionContextBuilder } from './actionContext.js';
|
||||
import type { TurnCommandEnv } from './commandEnv.js';
|
||||
|
||||
@@ -13,8 +12,5 @@ export interface TurnCommandSpecBase<TKey extends string = string> {
|
||||
|
||||
export interface TurnCommandModule<TSpec extends TurnCommandSpecBase = TurnCommandSpecBase> {
|
||||
commandSpec: TSpec;
|
||||
ActionDefinition: new (...args: unknown[]) => GeneralActionDefinition;
|
||||
ActionResolver?: new (...args: unknown[]) => GeneralActionResolver;
|
||||
CommandResolver?: new (...args: unknown[]) => unknown;
|
||||
actionContextBuilder?: ActionContextBuilder;
|
||||
}
|
||||
|
||||
@@ -12,54 +12,62 @@ import type {
|
||||
} from './types.js';
|
||||
|
||||
export interface GeneralActionModule<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
||||
getName?(): string;
|
||||
getInfo?(): string;
|
||||
getName?: (() => string) | undefined;
|
||||
getInfo?: (() => string) | undefined;
|
||||
|
||||
getPreTurnExecuteTriggerList?(
|
||||
context: GeneralActionContext<TriggerState>
|
||||
): GeneralTriggerCaller<TriggerState> | null;
|
||||
getPreTurnExecuteTriggerList?:
|
||||
| ((context: GeneralActionContext<TriggerState>) => GeneralTriggerCaller<TriggerState> | null)
|
||||
| undefined;
|
||||
|
||||
onCalcDomestic?(
|
||||
context: GeneralActionContext<TriggerState>,
|
||||
turnType: TriggerDomesticActionType,
|
||||
varType: TriggerDomesticVarType,
|
||||
value: number,
|
||||
aux?: unknown
|
||||
): number;
|
||||
onCalcDomestic?:
|
||||
| ((
|
||||
context: GeneralActionContext<TriggerState>,
|
||||
turnType: TriggerDomesticActionType,
|
||||
varType: TriggerDomesticVarType,
|
||||
value: number,
|
||||
aux?: unknown
|
||||
) => number)
|
||||
| undefined;
|
||||
|
||||
onCalcStat?(
|
||||
context: GeneralActionContext<TriggerState>,
|
||||
statName: GeneralStatName,
|
||||
value: number,
|
||||
aux?: unknown
|
||||
): number;
|
||||
onCalcStat?:
|
||||
| ((
|
||||
context: GeneralActionContext<TriggerState>,
|
||||
statName: GeneralStatName,
|
||||
value: number,
|
||||
aux?: unknown
|
||||
) => number)
|
||||
| undefined;
|
||||
|
||||
onCalcOpposeStat?(
|
||||
context: GeneralActionContext<TriggerState>,
|
||||
statName: GeneralStatName,
|
||||
value: number,
|
||||
aux?: unknown
|
||||
): number;
|
||||
onCalcOpposeStat?:
|
||||
| ((
|
||||
context: GeneralActionContext<TriggerState>,
|
||||
statName: GeneralStatName,
|
||||
value: number,
|
||||
aux?: unknown
|
||||
) => number)
|
||||
| undefined;
|
||||
|
||||
onCalcStrategic?(
|
||||
context: GeneralActionContext<TriggerState>,
|
||||
turnType: TriggerStrategicActionType,
|
||||
varType: TriggerStrategicVarType,
|
||||
value: number
|
||||
): number;
|
||||
onCalcStrategic?:
|
||||
| ((
|
||||
context: GeneralActionContext<TriggerState>,
|
||||
turnType: TriggerStrategicActionType,
|
||||
varType: TriggerStrategicVarType,
|
||||
value: number
|
||||
) => number)
|
||||
| undefined;
|
||||
|
||||
onCalcNationalIncome?(
|
||||
context: GeneralActionContext<TriggerState>,
|
||||
type: TriggerNationalIncomeType,
|
||||
amount: number
|
||||
): number;
|
||||
onCalcNationalIncome?:
|
||||
| ((context: GeneralActionContext<TriggerState>, type: TriggerNationalIncomeType, amount: number) => number)
|
||||
| undefined;
|
||||
|
||||
onArbitraryAction?(
|
||||
context: GeneralActionContext<TriggerState>,
|
||||
actionType: TriggerActionType,
|
||||
phase?: TriggerActionPhase | null,
|
||||
aux?: Record<string, unknown> | null
|
||||
): Record<string, unknown> | null;
|
||||
onArbitraryAction?:
|
||||
| ((
|
||||
context: GeneralActionContext<TriggerState>,
|
||||
actionType: TriggerActionType,
|
||||
phase?: TriggerActionPhase | null,
|
||||
aux?: Record<string, unknown> | null
|
||||
) => Record<string, unknown> | null)
|
||||
| undefined;
|
||||
}
|
||||
|
||||
export class GeneralActionPipeline<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
||||
|
||||
@@ -14,15 +14,16 @@ export const parseWarDexAux = (aux: unknown): WarDexAux => {
|
||||
const opposeRaw = aux.opposeType;
|
||||
|
||||
if (!isRecord(opposeRaw)) {
|
||||
return { isAttacker };
|
||||
return isAttacker === undefined ? {} : { isAttacker };
|
||||
}
|
||||
|
||||
const armType = opposeRaw.armType;
|
||||
if (typeof armType !== 'number') {
|
||||
return { isAttacker };
|
||||
return isAttacker === undefined ? {} : { isAttacker };
|
||||
}
|
||||
|
||||
return { isAttacker, opposeType: { armType } };
|
||||
const opposeType = { armType };
|
||||
return isAttacker === undefined ? { opposeType } : { isAttacker, opposeType };
|
||||
};
|
||||
|
||||
export const getAuxArmType = (aux: unknown): number | undefined => {
|
||||
|
||||
@@ -44,7 +44,7 @@ export const traitModule: TraitModule = {
|
||||
kind: 'war',
|
||||
getName: () => '견고',
|
||||
getInfo: () => '[전투] 상대 필살 확률 -20%p, 상대 계략 시도시 성공 확률 -10%p, 부상 없음, 아군 피해 -10%',
|
||||
onCalcOpposeStat: (_context, statName, value, _aux) => {
|
||||
onCalcOpposeStat: ((_context, statName, value, _aux) => {
|
||||
if (statName === 'warMagicSuccessProb' && typeof value === 'number') {
|
||||
return value - 0.1;
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export const traitModule: TraitModule = {
|
||||
return value - 0.2;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
}) as TraitModule['onCalcOpposeStat'],
|
||||
getBattleInitTriggerList: (_context) => {
|
||||
if (!_context.unit) return null;
|
||||
return new WarTriggerCaller(new che_부상무효(_context.unit));
|
||||
|
||||
@@ -4,6 +4,8 @@ import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import type { TraitModule } from '@sammo-ts/logic/triggers/special/types.js';
|
||||
import { getMetaNumber } from '@sammo-ts/logic/war/utils.js';
|
||||
|
||||
import { WarUnit } from '@sammo-ts/logic/war/units.js';
|
||||
|
||||
type WarUnitWithGeneral = WarUnit & { getGeneral: () => { meta: Record<string, unknown> } };
|
||||
|
||||
const hasGeneral = (unit: WarUnit): unit is WarUnitWithGeneral =>
|
||||
@@ -44,7 +46,11 @@ export const traitModule: TraitModule = {
|
||||
// Note: unit.getGeneral() is only available for WarUnitGeneral.
|
||||
// In a real scenario, we should check if unit is WarUnitGeneral.
|
||||
if (hasGeneral(unit)) {
|
||||
const killnum = getMetaNumber(unit.getGeneral().meta, 'rank_killnum', 0);
|
||||
const killnum = getMetaNumber(
|
||||
unit.getGeneral().meta as Record<string, import('@sammo-ts/logic/domain/entities.js').TriggerValue>,
|
||||
'rank_killnum',
|
||||
0
|
||||
);
|
||||
const logVal = Math.log2(Math.max(1, killnum / 5));
|
||||
attackMultiplier += logVal / 20;
|
||||
defenceMultiplier -= logVal / 50;
|
||||
|
||||
@@ -99,12 +99,12 @@ export const traitModule: TraitModule = {
|
||||
getName: () => '반계',
|
||||
getInfo: () =>
|
||||
'[전투] 상대의 계략 성공 확률 -10%p, 상대의 계략을 40% 확률로 되돌림, 반목 성공시 대미지 추가(+60% → +150%)',
|
||||
onCalcOpposeStat: (_context, statName, value, _aux) => {
|
||||
onCalcOpposeStat: ((_context, statName, value, _aux) => {
|
||||
if (statName === 'warMagicSuccessProb' && typeof value === 'number') {
|
||||
return value - 0.1;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
}) as TraitModule['onCalcOpposeStat'],
|
||||
getBattlePhaseTriggerList: (_context) => {
|
||||
if (!_context.unit) return null;
|
||||
return new WarTriggerCaller(new che_반계시도(_context.unit), new che_반계발동(_context.unit));
|
||||
|
||||
@@ -16,32 +16,38 @@ export interface WarActionContext<TriggerState extends GeneralTriggerState = Gen
|
||||
}
|
||||
|
||||
export interface WarActionModule<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
||||
getName?(): string;
|
||||
getInfo?(): string;
|
||||
getName?: (() => string) | undefined;
|
||||
getInfo?: (() => string) | undefined;
|
||||
|
||||
getBattleInitTriggerList?(context: WarActionContext<TriggerState>): WarTriggerCaller | null;
|
||||
getBattleInitTriggerList?: ((context: WarActionContext<TriggerState>) => WarTriggerCaller | null) | undefined;
|
||||
|
||||
getBattlePhaseTriggerList?(context: WarActionContext<TriggerState>): WarTriggerCaller | null;
|
||||
getBattlePhaseTriggerList?: ((context: WarActionContext<TriggerState>) => WarTriggerCaller | null) | undefined;
|
||||
|
||||
onCalcStat?(
|
||||
context: WarActionContext<TriggerState>,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
onCalcStat?:
|
||||
| ((
|
||||
context: WarActionContext<TriggerState>,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
) => number | [number, number])
|
||||
| undefined;
|
||||
|
||||
onCalcOpposeStat?(
|
||||
context: WarActionContext<TriggerState>,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
): number | [number, number];
|
||||
onCalcOpposeStat?:
|
||||
| ((
|
||||
context: WarActionContext<TriggerState>,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number],
|
||||
aux?: unknown
|
||||
) => number | [number, number])
|
||||
| undefined;
|
||||
|
||||
getWarPowerMultiplier?(
|
||||
context: WarActionContext<TriggerState>,
|
||||
unit: WarUnit<TriggerState>,
|
||||
oppose: WarUnit<TriggerState>
|
||||
): [number, number];
|
||||
getWarPowerMultiplier?:
|
||||
| ((
|
||||
context: WarActionContext<TriggerState>,
|
||||
unit: WarUnit<TriggerState>,
|
||||
oppose: WarUnit<TriggerState>
|
||||
) => [number, number])
|
||||
| undefined;
|
||||
}
|
||||
|
||||
export class WarActionPipeline<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
||||
|
||||
@@ -110,7 +110,10 @@ const isSupplyCity = (city: City): boolean => {
|
||||
return city.supplyState > 0;
|
||||
};
|
||||
|
||||
export const computeBattleOrder = (defender: WarUnit, attacker: WarUnitGeneral): number => {
|
||||
export const computeBattleOrder = <TriggerState extends GeneralTriggerState>(
|
||||
defender: WarUnit<TriggerState>,
|
||||
attacker: WarUnitGeneral<TriggerState>
|
||||
): number => {
|
||||
if (defender instanceof WarUnitCity) {
|
||||
const context = attacker.getActionContext();
|
||||
return attacker.getActionPipeline().onCalcOpposeStat(context, 'cityBattleOrder', -1);
|
||||
@@ -257,11 +260,14 @@ export const resolveWarBattle = <TriggerState extends GeneralTriggerState = Gene
|
||||
defenderGenerals.push(unit);
|
||||
}
|
||||
|
||||
if (defenderGenerals.length > 0 && computeBattleOrder(cityUnit, attackerUnit) > 0) {
|
||||
if (defenderGenerals.length > 0 && computeBattleOrder<TriggerState>(cityUnit, attackerUnit) > 0) {
|
||||
defenderUnits.push(cityUnit);
|
||||
}
|
||||
|
||||
defenderUnits.sort((lhs, rhs) => computeBattleOrder(rhs, attackerUnit) - computeBattleOrder(lhs, attackerUnit));
|
||||
defenderUnits.sort(
|
||||
(lhs, rhs) =>
|
||||
computeBattleOrder<TriggerState>(rhs, attackerUnit) - computeBattleOrder<TriggerState>(lhs, attackerUnit)
|
||||
);
|
||||
|
||||
const iter = defenderUnits.values();
|
||||
let defender: WarUnit<TriggerState> | null = null;
|
||||
|
||||
Reference in New Issue
Block a user