refactor: CoreState 一部分类型声明挪到 data-base

This commit is contained in:
AncTe committed 2026-04-21 21:01:53 +08:00
1 parent 1a569804d8
commit ec59a44efc
15 files changed
+98 -109

No files matched your search

@@ -0,0 +1,63 @@
import { logger } from '@motajs/common';
import { IFaceData, IRoleFaceBinder } from './types';
import { isNil } from 'lodash-es';
import { FaceDirection } from '.';
interface FaceInfo {
/** 此图块的朝向 */
readonly face: FaceDirection;
/** 此图块对应的映射 */
readonly map: Map<FaceDirection, number>;
}
export class RoleFaceBinder implements IRoleFaceBinder {
/** 每个图块对应的朝向信息 */
private faceMap: Map<number, FaceInfo> = new Map();
/** 主要朝向映射 */
private mainMap: Map<number, FaceDirection> = new Map();
malloc(identifier: number, main: FaceDirection): void {
this.mainMap.set(identifier, main);
const map = new Map<FaceDirection, number>();
map.set(main, identifier);
const info: FaceInfo = { face: main, map };
this.faceMap.set(identifier, info);
}
bind(identifier: number, main: number, face: FaceDirection): void {
const mainFace = this.mainMap.get(main);
if (isNil(mainFace)) {
logger.error(43, main.toString());
return;
}
if (mainFace === face) {
logger.error(44, main.toString());
return;
}
const { map } = this.faceMap.get(main)!;
map.set(face, identifier);
const info: FaceInfo = { face, map };
this.faceMap.set(identifier, info);
this.mainMap.set(identifier, mainFace);
}
getFaceOf(identifier: number, face: FaceDirection): IFaceData | null {
const info = this.faceMap.get(identifier);
if (!info) return null;
const target = info.map.get(face);
if (isNil(target)) return null;
const data: IFaceData = { identifier: target, face };
return data;
}
getFaceDirection(identifier: number): FaceDirection | undefined {
return this.faceMap.get(identifier)?.face;
}
getMainFace(identifier: number): IFaceData | null {
const face = this.mainMap.get(identifier);
if (isNil(face)) return null;
const data: IFaceData = { identifier, face };
return data;
}
}
@@ -1,2 +1,3 @@
export * from './face';
export * from './types';
export * from './utils';
@@ -16,3 +16,39 @@ export interface IFaceData {
/** 图块朝向 */
readonly face: FaceDirection;
}
export interface IRoleFaceBinder {
/**
* 给指定的图块分配朝向绑定
* @param identifier 图块数字
* @param main 主图块朝向,一般是朝下
*/
malloc(identifier: number, main: FaceDirection): void;
/**
* 将一个图块与另一个图块绑定朝向,需要注意要先调用 {@link malloc} 分配朝向信息
* @param identifier 当前图块数字
* @param main 主图块数字,即当前图块与目标图块属于主图块的另一个朝向
* @param face 当前图块的朝向方向
*/
bind(identifier: number, main: number, face: FaceDirection): void;
/**
* 获取一个图块指定朝向的图块数字
* @param identifier 图块数字,可以是任意朝向的图块数字
* @param face 要获取的朝向
*/
getFaceOf(identifier: number, face: FaceDirection): IFaceData | null;
/**
* 获取指定图块数字是哪个朝向
* @param identifier 图块数字
*/
getFaceDirection(identifier: number): FaceDirection | undefined;
/**
* 获取指定图块数字绑定至的主朝向
* @param identifier 图块数字,可以是任意朝向的图块数字
*/
getMainFace(identifier: number): IFaceData | null;
}
+2
View File
@@ -3,5 +3,7 @@ export * from './enemy';
export * from './flag';
export * from './hero';
export * from './load';
export * from './map';
export * from './game';
export * from './types';
+3
View File
@@ -0,0 +1,3 @@
export * from './layerState';
export * from './mapLayer';
export * from './types';
@@ -0,0 +1,139 @@
import {
Hookable,
HookController,
IHookController,
logger
} from '@motajs/common';
import {
ILayerState,
ILayerStateHooks,
IMapLayer,
IMapLayerHookController,
IMapLayerHooks
} from './types';
import { MapLayer } from './mapLayer';
export class LayerState
extends Hookable<ILayerStateHooks>
implements ILayerState
{
readonly layerList: Set<IMapLayer> = new Set();
/** 图层到图层别名映射 */
readonly layerAliasMap: WeakMap<IMapLayer, string> = new WeakMap();
/** 图层别名到图层的映射 */
readonly aliasLayerMap: Map<symbol, IMapLayer> = new Map();
/** 背景图块 */
private backgroundTile: number = 0;
/** 图层钩子映射 */
private layerHookMap: Map<IMapLayer, IMapLayerHookController> = new Map();
addLayer(width: number, height: number): IMapLayer {
const array = new Uint32Array(width * height);
const layer = new MapLayer(array, width, height);
this.layerList.add(layer);
this.forEachHook(hook => {
hook.onUpdateLayer?.(this.layerList);
});
const controller = layer.addHook(new StateMapLayerHook(this, layer));
this.layerHookMap.set(layer, controller);
controller.load();
return layer;
}
removeLayer(layer: IMapLayer): void {
this.layerList.delete(layer);
const alias = this.layerAliasMap.get(layer);
if (alias) {
const symbol = Symbol.for(alias);
this.aliasLayerMap.delete(symbol);
this.layerAliasMap.delete(layer);
}
this.forEachHook(hook => {
hook.onUpdateLayer?.(this.layerList);
});
const controller = this.layerHookMap.get(layer);
if (!controller) return;
controller.unload();
this.layerHookMap.delete(layer);
}
hasLayer(layer: IMapLayer): boolean {
return this.layerList.has(layer);
}
setLayerAlias(layer: IMapLayer, alias: string): void {
const symbol = Symbol.for(alias);
if (this.aliasLayerMap.has(symbol)) {
logger.warn(84, alias);
return;
}
this.layerAliasMap.set(layer, alias);
this.aliasLayerMap.set(symbol, layer);
}
getLayerByAlias(alias: string): IMapLayer | null {
const symbol = Symbol.for(alias);
return this.aliasLayerMap.get(symbol) ?? null;
}
getLayerAlias(layer: IMapLayer): string | undefined {
return this.layerAliasMap.get(layer);
}
resizeLayer(
layer: IMapLayer,
width: number,
height: number,
keepBlock: boolean = false
): void {
if (keepBlock) {
layer.resize(width, height);
} else {
layer.resize2(width, height);
}
}
setBackground(tile: number): void {
this.backgroundTile = tile;
this.forEachHook(hook => {
hook.onChangeBackground?.(tile);
});
}
getBackground(): number {
return this.backgroundTile;
}
protected createController(
hook: Partial<ILayerStateHooks>
): IHookController<ILayerStateHooks> {
return new HookController(this, hook);
}
}
class StateMapLayerHook implements Partial<IMapLayerHooks> {
constructor(
readonly state: LayerState,
readonly layer: IMapLayer
) {}
onUpdateArea(x: number, y: number, width: number, height: number): void {
this.state.forEachHook(hook => {
hook.onUpdateLayerArea?.(this.layer, x, y, width, height);
});
}
onUpdateBlock(block: number, x: number, y: number): void {
this.state.forEachHook(hook => {
hook.onUpdateLayerBlock?.(this.layer, block, x, y);
});
}
onResize(width: number, height: number): void {
this.state.forEachHook(hook => {
hook.onResizeLayer?.(this.layer, width, height);
});
}
}
+263
View File
@@ -0,0 +1,263 @@
import { isNil } from 'lodash-es';
import {
IMapLayer,
IMapLayerData,
IMapLayerHookController,
IMapLayerHooks
} from './types';
import { Hookable, HookController, logger } from '@motajs/common';
// todo: 提供 core.setBlock 等方法的替代方法,同时添加 setBlockList,以及前景背景的接口
export class MapLayer
extends Hookable<IMapLayerHooks, IMapLayerHookController>
implements IMapLayer
{
width: number;
height: number;
empty: boolean = true;
zIndex: number = 0;
/** 地图图块数组 */
private mapArray: Uint32Array;
/** 地图数据引用 */
private mapData: IMapLayerData;
constructor(array: Uint32Array, width: number, height: number) {
super();
this.width = width;
this.height = height;
const area = width * height;
this.mapArray = new Uint32Array(area);
// 超出的裁剪,不足的补零
this.mapArray.set(array);
this.mapData = {
expired: false,
array: this.mapArray
};
}
resize(width: number, height: number): void {
if (this.width === width && this.height === height) {
return;
}
this.mapData.expired = true;
const before = this.mapArray;
const beforeWidth = this.width;
const beforeHeight = this.height;
const beforeArea = beforeWidth * beforeHeight;
this.width = width;
this.height = height;
const area = width * height;
const newArray = new Uint32Array(area);
this.mapArray = newArray;
// 将原来的地图数组赋值给现在的
if (beforeArea > area) {
// 如果地图变小了,那么直接设置,不需要补零
for (let ny = 0; ny < height; ny++) {
const begin = ny * beforeWidth;
newArray.set(before.subarray(begin, begin + width), ny * width);
}
} else {
// 如果地图变大了,那么需要补零。因为新数组本来就是用 0 填充的,实际上只要赋值就可以了
for (let ny = 0; ny < beforeHeight; ny++) {
const begin = ny * beforeWidth;
newArray.set(
before.subarray(begin, begin + beforeWidth),
ny * width
);
}
}
this.mapData = {
expired: false,
array: this.mapArray
};
this.forEachHook(hook => {
hook.onResize?.(width, height);
});
}
resize2(width: number, height: number): void {
if (this.width === width && this.height === height) {
this.mapArray.fill(0);
return;
}
this.mapData.expired = true;
this.width = width;
this.height = height;
this.mapArray = new Uint32Array(width * height);
this.mapData = {
expired: false,
array: this.mapArray
};
this.empty = true;
this.forEachHook(hook => {
hook.onResize?.(width, height);
});
}
setBlock(block: number, x: number, y: number): void {
const index = y * this.width + x;
if (block === this.mapArray[index]) return;
this.mapArray[index] = block;
this.forEachHook(hook => {
hook.onUpdateBlock?.(block, x, y);
});
if (block !== 0) {
this.empty = false;
}
}
getBlock(x: number, y: number): number {
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
// 不在地图内,返回 -1
return -1;
}
return this.mapArray[y * this.width + x];
}
putMapData(array: Uint32Array, x: number, y: number, width: number): void {
if (array.length % width !== 0) {
logger.warn(8);
}
const height = Math.ceil(array.length / width);
if (width === this.width && height === this.height) {
this.mapArray.set(array);
this.forEachHook(hook => {
hook.onUpdateArea?.(x, y, width, height);
});
return;
}
const w = this.width;
const r = x + width;
const b = y + height;
if (x < 0 || y < 0 || r > w || b > this.height) {
logger.warn(9);
}
const nl = Math.max(x, 0);
const nt = Math.max(y, 0);
const nr = Math.min(r, w);
const nb = Math.min(b, this.height);
const nw = nr - nl;
const nh = nb - nt;
let empty = true;
for (let ny = 0; ny < nh; ny++) {
const start = ny * nw;
const offset = (ny + nt) * w + nl;
const sub = array.subarray(start, start + nw);
if (empty && sub.some(v => v !== 0)) {
// 空地图判断
empty = false;
}
this.mapArray.set(array.subarray(start, start + nw), offset);
}
this.forEachHook(hook => {
hook.onUpdateArea?.(x, y, width, height);
});
this.empty &&= empty;
}
getMapData(): Uint32Array;
getMapData(
x: number,
y: number,
width: number,
height: number
): Uint32Array;
getMapData(
x?: number,
y?: number,
width?: number,
height?: number
): Uint32Array {
if (isNil(x)) {
return new Uint32Array(this.mapArray);
}
if (isNil(y) || isNil(width) || isNil(height)) {
logger.warn(80);
return new Uint32Array();
}
const w = this.width;
const h = this.height;
const r = x + width;
const b = y + height;
if (x < 0 || y < 0 || r > w || b > h) {
logger.warn(81);
}
const res = new Uint32Array(width * height);
const arr = this.mapArray;
const nr = Math.min(r, w);
const nb = Math.min(b, h);
for (let nx = x; nx < nr; nx++) {
for (let ny = y; ny < nb; ny++) {
const origin = ny * w + nx;
const target = (ny - y) * width + (nx - x);
res[target] = arr[origin];
}
}
return res;
}
/**
* 获取地图数据的内部存储直接引用
*/
getMapRef(): IMapLayerData {
return this.mapData;
}
protected createController(
hook: Partial<IMapLayerHooks>
): IMapLayerHookController {
return new MapLayerHookController(this, hook);
}
setZIndex(zIndex: number): void {
this.zIndex = zIndex;
}
async openDoor(x: number, y: number): Promise<void> {
const index = y * this.width + x;
const num = this.mapArray[index];
if (num === 0) return;
await Promise.all(
this.forEachHook(hook => {
return hook.onOpenDoor?.(x, y);
})
);
this.setBlock(0, x, y);
}
async closeDoor(num: number, x: number, y: number): Promise<void> {
const index = y * this.width + x;
const nowNum = this.mapArray[index];
if (nowNum !== 0) {
logger.error(46, x.toString(), y.toString());
return;
}
await Promise.all(
this.forEachHook(hook => {
return hook.onCloseDoor?.(num, x, y);
})
);
this.setBlock(num, x, y);
}
}
class MapLayerHookController
extends HookController<IMapLayerHooks>
implements IMapLayerHookController
{
hookable: MapLayer;
constructor(
readonly layer: MapLayer,
hook: Partial<IMapLayerHooks>
) {
super(layer, hook);
this.hookable = layer;
}
getMapData(): Readonly<IMapLayerData> {
return this.layer.getMapRef();
}
}
+278
View File
@@ -0,0 +1,278 @@
import { IHookable, IHookBase, IHookController } from '@motajs/common';
export interface IMapLayerData {
/** 当前引用是否过期,当地图图层内部的地图数组引用更新时,此项会变为 `true` */
expired: boolean;
/** 地图图块数组,是对内部存储的直接引用 */
array: Uint32Array;
}
export interface IMapLayerHooks extends IHookBase {
/**
* 当地图大小发生变化时执行,如果调用了地图的 `resize` 方法,但是地图大小没变,则不会触发
* @param width 地图宽度
* @param height 地图高度
*/
onResize(width: number, height: number): void;
/**
* 当更新某个区域的图块时执行
* @param x 更新区域左上角横坐标
* @param y 更新区域左上角纵坐标
* @param width 更新区域宽度
* @param height 更新区域高度
*/
onUpdateArea(x: number, y: number, width: number, height: number): void;
/**
* 当更新某个点的图块时执行,如果设置的图块与原先一样,则不会触发此方法
* @param block 更新为的图块数字
* @param x 更新点横坐标
* @param y 更新点纵坐标
*/
onUpdateBlock(block: number, x: number, y: number): void;
/**
* 当开门时触发,返回一个 `Promise`,当相关动画执行完毕后兑现
* @param x 门横坐标
* @param y 门纵坐标
*/
onOpenDoor(x: number, y: number): Promise<void>;
/**
* 当关门时触发,返回一个 `Promise`,当相关动画执行完毕后兑现
* @param num 门的图块数字
* @param x 门横坐标
* @param y 门纵坐标
*/
onCloseDoor(num: number, x: number, y: number): Promise<void>;
}
export interface IMapLayerHookController
extends IHookController<IMapLayerHooks> {
/** 拓展所属的图层对象 */
readonly layer: IMapLayer;
/**
* 获取地图数据,是对内部存储的直接引用
*/
getMapData(): Readonly<IMapLayerData>;
}
export interface IMapLayer
extends IHookable<IMapLayerHooks, IMapLayerHookController> {
/** 地图宽度 */
readonly width: number;
/** 地图高度 */
readonly height: number;
/**
* 地图是否全部空白,此值具有充分性,但不具有必要性,
* 即如果其为 `true`,则地图一定空白,但是如果其为 `false`,那么地图也有可能空白
*/
readonly empty: boolean;
/** 图层纵深 */
readonly zIndex: number;
/**
* 调整地图尺寸,维持原有图块。如果尺寸变大,那么会补零,如果尺寸变小,那么会将当前数组裁剪
* @param width 地图宽度
* @param height 地图高度
*/
resize(width: number, height: number): void;
/**
* 调整地图尺寸,但是将地图全部重置为零,不保留原地图数据
* @param width 地图宽度
* @param height 地图高度
*/
resize2(width: number, height: number): void;
/**
* 设置某一点的图块
* @param block 图块数字
* @param x 图块横坐标
* @param y 图块纵坐标
*/
setBlock(block: number, x: number, y: number): void;
/**
* 获取指定点的图块
* @param x 图块横坐标
* @param y 图块纵坐标
* @returns 指定点的图块,如果没有图块,返回 0,如果不在地图上,返回 -1
*/
getBlock(x: number, y: number): number;
/**
* 设置地图图块
* @param array 地图图块数组
* @param x 数组第一项代表的横坐标
* @param y 数组第一项代表的纵坐标
* @param width 传入数组所表示的矩形范围的宽度
*/
putMapData(array: Uint32Array, x: number, y: number, width: number): void;
/**
* 获取整个地图的地图数组,是对内部地图数组的拷贝,并不能通过修改它来直接修改地图内容
*/
getMapData(): Uint32Array;
/**
* 获取地图指定区域的地图数组,是对内部地图数组的拷贝,并不能通过修改它来直接修改地图内容
* @param x 左上角横坐标
* @param y 左上角纵坐标
* @param width 获取区域的宽度
* @param height 获取区域的高度
*/
getMapData(
x: number,
y: number,
width: number,
height: number
): Uint32Array;
/**
* 获取整个地图的地图数组,是对内部数组的直接引用
*/
getMapRef(): IMapLayerData;
/**
* 设置地图纵深,会影响渲染的遮挡顺序
* @param zIndex 纵深
*/
setZIndex(zIndex: number): void;
/**
* 开启指定位置的门
* @param x 门横坐标
* @param y 门纵坐标
*/
openDoor(x: number, y: number): Promise<void>;
/**
* 在指定位置关门,门的图块数字由参数指定
* @param num 门图块数字
* @param x 门横坐标
* @param y 门纵坐标
*/
closeDoor(num: number, x: number, y: number): Promise<void>;
}
export interface ILayerStateHooks extends IHookBase {
/**
* 当设置背景图块时执行,如果设置的背景图块与原先一样,则不会执行
* @param tile 背景图块
*/
onChangeBackground(tile: number): void;
/**
* 当地图列表发生变化时执行
* @param layerList 地图图层列表
*/
onUpdateLayer(layerList: Set<IMapLayer>): void;
/**
* 当地图状态对象的某个图层发生区域更新时执行
* @param layer 触发更新的地图图层对象
* @param x 更新区域左上角横坐标
* @param y 更新区域左上角纵坐标
* @param width 更新区域宽度
* @param height 更新区域高度
*/
onUpdateLayerArea(
layer: IMapLayer,
x: number,
y: number,
width: number,
height: number
): void;
/**
* 当地图状态对象的某个图层设置图块时执行,如果设置的图块与原先一样则不会触发
* @param layer 触发更新的地图图层对象
* @param block 设置为的图块
* @param x 图块横坐标
* @param y 图块纵坐标
*/
onUpdateLayerBlock(
layer: IMapLayer,
block: number,
x: number,
y: number
): void;
/**
* 当地图状态对象的某个图层大小发生变化时执行
* @param layer 触发更新的地图图层对象
* @param width 地图的新宽度
* @param height 地图的新高度
*/
onResizeLayer(layer: IMapLayer, width: number, height: number): void;
}
export interface ILayerState extends IHookable<ILayerStateHooks> {
/** 地图列表 */
readonly layerList: Set<IMapLayer>;
/**
* 添加图层
* @param width 地图宽度
* @param height 地图高度
*/
addLayer(width: number, height: number): IMapLayer;
/**
* 移除指定图层
* @param layer 图层对象
*/
removeLayer(layer: IMapLayer): void;
/**
* 当前地图状态对象是否包含指定图层对象
* @param layer 图层对象
*/
hasLayer(layer: IMapLayer): boolean;
/**
* 设置图层别名
* @param layer 图层对象
* @param alias 图层别名
*/
setLayerAlias(layer: IMapLayer, alias: string): void;
/**
* 根据图层别名获取图层对象
* @param alias 图层别名
*/
getLayerByAlias(alias: string): IMapLayer | null;
/**
* 获取图层对象的别名
* @param layer 图层对象
*/
getLayerAlias(layer: IMapLayer): string | undefined;
/**
* 重新设置图层的大小
* @param layer 图层对象
* @param width 新的图层宽度
* @param height 新的图层高度
* @param keepBlock 是否保留原有图块,默认不保留
*/
resizeLayer(
layer: IMapLayer,
width: number,
height: number,
keepBlock?: boolean
): void;
/**
* 设置背景图块
* @param tile 背景图块数字
*/
setBackground(tile: number): void;
/**
* 获取背景图块数字,如果没有设置过,则返回 0
*/
getBackground(): number;
}
+50
View File
@@ -0,0 +1,50 @@
import { IMotaDataLoader } from './load';
import { ILoadProgressTotal } from '@motajs/loader';
import { IHeroFollower, IHeroState } from './hero';
import { IEnemyContext, IEnemyManager } from './enemy';
import { IFlagSystem } from './flag';
import { IRoleFaceBinder } from './common';
import { ILayerState } from './map';
export interface IStateSaveData {
/** 跟随者列表 */
readonly followers: readonly IHeroFollower[];
}
export interface IStateBase<TEnemy, THero> {
/** 朝向绑定 */
readonly roleFace: IRoleFaceBinder;
/** id 到图块数字的映射 */
readonly idNumberMap: Map<string, number>;
/** 图块数字到 id 的映射 */
readonly numberIdMap: Map<number, string>;
/** 加载进度对象 */
readonly loadProgress: ILoadProgressTotal;
/** 数据端加载对象 */
readonly dataLoader: IMotaDataLoader;
/** 地图状态 */
readonly layer: ILayerState;
/** 勇士状态 */
readonly hero: IHeroState<THero>;
/** 怪物管理器 */
readonly enemyManager: IEnemyManager<TEnemy>;
/** 怪物上下文 */
readonly enemyContext: IEnemyContext<TEnemy, THero>;
/** Flag 系统 */
readonly flags: IFlagSystem;
/**
* 保存当前状态
*/
saveState(): IStateSaveData;
/**
* 加载状态
* @param state 状态对象
*/
loadState(state: IStateSaveData): void;
}