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.
This commit is contained in:
unanmed 2026-09-16 19:35:54 +08:00
parent 9a2f581c80
commit b2626bd438
5 changed files with 16 additions and 22 deletions

View File

@ -66,7 +66,9 @@ export class HeroAttribute<THero> implements IHeroAttribute<THero> {
/**
* @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<THero> implements IHeroAttribute<THero> {
state: IHeroAttributeSave<THero>,
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<THero> implements IHeroAttribute<THero> {
}
// 逐键重算 final,清掉被移除修饰器留下的陈旧值
for (const key in this.attribute) {
for (const key of Object.keys(this.attribute as object)) {
this.recalculateAttribute(key as keyof THero);
}
}

View File

@ -91,7 +91,7 @@ export class CommonAura implements IEnemyAuraView<
}
apply(
handler: IEnemyHandler<IEnemyAttr, unknown>,
handler: IEnemyHandler<IEnemyAttr, any>,
baseEnemy: IReadonlyEnemy<IEnemyAttr>
): void {
const { enemy } = handler;

View File

@ -7,7 +7,7 @@ export class EnemyView<TAttr> implements IEnemyView<TAttr> {
constructor(
readonly baseEnemy: IEnemy<TAttr>,
readonly context: IEnemyContext<TAttr, unknown>
readonly context: IEnemyContext<TAttr, any>
) {
this.computedEnemy = baseEnemy.clone();
}

View File

@ -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<number> {
interface MapDamageFixture {
/** 被测地图伤害对象 */
damage: InstanceType<TestModules['MapDamage']>;
damage: IMapDamage<TestEnemyAttr, TestHeroAttr>;
/** 怪物上下文假对象 */
context: IEnemyContext<TestEnemyAttr, TestHeroAttr>;
/** 测试转换器 */

View File

@ -42,7 +42,7 @@ export interface IReadonlyEnemyHandler<
export interface IEnemyView<TEnemy> {
/** 怪物视图所属的上下文 */
readonly context: IEnemyContext<TEnemy, unknown>;
readonly context: IEnemyContext<TEnemy, any>;
/**
* 重置此怪物视图的状态,将计算后怪物对象恢复至初始状态
@ -80,13 +80,13 @@ export interface IEnemySpecialModifier<TEnemy> {
* 获取要添加到指定怪物身上的特殊属性
* @param handler 信息对象
*/
add(handler: IReadonlyEnemyHandler<TEnemy, unknown>): ISpecial<any>[];
add(handler: IReadonlyEnemyHandler<TEnemy, any>): ISpecial<any>[];
/**
* 获取制定怪物身上要删除的特殊属性
* @param handler 信息对象
*/
delete(handler: IReadonlyEnemyHandler<TEnemy, unknown>): ISpecial<any>[];
delete(handler: IReadonlyEnemyHandler<TEnemy, any>): ISpecial<any>[];
/**
* 修改一个怪物的特殊属性,如果真正进行了修改则返回 true,否则返回 false
@ -94,7 +94,7 @@ export interface IEnemySpecialModifier<TEnemy> {
* @param special 要修改的怪物特殊属性
*/
modify(
handler: IEnemyHandler<TEnemy, unknown>,
handler: IEnemyHandler<TEnemy, any>,
special: ISpecial<any>
): boolean;
}
@ -121,7 +121,7 @@ export interface IAuraView<TEnemy, TRange = any> {
* @param baseEnemy 原始怪物对象,即未进行任何修改的怪物对象
*/
apply(
handler: IEnemyHandler<TEnemy, unknown>,
handler: IEnemyHandler<TEnemy, any>,
baseEnemy: IReadonlyEnemy<TEnemy>
): void;
@ -131,7 +131,7 @@ export interface IAuraView<TEnemy, TRange = any> {
* @param baseEnemy 原始怪物对象,即未进行任何修改的怪物对象
*/
applySpecial(
handler: IEnemyHandler<TEnemy, unknown>,
handler: IEnemyHandler<TEnemy, any>,
baseEnemy: IReadonlyEnemy<TEnemy>
): IEnemySpecialModifier<TEnemy> | null;
}