From 53f067a0afeb025f9365b2c47c162f33a30a2323 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Thu, 17 Sep 2026 20:46:10 +0800 Subject: [PATCH] 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) --- .../data-base/src/hero/equipment.test.ts | 30 ++++++++++++++++++- packages-user/data-base/src/hero/equipment.ts | 15 ++++++---- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/packages-user/data-base/src/hero/equipment.test.ts b/packages-user/data-base/src/hero/equipment.test.ts index 06dad0c..71a8447 100644 --- a/packages-user/data-base/src/hero/equipment.test.ts +++ b/packages-user/data-base/src/hero/equipment.test.ts @@ -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(); diff --git a/packages-user/data-base/src/hero/equipment.ts b/packages-user/data-base/src/hero/equipment.ts index cd5d028..6b16472 100644 --- a/packages-user/data-base/src/hero/equipment.ts +++ b/packages-user/data-base/src/hero/equipment.ts @@ -294,22 +294,27 @@ export class HeroEquipment implements IHeroEquipment { const attrB: Partial = {}; const keys = new Set>(); - 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);