fix live sortie legacy logs
This commit is contained in:
@@ -254,6 +254,14 @@ const resolveConquerCity = <TriggerState extends GeneralTriggerState>(
|
||||
let totalGoldLoss = 0;
|
||||
let totalRiceLoss = 0;
|
||||
|
||||
const defenderNationJosaUl = JosaUtil.pick(defenderNationName, '을');
|
||||
const defenderNationJosaUn = JosaUtil.pick(defenderNationName, '은');
|
||||
const defenderNationJosaYi = JosaUtil.pick(defenderNationName, '이');
|
||||
attackerLogger.pushNationHistoryLog(`<D><b>${defenderNationName}</b></>${defenderNationJosaUl} 정복`);
|
||||
attackerLogger.pushGlobalHistoryLog(
|
||||
`<R><b>【멸망】</b></><D><b>${defenderNationName}</b></>${defenderNationJosaUn} <R>멸망</>했습니다.`
|
||||
);
|
||||
|
||||
for (const general of defenderGenerals) {
|
||||
// Legacy Util::toInt truncates these losses rather than rounding.
|
||||
const loseGold = Math.trunc(general.gold * rng.nextRange(0.2, 0.5));
|
||||
@@ -271,6 +279,11 @@ const resolveConquerCity = <TriggerState extends GeneralTriggerState>(
|
||||
generalId: general.id,
|
||||
nationId: general.nationId,
|
||||
});
|
||||
generalLogger.pushGeneralActionLog(
|
||||
`<D><b>${defenderNationName}</b></>${defenderNationJosaYi} <R>멸망</>했습니다.`,
|
||||
LogFormat.PLAIN
|
||||
);
|
||||
generalLogger.pushGeneralHistoryLog(`<D><b>${defenderNationName}</b></>${defenderNationJosaYi} <R>멸망</>`);
|
||||
generalLogger.pushGeneralActionLog(
|
||||
`도주하며 금<C>${loseGold}</> 쌀<C>${loseRice}</>을 분실했습니다.`,
|
||||
LogFormat.PLAIN
|
||||
@@ -285,6 +298,26 @@ const resolveConquerCity = <TriggerState extends GeneralTriggerState>(
|
||||
attackerNation.gold = round(attackerNation.gold + collapseRewardGold);
|
||||
attackerNation.rice = round(attackerNation.rice + collapseRewardRice);
|
||||
|
||||
const resourceLog =
|
||||
`<D><b>${defenderNationName}</b></> 정복으로 ` +
|
||||
`금<C>${collapseRewardGold.toLocaleString('en-US')}</> ` +
|
||||
`쌀<C>${collapseRewardRice.toLocaleString('en-US')}</>을 획득했습니다.`;
|
||||
for (const general of generals) {
|
||||
if (general.nationId !== attackerNation.id || general.officerLevel < 5) {
|
||||
continue;
|
||||
}
|
||||
if (general.id === attacker.id) {
|
||||
attackerLogger.pushGeneralActionLog(resourceLog, LogFormat.PLAIN);
|
||||
continue;
|
||||
}
|
||||
const chiefLogger = new ActionLogger({
|
||||
generalId: general.id,
|
||||
nationId: attackerNation.id,
|
||||
});
|
||||
chiefLogger.pushGeneralActionLog(resourceLog, LogFormat.PLAIN);
|
||||
pushLoggers([chiefLogger], logs);
|
||||
}
|
||||
|
||||
defenderNation.meta.collapsed = true;
|
||||
affectedNations.add(defenderNation);
|
||||
affectedNations.add(attackerNation);
|
||||
|
||||
@@ -36,6 +36,36 @@ const resolveNationVar = (nation: Nation | null, key: string): TriggerValue | nu
|
||||
}
|
||||
};
|
||||
|
||||
const isCityWarUnit = (unit: WarUnit): boolean =>
|
||||
typeof (unit as WarUnit & { getCityId?: unknown }).getCityId === 'function';
|
||||
|
||||
const buildLegacySmallWarLog = (me: WarUnit, oppose: WarUnit): string => {
|
||||
const warType = !me.isAttacker() ? 'defense' : isCityWarUnit(oppose) ? 'siege' : 'attack';
|
||||
const warTypeStr = me.isAttacker() ? '→' : '←';
|
||||
return [
|
||||
'<div class="small_war_log"> ',
|
||||
'<span class="me"> ',
|
||||
'<span class="name_plate"> ',
|
||||
`<span class="crew_type">${me.getCrewTypeShortName()}</span> `,
|
||||
'<span class="name_plate_cover" >【',
|
||||
`<span class="name">${me.getName()}</span>】 </span> </span> `,
|
||||
'<span class="crew_plate" >',
|
||||
`<span class="remain_crew">${me.getHP()}</span >`,
|
||||
'<span class="killed_plate">(',
|
||||
`<span class="killed_crew">${-me.getDeadCurrentBattle()}</span>)</span ></span> </span> `,
|
||||
`<span class="war_type war_type_${warType}">${warTypeStr}</span> `,
|
||||
'<span class="you"> ',
|
||||
'<span class="crew_plate" >',
|
||||
`<span class="remain_crew">${oppose.getHP()}</span >`,
|
||||
'<span class="killed_plate">(',
|
||||
`<span class="killed_crew">${-oppose.getDeadCurrentBattle()}</span>)</span ></span> `,
|
||||
'<span class="name_plate"> ',
|
||||
`<span class="crew_type">${oppose.getCrewTypeShortName()}</span> `,
|
||||
'<span class="name_plate_cover" >【',
|
||||
`<span class="name">${oppose.getName()}</span>】 </span> </span> </span></div>`,
|
||||
].join('');
|
||||
};
|
||||
|
||||
// 전투 유닛 공통 상태와 수치 계산(legacy WarUnit 계열 포팅).
|
||||
export abstract class WarUnit<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
||||
private static nextUnitId = 1;
|
||||
@@ -317,8 +347,7 @@ export abstract class WarUnit<TriggerState extends GeneralTriggerState = General
|
||||
return;
|
||||
}
|
||||
|
||||
const warTypeStr = this.isAttacker() ? '→' : '←';
|
||||
const message = `${this.getCrewTypeShortName()} ${this.getName()} ${warTypeStr} ${oppose.getCrewTypeShortName()} ${oppose.getName()} (${this.getHP()} / ${oppose.getHP()})`;
|
||||
const message = buildLegacySmallWarLog(this, oppose);
|
||||
this.logger.pushGeneralBattleResultLog(message, LogFormat.EVENT_YEAR_MONTH);
|
||||
this.logger.pushGeneralBattleDetailLog(message, LogFormat.EVENT_YEAR_MONTH);
|
||||
this.logger.pushGeneralActionLog(message, LogFormat.EVENT_YEAR_MONTH);
|
||||
|
||||
@@ -149,7 +149,11 @@ export class WarUnitCity extends WarUnit {
|
||||
dead *= 1.05;
|
||||
}
|
||||
|
||||
if (conflict[nationId] !== undefined) {
|
||||
if (Object.keys(conflict).length === 0) {
|
||||
// Legacy treats the first attacker as the conflict baseline and
|
||||
// emits the "new participant" history only for later nations.
|
||||
conflict[nationId] = dead;
|
||||
} else if (conflict[nationId] !== undefined) {
|
||||
conflict[nationId] += dead;
|
||||
} else {
|
||||
conflict[nationId] = dead;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RandUtil } from '@sammo-ts/common';
|
||||
import { JosaUtil, type RandUtil } from '@sammo-ts/common';
|
||||
|
||||
import type {
|
||||
City,
|
||||
@@ -350,6 +350,7 @@ export class WarUnitGeneral<
|
||||
}
|
||||
|
||||
public addLevelExp(value: number): void {
|
||||
const previousExpLevel = getMetaNumber(this.general.meta, META_EXP_LEVEL);
|
||||
const sideAdjusted = this.isAttacker() ? value : value * 0.8;
|
||||
const adjusted = this.actionPipeline.onCalcStat(this.getActionContext(), 'experience', sideAdjusted);
|
||||
this.general.experience += adjusted;
|
||||
@@ -357,7 +358,18 @@ export class WarUnitGeneral<
|
||||
this.general.experience < 1000
|
||||
? Math.trunc(this.general.experience / 100)
|
||||
: Math.trunc(Math.sqrt(this.general.experience / 10));
|
||||
this.general.meta[META_EXP_LEVEL] = clamp(nextExpLevel, 0, MAX_EXP_LEVEL);
|
||||
const resolvedExpLevel = clamp(nextExpLevel, 0, MAX_EXP_LEVEL);
|
||||
this.general.meta[META_EXP_LEVEL] = resolvedExpLevel;
|
||||
if (resolvedExpLevel === previousExpLevel) {
|
||||
return;
|
||||
}
|
||||
const josaRo = JosaUtil.pick(String(resolvedExpLevel), '로');
|
||||
this.logger.pushGeneralActionLog(
|
||||
resolvedExpLevel > previousExpLevel
|
||||
? `<C>Lv ${resolvedExpLevel}</>${josaRo} <C>레벨업</>!`
|
||||
: `<C>Lv ${resolvedExpLevel}</>${josaRo} <R>레벨다운</>!`,
|
||||
LogFormat.PLAIN
|
||||
);
|
||||
}
|
||||
|
||||
public addDedication(value: number): void {
|
||||
|
||||
@@ -185,6 +185,7 @@ describe('war aftermath', () => {
|
||||
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({
|
||||
@@ -239,5 +240,13 @@ describe('war aftermath', () => {
|
||||
// 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</>을 획득했습니다.',
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -171,6 +171,7 @@ describe('war triggers', () => {
|
||||
general.experience = 90;
|
||||
general.meta.explevel = 0;
|
||||
const crewType = new WarCrewType(buildUnitSet().crewTypes![0]!);
|
||||
const logger = new ActionLogger({ generalId: 1, nationId: 1 });
|
||||
const unit = new WarUnitGeneral(
|
||||
new RandUtil(new ConstantRNG(0)),
|
||||
buildConfig(),
|
||||
@@ -179,7 +180,7 @@ describe('war triggers', () => {
|
||||
buildNation(),
|
||||
true,
|
||||
crewType,
|
||||
new ActionLogger({ generalId: 1, nationId: 1 }),
|
||||
logger,
|
||||
new WarActionPipeline([
|
||||
{
|
||||
onCalcStat: (_context, statName, value) =>
|
||||
@@ -191,6 +192,11 @@ describe('war triggers', () => {
|
||||
unit.addLevelExp(10);
|
||||
expect(general.experience).toBe(102);
|
||||
expect(general.meta.explevel).toBe(1);
|
||||
expect(logger.flush()).toContainEqual(
|
||||
expect.objectContaining({
|
||||
text: '<C>Lv 1</>로 <C>레벨업</>!',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('applies the legacy 90% dexterity gain for wizard and siege arms', () => {
|
||||
@@ -265,6 +271,52 @@ describe('war triggers', () => {
|
||||
expect(attacker.hasActivatedSkill('필살')).toBe(true);
|
||||
expect(attacker.getWarPowerMultiply()).toBeCloseTo(1.3);
|
||||
});
|
||||
|
||||
it('treats the first conflict nation as the baseline', () => {
|
||||
const rng = new RandUtil(new ConstantRNG(0));
|
||||
const config = buildConfig();
|
||||
const city = buildCity();
|
||||
const cityUnit = new WarUnitCity(
|
||||
rng,
|
||||
config,
|
||||
city,
|
||||
buildNation(),
|
||||
new WarCrewType(buildUnitSet().crewTypes![1]!),
|
||||
new ActionLogger({}),
|
||||
200,
|
||||
180
|
||||
);
|
||||
const firstNation = buildNation();
|
||||
const firstAttacker = new WarUnitGeneral(
|
||||
rng,
|
||||
config,
|
||||
buildGeneral(80),
|
||||
city,
|
||||
firstNation,
|
||||
true,
|
||||
new WarCrewType(buildUnitSet().crewTypes![0]!),
|
||||
new ActionLogger({ generalId: 1, nationId: 1 }),
|
||||
new WarActionPipeline([])
|
||||
);
|
||||
cityUnit.setOppose(firstAttacker);
|
||||
expect(cityUnit.addConflict()).toBe(false);
|
||||
|
||||
const secondNation = { ...buildNation(), id: 2 };
|
||||
const secondGeneral = { ...buildGeneral(80), id: 2, nationId: 2 };
|
||||
const secondAttacker = new WarUnitGeneral(
|
||||
rng,
|
||||
config,
|
||||
secondGeneral,
|
||||
city,
|
||||
secondNation,
|
||||
true,
|
||||
new WarCrewType(buildUnitSet().crewTypes![0]!),
|
||||
new ActionLogger({ generalId: 2, nationId: 2 }),
|
||||
new WarActionPipeline([])
|
||||
);
|
||||
cityUnit.setOppose(secondAttacker);
|
||||
expect(cityUnit.addConflict()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveWarBattle', () => {
|
||||
|
||||
Reference in New Issue
Block a user