import { EcsComponent } from 'db://assets/mx/module/ecs'; import type { EntityId } from 'db://assets/mx/module/ecs'; import type { Cell } from '../battlefield/Cell'; import type { EffectSpec } from '../config/BattleConfigTypes'; import type { MembershipSet } from '../../../tools/MembershipSet'; /** 位置组件(格子坐标) */ export class TransformComp extends EcsComponent { static readonly ecsTypeId = 'mx.TransformComp'; constructor(public row = 0, public col = 0) { super(); } } /** 阵营组件 */ export class TeamComp extends EcsComponent { static readonly ecsTypeId = 'mx.TeamComp'; constructor(public team: number, public ownerId: number) { super(); } } /** 生命组件 */ export class HealthComp extends EcsComponent { static readonly ecsTypeId = 'mx.HealthComp'; constructor(public hp: number, public maxHp: number) { super(); } } /** 战斗属性组件 */ export class CombatComp extends EcsComponent { static readonly ecsTypeId = 'mx.CombatComp'; constructor( public attack: number, public attackSpeed: number, /** 攻击射程(像素,与配表 base_atk.range 一致;-1 全屏在写入前已展开) */ public range: number, public critRate: number, public critDamage: number, /** 攻速上限(次/秒);aspd_add 加成后仍受此限。0=不限 */ public maxAttackSpeed: number = 0, ) { super(); } } /** 技能集组件 */ export class AbilitySetComp extends EcsComponent { static readonly ecsTypeId = 'mx.AbilitySetComp'; constructor( public abilityIds: string[], public cooldowns: Record = {}, ) { super(); } } /** 占地组件 */ export class GridOccupancyComp extends EcsComponent { static readonly ecsTypeId = 'mx.GridOccupancyComp'; constructor( public anchor: Cell, public cells: Cell[], public blocksPath: boolean, ) { super(); } } /** 标签组件 */ export class TagComp extends EcsComponent { static readonly ecsTypeId = 'mx.TagComp'; constructor(public tags: string[] = []) { super(); } } /** 单个 Buff 实例 */ export interface BuffInstance { /** Buff 配置 ID */ buffId: string; /** 当前层数 */ stacks: number; /** 剩余时间(秒);次数型 Buff 为 Infinity */ remain: number; /** 施加者实体 ID */ caster: EntityId; /** 剩余生效次数(次数型 Buff 专用;undefined 表示非次数型) */ charges?: number; /** tick 累计时间 */ tickAccum?: number; } /** Buff 容器组件 */ export class BuffContainerComp extends EcsComponent { static readonly ecsTypeId = 'mx.BuffContainerComp'; constructor(public buffs: BuffInstance[] = []) { super(); } /** MovementSystem O(1):是否定身(眩晕 / 缚仙网 / 三昧真火 / 冰冻,移速归零) */ hasDebuff4 = false; /** buff 列表变更后同步定身标记 */ refreshDebuff4Flag(): void { for (let i = 0; i < this.buffs.length; i++) { const id = this.buffs[i].buffId; if (id === 'debuff_4' || id === 'debuff_bind_net' || id === 'debuff_fire_trap' || id === 'debuff_13') { this.hasDebuff4 = true; return; } } this.hasDebuff4 = false; } } /** 单位元数据组件 */ export class UnitMetaComp extends EcsComponent { static readonly ecsTypeId = 'mx.UnitMetaComp'; constructor( public unitDefId: string, public level = 1, /** 出生口(WaveSpawner 出怪口编号,复活回队用) */ public spawnPoint = 0, ) { super(); } /** * 空投飞禽等:芭蕉扇后退/复活回收目标格(逻辑格)。 * homeRow < 0 表示未设置,仍走出怪口。 */ public homeRow = -1; public homeCol = -1; /** 非空时表现层优先用此 Spine 皮肤;空串走默认阵营皮 */ public spineSkinOverride = ''; /** 从当前格抛物线跳到 path[0],不走出怪口、不沿途平移 */ public spawnJumpToPath = false; /** * 脚下用发光阴影(技能召唤等非基地波次)。 * false:基地出怪口波次怪用普通阴影。 */ public glowShadow = false; } /** 导航/移动组件(路径由 Pathfinder 规划,位移由 MovementSystem 驱动) */ export class MovementComp extends EcsComponent { static readonly ecsTypeId = 'mx.MovementComp'; /** 配表基础移速(不受减速/晕眩影响) */ public baseSpeed: number; /** 攻击目标世界坐标(逻辑像素,与 GridMap.cellToWorld 同系) */ public goalWorldX = 0; public goalWorldY = 0; /** 是否已绑定攻击目标世界坐标(用于距离停步开战) */ public hasAttackGoal = false; /** 出怪表现未完成时锁定逻辑位移(由 Presenter 解锁) */ public presentLocked = false; /** * 出生跳出保护:不可被索敌、不可被攻击命中。 * 跳出完成后再延时解除(由 Presenter 控制)。 */ public spawnProtected = false; /** * 逻辑坐标被权威改写(击退等)后,要求表现层即使 reachedGoal 也同步一次。 * 由 MovementBridge 消费并清零。 */ public forcePresentSync = false; /** * >0 时强制沿当前 path 以该速度移动(忽略攻击停步与移速同步)。 * 芭蕉扇推回等;路径走完后由 MovementSystem 清零并重寻路。 */ public forcePathSpeed = 0; /** * 强制路径结束时不重寻路,保持原地(presentLocked),供芭蕉扇「退到出怪口再隐藏入队」。 */ public forcePathHoldAtEnd = false; /** 芭蕉扇吹退中:暂时不可被索敌/命中 */ public bajiaoBlowRetreating = false; /** 阎王拘魂已入笼:脱离地图;伸爪阶段见 imprisonBy */ public imprisoned = false; /** 囚禁锚点世界坐标(逻辑像素) */ public imprisonAnchorX = 0; public imprisonAnchorY = 0; /** 囚禁锚点参考格(牢笼所在字块格,与 imprisonAnchor 成对) */ public imprisonAnchorRow = 0; public imprisonAnchorCol = 0; /** 囚禁时怪物节点 Cocos worldPosition(腰点对齐牢笼锚点,非格心) */ public imprisonCocosAnchorX = 0; public imprisonCocosAnchorY = 0; /** 囚禁施法者实体;伸爪瞬间即设置,拖入动画期间亦归属阎王 */ public imprisonBy: EntityId | null = null; /** 沿当前 path 到攻击点的剩余逻辑像素;无路径为 Infinity,由 MovementSystem 维护 */ public remainPathPx = Number.POSITIVE_INFINITY; constructor( public speed: number, public path: Cell[] = [], public pathIndex = 0, public worldX = 0, public worldY = 0, public reachedGoal = false, ) { super(); this.baseSpeed = speed; } } /** 阎王伸爪拖入中或已囚禁:归属阎王,他人不可选/不可打 */ export function isYanSoulClaimed(move: MovementComp | undefined | null): boolean { return move?.imprisonBy != null; } /** 芭蕉扇吹退中:暂时不可被索敌/命中 */ export function isBajiaoBlowRetreating(move: MovementComp | undefined | null): boolean { return !!move?.bajiaoBlowRetreating; } /** 阎王归属目标是否允许本次伤害(仅囚禁施法者的 yan_cage_dot) */ export function canDealDamageToYanClaimed( move: MovementComp | undefined | null, caster: EntityId, damageType?: string, ): boolean { if (!move?.imprisonBy) return true; return damageType === 'yan_cage_dot' && caster === move.imprisonBy; } /** 投射物组件 */ export class ProjectileComp extends EcsComponent { static readonly ecsTypeId = 'mx.ProjectileComp'; /** 飞行模式:homing=制导追踪;linear=直线穿透 */ mode: 'homing' | 'linear'; /** 直线模式方向(格/单位,已归一化) */ direction?: { dr: number; dc: number }; /** 直线模式剩余飞行距离(格) */ travelRemaining?: number; /** 剩余可穿透目标数(linear 模式;null=无限) */ pierceRemaining?: number | null; /** 已命中目标集合(穿透模式去重) */ hitSet?: MembershipSet; /** 表现层资源名(prefab/spine) */ prefab?: string; constructor( public caster: EntityId, public speed: number, public target: EntityId, public hitEffects: EffectSpec[], public damageScale: number, public damageType?: string, ) { super(); this.mode = 'homing'; } } /** 属性修改器 */ export interface AttributeModifier { /** 目标属性名 */ attr: string; /** 修改方式 */ op: 'Add' | 'Mul' | 'Override' | 'PostMulAdd'; /** 修改值 */ value: number; /** 来源标识(技能/Buff ID 等) */ source: string; /** 剩余持续时间(秒),undefined 表示永久 */ remain?: number; /** 同 attr + 同 group 的临时正面 modifier 只保留 1 个(新替换旧);undefined 不参与替换 */ group?: string; /** 剩余生效次数(atk_before/after 子类型3 的临时 buff 专用) */ charges?: number; } /** 属性组件(base + modifier) */ export class AttributeComp extends EcsComponent { static readonly ecsTypeId = 'mx.AttributeComp'; constructor( public base: Record = {}, public modifiers: AttributeModifier[] = [], ) { super(); } } /** 战斗属性 ID(与 CombatComp / AttributeComp 对应) */ export enum AttrId { /** 攻击力 */ Attack = 'attack', /** 攻击速度 */ AttackSpeed = 'attackSpeed', /** 攻击射程(像素) */ Range = 'range', /** 暴击率 */ CritRate = 'critRate', /** 暴击伤害 */ CritDamage = 'critDamage', /** 移动速度 */ MoveSpeed = 'moveSpeed', } /** * 普攻连击队列(atk_before 子类型1、次数型触发)。 * 首击命中后 pending>0;表现 finish 解锁时由 AbilitySystem 立即再施放普攻。 */ export class ComboAttackComp extends EcsComponent { static readonly ecsTypeId = 'mx.ComboAttackComp'; constructor( public pending = 0, public target: EntityId | null = null, public useMaxAspd = true, /** base_para code=6 连发间隔(秒);0 表示 finish 后立即再施放 */ public intervalSec = 0, /** 连发目标技能;缺省时取 interruptibleByCast 普攻 */ public abilityId: string | null = null, /** base_para code=6 连发已索敌单位(优先打不同目标) */ public usedTargets: EntityId[] = [], /** 已预约下一段连发定时器,防止 finish 重复 flush 叠定时器 */ public burstTimerPending = false, ) { super(); } } /** * 词条命中前后 hook(按普攻/大招分流)。 * base_atk 自身的 before/after 仍走 DealDamage.params。 */ export class AtkHookComp extends EcsComponent { static readonly ecsTypeId = 'mx.AtkHookComp'; constructor( /** up_type=1/4 → 普攻 */ public before: string[] = [], public after: string[] = [], /** up_type=2/4 → 大招 */ public ultBefore: string[] = [], public ultAfter: string[] = [], /** skill_special=1 时大招可暴击 */ public ultCanCrit = false, /** 受击增伤标记叠层(atk_after 子2)存在目标身上用 AttributeComp;此处仅占位扩展 */ public specials: string[] = [], /** Skill.skill 类型8:每 distPerPx 像素加 distAddWan(万分比) */ public distPerPx = 0, /** 每段距离的伤害加值(万分比) */ public distAddWan = 0, /** 距离加伤上限(万分比) */ public distMaxWan = 0, ) { super(); } }