Files
core2026/packages/logic/src/war/triggers.ts
T

117 lines
3.8 KiB
TypeScript

import type { RandUtil } from '@sammo-ts/common';
import type { General } from '@sammo-ts/logic/domain/entities.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;
attacker: WarUnit;
defender: WarUnit;
}
export interface WarTriggerEnv {
e_attacker: Record<string, unknown>;
e_defender: Record<string, unknown>;
stopNextAction?: boolean;
}
export type WarTrigger = Trigger<WarTriggerContext, WarTriggerEnv>;
export const createWarTriggerEnv = (): WarTriggerEnv => ({
e_attacker: {},
e_defender: {},
});
export class WarTriggerCaller extends TriggerCaller<WarTriggerContext, WarTriggerEnv> {}
export type WarTriggerFactory = (unit: WarUnit) => WarTrigger | WarTriggerCaller | null;
export type WarTriggerRegistry = Record<string, WarTriggerFactory>;
// 전투 트리거 공통 베이스. 전투 유닛 기준으로 동작하며, env는 공격/수비 분리 상태를 유지한다.
export abstract class BaseWarUnitTrigger implements WarTrigger {
public static readonly TYPE_NONE = 0;
public static readonly TYPE_ITEM = 1;
public static readonly TYPE_CONSUMABLE_ITEM = 1 | 2;
public static readonly TYPE_DEDUP_TYPE_BASE = 1024;
protected readonly raiseType: number;
protected constructor(
protected readonly unit: WarUnit,
public readonly priority: number,
raiseType = BaseWarUnitTrigger.TYPE_NONE
) {
this.raiseType = raiseType;
}
get uniqueId(): string {
return `${this.priority}_${this.constructor.name}_${this.unit.getUnitId()}_${this.raiseType}`;
}
action(context: WarTriggerContext, env: WarTriggerEnv): WarTriggerEnv {
const nextEnv: WarTriggerEnv = {
e_attacker: env.e_attacker ?? {},
e_defender: env.e_defender ?? {},
...(env.stopNextAction ? { stopNextAction: true } : {}),
};
if (nextEnv.stopNextAction) {
return nextEnv;
}
const self = this.unit;
const isAttacker = self.isAttacker();
const oppose = isAttacker ? context.defender : context.attacker;
const selfEnv = isAttacker ? nextEnv.e_attacker : nextEnv.e_defender;
const opposeEnv = isAttacker ? nextEnv.e_defender : nextEnv.e_attacker;
const shouldContinue = this.actionWar(self, oppose, selfEnv, opposeEnv);
nextEnv.e_attacker = isAttacker ? selfEnv : opposeEnv;
nextEnv.e_defender = isAttacker ? opposeEnv : selfEnv;
if (!shouldContinue) {
nextEnv.stopNextAction = true;
}
return nextEnv;
}
// 실제 트리거 구현부는 여기서 처리한다.
protected abstract actionWar(
self: WarUnit,
oppose: WarUnit,
selfEnv: Record<string, unknown>,
opposeEnv: Record<string, unknown>
): boolean;
public processConsumableItem(): boolean {
if (!(this.raiseType & BaseWarUnitTrigger.TYPE_ITEM)) {
return false;
}
if (this.unit.hasActivatedSkill('아이템사용')) {
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;
};
const general = unit.getGeneral?.();
if (!general) {
return false;
}
this.unit.activateSkill('아이템소모');
return removeEquippedItem(general, 'item') !== null;
}
}