diff --git a/packages-user/data-base/src/hero/attribute.test.ts b/packages-user/data-base/src/hero/attribute.test.ts index 406830b..0727b9b 100644 --- a/packages-user/data-base/src/hero/attribute.test.ts +++ b/packages-user/data-base/src/hero/attribute.test.ts @@ -203,6 +203,60 @@ describe('HeroAttribute modifier management', () => { expect(attribute.deleteModifierByIndex('hp', 0)).toBeNull(); }); + // 验证按索引删除后修饰器不再被迭代、索引解析为 -1 且最终属性按剩余修饰器重算 + it('removes a modifier by index from the iterator and the final attribute', () => { + const attribute = createAttribute(); + const first = new TestModifier(5, 10); + const second = new TestModifier(2, 0); + attribute.addModifier('hp', first); + attribute.addModifier('hp', second); + expect(attribute.getFinalAttribute('hp')).toBe(107); + + expect(attribute.deleteModifierByIndex('hp', 0)).toBe(first); + expect([...attribute.iterateModifiers()]).toEqual([['hp', second]]); + expect(attribute.getModifierIndex(first)).toBe(-1); + expect(attribute.getFinalAttribute('hp')).toBe(102); + }); + + // 验证被按索引删除的修饰器已解除归属,可以重新挂载且不再被码 108 拒绝 + it('allows an index-deleted modifier to be attached again', () => { + const attribute = createAttribute(); + const first = new TestModifier(5); + attribute.addModifier('hp', first); + + expect(attribute.deleteModifierByIndex('hp', 0)).toBe(first); + + const result = logger.catch(() => attribute.addModifier('hp', first)); + expect(result.info.map(info => info.code)).not.toContain(108); + expect(attribute.getModifierIndex(first)).toBe(0); + expect(attribute.getFinalAttribute('hp')).toBe(105); + }); + + // 验证索引越界或为负时返回 null 且不删除任何修饰器 + it('keeps every modifier when the index is out of range', () => { + const attribute = createAttribute(); + const first = new TestModifier(5, 10); + const second = new TestModifier(2, 0); + attribute.addModifier('hp', first); + attribute.addModifier('hp', second); + + expect(attribute.deleteModifierByIndex('hp', -1)).toBeNull(); + expect(attribute.deleteModifierByIndex('hp', 2)).toBeNull(); + expect([...attribute.getModifiers('hp')]).toEqual([first, second]); + expect(attribute.getFinalAttribute('hp')).toBe(107); + }); + + // 验证按索引删除不存档修饰器时其存盘开关记录被一并清理 + it('clears the save flag of an index-deleted modifier', () => { + const attribute = createAttribute(); + const unsaved = new TestModifier(3); + attribute.addModifier('hp', unsaved, false); + expect(attribute.getModifierSaveEnabled(unsaved)).toBe(false); + + expect(attribute.deleteModifierByIndex('hp', 0)).toBe(unsaved); + expect(attribute.getModifierSaveEnabled(unsaved)).toBe(true); + }); + // 验证存盘开关可分别设置与查询 it('tracks the per-modifier save flag', () => { const attribute = createAttribute(); diff --git a/packages-user/data-base/src/hero/attribute.ts b/packages-user/data-base/src/hero/attribute.ts index 7e95fc2..e372e8a 100644 --- a/packages-user/data-base/src/hero/attribute.ts +++ b/packages-user/data-base/src/hero/attribute.ts @@ -233,9 +233,12 @@ export class HeroAttribute implements IHeroAttribute { ): IHeroModifier | null { const arr = this.modifier.get(name); if (!arr) return null; - const modifier = arr.splice(index, 1); - if (modifier.length === 0) return null; - else return modifier[0] as IHeroModifier; + const modifier = arr[index] as IHeroModifier | undefined; + // 越界与负索引一律不删除任何修饰器,避免旧 splice 语义下误删末尾元素 + if (!modifier) return null; + // 删除簿记统一由 deleteModifier 承担,故需先取出修饰器再委托以保留返回值 + this.deleteModifier(name, modifier); + return modifier; } markDirty(name: keyof THero): void { diff --git a/packages-user/data-base/src/hero/types.ts b/packages-user/data-base/src/hero/types.ts index bfc7355..33dc0c2 100644 --- a/packages-user/data-base/src/hero/types.ts +++ b/packages-user/data-base/src/hero/types.ts @@ -219,7 +219,8 @@ export interface IHeroAttribute ): void; /** - * 根据修饰器索引删除指定修饰器 + * 根据修饰器索引删除指定修饰器。删除簿记与 `deleteModifier` 共用同一份实现, + * 修饰器会被解绑且不再属于本属性;索引越界或为负时返回 `null`,且不删除任何修饰器 * @param name 属性名称 * @param index 修饰器索引 * @returns 被删除的修饰器