mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-26 00:53:25 +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 hero from './state/hero';
|
||||||
import * as miscMechanism from './mechanism/misc';
|
import * as miscMechanism from './mechanism/misc';
|
||||||
import * as study from './mechanism/study';
|
import * as study from './mechanism/study';
|
||||||
|
import { registerPresetState } from './state/preset';
|
||||||
|
|
||||||
// ----- 类注册
|
// ----- 类注册
|
||||||
Mota.register('class', 'DamageEnemy', damage.DamageEnemy);
|
Mota.register('class', 'DamageEnemy', damage.DamageEnemy);
|
||||||
@ -37,3 +38,5 @@ main.loading = loading;
|
|||||||
loading.once('coreInit', () => {
|
loading.once('coreInit', () => {
|
||||||
Mota.Plugin.init();
|
Mota.Plugin.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerPresetState();
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { logger } from '@/core/common/logger';
|
import { logger } from '@/core/common/logger';
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { cloneDeep, isNil } from 'lodash-es';
|
import { cloneDeep, isNil } from 'lodash-es';
|
||||||
import { GameState, ISerializable } from './state';
|
import { GameState } from './state';
|
||||||
import { ItemState } from './item';
|
import { ItemState } from './item';
|
||||||
|
import { MonoStore } from '@/common/struct';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取勇士在某一点的属性
|
* 获取勇士在某一点的属性
|
||||||
@ -128,7 +129,11 @@ interface HeroStateEvent {
|
|||||||
set: [key: string | number | symbol, value: any];
|
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<
|
export class HeroState<
|
||||||
T extends object = IHeroStatusDefault
|
T extends object = IHeroStatusDefault
|
||||||
@ -139,7 +144,7 @@ export class HeroState<
|
|||||||
readonly buffable: Set<keyof T> = new Set();
|
readonly buffable: Set<keyof T> = new Set();
|
||||||
readonly buffMap: Map<keyof T, number> = new Map();
|
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) {
|
constructor(init: T) {
|
||||||
super();
|
super();
|
||||||
@ -220,7 +225,7 @@ export class HeroState<
|
|||||||
if (!this.buffable.has(key) || typeof this.status[key] !== 'number') {
|
if (!this.buffable.has(key) || typeof this.status[key] !== 'number') {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
13,
|
13,
|
||||||
`Cannot set buff non-number status. Key: ${String(key)}.`
|
`Cannot set buff of non-number status. Key: ${String(key)}.`
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -238,7 +243,7 @@ export class HeroState<
|
|||||||
if (!this.buffable.has(key) || typeof this.status[key] !== 'number') {
|
if (!this.buffable.has(key) || typeof this.status[key] !== 'number') {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
13,
|
13,
|
||||||
`Cannot set buff non-number status. Key: ${String(key)}.`
|
`Cannot set buff of non-number status. Key: ${String(key)}.`
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -254,11 +259,11 @@ export class HeroState<
|
|||||||
if (key === void 0) {
|
if (key === void 0) {
|
||||||
for (const [key, value] of Object.entries(this.status)) {
|
for (const [key, value] of Object.entries(this.status)) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.computedStatus[key] = this.cal(key, value);
|
this.computedStatus[key] = HeroState.cal(this, key, value);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
this.computedStatus[key] = this.cal(key, this.status[key]);
|
this.computedStatus[key] = HeroState.cal(this, key, this.status[key]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +273,11 @@ export class HeroState<
|
|||||||
*/
|
*/
|
||||||
refreshBuffable(): boolean {
|
refreshBuffable(): boolean {
|
||||||
for (const key of this.buffable) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
@ -277,7 +286,7 @@ export class HeroState<
|
|||||||
* 复写属性计算函数,默认函数不进行计算,直接将原属性返回
|
* 复写属性计算函数,默认函数不进行计算,直接将原属性返回
|
||||||
* @param fn 计算函数,传入两个参数,key表示属性名,value表示属性值,返回值表示计算结果
|
* @param fn 计算函数,传入两个参数,key表示属性名,value表示属性值,返回值表示计算结果
|
||||||
*/
|
*/
|
||||||
overrideCalculate(fn: HeroStatusCalculate<T>) {
|
static overrideCalculate(fn: HeroStatusCalculate) {
|
||||||
this.cal = fn;
|
this.cal = fn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -353,23 +362,23 @@ interface HeroEvent {
|
|||||||
|
|
||||||
export class Hero<T extends object = IHeroStatusDefault>
|
export class Hero<T extends object = IHeroStatusDefault>
|
||||||
extends EventEmitter<HeroEvent>
|
extends EventEmitter<HeroEvent>
|
||||||
implements IHeroItem, ISerializable
|
implements IHeroItem
|
||||||
{
|
{
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
floorId: FloorIds;
|
floorId: FloorIds;
|
||||||
id: string;
|
|
||||||
|
readonly items: Map<AllIdsOf<'items'>, number> = new Map();
|
||||||
|
readonly id: string;
|
||||||
|
|
||||||
state: HeroState<T>;
|
state: HeroState<T>;
|
||||||
items: Map<AllIdsOf<'items'>, number> = new Map();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
id: string,
|
id: string,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
floorId: FloorIds,
|
floorId: FloorIds,
|
||||||
state: HeroState<T>,
|
state: HeroState<T>
|
||||||
gameState: GameState
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@ -378,11 +387,11 @@ export class Hero<T extends object = IHeroStatusDefault>
|
|||||||
this.floorId = floorId;
|
this.floorId = floorId;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
|
||||||
const list = gameState.state.hero.list;
|
// const list = gameState.get<MonoStore<Hero<any>>>('hero')!.list;
|
||||||
if (list.has(id)) {
|
// if (list.has(id)) {
|
||||||
logger.warn(11, `Repeated hero: ${id}.`);
|
// logger.warn(11, `Repeated hero: ${id}.`);
|
||||||
}
|
// }
|
||||||
list.set(id, this);
|
// list.set(id, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -467,8 +476,4 @@ export class Hero<T extends object = IHeroStatusDefault>
|
|||||||
hasItem(item: AllIdsOf<'items'>): boolean {
|
hasItem(item: AllIdsOf<'items'>): boolean {
|
||||||
return this.itemCount(item) > 0;
|
return this.itemCount(item) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): string {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import EventEmitter from 'eventemitter3';
|
import EventEmitter from 'eventemitter3';
|
||||||
import type { Hero } from './hero';
|
import type { Hero } from './hero';
|
||||||
import { GameState, gameStates, IGameState } from './state';
|
import { GameState, gameStates } from './state';
|
||||||
import { loading } from '../game';
|
import { loading } from '../game';
|
||||||
|
|
||||||
type EffectFn = (state: IGameState, hero: Hero<any>) => void;
|
type EffectFn = (state: GameState, hero: Hero<any>) => void;
|
||||||
type CanUseEffectFn = (state: IGameState, hero: Hero<any>) => boolean;
|
type CanUseEffectFn = (state: GameState, hero: Hero<any>) => boolean;
|
||||||
|
|
||||||
interface ItemStateEvent {
|
interface ItemStateEvent {
|
||||||
use: [hero: Hero<any>];
|
use: [hero: Hero<any>];
|
||||||
@ -91,16 +91,17 @@ export class ItemState<
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用这个物品
|
* 使用这个物品
|
||||||
* @param state 游戏状态
|
* @param hero 使用物品的勇士
|
||||||
* @param num 使用的数量,仅对tools和items有效
|
|
||||||
*/
|
*/
|
||||||
use(hero: Hero<any>): boolean {
|
use(hero: Hero<any>): boolean {
|
||||||
if (!this.canUse(hero)) return false;
|
if (!this.canUse(hero)) return false;
|
||||||
if (!gameStates.now) return false;
|
if (!gameStates.now) return false;
|
||||||
const state = gameStates.now.state;
|
const state = gameStates.now;
|
||||||
this.useItemEffectFn?.(state, hero);
|
this.useItemEffectFn?.(state, hero);
|
||||||
if (this.useItemEvent) core.insertAction(this.useItemEvent);
|
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);
|
hero.addItem(this.id, -1);
|
||||||
this.emit('use', hero);
|
this.emit('use', hero);
|
||||||
@ -116,7 +117,7 @@ export class ItemState<
|
|||||||
if (num <= 0) return false;
|
if (num <= 0) return false;
|
||||||
if (hero.itemCount(this.id) < num) return false;
|
if (hero.itemCount(this.id) < num) return false;
|
||||||
if (!gameStates.now) 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 { 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 {
|
type ToJSONFunction<T> = (data: T) => string;
|
||||||
toJSON(): string;
|
type FromJSONFunction<T> = (data: string) => T;
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGameState {
|
export class GameState {
|
||||||
hero: MonoStore<Hero<any>>;
|
state: Map<string, any> = new Map();
|
||||||
route: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GameState implements ISerializable {
|
private static states: Set<string> = new Set();
|
||||||
state: IGameState;
|
private static toJSONFn: Map<string, ToJSONFunction<any>> = new Map();
|
||||||
|
private static fromJSONFn: Map<string, FromJSONFunction<any>> = new Map();
|
||||||
constructor(state: IGameState) {
|
|
||||||
this.state = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化游戏状态,可直接用于存储等操作
|
||||||
|
*/
|
||||||
toJSON() {
|
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) {}
|
* @param key 要获取的状态名称
|
||||||
|
*/
|
||||||
static loadStateFromJSON(json: string) {}
|
get<T>(key: string): T | undefined {
|
||||||
}
|
return this.state.get(key);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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