253 lines
7.1 KiB
TypeScript
253 lines
7.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { ConstantRNG, RandUtil } from '@sammo-ts/common';
|
|
|
|
import type { City, General, Nation } from '../src/domain/entities.js';
|
|
import type { UnitSetDefinition } from '../src/world/types.js';
|
|
import { resolveWarAftermath } from '../src/war/aftermath.js';
|
|
import type { WarAftermathConfig } from '../src/war/types.js';
|
|
|
|
const buildUnitSet = (): UnitSetDefinition => ({
|
|
id: 'test',
|
|
name: 'test',
|
|
crewTypes: [
|
|
{
|
|
id: 999,
|
|
armType: 9,
|
|
name: '성벽',
|
|
attack: 0,
|
|
defence: 0,
|
|
speed: 1,
|
|
avoid: 0,
|
|
magicCoef: 0,
|
|
cost: 0,
|
|
rice: 10,
|
|
requirements: [],
|
|
attackCoef: {},
|
|
defenceCoef: {},
|
|
info: [],
|
|
initSkillTrigger: null,
|
|
phaseSkillTrigger: null,
|
|
iActionList: null,
|
|
},
|
|
],
|
|
});
|
|
|
|
const buildConfig = (): WarAftermathConfig => ({
|
|
initialNationGenLimit: 1,
|
|
techLevelIncYear: 5,
|
|
initialAllowedTechLevel: 1,
|
|
maxTechLevel: 12,
|
|
defaultCityWall: 1000,
|
|
baseGold: 0,
|
|
baseRice: 0,
|
|
castleCrewTypeId: 999,
|
|
});
|
|
|
|
const buildCity = (id: number, nationId: number): City => ({
|
|
id,
|
|
name: `City${id}`,
|
|
nationId,
|
|
level: 2,
|
|
state: 0,
|
|
population: 10000,
|
|
populationMax: 10000,
|
|
agriculture: 1000,
|
|
agricultureMax: 1000,
|
|
commerce: 1000,
|
|
commerceMax: 1000,
|
|
security: 1000,
|
|
securityMax: 1000,
|
|
defence: 100,
|
|
defenceMax: 200,
|
|
supplyState: 1,
|
|
frontState: 0,
|
|
wall: 100,
|
|
wallMax: 200,
|
|
meta: {},
|
|
});
|
|
|
|
const buildNation = (id: number): Nation => ({
|
|
id,
|
|
name: `Nation${id}`,
|
|
color: '#000000',
|
|
capitalCityId: id,
|
|
chiefGeneralId: null,
|
|
gold: 1000,
|
|
rice: 1000,
|
|
power: 0,
|
|
level: 1,
|
|
typeCode: 'test',
|
|
meta: {
|
|
tech: 1000,
|
|
},
|
|
});
|
|
|
|
const buildGeneral = (id: number, nationId: number, cityId: number): General => ({
|
|
id,
|
|
name: `General${id}`,
|
|
nationId,
|
|
cityId,
|
|
troopId: 0,
|
|
stats: {
|
|
leadership: 70,
|
|
strength: 70,
|
|
intelligence: 70,
|
|
},
|
|
experience: 100,
|
|
dedication: 100,
|
|
officerLevel: 3,
|
|
role: {
|
|
personality: null,
|
|
specialDomestic: null,
|
|
specialWar: null,
|
|
items: {
|
|
horse: null,
|
|
weapon: null,
|
|
book: null,
|
|
item: null,
|
|
},
|
|
},
|
|
injury: 0,
|
|
gold: 1000,
|
|
rice: 1000,
|
|
crew: 1000,
|
|
crewTypeId: 999,
|
|
train: 80,
|
|
atmos: 80,
|
|
age: 20,
|
|
npcState: 0,
|
|
triggerState: {
|
|
flags: {},
|
|
counters: {},
|
|
modifiers: {},
|
|
meta: {},
|
|
},
|
|
meta: { killturn: 24 },
|
|
});
|
|
|
|
describe('war aftermath', () => {
|
|
it('updates tech and diplomacy deltas', () => {
|
|
const attackerNation = buildNation(1);
|
|
const defenderNation = buildNation(2);
|
|
const attackerCity = buildCity(1, 1);
|
|
const defenderCity = buildCity(2, 2);
|
|
const attacker = buildGeneral(1, 1, 1);
|
|
|
|
const outcome = resolveWarAftermath({
|
|
battle: {
|
|
attacker,
|
|
defenders: [],
|
|
defenderCity,
|
|
logs: [],
|
|
conquered: false,
|
|
reports: [
|
|
{
|
|
id: attacker.id,
|
|
type: 'general',
|
|
name: attacker.name,
|
|
isAttacker: true,
|
|
killed: 100,
|
|
dead: 50,
|
|
},
|
|
],
|
|
},
|
|
attackerNation,
|
|
defenderNation,
|
|
attackerCity,
|
|
defenderCity,
|
|
nations: [attackerNation, defenderNation],
|
|
cities: [attackerCity, defenderCity],
|
|
generals: [attacker],
|
|
unitSet: buildUnitSet(),
|
|
config: buildConfig(),
|
|
time: {
|
|
year: 200,
|
|
month: 1,
|
|
startYear: 180,
|
|
},
|
|
});
|
|
|
|
expect(attackerNation.meta.tech).toBe(1000.6);
|
|
expect(defenderNation.meta.tech).toBe(1000.9);
|
|
expect(outcome.diplomacyDeltas).toHaveLength(2);
|
|
expect(attackerCity.meta.dead).toBe(60);
|
|
expect(defenderCity.meta.dead).toBe(90);
|
|
});
|
|
|
|
it('applies conquest collapse rewards', () => {
|
|
const rng = new RandUtil(new ConstantRNG(0));
|
|
const attackerNation = buildNation(1);
|
|
const defenderNation = buildNation(2);
|
|
defenderNation.gold = 5000;
|
|
defenderNation.rice = 6000;
|
|
const attackerCity = buildCity(1, 1);
|
|
const defenderCity = buildCity(2, 2);
|
|
defenderCity.meta.conflict = JSON.stringify({ 99: 200, 1: 100 });
|
|
const attacker = buildGeneral(1, 1, 1);
|
|
attacker.officerLevel = 12;
|
|
const defender = buildGeneral(2, 2, 2);
|
|
|
|
const outcome = resolveWarAftermath({
|
|
battle: {
|
|
attacker,
|
|
defenders: [defender],
|
|
defenderCity,
|
|
logs: [],
|
|
conquered: true,
|
|
reports: [
|
|
{
|
|
id: attacker.id,
|
|
type: 'general',
|
|
name: attacker.name,
|
|
isAttacker: true,
|
|
killed: 10,
|
|
dead: 5,
|
|
},
|
|
{
|
|
id: defenderCity.id,
|
|
type: 'city',
|
|
name: defenderCity.name,
|
|
isAttacker: false,
|
|
killed: 0,
|
|
dead: 0,
|
|
},
|
|
],
|
|
},
|
|
attackerNation,
|
|
defenderNation,
|
|
attackerCity,
|
|
defenderCity,
|
|
nations: [attackerNation, defenderNation],
|
|
cities: [attackerCity, defenderCity],
|
|
generals: [attacker, defender],
|
|
unitSet: buildUnitSet(),
|
|
config: buildConfig(),
|
|
time: {
|
|
year: 200,
|
|
month: 1,
|
|
startYear: 180,
|
|
},
|
|
rng,
|
|
});
|
|
|
|
expect(outcome.conquest?.nationCollapsed).toBe(true);
|
|
expect(attackerNation.gold).toBe(3600);
|
|
expect(attackerNation.rice).toBe(4600);
|
|
expect(defender.experience).toBe(90);
|
|
expect(defender.dedication).toBe(50);
|
|
// Removed nations can remain in old persisted conflict metadata. They
|
|
// must never receive ownership during a later conquest.
|
|
expect(defenderCity.nationId).toBe(attackerNation.id);
|
|
expect(defenderCity.meta.conflict).toBe('{}');
|
|
expect(outcome.logs.map((log) => log.text)).toEqual(
|
|
expect.arrayContaining([
|
|
'<D><b>Nation2</b></>를 정복',
|
|
'<R><b>【멸망】</b></><D><b>Nation2</b></>는 <R>멸망</>했습니다.',
|
|
'<D><b>Nation2</b></>가 <R>멸망</>했습니다.',
|
|
'<D><b>Nation2</b></> 정복으로 금<C>2,600</> 쌀<C>3,600</>을 획득했습니다.',
|
|
])
|
|
);
|
|
});
|
|
});
|