import { logger } from '@motajs/common'; import { IHeroAttribute, IHeroModifier } from './types'; export abstract class BaseHeroModifier implements IHeroModifier { abstract readonly priority: number; owner: IHeroAttribute | null = null; constructor(private currentValue: V) {} get value(): V { return this.currentValue; } setValue(value: V): void { this.currentValue = value; this.owner?.markModifierDirty(this); } getValue(): V { return this.currentValue; } bindAttribute(attribute: IHeroAttribute | null): void { this.owner = attribute; } abstract modify(value: T, baseValue: T, name: string): T; abstract clone(): IHeroModifier; } export class HeroAttribute implements IHeroAttribute { /** 当前勇士属性修饰器 */ private readonly modifier: Map = new Map(); /** 当前每个修饰器对应的属性值 */ private readonly modifierName: Map = new Map(); /** 当前勇士最终属性 */ private readonly finalAttribute: THero; /** * @param attribute 当前勇士的基础属性 */ constructor(private readonly attribute: THero) { this.finalAttribute = structuredClone(attribute); } /** * 判定修饰器结果是否同引用 * @param curr 当前属性值 * @param next 修饰器修饰结果 */ private isSameReference(curr: unknown, next: unknown) { return typeof curr === 'object' && curr !== null && curr === next; } /** * 重新计算指定属性值 * @param name 属性名称 */ private recalculateAttribute(name: K): void { const modifierList = this.modifier.get(name); if (!modifierList) return; const baseValue = this.attribute[name]; let value = baseValue; for (const modifier of modifierList as IHeroModifier[]) { const nextValue = modifier.modify(value, baseValue, name); // 部署之后就没必要弹这个警告了,额外判断反而可能会有一定的性能损失,直接 tree-shaking 优化掉 if (import.meta.env.DEV && this.isSameReference(value, nextValue)) { const modiferName = modifier.constructor.name; logger.warn(109, modiferName, String(name)); } value = nextValue; } this.finalAttribute[name] = value; } getBaseAttribute(name: K): THero[K] { return this.attribute[name]; } getFinalAttribute(name: K): THero[K] { return this.finalAttribute[name]; } setBaseAttribute(name: K, value: THero[K]): void { this.attribute[name] = value; this.markDirty(name); } addModifier( name: K, modifier: IHeroModifier ): void { if (modifier.owner) { const modiferName = modifier.constructor.name; logger.warn(108, modiferName, String(name)); return; } const modifierList = this.modifier.getOrInsert(name, []); modifierList.push(modifier); modifierList.sort((left, right) => right.priority - left.priority); this.modifierName.set(modifier, name); modifier.bindAttribute(this); this.markDirty(name); } deleteModifier( name: K, modifier: IHeroModifier ): void { const modifierList = this.modifier.get(name); if (!modifierList) return; const index = modifierList.indexOf(modifier); if (index === -1) return; modifier.bindAttribute(null); modifierList.splice(index, 1); this.modifierName.delete(modifier); this.markDirty(name); } markDirty(name: keyof THero): void { this.recalculateAttribute(name); } markModifierDirty(modifier: IHeroModifier): void { const name = this.modifierName.get(modifier); if (name === undefined) return; this.markDirty(name); } clone(cloneModifier: boolean = true): IHeroAttribute { const cloned = new HeroAttribute( structuredClone(this.attribute) ); if (!cloneModifier) return cloned; // 拷贝修饰器 for (const [modifier, name] of this.modifierName) { cloned.addModifier( name, modifier.clone() as IHeroModifier ); } return cloned; } }