feat: NPC 세금 처리 로직 및 관련 핸들러 추가, 대형 테스트 맵 수정

This commit is contained in:
2026-01-24 10:06:55 +00:00
parent cb04c930b8
commit 753abdaf47
7 changed files with 185 additions and 40 deletions
+4 -2
View File
@@ -387,9 +387,11 @@ export class GeneralAI {
}
if (this.general.npcState >= 2 && this.general.officerLevel === 12 && !this.nation?.capitalCityId) {
const worldMeta = asRecord(this.world.meta);
const initYear = readNumber(worldMeta.initYear, this.scenarioMeta?.startYear ?? this.startYear);
const initMonth = readNumber(worldMeta.initMonth, 1);
const relYearMonth =
joinYearMonth(this.world.currentYear, this.world.currentMonth) -
joinYearMonth(this.scenarioMeta?.startYear ?? this.startYear, 1);
joinYearMonth(this.world.currentYear, this.world.currentMonth) - joinYearMonth(initYear, initMonth);
if (relYearMonth > 1) {
const establish = generalActionHandlers['건국']?.(this);
if (establish) {
@@ -298,7 +298,7 @@ export const do징병 = (ai: GeneralAI) => {
if (!city || !nation || !ai.unitSet || !ai.map) {
return null;
}
if ([0, 1].includes(ai.dipState)) {
if ([0, 1].includes(ai.dipState) && ai.general.npcState < 2) {
return null;
}
if (!(ai.genType & t통솔장)) {
+99
View File
@@ -0,0 +1,99 @@
import type { TurnCalendarContext, TurnCalendarHandler, InMemoryTurnWorld } from './inMemoryWorld.js';
import type { City, Nation } from '@sammo-ts/logic';
import { joinYearMonth, readNumber } from './ai/aiUtils.js';
const calcNationDevelopedRate = (cities: City[]): { pop: number; all: number } => {
if (cities.length === 0) {
return { pop: 0, all: 0 };
}
let pop = 0;
let agri = 0;
let comm = 0;
let secu = 0;
let def = 0;
let wall = 0;
for (const city of cities) {
pop += city.populationMax > 0 ? city.population / city.populationMax : 0;
agri += city.agricultureMax > 0 ? city.agriculture / city.agricultureMax : 0;
comm += city.commerceMax > 0 ? city.commerce / city.commerceMax : 0;
secu += city.securityMax > 0 ? city.security / city.securityMax : 0;
def += city.defenceMax > 0 ? city.defence / city.defenceMax : 0;
wall += city.wallMax > 0 ? city.wall / city.wallMax : 0;
}
const count = cities.length;
pop /= count;
agri /= count;
comm /= count;
secu /= count;
def /= count;
wall /= count;
const all = (pop + agri + comm + secu + def + wall) / 6;
return { pop, all };
};
const resolveNpcTaxRate = (cities: City[]): number => {
if (cities.length === 0) {
return 15;
}
const devRate = calcNationDevelopedRate(cities);
const avg = (devRate.pop + devRate.all) / 2;
if (avg > 0.95) {
return 25;
}
if (avg > 0.7) {
return 20;
}
if (avg > 0.5) {
return 15;
}
return 10;
};
const shouldUpdateRate = (month: number): boolean => month === 6 || month === 12;
const isNpcMonarchNation = (nation: Nation, world: InMemoryTurnWorld): boolean => {
if (!nation.capitalCityId || !nation.chiefGeneralId) {
return false;
}
const chief = world.getGeneralById(nation.chiefGeneralId);
return Boolean(chief && chief.npcState >= 2);
};
export const createNpcTaxHandler = (options: { getWorld: () => InMemoryTurnWorld | null }): TurnCalendarHandler => {
return {
onMonthChanged: (context: TurnCalendarContext) => {
if (!shouldUpdateRate(context.currentMonth)) {
return;
}
const world = options.getWorld();
if (!world) {
return;
}
const worldState = world.getState();
const meta = worldState.meta ?? {};
const initYear = readNumber(meta.initYear, NaN);
const initMonth = readNumber(meta.initMonth, NaN);
const hasInit = Number.isFinite(initYear) && Number.isFinite(initMonth);
const monthsSinceStart = hasInit
? joinYearMonth(context.currentYear, context.currentMonth) - joinYearMonth(initYear, initMonth)
: null;
const cities = world.listCities();
for (const nation of world.listNations()) {
if (!isNpcMonarchNation(nation, world)) {
continue;
}
const nationCities = cities.filter((city) => city.nationId === nation.id && city.supplyState > 0);
let rate = resolveNpcTaxRate(nationCities);
if (monthsSinceStart !== null && monthsSinceStart <= 4) {
rate = Math.min(rate, 10);
}
world.updateNation(nation.id, {
meta: {
...nation.meta,
rate,
},
});
}
},
};
};
@@ -351,6 +351,32 @@ const resolveDefinition = (
fallback: GeneralActionDefinition
): GeneralActionDefinition => definitions.get(actionKey) ?? fallback;
const resolveNpcTaxRate = (cities: City[]): number => {
if (cities.length === 0) {
return 15;
}
let pop = 0;
let all = 0;
for (const city of cities) {
const popRatio = city.populationMax > 0 ? city.population / city.populationMax : 0;
pop += popRatio;
all += calcCityDevRatio(city);
}
pop /= cities.length;
all /= cities.length;
const avg = (pop + all) / 2;
if (avg > 0.95) {
return 25;
}
if (avg > 0.7) {
return 20;
}
if (avg > 0.5) {
return 15;
}
return 10;
};
export const createReservedTurnHandler = async (options: {
reservedTurns: InMemoryReservedTurnStore;
scenarioConfig: ScenarioConfig;