You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
AdLike2048/assets/mx/module/battle/ecs/Components.ts

329 lines
12 KiB

3 weeks ago
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<string, number> = {},
) { 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):是否定身(眩晕 / 缚仙网 / 三昧真火 / 冰冻,移速归零) */
3 weeks ago
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') {
3 weeks ago
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;
2 weeks ago
/** 非空时表现层优先用此 Spine 皮肤;空串走默认阵营皮 */
3 weeks ago
public spineSkinOverride = '';
/** 从当前格抛物线跳到 path[0],不走出怪口、不沿途平移 */
public spawnJumpToPath = false;
/**
2 weeks ago
* 脚下用发光阴影(技能召唤等非基地波次)。
3 weeks ago
* 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<EntityId>;
/** 表现层资源名(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<string, number> = {},
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(); }
}