From b2626bd4381f3686ddc73bbf6e2b3cc290df5079 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Wed, 16 Sep 2026 19:35:54 +0800 Subject: [PATCH] fix(07-09): keep hero attribute instance stable and clear generic variance - HeroAttribute.loadState replaces its private base object; the HeroAttribute wrapper identity is preserved so equipment/combat references stay valid across load (#06-17-1/#06-17-2). - Clone the incoming base attribute in the constructor so the shared HERO_DEFAULT_ATTRIBUTE is no longer mutated across CoreState instances. - Use any for the hero position in combat handler/aura/enemy-view generics to clear the 45 new type errors introduced by HeroAttribute implementing ISaveableContent. --- packages-user/data-base/src/hero/attribute.ts | 19 ++++++------------- packages-user/data-state/src/enemy/aura.ts | 2 +- packages-user/data-system/src/combat/enemy.ts | 2 +- .../data-system/src/combat/mapDamage.test.ts | 3 ++- packages-user/data-system/src/combat/types.ts | 12 ++++++------ 5 files changed, 16 insertions(+), 22 deletions(-) diff --git a/packages-user/data-base/src/hero/attribute.ts b/packages-user/data-base/src/hero/attribute.ts index be69165..7e95fc2 100644 --- a/packages-user/data-base/src/hero/attribute.ts +++ b/packages-user/data-base/src/hero/attribute.ts @@ -66,7 +66,9 @@ export class HeroAttribute implements IHeroAttribute { /** * @param attribute 当前勇士的基础属性 */ - constructor(private readonly attribute: THero) { + constructor(private attribute: THero) { + // 克隆入参,避免调用方传入的共享基础属性对象被本实例改写 + this.attribute = structuredClone(attribute); this.finalAttribute = structuredClone(attribute); } @@ -337,17 +339,8 @@ export class HeroAttribute implements IHeroAttribute { state: IHeroAttributeSave, compression: SaveCompression ): void { - // 原地修改基础属性以保持同引用,使装备与战斗侧持有的属性引用跨读档始终有效 - for (const key in this.attribute) { - if (Object.prototype.hasOwnProperty.call(state.values, key)) { - continue; - } - delete this.attribute[key as keyof THero]; - } - const values = structuredClone(state.values); - for (const key in values) { - this.attribute[key as keyof THero] = values[key as keyof THero]; - } + // attribute 是内部对象,不必保证同引用,只要内部不会出现引用问题即可 + this.attribute = structuredClone(state.values); this.modifier.clear(); this.modifierName.clear(); @@ -363,7 +356,7 @@ export class HeroAttribute implements IHeroAttribute { } // 逐键重算 final,清掉被移除修饰器留下的陈旧值 - for (const key in this.attribute) { + for (const key of Object.keys(this.attribute as object)) { this.recalculateAttribute(key as keyof THero); } } diff --git a/packages-user/data-state/src/enemy/aura.ts b/packages-user/data-state/src/enemy/aura.ts index f3c4f64..6f7856a 100644 --- a/packages-user/data-state/src/enemy/aura.ts +++ b/packages-user/data-state/src/enemy/aura.ts @@ -91,7 +91,7 @@ export class CommonAura implements IEnemyAuraView< } apply( - handler: IEnemyHandler, + handler: IEnemyHandler, baseEnemy: IReadonlyEnemy ): void { const { enemy } = handler; diff --git a/packages-user/data-system/src/combat/enemy.ts b/packages-user/data-system/src/combat/enemy.ts index b28cc53..eac7bdf 100644 --- a/packages-user/data-system/src/combat/enemy.ts +++ b/packages-user/data-system/src/combat/enemy.ts @@ -7,7 +7,7 @@ export class EnemyView implements IEnemyView { constructor( readonly baseEnemy: IEnemy, - readonly context: IEnemyContext + readonly context: IEnemyContext ) { this.computedEnemy = baseEnemy.clone(); } diff --git a/packages-user/data-system/src/combat/mapDamage.test.ts b/packages-user/data-system/src/combat/mapDamage.test.ts index e08b482..735b8db 100644 --- a/packages-user/data-system/src/combat/mapDamage.test.ts +++ b/packages-user/data-system/src/combat/mapDamage.test.ts @@ -7,6 +7,7 @@ import { } from '@motajs/common'; import { type IEnemy, type IStateBase } from '@user/data-base'; import { + IMapDamage, type IEnemyContext, type IEnemyView, type IMapDamageConverter, @@ -280,7 +281,7 @@ class FakeTypedView implements IMapDamageView { interface MapDamageFixture { /** 被测地图伤害对象 */ - damage: InstanceType; + damage: IMapDamage; /** 怪物上下文假对象 */ context: IEnemyContext; /** 测试转换器 */ diff --git a/packages-user/data-system/src/combat/types.ts b/packages-user/data-system/src/combat/types.ts index 78c64d2..14a8481 100644 --- a/packages-user/data-system/src/combat/types.ts +++ b/packages-user/data-system/src/combat/types.ts @@ -42,7 +42,7 @@ export interface IReadonlyEnemyHandler< export interface IEnemyView { /** 怪物视图所属的上下文 */ - readonly context: IEnemyContext; + readonly context: IEnemyContext; /** * 重置此怪物视图的状态,将计算后怪物对象恢复至初始状态 @@ -80,13 +80,13 @@ export interface IEnemySpecialModifier { * 获取要添加到指定怪物身上的特殊属性 * @param handler 信息对象 */ - add(handler: IReadonlyEnemyHandler): ISpecial[]; + add(handler: IReadonlyEnemyHandler): ISpecial[]; /** * 获取制定怪物身上要删除的特殊属性 * @param handler 信息对象 */ - delete(handler: IReadonlyEnemyHandler): ISpecial[]; + delete(handler: IReadonlyEnemyHandler): ISpecial[]; /** * 修改一个怪物的特殊属性,如果真正进行了修改则返回 true,否则返回 false @@ -94,7 +94,7 @@ export interface IEnemySpecialModifier { * @param special 要修改的怪物特殊属性 */ modify( - handler: IEnemyHandler, + handler: IEnemyHandler, special: ISpecial ): boolean; } @@ -121,7 +121,7 @@ export interface IAuraView { * @param baseEnemy 原始怪物对象,即未进行任何修改的怪物对象 */ apply( - handler: IEnemyHandler, + handler: IEnemyHandler, baseEnemy: IReadonlyEnemy ): void; @@ -131,7 +131,7 @@ export interface IAuraView { * @param baseEnemy 原始怪物对象,即未进行任何修改的怪物对象 */ applySpecial( - handler: IEnemyHandler, + handler: IEnemyHandler, baseEnemy: IReadonlyEnemy ): IEnemySpecialModifier | null; }