fix(07-15): restore modifier bookkeeping when cloning an attribute

- clone() now binds each cloned modifier to the clone, records its modifierName, mirrors the source save flag and recomputes the attribute, matching addModifier's bookkeeping
- a cloned attribute can enumerate, index and persist its modifiers, and a cloned modifier's setValue notifies the clone
- add the bookkeeping and save-ability regression cases
This commit is contained in:
unanmed 2026-09-17 20:55:58 +08:00
parent 49116c0784
commit bb1986508a
2 changed files with 49 additions and 2 deletions

View File

@ -1,4 +1,4 @@
// 测试 HeroAttribute 构件:基础/最终属性、修饰器增删排序、存盘开关、克隆、自身存读档与告警码 108/109
// 测试 HeroAttribute 构件:基础/最终属性、修饰器增删排序、存盘开关、克隆、自身存读档与告警码 108/109、克隆体修饰器簿记与存档开关
import { afterAll, describe, expect, it, vi } from 'vitest';
import { BaseHeroModifier, HeroAttribute } from './attribute';
import { logger } from '@motajs/common';
@ -315,6 +315,45 @@ describe('HeroAttribute cloning and progress', () => {
expect(attribute.getBaseAttribute('hp')).toBe(100);
});
// 验证克隆体的修饰器可通过遍历、索引定位、setValue 通知与存档四种簿记访问
it('keeps the modifier bookkeeping of cloned attributes', () => {
const attribute = createNumericAttribute();
attribute.addModifier('hp', new TestModifier(5));
attribute.addModifier('atk', new TestModifier(3, 10));
const clone = attribute.clone();
const clonedHp = [...clone.getModifiers('hp')][0];
expect([...clone.iterateModifiers()].map(v => v[0]).sort()).toEqual([
'atk',
'hp'
]);
expect(clone.getModifierIndex(clonedHp)).toBe(0);
clonedHp.setValue(9);
expect(clone.getFinalAttribute('hp')).toBe(109);
expect(attribute.getFinalAttribute('hp')).toBe(105);
expect(
clone
.saveState(SaveCompression.NoCompression)
.modifiers.map(v => v.name)
.sort()
).toEqual(['atk', 'hp']);
});
// 验证克隆修饰器继承源修饰器的存档开关,不存档的修饰器不进入克隆体存档
it('keeps the save-ability of cloned modifiers', () => {
const attribute = createNumericAttribute();
attribute.addModifier('hp', new TestModifier(5), false);
const clone = attribute.clone();
const clonedHp = [...clone.getModifiers('hp')][0];
expect(clone.getModifierSaveEnabled(clonedHp)).toBe(false);
expect(
clone.saveState(SaveCompression.NoCompression).modifiers
).toHaveLength(0);
});
// 验证 catchCalculateProgress 输出计算过程且不修改最终属性
it('iterates calculation progress without touching the final value', () => {
const attribute = createAttribute();

View File

@ -306,7 +306,15 @@ export class HeroAttribute<THero> implements IHeroAttribute<THero> {
}
if (!cloneModifier) return cloned;
for (const [name, modifiers] of this.modifier) {
const arr: IHeroModifier[] = modifiers.map(v => v.clone());
const arr: IHeroModifier[] = modifiers.map(v => {
const copy = v.clone();
copy.bindAttribute(cloned);
cloned.modifierName.set(copy, name);
if (!this.getModifierSaveEnabled(v)) {
cloned.modifierNosave.add(copy);
}
return copy;
});
cloned.modifier.set(name, arr);
cloned.recalculateAttribute(name);
}