diff --git a/packages-user/data-base/src/flag/field.ts b/packages-user/data-base/src/flag/field.ts new file mode 100644 index 0000000..b21e4aa --- /dev/null +++ b/packages-user/data-base/src/flag/field.ts @@ -0,0 +1,43 @@ +import { IFlagCommonField, IFlagSystem } from './types'; +import { logger } from '@motajs/common'; + +export class FlagCommonField implements IFlagCommonField { + 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; + } +} diff --git a/packages-user/data-base/src/flag/index.ts b/packages-user/data-base/src/flag/index.ts new file mode 100644 index 0000000..86c5128 --- /dev/null +++ b/packages-user/data-base/src/flag/index.ts @@ -0,0 +1,3 @@ +export * from './field'; +export * from './system'; +export * from './types'; diff --git a/packages-user/data-base/src/flag/system.ts b/packages-user/data-base/src/flag/system.ts new file mode 100644 index 0000000..7063691 --- /dev/null +++ b/packages-user/data-base/src/flag/system.ts @@ -0,0 +1,73 @@ +import { IFlagCommonField, IFlagSystem, IFlagSystemSave } from './types'; +import { FlagCommonField } from './field'; + +export class FlagSystem implements IFlagSystem { + private readonly fieldMap: Map> = + new Map(); + + occupied(field: PropertyKey): boolean { + return this.fieldMap.has(field); + } + + insertField(field: PropertyKey, value: T): IFlagCommonField { + return this.getOrInsert(field, value); + } + + getField(field: PropertyKey): IFlagCommonField | null { + return this.fieldMap.get(field) ?? null; + } + + getOrInsert(field: PropertyKey, defaultValue: T): IFlagCommonField { + return this.fieldMap.getOrInsertComputed( + field, + () => new FlagCommonField(this, field, defaultValue) + ); + } + + getOrInsertComputed( + field: K, + defaultValue: (field: K) => T + ): IFlagCommonField { + return this.fieldMap.getOrInsertComputed( + field, + () => new FlagCommonField(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(field: PropertyKey): T | undefined { + return this.fieldMap.get(field)?.get(); + } + + getFieldValueDefaults(field: PropertyKey, defaultValue: T): T { + return this.getOrInsert(field, defaultValue).get(); + } + + saveState(): IFlagSystemSave { + const fields: Map = 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(this, key, undefined); + field.fromStructured(data); + this.fieldMap.set(key, field); + } + } +} diff --git a/packages-user/data-base/src/flag/types.ts b/packages-user/data-base/src/flag/types.ts new file mode 100644 index 0000000..7f77a28 --- /dev/null +++ b/packages-user/data-base/src/flag/types.ts @@ -0,0 +1,147 @@ +//#region 字段 + +export interface IFlagCommonField { + /** 此字段所处的 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; +} + +export interface IFlagSystem { + /** + * 判断一个字段是否被占用,类似于旧样板的 `core.hasFlag` + * @param field 字段名称 + */ + occupied(field: PropertyKey): boolean; + + /** + * 以指定值插入字段 + * @param field 字段名称 + * @param value 字段值 + */ + insertField(field: PropertyKey, value: T): IFlagCommonField; + + /** + * 获取指定字段对象 + * @param field 字段名称 + */ + getField(field: PropertyKey): IFlagCommonField | null; + + /** + * 获取指定字段,如果字段不存在则以 `defaultValue` 创建字段。 + * 如果默认值计算量较大,建议使用 {@link getOrInsertComputed} 方法。 + * @param field 字段名称 + * @param defaultValue 字段默认值 + */ + getOrInsert(field: PropertyKey, defaultValue: T): IFlagCommonField; + + /** + * 获取指定字段,如果字段不存在则以 `defaultValue` 创建字段。 + * 如果字段不存在则不会执行 `defaultValue` 函数,如果默认值计算量大,建议使用此方法。 + * @param field 字段名称 + * @param defaultValue 字段默认值函数 + */ + getOrInsertComputed( + field: K, + defaultValue: (field: K) => T + ): IFlagCommonField; + + /** + * 删除指定字段 + * @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(field: PropertyKey): T | undefined; + + /** + * 获取字段的值,如果字段不存在会返回默认值,并以默认值创建新字段, + * 类似于旧样板的 `core.getFlag`,且包含默认值,等同于如下代码: + * ```ts + * const field = flagSystem.getOrInsert(name, defaultValue); + * return field.get(); + * ``` + * @param field 字段名称 + * @param defaultValue 字段默认值 + */ + getFieldValueDefaults(field: PropertyKey, defaultValue: T): T; + + /** + * 对 Flag 系统进行结构化复制,形成存档对象 + */ + saveState(): IFlagSystemSave; + + /** + * 从指定存档对象读取信息 + * @param state 存档对象 + */ + loadState(state: IFlagSystemSave): void; +} + +//#endregion diff --git a/packages-user/data-base/src/game.ts b/packages-user/data-base/src/game.ts index 12187a0..66745cc 100644 --- a/packages-user/data-base/src/game.ts +++ b/packages-user/data-base/src/game.ts @@ -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(); diff --git a/packages-user/data-base/src/index.ts b/packages-user/data-base/src/index.ts index 4cbfdc8..4994172 100644 --- a/packages-user/data-base/src/index.ts +++ b/packages-user/data-base/src/index.ts @@ -1,5 +1,6 @@ export * from './common'; export * from './enemy'; +export * from './flag'; export * from './hero'; export * from './load'; diff --git a/packages-user/data-fallback/src/flag.ts b/packages-user/data-fallback/src/flag.ts new file mode 100644 index 0000000..7f5c05d --- /dev/null +++ b/packages-user/data-fallback/src/flag.ts @@ -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', (name: string, defaultValue?: T) => { + logger.warn(56, 'core.getFlag', 'IFlagSystem'); + if (defaultValue === undefined) { + return flags.getFieldValue(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); + }); +} diff --git a/packages-user/data-fallback/src/hero.ts b/packages-user/data-fallback/src/hero.ts new file mode 100644 index 0000000..d889bc3 --- /dev/null +++ b/packages-user/data-fallback/src/hero.ts @@ -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; + }); +} diff --git a/packages-user/data-fallback/src/index.ts b/packages-user/data-fallback/src/index.ts index a496d29..525aaa6 100644 --- a/packages-user/data-fallback/src/index.ts +++ b/packages-user/data-fallback/src/index.ts @@ -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); } diff --git a/packages-user/data-state/src/core.ts b/packages-user/data-state/src/core.ts index 3dd7db7..63b4527 100644 --- a/packages-user/data-state/src/core.ts +++ b/packages-user/data-state/src/core.ts @@ -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; readonly numberIdMap: Map; + readonly layer: ILayerState; readonly hero: IHeroState; readonly enemyManager: IEnemyManager; readonly enemyContext: IEnemyContext; + 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 { diff --git a/packages-user/data-state/src/index.ts b/packages-user/data-state/src/index.ts index 6df88ec..ee87910 100644 --- a/packages-user/data-state/src/index.ts +++ b/packages-user/data-state/src/index.ts @@ -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'; diff --git a/packages-user/data-state/src/types.ts b/packages-user/data-state/src/types.ts index 92f4328..ff5860a 100644 --- a/packages-user/data-state/src/types.ts +++ b/packages-user/data-state/src/types.ts @@ -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; /** 朝向绑定 */ readonly roleFace: IRoleFaceBinder; /** id 到图块数字的映射 */ @@ -31,11 +28,19 @@ export interface ICoreState { /** 图块数字到 id 的映射 */ readonly numberIdMap: Map; + /** 地图状态 */ + readonly layer: ILayerState; + /** 勇士状态 */ + readonly hero: IHeroState; + /** 怪物管理器 */ readonly enemyManager: IEnemyManager; /** 怪物上下文 */ readonly enemyContext: IEnemyContext; + /** Flag 系统 */ + readonly flags: IFlagSystem; + /** * 保存状态 */ diff --git a/packages/common/src/logger.json b/packages/common/src/logger.json index 08415b7..e58e476 100644 --- a/packages/common/src/logger.json +++ b/packages/common/src/logger.json @@ -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." } } diff --git a/public/libs/control.js b/public/libs/control.js index d071a7c..611d726 100644 --- a/public/libs/control.js +++ b/public/libs/control.js @@ -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 }; ////// 获得某个点的独立开关 ////// diff --git a/src/types/declaration/control.d.ts b/src/types/declaration/control.d.ts index d5f985b..6789f65 100644 --- a/src/types/declaration/control.d.ts +++ b/src/types/declaration/control.d.ts @@ -833,7 +833,8 @@ interface Control { * @param defaultValue 当变量不存在时的返回值,可选(事件流中默认填0)。 * @returns flags[name] ?? defaultValue */ - getFlag(name: string, defaultValue?: T): T; + getFlag(name: string): T | undefined; + getFlag(name: string, defaultValue: T): T; /** * @deprecated 可使用,暂时没有替代接口\