mirror of
https://github.com/motajs/template.git
synced 2026-05-02 12:23:13 +08:00
refactor: flag 系统
This commit is contained in:
parent
ebfe59b4f5
commit
ebae685a4e
43
packages-user/data-base/src/flag/field.ts
Normal file
43
packages-user/data-base/src/flag/field.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { IFlagCommonField, IFlagSystem } from './types';
|
||||
import { logger } from '@motajs/common';
|
||||
|
||||
export class FlagCommonField<T> implements IFlagCommonField<T> {
|
||||
readonly system: IFlagSystem;
|
||||
|
||||
/** Flag 当前值 */
|
||||
private value: T;
|
||||
|
||||
/** 此 Flag 的键名 */
|
||||
private readonly key: PropertyKey;
|
||||
|
||||
constructor(system: IFlagSystem, key: PropertyKey, value: T) {
|
||||
this.system = system;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
set(value: T): void {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
add(value: number): void {
|
||||
if (typeof this.value !== 'number') {
|
||||
logger.warn(111, String(this.key));
|
||||
return;
|
||||
}
|
||||
// @ts-expect-error T 已通过运行时检查确认为 number
|
||||
this.value += value;
|
||||
}
|
||||
|
||||
get(): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
toStructured(): any {
|
||||
return structuredClone(this.value);
|
||||
}
|
||||
|
||||
fromStructured(data: any): void {
|
||||
this.value = structuredClone(data) as T;
|
||||
}
|
||||
}
|
||||
3
packages-user/data-base/src/flag/index.ts
Normal file
3
packages-user/data-base/src/flag/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './field';
|
||||
export * from './system';
|
||||
export * from './types';
|
||||
73
packages-user/data-base/src/flag/system.ts
Normal file
73
packages-user/data-base/src/flag/system.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { IFlagCommonField, IFlagSystem, IFlagSystemSave } from './types';
|
||||
import { FlagCommonField } from './field';
|
||||
|
||||
export class FlagSystem implements IFlagSystem {
|
||||
private readonly fieldMap: Map<PropertyKey, FlagCommonField<any>> =
|
||||
new Map();
|
||||
|
||||
occupied(field: PropertyKey): boolean {
|
||||
return this.fieldMap.has(field);
|
||||
}
|
||||
|
||||
insertField<T>(field: PropertyKey, value: T): IFlagCommonField<T> {
|
||||
return this.getOrInsert(field, value);
|
||||
}
|
||||
|
||||
getField<T>(field: PropertyKey): IFlagCommonField<T> | null {
|
||||
return this.fieldMap.get(field) ?? null;
|
||||
}
|
||||
|
||||
getOrInsert<T>(field: PropertyKey, defaultValue: T): IFlagCommonField<T> {
|
||||
return this.fieldMap.getOrInsertComputed(
|
||||
field,
|
||||
() => new FlagCommonField<T>(this, field, defaultValue)
|
||||
);
|
||||
}
|
||||
|
||||
getOrInsertComputed<K extends PropertyKey, T>(
|
||||
field: K,
|
||||
defaultValue: (field: K) => T
|
||||
): IFlagCommonField<T> {
|
||||
return this.fieldMap.getOrInsertComputed(
|
||||
field,
|
||||
() => new FlagCommonField<T>(this, field, defaultValue(field))
|
||||
);
|
||||
}
|
||||
|
||||
deleteField(field: PropertyKey): void {
|
||||
this.fieldMap.delete(field);
|
||||
}
|
||||
|
||||
setFieldValue(field: PropertyKey, value: any): void {
|
||||
this.getOrInsert(field, value).set(value);
|
||||
}
|
||||
|
||||
addFieldValue(field: PropertyKey, value: number): void {
|
||||
this.getOrInsert(field, 0).add(value);
|
||||
}
|
||||
|
||||
getFieldValue<T>(field: PropertyKey): T | undefined {
|
||||
return this.fieldMap.get(field)?.get();
|
||||
}
|
||||
|
||||
getFieldValueDefaults<T>(field: PropertyKey, defaultValue: T): T {
|
||||
return this.getOrInsert(field, defaultValue).get();
|
||||
}
|
||||
|
||||
saveState(): IFlagSystemSave {
|
||||
const fields: Map<PropertyKey, any> = new Map();
|
||||
for (const [key, field] of this.fieldMap) {
|
||||
fields.set(key, field.toStructured());
|
||||
}
|
||||
return { fields };
|
||||
}
|
||||
|
||||
loadState(state: IFlagSystemSave): void {
|
||||
this.fieldMap.clear();
|
||||
for (const [key, data] of state.fields) {
|
||||
const field = new FlagCommonField<unknown>(this, key, undefined);
|
||||
field.fromStructured(data);
|
||||
this.fieldMap.set(key, field);
|
||||
}
|
||||
}
|
||||
}
|
||||
147
packages-user/data-base/src/flag/types.ts
Normal file
147
packages-user/data-base/src/flag/types.ts
Normal file
@ -0,0 +1,147 @@
|
||||
//#region 字段
|
||||
|
||||
export interface IFlagCommonField<T> {
|
||||
/** 此字段所处的 Flag 系统 */
|
||||
readonly system: IFlagSystem;
|
||||
|
||||
/**
|
||||
* 设置此字段的值
|
||||
* @param value 要设置为的值
|
||||
*/
|
||||
set(value: T): void;
|
||||
|
||||
/**
|
||||
* 增减此字段的值,如果此字段的当前值不是数字,那么会抛出警告
|
||||
* @param value 增减值
|
||||
*/
|
||||
add(value: number): void;
|
||||
|
||||
/**
|
||||
* 获取此字段的值
|
||||
*/
|
||||
get(): T;
|
||||
|
||||
/**
|
||||
* 转换为可结构化存储的数据
|
||||
*/
|
||||
toStructured(): any;
|
||||
|
||||
/**
|
||||
* 从结构化存储数据中读取数据
|
||||
* @param data 结构化存储数据
|
||||
*/
|
||||
fromStructured(data: any): void;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 系统
|
||||
|
||||
export interface IFlagSystemSave {
|
||||
/** 每个字段的值,值为 {@link IFlagCommonField.toStructured} 方法输出 */
|
||||
readonly fields: Map<PropertyKey, any>;
|
||||
}
|
||||
|
||||
export interface IFlagSystem {
|
||||
/**
|
||||
* 判断一个字段是否被占用,类似于旧样板的 `core.hasFlag`
|
||||
* @param field 字段名称
|
||||
*/
|
||||
occupied(field: PropertyKey): boolean;
|
||||
|
||||
/**
|
||||
* 以指定值插入字段
|
||||
* @param field 字段名称
|
||||
* @param value 字段值
|
||||
*/
|
||||
insertField<T>(field: PropertyKey, value: T): IFlagCommonField<T>;
|
||||
|
||||
/**
|
||||
* 获取指定字段对象
|
||||
* @param field 字段名称
|
||||
*/
|
||||
getField<T>(field: PropertyKey): IFlagCommonField<T> | null;
|
||||
|
||||
/**
|
||||
* 获取指定字段,如果字段不存在则以 `defaultValue` 创建字段。
|
||||
* 如果默认值计算量较大,建议使用 {@link getOrInsertComputed} 方法。
|
||||
* @param field 字段名称
|
||||
* @param defaultValue 字段默认值
|
||||
*/
|
||||
getOrInsert<T>(field: PropertyKey, defaultValue: T): IFlagCommonField<T>;
|
||||
|
||||
/**
|
||||
* 获取指定字段,如果字段不存在则以 `defaultValue` 创建字段。
|
||||
* 如果字段不存在则不会执行 `defaultValue` 函数,如果默认值计算量大,建议使用此方法。
|
||||
* @param field 字段名称
|
||||
* @param defaultValue 字段默认值函数
|
||||
*/
|
||||
getOrInsertComputed<K extends PropertyKey, T>(
|
||||
field: K,
|
||||
defaultValue: (field: K) => T
|
||||
): IFlagCommonField<T>;
|
||||
|
||||
/**
|
||||
* 删除指定字段
|
||||
* @param field 字段名称
|
||||
*/
|
||||
deleteField(field: PropertyKey): void;
|
||||
|
||||
/**
|
||||
* 设置字段的值,类似于旧样板的 `core.setFlag`,等同于如下代码:
|
||||
* ```ts
|
||||
* const field = flagSystem.getField(name);
|
||||
* field.set(value);
|
||||
* ```
|
||||
* @param field 字段名称
|
||||
* @param value 要设置为的值
|
||||
*/
|
||||
setFieldValue(field: PropertyKey, value: any): void;
|
||||
|
||||
/**
|
||||
* 对字段的值进行增减操作,如果字段的值不是数字,那么会抛出警告。
|
||||
* 类似于旧样板的 `core.addFlag`,等同于如下代码:
|
||||
* ```ts
|
||||
* const field = flagSystem.getField(name);
|
||||
* field.add(value);
|
||||
* ```
|
||||
* @param field 字段名称
|
||||
* @param value 字段增减值
|
||||
*/
|
||||
addFieldValue(field: PropertyKey, value: number): void;
|
||||
|
||||
/**
|
||||
* 获取指定字段的值,类似于旧样板的 `core.getFlag`,但是没有默认值,
|
||||
* 如果需要默认值使用 {@link getFieldValueDefaults} 等方法。等同于如下代码:
|
||||
* ```ts
|
||||
* flagSystem.getField(name)?.get();
|
||||
* ```
|
||||
* @param field 字段名称
|
||||
*/
|
||||
getFieldValue<T>(field: PropertyKey): T | undefined;
|
||||
|
||||
/**
|
||||
* 获取字段的值,如果字段不存在会返回默认值,并以默认值创建新字段,
|
||||
* 类似于旧样板的 `core.getFlag`,且包含默认值,等同于如下代码:
|
||||
* ```ts
|
||||
* const field = flagSystem.getOrInsert(name, defaultValue);
|
||||
* return field.get();
|
||||
* ```
|
||||
* @param field 字段名称
|
||||
* @param defaultValue 字段默认值
|
||||
*/
|
||||
getFieldValueDefaults<T>(field: PropertyKey, defaultValue: T): T;
|
||||
|
||||
/**
|
||||
* 对 Flag 系统进行结构化复制,形成存档对象
|
||||
*/
|
||||
saveState(): IFlagSystemSave;
|
||||
|
||||
/**
|
||||
* 从指定存档对象读取信息
|
||||
* @param state 存档对象
|
||||
*/
|
||||
loadState(state: IFlagSystemSave): void;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@ -143,6 +143,8 @@ export interface GameEvent {
|
||||
replayStatus: [replaying: boolean];
|
||||
/** 当加载存档时触发,Emitted in project/functions.js */
|
||||
loadData: [];
|
||||
/** 当重设 `core.status.hero` 时触发 */
|
||||
resetHero: [hero: any];
|
||||
}
|
||||
|
||||
export const hook = new EventEmitter<GameEvent>();
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export * from './common';
|
||||
export * from './enemy';
|
||||
export * from './flag';
|
||||
export * from './hero';
|
||||
export * from './load';
|
||||
|
||||
|
||||
38
packages-user/data-fallback/src/flag.ts
Normal file
38
packages-user/data-fallback/src/flag.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { Patch, PatchClass } from '@motajs/legacy-common';
|
||||
import { ICoreState } from '@user/data-state';
|
||||
|
||||
/**
|
||||
* Flag 系统 patch
|
||||
*/
|
||||
export function patchFlags(state: ICoreState) {
|
||||
const patch = new Patch(PatchClass.Control);
|
||||
const flags = state.flags;
|
||||
patch.add('setFlag', (name, value) => {
|
||||
logger.warn(56, 'core.setFlag', 'IFlagSystem');
|
||||
if (value === null || value === undefined) {
|
||||
flags.deleteField(name);
|
||||
} else {
|
||||
flags.setFieldValue(name, value);
|
||||
}
|
||||
});
|
||||
patch.add('getFlag', <T>(name: string, defaultValue?: T) => {
|
||||
logger.warn(56, 'core.getFlag', 'IFlagSystem');
|
||||
if (defaultValue === undefined) {
|
||||
return flags.getFieldValue<T>(name);
|
||||
} else {
|
||||
return flags.getFieldValueDefaults(name, defaultValue);
|
||||
}
|
||||
});
|
||||
patch.add('addFlag', (name, value) => {
|
||||
logger.warn(56, 'core.addFlag', 'IFlagSystem');
|
||||
if (typeof value !== 'number') return;
|
||||
flags.addFieldValue(name, value);
|
||||
});
|
||||
patch.add('hasFlag', name => {
|
||||
return flags.occupied(name);
|
||||
});
|
||||
patch.add('removeFlag', name => {
|
||||
flags.deleteField(name);
|
||||
});
|
||||
}
|
||||
37
packages-user/data-fallback/src/hero.ts
Normal file
37
packages-user/data-fallback/src/hero.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { hook } from '@user/data-base';
|
||||
import { ICoreState } from '@user/data-state';
|
||||
|
||||
export function patchHero(state: ICoreState) {
|
||||
hook.on('resetHero', hero => {
|
||||
// patch 旧样板的 core.status.hero,主要是为编辑器服务的
|
||||
const attr = state.hero.getModifiableAttribute();
|
||||
const proxy = new Proxy(hero, {
|
||||
set(target, p, newValue) {
|
||||
target[p] = newValue;
|
||||
// @ts-expect-error 旧样板无法处理此类型
|
||||
attr.setBaseAttribute(p, newValue);
|
||||
return true;
|
||||
},
|
||||
get(_, p) {
|
||||
// @ts-expect-error 旧样板无法处理此类型
|
||||
return attr.getBaseAttribute(p);
|
||||
}
|
||||
});
|
||||
core.status.hero = proxy;
|
||||
|
||||
// 不允许再使用旧样板的 flags 接口
|
||||
const flagsProxy = new Proxy(core.status.hero.flags, {
|
||||
set() {
|
||||
logger.error(54);
|
||||
return false;
|
||||
},
|
||||
get() {
|
||||
logger.error(54);
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
core.status.hero.flags = flagsProxy;
|
||||
window.flags = flagsProxy;
|
||||
});
|
||||
}
|
||||
@ -1,7 +1,12 @@
|
||||
import { ICoreState } from '@user/data-state';
|
||||
import { patchBattle } from './battle';
|
||||
import { patchDamage } from './damage';
|
||||
import { patchFlags } from './flag';
|
||||
import { patchHero } from './hero';
|
||||
|
||||
export function patchAll() {
|
||||
export function patchAll(state: ICoreState) {
|
||||
patchBattle();
|
||||
patchDamage();
|
||||
patchFlags(state);
|
||||
patchHero(state);
|
||||
}
|
||||
|
||||
@ -11,7 +11,9 @@ import {
|
||||
MapDamage,
|
||||
HeroAttribute,
|
||||
HeroState,
|
||||
IHeroState
|
||||
IHeroState,
|
||||
IFlagSystem,
|
||||
FlagSystem
|
||||
} from '@user/data-base';
|
||||
import { IEnemyAttr } from './enemy/types';
|
||||
import {
|
||||
@ -28,16 +30,18 @@ import { HERO_DEFAULT_ATTRIBUTE, TILE_HEIGHT, TILE_WIDTH } from './shared';
|
||||
import { IHeroAttr } from './hero';
|
||||
|
||||
export class CoreState implements ICoreState {
|
||||
readonly layer: ILayerState;
|
||||
readonly roleFace: IRoleFaceBinder;
|
||||
readonly idNumberMap: Map<string, number>;
|
||||
readonly numberIdMap: Map<number, string>;
|
||||
|
||||
readonly layer: ILayerState;
|
||||
readonly hero: IHeroState<IHeroAttr>;
|
||||
|
||||
readonly enemyManager: IEnemyManager<IEnemyAttr>;
|
||||
readonly enemyContext: IEnemyContext<IEnemyAttr, IHeroAttr>;
|
||||
|
||||
readonly flags: IFlagSystem;
|
||||
|
||||
constructor() {
|
||||
this.layer = new LayerState();
|
||||
this.roleFace = new RoleFaceBinder();
|
||||
@ -82,6 +86,12 @@ export class CoreState implements ICoreState {
|
||||
this.enemyContext = enemyContext;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 其他初始化
|
||||
|
||||
this.flags = new FlagSystem();
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
saveState(): IStateSaveData {
|
||||
|
||||
@ -70,8 +70,11 @@ export function create() {
|
||||
export const state = new CoreState();
|
||||
|
||||
export * from './common';
|
||||
export * from './core';
|
||||
export * from './enemy';
|
||||
export * from './hero';
|
||||
export * from './map';
|
||||
export * from './legacy';
|
||||
export * from './map';
|
||||
|
||||
export * from './core';
|
||||
export * from './shared';
|
||||
export * from './types';
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
} from '@user/data-base';
|
||||
import { IEnemyAttr } from './enemy/types';
|
||||
import { IHeroAttr } from './hero';
|
||||
import { IFlagSystem } from '../../data-base/src/flag/types';
|
||||
|
||||
export interface IGameDataState {
|
||||
/** 怪物管理器 */
|
||||
@ -20,10 +21,6 @@ export interface IStateSaveData {
|
||||
}
|
||||
|
||||
export interface ICoreState {
|
||||
/** 地图状态 */
|
||||
readonly layer: ILayerState;
|
||||
/** 勇士状态 */
|
||||
readonly hero: IHeroState<IHeroAttr>;
|
||||
/** 朝向绑定 */
|
||||
readonly roleFace: IRoleFaceBinder;
|
||||
/** id 到图块数字的映射 */
|
||||
@ -31,11 +28,19 @@ export interface ICoreState {
|
||||
/** 图块数字到 id 的映射 */
|
||||
readonly numberIdMap: Map<number, string>;
|
||||
|
||||
/** 地图状态 */
|
||||
readonly layer: ILayerState;
|
||||
/** 勇士状态 */
|
||||
readonly hero: IHeroState<IHeroAttr>;
|
||||
|
||||
/** 怪物管理器 */
|
||||
readonly enemyManager: IEnemyManager<IEnemyAttr>;
|
||||
/** 怪物上下文 */
|
||||
readonly enemyContext: IEnemyContext<IEnemyAttr, IHeroAttr>;
|
||||
|
||||
/** Flag 系统 */
|
||||
readonly flags: IFlagSystem;
|
||||
|
||||
/**
|
||||
* 保存状态
|
||||
*/
|
||||
|
||||
@ -53,6 +53,7 @@
|
||||
"51": "Animatable object cannot be animated by plans with the same start time.",
|
||||
"52": "To get divider payload, an excitation binding is expected.",
|
||||
"53": "Expected serializable value set as enemy's default attribute.",
|
||||
"54": "Legacy flags API has been removed, consider using new APIs.",
|
||||
"1201": "Floor-damage extension needs 'floor-binder' extension as dependency."
|
||||
},
|
||||
"warn": {
|
||||
@ -166,6 +167,7 @@
|
||||
"108": "Hero modifier '$1' has already been added once. Ignore repeated add for attribute '$2'.",
|
||||
"109": "Expected a different object reference returned, but got a same reference at modifier '$1' for property '$2'.",
|
||||
"110": "Expected a hero attribute binding before executing any enemy context calculation.",
|
||||
"111": "Cannot add value to flag field '$1', since the current value is not a number.",
|
||||
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency."
|
||||
}
|
||||
}
|
||||
|
||||
@ -2524,33 +2524,27 @@ control.prototype.getNextLvUpNeed = function () {
|
||||
|
||||
////// 设置某个自定义变量或flag //////
|
||||
control.prototype.setFlag = function (name, value) {
|
||||
if (value == null) return this.removeFlag(name);
|
||||
if (!core.status.hero) return;
|
||||
core.status.hero.flags[name] = value;
|
||||
// Deprecated. See packages-user/data-fallback/src/flag.ts
|
||||
};
|
||||
|
||||
////// 增加某个flag数值 //////
|
||||
control.prototype.addFlag = function (name, value) {
|
||||
if (!core.status.hero) return;
|
||||
core.setFlag(name, core.getFlag(name, 0) + value);
|
||||
// Deprecated. See packages-user/data-fallback/src/flag.ts
|
||||
};
|
||||
|
||||
////// 获得某个自定义变量或flag //////
|
||||
control.prototype.getFlag = function (name, defaultValue) {
|
||||
if (!core.status.hero) return defaultValue;
|
||||
var value = core.status.hero.flags[name];
|
||||
return value != null ? value : defaultValue;
|
||||
// Deprecated. See packages-user/data-fallback/src/flag.ts
|
||||
};
|
||||
|
||||
////// 是否存在某个自定义变量或flag,且值为true //////
|
||||
control.prototype.hasFlag = function (name) {
|
||||
return !!core.getFlag(name);
|
||||
// Deprecated. See packages-user/data-fallback/src/flag.ts
|
||||
};
|
||||
|
||||
////// 删除某个自定义变量或flag //////
|
||||
control.prototype.removeFlag = function (name) {
|
||||
if (!core.status.hero) return;
|
||||
delete core.status.hero.flags[name];
|
||||
// Deprecated. See packages-user/data-fallback/src/flag.ts
|
||||
};
|
||||
|
||||
////// 获得某个点的独立开关 //////
|
||||
|
||||
3
src/types/declaration/control.d.ts
vendored
3
src/types/declaration/control.d.ts
vendored
@ -833,7 +833,8 @@ interface Control {
|
||||
* @param defaultValue 当变量不存在时的返回值,可选(事件流中默认填0)。
|
||||
* @returns flags[name] ?? defaultValue
|
||||
*/
|
||||
getFlag<T>(name: string, defaultValue?: T): T;
|
||||
getFlag<T>(name: string): T | undefined;
|
||||
getFlag<T>(name: string, defaultValue: T): T;
|
||||
|
||||
/**
|
||||
* @deprecated 可使用,暂时没有替代接口\
|
||||
|
||||
Loading…
Reference in New Issue
Block a user