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.
174 lines
6.3 KiB
174 lines
6.3 KiB
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;
|
|
}
|
|
}
|
|
|