mirror of
https://github.com/motajs/template.git
synced 2026-09-01 00:52:28 +08:00
refactor: 游戏地图接口重构
This commit is contained in:
parent
1717400945
commit
7eeab32134
@ -51,10 +51,11 @@
|
|||||||
13. **非空判断**:对象直接用 `if (!object)`;字面量使用 `lodash` 的 `isNil`(`if (isNil(value))`),不得使用 `if (value === undefined)` 等方式。
|
13. **非空判断**:对象直接用 `if (!object)`;字面量使用 `lodash` 的 `isNil`(`if (isNil(value))`),不得使用 `if (value === undefined)` 等方式。
|
||||||
14. **公共方法**:不得出现仅在类中定义的公共方法,所有公共方法必须先在接口中定义,然后在类中使用 `implements` 实现。
|
14. **公共方法**:不得出现仅在类中定义的公共方法,所有公共方法必须先在接口中定义,然后在类中使用 `implements` 实现。
|
||||||
15. **成员只读**:所有需要被实现为类的接口,其成员必须只读,如果有赋值需求,那么应使用相应的方法来完成赋值操作。
|
15. **成员只读**:所有需要被实现为类的接口,其成员必须只读,如果有赋值需求,那么应使用相应的方法来完成赋值操作。
|
||||||
|
16. **对象类型**:对于任何接口或类的对象成员,必须使用接口作为类型,不得使用类作为类型,例如 `map: IGameMap` 符合要求,但 `map: GameMap` 不符合要求。
|
||||||
|
|
||||||
## 架构约束
|
## 架构约束
|
||||||
|
|
||||||
16. **渲染端被动**:任何情况下渲染端不会主动向数据端推送更新。渲染端仅通过钩子与数据端通信,被动获取信息,仅在某些情况下通过钩子影响数据端行为。
|
17. **渲染端被动**:任何情况下渲染端不会主动向数据端推送更新。渲染端仅通过钩子与数据端通信,被动获取信息,仅在某些情况下通过钩子影响数据端行为。
|
||||||
|
|
||||||
# 开发流程
|
# 开发流程
|
||||||
|
|
||||||
|
|||||||
3
dev.md
3
dev.md
@ -76,11 +76,12 @@
|
|||||||
- 长文件可使用 `#region` / `#endregion` 分段以支持折叠。
|
- 长文件可使用 `#region` / `#endregion` 分段以支持折叠。
|
||||||
- TODO 使用 `// TODO:` 或 `// todo:` 格式。
|
- TODO 使用 `// TODO:` 或 `// todo:` 格式。
|
||||||
- 单行注释的 `//` 与注释内容之间留一个空格;不允许出现非 jsDoc 的多行注释,如需多行注释,使用多个单行注释代替。
|
- 单行注释的 `//` 与注释内容之间留一个空格;不允许出现非 jsDoc 的多行注释,如需多行注释,使用多个单行注释代替。
|
||||||
- 注释合理换行:考虑中文字符较宽,建议每 40–60 个字符在标点符号后换行,不要频繁换行,每行长度应差不多,不允许在句中换行;参数注释换行后保持对齐。
|
- 注释合理换行:考虑中文字符较宽,建议每 40–60 个字符在标点符号后换行,不要频繁换行,每行长度应差不多,不要过短也不要过长,不允许在句中换行;参数注释换行后保持对齐。
|
||||||
- 单行注释结尾不加句号;较长的多行注释结尾可加句号。
|
- 单行注释结尾不加句号;较长的多行注释结尾可加句号。
|
||||||
- 一般不建议给接口(`inteface` 本身)、类型别名、枚举或类本身写注释(即需要紧接着一长段缩进的时候,这时候写注释不好看),特殊情况除外。
|
- 一般不建议给接口(`inteface` 本身)、类型别名、枚举或类本身写注释(即需要紧接着一长段缩进的时候,这时候写注释不好看),特殊情况除外。
|
||||||
- 对于 jsDoc 注释,方法注释必须使用换行风格;对于成员 jsDoc 注释,除非注释较长需要换行,否则使用不换行的风格。
|
- 对于 jsDoc 注释,方法注释必须使用换行风格;对于成员 jsDoc 注释,除非注释较长需要换行,否则使用不换行的风格。
|
||||||
- 代码中的注释必须包含价值,说明下一行为什么会出现,不得出现解释下一行代码等类型的无价值注释,如 `// 清空 Xxx` 就属于无价值注释。
|
- 代码中的注释必须包含价值,说明下一行为什么会出现,不得出现解释下一行代码等类型的无价值注释,如 `// 清空 Xxx` 就属于无价值注释。
|
||||||
|
- jsDoc 注释应该包含主要信息,不允许假设其使用场景,非必要不使用符号或英文单词,注释的目的是让用户看完注释就立刻知道接口的用处,而不是给写代码的人看,不得为了注释的正确性而使用奇怪的语序。
|
||||||
|
|
||||||
### 类型规范
|
### 类型规范
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { BaseProps, TagDefine } from '@motajs/render-vue';
|
import { BaseProps, TagDefine } from '@motajs/render-vue';
|
||||||
import { ERenderItemEvent, SizedCanvasImageSource } from '@motajs/render';
|
import { ERenderItemEvent, SizedCanvasImageSource } from '@motajs/render';
|
||||||
import { ILayerState } from '@user/data-base';
|
import { IGameMap } from '@user/data-base';
|
||||||
import { IMapExtensionManager, IMapRenderer } from '../map';
|
import { IMapExtensionManager, IMapRenderer } from '../map';
|
||||||
|
|
||||||
export interface IconProps extends BaseProps {
|
export interface IconProps extends BaseProps {
|
||||||
@ -24,7 +24,7 @@ export interface WinskinProps extends BaseProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MapRenderProps extends BaseProps {
|
export interface MapRenderProps extends BaseProps {
|
||||||
layerState: ILayerState;
|
layerState: IGameMap;
|
||||||
renderer: IMapRenderer;
|
renderer: IMapRenderer;
|
||||||
extension: IMapExtensionManager;
|
extension: IMapExtensionManager;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { MotaOffscreenCanvas2D, RenderItem } from '@motajs/render';
|
import { MotaOffscreenCanvas2D, RenderItem } from '@motajs/render';
|
||||||
import { ILayerState } from '@user/data-base';
|
import { IGameMap } from '@user/data-base';
|
||||||
import { IMapRenderer } from './types';
|
import { IMapRenderer } from './types';
|
||||||
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
||||||
import { CELL_HEIGHT, CELL_WIDTH, MAP_HEIGHT, MAP_WIDTH } from '../../shared';
|
import { CELL_HEIGHT, CELL_WIDTH, MAP_HEIGHT, MAP_WIDTH } from '../../shared';
|
||||||
@ -11,7 +11,7 @@ export class MapRenderItem extends RenderItem {
|
|||||||
* @param renderer 地图渲染器对象
|
* @param renderer 地图渲染器对象
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
readonly layerState: ILayerState,
|
readonly layerState: IGameMap,
|
||||||
readonly renderer: IMapRenderer,
|
readonly renderer: IMapRenderer,
|
||||||
readonly exManager: IMapExtensionManager
|
readonly exManager: IMapExtensionManager
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import {
|
|||||||
MapTileBehavior,
|
MapTileBehavior,
|
||||||
MapTileSizeTestMode
|
MapTileSizeTestMode
|
||||||
} from './types';
|
} from './types';
|
||||||
import { ILayerState, ILayerStateHooks, IMapLayer } from '@user/data-base';
|
import { IGameMap, IGameMapHooks, IMapLayer } from '@user/data-base';
|
||||||
import { IHookController, logger } from '@motajs/common';
|
import { IHookController, logger } from '@motajs/common';
|
||||||
import { compileProgramWith } from '@motajs/client-base';
|
import { compileProgramWith } from '@motajs/client-base';
|
||||||
import { isNil, maxBy } from 'lodash-es';
|
import { isNil, maxBy } from 'lodash-es';
|
||||||
@ -78,9 +78,9 @@ export class MapRenderer
|
|||||||
assetWidth: number = 4096;
|
assetWidth: number = 4096;
|
||||||
assetHeight: number = 4096;
|
assetHeight: number = 4096;
|
||||||
|
|
||||||
layerState: ILayerState;
|
layerState: IGameMap;
|
||||||
/** 地图状态钩子控制器 */
|
/** 地图状态钩子控制器 */
|
||||||
private layerStateHook: IHookController<ILayerStateHooks>;
|
private layerStateHook: IHookController<IGameMapHooks>;
|
||||||
|
|
||||||
/** 排序后的图层 */
|
/** 排序后的图层 */
|
||||||
private sortedLayers: IMapLayer[] = [];
|
private sortedLayers: IMapLayer[] = [];
|
||||||
@ -210,7 +210,7 @@ export class MapRenderer
|
|||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
readonly manager: IMaterialManager,
|
readonly manager: IMaterialManager,
|
||||||
layerState: ILayerState
|
layerState: IGameMap
|
||||||
) {
|
) {
|
||||||
this.movingIndexPool.push(
|
this.movingIndexPool.push(
|
||||||
...Array.from({ length: this.movingCount }, (_, i) => i).reverse()
|
...Array.from({ length: this.movingCount }, (_, i) => i).reverse()
|
||||||
@ -427,7 +427,7 @@ export class MapRenderer
|
|||||||
this.vertex.updateLayerArray();
|
this.vertex.updateLayerArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
setLayerState(layerState: ILayerState): void {
|
setLayerState(layerState: IGameMap): void {
|
||||||
if (layerState === this.layerState) return;
|
if (layerState === this.layerState) return;
|
||||||
this.layerStateHook.unload();
|
this.layerStateHook.unload();
|
||||||
this.layerState = layerState;
|
this.layerState = layerState;
|
||||||
@ -1735,7 +1735,7 @@ export class MapRenderer
|
|||||||
//#endregion
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
class RendererLayerStateHook implements Partial<ILayerStateHooks> {
|
class RendererLayerStateHook implements Partial<IGameMapHooks> {
|
||||||
constructor(readonly renderer: MapRenderer) {}
|
constructor(readonly renderer: MapRenderer) {}
|
||||||
|
|
||||||
onChangeBackground(tile: number): void {
|
onChangeBackground(tile: number): void {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
IMaterialManager,
|
IMaterialManager,
|
||||||
ITrackedAssetData
|
ITrackedAssetData
|
||||||
} from '@user/client-base';
|
} from '@user/client-base';
|
||||||
import { ILayerState, IMapLayer } from '@user/data-base';
|
import { IGameMap, IMapLayer } from '@user/data-base';
|
||||||
import { TimingFn } from 'mutate-animate';
|
import { TimingFn } from 'mutate-animate';
|
||||||
|
|
||||||
export const enum MapBackgroundRepeat {
|
export const enum MapBackgroundRepeat {
|
||||||
@ -339,7 +339,7 @@ export interface IMapRenderer {
|
|||||||
/** 顶点数组生成器 */
|
/** 顶点数组生成器 */
|
||||||
readonly vertex: IMapVertexGenerator;
|
readonly vertex: IMapVertexGenerator;
|
||||||
/** 使用的地图状态对象 */
|
/** 使用的地图状态对象 */
|
||||||
readonly layerState: ILayerState;
|
readonly layerState: IGameMap;
|
||||||
|
|
||||||
/** 地图宽度 */
|
/** 地图宽度 */
|
||||||
readonly mapWidth: number;
|
readonly mapWidth: number;
|
||||||
@ -432,7 +432,7 @@ export interface IMapRenderer {
|
|||||||
* 设置渲染器使用的地图状态
|
* 设置渲染器使用的地图状态
|
||||||
* @param layerState 地图状态
|
* @param layerState 地图状态
|
||||||
*/
|
*/
|
||||||
setLayerState(layerState: ILayerState): void;
|
setLayerState(layerState: IGameMap): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据标识符获取图层
|
* 根据标识符获取图层
|
||||||
|
|||||||
@ -8,13 +8,13 @@ import { FaceDirection, IDataCommon, IFaceHandler } from '@user/data-common';
|
|||||||
import { HeroMover } from './mover';
|
import { HeroMover } from './mover';
|
||||||
import {
|
import {
|
||||||
IHeroLocation,
|
IHeroLocation,
|
||||||
IHeroLocationHook,
|
IHeroLocationHooks,
|
||||||
IHeroLocationSave,
|
IHeroLocationSave,
|
||||||
IHeroMover
|
IHeroMover
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export class HeroLocation
|
export class HeroLocation
|
||||||
extends Hookable<IHeroLocationHook>
|
extends Hookable<IHeroLocationHooks>
|
||||||
implements IHeroLocation
|
implements IHeroLocation
|
||||||
{
|
{
|
||||||
x: number;
|
x: number;
|
||||||
@ -39,8 +39,8 @@ export class HeroLocation
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected createController(
|
protected createController(
|
||||||
hook: Partial<IHeroLocationHook>
|
hook: Partial<IHeroLocationHooks>
|
||||||
): IHookController<IHeroLocationHook> {
|
): IHookController<IHeroLocationHooks> {
|
||||||
return new HookController(this, hook);
|
return new HookController(this, hook);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import {
|
|||||||
IModifierStateSave,
|
IModifierStateSave,
|
||||||
IReadonlyHeroAttribute,
|
IReadonlyHeroAttribute,
|
||||||
IHeroChangeFloorInfo,
|
IHeroChangeFloorInfo,
|
||||||
IHeroStateHook
|
IHeroStateHooks
|
||||||
} from './types';
|
} from './types';
|
||||||
import {
|
import {
|
||||||
FaceDirection,
|
FaceDirection,
|
||||||
@ -34,7 +34,7 @@ import {
|
|||||||
} from '@motajs/common';
|
} from '@motajs/common';
|
||||||
|
|
||||||
export class HeroState<THero>
|
export class HeroState<THero>
|
||||||
extends Hookable<IHeroStateHook>
|
extends Hookable<IHeroStateHooks>
|
||||||
implements IHeroState<THero>
|
implements IHeroState<THero>
|
||||||
{
|
{
|
||||||
/** 修饰器工厂函数注册表 */
|
/** 修饰器工厂函数注册表 */
|
||||||
@ -72,8 +72,8 @@ export class HeroState<THero>
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected createController(
|
protected createController(
|
||||||
hook: Partial<IHeroStateHook>
|
hook: Partial<IHeroStateHooks>
|
||||||
): IHookController<IHeroStateHook> {
|
): IHookController<IHeroStateHooks> {
|
||||||
return new HookController(this, hook);
|
return new HookController(this, hook);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -227,7 +227,7 @@ export interface IHeroAttribute<THero> extends IReadonlyHeroAttribute<THero> {
|
|||||||
|
|
||||||
//#region 勇士位置
|
//#region 勇士位置
|
||||||
|
|
||||||
export interface IHeroLocationHook extends IHookBase {
|
export interface IHeroLocationHooks extends IHookBase {
|
||||||
/**
|
/**
|
||||||
* 当设置勇士位置时触发
|
* 当设置勇士位置时触发
|
||||||
* @param x 设置为的横坐标
|
* @param x 设置为的横坐标
|
||||||
@ -257,7 +257,7 @@ export interface IHeroLocation
|
|||||||
extends
|
extends
|
||||||
ISaveableContent<IHeroLocationSave>,
|
ISaveableContent<IHeroLocationSave>,
|
||||||
IObjectMovable,
|
IObjectMovable,
|
||||||
IHookable<IHeroLocationHook>,
|
IHookable<IHeroLocationHooks>,
|
||||||
IDataCommonExtended {
|
IDataCommonExtended {
|
||||||
/** 当前所在楼层 id,undefined 表示尚不处于任何楼层 */
|
/** 当前所在楼层 id,undefined 表示尚不处于任何楼层 */
|
||||||
readonly floorId: string | undefined;
|
readonly floorId: string | undefined;
|
||||||
@ -779,7 +779,7 @@ export interface IHeroChangeFloorInfo {
|
|||||||
readonly face: FaceDirection;
|
readonly face: FaceDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IHeroStateHook extends IHookBase {
|
export interface IHeroStateHooks extends IHookBase {
|
||||||
/**
|
/**
|
||||||
* 当勇士切换楼层前触发,此钩子执行完毕后会立刻触发 `IHeroLocation` 的 `onSetFloor` 钩子。
|
* 当勇士切换楼层前触发,此钩子执行完毕后会立刻触发 `IHeroLocation` 的 `onSetFloor` 钩子。
|
||||||
* 此钩子应该用于切换楼层前的过渡及必要的数据准备,不应该修改勇士位置相关的数据。
|
* 此钩子应该用于切换楼层前的过渡及必要的数据准备,不应该修改勇士位置相关的数据。
|
||||||
@ -813,7 +813,9 @@ export interface IHeroStateSave<THero> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IHeroState<THero>
|
export interface IHeroState<THero>
|
||||||
extends ISaveableContent<IHeroStateSave<THero>>, IHookable<IHeroStateHook> {
|
extends
|
||||||
|
ISaveableContent<IHeroStateSave<THero>>,
|
||||||
|
IHookable<IHeroStateHooks> {
|
||||||
/** 勇士移动对象 */
|
/** 勇士移动对象 */
|
||||||
readonly location: IHeroLocation;
|
readonly location: IHeroLocation;
|
||||||
/** 勇士属性对象 */
|
/** 勇士属性对象 */
|
||||||
|
|||||||
@ -1,183 +0,0 @@
|
|||||||
import {
|
|
||||||
HookController,
|
|
||||||
Hookable,
|
|
||||||
IHookController,
|
|
||||||
ITileLocator,
|
|
||||||
logger
|
|
||||||
} from '@motajs/common';
|
|
||||||
import {
|
|
||||||
IDynamicLayer,
|
|
||||||
IDynamicLayerHooks,
|
|
||||||
IDynamicTile,
|
|
||||||
IMapLayer
|
|
||||||
} from './types';
|
|
||||||
import { FaceDirection, IDataCommon, degradeFace } from '@user/data-common';
|
|
||||||
import { DynamicTile } from './dynamicTile';
|
|
||||||
|
|
||||||
export class DynamicLayer
|
|
||||||
extends Hookable<IDynamicLayerHooks>
|
|
||||||
implements IDynamicLayer
|
|
||||||
{
|
|
||||||
readonly state: IDataCommon;
|
|
||||||
|
|
||||||
/** 坐标到动态图块集合的映射,外层 key = y,内层 key = x,不使用 index 是为了支持地图外图块 */
|
|
||||||
private readonly tilePosMap: Map<number, Map<number, Set<IDynamicTile>>> =
|
|
||||||
new Map();
|
|
||||||
/** 动态图块到其当前坐标的映射 */
|
|
||||||
private readonly posTileMap: Map<IDynamicTile, ITileLocator> = new Map();
|
|
||||||
|
|
||||||
constructor(public readonly layer: IMapLayer) {
|
|
||||||
super();
|
|
||||||
this.state = layer.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected createController(
|
|
||||||
hook: Partial<IDynamicLayerHooks>
|
|
||||||
): IHookController<IDynamicLayerHooks> {
|
|
||||||
return new HookController(this, hook);
|
|
||||||
}
|
|
||||||
|
|
||||||
createDynamic(num: number, x: number, y: number): IDynamicTile {
|
|
||||||
const tile = new DynamicTile(num, x, y, this);
|
|
||||||
tile.setTriggerType(this.layer.getTriggerType(x, y));
|
|
||||||
this.addTileToPosMap(tile, x, y);
|
|
||||||
this.posTileMap.set(tile, { x, y });
|
|
||||||
this.forEachHook(hook => hook.onCreateTile?.(tile, this));
|
|
||||||
return tile;
|
|
||||||
}
|
|
||||||
|
|
||||||
transferToDynamic(
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
keepTrigger: boolean = true
|
|
||||||
): IDynamicTile {
|
|
||||||
const num = this.layer.getBlock(x, y);
|
|
||||||
const triggerType = keepTrigger ? this.layer.getTriggerType(x, y) : -1;
|
|
||||||
if (num === 0) {
|
|
||||||
logger.warn(127, x.toString(), y.toString());
|
|
||||||
}
|
|
||||||
this.layer.setBlock(0, x, y);
|
|
||||||
this.layer.revertTrigger(x, y);
|
|
||||||
const tile = this.createDynamic(num, x, y);
|
|
||||||
tile.setTriggerType(triggerType);
|
|
||||||
return tile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将动态图块上的触发器同步回当前静态格点
|
|
||||||
*/
|
|
||||||
private syncStaticTrigger(tile: IDynamicTile, keepTrigger: boolean): void {
|
|
||||||
if (keepTrigger) {
|
|
||||||
this.layer.setTriggerType(tile.triggerType, tile.x, tile.y);
|
|
||||||
} else {
|
|
||||||
this.layer.revertTrigger(tile.x, tile.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transferToStatic(tile: IDynamicTile, keepTrigger: boolean = true): void {
|
|
||||||
const { x, y } = tile;
|
|
||||||
const { width, height } = this.layer;
|
|
||||||
if (x < 0 || y < 0 || x >= width || y >= height) {
|
|
||||||
logger.warn(128, x.toString(), y.toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.layer.getBlock(x, y) !== 0) {
|
|
||||||
logger.warn(129, x.toString(), y.toString());
|
|
||||||
}
|
|
||||||
this.layer.setBlock(tile.num, x, y);
|
|
||||||
this.syncStaticTrigger(tile, keepTrigger);
|
|
||||||
this.removeTile(tile);
|
|
||||||
this.forEachHook(hook => hook.onDeleteTile?.(tile, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
transferToStaticIfSafe(
|
|
||||||
tile: IDynamicTile,
|
|
||||||
keepTrigger: boolean = true
|
|
||||||
): boolean {
|
|
||||||
const { x, y } = tile;
|
|
||||||
const { width, height } = this.layer;
|
|
||||||
if (x < 0 || y < 0 || x >= width || y >= height) {
|
|
||||||
logger.warn(128, x.toString(), y.toString());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.layer.getBlock(tile.x, tile.y) !== 0) return false;
|
|
||||||
this.layer.setBlock(tile.num, x, y);
|
|
||||||
this.syncStaticTrigger(tile, keepTrigger);
|
|
||||||
this.removeTile(tile);
|
|
||||||
this.forEachHook(hook => hook.onDeleteTile?.(tile, this));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteDynamic(tile: IDynamicTile): Promise<void> {
|
|
||||||
if (!this.posTileMap.has(tile)) {
|
|
||||||
logger.warn(130);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.removeTile(tile);
|
|
||||||
const hooks = this.forEachHook(hook => hook.onDeleteTile?.(tile, this));
|
|
||||||
await Promise.all(hooks);
|
|
||||||
}
|
|
||||||
|
|
||||||
getDynamicTilesAt(x: number, y: number): Iterable<IDynamicTile> {
|
|
||||||
return this.tilePosMap.get(y)?.get(x) ?? new Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
iterateDynamicTiles(): Iterable<IDynamicTile> {
|
|
||||||
return this.posTileMap.keys();
|
|
||||||
}
|
|
||||||
|
|
||||||
setDynamicDirection(tile: IDynamicTile, direction: FaceDirection): void {
|
|
||||||
const numBefore = tile.num;
|
|
||||||
tile.setFaceDirection(direction);
|
|
||||||
if (tile.num !== numBefore) return;
|
|
||||||
const degraded = degradeFace(direction);
|
|
||||||
if (degraded !== direction) {
|
|
||||||
tile.setFaceDirection(degraded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDynamicTile(tile: IDynamicTile): void {
|
|
||||||
const oldPos = this.posTileMap.get(tile);
|
|
||||||
if (oldPos) {
|
|
||||||
this.removeTileFromPosMap(tile, oldPos.x, oldPos.y);
|
|
||||||
oldPos.x = tile.x;
|
|
||||||
oldPos.y = tile.y;
|
|
||||||
this.addTileToPosMap(tile, tile.x, tile.y);
|
|
||||||
} else {
|
|
||||||
this.addTileToPosMap(tile, tile.x, tile.y);
|
|
||||||
this.posTileMap.set(tile, { x: tile.x, y: tile.y });
|
|
||||||
}
|
|
||||||
this.forEachHook(hook => hook.onUpdateTilePosition?.(tile, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将动态图块登记到指定坐标的索引表中
|
|
||||||
*/
|
|
||||||
private addTileToPosMap(tile: IDynamicTile, x: number, y: number): void {
|
|
||||||
const xMap = this.tilePosMap.getOrInsertComputed(y, () => new Map());
|
|
||||||
const set = xMap.getOrInsertComputed(x, () => new Set());
|
|
||||||
set.add(tile);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将动态图块从指定坐标的索引表中移除
|
|
||||||
*/
|
|
||||||
private removeTileFromPosMap(
|
|
||||||
tile: IDynamicTile,
|
|
||||||
x: number,
|
|
||||||
y: number
|
|
||||||
): void {
|
|
||||||
this.tilePosMap.get(y)?.get(x)?.delete(tile);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从两个内部映射中移除图块记录
|
|
||||||
*/
|
|
||||||
private removeTile(tile: IDynamicTile): void {
|
|
||||||
const pos = this.posTileMap.get(tile);
|
|
||||||
if (pos) {
|
|
||||||
this.removeTileFromPosMap(tile, pos.x, pos.y);
|
|
||||||
}
|
|
||||||
this.posTileMap.delete(tile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,70 +1,89 @@
|
|||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
import {
|
import {
|
||||||
FaceDirection,
|
FaceDirection,
|
||||||
IDataCommon,
|
|
||||||
IMoverController,
|
IMoverController,
|
||||||
IObjectMover,
|
IObjectMover,
|
||||||
IRoleFaceBinder,
|
|
||||||
ITileRawData
|
ITileRawData
|
||||||
} from '@user/data-common';
|
} from '@user/data-common';
|
||||||
import { IDynamicLayer, IDynamicTile } from './types';
|
import {
|
||||||
|
IDynamicBlockSave,
|
||||||
|
IDynamicTile,
|
||||||
|
IMapLayer,
|
||||||
|
IStaticTile
|
||||||
|
} from './types';
|
||||||
import { DynamicTileMover } from './mover';
|
import { DynamicTileMover } from './mover';
|
||||||
import { logger } from '@motajs/common';
|
import { logger } from '@motajs/common';
|
||||||
|
import { MapTileBase } from './tile';
|
||||||
|
|
||||||
export class DynamicTile implements IDynamicTile {
|
export class DynamicTile
|
||||||
readonly state: IDataCommon;
|
extends MapTileBase<IDynamicBlockSave>
|
||||||
|
implements IDynamicTile
|
||||||
|
{
|
||||||
readonly mover: IObjectMover<IDynamicTile>;
|
readonly mover: IObjectMover<IDynamicTile>;
|
||||||
|
|
||||||
raw: ITileRawData | null;
|
/** 内部存储的图块数字 */
|
||||||
triggerType: number;
|
private tileNum: number;
|
||||||
|
/** 内部存储的原始图块信息 */
|
||||||
/** 当前的朝向绑定对象 */
|
private tileRaw: ITileRawData | null;
|
||||||
private face: IRoleFaceBinder | null = null;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public num: number,
|
num: number,
|
||||||
public x: number,
|
public x: number,
|
||||||
public y: number,
|
public y: number,
|
||||||
public readonly layer: IDynamicLayer
|
layer: IMapLayer
|
||||||
) {
|
) {
|
||||||
this.state = layer.state;
|
super(x, y, layer);
|
||||||
|
this.tileNum = num;
|
||||||
this.mover = new DynamicTileMover(this);
|
this.mover = new DynamicTileMover(this);
|
||||||
this.triggerType = -1;
|
|
||||||
const data = this.state.tileStore.getData(num);
|
const data = this.state.tileStore.getData(num);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
logger.warn(143, num.toString());
|
logger.warn(143, num.toString());
|
||||||
this.raw = null;
|
this.tileRaw = null;
|
||||||
} else {
|
} else {
|
||||||
this.raw = data;
|
this.tileRaw = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setFaceBinder(binder: IRoleFaceBinder | null): void {
|
num(): number {
|
||||||
this.face = binder;
|
return this.tileNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFaceDirection(direction: FaceDirection): number {
|
raw(): ITileRawData | null {
|
||||||
if (!this.face) return this.num;
|
return this.tileRaw;
|
||||||
const next = this.face.getFaceOf(this.num, direction);
|
}
|
||||||
if (next) {
|
|
||||||
this.num = next.identifier;
|
set(num: number): void {
|
||||||
|
this.tileNum = num;
|
||||||
|
const data = this.state.tileStore.getData(num);
|
||||||
|
if (!data) {
|
||||||
|
logger.warn(143, num.toString());
|
||||||
|
this.tileRaw = null;
|
||||||
|
} else {
|
||||||
|
this.tileRaw = data;
|
||||||
}
|
}
|
||||||
return this.num;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setTriggerType(type: number): void {
|
setPos(x: number, y: number): void {
|
||||||
this.triggerType = type;
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.locator = { x, y };
|
||||||
|
this.layer.updateDynamicTile(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(): Promise<void> {
|
getCurrentFaceDirection(): FaceDirection {
|
||||||
return this.layer.deleteDynamic(this);
|
const curr = this.layer.faceBinder.getFaceDirection(this.tileNum);
|
||||||
|
if (isNil(curr)) {
|
||||||
|
return FaceDirection.Unknown;
|
||||||
|
} else {
|
||||||
|
return curr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toStatic(): void {
|
toStatic(): IStaticTile | null {
|
||||||
this.layer.transferToStatic(this);
|
return this.layer.transferToStatic(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
toStaticIfSafe(): boolean {
|
toStaticIfSafe(): IStaticTile | null {
|
||||||
return this.layer.transferToStaticIfSafe(this);
|
return this.layer.transferToStaticIfSafe(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,19 +93,31 @@ export class DynamicTile implements IDynamicTile {
|
|||||||
return this.mover.start();
|
return this.mover.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
setPos(x: number, y: number): void {
|
delete(): Promise<void> {
|
||||||
this.x = x;
|
return this.layer.deleteDynamic(this);
|
||||||
this.y = y;
|
|
||||||
this.layer.updateDynamicTile(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentFaceDirection(): FaceDirection {
|
saveState(): Readonly<IDynamicBlockSave> {
|
||||||
if (this.face) {
|
const save: IDynamicBlockSave = {
|
||||||
const face = this.face.getFaceDirection(this.num);
|
num: this.num()
|
||||||
if (isNil(face)) return FaceDirection.Unknown;
|
};
|
||||||
else return face;
|
|
||||||
|
if (this.triggers) {
|
||||||
|
save.triggers = this.triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return save;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadState(save: Readonly<IDynamicBlockSave>): void {
|
||||||
|
if (save.triggers) {
|
||||||
|
if (save.triggers.size === 0) {
|
||||||
|
this.useEmptyTrigger();
|
||||||
|
} else {
|
||||||
|
save.triggers.forEach(v => this.addTrigger(v));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return FaceDirection.Down;
|
this.clearTrigger();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,22 +5,25 @@ import {
|
|||||||
logger
|
logger
|
||||||
} from '@motajs/common';
|
} from '@motajs/common';
|
||||||
import {
|
import {
|
||||||
ILayerState,
|
IGameMap,
|
||||||
ILayerStateHooks,
|
IGameMapHooks,
|
||||||
|
IGameMapSave,
|
||||||
IMapLayer,
|
IMapLayer,
|
||||||
IMapLayerHookController,
|
IMapLayerHookController,
|
||||||
IMapLayerHooks
|
IMapLayerHooks,
|
||||||
|
IMapLayerSave,
|
||||||
|
IResizableMapLayer
|
||||||
} from './types';
|
} from './types';
|
||||||
import { IDataCommon, ITileStore } from '@user/data-common';
|
import {
|
||||||
|
IDataCommon,
|
||||||
|
ITileStore,
|
||||||
|
MapLocIndexer,
|
||||||
|
SaveCompression
|
||||||
|
} from '@user/data-common';
|
||||||
import { MapLayer } from './mapLayer';
|
import { MapLayer } from './mapLayer';
|
||||||
|
|
||||||
export class LayerState
|
export class GameMap extends Hookable<IGameMapHooks> implements IGameMap {
|
||||||
extends Hookable<ILayerStateHooks>
|
readonly layerList: Set<IResizableMapLayer> = new Set();
|
||||||
implements ILayerState
|
|
||||||
{
|
|
||||||
readonly layerList: Set<IMapLayer> = new Set();
|
|
||||||
/** 具体 MapLayer 实例列表,供内部 resize 使用 */
|
|
||||||
private readonly mapLayerList: Set<MapLayer> = new Set();
|
|
||||||
/** 图层到图层别名映射 */
|
/** 图层到图层别名映射 */
|
||||||
readonly layerAliasMap: WeakMap<IMapLayer, string> = new WeakMap();
|
readonly layerAliasMap: WeakMap<IMapLayer, string> = new WeakMap();
|
||||||
/** 图层别名到图层的映射 */
|
/** 图层别名到图层的映射 */
|
||||||
@ -32,11 +35,14 @@ export class LayerState
|
|||||||
/** 图层钩子映射 */
|
/** 图层钩子映射 */
|
||||||
private layerHookMap: Map<IMapLayer, IMapLayerHookController> = new Map();
|
private layerHookMap: Map<IMapLayer, IMapLayerHookController> = new Map();
|
||||||
|
|
||||||
|
/** 坐标索引器 */
|
||||||
|
readonly indexer = new MapLocIndexer();
|
||||||
|
|
||||||
active: boolean = false;
|
active: boolean = false;
|
||||||
eventLayer: IMapLayer | null = null;
|
eventLayer: IMapLayer | null = null;
|
||||||
|
|
||||||
/** 楼层级脏标记 */
|
/** 楼层自身脏标记 */
|
||||||
private dirty: boolean = false;
|
private selfDirty: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly state: IDataCommon,
|
public readonly state: IDataCommon,
|
||||||
@ -45,19 +51,13 @@ export class LayerState
|
|||||||
public height: number
|
public height: number
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
this.indexer.setWidth(width);
|
||||||
}
|
}
|
||||||
|
|
||||||
addLayer(): IMapLayer {
|
addLayer(): IMapLayer {
|
||||||
const array = new Uint32Array(this.width * this.height);
|
const array = new Uint32Array(this.width * this.height);
|
||||||
const layer = new MapLayer(
|
const layer = new MapLayer(array, this.width, this.height, this);
|
||||||
array,
|
|
||||||
this.width,
|
|
||||||
this.height,
|
|
||||||
this,
|
|
||||||
this.tileStore
|
|
||||||
);
|
|
||||||
this.layerList.add(layer);
|
this.layerList.add(layer);
|
||||||
this.mapLayerList.add(layer);
|
|
||||||
this.forEachHook(hook => {
|
this.forEachHook(hook => {
|
||||||
hook.onUpdateLayer?.(this.layerList);
|
hook.onUpdateLayer?.(this.layerList);
|
||||||
});
|
});
|
||||||
@ -68,8 +68,7 @@ export class LayerState
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeLayer(layer: IMapLayer): void {
|
removeLayer(layer: IMapLayer): void {
|
||||||
this.layerList.delete(layer);
|
this.layerList.delete(layer as IResizableMapLayer);
|
||||||
this.mapLayerList.delete(layer as MapLayer);
|
|
||||||
const alias = this.layerAliasMap.get(layer);
|
const alias = this.layerAliasMap.get(layer);
|
||||||
if (alias) {
|
if (alias) {
|
||||||
const symbol = Symbol.for(alias);
|
const symbol = Symbol.for(alias);
|
||||||
@ -86,7 +85,7 @@ export class LayerState
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasLayer(layer: IMapLayer): boolean {
|
hasLayer(layer: IMapLayer): boolean {
|
||||||
return this.layerList.has(layer);
|
return this.layerList.has(layer as IResizableMapLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
setLayerAlias(layer: IMapLayer, alias: string): void {
|
setLayerAlias(layer: IMapLayer, alias: string): void {
|
||||||
@ -115,7 +114,8 @@ export class LayerState
|
|||||||
): void {
|
): void {
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
for (const layer of this.mapLayerList) {
|
this.indexer.setWidth(width);
|
||||||
|
for (const layer of this.layerList) {
|
||||||
if (keepBlock) {
|
if (keepBlock) {
|
||||||
layer.resize(width, height);
|
layer.resize(width, height);
|
||||||
} else {
|
} else {
|
||||||
@ -143,50 +143,86 @@ export class LayerState
|
|||||||
if (!layer) {
|
if (!layer) {
|
||||||
this.eventLayer = null;
|
this.eventLayer = null;
|
||||||
} else {
|
} else {
|
||||||
if (!this.layerList.has(layer)) {
|
if (!this.layerList.has(layer as IResizableMapLayer)) {
|
||||||
|
logger.warn(131);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.eventLayer = layer;
|
this.eventLayer = layer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isDirty(): boolean {
|
dirty(): boolean {
|
||||||
return this.dirty;
|
if (this.selfDirty) return true;
|
||||||
|
for (const layer of this.layerList) {
|
||||||
|
if (layer.dirty()) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setDirty(dirty: boolean): void {
|
markDirty(dirty: boolean): void {
|
||||||
this.dirty = dirty;
|
this.selfDirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
compareWith(data: Map<number, Uint32Array>): void {
|
||||||
|
for (const layer of this.layerList) {
|
||||||
|
const refArray = data.get(layer.zIndex);
|
||||||
|
if (refArray) {
|
||||||
|
layer.compareWith(refArray);
|
||||||
|
} else {
|
||||||
|
layer.markDirty(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断图层存档是否不含任何有效内容
|
||||||
|
* @param save 图层存档
|
||||||
|
*/
|
||||||
|
private isEmptyLayerSave(save: IMapLayerSave): boolean {
|
||||||
|
if (save.fullMap) return false;
|
||||||
|
if (save.rows && save.rows.size > 0) return false;
|
||||||
|
if (save.staticBlocks && save.staticBlocks.size > 0) return false;
|
||||||
|
if (save.dynamicBlocks && save.dynamicBlocks.size > 0) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveState(compression: SaveCompression): IGameMapSave {
|
||||||
|
const layers = new Map<number, IMapLayerSave>();
|
||||||
|
for (const layer of this.layerList) {
|
||||||
|
const save = layer.saveState(compression);
|
||||||
|
if (this.isEmptyLayerSave(save)) continue;
|
||||||
|
layers.set(layer.zIndex, save);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
background: this.backgroundTile,
|
||||||
|
layers
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
loadState(save: IGameMapSave, compression: SaveCompression): void {
|
||||||
|
this.setBackground(save.background);
|
||||||
|
for (const layer of this.layerList) {
|
||||||
|
const layerSave = save.layers.get(layer.zIndex);
|
||||||
|
if (!layerSave) continue;
|
||||||
|
layer.loadState(layerSave, compression);
|
||||||
|
}
|
||||||
|
this.markDirty(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createController(
|
protected createController(
|
||||||
hook: Partial<ILayerStateHooks>
|
hook: Partial<IGameMapHooks>
|
||||||
): IHookController<ILayerStateHooks> {
|
): IHookController<IGameMapHooks> {
|
||||||
return new HookController(this, hook);
|
return new HookController(this, hook);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StateMapLayerHook implements Partial<IMapLayerHooks> {
|
class StateMapLayerHook implements Partial<IMapLayerHooks> {
|
||||||
constructor(
|
constructor(
|
||||||
readonly state: LayerState,
|
readonly state: GameMap,
|
||||||
readonly layer: IMapLayer
|
readonly layer: IMapLayer
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
onUpdateArea(x: number, y: number, width: number, height: number): void {
|
|
||||||
this.state.setDirty(true);
|
|
||||||
this.state.forEachHook(hook => {
|
|
||||||
hook.onUpdateLayerArea?.(this.layer, x, y, width, height);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onUpdateBlock(block: number, x: number, y: number): void {
|
|
||||||
this.state.setDirty(true);
|
|
||||||
this.state.forEachHook(hook => {
|
|
||||||
hook.onUpdateLayerBlock?.(this.layer, block, x, y);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onResize(width: number, height: number): void {
|
onResize(width: number, height: number): void {
|
||||||
this.state.setDirty(true);
|
|
||||||
this.state.forEachHook(hook => {
|
this.state.forEachHook(hook => {
|
||||||
hook.onResizeLayer?.(this.layer, width, height);
|
hook.onResizeLayer?.(this.layer, width, height);
|
||||||
});
|
});
|
||||||
@ -1,4 +1,4 @@
|
|||||||
export * from './layerState';
|
export * from './gameMap';
|
||||||
export * from './mapLayer';
|
export * from './mapLayer';
|
||||||
export * from './mapState';
|
export * from './mapState';
|
||||||
export * from './mover';
|
export * from './mover';
|
||||||
|
|||||||
@ -1,18 +1,27 @@
|
|||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
import {
|
import {
|
||||||
IDynamicLayer,
|
IDynamicBlockSave,
|
||||||
|
IDynamicTile,
|
||||||
|
IGameMap,
|
||||||
ILayerLocation,
|
ILayerLocation,
|
||||||
ILayerState,
|
|
||||||
IMapLayer,
|
IMapLayer,
|
||||||
IMapLayerData,
|
IMapLayerData,
|
||||||
IMapLayerHookController,
|
IMapLayerHookController,
|
||||||
IMapLayerHooks
|
IMapLayerHooks,
|
||||||
|
IMapLayerSave,
|
||||||
|
IStaticBlockSave,
|
||||||
|
IStaticTile
|
||||||
} from './types';
|
} from './types';
|
||||||
import { Hookable, HookController, logger } from '@motajs/common';
|
import { Hookable, HookController, ITileLocator, logger } from '@motajs/common';
|
||||||
import { DynamicLayer } from './dynamicLayer';
|
import {
|
||||||
import { IDataCommon, ITileStore } from '@user/data-common';
|
degradeFace,
|
||||||
|
FaceDirection,
|
||||||
// todo: 提供 core.setBlock 等方法的替代方法,同时添加 setBlockList,以及前景背景的接口
|
IDataCommon,
|
||||||
|
IRoleFaceBinder,
|
||||||
|
SaveCompression
|
||||||
|
} from '@user/data-common';
|
||||||
|
import { DynamicTile } from './dynamicTile';
|
||||||
|
import { StaticTile } from './staticTile';
|
||||||
|
|
||||||
export class MapLayer
|
export class MapLayer
|
||||||
extends Hookable<IMapLayerHooks, IMapLayerHookController>
|
extends Hookable<IMapLayerHooks, IMapLayerHookController>
|
||||||
@ -25,127 +34,116 @@ export class MapLayer
|
|||||||
empty: boolean = true;
|
empty: boolean = true;
|
||||||
zIndex: number = 0;
|
zIndex: number = 0;
|
||||||
|
|
||||||
|
faceBinder: IRoleFaceBinder;
|
||||||
|
|
||||||
/** 地图图块数组 */
|
/** 地图图块数组 */
|
||||||
private mapArray: Uint32Array;
|
private mapArray: Uint32Array;
|
||||||
/** 地图数据引用 */
|
/** 地图数据引用 */
|
||||||
private mapData: IMapLayerData;
|
private mapData: IMapLayerData;
|
||||||
/** 手动触发器覆盖映射,key = y * width + x */
|
/** 静态图块实例缓存,key = y * width + x */
|
||||||
private triggerMap: Map<number, number> = new Map();
|
private readonly staticTileCache: Map<number, StaticTile> = new Map();
|
||||||
|
/** 坐标到动态图块集合的映射,外层 key = y,内层 key = x */
|
||||||
readonly dynamicLayer: IDynamicLayer;
|
private readonly tilePosMap: Map<number, Map<number, Set<IDynamicTile>>> =
|
||||||
|
new Map();
|
||||||
|
/** 动态图块到其当前坐标的映射 */
|
||||||
|
private readonly posTileMap: Map<IDynamicTile, ITileLocator> = new Map();
|
||||||
|
/** 图层脏标记 */
|
||||||
|
private layerDirty: boolean = false;
|
||||||
|
/** 图层参考基准,用于存档压缩对比 */
|
||||||
|
private refArray: Uint32Array | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
array: Uint32Array,
|
array: Uint32Array,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
public readonly layerState: ILayerState,
|
public readonly map: IGameMap
|
||||||
private readonly tileStore: ITileStore
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.state = layerState.state;
|
this.state = map.state;
|
||||||
|
this.faceBinder = this.state.roleFace;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
const area = width * height;
|
const area = width * height;
|
||||||
this.mapArray = new Uint32Array(area);
|
this.mapArray = new Uint32Array(area);
|
||||||
// 超出的裁剪,不足的补零
|
|
||||||
this.mapArray.set(array);
|
this.mapArray.set(array);
|
||||||
this.mapData = {
|
this.mapData = {
|
||||||
expired: false,
|
expired: false,
|
||||||
array: this.mapArray
|
array: this.mapArray
|
||||||
};
|
};
|
||||||
this.dynamicLayer = new DynamicLayer(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
inMap(x: number, y: number): boolean {
|
|
||||||
return x >= 0 && y >= 0 && x < this.width && y < this.height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在地图尺寸变化后重新映射手动触发器覆盖表
|
* 将动态图块登记到指定坐标的索引表中
|
||||||
|
* @param tile 动态图块
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
*/
|
*/
|
||||||
private remapTriggerMap(
|
private addTileToPosMap(tile: IDynamicTile, x: number, y: number): void {
|
||||||
beforeWidth: number,
|
const xMap = this.tilePosMap.getOrInsertComputed(y, () => new Map());
|
||||||
width: number,
|
const set = xMap.getOrInsertComputed(x, () => new Set());
|
||||||
height: number
|
set.add(tile);
|
||||||
): Map<number, number> {
|
|
||||||
const next = new Map<number, number>();
|
|
||||||
for (const [index, type] of this.triggerMap) {
|
|
||||||
const x = index % beforeWidth;
|
|
||||||
const y = Math.floor(index / beforeWidth);
|
|
||||||
if (x < width && y < height) {
|
|
||||||
next.set(y * width + x, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resize(width: number, height: number): void {
|
/**
|
||||||
if (this.width === width && this.height === height) {
|
* 将动态图块从指定坐标的索引表中移除
|
||||||
return;
|
* @param tile 动态图块
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
*/
|
||||||
|
private removeTileFromPosMap(
|
||||||
|
tile: IDynamicTile,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
): void {
|
||||||
|
this.tilePosMap.get(y)?.get(x)?.delete(tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从两个内部映射中移除图块记录
|
||||||
|
* @param tile 动态图块
|
||||||
|
*/
|
||||||
|
private removeTile(tile: IDynamicTile): void {
|
||||||
|
const pos = this.posTileMap.get(tile);
|
||||||
|
if (pos) {
|
||||||
|
this.removeTileFromPosMap(tile, pos.x, pos.y);
|
||||||
}
|
}
|
||||||
this.mapData.expired = true;
|
this.posTileMap.delete(tile);
|
||||||
const before = this.mapArray;
|
}
|
||||||
const beforeWidth = this.width;
|
|
||||||
const beforeHeight = this.height;
|
/**
|
||||||
const beforeArea = beforeWidth * beforeHeight;
|
* 将动态图块的触发器同步回当前静态格点
|
||||||
this.width = width;
|
* @param tile 动态图块
|
||||||
this.height = height;
|
* @param keepTrigger 是否保留触发器
|
||||||
const area = width * height;
|
*/
|
||||||
const newArray = new Uint32Array(area);
|
private syncStaticTrigger(tile: IDynamicTile, keepTrigger: boolean): void {
|
||||||
this.triggerMap = this.remapTriggerMap(beforeWidth, width, height);
|
const staticTile = this.getTile(tile.x, tile.y);
|
||||||
this.mapArray = newArray;
|
if (!staticTile) return;
|
||||||
// 将原来的地图数组赋值给现在的
|
if (keepTrigger) {
|
||||||
if (beforeArea > area) {
|
const triggers = tile.triggers;
|
||||||
// 如果地图变小了,那么直接设置,不需要补零
|
if (!triggers) {
|
||||||
for (let ny = 0; ny < height; ny++) {
|
staticTile.clearTrigger();
|
||||||
const begin = ny * beforeWidth;
|
} else {
|
||||||
newArray.set(before.subarray(begin, begin + width), ny * width);
|
staticTile.clearTrigger();
|
||||||
|
for (const trigger of triggers) {
|
||||||
|
staticTile.addTrigger(trigger);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 如果地图变大了,那么需要补零。因为新数组本来就是用 0 填充的,实际上只要赋值就可以了
|
staticTile.clearTrigger();
|
||||||
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 {
|
//#region 静态图层操作
|
||||||
if (this.width === width && this.height === height) {
|
|
||||||
this.empty = true;
|
inMap(x: number, y: number): boolean {
|
||||||
this.mapArray.fill(0);
|
return x >= 0 && y >= 0 && x < this.width && y < this.height;
|
||||||
this.triggerMap.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.mapData.expired = true;
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.mapArray = new Uint32Array(width * height);
|
|
||||||
this.triggerMap.clear();
|
|
||||||
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 {
|
setBlock(block: number, x: number, y: number): void {
|
||||||
const index = y * this.width + x;
|
const index = y * this.width + x;
|
||||||
if (block === this.mapArray[index]) return;
|
if (block === this.mapArray[index]) return;
|
||||||
this.mapArray[index] = block;
|
this.mapArray[index] = block;
|
||||||
|
this.layerDirty = true;
|
||||||
this.forEachHook(hook => {
|
this.forEachHook(hook => {
|
||||||
hook.onUpdateBlock?.(block, x, y);
|
hook.onUpdateBlock?.(block, x, y);
|
||||||
});
|
});
|
||||||
@ -161,74 +159,47 @@ export class MapLayer
|
|||||||
return this.mapArray[y * this.width + x];
|
return this.mapArray[y * this.width + x];
|
||||||
}
|
}
|
||||||
|
|
||||||
getLocationData(x: number, y: number): ILayerLocation | null {
|
getTile(x: number, y: number): IStaticTile | null {
|
||||||
if (!this.inMap(x, y)) return null;
|
if (!this.inMap(x, y)) return null;
|
||||||
const index = y * this.width + x;
|
const index = this.map.indexer.locToIndex(x, y);
|
||||||
const num = this.mapArray[index];
|
let staticTile = this.staticTileCache.get(index);
|
||||||
const raw = this.state.tileStore.getData(num);
|
if (!staticTile) {
|
||||||
const dynamics = this.dynamicLayer.getDynamicTilesAt(x, y);
|
staticTile = new StaticTile(x, y, this);
|
||||||
const trigger = this.triggerMap.get(index) ?? -1;
|
this.staticTileCache.set(index, staticTile);
|
||||||
|
}
|
||||||
|
return staticTile;
|
||||||
|
}
|
||||||
|
|
||||||
const data: ILayerLocation = {
|
getLocationData(x: number, y: number): ILayerLocation | null {
|
||||||
tile: num,
|
const staticTile = this.getTile(x, y);
|
||||||
raw,
|
if (!staticTile) return null;
|
||||||
trigger,
|
const num = staticTile.num();
|
||||||
dynamics,
|
const dynamics = this.getDynamicTilesAt(x, y);
|
||||||
|
return {
|
||||||
locator: { x, y },
|
locator: { x, y },
|
||||||
block: raw
|
tile: num,
|
||||||
|
dynamics,
|
||||||
|
static: staticTile
|
||||||
};
|
};
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
getTriggerType(x: number, y: number): number {
|
|
||||||
if (!this.inMap(x, y)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
const index = y * this.width + x;
|
|
||||||
if (this.triggerMap.has(index)) {
|
|
||||||
return this.triggerMap.get(index)!;
|
|
||||||
}
|
|
||||||
return this.tileStore.getTrigger(this.mapArray[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTriggerType(type: number, x: number, y: number): void {
|
|
||||||
if (!this.inMap(x, y)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const index = y * this.width + x;
|
|
||||||
if (this.tileStore.getTrigger(this.mapArray[index]) === type) {
|
|
||||||
this.triggerMap.delete(index);
|
|
||||||
} else {
|
|
||||||
this.triggerMap.set(index, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
revertTrigger(x: number, y: number): void {
|
|
||||||
if (this.inMap(x, y)) {
|
|
||||||
this.triggerMap.delete(y * this.width + x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTrigger(): void {
|
|
||||||
this.triggerMap.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
putMapData(array: Uint32Array, x: number, y: number, width: number): void {
|
putMapData(array: Uint32Array, x: number, y: number, width: number): void {
|
||||||
if (array.length % width !== 0) {
|
if (array.length % width !== 0) {
|
||||||
logger.warn(8);
|
logger.warn(8);
|
||||||
}
|
}
|
||||||
const height = Math.ceil(array.length / width);
|
this.layerDirty = true;
|
||||||
if (width === this.width && height === this.height) {
|
const h = Math.ceil(array.length / width);
|
||||||
|
if (width === this.width && h === this.height) {
|
||||||
this.mapArray.set(array);
|
this.mapArray.set(array);
|
||||||
|
this.staticTileCache.clear();
|
||||||
this.forEachHook(hook => {
|
this.forEachHook(hook => {
|
||||||
hook.onUpdateArea?.(x, y, width, height);
|
hook.onUpdateArea?.(x, y, width, h);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const w = this.width;
|
const w = this.width;
|
||||||
const r = x + width;
|
const r = x + width;
|
||||||
const b = y + height;
|
const b = y + h;
|
||||||
if (x < 0 || y < 0 || r > w || b > this.height) {
|
if (x < 0 || y < 0 || r > w || b > this.height) {
|
||||||
logger.warn(9);
|
logger.warn(9);
|
||||||
}
|
}
|
||||||
@ -244,13 +215,12 @@ export class MapLayer
|
|||||||
const offset = (ny + nt) * w + nl;
|
const offset = (ny + nt) * w + nl;
|
||||||
const sub = array.subarray(start, start + nw);
|
const sub = array.subarray(start, start + nw);
|
||||||
if (empty && sub.some(v => v !== 0)) {
|
if (empty && sub.some(v => v !== 0)) {
|
||||||
// 空地图判断
|
|
||||||
empty = false;
|
empty = false;
|
||||||
}
|
}
|
||||||
this.mapArray.set(array.subarray(start, start + nw), offset);
|
this.mapArray.set(array.subarray(start, start + nw), offset);
|
||||||
}
|
}
|
||||||
this.forEachHook(hook => {
|
this.forEachHook(hook => {
|
||||||
hook.onUpdateArea?.(x, y, width, height);
|
hook.onUpdateArea?.(x, y, width, h);
|
||||||
});
|
});
|
||||||
this.empty &&= empty;
|
this.empty &&= empty;
|
||||||
}
|
}
|
||||||
@ -305,6 +275,7 @@ export class MapLayer
|
|||||||
}
|
}
|
||||||
this.mapData.expired = true;
|
this.mapData.expired = true;
|
||||||
this.mapArray = array;
|
this.mapArray = array;
|
||||||
|
this.staticTileCache.clear();
|
||||||
this.mapData = {
|
this.mapData = {
|
||||||
expired: false,
|
expired: false,
|
||||||
array: this.mapArray
|
array: this.mapArray
|
||||||
@ -319,24 +290,154 @@ export class MapLayer
|
|||||||
return this.mapData;
|
return this.mapData;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTriggerRef(triggers: Map<number, number>): void {
|
setStaticDirection(x: number, y: number, direction: FaceDirection): number {
|
||||||
this.triggerMap = triggers;
|
const tile = this.getTile(x, y);
|
||||||
|
if (!tile) return -1;
|
||||||
|
return tile.setFaceDirection(direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTriggerRef(): ReadonlyMap<number, number> {
|
*iterateBlocks(): Iterable<ILayerLocation> {
|
||||||
return this.triggerMap;
|
for (let y = 0; y < this.height; y++) {
|
||||||
|
for (let x = 0; x < this.width; x++) {
|
||||||
|
const num = this.getBlock(x, y);
|
||||||
|
if (num !== 0) {
|
||||||
|
yield this.getLocationData(x, y)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createController(
|
//#endregion
|
||||||
hook: Partial<IMapLayerHooks>
|
|
||||||
): IMapLayerHookController {
|
//#region 动态图层操作
|
||||||
return new MapLayerHookController(this, hook);
|
|
||||||
|
createDynamic(num: number, x: number, y: number): IDynamicTile {
|
||||||
|
const tile = new DynamicTile(num, x, y, this);
|
||||||
|
const location = this.getLocationData(x, y);
|
||||||
|
if (location) {
|
||||||
|
const staticTriggers = location.static.triggers;
|
||||||
|
if (staticTriggers) {
|
||||||
|
for (const trigger of staticTriggers) {
|
||||||
|
tile.addTrigger(trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.addTileToPosMap(tile, x, y);
|
||||||
|
this.posTileMap.set(tile, { x, y });
|
||||||
|
this.forEachHook(hook => hook.onCreateDynamic?.(tile));
|
||||||
|
return tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
setZIndex(zIndex: number): void {
|
transferToDynamic(
|
||||||
this.zIndex = zIndex;
|
x: number,
|
||||||
|
y: number,
|
||||||
|
keepTrigger: boolean = true
|
||||||
|
): IDynamicTile | null {
|
||||||
|
if (!this.inMap(x, y)) {
|
||||||
|
logger.warn(131, x.toString(), y.toString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const num = this.getBlock(x, y);
|
||||||
|
if (num === 0) {
|
||||||
|
logger.warn(127, x.toString(), y.toString());
|
||||||
|
}
|
||||||
|
this.setBlock(0, x, y);
|
||||||
|
const tile = this.createDynamic(num, x, y);
|
||||||
|
const staticTile = this.getTile(x, y);
|
||||||
|
if (staticTile) {
|
||||||
|
staticTile.clearTrigger();
|
||||||
|
}
|
||||||
|
if (!keepTrigger) {
|
||||||
|
tile.clearTrigger();
|
||||||
|
}
|
||||||
|
return tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transferToStatic(
|
||||||
|
tile: IDynamicTile,
|
||||||
|
keepTrigger: boolean = true
|
||||||
|
): IStaticTile | null {
|
||||||
|
const x = tile.x;
|
||||||
|
const y = tile.y;
|
||||||
|
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
|
||||||
|
logger.warn(128, x.toString(), y.toString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (this.getBlock(x, y) !== 0) {
|
||||||
|
logger.warn(129, x.toString(), y.toString());
|
||||||
|
}
|
||||||
|
this.setBlock(tile.num(), x, y);
|
||||||
|
this.syncStaticTrigger(tile, keepTrigger);
|
||||||
|
this.removeTile(tile);
|
||||||
|
this.forEachHook(hook => hook.onDeleteDynamic?.(tile));
|
||||||
|
return this.getTile(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
transferToStaticIfSafe(
|
||||||
|
tile: IDynamicTile,
|
||||||
|
keepTrigger: boolean = true
|
||||||
|
): IStaticTile | null {
|
||||||
|
const x = tile.x;
|
||||||
|
const y = tile.y;
|
||||||
|
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
|
||||||
|
logger.warn(128, x.toString(), y.toString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (this.getBlock(tile.x, tile.y) !== 0) return null;
|
||||||
|
this.setBlock(tile.num(), x, y);
|
||||||
|
this.syncStaticTrigger(tile, keepTrigger);
|
||||||
|
this.removeTile(tile);
|
||||||
|
this.forEachHook(hook => hook.onDeleteDynamic?.(tile));
|
||||||
|
return this.getTile(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteDynamic(tile: IDynamicTile): Promise<void> {
|
||||||
|
if (!this.posTileMap.has(tile)) {
|
||||||
|
logger.warn(130);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.removeTile(tile);
|
||||||
|
const hooks = this.forEachHook(hook => hook.onDeleteDynamic?.(tile));
|
||||||
|
await Promise.all(hooks);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDynamicTilesAt(x: number, y: number): Iterable<IDynamicTile> {
|
||||||
|
return this.tilePosMap.get(y)?.get(x) ?? new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
iterateDynamicTiles(): Iterable<IDynamicTile> {
|
||||||
|
return this.posTileMap.keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
setDynamicDirection(tile: IDynamicTile, direction: FaceDirection): number {
|
||||||
|
const numBefore = tile.num();
|
||||||
|
tile.setFaceDirection(direction);
|
||||||
|
if (tile.num() !== numBefore) return tile.num();
|
||||||
|
const degraded = degradeFace(direction);
|
||||||
|
if (degraded !== direction) {
|
||||||
|
tile.setFaceDirection(degraded);
|
||||||
|
}
|
||||||
|
return tile.num();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDynamicTile(tile: IDynamicTile): void {
|
||||||
|
const oldPos = this.posTileMap.get(tile);
|
||||||
|
if (oldPos) {
|
||||||
|
this.removeTileFromPosMap(tile, oldPos.x, oldPos.y);
|
||||||
|
oldPos.x = tile.x;
|
||||||
|
oldPos.y = tile.y;
|
||||||
|
this.addTileToPosMap(tile, tile.x, tile.y);
|
||||||
|
} else {
|
||||||
|
this.addTileToPosMap(tile, tile.x, tile.y);
|
||||||
|
this.posTileMap.set(tile, { x: tile.x, y: tile.y });
|
||||||
|
}
|
||||||
|
this.forEachHook(hook => hook.onUpdateDynamicPosition?.(tile));
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 开关门
|
||||||
|
|
||||||
async openDoor(x: number, y: number): Promise<void> {
|
async openDoor(x: number, y: number): Promise<void> {
|
||||||
const index = y * this.width + x;
|
const index = y * this.width + x;
|
||||||
const num = this.mapArray[index];
|
const num = this.mapArray[index];
|
||||||
@ -363,6 +464,352 @@ export class MapLayer
|
|||||||
);
|
);
|
||||||
this.setBlock(num, x, y);
|
this.setBlock(num, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 图层操作
|
||||||
|
|
||||||
|
setZIndex(zIndex: number): void {
|
||||||
|
this.zIndex = zIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFaceBinder(binder: IRoleFaceBinder | null): void {
|
||||||
|
if (!binder) return;
|
||||||
|
this.faceBinder = binder;
|
||||||
|
}
|
||||||
|
|
||||||
|
dirty(): boolean {
|
||||||
|
return this.layerDirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
markDirty(dirty: boolean): void {
|
||||||
|
this.layerDirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断当前图层的地图矩阵是否与参考基准完全一致
|
||||||
|
*/
|
||||||
|
private isEqualToRef(): boolean {
|
||||||
|
const ref = this.refArray;
|
||||||
|
if (!ref) return false;
|
||||||
|
if (this.mapArray.length !== ref.length) return false;
|
||||||
|
return !this.mapArray.some((v, i) => ref[i] !== v);
|
||||||
|
}
|
||||||
|
|
||||||
|
compareWith(data: Uint32Array): void {
|
||||||
|
if (this.refArray) return;
|
||||||
|
this.refArray = data;
|
||||||
|
this.layerDirty = !this.isEqualToRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createController(
|
||||||
|
hook: Partial<IMapLayerHooks>
|
||||||
|
): IMapLayerHookController {
|
||||||
|
return new MapLayerHookController(this, hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
resize(width: number, height: number): void {
|
||||||
|
if (this.width === width && this.height === height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.layerDirty = true;
|
||||||
|
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;
|
||||||
|
this.staticTileCache.clear();
|
||||||
|
if (beforeArea > area) {
|
||||||
|
for (let ny = 0; ny < height; ny++) {
|
||||||
|
const begin = ny * beforeWidth;
|
||||||
|
newArray.set(before.subarray(begin, begin + width), ny * width);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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 {
|
||||||
|
this.layerDirty = true;
|
||||||
|
if (this.width === width && this.height === height) {
|
||||||
|
this.empty = true;
|
||||||
|
this.mapArray.fill(0);
|
||||||
|
this.staticTileCache.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.mapData.expired = true;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.mapArray = new Uint32Array(width * height);
|
||||||
|
this.staticTileCache.clear();
|
||||||
|
this.mapData = {
|
||||||
|
expired: false,
|
||||||
|
array: this.mapArray
|
||||||
|
};
|
||||||
|
this.empty = true;
|
||||||
|
this.forEachHook(hook => {
|
||||||
|
hook.onResize?.(width, height);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 存读档
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存静态图块实例
|
||||||
|
*/
|
||||||
|
private saveStatics(): Map<number, IStaticBlockSave> {
|
||||||
|
const blocks = new Map<number, IStaticBlockSave>();
|
||||||
|
for (const location of this.iterateBlocks()) {
|
||||||
|
const tile = location.static;
|
||||||
|
if (!tile.shouldSave()) continue;
|
||||||
|
const index = this.map.indexer.locaterToIndex(location.locator);
|
||||||
|
blocks.set(index, tile.saveState(SaveCompression.NoCompression));
|
||||||
|
}
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存动态图块实例
|
||||||
|
*/
|
||||||
|
private saveDynamics(): Map<number, IDynamicBlockSave[]> {
|
||||||
|
const blocks = new Map<number, IDynamicBlockSave[]>();
|
||||||
|
for (const tile of this.iterateDynamicTiles()) {
|
||||||
|
const index = this.map.indexer.locaterToIndex(tile.locator);
|
||||||
|
const list = blocks.getOrInsert(index, []);
|
||||||
|
list.push(tile.saveState(SaveCompression.NoCompression));
|
||||||
|
}
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与参考行比较,返回与参考基准不同的行
|
||||||
|
* @param refArray 参考基准数组
|
||||||
|
*/
|
||||||
|
private diffRows(refArray: Uint32Array): Map<number, Uint32Array> {
|
||||||
|
const rows = new Map<number, Uint32Array>();
|
||||||
|
for (let row = 0; row < this.height; row++) {
|
||||||
|
const start = row * this.width;
|
||||||
|
const end = start + this.width;
|
||||||
|
const slice = this.mapArray.subarray(start, end);
|
||||||
|
const refSlice = refArray.subarray(start, end);
|
||||||
|
const same = refSlice.every((v, i) => slice[i] === v);
|
||||||
|
if (!same) {
|
||||||
|
rows.set(row, new Uint32Array(slice));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以无压缩方式序列化当前图层
|
||||||
|
*/
|
||||||
|
private saveNoCompression(): IMapLayerSave {
|
||||||
|
return {
|
||||||
|
width: this.width,
|
||||||
|
height: this.height,
|
||||||
|
fullMap: new Uint32Array(this.mapArray),
|
||||||
|
staticBlocks: this.saveStatics(),
|
||||||
|
dynamicBlocks: this.saveDynamics()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以低压缩方式序列化当前图层
|
||||||
|
*/
|
||||||
|
private saveLowCompression(): IMapLayerSave {
|
||||||
|
const staticBlocks = this.saveStatics();
|
||||||
|
const dynamicBlocks = this.saveDynamics();
|
||||||
|
if (this.layerDirty && (!this.refArray || !this.isEqualToRef())) {
|
||||||
|
return {
|
||||||
|
width: this.width,
|
||||||
|
height: this.height,
|
||||||
|
fullMap: new Uint32Array(this.mapArray),
|
||||||
|
staticBlocks,
|
||||||
|
dynamicBlocks
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
width: this.width,
|
||||||
|
height: this.height,
|
||||||
|
staticBlocks,
|
||||||
|
dynamicBlocks
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以高压缩方式序列化当前图层
|
||||||
|
*/
|
||||||
|
private saveHighCompression(): IMapLayerSave {
|
||||||
|
const staticBlocks = this.saveStatics();
|
||||||
|
const dynamicBlocks = this.saveDynamics();
|
||||||
|
if (this.layerDirty) {
|
||||||
|
if (this.refArray) {
|
||||||
|
return {
|
||||||
|
width: this.width,
|
||||||
|
height: this.height,
|
||||||
|
rows: this.diffRows(this.refArray),
|
||||||
|
staticBlocks,
|
||||||
|
dynamicBlocks
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
width: this.width,
|
||||||
|
height: this.height,
|
||||||
|
fullMap: new Uint32Array(this.mapArray),
|
||||||
|
staticBlocks,
|
||||||
|
dynamicBlocks
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
width: this.width,
|
||||||
|
height: this.height,
|
||||||
|
staticBlocks,
|
||||||
|
dynamicBlocks
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveState(compression: SaveCompression): IMapLayerSave {
|
||||||
|
if (compression === SaveCompression.HighCompression) {
|
||||||
|
return this.saveHighCompression();
|
||||||
|
} else if (compression === SaveCompression.LowCompression) {
|
||||||
|
return this.saveLowCompression();
|
||||||
|
} else {
|
||||||
|
return this.saveNoCompression();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取静态图块实例存档数据
|
||||||
|
* @param save 静态图块实例存档
|
||||||
|
*/
|
||||||
|
private loadStatics(save: ReadonlyMap<number, IStaticBlockSave>): void {
|
||||||
|
for (const [index, tileSave] of save) {
|
||||||
|
const { x, y } = this.map.indexer.indexToLocator(index);
|
||||||
|
const location = this.getLocationData(x, y);
|
||||||
|
if (!location) continue;
|
||||||
|
location.static.loadState(tileSave, SaveCompression.NoCompression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取动态图块实例存档数据
|
||||||
|
* @param save 动态图块实例存档
|
||||||
|
*/
|
||||||
|
private loadDynamics(save: ReadonlyMap<number, IDynamicBlockSave[]>): void {
|
||||||
|
for (const [index, dynamics] of save) {
|
||||||
|
const { x, y } = this.map.indexer.indexToLocator(index);
|
||||||
|
for (const block of dynamics) {
|
||||||
|
const tile = this.createDynamic(block.num, x, y);
|
||||||
|
tile.loadState(block, SaveCompression.NoCompression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以无压缩方式读取当前图层
|
||||||
|
* @param save 图层存档
|
||||||
|
*/
|
||||||
|
private loadNoCompression(save: IMapLayerSave): void {
|
||||||
|
if (save.fullMap) {
|
||||||
|
this.setMapRef(new Uint32Array(save.fullMap));
|
||||||
|
}
|
||||||
|
if (save.staticBlocks) {
|
||||||
|
this.loadStatics(save.staticBlocks);
|
||||||
|
}
|
||||||
|
if (save.dynamicBlocks) {
|
||||||
|
this.loadDynamics(save.dynamicBlocks);
|
||||||
|
}
|
||||||
|
this.layerDirty = !this.isEqualToRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以低压缩方式读取当前图层
|
||||||
|
* @param save 图层存档
|
||||||
|
*/
|
||||||
|
private loadLowCompression(save: IMapLayerSave): void {
|
||||||
|
if (save.fullMap) {
|
||||||
|
this.setMapRef(new Uint32Array(save.fullMap));
|
||||||
|
this.layerDirty = true;
|
||||||
|
} else if (this.refArray) {
|
||||||
|
this.setMapRef(new Uint32Array(this.refArray));
|
||||||
|
this.layerDirty = false;
|
||||||
|
} else {
|
||||||
|
logger.warn(124, this.zIndex.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (save.staticBlocks) {
|
||||||
|
this.loadStatics(save.staticBlocks);
|
||||||
|
}
|
||||||
|
if (save.dynamicBlocks) {
|
||||||
|
this.loadDynamics(save.dynamicBlocks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以高压缩方式读取当前图层
|
||||||
|
* @param save 图层存档
|
||||||
|
*/
|
||||||
|
private loadHighCompression(save: IMapLayerSave): void {
|
||||||
|
if (save.rows && save.rows.size > 0) {
|
||||||
|
if (this.refArray) {
|
||||||
|
const buf = new Uint32Array(this.refArray);
|
||||||
|
for (const [rowIdx, rowData] of save.rows) {
|
||||||
|
buf.set(rowData, rowIdx * this.width);
|
||||||
|
}
|
||||||
|
this.setMapRef(buf);
|
||||||
|
this.layerDirty = true;
|
||||||
|
} else {
|
||||||
|
logger.warn(124, this.zIndex.toString());
|
||||||
|
}
|
||||||
|
} else if (this.refArray) {
|
||||||
|
this.setMapRef(new Uint32Array(this.refArray));
|
||||||
|
this.layerDirty = false;
|
||||||
|
} else {
|
||||||
|
logger.warn(124, this.zIndex.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (save.staticBlocks) {
|
||||||
|
this.loadStatics(save.staticBlocks);
|
||||||
|
}
|
||||||
|
if (save.dynamicBlocks) {
|
||||||
|
this.loadDynamics(save.dynamicBlocks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadState(save: IMapLayerSave, compression: SaveCompression): void {
|
||||||
|
if (compression === SaveCompression.HighCompression) {
|
||||||
|
this.loadHighCompression(save);
|
||||||
|
} else if (compression === SaveCompression.LowCompression) {
|
||||||
|
this.loadLowCompression(save);
|
||||||
|
} else {
|
||||||
|
this.loadNoCompression(save);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
class MapLayerHookController
|
class MapLayerHookController
|
||||||
|
|||||||
@ -1,27 +1,25 @@
|
|||||||
import { logger } from '@motajs/common';
|
import { isNil, uniq } from 'lodash-es';
|
||||||
import { IDataCommon, IMapRawData, SaveCompression } from '@user/data-common';
|
import { IDataCommon, IMapRawData, SaveCompression } from '@user/data-common';
|
||||||
import { ITileStore } from '@user/data-common';
|
import { ITileStore } from '@user/data-common';
|
||||||
import {
|
import {
|
||||||
ILayerState,
|
IGameMap,
|
||||||
ILayerStateSave,
|
IGameMapSave,
|
||||||
IMapLayer,
|
|
||||||
IMapLayerSave,
|
|
||||||
IMapState,
|
IMapState,
|
||||||
IMapStoreSave,
|
IMapStoreSave,
|
||||||
MapArea
|
MapArea
|
||||||
} from './types';
|
} from './types';
|
||||||
import { LayerState } from './layerState';
|
import { GameMap } from './gameMap';
|
||||||
import { isNil, uniq } from 'lodash-es';
|
import { logger } from '@motajs/common';
|
||||||
|
|
||||||
export class MapState implements IMapState {
|
export class MapState implements IMapState {
|
||||||
/** 楼层 id 到状态对象的映射 */
|
/** 楼层 id 到状态对象的映射 */
|
||||||
private readonly mapData: Map<string, LayerState> = new Map();
|
private readonly mapData: Map<string, IGameMap> = new Map();
|
||||||
|
|
||||||
/** 所有楼层 id 的有序数组 */
|
/** 所有楼层 id 的有序数组 */
|
||||||
readonly maps: string[] = [];
|
readonly maps: string[] = [];
|
||||||
|
|
||||||
/** 差分压缩参考基准,首次 compareWith 后设置,之后不再更新 */
|
/** 是否已经调用过 compareWith 设置参考基准 */
|
||||||
private refData: Map<string, Map<number, Uint32Array>> | null = null;
|
private compared: boolean = false;
|
||||||
|
|
||||||
/** 分区列表 */
|
/** 分区列表 */
|
||||||
private areaList: Set<MapArea> = new Set();
|
private areaList: Set<MapArea> = new Set();
|
||||||
@ -39,7 +37,7 @@ export class MapState implements IMapState {
|
|||||||
|
|
||||||
//#region 楼层管理
|
//#region 楼层管理
|
||||||
|
|
||||||
fromRaw(raw: IMapRawData): ILayerState | null {
|
fromRaw(raw: IMapRawData): IGameMap | null {
|
||||||
let length = 0;
|
let length = 0;
|
||||||
const entries = Object.entries(raw.map);
|
const entries = Object.entries(raw.map);
|
||||||
for (const [_, map] of Object.entries(raw.map)) {
|
for (const [_, map] of Object.entries(raw.map)) {
|
||||||
@ -57,7 +55,7 @@ export class MapState implements IMapState {
|
|||||||
|
|
||||||
// 设置楼层图块数据
|
// 设置楼层图块数据
|
||||||
const height = length / raw.width;
|
const height = length / raw.width;
|
||||||
const state = this.createLayerState(raw.floorId, raw.width, height);
|
const state = this.createMap(raw.floorId, raw.width, height);
|
||||||
for (const [zIndex, map] of entries) {
|
for (const [zIndex, map] of entries) {
|
||||||
const z = Number(zIndex);
|
const z = Number(zIndex);
|
||||||
if (isNaN(z)) {
|
if (isNaN(z)) {
|
||||||
@ -81,7 +79,10 @@ export class MapState implements IMapState {
|
|||||||
if (!isNil(data.trigger)) {
|
if (!isNil(data.trigger)) {
|
||||||
const x = indexNum % raw.width;
|
const x = indexNum % raw.width;
|
||||||
const y = Math.floor(indexNum / raw.width);
|
const y = Math.floor(indexNum / raw.width);
|
||||||
layer.setTriggerType(data.trigger, x, y);
|
const location = layer.getLocationData(x, y);
|
||||||
|
if (location) {
|
||||||
|
location.static.addTrigger(data.trigger);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,16 +90,16 @@ export class MapState implements IMapState {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
createLayerState(id: string, width: number, height: number): ILayerState {
|
createMap(id: string, width: number, height: number): IGameMap {
|
||||||
if (this.mapData.has(id)) {
|
if (this.mapData.has(id)) {
|
||||||
logger.warn(121, id);
|
logger.warn(121, id);
|
||||||
} else {
|
} else {
|
||||||
this.maps.push(id);
|
this.maps.push(id);
|
||||||
}
|
}
|
||||||
const state = new LayerState(this.state, this.tileStore, width, height);
|
const state = new GameMap(this.state, this.tileStore, width, height);
|
||||||
// 若 refData 已存在,新楼层直接视为全脏
|
// 若已设置参考基准,新楼层直接视为全脏
|
||||||
if (this.refData !== null) {
|
if (this.compared) {
|
||||||
state.setDirty(true);
|
state.markDirty(true);
|
||||||
}
|
}
|
||||||
this.mapData.set(id, state);
|
this.mapData.set(id, state);
|
||||||
return state;
|
return state;
|
||||||
@ -109,24 +110,11 @@ export class MapState implements IMapState {
|
|||||||
this.maps.push(...uniq(maps));
|
this.maps.push(...uniq(maps));
|
||||||
}
|
}
|
||||||
|
|
||||||
useManualOrder(sort: (arr: string[]) => string[]): void {
|
getMap(id: string): IGameMap | null {
|
||||||
const copy = this.maps.slice();
|
|
||||||
const sorted = sort(copy);
|
|
||||||
const oldSet = new Set(this.maps);
|
|
||||||
const newSet = new Set(sorted);
|
|
||||||
if (oldSet.size !== newSet.size || !newSet.isSubsetOf(oldSet)) {
|
|
||||||
logger.warn(125);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.maps.length = 0;
|
|
||||||
this.maps.push(...uniq(sorted));
|
|
||||||
}
|
|
||||||
|
|
||||||
getLayerState(id: string): ILayerState | null {
|
|
||||||
return this.mapData.get(id) ?? null;
|
return this.mapData.get(id) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveMap(id: string): ILayerState | null {
|
getActiveMap(id: string): IGameMap | null {
|
||||||
const state = this.mapData.get(id);
|
const state = this.mapData.get(id);
|
||||||
if (!state || !state.active) return null;
|
if (!state || !state.active) return null;
|
||||||
return state;
|
return state;
|
||||||
@ -216,411 +204,66 @@ export class MapState implements IMapState {
|
|||||||
this.mapData.get(id)?.setActiveStatus(active);
|
this.mapData.get(id)?.setActiveStatus(active);
|
||||||
}
|
}
|
||||||
|
|
||||||
*iterateActiveMaps(): Iterable<[string, ILayerState]> {
|
*iterateActiveMaps(): Iterable<[string, IGameMap]> {
|
||||||
for (const [id, state] of this.mapData) {
|
for (const [id, state] of this.mapData) {
|
||||||
if (state.active) yield [id, state];
|
if (state.active) yield [id, state];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*iterateInactiveMaps(): Iterable<[string, ILayerState]> {
|
*iterateInactiveMaps(): Iterable<[string, IGameMap]> {
|
||||||
for (const [id, state] of this.mapData) {
|
for (const [id, state] of this.mapData) {
|
||||||
if (!state.active) yield [id, state];
|
if (!state.active) yield [id, state];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
iterateAllMaps(): Iterable<[string, ILayerState]> {
|
iterateAllMaps(): Iterable<[string, IGameMap]> {
|
||||||
return this.mapData;
|
return this.mapData;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 存档工具
|
//#region 存档对比
|
||||||
|
|
||||||
compareWith(ref: Map<string, Map<number, Uint32Array>>): void {
|
compareWith(ref: Map<string, Map<number, Uint32Array>>): void {
|
||||||
if (this.refData !== null) return;
|
if (this.compared) return;
|
||||||
this.refData = ref;
|
this.compared = true;
|
||||||
|
|
||||||
for (const [id, state] of this.mapData) {
|
for (const [id, state] of this.mapData) {
|
||||||
const refFloor = ref.get(id);
|
const refFloor = ref.get(id);
|
||||||
if (!refFloor) {
|
if (!refFloor) {
|
||||||
state.setDirty(true);
|
state.markDirty(true);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let dirty = false;
|
state.compareWith(refFloor);
|
||||||
for (const layer of state.layerList) {
|
|
||||||
const refArray = refFloor.get(layer.zIndex);
|
|
||||||
if (!refArray) {
|
|
||||||
dirty = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const cur = layer.getMapRef().array;
|
|
||||||
if (cur.length !== refArray.length) {
|
|
||||||
dirty = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (cur.some((v, i) => refArray[i] !== v)) {
|
|
||||||
dirty = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state.setDirty(dirty);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
//#region 存读档
|
||||||
* 提取楼层内所有图层的静态触发器覆盖映射
|
|
||||||
* @param state 目标楼层状态
|
|
||||||
*/
|
|
||||||
private getTriggerMap(state: ILayerState) {
|
|
||||||
const triggers = new Map<number, Map<number, number>>();
|
|
||||||
for (const layer of state.layerList) {
|
|
||||||
const map = layer.getTriggerRef();
|
|
||||||
if (map.size > 0) {
|
|
||||||
triggers.set(layer.zIndex, new Map(map));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return triggers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构造仅包含背景和静态触发器的空楼层存档
|
|
||||||
* @param state 目标楼层状态
|
|
||||||
*/
|
|
||||||
private emptySave(state: ILayerState): ILayerStateSave {
|
|
||||||
return {
|
|
||||||
background: state.getBackground(),
|
|
||||||
layers: new Map(),
|
|
||||||
triggers: this.getTriggerMap(state)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将单个图层序列化为完整地图存档
|
|
||||||
* @param layer 目标图层
|
|
||||||
*/
|
|
||||||
private fullLayer(layer: IMapLayer): IMapLayerSave {
|
|
||||||
return {
|
|
||||||
width: layer.width,
|
|
||||||
height: layer.height,
|
|
||||||
fullMap: new Uint32Array(layer.getMapRef().array)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将楼层所有图层全量序列化(NoCompression / LowCompression 用)
|
|
||||||
* @param state 目标楼层状态
|
|
||||||
*/
|
|
||||||
private saveLayerStateFull(state: LayerState): ILayerStateSave {
|
|
||||||
const background = state.getBackground();
|
|
||||||
const layers = new Map<number, IMapLayerSave>();
|
|
||||||
const triggers = new Map<number, Map<number, number>>();
|
|
||||||
for (const layer of state.layerList) {
|
|
||||||
const arr = layer.getMapRef().array;
|
|
||||||
layers.set(layer.zIndex, {
|
|
||||||
width: layer.width,
|
|
||||||
height: layer.height,
|
|
||||||
fullMap: new Uint32Array(arr)
|
|
||||||
});
|
|
||||||
triggers.set(layer.zIndex, new Map(layer.getTriggerRef()));
|
|
||||||
}
|
|
||||||
return { background, layers, triggers };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 仅返回与参考基准不同的行(HighCompression 用)
|
|
||||||
* @param layer 目标图层
|
|
||||||
* @param refArray 该图层对应的参考地图数据
|
|
||||||
*/
|
|
||||||
private diffRows(
|
|
||||||
layer: IMapLayer,
|
|
||||||
refArray: Uint32Array
|
|
||||||
): Map<number, Uint32Array> {
|
|
||||||
const rows = new Map<number, Uint32Array>();
|
|
||||||
const arr = layer.getMapRef().array;
|
|
||||||
|
|
||||||
for (let row = 0; row < layer.height; row++) {
|
|
||||||
const start = row * layer.width;
|
|
||||||
const end = start + layer.width;
|
|
||||||
const slice = arr.subarray(start, end);
|
|
||||||
const refSlice = refArray.subarray(start, end);
|
|
||||||
const same = refSlice.every((v, i) => slice[i] === v);
|
|
||||||
if (!same) {
|
|
||||||
rows.set(row, new Uint32Array(slice));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断楼层所有图层是否与参考基准完全一致(LowCompression 去误判用)
|
|
||||||
* @param id 楼层 id
|
|
||||||
* @param state 目标楼层状态
|
|
||||||
*/
|
|
||||||
private isStateEqualToRef(id: string, state: LayerState): boolean {
|
|
||||||
const refFloor = this.refData?.get(id);
|
|
||||||
if (!refFloor) return false;
|
|
||||||
for (const layer of state.layerList) {
|
|
||||||
const refArray = refFloor.get(layer.zIndex);
|
|
||||||
if (!refArray) return false;
|
|
||||||
const cur = layer.getMapRef().array;
|
|
||||||
if (cur.length !== refArray.length) return false;
|
|
||||||
if (cur.some((v, i) => v !== refArray[i])) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region 存档
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 以无压缩方式序列化所有激活楼层
|
|
||||||
*/
|
|
||||||
private saveNoCompression(): IMapStoreSave {
|
|
||||||
const floors = new Map<string, ILayerStateSave>();
|
|
||||||
for (const [id, state] of this.mapData) {
|
|
||||||
if (!state.active) continue;
|
|
||||||
floors.set(id, this.saveLayerStateFull(state));
|
|
||||||
}
|
|
||||||
return { floors };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 以低压缩方式序列化所有激活楼层
|
|
||||||
*/
|
|
||||||
private saveLowCompression(): IMapStoreSave {
|
|
||||||
const floors = new Map<string, ILayerStateSave>();
|
|
||||||
if (this.refData) {
|
|
||||||
// 包含参考标准时需要对比
|
|
||||||
for (const [id, state] of this.mapData) {
|
|
||||||
if (!state.active) continue;
|
|
||||||
if (state.isDirty() && this.isStateEqualToRef(id, state)) {
|
|
||||||
floors.set(id, this.saveLayerStateFull(state));
|
|
||||||
} else {
|
|
||||||
floors.set(id, this.emptySave(state));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 不包含参考标准时仅看 dirty 标记
|
|
||||||
for (const [id, state] of this.mapData) {
|
|
||||||
if (!state.active) continue;
|
|
||||||
if (state.isDirty()) {
|
|
||||||
floors.set(id, this.saveLayerStateFull(state));
|
|
||||||
} else {
|
|
||||||
floors.set(id, this.emptySave(state));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { floors };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 以高压缩方式序列化所有激活楼层
|
|
||||||
*/
|
|
||||||
private saveHighCompression(): IMapStoreSave {
|
|
||||||
const floors = new Map<string, ILayerStateSave>();
|
|
||||||
// 没有参考标准,直接退回低压缩级别
|
|
||||||
if (!this.refData) return this.saveLowCompression();
|
|
||||||
|
|
||||||
for (const [id, state] of this.mapData) {
|
|
||||||
if (!state.active) continue;
|
|
||||||
if (state.isDirty()) {
|
|
||||||
const refFloor = this.refData.get(id);
|
|
||||||
const layersMap = new Map<number, IMapLayerSave>();
|
|
||||||
// 对每一个地图的每一行进行遍历,然后仅存储有差别的行
|
|
||||||
for (const layer of state.layerList) {
|
|
||||||
const refArray = refFloor?.get(layer.zIndex);
|
|
||||||
if (refArray) {
|
|
||||||
const rows = this.diffRows(layer, refArray);
|
|
||||||
if (rows.size === 0) continue;
|
|
||||||
layersMap.set(layer.zIndex, {
|
|
||||||
width: layer.width,
|
|
||||||
height: layer.height,
|
|
||||||
rows
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
layersMap.set(layer.zIndex, this.fullLayer(layer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
floors.set(id, {
|
|
||||||
background: state.getBackground(),
|
|
||||||
layers: layersMap,
|
|
||||||
triggers: this.getTriggerMap(state)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
floors.set(id, this.emptySave(state));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { floors };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载指定图层的触发器
|
|
||||||
* @param save 楼层存档数据
|
|
||||||
* @param layer 目标图层
|
|
||||||
*/
|
|
||||||
private loadTriggers(save: ILayerStateSave, layer: IMapLayer) {
|
|
||||||
const triggers = save.triggers.get(layer.zIndex);
|
|
||||||
layer.clearTrigger();
|
|
||||||
if (triggers) {
|
|
||||||
layer.setTriggerRef(new Map(triggers));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* NoCompression 读档:每个图层均有 fullMap,直接转移所有权,无需参考基准。
|
|
||||||
* @param state 整体地图存档数据
|
|
||||||
*/
|
|
||||||
private loadNoCompression(state: IMapStoreSave): void {
|
|
||||||
for (const [id, cur] of this.mapData) {
|
|
||||||
cur.setActiveStatus(state.floors.has(id));
|
|
||||||
}
|
|
||||||
for (const [id, save] of state.floors) {
|
|
||||||
const cur = this.mapData.get(id);
|
|
||||||
if (!cur) {
|
|
||||||
logger.warn(122, id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
cur.setBackground(save.background);
|
|
||||||
for (const layer of cur.layerList) {
|
|
||||||
// 地图
|
|
||||||
const layerSave = save.layers.get(layer.zIndex);
|
|
||||||
if (layerSave?.fullMap) {
|
|
||||||
layer.setMapRef(new Uint32Array(layerSave.fullMap));
|
|
||||||
}
|
|
||||||
// 触发器
|
|
||||||
this.loadTriggers(save, layer);
|
|
||||||
}
|
|
||||||
// 需要额外进行判断是否与参考地图相同
|
|
||||||
if (this.isStateEqualToRef(id, cur)) {
|
|
||||||
cur.setDirty(false);
|
|
||||||
} else {
|
|
||||||
cur.setDirty(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LowCompression 读档:
|
|
||||||
* - layers 有数据(dirty 楼层)→ fullMap 直接转移所有权
|
|
||||||
* - layers 为空(非 dirty 楼层)→ 从参考基准恢复
|
|
||||||
* @param state 整体地图存档数据
|
|
||||||
*/
|
|
||||||
private loadLowCompression(state: IMapStoreSave): void {
|
|
||||||
if (!this.refData) {
|
|
||||||
logger.error(55);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const [id, cur] of this.mapData) {
|
|
||||||
cur.setActiveStatus(state.floors.has(id));
|
|
||||||
}
|
|
||||||
for (const [id, save] of state.floors) {
|
|
||||||
const cur = this.mapData.get(id);
|
|
||||||
const refFloor = this.refData.get(id);
|
|
||||||
if (!cur) {
|
|
||||||
logger.warn(122, id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!refFloor) {
|
|
||||||
logger.warn(124, id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
cur.setBackground(save.background);
|
|
||||||
let shouldDirty = false;
|
|
||||||
for (const layer of cur.layerList) {
|
|
||||||
// 地图
|
|
||||||
const layerSave = save.layers.get(layer.zIndex);
|
|
||||||
if (layerSave?.fullMap) {
|
|
||||||
layer.setMapRef(new Uint32Array(layerSave.fullMap));
|
|
||||||
shouldDirty = true;
|
|
||||||
} else {
|
|
||||||
const refArray = refFloor.get(layer.zIndex);
|
|
||||||
if (!refArray) {
|
|
||||||
logger.warn(124, id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
layer.setMapRef(new Uint32Array(refArray));
|
|
||||||
}
|
|
||||||
// 触发器
|
|
||||||
this.loadTriggers(save, layer);
|
|
||||||
}
|
|
||||||
cur.setDirty(shouldDirty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HighCompression 读档:
|
|
||||||
* - layers 有数据(dirty 楼层)→ 以参考基准为底,叠加差分行
|
|
||||||
* - layers 为空(非 dirty 楼层)或图层无变化(rows 缺失)→ 从参考基准恢复
|
|
||||||
* @param state 整体地图存档数据
|
|
||||||
*/
|
|
||||||
private loadHighCompression(state: IMapStoreSave): void {
|
|
||||||
if (!this.refData) {
|
|
||||||
logger.error(55);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const [id, cur] of this.mapData) {
|
|
||||||
cur.setActiveStatus(state.floors.has(id));
|
|
||||||
}
|
|
||||||
for (const [id, save] of state.floors) {
|
|
||||||
const cur = this.mapData.get(id);
|
|
||||||
const refFloor = this.refData.get(id);
|
|
||||||
if (!cur) {
|
|
||||||
logger.warn(122, id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!refFloor) {
|
|
||||||
logger.warn(124, id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
cur.setBackground(save.background);
|
|
||||||
let shouldDirty = false;
|
|
||||||
for (const layer of cur.layerList) {
|
|
||||||
const refArray = refFloor.get(layer.zIndex);
|
|
||||||
if (!refArray) {
|
|
||||||
logger.warn(124, id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 地图
|
|
||||||
const layerSave = save.layers.get(layer.zIndex);
|
|
||||||
if (!layerSave?.rows || layerSave.rows.size === 0) {
|
|
||||||
// 图层无变化或非 dirty 楼层,从参考基准恢复
|
|
||||||
layer.setMapRef(new Uint32Array(refArray));
|
|
||||||
} else {
|
|
||||||
// 以参考基准为底,叠加差分行
|
|
||||||
shouldDirty = true;
|
|
||||||
const buf = new Uint32Array(refArray);
|
|
||||||
for (const [rowIdx, rowData] of layerSave.rows) {
|
|
||||||
buf.set(rowData, rowIdx * layer.width);
|
|
||||||
}
|
|
||||||
layer.setMapRef(buf);
|
|
||||||
}
|
|
||||||
// 触发器
|
|
||||||
this.loadTriggers(save, layer);
|
|
||||||
}
|
|
||||||
cur.setDirty(shouldDirty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
saveState(compression: SaveCompression): IMapStoreSave {
|
saveState(compression: SaveCompression): IMapStoreSave {
|
||||||
if (compression === SaveCompression.HighCompression) {
|
const floors = new Map<string, IGameMapSave>();
|
||||||
return this.saveHighCompression();
|
for (const [id, state] of this.mapData) {
|
||||||
} else if (compression === SaveCompression.LowCompression) {
|
if (!state.active) continue;
|
||||||
return this.saveLowCompression();
|
floors.set(id, state.saveState(compression));
|
||||||
} else {
|
|
||||||
return this.saveNoCompression();
|
|
||||||
}
|
}
|
||||||
|
return { floors };
|
||||||
}
|
}
|
||||||
|
|
||||||
loadState(state: IMapStoreSave, compression: SaveCompression): void {
|
loadState(state: IMapStoreSave, compression: SaveCompression): void {
|
||||||
if (compression === SaveCompression.HighCompression) {
|
if (compression !== SaveCompression.NoCompression && !this.compared) {
|
||||||
this.loadHighCompression(state);
|
logger.error(55);
|
||||||
} else if (compression === SaveCompression.LowCompression) {
|
return;
|
||||||
this.loadLowCompression(state);
|
}
|
||||||
} else {
|
for (const [id, cur] of this.mapData) {
|
||||||
this.loadNoCompression(state);
|
cur.setActiveStatus(state.floors.has(id));
|
||||||
|
}
|
||||||
|
for (const [id, save] of state.floors) {
|
||||||
|
const cur = this.mapData.get(id);
|
||||||
|
if (!cur) {
|
||||||
|
logger.warn(122, id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cur.loadState(save, compression);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { ITileLocator, logger } from '@motajs/common';
|
import { ITileLocator, logger } from '@motajs/common';
|
||||||
import {
|
import {
|
||||||
getFaceMovement,
|
getFaceMovement,
|
||||||
IMoverController,
|
|
||||||
ObjectMover,
|
ObjectMover,
|
||||||
ObjectMoveStep,
|
ObjectMoveStep,
|
||||||
ObjectMoveType
|
ObjectMoveType
|
||||||
@ -9,8 +8,6 @@ import {
|
|||||||
import { IDynamicTile } from './types';
|
import { IDynamicTile } from './types';
|
||||||
import { DYNAMIC_MOVER_FACE } from '../shared';
|
import { DYNAMIC_MOVER_FACE } from '../shared';
|
||||||
|
|
||||||
//#region 动态图块
|
|
||||||
|
|
||||||
const enum DynamicMoveCode {
|
const enum DynamicMoveCode {
|
||||||
/** 正常执行 */
|
/** 正常执行 */
|
||||||
Success
|
Success
|
||||||
@ -94,5 +91,3 @@ export class DynamicTileMover extends ObjectMover<IDynamicTile> {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|||||||
59
packages-user/data-base/src/map/staticTile.ts
Normal file
59
packages-user/data-base/src/map/staticTile.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { ITileRawData } from '@user/data-common';
|
||||||
|
import {
|
||||||
|
IDynamicTile,
|
||||||
|
IMapLayer,
|
||||||
|
IStaticBlockSave,
|
||||||
|
IStaticTile
|
||||||
|
} from './types';
|
||||||
|
import { MapTileBase } from './tile';
|
||||||
|
|
||||||
|
export class StaticTile
|
||||||
|
extends MapTileBase<IStaticBlockSave>
|
||||||
|
implements IStaticTile
|
||||||
|
{
|
||||||
|
constructor(x: number, y: number, layer: IMapLayer) {
|
||||||
|
super(x, y, layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
num(): number {
|
||||||
|
return this.layer.getBlock(this.locator.x, this.locator.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
raw(): ITileRawData | null {
|
||||||
|
return this.state.tileStore.getData(this.num());
|
||||||
|
}
|
||||||
|
|
||||||
|
set(num: number): void {
|
||||||
|
this.layer.setBlock(num, this.locator.x, this.locator.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
toDynamic(): IDynamicTile {
|
||||||
|
return this.layer.transferToDynamic(this.locator.x, this.locator.y)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldSave(): boolean {
|
||||||
|
return !!this.triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveState(): Readonly<IStaticBlockSave> {
|
||||||
|
const save: IStaticBlockSave = {};
|
||||||
|
|
||||||
|
if (this.triggers) {
|
||||||
|
save.triggers = this.triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return save;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadState(save: Readonly<IStaticBlockSave>): void {
|
||||||
|
if (save.triggers) {
|
||||||
|
if (save.triggers.size === 0) {
|
||||||
|
this.useEmptyTrigger();
|
||||||
|
} else {
|
||||||
|
save.triggers.forEach(v => this.addTrigger(v));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.clearTrigger();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
70
packages-user/data-base/src/map/tile.ts
Normal file
70
packages-user/data-base/src/map/tile.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { ITileLocator } from '@motajs/common';
|
||||||
|
import {
|
||||||
|
FaceDirection,
|
||||||
|
IDataCommon,
|
||||||
|
IDataCommonExtended,
|
||||||
|
IMapBlockRawData,
|
||||||
|
ISaveableContent,
|
||||||
|
ITileRawData
|
||||||
|
} from '@user/data-common';
|
||||||
|
import { IMapBlockSaveBase, IMapLayer, ITileBase } from './types';
|
||||||
|
|
||||||
|
export abstract class MapTileBase<TSave extends IMapBlockSaveBase>
|
||||||
|
implements ITileBase, IDataCommonExtended, ISaveableContent<Readonly<TSave>>
|
||||||
|
{
|
||||||
|
readonly state: IDataCommon;
|
||||||
|
readonly layer: IMapLayer;
|
||||||
|
locator: ITileLocator;
|
||||||
|
triggers: Set<number> | null = null;
|
||||||
|
|
||||||
|
constructor(x: number, y: number, layer: IMapLayer) {
|
||||||
|
this.layer = layer;
|
||||||
|
this.state = layer.state;
|
||||||
|
this.locator = { x, y };
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract num(): number;
|
||||||
|
|
||||||
|
abstract raw(): ITileRawData | null;
|
||||||
|
|
||||||
|
abstract set(num: number): void;
|
||||||
|
|
||||||
|
block(): IMapBlockRawData | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFaceDirection(direction: FaceDirection): number {
|
||||||
|
const cur = this.num();
|
||||||
|
const next = this.layer.faceBinder.getFaceOf(cur, direction);
|
||||||
|
if (next) {
|
||||||
|
this.set(next.identifier);
|
||||||
|
return next.identifier;
|
||||||
|
} else {
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTrigger(): void {
|
||||||
|
this.triggers = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
addTrigger(trigger: number): void {
|
||||||
|
if (!this.triggers) {
|
||||||
|
this.triggers = new Set();
|
||||||
|
}
|
||||||
|
this.triggers.add(trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteTrigger(trigger: number): void {
|
||||||
|
if (!this.triggers) return;
|
||||||
|
this.triggers.delete(trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEmptyTrigger(): void {
|
||||||
|
this.triggers = new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract saveState(): Readonly<TSave>;
|
||||||
|
|
||||||
|
abstract loadState(save: Readonly<TSave>): void;
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
FaceDirection,
|
FaceDirection,
|
||||||
IDataCommonExtended,
|
IDataCommonExtended,
|
||||||
|
ILocationHelper,
|
||||||
IMapBlockRawData,
|
IMapBlockRawData,
|
||||||
IMapRawData,
|
IMapRawData,
|
||||||
IMoverController,
|
IMoverController,
|
||||||
@ -18,7 +19,135 @@ import {
|
|||||||
} from '@user/data-common';
|
} from '@user/data-common';
|
||||||
import { ITileStore } from '@user/data-common';
|
import { ITileStore } from '@user/data-common';
|
||||||
|
|
||||||
//#region 静态图层
|
//#region 图块信息
|
||||||
|
|
||||||
|
export interface IMapBlockSaveBase {
|
||||||
|
/** 此图块的触发器 */
|
||||||
|
triggers?: ReadonlySet<number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IStaticBlockSave extends IMapBlockSaveBase {}
|
||||||
|
|
||||||
|
export interface IDynamicBlockSave extends IMapBlockSaveBase {
|
||||||
|
/** 此动态图块的图块数字 */
|
||||||
|
num: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITileBase {
|
||||||
|
/** 该图块所处的位置 */
|
||||||
|
readonly locator: ITileLocator;
|
||||||
|
/** 该图块所处的图层对象 */
|
||||||
|
readonly layer: IMapLayer;
|
||||||
|
/** 包含的触发器列表,不存在时表示使用图块本身的触发器 */
|
||||||
|
readonly triggers: ReadonlySet<number> | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取该图块的原始图块信息
|
||||||
|
*/
|
||||||
|
raw(): ITileRawData | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取该图块的原始点位信息
|
||||||
|
*/
|
||||||
|
block(): IMapBlockRawData | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取该图块的图块数字
|
||||||
|
*/
|
||||||
|
num(): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置此图块为指定图块
|
||||||
|
* @param num 图块数字
|
||||||
|
*/
|
||||||
|
set(num: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置图块朝向,会一并修改 {@link num},返回设置后的当前图块数字
|
||||||
|
* @param direction 图块朝向
|
||||||
|
*/
|
||||||
|
setFaceDirection(direction: FaceDirection): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空此图块实例的触发器,回退为图块本身的触发器
|
||||||
|
*/
|
||||||
|
clearTrigger(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在此图块实例上添加触发器,当图块实例包含触发器时,将会覆盖图块本身的触发器
|
||||||
|
* @param trigger 触发器数字
|
||||||
|
*/
|
||||||
|
addTrigger(trigger: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除此图块实例上添加的触发器
|
||||||
|
* @param trigger 触发器数字
|
||||||
|
*/
|
||||||
|
deleteTrigger(trigger: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将此图块实例的触发器设置为无触发器,并覆盖图块本身的触发器。若图块本身包含触发器,此方法可以将其禁用
|
||||||
|
*/
|
||||||
|
useEmptyTrigger(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IStaticTile
|
||||||
|
extends
|
||||||
|
ITileBase,
|
||||||
|
IDataCommonExtended,
|
||||||
|
ISaveableContent<Readonly<IStaticBlockSave>> {
|
||||||
|
/**
|
||||||
|
* 将此静态图块转换为动态图块
|
||||||
|
*/
|
||||||
|
toDynamic(): IDynamicTile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断当前静态图块是否需要存档
|
||||||
|
*/
|
||||||
|
shouldSave(): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDynamicTile
|
||||||
|
extends
|
||||||
|
ITileBase,
|
||||||
|
IObjectMovable,
|
||||||
|
IDataCommonExtended,
|
||||||
|
ISaveableContent<Readonly<IDynamicBlockSave>> {
|
||||||
|
/** 该动态图块的移动器 */
|
||||||
|
readonly mover: IObjectMover<IDynamicTile>;
|
||||||
|
/** 该动态图块所属的动态图层 */
|
||||||
|
readonly layer: IMapLayer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将该动态图块转换为静态图块,如果该动态图块的坐标不为整数或正在移动,那么会转换失败并返回 `null`。
|
||||||
|
* 如果目标点有静态图块,那么会将其覆盖。
|
||||||
|
*/
|
||||||
|
toStatic(): IStaticTile | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将该动态图块转换为静态图块,如果该动态图块的坐标不为整数或正在移动,或目标点不为空,那么会转换失败并返回 `null`。
|
||||||
|
* 即在 `toStatic` 的基础上添加了一个目标点是否为空的判断。
|
||||||
|
*/
|
||||||
|
toStaticIfSafe(): IStaticTile | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单步便捷移动接口,适用于简单移动场景,复杂路径通过 `tile.mover` 访问。
|
||||||
|
* 等价于:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* mover.step(dir, count);
|
||||||
|
* return mover.start();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
step(dir: FaceDirection, count?: number): IMoverController | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接删除此图块
|
||||||
|
*/
|
||||||
|
delete(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region 地图图层
|
||||||
|
|
||||||
export interface IMapLayerData {
|
export interface IMapLayerData {
|
||||||
/** 当前引用是否过期,当地图图层内部的地图数组引用更新时,此项会变为 `true` */
|
/** 当前引用是否过期,当地图图层内部的地图数组引用更新时,此项会变为 `true` */
|
||||||
@ -32,14 +161,10 @@ export interface ILayerLocation {
|
|||||||
readonly locator: ITileLocator;
|
readonly locator: ITileLocator;
|
||||||
/** 该点的静态图块数字 */
|
/** 该点的静态图块数字 */
|
||||||
readonly tile: number;
|
readonly tile: number;
|
||||||
/** 该点的静态图块信息 */
|
|
||||||
readonly raw: ITileRawData | null;
|
|
||||||
/** 该点的静态触发器,-1 表示未设置(即使用图块本身的触发器),否则表示该点的静态触发器,覆盖图块本身的触发器 */
|
|
||||||
readonly trigger: number;
|
|
||||||
/** 该点包含的所有动态图块 */
|
/** 该点包含的所有动态图块 */
|
||||||
readonly dynamics: Iterable<IDynamicTile>;
|
readonly dynamics: Iterable<IDynamicTile>;
|
||||||
/** 该点的静态图块原始数据 */
|
/** 该点的静态图块原始数据 */
|
||||||
readonly block: IMapBlockRawData;
|
readonly static: IStaticTile;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMapLayerHooks extends IHookBase {
|
export interface IMapLayerHooks extends IHookBase {
|
||||||
@ -51,7 +176,7 @@ export interface IMapLayerHooks extends IHookBase {
|
|||||||
onResize(width: number, height: number): void;
|
onResize(width: number, height: number): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当更新某个区域的图块时执行
|
* 当更新某个区域的图块时执行,对应于 `putMapData` 方法
|
||||||
* @param x 更新区域左上角横坐标
|
* @param x 更新区域左上角横坐标
|
||||||
* @param y 更新区域左上角纵坐标
|
* @param y 更新区域左上角纵坐标
|
||||||
* @param width 更新区域宽度
|
* @param width 更新区域宽度
|
||||||
@ -60,7 +185,7 @@ export interface IMapLayerHooks extends IHookBase {
|
|||||||
onUpdateArea(x: number, y: number, width: number, height: number): void;
|
onUpdateArea(x: number, y: number, width: number, height: number): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当更新某个点的图块时执行,如果设置的图块与原先一样,则不会触发此方法
|
* 当更新某个点的图块时执行,如果设置的图块与原先一样,则不会触发此方法,对应于 `setBlock` 方法
|
||||||
* @param block 更新为的图块数字
|
* @param block 更新为的图块数字
|
||||||
* @param x 更新点横坐标
|
* @param x 更新点横坐标
|
||||||
* @param y 更新点纵坐标
|
* @param y 更新点纵坐标
|
||||||
@ -81,6 +206,24 @@ export interface IMapLayerHooks extends IHookBase {
|
|||||||
* @param y 门纵坐标
|
* @param y 门纵坐标
|
||||||
*/
|
*/
|
||||||
onCloseDoor(num: number, x: number, y: number): Promise<void>;
|
onCloseDoor(num: number, x: number, y: number): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当动态图块被创建时触发,包括从静态图块转换为动态图块
|
||||||
|
* @param tile 被创建的动态图块
|
||||||
|
*/
|
||||||
|
onCreateDynamic?(tile: IDynamicTile): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当动态图块被删除时触发
|
||||||
|
* @param tile 被删除的动态图块
|
||||||
|
*/
|
||||||
|
onDeleteDynamic?(tile: IDynamicTile): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当更新动态图块的位置时触发
|
||||||
|
* @param tile 更新位置的图块
|
||||||
|
*/
|
||||||
|
onUpdateDynamicPosition?(tile: IDynamicTile): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMapLayerHookController extends IHookController<IMapLayerHooks> {
|
export interface IMapLayerHookController extends IHookController<IMapLayerHooks> {
|
||||||
@ -93,36 +236,9 @@ export interface IMapLayerHookController extends IHookController<IMapLayerHooks>
|
|||||||
getMapData(): Readonly<IMapLayerData>;
|
getMapData(): Readonly<IMapLayerData>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMapLayer
|
interface ILayerStatic {
|
||||||
extends
|
|
||||||
IHookable<IMapLayerHooks, IMapLayerHookController>,
|
|
||||||
IDataCommonExtended {
|
|
||||||
/** 地图宽度 */
|
|
||||||
readonly width: number;
|
|
||||||
/** 地图高度 */
|
|
||||||
readonly height: number;
|
|
||||||
/**
|
/**
|
||||||
* 地图是否全部空白,此值具有充分性,但不具有必要性,
|
* 设置某一点的图块,会标记图层为脏
|
||||||
* 即如果其为 `true`,则地图一定空白,但是如果其为 `false`,那么地图也有可能空白
|
|
||||||
*/
|
|
||||||
readonly empty: boolean;
|
|
||||||
/** 图层纵深 */
|
|
||||||
readonly zIndex: number;
|
|
||||||
|
|
||||||
/** 当前图层所属的地图状态对象 */
|
|
||||||
readonly layerState: ILayerState;
|
|
||||||
/** 此图层对应的动态图块图层,z 层级与静态图块一致 */
|
|
||||||
readonly dynamicLayer: IDynamicLayer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断指定坐标是否在地图内
|
|
||||||
* @param x 横坐标
|
|
||||||
* @param y 纵坐标
|
|
||||||
*/
|
|
||||||
inMap(x: number, y: number): boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置某一点的图块
|
|
||||||
* @param block 图块数字
|
* @param block 图块数字
|
||||||
* @param x 图块横坐标
|
* @param x 图块横坐标
|
||||||
* @param y 图块纵坐标
|
* @param y 图块纵坐标
|
||||||
@ -130,7 +246,7 @@ export interface IMapLayer
|
|||||||
setBlock(block: number, x: number, y: number): void;
|
setBlock(block: number, x: number, y: number): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定点的图块
|
* 获取指定点的图块数字
|
||||||
* @param x 图块横坐标
|
* @param x 图块横坐标
|
||||||
* @param y 图块纵坐标
|
* @param y 图块纵坐标
|
||||||
* @returns 指定点的图块,如果没有图块,返回 0,如果不在地图上,返回 -1
|
* @returns 指定点的图块,如果没有图块,返回 0,如果不在地图上,返回 -1
|
||||||
@ -138,41 +254,14 @@ export interface IMapLayer
|
|||||||
getBlock(x: number, y: number): number;
|
getBlock(x: number, y: number): number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定点的所有图块信息
|
* 获取指定点的静态图块实例
|
||||||
* @param x 横坐标
|
|
||||||
* @param y 纵坐标
|
|
||||||
*/
|
|
||||||
getLocationData(x: number, y: number): ILayerLocation | null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定点的静态图块对应的有效触发器类型,若手动覆盖不存在则回退到图块默认触发器
|
|
||||||
* @param x 图块横坐标
|
* @param x 图块横坐标
|
||||||
* @param y 图块纵坐标
|
* @param y 图块纵坐标
|
||||||
*/
|
*/
|
||||||
getTriggerType(x: number, y: number): number;
|
getTile(x: number, y: number): IStaticTile | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置指定点静态图块的触发器
|
* 设置地图图块,会标记图层为脏
|
||||||
* @param type 触发器类型
|
|
||||||
* @param x 图块横坐标
|
|
||||||
* @param y 图块纵坐标
|
|
||||||
*/
|
|
||||||
setTriggerType(type: number, x: number, y: number): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除指定点静态图块的触发器,回退为图块默认触发器
|
|
||||||
* @param x 图块横坐标
|
|
||||||
* @param y 图块纵坐标
|
|
||||||
*/
|
|
||||||
revertTrigger(x: number, y: number): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空地图上所有静态图块的手动设置的触发器,恢复为图块默认触发器
|
|
||||||
*/
|
|
||||||
clearTrigger(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置地图图块
|
|
||||||
* @param array 地图图块数组
|
* @param array 地图图块数组
|
||||||
* @param x 数组第一项代表的横坐标
|
* @param x 数组第一项代表的横坐标
|
||||||
* @param y 数组第一项代表的纵坐标
|
* @param y 数组第一项代表的纵坐标
|
||||||
@ -199,9 +288,8 @@ export interface IMapLayer
|
|||||||
): Uint32Array;
|
): Uint32Array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 直接替换内部图块数组引用,跳过拷贝,高性能但风险较高。
|
* 直接替换内部图块数组引用,跳过拷贝,高性能但风险较高。一般仅供内部使用,外部不需要调用。
|
||||||
* 一般仅供 `MapStore` 读档时内部使用,外部正常情况下不应调用。
|
* 此操作不会标记图层为脏。调用方需确保传入数组的长度与 `width * height` 匹配,
|
||||||
* 调用方需确保传入数组的长度与 `width * height` 匹配,
|
|
||||||
* 且调用后不得再持有或修改传入的数组。
|
* 且调用后不得再持有或修改传入的数组。
|
||||||
* @param array 地图数组,会直接替换内部引用
|
* @param array 地图数组,会直接替换内部引用
|
||||||
*/
|
*/
|
||||||
@ -213,15 +301,170 @@ export interface IMapLayer
|
|||||||
getMapRef(): IMapLayerData;
|
getMapRef(): IMapLayerData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 直接设置内部触发器映射对象,一般仅供内部存读档使用,外部正常情况下不应调用
|
* 设置静态图块的朝向
|
||||||
* @param triggers 触发器映射
|
* @param x 要设置的静态图块横坐标
|
||||||
|
* @param y 要设置的静态图块纵坐标
|
||||||
|
* @param direction 目标朝向
|
||||||
|
* @returns 更新朝向后该静态图块的图块数字
|
||||||
*/
|
*/
|
||||||
setTriggerRef(triggers: Map<number, number>): void;
|
setStaticDirection(x: number, y: number, direction: FaceDirection): number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取静态触发器覆盖映射,一般仅供内部存档逻辑使用
|
* 迭代所有的图块
|
||||||
*/
|
*/
|
||||||
getTriggerRef(): ReadonlyMap<number, number>;
|
iterateBlocks(): Iterable<ILayerLocation>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ILayerDynamic {
|
||||||
|
/**
|
||||||
|
* 在指定位置创建一个动态图块
|
||||||
|
* @param num 图块数字
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
*/
|
||||||
|
createDynamic(num: number, x: number, y: number): IDynamicTile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从静态图层读取并清除指定位置的图块,创建对应动态图块并返回
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
* @param keepTrigger 是否将静态图块的触发器保留至动态图块,不论是否保留,此格的静态触发器都会被清空
|
||||||
|
*/
|
||||||
|
transferToDynamic(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
keepTrigger?: boolean
|
||||||
|
): IDynamicTile | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将动态图块还原为静态图块
|
||||||
|
* @param tile 要还原的动态图块
|
||||||
|
* @param keepTrigger 是否保留动态图块的触发器至静态图块,若不保留,那么此格将回退为静态图块本身的触发器
|
||||||
|
*/
|
||||||
|
transferToStatic(
|
||||||
|
tile: IDynamicTile,
|
||||||
|
keepTrigger?: boolean
|
||||||
|
): IStaticTile | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅当目标位置不存在静态图块时才还原为静态图块,否则不转换
|
||||||
|
* @param tile 要还原的动态图块
|
||||||
|
* @param keepTrigger 是否保留动态图块的触发器至静态图块,若不保留,那么此格将回退为静态图块本身的触发器
|
||||||
|
*/
|
||||||
|
transferToStaticIfSafe(
|
||||||
|
tile: IDynamicTile,
|
||||||
|
keepTrigger?: boolean
|
||||||
|
): IStaticTile | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定动态图块,当删除完成后兑现返回值
|
||||||
|
* @param tile 要删除的动态图块
|
||||||
|
*/
|
||||||
|
deleteDynamic(tile: IDynamicTile): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定格点上所有动态图块的可迭代对象
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
*/
|
||||||
|
getDynamicTilesAt(x: number, y: number): Iterable<IDynamicTile>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 迭代所有的动态图块
|
||||||
|
*/
|
||||||
|
iterateDynamicTiles(): Iterable<IDynamicTile>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置动态图块的朝向
|
||||||
|
* @param tile 要设置朝向的动态图块
|
||||||
|
* @param direction 目标朝向
|
||||||
|
* @returns 更新朝向后该动态图块的图块数字
|
||||||
|
*/
|
||||||
|
setDynamicDirection(tile: IDynamicTile, direction: FaceDirection): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新动态图块位置,用于在图块发生移动时更新内部存储,一般不需要手动调用
|
||||||
|
* @param tile 动态图块
|
||||||
|
*/
|
||||||
|
updateDynamicTile(tile: IDynamicTile): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMapLayerSave {
|
||||||
|
/** 地图宽度 */
|
||||||
|
readonly width: number;
|
||||||
|
/** 地图高度 */
|
||||||
|
readonly height: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每行的地图数据,键表示行索引,值表示该行完整的 Uint32Array 数据。
|
||||||
|
* HighCompression 时使用此接口,仅包含与参考基准不同的行。
|
||||||
|
*/
|
||||||
|
readonly rows?: ReadonlyMap<number, Uint32Array>;
|
||||||
|
|
||||||
|
/** 完整地图,当使用 `NoCompression` 和 `LowCompression` 时使用此接口 */
|
||||||
|
readonly fullMap?: Uint32Array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每个静态图块实例的存储,在 `LowCompression` 和 `HighCompression` 下,
|
||||||
|
* 此存储只会保存被标记为脏的图块,避免占用过大的存储空间。
|
||||||
|
*/
|
||||||
|
readonly staticBlocks?: ReadonlyMap<number, Readonly<IStaticBlockSave>>;
|
||||||
|
/**
|
||||||
|
* 每个动态图块实例的存储,在 `LowCompression` 和 `HighCompression` 下,
|
||||||
|
* 此存储只会保存被标记为脏的图块,避免占用过大的存储空间。
|
||||||
|
*/
|
||||||
|
readonly dynamicBlocks?: ReadonlyMap<number, Readonly<IDynamicBlockSave>[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMapLayer
|
||||||
|
extends
|
||||||
|
IHookable<IMapLayerHooks, IMapLayerHookController>,
|
||||||
|
IDataCommonExtended,
|
||||||
|
ILayerStatic,
|
||||||
|
ILayerDynamic,
|
||||||
|
ISaveableContent<IMapLayerSave> {
|
||||||
|
/** 地图宽度 */
|
||||||
|
readonly width: number;
|
||||||
|
/** 地图高度 */
|
||||||
|
readonly height: number;
|
||||||
|
/**
|
||||||
|
* 地图是否全部空白,此值具有充分性,但不具有必要性,
|
||||||
|
* 即如果其为 `true`,则地图一定空白,但是如果其为 `false`,那么地图也有可能空白
|
||||||
|
*/
|
||||||
|
readonly empty: boolean;
|
||||||
|
/** 图层纵深 */
|
||||||
|
readonly zIndex: number;
|
||||||
|
|
||||||
|
/** 当前图层所属的地图状态对象 */
|
||||||
|
readonly map: IGameMap;
|
||||||
|
/** 该图层使用的朝向绑定器 */
|
||||||
|
readonly faceBinder: IRoleFaceBinder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置图层的参考基准,存档时会根据参考基准进行必要的压缩处理,仅可调用一次,多次调用无效
|
||||||
|
* @param data 图层的静态地图矩阵
|
||||||
|
*/
|
||||||
|
compareWith(data: Uint32Array): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断指定坐标是否在地图内
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
*/
|
||||||
|
inMap(x: number, y: number): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置该图层使用的朝向绑定器,用于控制图块转向操作
|
||||||
|
* @param binder 朝向绑定器
|
||||||
|
*/
|
||||||
|
setFaceBinder(binder: IRoleFaceBinder | null): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定点的所有图块信息
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
*/
|
||||||
|
getLocationData(x: number, y: number): ILayerLocation | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置地图纵深,会影响渲染的遮挡顺序
|
* 设置地图纵深,会影响渲染的遮挡顺序
|
||||||
@ -243,54 +486,53 @@ export interface IMapLayer
|
|||||||
* @param y 门纵坐标
|
* @param y 门纵坐标
|
||||||
*/
|
*/
|
||||||
closeDoor(num: number, x: number, y: number): Promise<void>;
|
closeDoor(num: number, x: number, y: number): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前图层是否为脏,即是否经过了修改,相对于设置的参考基准。
|
||||||
|
* 图层相对于参考基准变化时一定为 `true`,但为 `true` 时图层不一定相对参考基准发生了变化。
|
||||||
|
* 此标记仅针对静态图层,动态图块的改动不会影响此值。
|
||||||
|
*/
|
||||||
|
dirty(): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记当前图层是否为脏,会影响存档,如果不明白原理不建议随意调用
|
||||||
|
* @param dirty 图层是否为脏
|
||||||
|
*/
|
||||||
|
markDirty(dirty: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IResizableMapLayer extends IMapLayer {
|
||||||
|
/**
|
||||||
|
* 重设图层宽高,清空地图上的所有内容,会标记图层为脏
|
||||||
|
* @param width 要设置为的宽度
|
||||||
|
* @param height 要设置为的高度
|
||||||
|
*/
|
||||||
|
resize(width: number, height: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重设图层宽高,会保留已有图块内容,扩大的区域补零,缩小的区域裁剪,会标记图层为脏
|
||||||
|
* @param width 要设置为的宽度
|
||||||
|
* @param height 要设置为的高度
|
||||||
|
*/
|
||||||
|
resize2(width: number, height: number): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 图层管理
|
//#region 图层管理
|
||||||
|
|
||||||
export interface ILayerStateHooks extends IHookBase {
|
export interface IGameMapHooks extends IHookBase {
|
||||||
/**
|
/**
|
||||||
* 当设置背景图块时执行,如果设置的背景图块与原先一样,则不会执行
|
* 当设置背景图块时执行,如果设置的背景图块与原先一样,则不会执行
|
||||||
* @param tile 背景图块
|
* @param tile 背景图块
|
||||||
*/
|
*/
|
||||||
onChangeBackground(tile: number): void;
|
onChangeBackground?(tile: number): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当地图列表发生变化时执行
|
* 当地图列表发生变化时执行
|
||||||
* @param layerList 地图图层列表
|
* @param layerList 地图图层列表
|
||||||
*/
|
*/
|
||||||
onUpdateLayer(layerList: Set<IMapLayer>): void;
|
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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当地图状态对象的某个图层大小发生变化时执行
|
* 当地图状态对象的某个图层大小发生变化时执行
|
||||||
@ -298,11 +540,23 @@ export interface ILayerStateHooks extends IHookBase {
|
|||||||
* @param width 地图的新宽度
|
* @param width 地图的新宽度
|
||||||
* @param height 地图的新高度
|
* @param height 地图的新高度
|
||||||
*/
|
*/
|
||||||
onResizeLayer(layer: IMapLayer, width: number, height: number): void;
|
onResizeLayer?(layer: IMapLayer, width: number, height: number): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILayerState
|
export interface IGameMapSave {
|
||||||
extends IHookable<ILayerStateHooks>, IDataCommonExtended {
|
/** 楼层背景 */
|
||||||
|
readonly background: number;
|
||||||
|
/** 该地图每一个图层的存档信息,键表示纵深,值对应图层存档数据 */
|
||||||
|
readonly layers: ReadonlyMap<number, IMapLayerSave>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGameMap
|
||||||
|
extends
|
||||||
|
IHookable<IGameMapHooks>,
|
||||||
|
IDataCommonExtended,
|
||||||
|
ISaveableContent<IGameMapSave> {
|
||||||
|
/** 坐标索引器,用于坐标与扁平索引之间的双向转换 */
|
||||||
|
readonly indexer: ILocationHelper;
|
||||||
/** 地图列表 */
|
/** 地图列表 */
|
||||||
readonly layerList: Set<IMapLayer>;
|
readonly layerList: Set<IMapLayer>;
|
||||||
/** 当前楼层共享的图块定义 store */
|
/** 当前楼层共享的图块定义 store */
|
||||||
@ -352,6 +606,12 @@ export interface ILayerState
|
|||||||
*/
|
*/
|
||||||
getLayerAlias(layer: IMapLayer): string | undefined;
|
getLayerAlias(layer: IMapLayer): string | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置地图的参考基准,存档时会根据参考基准进行必要的压缩,仅可调用一次,多次调用无效
|
||||||
|
* @param data 地图的静态地图矩阵,键表示纵深,值表示对应图层的矩阵
|
||||||
|
*/
|
||||||
|
compareWith(data: Map<number, Uint32Array>): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新设置所有图层的大小,同时更新楼层预设宽高
|
* 重新设置所有图层的大小,同时更新楼层预设宽高
|
||||||
* @param width 新的地图宽度
|
* @param width 新的地图宽度
|
||||||
@ -384,47 +644,26 @@ export interface ILayerState
|
|||||||
setEventLayer(layer: IMapLayer | null): void;
|
setEventLayer(layer: IMapLayer | null): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 楼层是否被修改过(相对于参考基准)
|
* 当前地图是否为脏,相对于参考基准,当任意图层或地图设置发生变化时为脏。
|
||||||
|
* 地图相对于参考基准变化时一定为 `true`,但为 `true` 时地图不一定相对参考基准发生了变化。
|
||||||
|
* 此标记仅针对静态图层和本地图的地图设置,动态图块的改动不会影响此值。
|
||||||
*/
|
*/
|
||||||
isDirty(): boolean;
|
dirty(): boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置楼层脏标记
|
* 标记当前地图是否为脏,会影响存档,如果不明白原理不建议随意调用
|
||||||
|
* @param dirty 地图是否为脏
|
||||||
*/
|
*/
|
||||||
setDirty(dirty: boolean): void;
|
markDirty(dirty: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 楼层管理
|
//#region 楼层管理
|
||||||
|
|
||||||
export interface IMapLayerSave {
|
|
||||||
readonly width: number;
|
|
||||||
readonly height: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* key = 行索引,value = 该行完整的 Uint32Array 数据;
|
|
||||||
* HighCompression 时使用此接口,仅包含与参考基准不同的行;
|
|
||||||
* 读档时,不在此 Map 中的行从参考基准还原。
|
|
||||||
*/
|
|
||||||
readonly rows?: ReadonlyMap<number, Uint32Array>;
|
|
||||||
|
|
||||||
/** 完整地图,当使用 `NoCompression` 和 `LowCompression` 时使用此接口 */
|
|
||||||
readonly fullMap?: Uint32Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ILayerStateSave {
|
|
||||||
/** 楼层背景 */
|
|
||||||
readonly background: number;
|
|
||||||
/** key = zIndex,value = 对应图层存档数据 */
|
|
||||||
readonly layers: ReadonlyMap<number, IMapLayerSave>;
|
|
||||||
/** 静态触发器覆盖映射,仅在存在覆盖时写入 */
|
|
||||||
readonly triggers: ReadonlyMap<number, ReadonlyMap<number, number>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMapStoreSave {
|
export interface IMapStoreSave {
|
||||||
/** key = 楼层 id,只包含 active 的楼层,inactive 的楼层不写入,读档时无需处理 */
|
/** 键表示楼层 id,只包含已激活的楼层,未激活的楼层不写入 */
|
||||||
readonly floors: ReadonlyMap<string, ILayerStateSave>;
|
readonly floors: ReadonlyMap<string, IGameMapSave>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMapAreaInterval {
|
export interface IMapAreaInterval {
|
||||||
@ -441,34 +680,34 @@ export interface IMapState
|
|||||||
/** 所有楼层的 id 有序数组 */
|
/** 所有楼层的 id 有序数组 */
|
||||||
readonly maps: ReadonlyArray<string>;
|
readonly maps: ReadonlyArray<string>;
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定 id 的楼层状态,不存在则返回 null
|
|
||||||
* @param id 楼层 id
|
|
||||||
*/
|
|
||||||
getLayerState(id: string): ILayerState | null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定 id 的楼层状态,要求楼层必须是 active 的,否则返回 null
|
|
||||||
* @param id 楼层 id
|
|
||||||
*/
|
|
||||||
getActiveMap(id: string): ILayerState | null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从楼层原始数据生成楼层状态
|
|
||||||
* @param raw 楼层原始数据
|
|
||||||
*/
|
|
||||||
fromRaw(raw: IMapRawData): ILayerState | null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建并注册一个空白楼层,若 id 已存在则警告并覆盖,返回楼层状态对象
|
* 创建并注册一个空白楼层,若 id 已存在则警告并覆盖,返回楼层状态对象
|
||||||
* @param id 楼层 id
|
* @param id 楼层 id
|
||||||
* @param width 地图宽度
|
* @param width 地图宽度
|
||||||
* @param height 地图高度
|
* @param height 地图高度
|
||||||
*/
|
*/
|
||||||
createLayerState(id: string, width: number, height: number): ILayerState;
|
createMap(id: string, width: number, height: number): IGameMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定 id 的楼层是否激活,不存在的 id 返回 false
|
* 获取指定 id 的楼层状态,不存在则返回 `null`
|
||||||
|
* @param id 楼层 id
|
||||||
|
*/
|
||||||
|
getMap(id: string): IGameMap | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从楼层原始数据生成楼层状态
|
||||||
|
* @param raw 楼层原始数据
|
||||||
|
*/
|
||||||
|
fromRaw(raw: IMapRawData): IGameMap | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定 id 的楼层状态,要求楼层必须是已激活的,否则返回 `null`
|
||||||
|
* @param id 楼层 id
|
||||||
|
*/
|
||||||
|
getActiveMap(id: string): IGameMap | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定 id 的楼层是否激活,不存在的 id 返回 `false`
|
||||||
* @param id 楼层 id
|
* @param id 楼层 id
|
||||||
*/
|
*/
|
||||||
isMapActive(id: string): boolean;
|
isMapActive(id: string): boolean;
|
||||||
@ -481,23 +720,23 @@ export interface IMapState
|
|||||||
setMapActiveStatus(id: string, active: boolean): void;
|
setMapActiveStatus(id: string, active: boolean): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 迭代所有 active 的楼层,yield [id, ILayerState]
|
* 迭代所有已激活的楼层
|
||||||
*/
|
*/
|
||||||
iterateActiveMaps(): Iterable<[string, ILayerState]>;
|
iterateActiveMaps(): Iterable<[string, IGameMap]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 迭代所有 inactive 的楼层,yield [id, ILayerState]
|
* 迭代所有未激活的楼层
|
||||||
*/
|
*/
|
||||||
iterateInactiveMaps(): Iterable<[string, ILayerState]>;
|
iterateInactiveMaps(): Iterable<[string, IGameMap]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 迭代所有楼层,yield [id, ILayerState]
|
* 迭代所有楼层
|
||||||
*/
|
*/
|
||||||
iterateAllMaps(): Iterable<[string, ILayerState]>;
|
iterateAllMaps(): Iterable<[string, IGameMap]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置压缩参考基准,以首次调用为唯一基准,再次调用不更新。
|
* 设置参考基准,存档时会根据参考基准进行必要的压缩处理,仅可调用一次,多次调用无效
|
||||||
* @param ref 外层 key = 楼层 id,内层 key = zIndex,value = 图层完整图块数据
|
* @param ref 外层键表示楼层 id,内层键表示图层纵深,值表示图层完整图块数据
|
||||||
*/
|
*/
|
||||||
compareWith(ref: Map<string, Map<number, Uint32Array>>): void;
|
compareWith(ref: Map<string, Map<number, Uint32Array>>): void;
|
||||||
|
|
||||||
@ -507,13 +746,6 @@ export interface IMapState
|
|||||||
*/
|
*/
|
||||||
setMapList(maps: string[]): void;
|
setMapList(maps: string[]): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用自定义排序函数重排 maps。排序函数接收当前列表的副本,返回新顺序。
|
|
||||||
* 若返回的数组元素集合与原列表不一致,则警告并放弃本次排序
|
|
||||||
* @param sort 排序函数
|
|
||||||
*/
|
|
||||||
useManualOrder(sort: (arr: string[]) => string[]): void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设定分区列表。每个分区由一个或多个区间组成
|
* 设定分区列表。每个分区由一个或多个区间组成
|
||||||
* @param areas 分区集合
|
* @param areas 分区集合
|
||||||
@ -527,7 +759,7 @@ export interface IMapState
|
|||||||
activeArea(id: string): void;
|
activeArea(id: string): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 去激活指定楼层所属分区的所有楼层
|
* 取消激活指定楼层所属分区的所有楼层
|
||||||
* @param id 楼层 id
|
* @param id 楼层 id
|
||||||
*/
|
*/
|
||||||
deactiveArea(id: string): void;
|
deactiveArea(id: string): void;
|
||||||
@ -539,169 +771,11 @@ export interface IMapState
|
|||||||
useAutoActivitor(enable: boolean): void;
|
useAutoActivitor(enable: boolean): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知当前进入的楼层。开启自动激活器时,将自动去激活上一个分区并激活新分区
|
* 通知当前勇士进入的楼层,用于自动分区。当开启自动分区激活器时,若进入的楼层不属于当前分区,
|
||||||
|
* 那么会自动取消激活当前分区,然后激活新进入的分区。此函数一般不需要手动调用。
|
||||||
* @param id 楼层 id
|
* @param id 楼层 id
|
||||||
*/
|
*/
|
||||||
notifyEnterFloor(id: string): void;
|
notifyEnterFloor(id: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 动态图块
|
|
||||||
|
|
||||||
export interface IDynamicLayerHooks extends IHookBase {
|
|
||||||
/**
|
|
||||||
* 当图块被创建(含从静态图块转换)时触发
|
|
||||||
* @param tile 被创建的动态图块
|
|
||||||
* @param layer 所属的动态图层
|
|
||||||
*/
|
|
||||||
onCreateTile?(tile: IDynamicTile, layer: IDynamicLayer): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 当图块被删除时触发
|
|
||||||
* @param tile 被删除的动态图块
|
|
||||||
* @param layer 所属的动态图层
|
|
||||||
*/
|
|
||||||
onDeleteTile?(tile: IDynamicTile, layer: IDynamicLayer): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 当更新动态图块的位置时触发(包括使用 `mover` 触发的移动)
|
|
||||||
* @param tile 更新位置的图块
|
|
||||||
* @param layer 所属的动态图层
|
|
||||||
*/
|
|
||||||
onUpdateTilePosition?(tile: IDynamicTile, layer: IDynamicLayer): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDynamicLayer
|
|
||||||
extends IHookable<IDynamicLayerHooks>, IDataCommonExtended {
|
|
||||||
/** 当前动态图层所属的静态图层 */
|
|
||||||
readonly layer: IMapLayer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在指定位置创建一个动态图块
|
|
||||||
* @param num 图块数字
|
|
||||||
* @param x 横坐标
|
|
||||||
* @param y 纵坐标
|
|
||||||
* @returns 创建的动态图块引用
|
|
||||||
*/
|
|
||||||
createDynamic(num: number, x: number, y: number): IDynamicTile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从所属静态图层读取并清除指定位置的图块,创建对应动态图块并返回引用。
|
|
||||||
* 若该位置图块为 0,则发出警告并仍然创建 `num = 0` 的动态图块
|
|
||||||
* @param x 横坐标
|
|
||||||
* @param y 纵坐标
|
|
||||||
* @returns 创建的动态图块引用
|
|
||||||
*/
|
|
||||||
transferToDynamic(
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
keepTrigger?: boolean
|
|
||||||
): IDynamicTile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将动态图块还原为静态图块。坐标越界则警告并放弃,
|
|
||||||
* 否则写回静态图层并触发 {@link IDynamicLayerHooks.onDeleteTile}
|
|
||||||
* @param tile 要还原的动态图块
|
|
||||||
*/
|
|
||||||
transferToStatic(tile: IDynamicTile, keepTrigger?: boolean): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 仅当目标位置静态图块为 0(空白)时才还原为静态图块,否则不转换
|
|
||||||
* @param tile 要还原的动态图块
|
|
||||||
* @returns 是否转换成功
|
|
||||||
*/
|
|
||||||
transferToStaticIfSafe(tile: IDynamicTile, keepTrigger?: boolean): boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除指定动态图块,触发 {@link IDynamicLayerHooks.onDeleteTile} 钩子。
|
|
||||||
* 若图块不属于此层则发出警告
|
|
||||||
* @param tile 要删除的动态图块
|
|
||||||
*/
|
|
||||||
deleteDynamic(tile: IDynamicTile): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定格点上所有动态图块的可迭代对象
|
|
||||||
* @param x 横坐标
|
|
||||||
* @param y 纵坐标
|
|
||||||
*/
|
|
||||||
getDynamicTilesAt(x: number, y: number): Iterable<IDynamicTile>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 迭代所有的动态图块
|
|
||||||
*/
|
|
||||||
iterateDynamicTiles(): Iterable<IDynamicTile>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手动设置动态图块的朝向,更新 `tile.num`(若有朝向绑定)。
|
|
||||||
* 转向逻辑与移动时的转向逻辑相同,但不触发移动
|
|
||||||
* @param tile 要设置朝向的动态图块
|
|
||||||
* @param direction 目标朝向
|
|
||||||
*/
|
|
||||||
setDynamicDirection(tile: IDynamicTile, direction: FaceDirection): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新图块内部存储位置
|
|
||||||
* @param tile 动态图块
|
|
||||||
*/
|
|
||||||
updateDynamicTile(tile: IDynamicTile): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDynamicTile extends IObjectMovable, IDataCommonExtended {
|
|
||||||
/** 当前图块数字 */
|
|
||||||
readonly num: number;
|
|
||||||
/** 当前动态图块携带的触发器类型,-1 表示无触发器 */
|
|
||||||
readonly triggerType: number;
|
|
||||||
/** 当前图块所属的动态图层 */
|
|
||||||
readonly layer: IDynamicLayer;
|
|
||||||
/** 当前动态图块的移动器 */
|
|
||||||
readonly mover: IObjectMover<IDynamicTile>;
|
|
||||||
/** 当前动态图块的图块数据 */
|
|
||||||
readonly raw: ITileRawData | null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置图块朝向,会一并修改 {@link num},返回设置后的当前图块数字
|
|
||||||
* @param direction 图块朝向
|
|
||||||
*/
|
|
||||||
setFaceDirection(direction: FaceDirection): number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置当前动态图块的触发器类型
|
|
||||||
* @param type 触发器类型
|
|
||||||
*/
|
|
||||||
setTriggerType(type: number): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 直接删除此图块
|
|
||||||
*/
|
|
||||||
delete(): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将当前图块还原为静态图块
|
|
||||||
*/
|
|
||||||
toStatic(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 还原为静态图块,如果当前位置有东西则不转换
|
|
||||||
*/
|
|
||||||
toStaticIfSafe(): boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单步便捷移动接口,适用于简单移动场景,复杂路径通过 `tile.mover` 访问。
|
|
||||||
* 等价于:
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* mover.step(dir, count);
|
|
||||||
* return mover.start();
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
step(dir: FaceDirection, count?: number): IMoverController | null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注入朝向绑定器,初始状态视为无朝向绑定
|
|
||||||
* @param binder 朝向绑定器
|
|
||||||
*/
|
|
||||||
setFaceBinder(binder: IRoleFaceBinder | null): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|||||||
@ -8,12 +8,12 @@ import {
|
|||||||
IReplayArray,
|
IReplayArray,
|
||||||
IReplayReadStream,
|
IReplayReadStream,
|
||||||
IReplaySandbox,
|
IReplaySandbox,
|
||||||
IReplaySandboxHook,
|
IReplaySandboxHooks,
|
||||||
IReplaySystem
|
IReplaySystem
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export class ReplaySandbox
|
export class ReplaySandbox
|
||||||
extends Hookable<IReplaySandboxHook>
|
extends Hookable<IReplaySandboxHooks>
|
||||||
implements IReplaySandbox
|
implements IReplaySandbox
|
||||||
{
|
{
|
||||||
pausing: boolean = true;
|
pausing: boolean = true;
|
||||||
@ -41,8 +41,8 @@ export class ReplaySandbox
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected createController(
|
protected createController(
|
||||||
hook: Partial<IReplaySandboxHook>
|
hook: Partial<IReplaySandboxHooks>
|
||||||
): IHookController<IReplaySandboxHook> {
|
): IHookController<IReplaySandboxHooks> {
|
||||||
return new HookController(this, hook);
|
return new HookController(this, hook);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
IReplaySandbox,
|
IReplaySandbox,
|
||||||
IReplaySandboxConfig,
|
IReplaySandboxConfig,
|
||||||
IReplaySystem,
|
IReplaySystem,
|
||||||
IReplaySystemHook,
|
IReplaySystemHooks,
|
||||||
ReplayCommandWidth,
|
ReplayCommandWidth,
|
||||||
ReplayParamValue
|
ReplayParamValue
|
||||||
} from './types';
|
} from './types';
|
||||||
@ -18,7 +18,7 @@ import { ReplayArray } from './array';
|
|||||||
import { ReplaySandbox } from './sandbox';
|
import { ReplaySandbox } from './sandbox';
|
||||||
|
|
||||||
export class ReplaySystem
|
export class ReplaySystem
|
||||||
extends Hookable<IReplaySystemHook>
|
extends Hookable<IReplaySystemHooks>
|
||||||
implements IReplaySystem
|
implements IReplaySystem
|
||||||
{
|
{
|
||||||
replaying: boolean = false;
|
replaying: boolean = false;
|
||||||
@ -42,8 +42,8 @@ export class ReplaySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected createController(
|
protected createController(
|
||||||
hook: Partial<IReplaySystemHook>
|
hook: Partial<IReplaySystemHooks>
|
||||||
): IHookController<IReplaySystemHook> {
|
): IHookController<IReplaySystemHooks> {
|
||||||
return new HookController(this, hook);
|
return new HookController(this, hook);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export interface IReplayCommand {
|
|||||||
execute(step: IReplayStepHandler): Promise<boolean>;
|
execute(step: IReplayStepHandler): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IReplaySandboxHook extends IHookBase {
|
export interface IReplaySandboxHooks extends IHookBase {
|
||||||
/**
|
/**
|
||||||
* 当录像沙箱将某一步录像操作执行完毕后触发
|
* 当录像沙箱将某一步录像操作执行完毕后触发
|
||||||
* @param step 当前录像步信息
|
* @param step 当前录像步信息
|
||||||
@ -54,7 +54,7 @@ export interface IReplaySandboxHook extends IHookBase {
|
|||||||
onResumeReplay?(): void;
|
onResumeReplay?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IReplaySandbox extends IHookable<IReplaySandboxHook> {
|
export interface IReplaySandbox extends IHookable<IReplaySandboxHooks> {
|
||||||
/** 是否处于暂停状态 */
|
/** 是否处于暂停状态 */
|
||||||
readonly pausing: boolean;
|
readonly pausing: boolean;
|
||||||
/** 当前录像是否已经播放完毕 */
|
/** 当前录像是否已经播放完毕 */
|
||||||
@ -282,7 +282,7 @@ export interface IReplayArray {
|
|||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IReplaySystemHook extends IHookBase {
|
export interface IReplaySystemHooks extends IHookBase {
|
||||||
/**
|
/**
|
||||||
* 当创建新的录像沙盒时触发
|
* 当创建新的录像沙盒时触发
|
||||||
* @param sandbox 创建的录像沙盒
|
* @param sandbox 创建的录像沙盒
|
||||||
@ -313,7 +313,7 @@ export interface IReplaySandboxConfig {
|
|||||||
save?: Map<string, unknown>;
|
save?: Map<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IReplaySystem extends IHookable<IReplaySystemHook> {
|
export interface IReplaySystem extends IHookable<IReplaySystemHooks> {
|
||||||
/** 当前是否处在录像播放状态 */
|
/** 当前是否处在录像播放状态 */
|
||||||
readonly replaying: boolean;
|
readonly replaying: boolean;
|
||||||
/** 当前正在播放的录像沙箱实例 */
|
/** 当前正在播放的录像沙箱实例 */
|
||||||
|
|||||||
@ -330,11 +330,7 @@ export class CoreState implements ICoreState {
|
|||||||
const reference = new Map<string, Map<number, Uint32Array>>();
|
const reference = new Map<string, Map<number, Uint32Array>>();
|
||||||
for (const id of floors) {
|
for (const id of floors) {
|
||||||
const floor = data[id];
|
const floor = data[id];
|
||||||
const state = this.maps.createLayerState(
|
const state = this.maps.createMap(id, floor.width, floor.height);
|
||||||
id,
|
|
||||||
floor.width,
|
|
||||||
floor.height
|
|
||||||
);
|
|
||||||
const bg = state.addLayer();
|
const bg = state.addLayer();
|
||||||
const bg2 = state.addLayer();
|
const bg2 = state.addLayer();
|
||||||
const event = state.addLayer();
|
const event = state.addLayer();
|
||||||
|
|||||||
@ -46,7 +46,7 @@ export class DefaultHeroMoveTopImpl implements IHeroMoveTopImpl {
|
|||||||
|
|
||||||
inBound(x: number, y: number, floorId: string | undefined): boolean {
|
inBound(x: number, y: number, floorId: string | undefined): boolean {
|
||||||
if (isNil(floorId)) return false;
|
if (isNil(floorId)) return false;
|
||||||
const layerState = this.maps.getLayerState(floorId);
|
const layerState = this.maps.getMap(floorId);
|
||||||
if (!layerState) return false;
|
if (!layerState) return false;
|
||||||
const { width, height } = layerState;
|
const { width, height } = layerState;
|
||||||
return x >= 0 && y >= 0 && x < width && y < height;
|
return x >= 0 && y >= 0 && x < width && y < height;
|
||||||
@ -66,7 +66,7 @@ export class DefaultHeroMoveTopImpl implements IHeroMoveTopImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const map = this.maps.getLayerState(floorId);
|
const map = this.maps.getMap(floorId);
|
||||||
if (!map) return false;
|
if (!map) return false;
|
||||||
const event = map.eventLayer;
|
const event = map.eventLayer;
|
||||||
if (!event) return false;
|
if (!event) return false;
|
||||||
@ -115,7 +115,7 @@ export class DefaultHeroMoveTopImpl implements IHeroMoveTopImpl {
|
|||||||
shouldHit(handler: IHeroMoveTopHandler): boolean {
|
shouldHit(handler: IHeroMoveTopHandler): boolean {
|
||||||
const { nextLoc, floorId } = handler;
|
const { nextLoc, floorId } = handler;
|
||||||
if (isNil(floorId)) return false;
|
if (isNil(floorId)) return false;
|
||||||
const layerState = this.maps.getLayerState(floorId);
|
const layerState = this.maps.getMap(floorId);
|
||||||
if (!layerState) return false;
|
if (!layerState) return false;
|
||||||
const eventLayer = layerState.eventLayer;
|
const eventLayer = layerState.eventLayer;
|
||||||
if (!eventLayer) return false;
|
if (!eventLayer) return false;
|
||||||
@ -146,7 +146,7 @@ export class DefaultHeroMoveTopImpl implements IHeroMoveTopImpl {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (isNil(handler.floorId)) return Promise.resolve();
|
if (isNil(handler.floorId)) return Promise.resolve();
|
||||||
|
|
||||||
const map = this.maps.getLayerState(handler.floorId);
|
const map = this.maps.getMap(handler.floorId);
|
||||||
if (!map) return Promise.resolve();
|
if (!map) return Promise.resolve();
|
||||||
const event = map.eventLayer;
|
const event = map.eventLayer;
|
||||||
if (!event) return Promise.resolve();
|
if (!event) return Promise.resolve();
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
ICombatFlow,
|
ICombatFlow,
|
||||||
ICombatFlowHandler,
|
ICombatFlowHandler,
|
||||||
ICombatFlowHook,
|
ICombatFlowHooks,
|
||||||
ICombatScript,
|
ICombatScript,
|
||||||
IDamageContext,
|
IDamageContext,
|
||||||
IEnemyContext,
|
IEnemyContext,
|
||||||
@ -25,7 +25,7 @@ import {
|
|||||||
} from '@user/data-base';
|
} from '@user/data-base';
|
||||||
|
|
||||||
export class CombatFlow<TEnemy, THero>
|
export class CombatFlow<TEnemy, THero>
|
||||||
extends Hookable<ICombatFlowHook<TEnemy, THero>>
|
extends Hookable<ICombatFlowHooks<TEnemy, THero>>
|
||||||
implements ICombatFlow<TEnemy, THero>
|
implements ICombatFlow<TEnemy, THero>
|
||||||
{
|
{
|
||||||
hero: IHeroAttribute<THero> | null = null;
|
hero: IHeroAttribute<THero> | null = null;
|
||||||
@ -42,8 +42,8 @@ export class CombatFlow<TEnemy, THero>
|
|||||||
//#region 对象控制
|
//#region 对象控制
|
||||||
|
|
||||||
protected createController(
|
protected createController(
|
||||||
hook: Partial<ICombatFlowHook<TEnemy, THero>>
|
hook: Partial<ICombatFlowHooks<TEnemy, THero>>
|
||||||
): IHookController<ICombatFlowHook<TEnemy, THero>> {
|
): IHookController<ICombatFlowHooks<TEnemy, THero>> {
|
||||||
return new HookController(this, hook);
|
return new HookController(this, hook);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -750,7 +750,7 @@ export interface ICombatFlowHandler<TEnemy, THero> extends IDataBaseExtended {
|
|||||||
readonly locator: ITileLocator;
|
readonly locator: ITileLocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICombatFlowHook<TEnemy, THero> extends IHookBase {
|
export interface ICombatFlowHooks<TEnemy, THero> extends IHookBase {
|
||||||
/**
|
/**
|
||||||
* 战前脚本
|
* 战前脚本
|
||||||
* @param info 战斗伤害信息
|
* @param info 战斗伤害信息
|
||||||
@ -799,7 +799,7 @@ export interface ICombatResult<TEnemy, THero> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ICombatFlow<TEnemy, THero>
|
export interface ICombatFlow<TEnemy, THero>
|
||||||
extends IHookable<ICombatFlowHook<TEnemy, THero>>, IDataBaseExtended {
|
extends IHookable<ICombatFlowHooks<TEnemy, THero>>, IDataBaseExtended {
|
||||||
/** 勇士属性对象 */
|
/** 勇士属性对象 */
|
||||||
readonly hero: IReadonlyHeroAttribute<THero> | null;
|
readonly hero: IReadonlyHeroAttribute<THero> | null;
|
||||||
/** 怪物上下文对象 */
|
/** 怪物上下文对象 */
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { ITileLocator } from '@motajs/common';
|
import { ITileLocator } from '@motajs/common';
|
||||||
import {
|
import {
|
||||||
ILayerState,
|
IGameMap,
|
||||||
IMapLayer,
|
IMapLayer,
|
||||||
IStateBase,
|
IStateBase,
|
||||||
IDataBaseExtended
|
IDataBaseExtended
|
||||||
@ -21,7 +21,7 @@ export interface ITriggerHandler {
|
|||||||
/** 当前全局状态对象 */
|
/** 当前全局状态对象 */
|
||||||
readonly state: IStateBase;
|
readonly state: IStateBase;
|
||||||
/** 当前楼层状态对象 */
|
/** 当前楼层状态对象 */
|
||||||
readonly layer?: ILayerState;
|
readonly layer?: IGameMap;
|
||||||
/** 当前参与触发的图层对象 */
|
/** 当前参与触发的图层对象 */
|
||||||
readonly mapLayer?: IMapLayer;
|
readonly mapLayer?: IMapLayer;
|
||||||
/** 当前触发点定位符 */
|
/** 当前触发点定位符 */
|
||||||
|
|||||||
@ -191,10 +191,10 @@
|
|||||||
"125": "Expected sorted floor id array has a same floor id set, but an array with a different floor id set is returned.",
|
"125": "Expected sorted floor id array has a same floor id set, but an array with a different floor id set is returned.",
|
||||||
"126": "Only move code '$1' is expected, but got '$2'.",
|
"126": "Only move code '$1' is expected, but got '$2'.",
|
||||||
"127": "Block at $1,$2 is 0, the dynamic tile will have num 0.",
|
"127": "Block at $1,$2 is 0, the dynamic tile will have num 0.",
|
||||||
"128": "Position $1,$2 is out of map bounds, operation cancelled.",
|
"128": "Cannot transfer $1 to $2, since target position $3,$4 out of bounds.",
|
||||||
"129": "Position $1,$2 is not empty, existing block will be overwritten.",
|
"129": "Position $1,$2 is not empty, transfering dynamic block to static will overwrite existing static tile.",
|
||||||
"130": "The given tile is not managed by this dynamic layer.",
|
"130": "The given tile is not managed by this dynamic layer.",
|
||||||
"131": "Event layer to set is not belong to current LayerState.",
|
"131": "Cannot set event layer since target map layer does not belongs to current GameMap instance.",
|
||||||
"132": "Trigger registry entry of $1 '$2' already exists, new factory will override old factory.",
|
"132": "Trigger registry entry of $1 '$2' already exists, new factory will override old factory.",
|
||||||
"133": "TileStore.addTile: tile number $1 already exists (id: '$2'), old tile data will be overridden.",
|
"133": "TileStore.addTile: tile number $1 already exists (id: '$2'), old tile data will be overridden.",
|
||||||
"134": "TileStore.addTile: tile id '$1' already maps to number $2, old tile data will be overridden.",
|
"134": "TileStore.addTile: tile id '$1' already maps to number $2, old tile data will be overridden.",
|
||||||
@ -206,7 +206,7 @@
|
|||||||
"140": "Different priority of combat script is expected, but got a same one.",
|
"140": "Different priority of combat script is expected, but got a same one.",
|
||||||
"141": "Some issue may occur in binded damage system on combat flow, please check the console.",
|
"141": "Some issue may occur in binded damage system on combat flow, please check the console.",
|
||||||
"142": "Expected a specific tile number after convert id '$1' to number, but got null.",
|
"142": "Expected a specific tile number after convert id '$1' to number, but got null.",
|
||||||
"143": "Cannot create dynamic tile for $1 since its raw data is not found.",
|
"143": "Cannot create dynamic tile for $1 since its raw data does not found.",
|
||||||
"144": "A hero move top implementation object binding is required for hero moving.",
|
"144": "A hero move top implementation object binding is required for hero moving.",
|
||||||
"145": "No tile registered for item '$1'.",
|
"145": "No tile registered for item '$1'.",
|
||||||
"146": "No equipment with uid of $1 in hero's inventory.",
|
"146": "No equipment with uid of $1 in hero's inventory.",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user