refactor: 存档系统 & 勇士属性存档

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
AncTeandCopilot committed 2026-04-24 17:29:13 +08:00
1 parent ec59a44efc
commit 8c1becc9f1
19 files changed
+1112 -54

No files matched your search

@@ -52,3 +52,31 @@ export interface IRoleFaceBinder {
*/
getMainFace(identifier: number): IFaceData | null;
}
//#region 功能接口
export const enum SaveCompression {
/** 不进行压缩,仅提取必要数据 */
NoCompression,
/** 进行小幅度压缩,以性能为主要考虑目标 */
LowCompression,
/** 进行大幅度压缩,以体积为主要考虑目标 */
HighCompression
}
export interface ISaveableContent<T> {
/**
* 保存对象状态,返回的对象应该经过深拷贝(即 `structuedClone`)
* @param compression 压缩级别
*/
saveState(compression: SaveCompression): T;
/**
* 读取对象状态
* @param state 状态对象
* @param compression 压缩级别
*/
loadState(state: T, compression: SaveCompression): void;
}
//#endregion
+1 -1
View File
@@ -2,7 +2,7 @@ import { IFlagCommonField, IFlagSystem, IFlagSystemSave } from './types';
import { FlagCommonField } from './field';
export class FlagSystem implements IFlagSystem {
private readonly fieldMap: Map<PropertyKey, FlagCommonField<any>> =
private readonly fieldMap: Map<PropertyKey, IFlagCommonField<any>> =
new Map();
occupied(field: PropertyKey): boolean {
+3 -12
View File
@@ -1,5 +1,7 @@
//#region 字段
import { ISaveableContent } from '../common';
export interface IFlagCommonField<T> {
/** 此字段所处的 Flag 系统 */
readonly system: IFlagSystem;
@@ -42,7 +44,7 @@ export interface IFlagSystemSave {
readonly fields: Map<PropertyKey, any>;
}
export interface IFlagSystem {
export interface IFlagSystem extends ISaveableContent<IFlagSystemSave> {
/**
* 判断一个字段是否被占用,类似于旧样板的 `core.hasFlag`
* @param field 字段名称
@@ -131,17 +133,6 @@ export interface IFlagSystem {
* @param defaultValue 字段默认值
*/
getFieldValueDefaults<T>(field: PropertyKey, defaultValue: T): T;
/**
* 对 Flag 系统进行结构化复制,形成存档对象
*/
saveState(): IFlagSystemSave;
/**
* 从指定存档对象读取信息
* @param state 存档对象
*/
loadState(state: IFlagSystemSave): void;
}
//#endregion
+21 -1
View File
@@ -1,7 +1,9 @@
import { logger } from '@motajs/common';
import { SaveCompression } from '../common';
import { IHeroAttribute, IHeroModifier } from './types';
export abstract class BaseHeroModifier<T, V> implements IHeroModifier<T, V> {
export abstract class BaseHeroModifier<T, V> implements IHeroModifier<T, V, V> {
abstract readonly type: string;
abstract readonly priority: number;
owner: IHeroAttribute<unknown> | null = null;
@@ -25,6 +27,14 @@ export abstract class BaseHeroModifier<T, V> implements IHeroModifier<T, V> {
this.owner = attribute;
}
saveState(_compression: SaveCompression): V {
return this.currentValue;
}
loadState(state: V, _compression: SaveCompression): void {
this.setValue(state);
}
abstract modify(value: T, baseValue: T, name: string): T;
abstract clone(): IHeroModifier<T, V>;
@@ -161,4 +171,14 @@ export class HeroAttribute<THero> implements IHeroAttribute<THero> {
getModifiableClone(): IHeroAttribute<THero> {
return this.clone();
}
toStructured(): THero {
return structuredClone(this.attribute);
}
*iterateModifiers(): IterableIterator<[keyof THero, IHeroModifier]> {
for (const [modifier, name] of this.modifierName) {
yield [name, modifier];
}
}
}
+79
View File
@@ -1,11 +1,20 @@
import { HeroAttribute } from './attribute';
import {
IHeroAttribute,
IHeroModifier,
IHeroMover,
IHeroState,
IHeroStateSave,
IModifierStateSave,
IReadonlyHeroAttribute
} from './types';
import { SaveCompression } from '../common';
import { logger } from '@motajs/common';
export class HeroState<THero> implements IHeroState<THero> {
/** 修饰器工厂函数注册表 */
private readonly registry: Map<string, () => IHeroModifier> = new Map();
constructor(
public mover: IHeroMover,
public attribute: IHeroAttribute<THero>
@@ -34,4 +43,74 @@ export class HeroState<THero> implements IHeroState<THero> {
getIsolatedAttribute(): IHeroAttribute<THero> {
return this.attribute.getModifiableClone();
}
registerModifier(type: string, cons: () => IHeroModifier): void {
this.registry.set(type, cons);
}
createModifier<T, V>(type: string): IHeroModifier<T, V> | null {
const cons = this.registry.get(type);
if (!cons) {
logger.warn(116, type);
return null;
} else {
return cons() as IHeroModifier<T, V>;
}
}
createAndInsertModifier<K extends keyof THero, V>(
type: string,
name: K
): IHeroModifier<THero[K], V> | null {
const modifier = this.createModifier<THero[K], V>(type);
if (!modifier) return null;
this.attribute.addModifier(name, modifier);
return modifier;
}
saveState(compression: SaveCompression): IHeroStateSave<THero> {
const modifiers: IModifierStateSave[] = [];
for (const [name, modifier] of this.attribute.iterateModifiers()) {
modifiers.push({
name,
type: modifier.type,
state: modifier.saveState(compression)
});
}
return {
attribute: this.attribute.toStructured(),
locator: {
x: this.mover.x,
y: this.mover.y,
direction: this.mover.direction
},
followers: structuredClone(this.mover.followers),
modifiers
};
}
loadState(
state: IHeroStateSave<THero>,
compression: SaveCompression
): void {
const newAttribute = new HeroAttribute<THero>(state.attribute);
for (const save of state.modifiers) {
const cons = this.registry.get(save.type);
if (!cons) continue;
const modifier = cons();
modifier.loadState(save.state as never, compression);
newAttribute.addModifier(
save.name as keyof THero,
modifier as unknown as IHeroModifier<THero[keyof THero]>
);
}
this.attribute = newAttribute;
this.mover.setPosition(state.locator.x, state.locator.y);
this.mover.turn(state.locator.direction);
this.mover.removeAllFollowers();
state.followers.forEach(follower => {
this.mover.addFollower(follower.num, follower.identifier);
this.mover.setFollowerAlpha(follower.identifier, follower.alpha);
});
}
}
+65 -4
View File
@@ -1,9 +1,15 @@
import { IHookBase, IHookable } from '@motajs/common';
import { FaceDirection } from '@user/data-state';
import { IFacedTileLocator, IHookBase, IHookable } from '@motajs/common';
import { FaceDirection, ISaveableContent } from '../common';
//#region 勇士属性
export interface IHeroModifier<T = unknown, V = unknown> {
export interface IHeroModifier<
T = unknown,
V = unknown,
S = unknown
> extends ISaveableContent<S> {
/** 修饰器类型 */
readonly type: string;
/** 修饰器优先级 */
readonly priority: number;
/** 修饰器参数值 */
@@ -42,6 +48,15 @@ export interface IHeroModifier<T = unknown, V = unknown> {
clone(): IHeroModifier<T, V>;
}
export interface IModifierStateSave {
/** 属性名称 */
readonly name: PropertyKey;
/** 修饰器类型 */
readonly type: string;
/** 修饰器存档数据 */
readonly state: unknown;
}
export interface IReadonlyHeroAttribute<THero> {
/**
* 获取勇士的基础属性,即未经过任何 Buff 或装备等加成的属性
@@ -77,6 +92,16 @@ export interface IReadonlyHeroAttribute<THero> {
* 获取此勇士属性对象的可修改副本
*/
getModifiableClone(): IHeroAttribute<THero>;
/**
* 转换为结构化对象
*/
toStructured(): THero;
/**
* 遍历所有已挂载的属性修饰器
*/
iterateModifiers(): Iterable<[PropertyKey, IHeroModifier]>;
}
export interface IHeroAttribute<THero> extends IReadonlyHeroAttribute<THero> {
@@ -332,7 +357,20 @@ export interface IHeroMover extends IHookable<IHeroMovingHooks> {
//#region 勇士状态
export interface IHeroState<THero> {
export interface IHeroStateSave<THero> {
/** 勇士属性状态 */
readonly attribute: THero;
/** 勇士当前位置 */
readonly locator: IFacedTileLocator;
/** 勇士当前的跟随者 */
readonly followers: readonly Readonly<IHeroFollower>[];
/** 勇士属性修饰器状态 */
readonly modifiers: readonly IModifierStateSave[];
}
export interface IHeroState<THero> extends ISaveableContent<
IHeroStateSave<THero>
> {
/** 勇士移动对象 */
readonly mover: IHeroMover;
/** 勇士属性对象 */
@@ -369,6 +407,29 @@ export interface IHeroState<THero> {
* 获取独立勇士属性对象,修改此对象不会影响勇士本身的属性
*/
getIsolatedAttribute(): IHeroAttribute<THero>;
/**
* 注册一个修饰器工厂函数
* @param type 修饰器类型
* @param cons 工厂函数
*/
registerModifier(type: string, cons: () => IHeroModifier): void;
/**
* 创建指定类型的修饰器实例
* @param type 修饰器类型
*/
createModifier<T, V>(type: string): IHeroModifier<T, V> | null;
/**
* 创建指定类型的修饰器实例并插入至勇士属性对象
* @param type 修饰器类型
* @param name 属性名称
*/
createAndInsertModifier<K extends keyof THero, V>(
type: string,
name: K
): IHeroModifier<THero[K], V> | null;
}
//#endregion
+9 -16
View File
@@ -1,9 +1,7 @@
import { IMotaDataLoader } from './load';
import { ILoadProgressTotal } from '@motajs/loader';
import { IHeroFollower, IHeroState } from './hero';
import { IEnemyContext, IEnemyManager } from './enemy';
import { IEnemyManager } from './enemy';
import { IFlagSystem } from './flag';
import { IRoleFaceBinder } from './common';
import { IRoleFaceBinder, ISaveableContent } from './common';
import { ILayerState } from './map';
export interface IStateSaveData {
@@ -19,11 +17,6 @@ export interface IStateBase<TEnemy, THero> {
/** 图块数字到 id 的映射 */
readonly numberIdMap: Map<number, string>;
/** 加载进度对象 */
readonly loadProgress: ILoadProgressTotal;
/** 数据端加载对象 */
readonly dataLoader: IMotaDataLoader;
/** 地图状态 */
readonly layer: ILayerState;
/** 勇士状态 */
@@ -31,20 +24,20 @@ export interface IStateBase<TEnemy, THero> {
/** 怪物管理器 */
readonly enemyManager: IEnemyManager<TEnemy>;
/** 怪物上下文 */
readonly enemyContext: IEnemyContext<TEnemy, THero>;
/** Flag 系统 */
readonly flags: IFlagSystem;
/**
* 保存当前状态
* 添加可存档对象,添加后系统将会自动在存档时将对象存储
* @param id 可存档对象的 id
* @param content 可存档对象
*/
saveState(): IStateSaveData;
addSaveableContent(id: string, content: ISaveableContent<unknown>): void;
/**
* 加载状态
* @param state 状态对象
* 根据 id 获取对应的可存档对象
* @param id 可存档对象的 id
*/
loadState(state: IStateSaveData): void;
getSaveableContent<T>(id: string): ISaveableContent<T> | null;
}