fix general status transition parity

This commit is contained in:
2026-07-26 17:12:54 +00:00
parent a0df7f5b3c
commit 80a2c9502f
10 changed files with 238 additions and 17 deletions
+3
View File
@@ -106,6 +106,7 @@ export type GeneralActionEffect<TriggerState extends GeneralTriggerState = Gener
export interface GeneralActionOutcome<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
effects: GeneralActionEffect<TriggerState>[];
completed?: boolean;
deletedTroopIds?: number[];
alternative?: {
commandKey: string;
args: unknown;
@@ -146,6 +147,7 @@ export interface GeneralActionResolution {
commandKey: string;
args: unknown;
};
deletedTroopIds?: number[];
}
export const createGeneralPatchEffect = <TriggerState extends GeneralTriggerState = GeneralTriggerState>(
@@ -388,6 +390,7 @@ export const resolveGeneralAction = <TriggerState extends GeneralTriggerState =
logs,
effects: pendingEffects,
...(outcome?.alternative ? { alternative: outcome.alternative } : {}),
...(outcome?.deletedTroopIds?.length ? { deletedTroopIds: outcome.deletedTroopIds } : {}),
};
if (nextWorld.city) {
resolution.city = nextWorld.city as City;
@@ -7,7 +7,11 @@ import type {
GeneralActionOutcome,
GeneralActionResolveContext,
} from '@sammo-ts/logic/actions/engine.js';
import { createGeneralPatchEffect, createLogEffect } from '@sammo-ts/logic/actions/engine.js';
import {
createGeneralPatchEffect,
createLogEffect,
createNationPatchEffect,
} from '@sammo-ts/logic/actions/engine.js';
import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js';
import { JosaUtil } from '@sammo-ts/common';
import { z } from 'zod';
@@ -49,6 +53,15 @@ export class ActionDefinition<
}
buildConstraints(_ctx: ConstraintContext, _args: AbdicationArgs): Constraint[] {
if (_ctx.actorId === _args.destGeneralID) {
return [
{
name: 'differentDestGeneral',
requires: () => [],
test: () => ({ kind: 'deny', reason: '본인입니다' }),
},
];
}
return [beLord(), existsDestGeneral(), friendlyDestGeneral()];
}
@@ -66,7 +79,8 @@ export class ActionDefinition<
throw new Error('선양 대상 장수가 없습니다.');
}
const penaltyRaw = destGeneral.meta.penalty;
const penaltyRaw =
(destGeneral as General<TriggerState> & { penalty?: unknown }).penalty ?? destGeneral.meta.penalty;
if (penaltyRaw && typeof penaltyRaw === 'object' && !Array.isArray(penaltyRaw)) {
const penaltyMap = penaltyRaw as Record<string, unknown>;
for (const penaltyKey of blockedPenaltyKeys) {
@@ -79,6 +93,7 @@ export class ActionDefinition<
format: LogFormat.MONTH,
}),
],
completed: false,
};
}
}
@@ -135,7 +150,7 @@ export class ActionDefinition<
createGeneralPatchEffect(
{
officerLevel: 1,
experience: Math.floor(general.experience * 0.7),
experience: Math.round(general.experience * 0.7),
meta: {
...general.meta,
officer_city: 0,
@@ -143,7 +158,8 @@ export class ActionDefinition<
},
},
general.id
)
),
createNationPatchEffect({ chiefGeneralId: destGeneral.id }, nation.id)
);
return { effects };
@@ -48,8 +48,10 @@ export class ActionResolver<
const value = typeof nextMeta[key] === 'number' ? nextMeta[key] : 0;
nextMeta[key] = Math.round(value * 0.5);
}
nextMeta.specAge = 0;
nextMeta.specAge2 = 0;
delete nextMeta.specAge;
delete nextMeta.specAge2;
nextMeta.specage = 0;
nextMeta.specage2 = 0;
for (const type of LEGACY_RANK_DATA_TYPES) {
nextMeta[rankDataMetaKey(type)] = 0;
}
@@ -57,7 +59,7 @@ export class ActionResolver<
const josaYi = JosaUtil.pick(general.name, '이');
context.addLog(`<Y>${general.name}</>${josaYi} <R>은퇴</>하고 그 자손이 유지를 이어받았습니다.`, {
scope: LogScope.SYSTEM,
category: LogCategory.ACTION,
category: LogCategory.SUMMARY,
format: LogFormat.RAWTEXT,
});
context.addLog('나이가 들어 <R>은퇴</>하고 자손에게 자리를 물려줍니다.', {
@@ -66,8 +66,8 @@ export class ActionResolver<
// Penalty
const betrayal = typeof general.meta.betray === 'number' ? general.meta.betray : 0;
const penaltyRatio = betrayal * 0.1;
const nextExp = Math.floor(general.experience * (1 - penaltyRatio));
const nextDed = Math.floor(general.dedication * (1 - penaltyRatio));
const nextExp = Math.round(general.experience * (1 - penaltyRatio));
const nextDed = Math.round(general.dedication * (1 - penaltyRatio));
context.addLog(`<D><b>${nation.name}</b></>에서 하야했습니다.`, {
category: LogCategory.ACTION,
@@ -80,7 +80,7 @@ export class ActionResolver<
const josaYi = JosaUtil.pick(general.name, '이');
context.addLog(`<Y>${general.name}</>${josaYi} <D><b>${nation.name}</b></>에서 <R>하야</>했습니다.`, {
scope: LogScope.SYSTEM,
category: LogCategory.ACTION,
category: LogCategory.SUMMARY,
format: LogFormat.RAWTEXT,
});
@@ -125,7 +125,10 @@ export class ActionResolver<
)
);
return { effects };
return {
effects,
...(general.troopId === general.id ? { deletedTroopIds: [general.id] } : {}),
};
}
}
@@ -223,6 +223,7 @@ describe('migrated general commands', () => {
expect(updatedHeir.officerLevel).toBe(12);
expect(updatedLord.officerLevel).toBe(1);
expect(updatedLord.experience).toBe(700);
expect(world.getNation(1)!.chiefGeneralId).toBe(heir.id);
});
it('che_증여: 금은 레거시 최소 보유량 0을 적용해 요청한 금액을 이전한다', async () => {