refactor: flag 系统

This commit is contained in:
unanmed 2026-04-18 22:39:37 +08:00
parent ebfe59b4f5
commit ebae685a4e
15 changed files with 385 additions and 21 deletions

View 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;
}
}

View File

@ -0,0 +1,3 @@
export * from './field';
export * from './system';
export * from './types';

View 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);
}
}
}

View 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

View File

@ -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>();

View File

@ -1,5 +1,6 @@
export * from './common';
export * from './enemy';
export * from './flag';
export * from './hero';
export * from './load';

View 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);
});
}

View 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;
});
}

View File

@ -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);
}

View File

@ -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 {

View File

@ -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';

View File

@ -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;
/**
*
*/

View File

@ -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."
}
}

View File

@ -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
};
////// 获得某个点的独立开关 //////

View File

@ -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 使\