mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-02-28 17:37:07 +08:00
feat: state 及其序列化
This commit is contained in:
parent
1df2bc66f4
commit
c1c5d29e89
75
src/common/struct.ts
Normal file
75
src/common/struct.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
|
||||
interface MonoStoreEvent<T> {
|
||||
change: [before: T | undefined, after: T | undefined];
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个可序列化存储结构,存储多个数据,但是同时只能有一个数据正在使用,适合那些需要切换的场景,
|
||||
* 例如多勇士,多摄像机等,就可以通过此结构进行存储,然后同时只能操作一个勇士、摄像机等。
|
||||
*/
|
||||
export class MonoStore<T> extends EventEmitter<MonoStoreEvent<T>> {
|
||||
list: Map<string, T> = new Map();
|
||||
using?: T;
|
||||
usingId?: string;
|
||||
|
||||
/**
|
||||
* 使用指定id的数据
|
||||
* @param id 要使用的数据id
|
||||
*/
|
||||
use(id: string) {
|
||||
const before = this.using;
|
||||
this.using = this.list.get(id);
|
||||
this.usingId = id;
|
||||
this.emit('change', before, this.using);
|
||||
}
|
||||
|
||||
static toJSON(data: MonoStore<any>) {
|
||||
return JSON.stringify({
|
||||
now: data.usingId,
|
||||
data: [...data.list]
|
||||
});
|
||||
}
|
||||
|
||||
static fromJSON<T>(data: string): MonoStore<T> {
|
||||
const d = JSON.parse(data);
|
||||
const arr: [string, T][] = d.data;
|
||||
const store = new MonoStore<T>();
|
||||
arr.forEach(([key, value]) => {
|
||||
store.list.set(key, value);
|
||||
});
|
||||
if (d.now) store.use(d.now);
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace SerializeUtils {
|
||||
interface StructSerializer<T> {
|
||||
toJSON(data: T): string;
|
||||
fromJSON(data: string): T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map键值对序列化函数,如果使用Map作为state,那么需要使用这里面的函数作为序列化与反序列化函数
|
||||
*/
|
||||
export const mapSerializer: StructSerializer<Map<any, any>> = {
|
||||
toJSON(data) {
|
||||
return JSON.stringify([...data]);
|
||||
},
|
||||
fromJSON(data) {
|
||||
return new Map(JSON.parse(data));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set集合序列化函数,如果使用Set作为state,那么需要使用这里面的函数作为序列化与反序列化函数
|
||||
*/
|
||||
export const setSerializer: StructSerializer<Set<any>> = {
|
||||
toJSON(data) {
|
||||
return JSON.stringify([...data]);
|
||||
},
|
||||
fromJSON(data) {
|
||||
return new Set(JSON.parse(data));
|
||||
}
|
||||
};
|
||||
}
|
@ -9,6 +9,7 @@ import * as battle from './enemy/battle';
|
||||
import * as hero from './state/hero';
|
||||
import * as miscMechanism from './mechanism/misc';
|
||||
import * as study from './mechanism/study';
|
||||
import { registerPresetState } from './state/preset';
|
||||
|
||||
// ----- 类注册
|
||||
Mota.register('class', 'DamageEnemy', damage.DamageEnemy);
|
||||
@ -37,3 +38,5 @@ main.loading = loading;
|
||||
loading.once('coreInit', () => {
|
||||
Mota.Plugin.init();
|
||||
});
|
||||
|
||||
registerPresetState();
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { logger } from '@/core/common/logger';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { cloneDeep, isNil } from 'lodash-es';
|
||||
import { GameState, ISerializable } from './state';
|
||||
import { GameState } from './state';
|
||||
import { ItemState } from './item';
|
||||
import { MonoStore } from '@/common/struct';
|
||||
|
||||
/**
|
||||
* 获取勇士在某一点的属性
|
||||
@ -128,7 +129,11 @@ interface HeroStateEvent {
|
||||
set: [key: string | number | symbol, value: any];
|
||||
}
|
||||
|
||||
type HeroStatusCalculate<T> = <K extends keyof T>(key: K, value: T[K]) => T[K];
|
||||
type HeroStatusCalculate = (
|
||||
hero: HeroState<any>,
|
||||
key: string | number | symbol,
|
||||
value: any
|
||||
) => any;
|
||||
|
||||
export class HeroState<
|
||||
T extends object = IHeroStatusDefault
|
||||
@ -139,7 +144,7 @@ export class HeroState<
|
||||
readonly buffable: Set<keyof T> = new Set();
|
||||
readonly buffMap: Map<keyof T, number> = new Map();
|
||||
|
||||
private cal: HeroStatusCalculate<T> = (_, value) => value;
|
||||
private static cal: HeroStatusCalculate = (_0, _1, value) => value;
|
||||
|
||||
constructor(init: T) {
|
||||
super();
|
||||
@ -220,7 +225,7 @@ export class HeroState<
|
||||
if (!this.buffable.has(key) || typeof this.status[key] !== 'number') {
|
||||
logger.warn(
|
||||
13,
|
||||
`Cannot set buff non-number status. Key: ${String(key)}.`
|
||||
`Cannot set buff of non-number status. Key: ${String(key)}.`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@ -238,7 +243,7 @@ export class HeroState<
|
||||
if (!this.buffable.has(key) || typeof this.status[key] !== 'number') {
|
||||
logger.warn(
|
||||
13,
|
||||
`Cannot set buff non-number status. Key: ${String(key)}.`
|
||||
`Cannot set buff of non-number status. Key: ${String(key)}.`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@ -254,11 +259,11 @@ export class HeroState<
|
||||
if (key === void 0) {
|
||||
for (const [key, value] of Object.entries(this.status)) {
|
||||
// @ts-ignore
|
||||
this.computedStatus[key] = this.cal(key, value);
|
||||
this.computedStatus[key] = HeroState.cal(this, key, value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
this.computedStatus[key] = this.cal(key, this.status[key]);
|
||||
this.computedStatus[key] = HeroState.cal(this, key, this.status[key]);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -268,7 +273,11 @@ export class HeroState<
|
||||
*/
|
||||
refreshBuffable(): boolean {
|
||||
for (const key of this.buffable) {
|
||||
this.computedStatus[key] = this.cal(key, this.status[key]);
|
||||
this.computedStatus[key] = HeroState.cal(
|
||||
this,
|
||||
key,
|
||||
this.status[key]
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -277,7 +286,7 @@ export class HeroState<
|
||||
* 复写属性计算函数,默认函数不进行计算,直接将原属性返回
|
||||
* @param fn 计算函数,传入两个参数,key表示属性名,value表示属性值,返回值表示计算结果
|
||||
*/
|
||||
overrideCalculate(fn: HeroStatusCalculate<T>) {
|
||||
static overrideCalculate(fn: HeroStatusCalculate) {
|
||||
this.cal = fn;
|
||||
}
|
||||
}
|
||||
@ -353,23 +362,23 @@ interface HeroEvent {
|
||||
|
||||
export class Hero<T extends object = IHeroStatusDefault>
|
||||
extends EventEmitter<HeroEvent>
|
||||
implements IHeroItem, ISerializable
|
||||
implements IHeroItem
|
||||
{
|
||||
x: number;
|
||||
y: number;
|
||||
floorId: FloorIds;
|
||||
id: string;
|
||||
|
||||
readonly items: Map<AllIdsOf<'items'>, number> = new Map();
|
||||
readonly id: string;
|
||||
|
||||
state: HeroState<T>;
|
||||
items: Map<AllIdsOf<'items'>, number> = new Map();
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
x: number,
|
||||
y: number,
|
||||
floorId: FloorIds,
|
||||
state: HeroState<T>,
|
||||
gameState: GameState
|
||||
state: HeroState<T>
|
||||
) {
|
||||
super();
|
||||
this.id = id;
|
||||
@ -378,11 +387,11 @@ export class Hero<T extends object = IHeroStatusDefault>
|
||||
this.floorId = floorId;
|
||||
this.state = state;
|
||||
|
||||
const list = gameState.state.hero.list;
|
||||
if (list.has(id)) {
|
||||
logger.warn(11, `Repeated hero: ${id}.`);
|
||||
}
|
||||
list.set(id, this);
|
||||
// const list = gameState.get<MonoStore<Hero<any>>>('hero')!.list;
|
||||
// if (list.has(id)) {
|
||||
// logger.warn(11, `Repeated hero: ${id}.`);
|
||||
// }
|
||||
// list.set(id, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -467,8 +476,4 @@ export class Hero<T extends object = IHeroStatusDefault>
|
||||
hasItem(item: AllIdsOf<'items'>): boolean {
|
||||
return this.itemCount(item) > 0;
|
||||
}
|
||||
|
||||
toJSON(): string {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import type { Hero } from './hero';
|
||||
import { GameState, gameStates, IGameState } from './state';
|
||||
import { GameState, gameStates } from './state';
|
||||
import { loading } from '../game';
|
||||
|
||||
type EffectFn = (state: IGameState, hero: Hero<any>) => void;
|
||||
type CanUseEffectFn = (state: IGameState, hero: Hero<any>) => boolean;
|
||||
type EffectFn = (state: GameState, hero: Hero<any>) => void;
|
||||
type CanUseEffectFn = (state: GameState, hero: Hero<any>) => boolean;
|
||||
|
||||
interface ItemStateEvent {
|
||||
use: [hero: Hero<any>];
|
||||
@ -91,16 +91,17 @@ export class ItemState<
|
||||
|
||||
/**
|
||||
* 使用这个物品
|
||||
* @param state 游戏状态
|
||||
* @param num 使用的数量,仅对tools和items有效
|
||||
* @param hero 使用物品的勇士
|
||||
*/
|
||||
use(hero: Hero<any>): boolean {
|
||||
if (!this.canUse(hero)) return false;
|
||||
if (!gameStates.now) return false;
|
||||
const state = gameStates.now.state;
|
||||
const state = gameStates.now;
|
||||
this.useItemEffectFn?.(state, hero);
|
||||
if (this.useItemEvent) core.insertAction(this.useItemEvent);
|
||||
if (!this.noRoute) state.route.push(`item:${this.id}`);
|
||||
if (!this.noRoute) {
|
||||
state.get<string[]>('route')!.push(`item:${this.id}`);
|
||||
}
|
||||
|
||||
hero.addItem(this.id, -1);
|
||||
this.emit('use', hero);
|
||||
@ -116,7 +117,7 @@ export class ItemState<
|
||||
if (num <= 0) return false;
|
||||
if (hero.itemCount(this.id) < num) return false;
|
||||
if (!gameStates.now) return false;
|
||||
return !!this.canUseItemEffectFn?.(gameStates.now.state, hero);
|
||||
return !!this.canUseItemEffectFn?.(gameStates.now, hero);
|
||||
}
|
||||
|
||||
/**
|
||||
|
64
src/game/state/preset.ts
Normal file
64
src/game/state/preset.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { MonoStore } from '@/common/struct';
|
||||
import { GameState } from './state';
|
||||
import { Hero, HeroState } from './hero';
|
||||
|
||||
export function registerPresetState() {
|
||||
GameState.register<MonoStore<Hero<any>>>('hero', heroToJSON, heroFromJSON);
|
||||
}
|
||||
|
||||
interface HeroSave {
|
||||
x: number;
|
||||
y: number;
|
||||
floorId: FloorIds;
|
||||
id: string;
|
||||
items: [AllIdsOf<'items'>, number][];
|
||||
state: {
|
||||
status: any;
|
||||
buffable: (string | number | symbol)[];
|
||||
buffMap: [string | number | symbol, number][];
|
||||
};
|
||||
}
|
||||
|
||||
interface HeroSerializable {
|
||||
now: string | null;
|
||||
saves: HeroSave[];
|
||||
}
|
||||
|
||||
function heroToJSON(data: MonoStore<Hero<any>>): string {
|
||||
const now = data.usingId ?? null;
|
||||
const saves: HeroSave[] = [...data.list.values()].map(v => {
|
||||
return {
|
||||
x: v.x,
|
||||
y: v.y,
|
||||
floorId: v.floorId,
|
||||
id: v.id,
|
||||
items: [...v.items],
|
||||
state: {
|
||||
status: v.state.status,
|
||||
buffable: [...v.state.buffable],
|
||||
buffMap: [...v.state.buffMap]
|
||||
}
|
||||
};
|
||||
});
|
||||
const obj: HeroSerializable = {
|
||||
now,
|
||||
saves
|
||||
};
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
|
||||
function heroFromJSON(data: string): MonoStore<Hero<any>> {
|
||||
const obj: HeroSerializable = JSON.parse(data);
|
||||
const store = new MonoStore<Hero<any>>();
|
||||
const saves: [string, Hero<any>][] = obj.saves.map(v => {
|
||||
const state = new HeroState(v.state.status);
|
||||
v.state.buffable.forEach(v => state.buffable.add(v));
|
||||
v.state.buffMap.forEach(v => state.buffMap.set(v[0], v[1]));
|
||||
const hero = new Hero(v.id, v.x, v.y, v.floorId, state);
|
||||
v.items.forEach(v => hero.items.set(v[0], v[1]));
|
||||
return [hero.id, hero];
|
||||
});
|
||||
store.list = new Map(saves);
|
||||
if (obj.now) store.use(obj.now);
|
||||
return store;
|
||||
}
|
@ -1,53 +1,89 @@
|
||||
import { Undoable } from '@/core/interface';
|
||||
import { Hero, HeroState } from './hero';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { logger } from '@/core/common/logger';
|
||||
|
||||
export interface ISerializable {
|
||||
toJSON(): string;
|
||||
}
|
||||
type ToJSONFunction<T> = (data: T) => string;
|
||||
type FromJSONFunction<T> = (data: string) => T;
|
||||
|
||||
export interface IGameState {
|
||||
hero: MonoStore<Hero<any>>;
|
||||
route: string[];
|
||||
}
|
||||
export class GameState {
|
||||
state: Map<string, any> = new Map();
|
||||
|
||||
export class GameState implements ISerializable {
|
||||
state: IGameState;
|
||||
|
||||
constructor(state: IGameState) {
|
||||
this.state = state;
|
||||
}
|
||||
private static states: Set<string> = new Set();
|
||||
private static toJSONFn: Map<string, ToJSONFunction<any>> = new Map();
|
||||
private static fromJSONFn: Map<string, FromJSONFunction<any>> = new Map();
|
||||
|
||||
/**
|
||||
* 序列化游戏状态,可直接用于存储等操作
|
||||
*/
|
||||
toJSON() {
|
||||
return '';
|
||||
const obj: Record<string, string> = {};
|
||||
this.state.forEach((v, k) => {
|
||||
const to = GameState.toJSONFn.get(k);
|
||||
if (to) obj[k] = to(v);
|
||||
else obj[k] = JSON.stringify(v);
|
||||
});
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
|
||||
static loadState(state: GameState) {}
|
||||
|
||||
static fromJSON(json: string) {}
|
||||
|
||||
static loadStateFromJSON(json: string) {}
|
||||
}
|
||||
|
||||
interface MonoStoreEvent<T> {
|
||||
change: [before: T | undefined, after: T | undefined];
|
||||
}
|
||||
|
||||
export class MonoStore<T extends ISerializable>
|
||||
extends EventEmitter<MonoStoreEvent<T>>
|
||||
implements ISerializable
|
||||
{
|
||||
list: Map<string, T> = new Map();
|
||||
using?: T;
|
||||
|
||||
use(id: string) {
|
||||
const before = this.using;
|
||||
this.using = this.list.get(id);
|
||||
this.emit('change', before, this.using);
|
||||
/**
|
||||
* 获取某个游戏状态
|
||||
* @param key 要获取的状态名称
|
||||
*/
|
||||
get<T>(key: string): T | undefined {
|
||||
return this.state.get(key);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return '';
|
||||
/**
|
||||
* 设置某个游戏状态
|
||||
* @param key 要设置的状态名称
|
||||
* @param data 状态数据
|
||||
*/
|
||||
set(key: string, data: any) {
|
||||
this.state.set(key, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个新的状态,如果重复则会覆盖
|
||||
* @param key 状态名称
|
||||
* @param toJSON 状态的序列化函数,传入状态数据,要求返回序列化后的字符串,
|
||||
* 不填则表示使用JSON.stringify进行序列化
|
||||
* @param fromJSON 状态的反序列化函数,传入序列化后的字符串,要求返回反序列化的状态数据,
|
||||
* 不填表示使用JSON.parse进行反序列化
|
||||
*/
|
||||
static register<T>(
|
||||
key: string,
|
||||
toJSON?: ToJSONFunction<T>,
|
||||
fromJSON?: FromJSONFunction<T>
|
||||
) {
|
||||
if (this.states.has(key)) {
|
||||
logger.warn(16, `Override repeated state key: ${key}.`);
|
||||
}
|
||||
|
||||
if (toJSON) {
|
||||
this.toJSONFn.set(key, toJSON);
|
||||
} else {
|
||||
this.toJSONFn.delete(key);
|
||||
}
|
||||
if (fromJSON) {
|
||||
this.fromJSONFn.set(key, fromJSON);
|
||||
} else {
|
||||
this.fromJSONFn.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从序列化字符串读取游戏状态
|
||||
* @param json 序列化字符串
|
||||
*/
|
||||
static fromJSON(json: string) {
|
||||
const obj: Record<string, string> = JSON.parse(json);
|
||||
const state = new GameState();
|
||||
for (const [key, data] of Object.entries(obj)) {
|
||||
const from = this.fromJSONFn.get(key);
|
||||
if (from) state.set(key, from(data));
|
||||
else state.set(key, JSON.parse(data));
|
||||
}
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user