mirror of
https://github.com/unanmed/HumanBreak.git
synced 2026-11-12 00:24:30 +08:00
feat: state 及其序列化
This commit is contained in:
1 parent
1df2bc66f4
commit
c1c5d29e89
6 files changed
+255
-71
No files matched your search
@@ -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();
|
||||
+28
-23
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+76
-40
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user