fix(07-15): compare equipment without rebinding the equipped modifiers

- compareEquip clones each compared item's modifier before adding it to the comparison clone and removes the same clone objects afterwards
- the live attribute's already-bound modifiers never enter the clone and are never unbound
- add the equipped-item diff regression case (both directions, no warning 108, live bookkeeping untouched)
This commit is contained in:
unanmed 2026-09-17 20:46:10 +08:00
parent 19ad1ea91a
commit 53f067a0af
2 changed files with 39 additions and 6 deletions

View File

@ -1,4 +1,4 @@
// 测试 HeroEquipment 组合行为:槽位判定、装备/替换/卸下、属性修饰器联动、compareEquip 与码 146/147
// 测试 HeroEquipment 组合行为:槽位判定、装备/替换/卸下、属性修饰器联动、compareEquip 与码 146/147、现役装备参与比较
import { afterAll, describe, expect, it, vi } from 'vitest';
import {
type IDataCommon,
@ -431,6 +431,34 @@ describe('HeroEquipment compare and guards', () => {
expect(diff.def).toBe(-3);
});
// 验证被比较的两件装备中有一件是现役装备时,双向差值仍符合契约且活属性簿记零扰动
it('diffs the final attributes when one compared item is currently equipped', () => {
const env = createEnv();
registerItem(env, createItem(10, 'sword', [0], [['atk', 5]]));
registerItem(env, createItem(11, 'axe', [0], [['atk', 12]]));
env.equipment.setSlots(['weapon']);
const sword = env.store.add(10);
env.attribute.addModifier('atk', new ValueModifier(7, -1));
env.equipment.equip(sword, 0);
expect(env.attribute.getFinalAttribute('atk')).toBe(22);
const axe = env.store.add(11);
const swordModifier = [...env.store.get(sword)!.getModifiers()][0][1];
const { ret, info } = logger.catch(() => [
env.equipment.compareEquip(sword, axe, 0),
env.equipment.compareEquip(axe, sword, 0)
]);
expect(ret[0].atk).toBe(-7);
expect(ret[1].atk).toBe(7);
expect(Object.keys(ret[0])).toEqual(['atk']);
expect(info.map(v => v.code)).not.toContain(108);
expect(
env.attribute.getModifierIndex(swordModifier)
).toBeGreaterThanOrEqual(0);
expect(env.attribute.getFinalAttribute('atk')).toBe(22);
});
// 验证装备实例经 rebuildModifiers 重建后原属性已不含其修饰器,克隆上不会误删同名旁的修饰器
it('keeps foreign modifiers when the equipped instance was rebuilt', () => {
const env = createEnv();

View File

@ -294,22 +294,27 @@ export class HeroEquipment<THero> implements IHeroEquipment<THero> {
const attrB: Partial<THero> = {};
const keys = new Set<SelectKey<THero, number>>();
for (const [name, modifier] of stateA.getModifiers()) {
const modifiersA = [...stateA.getModifiers()];
const addedA: typeof modifiersA = [];
for (const [name, modifier] of modifiersA) {
const copy = modifier.clone();
// @ts-expect-error 泛型无法推导
clone.addModifier(name, modifier);
clone.addModifier(name, copy);
addedA.push([name, copy]);
}
for (const [name] of stateA.getModifiers()) {
attrA[name] = clone.getFinalAttribute(name);
keys.add(name);
}
for (const [name, modifier] of stateA.getModifiers()) {
for (const [name, copy] of addedA) {
// @ts-expect-error 泛型无法推导
clone.deleteModifier(name, modifier);
clone.deleteModifier(name, copy);
}
for (const [name, modifier] of stateB.getModifiers()) {
const copy = modifier.clone();
// @ts-expect-error 泛型无法推导
clone.addModifier(name, modifier);
clone.addModifier(name, copy);
}
for (const [name] of stateB.getModifiers()) {
attrB[name] = clone.getFinalAttribute(name);