feat: apply traits in live turn runtime

This commit is contained in:
2026-07-25 08:14:25 +00:00
parent 790f4915ab
commit 88afcb5465
24 changed files with 475 additions and 81 deletions
+2 -29
View File
@@ -15,6 +15,7 @@ import {
loadItemModules,
createInheritBuffModules,
createTraitModuleRegistry,
createOfficerLevelActionModules,
DOMESTIC_TRAIT_KEYS,
loadDomesticTraitModules,
loadNationTraitModules,
@@ -32,7 +33,6 @@ import {
type UnitSetDefinition,
type WarBattleOutcome,
type WarActionModule,
type WarActionContext,
type WarUnitReport,
type WarBattleTraceEvent,
type CrewTypeDefinition,
@@ -56,34 +56,7 @@ const traitRegistry = createTraitModuleRegistry({
});
const traitWarModules: WarActionModule[] = [
new TraitWarActionRouter('nation', traitRegistry),
{
onCalcStat: (context: WarActionContext, statName, value) => {
if (statName !== 'leadership' || typeof value !== 'number') {
return value;
}
let officerLevel = context.general.officerLevel;
// Legacy simulator payload omits general.city, so low city offices are
// always downgraded by TriggerOfficerLevel's officer_city comparison.
if (officerLevel >= 2 && officerLevel <= 4) {
officerLevel = 1;
}
const nationLevel = context.nation?.level ?? 0;
const leadershipBonus = officerLevel === 12 ? nationLevel * 2 : officerLevel >= 5 ? nationLevel : 0;
return value + leadershipBonus;
},
getWarPowerMultiplier: (context: WarActionContext) => {
let officerLevel = context.general.officerLevel;
if (officerLevel >= 2 && officerLevel <= 4) {
officerLevel = 1;
}
if (officerLevel === 12) return [1.07, 0.93];
if (officerLevel === 11) return [1.05, 0.95];
if ([10, 8, 6].includes(officerLevel)) return [1.1, 1];
if ([9, 7, 5].includes(officerLevel)) return [1, 0.9];
if ([4, 3, 2].includes(officerLevel)) return [1.05, 0.95];
return [1, 1];
},
},
createOfficerLevelActionModules().war,
new TraitWarActionRouter('domestic', traitRegistry),
new TraitWarActionRouter('war', traitRegistry),
new TraitWarActionRouter('personality', traitRegistry),
@@ -2,7 +2,7 @@ import { TRPCError } from '@trpc/server';
import { authedProcedure } from '../../../trpc.js';
import { getMyGeneral } from '../../shared/general.js';
import { assertNationAccess, mapGeneralList, resolveChiefStatMin } from '../shared.js';
import { assertNationAccess, loadTraitNames, mapGeneralList, resolveChiefStatMin } from '../shared.js';
export const getGeneralList = authedProcedure.query(async ({ ctx }) => {
const general = await getMyGeneral(ctx);
@@ -62,6 +62,7 @@ export const getGeneralList = authedProcedure.query(async ({ ctx }) => {
const cityNameMap = new Map(cityRows.map((city) => [city.id, city.name]));
const troopNameMap = new Map(troopRows.map((troop) => [troop.troopLeaderId, troop.name]));
const list = await mapGeneralList(generalRows, cityNameMap, troopNameMap);
const nationTrait = (await loadTraitNames([nation.typeCode], 'nation')).get(nation.typeCode);
return {
nation: {
@@ -70,6 +71,11 @@ export const getGeneralList = authedProcedure.query(async ({ ctx }) => {
color: nation.color,
level: nation.level,
typeCode: nation.typeCode,
type: {
key: nation.typeCode,
name: nationTrait?.name ?? nation.typeCode,
info: nationTrait?.info ?? '',
},
capitalCityId: nation.capitalCityId ?? 0,
},
chiefStatMin: resolveChiefStatMin(worldState),
+3
View File
@@ -498,18 +498,21 @@ export const mapGeneralList = async (
? {
key: personalityKey,
name: personalityMap.get(personalityKey)?.name ?? personalityKey,
info: personalityMap.get(personalityKey)?.info ?? '',
}
: null,
specialDomestic: domesticKey
? {
key: domesticKey,
name: domesticMap.get(domesticKey)?.name ?? domesticKey,
info: domesticMap.get(domesticKey)?.info ?? '',
}
: null,
specialWar: warKey
? {
key: warKey,
name: warMap.get(warKey)?.name ?? warKey,
info: warMap.get(warKey)?.info ?? '',
}
: null,
belong,