fix general military preparation parity

This commit is contained in:
2026-07-26 16:02:24 +00:00
parent eed4017c2c
commit cabb479cf6
3 changed files with 303 additions and 25 deletions
@@ -18,12 +18,7 @@ import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js'
import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
import type { GeneralTurnCommandSpec } from './index.js';
import { parseArgsWithSchema } from '../parseArgs.js';
import {
cloneItemInventory,
ensureItemInventory,
equipNewItem,
removeEquippedItem,
} from '@sammo-ts/logic/items/inventory.js';
import { equipNewItem, removeEquippedItem } from '@sammo-ts/logic/items/inventory.js';
const ACTION_NAME = '장비매매';
const ACTION_KEY = 'che_장비매매';
@@ -167,17 +162,12 @@ export class ActionDefinition<
const josaUl = JosaUtil.pick(itemRawName, '을');
const nextGold = buying ? Math.max(0, general.gold - itemCost) : general.gold + Math.floor(itemCost / 2);
const nextGeneral = {
...general,
role: { ...general.role, items: { ...general.role.items } },
itemInventory: cloneItemInventory(ensureItemInventory(general)),
};
if (buying) {
equipNewItem(nextGeneral, itemType, finalItemCode, {
equipNewItem(general, itemType, finalItemCode, {
...(item?.initialCharges === undefined ? {} : { charges: item.initialCharges }),
});
} else {
removeEquippedItem(nextGeneral, itemType);
removeEquippedItem(general, itemType);
}
if (buying) {
@@ -198,7 +188,7 @@ export class ActionDefinition<
const josaYi = JosaUtil.pick(general.name, '이');
context.addLog(`<Y>${general.name}</>${josaYi} <C>${itemName}</>${josaUl} 판매했습니다!`, {
scope: LogScope.SYSTEM,
category: LogCategory.ACTION,
category: LogCategory.SUMMARY,
});
context.addLog(
`<R><b>【판매】</b></><D><b>${nation.name}</b></>의 <Y>${general.name}</>${josaYi} <C>${itemName}</>${josaUl} 판매했습니다!`,
@@ -216,8 +206,6 @@ export class ActionDefinition<
createGeneralPatchEffect<TriggerState>({
gold: nextGold,
experience: general.experience + 10,
role: nextGeneral.role,
itemInventory: nextGeneral.itemInventory,
}),
],
};
@@ -68,14 +68,21 @@ export const ARGS_SCHEMA = z.preprocess(
return { ...record, crewType };
},
z.object({
crewType: z.preprocess(
(value) => (typeof value === 'number' ? Math.floor(value) : value),
z.number().int().positive()
),
amount: z.preprocess(
(value) => (typeof value === 'number' ? Math.floor(value) : value),
z.number().int().min(0)
),
crewType: z.number().int().positive(),
amount: z.preprocess((value) => {
if (typeof value === 'number') {
return Number.isFinite(value) ? Math.trunc(value) : value;
}
if (typeof value === 'string') {
const trimmed = value.trim();
if (!/^[+-]?(?:(?:\d+(?:\.\d*)?)|(?:\.\d+))(?:[eE][+-]?\d+)?$/.test(trimmed)) {
return value;
}
const numeric = Number(trimmed);
return Number.isFinite(numeric) ? Math.trunc(numeric) : value;
}
return value;
}, z.number().int().min(0)),
})
);
export type RecruitArgs = z.infer<typeof ARGS_SCHEMA>;
@@ -107,6 +114,10 @@ const readCityTrust = (city: City, fallback: number): number => {
return typeof trust === 'number' ? trust : fallback;
};
// 레거시 city.trust는 MariaDB FLOAT이며 다음 명령에서 6자리 유효숫자로
// 재조회된다. 메모리 상태도 같은 persistence 경계로 정규화한다.
const toLegacyStoredTrust = (value: number): number => Number(value.toPrecision(6));
const addMetaNumber = (meta: GeneralMeta, key: string, delta: number): GeneralMeta => {
const current = typeof meta[key] === 'number' ? (meta[key] as number) : 0;
return { ...meta, [key]: current + delta };
@@ -365,7 +376,7 @@ export class ActionResolver<
const nextPopulation = Math.max(city.population - recruitPop, 0);
const baseTrust = readCityTrust(city, this.env.defaultTrust ?? DEFAULT_TRUST);
const trustLoss = city.population > 0 ? (recruitPop / city.population / costOffset) * 100 : 0;
const nextTrust = Math.max(baseTrust - trustLoss, 0);
const nextTrust = toLegacyStoredTrust(Math.max(baseTrust - trustLoss, 0));
const actionName = this.env.actionName ?? ACTION_NAME;
const [nextCrewTypeId, nextCrew, nextTrain, nextAtmos] =