From 3e68880e82718df8f98b1a1c6cc750cd089f1a14 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Thu, 17 Sep 2026 16:06:53 +0800 Subject: [PATCH] =?UTF-8?q?fix(07-12):=20compareEquip=20=E5=9C=A8=E5=85=8B?= =?UTF-8?q?=E9=9A=86=E5=B1=9E=E6=80=A7=E4=B8=8A=E6=8C=89=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E5=AE=9A=E4=BD=8D=E5=88=A0=E9=99=A4=E7=9B=AE=E6=A0=87=EF=BC=88?= =?UTF-8?q?Q4=20=E4=BB=85=E4=BF=AE=20(i)=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 原属性索引为负或克隆槽位越界时不删除任何修饰器 - 删除目标改为克隆属性同槽位的自身对象,不再跨对象拼接索引 - 回归构造 rebuildModifiers 重建后的负索引路径,含触发机制断言 --- .../data-base/src/hero/equipment.test.ts | 37 +++++++++++++++++++ packages-user/data-base/src/hero/equipment.ts | 7 +++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages-user/data-base/src/hero/equipment.test.ts b/packages-user/data-base/src/hero/equipment.test.ts index 1268e31..144a2f7 100644 --- a/packages-user/data-base/src/hero/equipment.test.ts +++ b/packages-user/data-base/src/hero/equipment.test.ts @@ -7,11 +7,13 @@ import { ItemCategory, ItemStore, ReplayCode, + SaveCompression, TileStore, TileType } from '@user/data-common'; import { logger } from '@motajs/common'; import { HeroAttribute } from './attribute'; +import { ValueModifier } from './modifier'; import { HeroEquipsStore } from './equipStore'; import { HeroEquipment } from './equipment'; import { EquipStatus } from './types'; @@ -429,6 +431,41 @@ describe('HeroEquipment compare and guards', () => { expect(diff.def).toBe(-3); }); + // 验证装备实例经 rebuildModifiers 重建后原属性已不含其修饰器,克隆上不会误删同名旁的修饰器 + it('keeps foreign modifiers when the equipped instance was rebuilt', () => { + const env = createEnv(); + registerItem(env, createItem(10, 'sword', [0], [['atk', 5]])); + registerItem(env, createItem(11, 'axe', [0], [['atk', 12]])); + registerItem(env, createItem(12, 'bow', [0], [['def', 3]])); + 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 bow = env.store.add(12); + // 现役装备的修饰器仍在原属性上时可被定位删除,差值等于斧加成减现役剑加成 + expect(env.equipment.compareEquip(axe, bow, 0).atk).toBe(7); + + // 让现役装备走一次读档重建,其修饰器变为新对象,而原属性上仍是旧对象 + env.store.loadState( + env.store.saveState(SaveCompression.NoCompression), + SaveCompression.NoCompression + ); + const rebuilt = [...env.store.get(sword)!.getModifiers()][0][1]; + // 触发机制:重建后的修饰器无法在原属性上解析,删除目标只能退化为按槽位取值, + // 旧实现把该索引直接用在克隆属性上,负索引会删掉克隆属性末尾的英雄侧修饰器 + expect(env.attribute.getModifierIndex(rebuilt)).toBe(-1); + + const diff = env.equipment.compareEquip(axe, bow, 0); + + // 原属性不含现役装备的修饰器时克隆上不删除任何修饰器,故差值只体现斧与弓的加成 + expect(diff.atk).toBe(12); + expect(diff.def).toBe(-3); + }); + // 验证装备实例缺失时卸下告警 146 it('warns code 146 when the equipped instance is missing', () => { const env = createEnv(); diff --git a/packages-user/data-base/src/hero/equipment.ts b/packages-user/data-base/src/hero/equipment.ts index ec49826..d82519f 100644 --- a/packages-user/data-base/src/hero/equipment.ts +++ b/packages-user/data-base/src/hero/equipment.ts @@ -279,8 +279,13 @@ export class HeroEquipment implements IHeroEquipment { return {} as Partial; } for (const [name, modifier] of state.getModifiers()) { + // 索引取自原属性,为负时说明原属性已不含该修饰器对象,克隆上不得删除任何修饰器 const index = this.attribute.getModifierIndex(modifier); - clone.deleteModifierByIndex(name, index); + if (index < 0) continue; + // 克隆体按原属性同顺序重建修饰器数组,故取克隆自己的同槽位新对象作为删除目标 + const cloned = [...clone.getModifiers(name)][index]; + if (!cloned) continue; + clone.deleteModifier(name, cloned); } }