feat: eslint 적용 및 관련 코드 일괄 수정

This commit is contained in:
2026-01-05 15:46:47 +00:00
parent cb312f02b3
commit c965b1120f
387 changed files with 39808 additions and 38800 deletions
+7 -26
View File
@@ -1,11 +1,6 @@
import type { RandUtil } from '@sammo-ts/common';
import type {
City,
General,
GeneralTriggerState,
Nation,
} from '@sammo-ts/logic/domain/entities.js';
import type { City, General, GeneralTriggerState, Nation } from '@sammo-ts/logic/domain/entities.js';
import type { ActionLogger } from '@sammo-ts/logic/logging/actionLogger.js';
import type { WarStatName } from '@sammo-ts/logic/triggers/types.js';
import type { WarUnit } from './units.js';
@@ -24,13 +19,9 @@ export interface WarActionModule<TriggerState extends GeneralTriggerState = Gene
getName?(): string;
getInfo?(): string;
getBattleInitTriggerList?(
context: WarActionContext<TriggerState>
): WarTriggerCaller | null;
getBattleInitTriggerList?(context: WarActionContext<TriggerState>): WarTriggerCaller | null;
getBattlePhaseTriggerList?(
context: WarActionContext<TriggerState>
): WarTriggerCaller | null;
getBattlePhaseTriggerList?(context: WarActionContext<TriggerState>): WarTriggerCaller | null;
onCalcStat?(
context: WarActionContext<TriggerState>,
@@ -53,9 +44,7 @@ export interface WarActionModule<TriggerState extends GeneralTriggerState = Gene
): [number, number];
}
export class WarActionPipeline<
TriggerState extends GeneralTriggerState = GeneralTriggerState
> {
export class WarActionPipeline<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
// 전투용 iAction 파이프라인: 스탯/트리거/전투력 보정 흐름을 순서대로 적용한다.
private readonly modules: WarActionModule<TriggerState>[];
@@ -63,9 +52,7 @@ export class WarActionPipeline<
this.modules = modules.filter(Boolean) as WarActionModule<TriggerState>[];
}
getBattleInitTriggerList(
context: WarActionContext<TriggerState>
): WarTriggerCaller {
getBattleInitTriggerList(context: WarActionContext<TriggerState>): WarTriggerCaller {
const caller = new WarTriggerCaller();
for (const module of this.modules) {
const triggers = module.getBattleInitTriggerList?.(context);
@@ -76,9 +63,7 @@ export class WarActionPipeline<
return caller;
}
getBattlePhaseTriggerList(
context: WarActionContext<TriggerState>
): WarTriggerCaller {
getBattlePhaseTriggerList(context: WarActionContext<TriggerState>): WarTriggerCaller {
const caller = new WarTriggerCaller();
for (const module of this.modules) {
const triggers = module.getBattlePhaseTriggerList?.(context);
@@ -132,11 +117,7 @@ export class WarActionPipeline<
if (!module.getWarPowerMultiplier) {
continue;
}
const [attMul, defMul] = module.getWarPowerMultiplier(
context,
unit,
oppose
);
const [attMul, defMul] = module.getWarPowerMultiplier(context, unit, oppose);
attack *= attMul;
defence *= defMul;
}
+40 -138
View File
@@ -1,22 +1,9 @@
import {
JosaUtil,
LiteHashDRBG,
RandUtil,
} from '@sammo-ts/common';
import { JosaUtil, LiteHashDRBG, RandUtil } from '@sammo-ts/common';
import type {
City,
General,
GeneralTriggerState,
Nation,
} from '@sammo-ts/logic/domain/entities.js';
import type { City, General, GeneralTriggerState, Nation } from '@sammo-ts/logic/domain/entities.js';
import { ActionLogger } from '@sammo-ts/logic/logging/actionLogger.js';
import { LogFormat, type LogEntryDraft } from '@sammo-ts/logic/logging/types.js';
import {
buildCrewTypeIndex,
getTechCost,
getTechLevel,
} from '@sammo-ts/logic/world/unitSet.js';
import { buildCrewTypeIndex, getTechCost, getTechLevel } from '@sammo-ts/logic/world/unitSet.js';
import type { WarUnitReport } from './types.js';
import type {
ConquerCityOutcome,
@@ -26,21 +13,12 @@ import type {
WarAftermathTechContext,
WarDiplomacyDelta,
} from './types.js';
import {
clamp,
clampMin,
getMetaNumber,
round,
simpleSerialize,
} from './utils.js';
import { clamp, clampMin, getMetaNumber, round, simpleSerialize } from './utils.js';
const META_DEAD = 'dead';
const META_CONFLICT = 'conflict';
const findReport = (
reports: WarUnitReport[],
predicate: (report: WarUnitReport) => boolean
): WarUnitReport | null => {
const findReport = (reports: WarUnitReport[], predicate: (report: WarUnitReport) => boolean): WarUnitReport | null => {
for (const report of reports) {
if (predicate(report)) {
return report;
@@ -49,8 +27,7 @@ const findReport = (
return null;
};
const getDeadCounter = (city: City): number =>
getMetaNumber(city.meta, META_DEAD, 0);
const getDeadCounter = (city: City): number => getMetaNumber(city.meta, META_DEAD, 0);
const setDeadCounter = (city: City, value: number): void => {
city.meta[META_DEAD] = round(value);
@@ -67,19 +44,12 @@ const isSupplyCity = (city: City): boolean => {
return city.supplyState > 0;
};
const resolveCityTrainAtmos = (year: number, startYear: number): number =>
clamp(year - startYear + 59, 60, 110);
const resolveCityTrainAtmos = (year: number, startYear: number): number => clamp(year - startYear + 59, 60, 110);
const isTechLimited = (
tech: number,
year: number,
startYear: number,
config: WarAftermathConfig
): boolean => {
const isTechLimited = (tech: number, year: number, startYear: number, config: WarAftermathConfig): boolean => {
const relYear = clampMin(year - startYear, 0);
const relMaxTech = clamp(
Math.floor(relYear / config.techLevelIncYear) +
config.initialAllowedTechLevel,
Math.floor(relYear / config.techLevelIncYear) + config.initialAllowedTechLevel,
1,
config.maxTechLevel
);
@@ -92,15 +62,9 @@ const resolveNationGenCount = <TriggerState extends GeneralTriggerState>(
generals: General<TriggerState>[],
config: WarAftermathConfig
): { total: number; effective: number } => {
const fallback = generals.filter(
(general) => general.nationId === nation.id
).length;
const fallback = generals.filter((general) => general.nationId === nation.id).length;
let total = getMetaNumber(nation.meta, 'gennum', fallback);
let effective = generals.filter(
(general) =>
general.nationId === nation.id &&
general.npcState !== 5
).length;
let effective = generals.filter((general) => general.nationId === nation.id && general.npcState !== 5).length;
if (effective < config.initialNationGenLimit) {
total = config.initialNationGenLimit;
@@ -126,11 +90,7 @@ const applyNationTechGain = <TriggerState extends GeneralTriggerState>(
});
}
const { total, effective } = resolveNationGenCount(
nation,
input.generals,
config
);
const { total, effective } = resolveNationGenCount(nation, input.generals, config);
if (total !== effective) {
gain *= total / effective;
@@ -145,10 +105,7 @@ const applyNationTechGain = <TriggerState extends GeneralTriggerState>(
nation.meta.tech = round(tech);
};
const resolveConquerNation = (
city: City,
attackerNationId: number
): number => {
const resolveConquerNation = (city: City, attackerNationId: number): number => {
const rawConflict = city.meta[META_CONFLICT];
if (!rawConflict) {
return attackerNationId;
@@ -183,26 +140,20 @@ const findNextCapital = (
capturedCityId: number,
oldCapital: City
): City | null => {
const candidates = cities.filter(
(city) => city.nationId === defenderNationId && city.id !== capturedCityId
);
const candidates = cities.filter((city) => city.nationId === defenderNationId && city.id !== capturedCityId);
if (!candidates.length) {
return null;
}
const oldPos = getCityPosition(oldCapital);
if (!oldPos) {
return candidates.sort(
(lhs, rhs) => rhs.population - lhs.population
)[0]!;
return candidates.sort((lhs, rhs) => rhs.population - lhs.population)[0]!;
}
return candidates
.map((city) => {
const pos = getCityPosition(city);
const distance = pos
? Math.hypot(pos.x - oldPos.x, pos.y - oldPos.y)
: Number.MAX_SAFE_INTEGER;
const distance = pos ? Math.hypot(pos.x - oldPos.x, pos.y - oldPos.y) : Number.MAX_SAFE_INTEGER;
return { city, distance };
})
.sort((lhs, rhs) => {
@@ -224,14 +175,7 @@ const resolveConquerCity = <TriggerState extends GeneralTriggerState>(
input: WarAftermathInput<TriggerState>,
rng: RandUtil
): ConquerCityOutcome<TriggerState> => {
const {
attackerNation,
defenderNation,
defenderCity,
cities,
generals,
config,
} = input;
const { attackerNation, defenderNation, defenderCity, cities, generals, config } = input;
const attacker = input.battle.attacker;
const logs: LogEntryDraft[] = [];
@@ -247,9 +191,7 @@ const resolveConquerCity = <TriggerState extends GeneralTriggerState>(
const defenderNationId = defenderNation?.id ?? 0;
const defenderNationName = defenderNation?.name ?? '공백지';
const defenderNationDecoration = defenderNationId
? `<D><b>${defenderNationName}</b></>의`
: '공백지인';
const defenderNationDecoration = defenderNationId ? `<D><b>${defenderNationName}</b></>의` : '공백지인';
const attackerNationName = attackerNation.name;
const attackerGeneralName = attacker.name;
@@ -260,13 +202,8 @@ const resolveConquerCity = <TriggerState extends GeneralTriggerState>(
const josaYiGen = JosaUtil.pick(attackerGeneralName, '이');
const josaYiCity = JosaUtil.pick(cityName, '이');
attackerLogger.pushGeneralActionLog(
`<G><b>${cityName}</b></> 공략에 <S>성공</>했습니다.`,
LogFormat.PLAIN
);
attackerLogger.pushGeneralHistoryLog(
`<G><b>${cityName}</b></>${josaUl} <S>점령</>`
);
attackerLogger.pushGeneralActionLog(`<G><b>${cityName}</b></> 공략에 <S>성공</>했습니다.`, LogFormat.PLAIN);
attackerLogger.pushGeneralHistoryLog(`<G><b>${cityName}</b></>${josaUl} <S>점령</>`);
attackerLogger.pushGlobalActionLog(
`<Y>${attackerGeneralName}</>${josaYiGen} <G><b>${cityName}</b></> 공략에 <S>성공</>했습니다.`
);
@@ -285,9 +222,7 @@ const resolveConquerCity = <TriggerState extends GeneralTriggerState>(
pushLoggers([defenderNationLogger], logs);
}
const defenderCityCount = defenderNationId
? cities.filter((city) => city.nationId === defenderNationId).length
: 0;
const defenderCityCount = defenderNationId ? cities.filter((city) => city.nationId === defenderNationId).length : 0;
const nationCollapsed = defenderNationId !== 0 && defenderCityCount === 1;
let collapseRewardGold = 0;
@@ -295,9 +230,7 @@ const resolveConquerCity = <TriggerState extends GeneralTriggerState>(
// 국가 붕괴 시 자원 손실과 포상 정산.
if (nationCollapsed && defenderNation) {
const defenderGenerals = generals.filter(
(general) => general.nationId === defenderNationId
);
const defenderGenerals = generals.filter((general) => general.nationId === defenderNationId);
let totalGoldLoss = 0;
let totalRiceLoss = 0;
@@ -324,12 +257,8 @@ const resolveConquerCity = <TriggerState extends GeneralTriggerState>(
affectedGenerals.add(general);
}
collapseRewardGold =
Math.max(0, defenderNation.gold - config.baseGold) * 0.5 +
totalGoldLoss * 0.5;
collapseRewardRice =
Math.max(0, defenderNation.rice - config.baseRice) * 0.5 +
totalRiceLoss * 0.5;
collapseRewardGold = Math.max(0, defenderNation.gold - config.baseGold) * 0.5 + totalGoldLoss * 0.5;
collapseRewardRice = Math.max(0, defenderNation.rice - config.baseRice) * 0.5 + totalRiceLoss * 0.5;
attackerNation.gold = round(attackerNation.gold + collapseRewardGold);
attackerNation.rice = round(attackerNation.rice + collapseRewardRice);
@@ -341,12 +270,7 @@ const resolveConquerCity = <TriggerState extends GeneralTriggerState>(
// 수도 함락 시 수도 이전 및 내부 사기/자원 페널티.
if (!nationCollapsed && defenderNation && defenderNation.capitalCityId === defenderCity.id) {
const nextCapital = findNextCapital(
cities,
defenderNationId,
defenderCity.id,
defenderCity
);
const nextCapital = findNextCapital(cities, defenderNationId, defenderCity.id, defenderCity);
if (nextCapital) {
defenderNation.capitalCityId = nextCapital.id;
defenderNation.gold = round(defenderNation.gold * 0.5);
@@ -373,8 +297,7 @@ const resolveConquerCity = <TriggerState extends GeneralTriggerState>(
const conquerNation =
conquerNationId === attackerNation.id
? attackerNation
: input.nations.find((nation) => nation.id === conquerNationId) ??
attackerNation;
: (input.nations.find((nation) => nation.id === conquerNationId) ?? attackerNation);
if (conquerNationId === attackerNation.id) {
attacker.cityId = defenderCity.id;
@@ -431,9 +354,7 @@ const resolveConquerCity = <TriggerState extends GeneralTriggerState>(
};
};
export const resolveWarAftermath = <
TriggerState extends GeneralTriggerState = GeneralTriggerState
>(
export const resolveWarAftermath = <TriggerState extends GeneralTriggerState = GeneralTriggerState>(
input: WarAftermathInput<TriggerState>
): WarAftermathOutcome<TriggerState> => {
const logs: LogEntryDraft[] = [];
@@ -442,14 +363,8 @@ export const resolveWarAftermath = <
const affectedCities = new Set<City>();
const affectedGenerals = new Set<General<TriggerState>>();
const attackerReport = findReport(
input.battle.reports,
(report) => report.type === 'general' && report.isAttacker
);
const cityReport = findReport(
input.battle.reports,
(report) => report.type === 'city'
);
const attackerReport = findReport(input.battle.reports, (report) => report.type === 'general' && report.isAttacker);
const cityReport = findReport(input.battle.reports, (report) => report.type === 'city');
const attackerKilled = attackerReport?.killed ?? 0;
const attackerDead = attackerReport?.dead ?? 0;
@@ -484,10 +399,7 @@ export const resolveWarAftermath = <
defenderNation.rice = clampMin(defenderNation.rice - rice, 0);
affectedNations.add(defenderNation);
} else if (input.battle.conquered) {
const bonus =
defenderNation.capitalCityId === input.defenderCity.id
? 1000
: 500;
const bonus = defenderNation.capitalCityId === input.defenderCity.id ? 1000 : 500;
defenderNation.rice = round(defenderNation.rice + bonus);
affectedNations.add(defenderNation);
}
@@ -496,31 +408,21 @@ export const resolveWarAftermath = <
// 기술 경험치와 외교 사망자 수치 갱신.
if (input.attackerNation.id && attackerReport) {
const attackerTechGain = attackerDead * 0.012;
applyNationTechGain(
input.attackerNation,
attackerTechGain,
input,
{
side: 'attacker',
nation: input.attackerNation,
attackerReport,
}
);
applyNationTechGain(input.attackerNation, attackerTechGain, input, {
side: 'attacker',
nation: input.attackerNation,
attackerReport,
});
affectedNations.add(input.attackerNation);
}
if (input.defenderNation && input.defenderNation.id !== 0 && attackerReport) {
const defenderTechGain = attackerKilled * 0.009;
applyNationTechGain(
input.defenderNation,
defenderTechGain,
input,
{
side: 'defender',
nation: input.defenderNation,
attackerReport,
}
);
applyNationTechGain(input.defenderNation, defenderTechGain, input, {
side: 'defender',
nation: input.defenderNation,
attackerReport,
});
affectedNations.add(input.defenderNation);
diplomacyDeltas.push(
+81 -152
View File
@@ -1,30 +1,13 @@
import {
JosaUtil,
LiteHashDRBG,
RandUtil,
} from '@sammo-ts/common';
import { JosaUtil, LiteHashDRBG, RandUtil } from '@sammo-ts/common';
import type {
City,
General,
GeneralTriggerState,
} from '@sammo-ts/logic/domain/entities.js';
import type { City, General, GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js';
import { ActionLogger } from '@sammo-ts/logic/logging/actionLogger.js';
import { LogFormat } from '@sammo-ts/logic/logging/types.js';
import { buildCrewTypeIndex as buildCrewTypeDefinitionIndex } from '@sammo-ts/logic/world/unitSet.js';
import { WarActionPipeline } from './actions.js';
import { WarCrewType } from './crewType.js';
import {
WarTriggerCaller,
createWarTriggerEnv,
type WarTriggerRegistry,
} from './triggers.js';
import type {
WarBattleInput,
WarBattleOutcome,
WarGeneralInput,
WarUnitReport,
} from './types.js';
import { WarTriggerCaller, createWarTriggerEnv, type WarTriggerRegistry } from './triggers.js';
import type { WarBattleInput, WarBattleOutcome, WarGeneralInput, WarUnitReport } from './types.js';
import { getMetaNumber } from './utils.js';
import { WarUnitCity, WarUnitGeneral, type WarUnit } from './units.js';
@@ -33,15 +16,10 @@ const META_FULL_STRENGTH = 'fullStrength';
const META_FULL_INTELLIGENCE = 'fullIntelligence';
const META_DEFENCE_TRAIN = 'defenceTrain';
const defaultLoggerFactory = (options: {
generalId?: number;
nationId?: number;
}): ActionLogger => new ActionLogger(options);
const defaultLoggerFactory = (options: { generalId?: number; nationId?: number }): ActionLogger =>
new ActionLogger(options);
const resolveCrewType = (
crewTypeIndex: Map<number, WarCrewType>,
crewTypeId: number
): WarCrewType => {
const resolveCrewType = (crewTypeIndex: Map<number, WarCrewType>, crewTypeId: number): WarCrewType => {
const crewType = crewTypeIndex.get(crewTypeId);
if (!crewType) {
throw new Error(`Invalid crew type: ${crewTypeId}`);
@@ -49,9 +27,7 @@ const resolveCrewType = (
return crewType;
};
const buildWarCrewTypeIndex = (
unitSet: WarBattleInput['unitSet']
): Map<number, WarCrewType> => {
const buildWarCrewTypeIndex = (unitSet: WarBattleInput['unitSet']): Map<number, WarCrewType> => {
const index = new Map<number, WarCrewType>();
const crewTypes = buildCrewTypeDefinitionIndex(unitSet);
for (const [id, crewType] of crewTypes) {
@@ -62,8 +38,7 @@ const buildWarCrewTypeIndex = (
const createPipeline = <TriggerState extends GeneralTriggerState>(
input: WarGeneralInput<TriggerState>
): WarActionPipeline<TriggerState> =>
new WarActionPipeline(input.modules ?? []);
): WarActionPipeline<TriggerState> => new WarActionPipeline(input.modules ?? []);
const appendCrewTypeTriggers = (
caller: WarTriggerCaller,
@@ -91,10 +66,7 @@ const appendCrewTypeTriggers = (
}
};
const buildBattleInitTriggers = (
unit: WarUnit,
registry: WarTriggerRegistry | undefined
): WarTriggerCaller => {
const buildBattleInitTriggers = (unit: WarUnit, registry: WarTriggerRegistry | undefined): WarTriggerCaller => {
const caller = new WarTriggerCaller();
if (unit instanceof WarUnitGeneral) {
const context = unit.getActionContext();
@@ -104,10 +76,7 @@ const buildBattleInitTriggers = (
return caller;
};
const buildBattlePhaseTriggers = (
unit: WarUnit,
registry: WarTriggerRegistry | undefined
): WarTriggerCaller => {
const buildBattlePhaseTriggers = (unit: WarUnit, registry: WarTriggerRegistry | undefined): WarTriggerCaller => {
const caller = new WarTriggerCaller();
if (unit instanceof WarUnitGeneral) {
appendCrewTypeTriggers(caller, unit, ['che_필살'], registry);
@@ -118,18 +87,16 @@ const buildBattlePhaseTriggers = (
return caller;
};
const resolveFullStats = (general: General): {
const resolveFullStats = (
general: General
): {
leadership: number;
strength: number;
intelligence: number;
} => ({
leadership: getMetaNumber(general.meta, META_FULL_LEADERSHIP, general.stats.leadership),
strength: getMetaNumber(general.meta, META_FULL_STRENGTH, general.stats.strength),
intelligence: getMetaNumber(
general.meta,
META_FULL_INTELLIGENCE,
general.stats.intelligence
),
intelligence: getMetaNumber(general.meta, META_FULL_INTELLIGENCE, general.stats.intelligence),
});
const isSupplyCity = (city: City): boolean => {
@@ -143,15 +110,10 @@ const isSupplyCity = (city: City): boolean => {
return city.supplyState > 0;
};
export const computeBattleOrder = (
defender: WarUnit,
attacker: WarUnitGeneral
): number => {
export const computeBattleOrder = (defender: WarUnit, attacker: WarUnitGeneral): number => {
if (defender instanceof WarUnitCity) {
const context = attacker.getActionContext();
return attacker
.getActionPipeline()
.onCalcOpposeStat(context, 'cityBattleOrder', -1);
return attacker.getActionPipeline().onCalcOpposeStat(context, 'cityBattleOrder', -1);
}
if (!(defender instanceof WarUnitGeneral)) {
@@ -174,18 +136,12 @@ export const computeBattleOrder = (
return 0;
}
const realStat =
general.stats.leadership +
general.stats.strength +
general.stats.intelligence;
const realStat = general.stats.leadership + general.stats.strength + general.stats.intelligence;
const fullStats = resolveFullStats(general);
const fullStat =
fullStats.leadership + fullStats.strength + fullStats.intelligence;
const fullStat = fullStats.leadership + fullStats.strength + fullStats.intelligence;
const totalStat = (realStat + fullStat) / 2;
const totalCrew =
(general.crew / 1_000_000) *
Math.pow(general.train * general.atmos, 1.5);
const totalCrew = (general.crew / 1_000_000) * Math.pow(general.train * general.atmos, 1.5);
return totalStat + totalCrew / 100;
};
@@ -231,17 +187,11 @@ const flushLoggers = (loggers: ActionLogger[]): Array<ReturnType<ActionLogger['f
return logs;
};
export const resolveWarBattle = <
TriggerState extends GeneralTriggerState = GeneralTriggerState
>(
export const resolveWarBattle = <TriggerState extends GeneralTriggerState = GeneralTriggerState>(
input: WarBattleInput<TriggerState>
): WarBattleOutcome<TriggerState> => {
// process_war.php 전투 루프를 순수 로직으로 이식한다.
const rng =
input.rng ??
new RandUtil(
LiteHashDRBG.build(input.seed ?? '')
);
const rng = input.rng ?? new RandUtil(LiteHashDRBG.build(input.seed ?? ''));
const loggerFactory = input.loggerFactory ?? defaultLoggerFactory;
const triggerRegistry = input.triggerRegistry;
@@ -307,18 +257,11 @@ export const resolveWarBattle = <
defenderGenerals.push(unit);
}
if (
defenderGenerals.length > 0 &&
computeBattleOrder(cityUnit, attackerUnit) > 0
) {
if (defenderGenerals.length > 0 && computeBattleOrder(cityUnit, attackerUnit) > 0) {
defenderUnits.push(cityUnit);
}
defenderUnits.sort(
(lhs, rhs) =>
computeBattleOrder(rhs, attackerUnit) -
computeBattleOrder(lhs, attackerUnit)
);
defenderUnits.sort((lhs, rhs) => computeBattleOrder(rhs, attackerUnit) - computeBattleOrder(lhs, attackerUnit));
const iter = defenderUnits.values();
let defender: WarUnit<TriggerState> | null = null;
@@ -345,8 +288,7 @@ export const resolveWarBattle = <
let conquerCity = false;
let logWritten = false;
const attackerNationName =
(attackerUnit.getNationVar('name') as string | null) ?? 'UNKNOWN';
const attackerNationName = (attackerUnit.getNationVar('name') as string | null) ?? 'UNKNOWN';
const attackerName = attackerUnit.getName();
const cityName = cityUnit.getName();
const seedText = input.seed ? `(전투시드: ${input.seed})` : '';
@@ -413,17 +355,21 @@ export const resolveWarBattle = <
const josaRo = JosaUtil.pick(attackerCrewName, '로');
const josaUl = JosaUtil.pick(defenderCrewName, '을');
attackerUnit.getLogger().pushGeneralActionLog(
`${attackerCrewName}${josaRo} <Y>${defenderName}</>의 ${defenderCrewName}${josaUl} <M>공격</>합니다.`,
LogFormat.MONTH
);
attackerUnit
.getLogger()
.pushGeneralActionLog(
`${attackerCrewName}${josaRo} <Y>${defenderName}</>의 ${defenderCrewName}${josaUl} <M>공격</>합니다.`,
LogFormat.MONTH
);
const defJosaRo = JosaUtil.pick(defenderCrewName, '로');
const defJosaUl = JosaUtil.pick(attackerCrewName, '을');
defender.getLogger().pushGeneralActionLog(
`${defenderCrewName}${defJosaRo} <Y>${attackerName}</>의 ${attackerCrewName}${defJosaUl} <M>수비</>합니다.`,
LogFormat.MONTH
);
defender
.getLogger()
.pushGeneralActionLog(
`${defenderCrewName}${defJosaRo} <Y>${attackerName}</>의 ${attackerCrewName}${defJosaUl} <M>수비</>합니다.`,
LogFormat.MONTH
);
} else {
const josaYiName = JosaUtil.pick(attackerName, '이');
const josaRoCrew = JosaUtil.pick(attackerCrewName, '로');
@@ -442,10 +388,7 @@ export const resolveWarBattle = <
const initCaller = buildBattleInitTriggers(attackerUnit, triggerRegistry);
initCaller.merge(buildBattleInitTriggers(defender, triggerRegistry));
initCaller.fire(
{ rng, attacker: attackerUnit, defender },
createWarTriggerEnv()
);
initCaller.fire({ rng, attacker: attackerUnit, defender }, createWarTriggerEnv());
}
attackerUnit.beginPhase();
@@ -453,10 +396,7 @@ export const resolveWarBattle = <
const battleCaller = buildBattlePhaseTriggers(attackerUnit, triggerRegistry);
battleCaller.merge(buildBattlePhaseTriggers(defender, triggerRegistry));
battleCaller.fire(
{ rng, attacker: attackerUnit, defender },
createWarTriggerEnv()
);
battleCaller.fire({ rng, attacker: attackerUnit, defender }, createWarTriggerEnv());
let deadDefender = attackerUnit.calcDamage();
let deadAttacker = defender.calcDamage();
@@ -486,18 +426,20 @@ export const resolveWarBattle = <
attackerUnit.increaseKilled(deadDefender);
defender.increaseKilled(deadAttacker);
const phaseNickname = defender.getPhase() < 0
? '先'
: `${attackerUnit.getPhase() + 1} `;
const phaseNickname = defender.getPhase() < 0 ? '先' : `${attackerUnit.getPhase() + 1} `;
if (deadAttacker > 0 || deadDefender > 0) {
attackerUnit.getLogger().pushGeneralBattleDetailLog(
`${phaseNickname}: <Y1>【${attackerUnit.getName()}】</> <C>${attackerUnit.getHP()} (-${deadAttacker})</> VS <C>${defender.getHP()} (-${deadDefender})</> <Y1>【${defender.getName()}】</>`
);
attackerUnit
.getLogger()
.pushGeneralBattleDetailLog(
`${phaseNickname}: <Y1>【${attackerUnit.getName()}】</> <C>${attackerUnit.getHP()} (-${deadAttacker})</> VS <C>${defender.getHP()} (-${deadDefender})</> <Y1>【${defender.getName()}】</>`
);
defender.getLogger().pushGeneralBattleDetailLog(
`${phaseNickname}: <Y1>【${defender.getName()}】</> <C>${defender.getHP()} (-${deadDefender})</> VS <C>${attackerUnit.getHP()} (-${deadAttacker})</> <Y1>【${attackerUnit.getName()}】</>`
);
defender
.getLogger()
.pushGeneralBattleDetailLog(
`${phaseNickname}: <Y1>【${defender.getName()}】</> <C>${defender.getHP()} (-${deadDefender})</> VS <C>${attackerUnit.getHP()} (-${deadAttacker})</> <Y1>【${attackerUnit.getName()}】</>`
);
}
attackerUnit.addPhase();
@@ -521,14 +463,18 @@ export const resolveWarBattle = <
`<Y>${attackerUnit.getName()}</>의 ${attackerUnit.getCrewTypeName()}${josaYiCrew} 퇴각했습니다.`,
LogFormat.MONTH
);
attackerUnit.getLogger().pushGeneralActionLog(
attackerState.noRice ? '군량 부족으로 퇴각합니다.' : '퇴각했습니다.',
LogFormat.PLAIN
);
defender.getLogger().pushGeneralActionLog(
`<Y>${attackerUnit.getName()}</>의 ${attackerUnit.getCrewTypeName()}${josaYiCrew} 퇴각했습니다.`,
LogFormat.PLAIN
);
attackerUnit
.getLogger()
.pushGeneralActionLog(
attackerState.noRice ? '군량 부족으로 퇴각합니다.' : '퇴각했습니다.',
LogFormat.PLAIN
);
defender
.getLogger()
.pushGeneralActionLog(
`<Y>${attackerUnit.getName()}</>의 ${attackerUnit.getCrewTypeName()}${josaYiCrew} 퇴각했습니다.`,
LogFormat.PLAIN
);
break;
}
@@ -562,27 +508,25 @@ export const resolveWarBattle = <
`<Y>${defender.getName()}</>의 ${defender.getCrewTypeName()}${josaYiDefCrew} 패퇴했습니다.`,
LogFormat.MONTH
);
attackerUnit.getLogger().pushGeneralActionLog(
`<Y>${defender.getName()}</>의 ${defender.getCrewTypeName()}${josaYiDefCrew} 패퇴했습니다.`,
LogFormat.PLAIN
);
defender.getLogger().pushGeneralActionLog(
'군량 부족으로 패퇴합니다.',
LogFormat.PLAIN
);
attackerUnit
.getLogger()
.pushGeneralActionLog(
`<Y>${defender.getName()}</>의 ${defender.getCrewTypeName()}${josaYiDefCrew} 패퇴했습니다.`,
LogFormat.PLAIN
);
defender.getLogger().pushGeneralActionLog('군량 부족으로 패퇴합니다.', LogFormat.PLAIN);
} else {
attackerLogger.pushGlobalActionLog(
`<Y>${defender.getName()}</>의 ${defender.getCrewTypeName()}${josaYiDefCrew} 전멸했습니다.`,
LogFormat.MONTH
);
attackerUnit.getLogger().pushGeneralActionLog(
`<Y>${defender.getName()}</>의 ${defender.getCrewTypeName()}${josaYiDefCrew} 전멸했습니다.`,
LogFormat.PLAIN
);
defender.getLogger().pushGeneralActionLog(
'전멸했습니다.',
LogFormat.PLAIN
);
attackerUnit
.getLogger()
.pushGeneralActionLog(
`<Y>${defender.getName()}</>의 ${defender.getCrewTypeName()}${josaYiDefCrew} 전멸했습니다.`,
LogFormat.PLAIN
);
defender.getLogger().pushGeneralActionLog('전멸했습니다.', LogFormat.PLAIN);
}
if (attackerUnit.getPhase() >= attackerUnit.getMaxPhase()) {
@@ -623,10 +567,7 @@ export const resolveWarBattle = <
}
}
const logs = flushLoggers([
attackerLogger,
...defenderGenerals.map((unit) => unit.getLogger()),
]);
const logs = flushLoggers([attackerLogger, ...defenderGenerals.map((unit) => unit.getLogger())]);
const reports: WarUnitReport[] = [
resolveUnitReport(attackerUnit),
@@ -644,23 +585,15 @@ export const resolveWarBattle = <
metrics: {
attackerPhase: attackerUnit.getPhase(),
attackerActivatedSkills: attackerUnit.getActivatedSkillLog(),
defenderActivatedSkills: defenderUnits.map((unit) =>
unit.getActivatedSkillLog()
),
defenderActivatedSkills: defenderUnits.map((unit) => unit.getActivatedSkillLog()),
},
};
};
export const resolveDefenderOrder = <
TriggerState extends GeneralTriggerState = GeneralTriggerState
>(
export const resolveDefenderOrder = <TriggerState extends GeneralTriggerState = GeneralTriggerState>(
input: WarBattleInput<TriggerState>
): number[] => {
const rng =
input.rng ??
new RandUtil(
LiteHashDRBG.build(input.seed ?? '')
);
const rng = input.rng ?? new RandUtil(LiteHashDRBG.build(input.seed ?? ''));
const loggerFactory = input.loggerFactory ?? defaultLoggerFactory;
const crewTypeIndex = buildWarCrewTypeIndex(input.unitSet);
@@ -709,11 +642,7 @@ export const resolveDefenderOrder = <
defenderUnits.push(unit);
}
defenderUnits.sort(
(lhs, rhs) =>
computeBattleOrder(rhs, attackerUnit) -
computeBattleOrder(lhs, attackerUnit)
);
defenderUnits.sort((lhs, rhs) => computeBattleOrder(rhs, attackerUnit) - computeBattleOrder(lhs, attackerUnit));
return defenderUnits.map((unit) => unit.getGeneral().id);
};
+2 -7
View File
@@ -24,9 +24,7 @@ export const createWarTriggerEnv = (): WarTriggerEnv => ({
export class WarTriggerCaller extends TriggerCaller<WarTriggerContext, WarTriggerEnv> {}
export type WarTriggerFactory = (
unit: WarUnit
) => WarTrigger | WarTriggerCaller | null;
export type WarTriggerFactory = (unit: WarUnit) => WarTrigger | WarTriggerCaller | null;
export type WarTriggerRegistry = Record<string, WarTriggerFactory>;
@@ -51,10 +49,7 @@ export abstract class BaseWarUnitTrigger implements WarTrigger {
return `${this.priority}_${this.constructor.name}_${this.unit.getUnitId()}_${this.raiseType}`;
}
action(
context: WarTriggerContext,
env: WarTriggerEnv
): WarTriggerEnv {
action(context: WarTriggerContext, env: WarTriggerEnv): WarTriggerEnv {
const nextEnv: WarTriggerEnv = {
e_attacker: env.e_attacker ?? {},
e_defender: env.e_defender ?? {},
+3 -14
View File
@@ -54,15 +54,8 @@ class ActivateTrigger extends BaseWarUnitTrigger {
}
selfEnv['치료발동'] = true;
oppose
.getLogger()
.pushGeneralBattleDetailLog(
'상대가 <R>치료</>했다!',
LogFormat.PLAIN
);
self
.getLogger()
.pushGeneralBattleDetailLog('<C>치료</>했다!', LogFormat.PLAIN);
oppose.getLogger().pushGeneralBattleDetailLog('상대가 <R>치료</>했다!', LogFormat.PLAIN);
self.getLogger().pushGeneralBattleDetailLog('<C>치료</>했다!', LogFormat.PLAIN);
oppose.multiplyWarPowerMultiply(0.7);
if (self instanceof WarUnitGeneral) {
@@ -79,9 +72,5 @@ export const triggerModule: WarTriggerModule = {
key: 'che_의술',
name: '의술',
info: '[전투] 페이즈마다 치료 발동(아군 피해 30% 감소, 부상 회복)',
createTriggerList: (unit) =>
new WarTriggerCaller(
new AttemptTrigger(unit),
new ActivateTrigger(unit)
),
createTriggerList: (unit) => new WarTriggerCaller(new AttemptTrigger(unit), new ActivateTrigger(unit)),
};
+7 -30
View File
@@ -11,14 +11,7 @@ export class CheSnipingAttemptTrigger extends BaseWarUnitTrigger {
private readonly woundMax: number;
private readonly addAtmos: number;
constructor(
unit: WarUnit,
raiseType: number,
ratio: number,
woundMin: number,
woundMax: number,
addAtmos = 20
) {
constructor(unit: WarUnit, raiseType: number, ratio: number, woundMin: number, woundMax: number, addAtmos = 20) {
super(unit, TriggerPriority.Pre + 100, raiseType);
this.ratio = ratio;
this.woundMin = woundMin;
@@ -85,27 +78,12 @@ export class CheSnipingActivateTrigger extends BaseWarUnitTrigger {
if (oppose instanceof WarUnitGeneral) {
self.getLogger().pushGeneralActionLog('상대를 <C>저격</>했다!', LogFormat.PLAIN);
self.getLogger().pushGeneralBattleDetailLog(
'상대 <C>저격</>했다!',
LogFormat.PLAIN
);
oppose.getLogger().pushGeneralActionLog(
'상대에게 <R>저격</>당했다!',
LogFormat.PLAIN
);
oppose.getLogger().pushGeneralBattleDetailLog(
'상대에게 <R>저격</>당했다!',
LogFormat.PLAIN
);
self.getLogger().pushGeneralBattleDetailLog('상대를 <C>저격</>했다!', LogFormat.PLAIN);
oppose.getLogger().pushGeneralActionLog('상대에게 <R>저격</>했다!', LogFormat.PLAIN);
oppose.getLogger().pushGeneralBattleDetailLog('상대에게 <R>저격</>당했다!', LogFormat.PLAIN);
} else {
self.getLogger().pushGeneralActionLog(
'성벽 수비대장을 <C>저격</>했다!',
LogFormat.PLAIN
);
self.getLogger().pushGeneralBattleDetailLog(
'성벽 수비대장을 <C>저격</>했다!',
LogFormat.PLAIN
);
self.getLogger().pushGeneralActionLog('성벽 수비대장을 <C>저격</>했다!', LogFormat.PLAIN);
self.getLogger().pushGeneralBattleDetailLog('성벽 수비대장을 <C>저격</>했다!', LogFormat.PLAIN);
}
const addAtmos = Number(selfEnv['addAtmos'] ?? 0);
@@ -115,8 +93,7 @@ export class CheSnipingActivateTrigger extends BaseWarUnitTrigger {
const woundMin = Number(selfEnv['woundMin'] ?? 10);
const woundMax = Number(selfEnv['woundMax'] ?? 80);
oppose.getGeneral().injury = clamp(
oppose.getGeneral().injury +
self.rng.nextRangeInt(woundMin, woundMax),
oppose.getGeneral().injury + self.rng.nextRangeInt(woundMin, woundMax),
0,
80
);
+3 -11
View File
@@ -53,12 +53,8 @@ class ActivateTrigger extends BaseWarUnitTrigger {
}
selfEnv['필살발동'] = true;
oppose
.getLogger()
.pushGeneralBattleDetailLog('상대의 <R>필살</>공격!</>', LogFormat.PLAIN);
self
.getLogger()
.pushGeneralBattleDetailLog('<C>필살</>공격!</>', LogFormat.PLAIN);
oppose.getLogger().pushGeneralBattleDetailLog('상대의 <R>필살</>공격!</>', LogFormat.PLAIN);
self.getLogger().pushGeneralBattleDetailLog('<C>필살</>공격!</>', LogFormat.PLAIN);
self.multiplyWarPowerMultiply(self.criticalDamage());
return true;
@@ -69,9 +65,5 @@ export const triggerModule: WarTriggerModule = {
key: 'che_필살',
name: '필살',
info: '[전투] 페이즈마다 확률로 필살 발동',
createTriggerList: (unit) =>
new WarTriggerCaller(
new AttemptTrigger(unit),
new ActivateTrigger(unit)
),
createTriggerList: (unit) => new WarTriggerCaller(new AttemptTrigger(unit), new ActivateTrigger(unit)),
};
+5 -15
View File
@@ -1,13 +1,9 @@
import type { WarTriggerModule, WarTriggerModuleExport } from './types.js';
import type { WarTriggerRegistry } from '@sammo-ts/logic/war/triggers.js';
export const WAR_TRIGGER_KEYS = [
'che_필살',
'che_의술',
] as const;
export const WAR_TRIGGER_KEYS = ['che_필살', 'che_의술'] as const;
export type WarTriggerKey =
(typeof WAR_TRIGGER_KEYS)[number];
export type WarTriggerKey = (typeof WAR_TRIGGER_KEYS)[number];
export type WarTriggerImporter = () => Promise<WarTriggerModuleExport>;
@@ -22,9 +18,7 @@ export const isWarTriggerKey = (value: string): value is WarTriggerKey =>
export class WarTriggerLoader {
private readonly cache = new Map<WarTriggerKey, Promise<WarTriggerModule>>();
constructor(
private readonly importers: Record<WarTriggerKey, WarTriggerImporter> = defaultImporters
) {}
constructor(private readonly importers: Record<WarTriggerKey, WarTriggerImporter> = defaultImporters) {}
async load(key: WarTriggerKey): Promise<WarTriggerModule> {
const cached = this.cache.get(key);
@@ -41,9 +35,7 @@ export class WarTriggerLoader {
}
const resolved = module.triggerModule;
if (resolved.key !== key) {
throw new Error(
`War trigger key mismatch: expected ${key}, got ${resolved.key}`
);
throw new Error(`War trigger key mismatch: expected ${key}, got ${resolved.key}`);
}
return resolved;
});
@@ -68,9 +60,7 @@ export const loadWarTriggerModules = async (
return modules;
};
export const createWarTriggerRegistry = (
modules: WarTriggerModule[]
): WarTriggerRegistry => {
export const createWarTriggerRegistry = (modules: WarTriggerModule[]): WarTriggerRegistry => {
const registry: WarTriggerRegistry = {};
for (const module of modules) {
registry[module.key] = (unit) => module.createTriggerList(unit);
+7 -24
View File
@@ -1,11 +1,6 @@
import type { RandUtil } from '@sammo-ts/common';
import type {
City,
General,
GeneralTriggerState,
Nation,
} from '@sammo-ts/logic/domain/entities.js';
import type { City, General, GeneralTriggerState, Nation } from '@sammo-ts/logic/domain/entities.js';
import type { ActionLogger } from '@sammo-ts/logic/logging/actionLogger.js';
import type { LogEntryDraft } from '@sammo-ts/logic/logging/types.js';
import type { UnitSetDefinition } from '@sammo-ts/logic/world/types.js';
@@ -38,9 +33,7 @@ export interface WarTimeContext {
startYear: number;
}
export interface WarGeneralInput<
TriggerState extends GeneralTriggerState = GeneralTriggerState
> {
export interface WarGeneralInput<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
general: General<TriggerState>;
city: City;
nation: Nation | null;
@@ -48,9 +41,7 @@ export interface WarGeneralInput<
modules?: Array<WarActionModule<TriggerState> | null | undefined>;
}
export interface WarBattleInput<
TriggerState extends GeneralTriggerState = GeneralTriggerState
> {
export interface WarBattleInput<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
seed?: string;
rng?: RandUtil;
unitSet: UnitSetDefinition;
@@ -79,9 +70,7 @@ export interface WarBattleMetrics {
defenderActivatedSkills: Array<Record<string, number>>;
}
export interface WarBattleOutcome<
TriggerState extends GeneralTriggerState = GeneralTriggerState
> {
export interface WarBattleOutcome<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
attacker: General<TriggerState>;
defenders: General<TriggerState>[];
defenderCity: City;
@@ -115,9 +104,7 @@ export interface WarDiplomacyDelta {
deadDelta: number;
}
export interface ConquerCityOutcome<
TriggerState extends GeneralTriggerState = GeneralTriggerState
> {
export interface ConquerCityOutcome<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
conquerNationId: number;
nationCollapsed: boolean;
collapseRewardGold: number;
@@ -128,9 +115,7 @@ export interface ConquerCityOutcome<
generals: General<TriggerState>[];
}
export interface WarAftermathInput<
TriggerState extends GeneralTriggerState = GeneralTriggerState
> {
export interface WarAftermathInput<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
battle: WarBattleOutcome<TriggerState>;
attackerNation: Nation;
defenderNation: Nation | null;
@@ -147,9 +132,7 @@ export interface WarAftermathInput<
calcNationTechGain?: (context: WarAftermathTechContext) => number;
}
export interface WarAftermathOutcome<
TriggerState extends GeneralTriggerState = GeneralTriggerState
> {
export interface WarAftermathOutcome<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
nations: Nation[];
cities: City[];
generals: General<TriggerState>[];
+32 -128
View File
@@ -40,8 +40,7 @@ const META_LEADERSHIP_EXP = 'leadershipExp';
const RANK_WARNUM = `${META_RANK_PREFIX}warnum`;
const RANK_KILLNUM = `${META_RANK_PREFIX}killnum`;
const toDexStatName = (armType: number): WarStatName =>
`dex${armType}` as WarStatName;
const toDexStatName = (armType: number): WarStatName => `dex${armType}` as WarStatName;
const RANK_DEATHNUM = `${META_RANK_PREFIX}deathnum`;
const RANK_OCCUPIED = `${META_RANK_PREFIX}occupied`;
const RANK_KILLCREW = `${META_RANK_PREFIX}killcrew`;
@@ -51,8 +50,7 @@ const RANK_DEATHCREW_PERSON = `${META_RANK_PREFIX}deathcrew_person`;
const WAR_CRITICAL_RANGE: [number, number] = [1.3, 2.0];
const resolveNationTech = (nation: Nation | null): number =>
nation ? getMetaNumber(nation.meta, 'tech', 0) : 0;
const resolveNationTech = (nation: Nation | null): number => (nation ? getMetaNumber(nation.meta, 'tech', 0) : 0);
const resolveNationVar = (nation: Nation | null, key: string): TriggerValue | null => {
if (!nation) {
@@ -79,9 +77,7 @@ const resolveNationVar = (nation: Nation | null, key: string): TriggerValue | nu
};
// 전투 유닛 공통 상태와 수치 계산(legacy WarUnit 계열 포팅).
export abstract class WarUnit<
TriggerState extends GeneralTriggerState = GeneralTriggerState
> {
export abstract class WarUnit<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
private static nextUnitId = 1;
private readonly unitId: number;
@@ -226,8 +222,7 @@ export abstract class WarUnit<
if (!state) {
continue;
}
this.logActivatedSkill[skillName] =
(this.logActivatedSkill[skillName] ?? 0) + 1;
this.logActivatedSkill[skillName] = (this.logActivatedSkill[skillName] ?? 0) + 1;
}
this.activatedSkill = {};
}
@@ -237,8 +232,7 @@ export abstract class WarUnit<
}
public hasActivatedSkillOnLog(skillName: string): number {
return (this.logActivatedSkill[skillName] ?? 0) +
(this.hasActivatedSkill(skillName) ? 1 : 0);
return (this.logActivatedSkill[skillName] ?? 0) + (this.hasActivatedSkill(skillName) ? 1 : 0);
}
public getActivatedSkillLog(): Record<string, number> {
@@ -295,9 +289,7 @@ export abstract class WarUnit<
warPower *= getDexLog(myDex, opposeDex);
warPower *= this.getCrewType().getAttackCoef(oppose.getCrewType());
opposeWarPowerMultiply *= this.getCrewType().getDefenceCoef(
oppose.getCrewType()
);
opposeWarPowerMultiply *= this.getCrewType().getDefenceCoef(oppose.getCrewType());
this.warPower = warPower;
oppose.setWarPowerMultiply(opposeWarPowerMultiply);
@@ -394,7 +386,7 @@ export abstract class WarUnit<
// 장수 전투 유닛(legacy WarUnitGeneral 포팅).
export class WarUnitGeneral<
TriggerState extends GeneralTriggerState = GeneralTriggerState
TriggerState extends GeneralTriggerState = GeneralTriggerState,
> extends WarUnit<TriggerState> {
private readonly actionPipeline: WarActionPipeline<TriggerState>;
private killedPerson = 0;
@@ -470,8 +462,8 @@ export class WarUnitGeneral<
const baseTurnTime = this.isAttacker()
? getMetaString(this.general.meta, META_TURN_TIME)
: oppose instanceof WarUnitGeneral
? getMetaString(oppose.general.meta, META_TURN_TIME)
: getMetaString(this.general.meta, META_TURN_TIME);
? getMetaString(oppose.general.meta, META_TURN_TIME)
: getMetaString(this.general.meta, META_TURN_TIME);
if (!baseTurnTime) {
return;
}
@@ -486,36 +478,18 @@ export class WarUnitGeneral<
const base = this.getCrewType().speed;
const aux = { isAttacker: this.isAttacker() };
const context = this.getActionContext();
const phase = this.actionPipeline.onCalcStat(
context,
'initWarPhase',
base,
aux
);
const phase = this.actionPipeline.onCalcStat(context, 'initWarPhase', base, aux);
return phase + this.bonusPhase;
}
private resolveStatValue(statName: keyof StatBlock, base: number): number {
return this.actionPipeline.onCalcStat(
this.getActionContext(),
statName,
base
);
return this.actionPipeline.onCalcStat(this.getActionContext(), statName, base);
}
private resolveMainStat(armType: number): number {
const leadership = this.resolveStatValue(
'leadership',
this.general.stats.leadership
);
const strength = this.resolveStatValue(
'strength',
this.general.stats.strength
);
const intelligence = this.resolveStatValue(
'intelligence',
this.general.stats.intelligence
);
const leadership = this.resolveStatValue('leadership', this.general.stats.leadership);
const strength = this.resolveStatValue('strength', this.general.stats.strength);
const intelligence = this.resolveStatValue('intelligence', this.general.stats.intelligence);
if (armType === this.config.armTypes.wizard) {
return intelligence;
@@ -529,24 +503,18 @@ export class WarUnitGeneral<
return strength;
}
private resolveOpposeStatValue(
statName: WarStatName,
value: number,
aux?: Record<string, unknown>
): number {
private resolveOpposeStatValue(statName: WarStatName, value: number, aux?: Record<string, unknown>): number {
const oppose = this.getOppose();
if (!(oppose instanceof WarUnitGeneral)) {
return value;
}
return oppose
.getActionPipeline()
.onCalcOpposeStat(this.getActionContext(), statName, value, aux);
return oppose.getActionPipeline().onCalcOpposeStat(this.getActionContext(), statName, value, aux);
}
public override getDex(crewType: WarCrewType): number {
const armType =
crewType.armType === this.config.armTypes.castle
? this.config.armTypes.siege ?? crewType.armType
? (this.config.armTypes.siege ?? crewType.armType)
: crewType.armType;
const base = getMetaNumber(this.general.meta, `${META_DEX_PREFIX}${armType}`);
const aux = {
@@ -554,12 +522,7 @@ export class WarUnitGeneral<
opposeType: this.oppose?.getCrewType() ?? null,
};
const statName = toDexStatName(armType);
let dex = this.actionPipeline.onCalcStat(
this.getActionContext(),
statName,
base,
aux
);
let dex = this.actionPipeline.onCalcStat(this.getActionContext(), statName, base, aux);
dex = this.resolveOpposeStatValue(statName, dex, aux);
return dex;
}
@@ -588,12 +551,7 @@ export class WarUnitGeneral<
public override getComputedTrain(): number {
const aux = { isAttacker: this.isAttacker() };
let train = this.actionPipeline.onCalcStat(
this.getActionContext(),
'bonusTrain',
this.general.train,
aux
);
let train = this.actionPipeline.onCalcStat(this.getActionContext(), 'bonusTrain', this.general.train, aux);
train = this.resolveOpposeStatValue('bonusTrain', train, aux);
train += this.trainBonus;
return train;
@@ -601,12 +559,7 @@ export class WarUnitGeneral<
public override getComputedAtmos(): number {
const aux = { isAttacker: this.isAttacker() };
let atmos = this.actionPipeline.onCalcStat(
this.getActionContext(),
'bonusAtmos',
this.general.atmos,
aux
);
let atmos = this.actionPipeline.onCalcStat(this.getActionContext(), 'bonusAtmos', this.general.atmos, aux);
atmos = this.resolveOpposeStatValue('bonusAtmos', atmos, aux);
atmos += this.atmosBonus;
return atmos;
@@ -630,12 +583,7 @@ export class WarUnitGeneral<
ratio = Math.min(50, ratio) / 100;
const aux = { isAttacker: this.isAttacker() };
ratio = this.actionPipeline.onCalcStat(
this.getActionContext(),
'warCriticalRatio',
ratio,
aux
);
ratio = this.actionPipeline.onCalcStat(this.getActionContext(), 'warCriticalRatio', ratio, aux);
ratio = this.resolveOpposeStatValue('warCriticalRatio', ratio, aux);
return ratio;
}
@@ -646,18 +594,10 @@ export class WarUnitGeneral<
avoidRatio *= this.getComputedTrain() / 100;
const aux = { isAttacker: this.isAttacker() };
avoidRatio = this.actionPipeline.onCalcStat(
this.getActionContext(),
'warAvoidRatio',
avoidRatio,
aux
);
avoidRatio = this.actionPipeline.onCalcStat(this.getActionContext(), 'warAvoidRatio', avoidRatio, aux);
avoidRatio = this.resolveOpposeStatValue('warAvoidRatio', avoidRatio, aux);
if (
oppose &&
oppose.getCrewType().armType === this.config.armTypes.footman
) {
if (oppose && oppose.getCrewType().armType === this.config.armTypes.footman) {
avoidRatio *= 0.75;
}
@@ -665,19 +605,11 @@ export class WarUnitGeneral<
}
public override addTrain(train: number): void {
this.general.train = clamp(
this.general.train + train,
0,
this.config.maxTrainByWar
);
this.general.train = clamp(this.general.train + train, 0, this.config.maxTrainByWar);
}
public override addAtmos(atmos: number): void {
this.general.atmos = clamp(
this.general.atmos + atmos,
0,
this.config.maxAtmosByWar
);
this.general.atmos = clamp(this.general.atmos + atmos, 0, this.config.maxAtmosByWar);
}
public override addWin(): void {
@@ -688,11 +620,7 @@ export class WarUnitGeneral<
}
const atmosMultiplier = this.isAttacker() ? 1.1 : 1.05;
this.general.atmos = clamp(
Math.round(this.general.atmos * atmosMultiplier),
0,
this.config.maxAtmosByWar
);
this.general.atmos = clamp(Math.round(this.general.atmos * atmosMultiplier), 0, this.config.maxAtmosByWar);
this.addStatExp(1);
}
@@ -741,11 +669,7 @@ export class WarUnitGeneral<
}
rice *= this.getCrewType().rice;
rice *= getTechCost(resolveNationTech(this.nation));
rice = this.actionPipeline.onCalcStat(
this.getActionContext(),
'killRice',
rice
);
rice = this.actionPipeline.onCalcStat(this.getActionContext(), 'killRice', rice);
return rice;
}
@@ -815,11 +739,7 @@ export class WarUnitGeneral<
nextOpposeMultiply *= ratio;
}
const [myMul, opposeMul] = this.actionPipeline.getWarPowerMultiplier(
this.getActionContext(),
this,
oppose
);
const [myMul, opposeMul] = this.actionPipeline.getWarPowerMultiplier(this.getActionContext(), this, oppose);
nextWarPower *= myMul;
nextOpposeMultiply *= opposeMul;
@@ -830,11 +750,7 @@ export class WarUnitGeneral<
public override criticalDamage(): number {
let range: [number, number] = WAR_CRITICAL_RANGE;
range = this.actionPipeline.onCalcStat(
this.getActionContext(),
'criticalDamageRange',
range
);
range = this.actionPipeline.onCalcStat(this.getActionContext(), 'criticalDamageRange', range);
return this.rng.nextRange(...range);
}
@@ -850,11 +766,7 @@ export class WarUnitGeneral<
}
this.activateSkill('부상');
this.general.injury = clamp(
this.general.injury + this.rng.nextRangeInt(10, 80),
0,
80
);
this.general.injury = clamp(this.general.injury + this.rng.nextRangeInt(10, 80), 0, 80);
this.logger.pushGeneralActionLog('전투중 <R>부상</>당했다!', LogFormat.PLAIN);
return true;
}
@@ -880,18 +792,10 @@ export class WarUnitGeneral<
increaseMetaNumber(this.general.meta, RANK_DEATHCREW, this.dead);
if (this.killedPerson) {
increaseMetaNumber(
this.general.meta,
RANK_KILLCREW_PERSON,
this.killedPerson
);
increaseMetaNumber(this.general.meta, RANK_KILLCREW_PERSON, this.killedPerson);
}
if (this.deadPerson) {
increaseMetaNumber(
this.general.meta,
RANK_DEATHCREW_PERSON,
this.deadPerson
);
increaseMetaNumber(this.general.meta, RANK_DEATHCREW_PERSON, this.deadPerson);
}
this.general.rice = round(this.general.rice);
+13 -63
View File
@@ -1,76 +1,33 @@
import type { TriggerValue } from '@sammo-ts/logic/domain/entities.js';
const DEX_LEVEL_THRESHOLDS = [
0,
350,
1375,
3500,
7125,
12650,
20475,
31000,
44625,
61750,
82775,
108100,
138125,
173250,
213875,
260400,
313225,
372750,
439375,
513500,
595525,
685850,
784875,
893000,
1010625,
1138150,
1275975,
0, 350, 1375, 3500, 7125, 12650, 20475, 31000, 44625, 61750, 82775, 108100, 138125, 173250, 213875, 260400, 313225,
372750, 439375, 513500, 595525, 685850, 784875, 893000, 1010625, 1138150, 1275975,
];
export const clamp = (value: number, min: number, max: number): number =>
Math.max(min, Math.min(value, max));
export const clamp = (value: number, min: number, max: number): number => Math.max(min, Math.min(value, max));
export const clampMin = (value: number, min: number): number =>
value < min ? min : value;
export const clampMin = (value: number, min: number): number => (value < min ? min : value);
export const clampMax = (value: number, max: number): number =>
value > max ? max : value;
export const clampMax = (value: number, max: number): number => (value > max ? max : value);
export const round = (value: number): number => Math.round(value);
export const getMetaNumber = (
meta: Record<string, TriggerValue>,
key: string,
fallback = 0
): number => {
export const getMetaNumber = (meta: Record<string, TriggerValue>, key: string, fallback = 0): number => {
const value = meta[key];
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
};
export const getMetaString = (
meta: Record<string, TriggerValue>,
key: string
): string | null => {
export const getMetaString = (meta: Record<string, TriggerValue>, key: string): string | null => {
const value = meta[key];
return typeof value === 'string' ? value : null;
};
export const setMetaNumber = (
meta: Record<string, TriggerValue>,
key: string,
value: number
): void => {
export const setMetaNumber = (meta: Record<string, TriggerValue>, key: string, value: number): void => {
meta[key] = round(value);
};
export const increaseMetaNumber = (
meta: Record<string, TriggerValue>,
key: string,
delta: number
): number => {
export const increaseMetaNumber = (meta: Record<string, TriggerValue>, key: string, delta: number): number => {
const next = getMetaNumber(meta, key) + delta;
meta[key] = round(next);
return next;
@@ -92,12 +49,9 @@ export const getDexLevel = (dex: number): number => {
return level;
};
export const getDexLog = (dex1: number, dex2: number): number =>
(getDexLevel(dex1) - getDexLevel(dex2)) / 55 + 1;
export const getDexLog = (dex1: number, dex2: number): number => (getDexLevel(dex1) - getDexLevel(dex2)) / 55 + 1;
export const parseConflict = (
raw: TriggerValue | undefined
): Record<number, number> | null => {
export const parseConflict = (raw: TriggerValue | undefined): Record<number, number> | null => {
if (raw === undefined || raw === null) {
return null;
}
@@ -120,9 +74,7 @@ export const parseConflict = (
return null;
};
export const stringifyConflict = (
conflict: Record<number, number> | null
): string => {
export const stringifyConflict = (conflict: Record<number, number> | null): string => {
if (!conflict) {
return '{}';
}
@@ -139,9 +91,7 @@ export const stringifyConflict = (
return JSON.stringify(ordered);
};
export const sortConflict = (
conflict: Record<number, number>
): Record<number, number> => {
export const sortConflict = (conflict: Record<number, number>): Record<number, number> => {
const ordered: Record<number, number> = {};
const entries = Object.entries(conflict)
.map(([key, value]) => [Number(key), value] as const)