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/combat/AttributeSystem.ts

175 lines
6.3 KiB

3 weeks ago
import type { World } from 'db://assets/mx/module/ecs';
import type { EntityId } from 'db://assets/mx/module/ecs';
import type { ISystem } from 'db://assets/mx/module/ecs';
import {
AttrId,
AttributeComp,
CombatComp,
type AttributeModifier,
} from '../ecs/Components';
/** 从 CombatComp 读取与 AttrId 对齐的属性 */
function readCombatAttr(combat: CombatComp, attr: string): number | undefined {
switch (attr) {
case AttrId.Attack: return combat.attack;
case AttrId.AttackSpeed: return combat.attackSpeed;
case AttrId.Range: return combat.range;
case AttrId.CritRate: return combat.critRate;
case AttrId.CritDamage: return combat.critDamage;
default: return undefined;
}
}
/** 属性系统:base + modifier */
export class AttributeSystem implements ISystem {
readonly priority = 10;
enabled = true;
constructor(private readonly _world: World) {}
/**
* @param entityId 实体 ID
* @param attr 属性名
* @returns 最终属性值(含修改器)
*/
getAttr(entityId: EntityId, attr: string): number {
const world = this._world;
if (!world.isAlive(entityId)) return 0;
const attrComp = world.getComponent(entityId, AttributeComp);
const combat = world.getComponent(entityId, CombatComp);
let base = 0;
if (attrComp?.base[attr] != null) {
base = attrComp.base[attr];
} else if (combat) {
const fromCombat = readCombatAttr(combat, attr);
if (fromCombat != null) base = fromCombat;
}
const mods = attrComp?.modifiers.filter(m => m.attr === attr) ?? [];
let add = 0;
let mul = 1;
let post = 0;
for (const m of mods) {
if (m.op === 'Add') add += m.value;
else if (m.op === 'Mul') mul *= m.value;
else if (m.op === 'PostMulAdd') post += m.value;
else if (m.op === 'Override') return m.value;
}
return (base + add) * mul + post;
}
/** 将 CombatComp 同步到 AttributeComp.base */
syncCombatToAttributes(entityId: EntityId): void {
const world = this._world;
const combat = world.getComponent(entityId, CombatComp);
if (!combat) return;
let attr = world.getComponent(entityId, AttributeComp);
if (!attr) {
attr = new AttributeComp();
world.addComponent(entityId, attr);
}
attr.base[AttrId.Attack] = combat.attack;
attr.base[AttrId.AttackSpeed] = combat.attackSpeed;
attr.base[AttrId.Range] = combat.range;
attr.base[AttrId.CritRate] = combat.critRate;
attr.base[AttrId.CritDamage] = combat.critDamage;
}
/**
* @param entityId 实体 ID
* @param mod 属性修改器
*/
addModifier(entityId: EntityId, mod: AttributeModifier): void {
const world = this._world;
if (!world.isAlive(entityId)) return;
let attr = world.getComponent(entityId, AttributeComp);
if (!attr) {
attr = new AttributeComp();
world.addComponent(entityId, attr);
}
// 同 attr + 同 group 的临时正面 modifier 只保留 1 个:移除旧的
if (mod.group) {
attr.modifiers = attr.modifiers.filter(m => !(m.attr === mod.attr && m.group === mod.group));
}
attr.modifiers.push(mod);
}
/**
* 按 group 移除属性 modifier(次数型 buff 耗尽后清理加成用)
* @param entityId 实体 ID
* @param group modifier group 标识
*/
removeModifiersByGroup(entityId: EntityId, group: string): void {
const attr = this._world.getComponent(entityId, AttributeComp);
if (!attr) return;
attr.modifiers = attr.modifiers.filter(m => m.group !== group);
}
/** @param dt 帧间隔(秒),递减限时修改器 */
tick(dt: number): void {
this._world.view(AttributeComp).forEach((_id, attr) => {
const mods = attr.modifiers;
let write = 0;
for (let i = 0; i < mods.length; i++) {
const m = mods[i];
if (m.remain == null) {
mods[write++] = m;
continue;
}
m.remain -= dt;
if (m.remain > 0) mods[write++] = m;
}
mods.length = write;
});
}
/**
* 取最终攻速:base × (1 + sum(aspd_add/10000)),受 combat.maxAttackSpeed 上限。
* 支持 Override(最快攻速 buff 强制值,仍受 aspd_min 上限)。
* aspd_add modifier 的 value 存原始万分比值(如 2000=20%)。
*/
getFinalAttackSpeed(entityId: EntityId): number {
const combat = this._world.getComponent(entityId, CombatComp);
const attr = this._world.getComponent(entityId, AttributeComp);
// Override(最快攻速):取最后一个 Override 值
let override: number | null = null;
if (attr) {
for (const m of attr.modifiers) {
if (m.attr === AttrId.AttackSpeed && m.op === 'Override') override = m.value;
}
}
const cap = combat?.maxAttackSpeed ?? 0;
if (override != null) {
// 最快攻速:强制值仍受 aspd_min 上限
return Math.max(0.01, cap > 0 ? Math.min(override, cap) : override);
}
let base = combat?.attackSpeed ?? attr?.base[AttrId.AttackSpeed] ?? 1;
if (attr) {
let addRatio = 0;
for (const m of attr.modifiers) {
if (m.attr === 'aspd_add' && m.op === 'Add') addRatio += m.value / 10000;
}
base *= (1 + addRatio);
}
if (cap > 0) base = Math.min(base, cap);
return Math.max(0.01, base);
}
/**
* 取最终 CD:baseCd / (1 + sum(cd_add/10000))。
* cd_add modifier 的 value 存原始万分比值(如 3000=30% 减CD)。
*/
getFinalCooldown(entityId: EntityId, baseCd: number): number {
const attr = this._world.getComponent(entityId, AttributeComp);
let addRatio = 0;
if (attr) {
for (const m of attr.modifiers) {
if (m.attr === 'cd_add' && m.op === 'Add') addRatio += m.value / 10000;
}
}
return addRatio > 0 ? baseCd / (1 + addRatio) : baseCd;
}
}