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

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