fix: 전투 이펙트와 차등 하네스 호환성을 복원한다
Ref 기준의 판정 순서, 난수 소비, 아이템·특기 이펙트와 로그 형식을 맞춘다. 전체 전투 결과와 이벤트·상태·난수 trace를 엄격 비교하고 fixture 및 출병 예약 회귀를 보강한다.
This commit is contained in:
@@ -21,7 +21,7 @@ import { compileCrewTypeCatalog } from '../crewType/index.js';
|
||||
import type { City, General, Nation } from '../domain/entities.js';
|
||||
import { createInheritBuffModules } from '../inheritance/inheritBuff.js';
|
||||
import { createItemActionModules, createItemModuleRegistry, ITEM_KEYS, loadItemModules } from '../items/index.js';
|
||||
import { formatLogText, LogCategory, LogFormat, LogScope } from '../logging/index.js';
|
||||
import { formatLogText, LogCategory, LogFormat, LogScope, type ActionLogger } from '../logging/index.js';
|
||||
import {
|
||||
createCrewTypeWarTriggerRegistry,
|
||||
resolveDefenderOrder,
|
||||
@@ -344,6 +344,10 @@ const resolveDefenderOrderPayload = (payload: BattleSimJobPayload): number[] =>
|
||||
export interface BattleSimProcessorOptions {
|
||||
trace?: (event: WarBattleTraceEvent) => void;
|
||||
rngFactory?: (seed: string) => RandUtil;
|
||||
/** Comparison-only logger instrumentation; production callers omit it. */
|
||||
loggerFactory?: (options: { generalId?: number; nationId?: number }) => ActionLogger;
|
||||
/** Comparison-only observation of the resolved pure battle outcome. */
|
||||
onBattleResolved?: (outcome: WarBattleOutcome) => void;
|
||||
}
|
||||
|
||||
export const processBattleSimJob = (
|
||||
@@ -416,9 +420,12 @@ export const processBattleSimJob = (
|
||||
})),
|
||||
defenderCity,
|
||||
defenderNation,
|
||||
...(options.loggerFactory ? { loggerFactory: options.loggerFactory } : {}),
|
||||
...(options.trace ? { trace: options.trace } : {}),
|
||||
});
|
||||
|
||||
options.onBattleResolved?.(outcome);
|
||||
|
||||
lastBattle = outcome;
|
||||
const attackerReport = outcome.reports.find(
|
||||
(report: WarUnitReport) => report.type === 'general' && report.isAttacker
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import type { GeneralActionContext } from '@sammo-ts/logic/triggers/general.js';
|
||||
import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/actionModules/types.js';
|
||||
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
|
||||
import { WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import { che_부적 } from '@sammo-ts/logic/war/triggers/che_부적.js';
|
||||
import { che_부상무효 } from '@sammo-ts/logic/war/triggers/che_견고.js';
|
||||
import type { ItemModule } from './types.js';
|
||||
|
||||
const ITEM_KEY = 'che_부적_태현청생부';
|
||||
const RAISE_TYPE = BaseWarUnitTrigger.TYPE_ITEM + BaseWarUnitTrigger.TYPE_DEDUP_TYPE_BASE * 303;
|
||||
|
||||
export const itemModule: ItemModule = {
|
||||
key: ITEM_KEY,
|
||||
@@ -28,8 +30,12 @@ export const itemModule: ItemModule = {
|
||||
}
|
||||
return value;
|
||||
} as NonNullable<ItemModule['onCalcStat']>,
|
||||
getBattleInitTriggerList: (context) => {
|
||||
if (!context.unit) return null;
|
||||
return new WarTriggerCaller(new che_부상무효(context.unit, RAISE_TYPE), new che_부적(context.unit, RAISE_TYPE));
|
||||
},
|
||||
getBattlePhaseTriggerList: (context) => {
|
||||
if (!context.unit) return null;
|
||||
return new WarTriggerCaller(new che_부적(context.unit));
|
||||
return new WarTriggerCaller(new che_부적(context.unit, RAISE_TYPE));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { che_진압 } from '@sammo-ts/logic/war/triggers/che_진압.js';
|
||||
|
||||
import { WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import type { ItemModule } from './types.js';
|
||||
|
||||
const ITEM_KEY = 'che_진압_박혁론';
|
||||
const RAISE_TYPE = BaseWarUnitTrigger.TYPE_NONE;
|
||||
|
||||
export const itemModule: ItemModule = {
|
||||
key: ITEM_KEY,
|
||||
@@ -18,6 +19,6 @@ export const itemModule: ItemModule = {
|
||||
unique: false,
|
||||
getBattlePhaseTriggerList: (context) => {
|
||||
if (!context.unit) return null;
|
||||
return new WarTriggerCaller(new che_진압(context.unit));
|
||||
return new WarTriggerCaller(new che_진압(context.unit, RAISE_TYPE));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { GeneralTriggerCaller } from '@sammo-ts/logic/triggers/general.js';
|
||||
import { CheUisulCityHealTrigger } from '@sammo-ts/logic/triggers/generalTriggers/che_도시치료.js';
|
||||
import { triggerModule as medicalWarTriggerModule } from '@sammo-ts/logic/war/triggers/che_의술.js';
|
||||
import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import { che_의술발동, che_의술시도 } from '@sammo-ts/logic/war/triggers/che_의술.js';
|
||||
import type { ItemModule } from './types.js';
|
||||
|
||||
const INFO =
|
||||
'[군사] 매 턴마다 자신(100%)과 소속 도시 장수(적 포함 50%) 부상 회복<br>[전투] 페이즈마다 40% 확률로 치료 발동(아군 피해 30% 감소, 부상 회복)';
|
||||
|
||||
const ATTEMPT_DEDUP_TYPE: Record<string, number> = {
|
||||
che_의술_상한잡병론: 301,
|
||||
che_의술_정력견혈산: 302,
|
||||
che_의술_청낭서: 302,
|
||||
che_의술_태평청령: 303,
|
||||
};
|
||||
|
||||
export const createMedicalItem = (key: string, rawName: string): ItemModule => ({
|
||||
key,
|
||||
rawName,
|
||||
@@ -18,6 +26,15 @@ export const createMedicalItem = (key: string, rawName: string): ItemModule => (
|
||||
reqSecu: 0,
|
||||
unique: true,
|
||||
getPreTurnExecuteTriggerList: (context) => new GeneralTriggerCaller(new CheUisulCityHealTrigger(context.general)),
|
||||
getBattlePhaseTriggerList: (context) =>
|
||||
context.unit ? medicalWarTriggerModule.createTriggerList(context.unit) : null,
|
||||
getBattlePhaseTriggerList: (context) => {
|
||||
if (!context.unit) {
|
||||
return null;
|
||||
}
|
||||
const attemptRaiseType =
|
||||
BaseWarUnitTrigger.TYPE_ITEM + BaseWarUnitTrigger.TYPE_DEDUP_TYPE_BASE * (ATTEMPT_DEDUP_TYPE[key] ?? 0);
|
||||
return new WarTriggerCaller(
|
||||
new che_의술시도(context.unit, attemptRaiseType),
|
||||
new che_의술발동(context.unit, BaseWarUnitTrigger.TYPE_ITEM)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { TraitModule } from '@sammo-ts/logic/actionModules/traits/types.js';
|
||||
import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import { che_의술발동, che_의술시도 } from '@sammo-ts/logic/war/triggers/che_의술.js';
|
||||
import { che_저격발동, che_저격시도 } from '@sammo-ts/logic/war/triggers/che_저격.js';
|
||||
import type { ItemModule } from './types.js';
|
||||
|
||||
export const createEventBattleTraitItemModule = (
|
||||
@@ -45,7 +48,25 @@ export const createEventBattleTraitItemModule = (
|
||||
itemModule.getBattleInitTriggerList = traitModule.getBattleInitTriggerList;
|
||||
}
|
||||
if (traitModule.getBattlePhaseTriggerList) {
|
||||
itemModule.getBattlePhaseTriggerList = traitModule.getBattlePhaseTriggerList;
|
||||
if (traitModule.key === 'che_저격') {
|
||||
itemModule.getBattlePhaseTriggerList = (context) =>
|
||||
context.unit
|
||||
? new WarTriggerCaller(
|
||||
new che_저격시도(context.unit, BaseWarUnitTrigger.TYPE_ITEM, 0.5, 20, 40),
|
||||
new che_저격발동(context.unit, BaseWarUnitTrigger.TYPE_ITEM)
|
||||
)
|
||||
: null;
|
||||
} else if (traitModule.key === 'che_의술') {
|
||||
itemModule.getBattlePhaseTriggerList = (context) =>
|
||||
context.unit
|
||||
? new WarTriggerCaller(
|
||||
new che_의술시도(context.unit, BaseWarUnitTrigger.TYPE_ITEM),
|
||||
new che_의술발동(context.unit)
|
||||
)
|
||||
: null;
|
||||
} else {
|
||||
itemModule.getBattlePhaseTriggerList = traitModule.getBattlePhaseTriggerList;
|
||||
}
|
||||
}
|
||||
if (traitModule.getWarPowerMultiplier) {
|
||||
itemModule.getWarPowerMultiplier =
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TriggerPriority } from '@sammo-ts/logic/triggers/core.js';
|
||||
import { consumeEquippedItemCharge, getEquippedItemInstance } from './inventory.js';
|
||||
import { getEquippedItemInstance } from './inventory.js';
|
||||
import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import { WarUnitCity, WarUnitGeneral, type WarUnit } from '@sammo-ts/logic/war/units.js';
|
||||
import type { ItemModule } from './types.js';
|
||||
@@ -17,6 +17,15 @@ class EventRamConsumptionTrigger extends BaseWarUnitTrigger {
|
||||
_selfEnv: Record<string, unknown>,
|
||||
_opposeEnv: Record<string, unknown>
|
||||
): boolean {
|
||||
if (self.hasActivatedSkillOnLog('충차공격') > 0 && self.getPhase() === self.getMaxPhase() - 1) {
|
||||
if (self instanceof WarUnitGeneral) {
|
||||
const equipped = getEquippedItemInstance(self.getGeneral(), 'item');
|
||||
if (equipped?.itemKey === ITEM_KEY && (equipped.state.charges ?? 0) <= 0) {
|
||||
this.processConsumableItem();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!(self instanceof WarUnitGeneral) || !(oppose instanceof WarUnitCity)) {
|
||||
return true;
|
||||
}
|
||||
@@ -28,9 +37,15 @@ class EventRamConsumptionTrigger extends BaseWarUnitTrigger {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.activateSkill('충차공격', '아이템사용');
|
||||
self.getLogger().pushGeneralBattleDetailLog('<C>충차</>로 성벽을 공격합니다.');
|
||||
consumeEquippedItemCharge(general, 'item', ITEM_KEY, 2);
|
||||
self.activateSkill('충차공격');
|
||||
const equipped = getEquippedItemInstance(general, 'item');
|
||||
if (equipped?.itemKey === ITEM_KEY) {
|
||||
const remaining = equipped.state.charges ?? 2;
|
||||
// Ref decrements the purchase-time remain값 at first city contact,
|
||||
// but only deletes the item in the last battle phase.
|
||||
equipped.state.charges = remaining - 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import { WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
|
||||
import type { WarUnit } from '@sammo-ts/logic/war/units.js';
|
||||
import type { ItemModule, ItemModuleExport } from './types.js';
|
||||
import { listEquippedItemKeys } from './utils.js';
|
||||
import { removeEquippedItem } from './inventory.js';
|
||||
import { registerLegacyBattleItemIdentity, removeEquippedItem } from './inventory.js';
|
||||
|
||||
export const ITEM_KEYS = [
|
||||
'che_간파_노군입산부',
|
||||
@@ -664,12 +664,17 @@ class ItemWarActionRouter<
|
||||
private resolveModules(context: WarActionContext<TriggerState>): Array<ItemModule<TriggerState>> {
|
||||
const keys = listEquippedItemKeys(context.general);
|
||||
const modules: Array<ItemModule<TriggerState>> = [];
|
||||
let itemIdentity = { name: '-', rawName: '-' };
|
||||
for (const key of keys) {
|
||||
const module = this.registry.get(key);
|
||||
if (module) {
|
||||
modules.push(module);
|
||||
if (module.slot === 'item') {
|
||||
itemIdentity = { name: module.name, rawName: module.rawName };
|
||||
}
|
||||
}
|
||||
}
|
||||
registerLegacyBattleItemIdentity(context.general, itemIdentity);
|
||||
return modules;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,27 @@ import type {
|
||||
const ITEM_SLOTS: GeneralItemSlot[] = ['horse', 'weapon', 'book', 'item'];
|
||||
const INVENTORY_META_KEY = 'itemInventory';
|
||||
|
||||
export interface LegacyBattleItemIdentity {
|
||||
name: string;
|
||||
rawName: string;
|
||||
}
|
||||
|
||||
// Ref's BaseWarUnitTrigger::processConsumableItem() asks General::getItem()
|
||||
// for the *item-slot* display name even when a weapon trigger raised the
|
||||
// shared item flag. Keep that transient lookup out of persisted General meta.
|
||||
const legacyBattleItemIdentities = new WeakMap<object, LegacyBattleItemIdentity>();
|
||||
|
||||
export const registerLegacyBattleItemIdentity = <TriggerState extends GeneralTriggerState>(
|
||||
general: General<TriggerState>,
|
||||
identity: LegacyBattleItemIdentity
|
||||
): void => {
|
||||
legacyBattleItemIdentities.set(general, identity);
|
||||
};
|
||||
|
||||
export const getLegacyBattleItemIdentity = <TriggerState extends GeneralTriggerState>(
|
||||
general: General<TriggerState>
|
||||
): LegacyBattleItemIdentity => legacyBattleItemIdentities.get(general) ?? { name: '-', rawName: '-' };
|
||||
|
||||
const emptyState = (): GeneralItemInstanceState => ({ values: {} });
|
||||
|
||||
const cloneState = (state: GeneralItemInstanceState): GeneralItemInstanceState => ({
|
||||
@@ -57,9 +78,7 @@ const readState = (value: unknown): GeneralItemInstanceState | null => {
|
||||
return null;
|
||||
}
|
||||
const charges =
|
||||
typeof record['charges'] === 'number' && Number.isInteger(record['charges']) && record['charges'] >= 0
|
||||
? record['charges']
|
||||
: undefined;
|
||||
typeof record['charges'] === 'number' && Number.isInteger(record['charges']) ? record['charges'] : undefined;
|
||||
const valuesRecord = asRecord(record['values']) ?? {};
|
||||
const values: Record<string, TriggerValue> = {};
|
||||
for (const [key, entry] of Object.entries(valuesRecord)) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { JosaUtil, LiteHashDRBG, RandUtil } from '@sammo-ts/common';
|
||||
|
||||
import type { City, GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js';
|
||||
import type { City, General, GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js';
|
||||
import { compileCrewTypeCatalog, isCrewTypeWarActionRouter } from '@sammo-ts/logic/crewType/catalog.js';
|
||||
import { ActionLogger } from '@sammo-ts/logic/logging/actionLogger.js';
|
||||
import { LogFormat, type LogEntryDraft } from '@sammo-ts/logic/logging/types.js';
|
||||
@@ -110,6 +110,17 @@ const isSupplyCity = (city: City): boolean => {
|
||||
return city.supplyState > 0;
|
||||
};
|
||||
|
||||
const resolveLegacyTurnHourMinute = (general: General): string => {
|
||||
const raw = general.meta['turnTime'];
|
||||
if (typeof raw === 'string' && raw.length >= 16) {
|
||||
return raw.slice(11, 16);
|
||||
}
|
||||
if (general.turnTime instanceof Date && Number.isFinite(general.turnTime.getTime())) {
|
||||
return general.turnTime.toISOString().slice(11, 16);
|
||||
}
|
||||
return '00:00';
|
||||
};
|
||||
|
||||
export const computeBattleOrder = <TriggerState extends GeneralTriggerState>(
|
||||
defender: WarUnit<TriggerState>,
|
||||
attacker: WarUnitGeneral<TriggerState>
|
||||
@@ -390,7 +401,8 @@ export const resolveWarBattle = <TriggerState extends GeneralTriggerState = Gene
|
||||
const attackerNationName = (attackerUnit.getNationVar('name') as string | null) ?? 'UNKNOWN';
|
||||
const attackerName = attackerUnit.getName();
|
||||
const cityName = cityUnit.getName();
|
||||
const seedText = input.seed ? `<span class="hidden_but_copyable">(전투시드: ${input.seed})</span>` : '';
|
||||
const seedText = input.seed ? `<span class='hidden_but_copyable'>(전투시드: ${input.seed})</span>` : '';
|
||||
const turnHourMinute = resolveLegacyTurnHourMinute(attackerUnit.getGeneral());
|
||||
|
||||
const josaRo = JosaUtil.pick(cityName, '로');
|
||||
const josaYi = JosaUtil.pick(attackerName, '이');
|
||||
@@ -400,7 +412,7 @@ export const resolveWarBattle = <TriggerState extends GeneralTriggerState = Gene
|
||||
LogFormat.MONTH
|
||||
);
|
||||
attackerLogger.pushGeneralActionLog(
|
||||
`<G><b>${cityName}</b></>${josaRo} <M>진격</>합니다.${seedText}`,
|
||||
`<G><b>${cityName}</b></>${josaRo} <M>진격</>합니다.${seedText} <1>${turnHourMinute}</>`,
|
||||
LogFormat.MONTH
|
||||
);
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { RandUtil } from '@sammo-ts/common';
|
||||
import { JosaUtil, type RandUtil } from '@sammo-ts/common';
|
||||
|
||||
import type { General } from '@sammo-ts/logic/domain/entities.js';
|
||||
import { getLegacyBattleItemIdentity, removeEquippedItem } from '@sammo-ts/logic/items/inventory.js';
|
||||
import { LogFormat } from '@sammo-ts/logic/logging/types.js';
|
||||
import { TriggerCaller, type Trigger } from '@sammo-ts/logic/triggers/core.js';
|
||||
import type { WarUnit } from './units.js';
|
||||
import { removeEquippedItem } from '@sammo-ts/logic/items/inventory.js';
|
||||
|
||||
export interface WarTriggerContext {
|
||||
rng: RandUtil;
|
||||
@@ -97,12 +98,6 @@ export abstract class BaseWarUnitTrigger implements WarTrigger {
|
||||
return false;
|
||||
}
|
||||
this.unit.activateSkill('아이템사용');
|
||||
if (this.raiseType !== BaseWarUnitTrigger.TYPE_CONSUMABLE_ITEM) {
|
||||
return false;
|
||||
}
|
||||
if (this.unit.hasActivatedSkill('아이템소모')) {
|
||||
return false;
|
||||
}
|
||||
const unit = this.unit as WarUnit & {
|
||||
getGeneral?: () => General;
|
||||
};
|
||||
@@ -110,7 +105,18 @@ export abstract class BaseWarUnitTrigger implements WarTrigger {
|
||||
if (!general) {
|
||||
return false;
|
||||
}
|
||||
const item = getLegacyBattleItemIdentity(general);
|
||||
this.unit.activateSkill(item.name);
|
||||
if (this.raiseType !== BaseWarUnitTrigger.TYPE_CONSUMABLE_ITEM) {
|
||||
return false;
|
||||
}
|
||||
if (this.unit.hasActivatedSkill('아이템소모')) {
|
||||
return false;
|
||||
}
|
||||
this.unit.activateSkill('아이템소모');
|
||||
return removeEquippedItem(general, 'item') !== null;
|
||||
const josaUl = JosaUtil.pick(item.rawName, '을');
|
||||
this.unit.getLogger().pushGeneralActionLog(`<C>${item.name}</>${josaUl} 사용!`, LogFormat.PLAIN);
|
||||
removeEquippedItem(general, 'item');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { TriggerPriority } from '@sammo-ts/logic/triggers/core.js';
|
||||
import { BaseWarUnitTrigger } from '../triggers.js';
|
||||
import type { WarUnit } from '../units.js';
|
||||
|
||||
export class che_부적 extends BaseWarUnitTrigger {
|
||||
constructor(unit: WarUnit, raiseType: number = 0) {
|
||||
super(unit, 0, raiseType);
|
||||
super(unit, TriggerPriority.Begin, raiseType);
|
||||
}
|
||||
|
||||
protected actionWar(self: WarUnit): boolean {
|
||||
self.activateSkill('저격불가', '부상무효');
|
||||
protected actionWar(_self: WarUnit, oppose: WarUnit): boolean {
|
||||
// Ref WarActivateSkills(..., isSelf=false): the talisman's owner is
|
||||
// injury-proof, while the opposing unit is prevented from sniping.
|
||||
oppose.activateSkill('저격불가');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import type { WarTriggerModule } from './types.js';
|
||||
|
||||
// 의술: 치료 시도
|
||||
export class che_의술시도 extends BaseWarUnitTrigger {
|
||||
constructor(unit: WarUnit) {
|
||||
super(unit, TriggerPriority.Pre + 350);
|
||||
constructor(unit: WarUnit, raiseType = BaseWarUnitTrigger.TYPE_NONE) {
|
||||
super(unit, TriggerPriority.Pre + 350, raiseType);
|
||||
}
|
||||
|
||||
protected actionWar(
|
||||
@@ -36,8 +36,8 @@ export class che_의술시도 extends BaseWarUnitTrigger {
|
||||
|
||||
// 의술: 치료 발동
|
||||
export class che_의술발동 extends BaseWarUnitTrigger {
|
||||
constructor(unit: WarUnit) {
|
||||
super(unit, TriggerPriority.Post + 550);
|
||||
constructor(unit: WarUnit, raiseType = BaseWarUnitTrigger.TYPE_NONE) {
|
||||
super(unit, TriggerPriority.Post + 550, raiseType);
|
||||
}
|
||||
|
||||
protected actionWar(
|
||||
|
||||
@@ -58,7 +58,9 @@ export class che_저지 extends BaseWarUnitTrigger {
|
||||
self.addDex(self.getCrewType(), calcDamage);
|
||||
|
||||
self.addLevelExp(calcDamage / 50);
|
||||
let rice = self.calcRiceConsumption(calcDamage);
|
||||
// Ref calcRiceConsumption() declares an int parameter, so the
|
||||
// fractional 90% counter-damage is truncated before rice cost.
|
||||
let rice = self.calcRiceConsumption(Math.trunc(calcDamage));
|
||||
rice *= 0.25;
|
||||
const general = self.getGeneral();
|
||||
general.rice = Math.max(0, general.rice - rice);
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { TriggerPriority } from '@sammo-ts/logic/triggers/core.js';
|
||||
import { BaseWarUnitTrigger } from '../triggers.js';
|
||||
import type { WarUnit } from '../units.js';
|
||||
|
||||
export class che_진압 extends BaseWarUnitTrigger {
|
||||
constructor(unit: WarUnit, raiseType: number = 0) {
|
||||
super(unit, 0, raiseType);
|
||||
super(unit, TriggerPriority.Begin, raiseType);
|
||||
}
|
||||
|
||||
protected actionWar(self: WarUnit): boolean {
|
||||
self.activateSkill('반계불가', '격노불가');
|
||||
protected actionWar(_self: WarUnit, oppose: WarUnit): boolean {
|
||||
// Ref's 진압 is an opposing-unit restriction, not a self debuff.
|
||||
oppose.activateSkill('반계불가', '격노불가');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,10 +192,16 @@ export class WarUnitGeneral<
|
||||
return truncate ? Math.trunc(clamped) : clamped;
|
||||
}
|
||||
|
||||
private resolveMainStat(armType: number, withInjury = true): number {
|
||||
const leadership = this.getComputedStat('leadership', this.general.stats.leadership, { withInjury });
|
||||
const strength = this.getComputedStat('strength', this.general.stats.strength, { withInjury });
|
||||
const intelligence = this.getComputedStat('intelligence', this.general.stats.intelligence, { withInjury });
|
||||
private resolveMainStat(armType: number, withInjury = true, truncate = true): number {
|
||||
const leadership = this.getComputedStat('leadership', this.general.stats.leadership, {
|
||||
withInjury,
|
||||
truncate,
|
||||
});
|
||||
const strength = this.getComputedStat('strength', this.general.stats.strength, { withInjury, truncate });
|
||||
const intelligence = this.getComputedStat('intelligence', this.general.stats.intelligence, {
|
||||
withInjury,
|
||||
truncate,
|
||||
});
|
||||
|
||||
if (armType === this.config.armTypes.wizard) {
|
||||
return intelligence;
|
||||
@@ -279,7 +285,10 @@ export class WarUnitGeneral<
|
||||
return 0;
|
||||
}
|
||||
|
||||
const mainStat = this.resolveMainStat(armType, false);
|
||||
// GameUnitDetail::getCriticalRatio requests each Ref stat with
|
||||
// useFloor=false, so action bonuses such as 징병's +25% leadership must
|
||||
// retain their fractional part until after the probability is formed.
|
||||
const mainStat = this.resolveMainStat(armType, false, false);
|
||||
const coef =
|
||||
armType === this.config.armTypes.wizard ||
|
||||
armType === this.config.armTypes.siege ||
|
||||
|
||||
Reference in New Issue
Block a user