feat: 静态触发器存储

This commit is contained in:
AncTe committed 2026-05-17 20:49:08 +08:00
1 parent 13fc4e1b7c
commit bf781c5ee8
9 files changed
+741 -174

No files matched your search

+48 -10
View File
@@ -36,22 +36,42 @@ export class DynamicLayer
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): IDynamicTile {
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);
return this.createDynamic(num, x, y);
this.layer.revertTrigger(x, y);
const tile = this.createDynamic(num, x, y);
tile.setTriggerType(triggerType);
return tile;
}
transferToStatic(tile: IDynamicTile): void {
/**
* 将动态图块上的触发器同步回当前静态格点
*/
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) {
@@ -62,16 +82,26 @@ export class DynamicLayer
logger.warn(129, x.toString(), y.toString());
}
this.layer.setBlock(tile.num, x, y);
this.syncStaticTrigger(tile, keepTrigger);
this.removeTile(tile);
this.forEachHook(hook => {
void hook.onDeleteTile?.(tile, this);
});
this.forEachHook(hook => hook.onDeleteTile?.(tile, this));
}
transferToStaticIfSafe(tile: IDynamicTile): boolean {
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, tile.x, tile.y);
this.deleteDynamic(tile);
this.layer.setBlock(tile.num, x, y);
this.syncStaticTrigger(tile, keepTrigger);
this.removeTile(tile);
this.forEachHook(hook => hook.onDeleteTile?.(tile, this));
return true;
}
@@ -117,12 +147,18 @@ export class DynamicLayer
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,
@@ -131,7 +167,9 @@ export class DynamicLayer
this.tilePosMap.get(y)?.get(x)?.delete(tile);
}
/** 从两个内部映射中移除图块记录 */
/**
* 从两个内部映射中移除图块记录
*/
private removeTile(tile: IDynamicTile): void {
const pos = this.posTileMap.get(tile);
if (pos) {
@@ -10,6 +10,7 @@ import { DynamicTileMover } from './mover';
export class DynamicTile implements IDynamicTile {
readonly mover: IObjectMover<IDynamicTile>;
triggerType: number;
/** 当前的朝向绑定对象 */
private face: IRoleFaceBinder | null = null;
@@ -21,6 +22,7 @@ export class DynamicTile implements IDynamicTile {
public readonly layer: IDynamicLayer
) {
this.mover = new DynamicTileMover(this);
this.triggerType = -1;
}
setFaceBinder(binder: IRoleFaceBinder | null): void {
@@ -36,6 +38,10 @@ export class DynamicTile implements IDynamicTile {
return this.num;
}
setTriggerType(type: number): void {
this.triggerType = type;
}
delete(): Promise<void> {
return this.layer.deleteDynamic(this);
}
@@ -11,6 +11,7 @@ import {
IMapLayerHookController,
IMapLayerHooks
} from './types';
import { ITileStore } from '../store';
import { MapLayer } from './mapLayer';
export class LayerState
@@ -38,6 +39,7 @@ export class LayerState
private dirty: boolean = false;
constructor(
public readonly tileStore: ITileStore,
public width: number,
public height: number
) {
@@ -46,7 +48,12 @@ export class LayerState
addLayer(): IMapLayer {
const array = new Uint32Array(this.width * this.height);
const layer = new MapLayer(array, this.width, this.height);
const layer = new MapLayer(
array,
this.width,
this.height,
this.tileStore
);
this.layerList.add(layer);
this.mapLayerList.add(layer);
this.forEachHook(hook => {
+77 -8
View File
@@ -8,6 +8,7 @@ import {
} from './types';
import { Hookable, HookController, logger } from '@motajs/common';
import { DynamicLayer } from './dynamicLayer';
import { ITileStore } from '../store';
// todo: 提供 core.setBlock 等方法的替代方法,同时添加 setBlockList,以及前景背景的接口
@@ -24,10 +25,17 @@ export class MapLayer
private mapArray: Uint32Array;
/** 地图数据引用 */
private mapData: IMapLayerData;
/** 手动触发器覆盖映射,key = y * width + x */
private triggerMap: Map<number, number> = new Map();
readonly dynamicLayer: IDynamicLayer;
constructor(array: Uint32Array, width: number, height: number) {
constructor(
array: Uint32Array,
width: number,
height: number,
private readonly tileStore: ITileStore
) {
super();
this.width = width;
this.height = height;
@@ -42,6 +50,25 @@ export class MapLayer
this.dynamicLayer = new DynamicLayer(this);
}
/**
* 在地图尺寸变化后重新映射手动触发器覆盖表
*/
private remapTriggerMap(
beforeWidth: number,
width: number,
height: number
): 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;
@@ -55,6 +82,7 @@ export class MapLayer
this.height = height;
const area = width * height;
const newArray = new Uint32Array(area);
this.triggerMap = this.remapTriggerMap(beforeWidth, width, height);
this.mapArray = newArray;
// 将原来的地图数组赋值给现在的
if (beforeArea > area) {
@@ -84,13 +112,16 @@ export class MapLayer
resize2(width: number, height: number): void {
if (this.width === width && this.height === height) {
this.empty = true;
this.mapArray.fill(0);
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
@@ -121,6 +152,39 @@ export class MapLayer
return this.mapArray[y * this.width + x];
}
getTriggerType(x: number, y: number): number {
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
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 (x < 0 || y < 0 || x >= this.width || y >= this.height) {
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 (x >= 0 && y >= 0 && x < this.width && y < this.height) {
this.triggerMap.delete(y * this.width + x);
}
}
clearTrigger(): void {
this.triggerMap.clear();
}
putMapData(array: Uint32Array, x: number, y: number, width: number): void {
if (array.length % width !== 0) {
logger.warn(8);
@@ -201,13 +265,6 @@ export class MapLayer
return res;
}
/**
* 获取地图数据的内部存储直接引用
*/
getMapRef(): IMapLayerData {
return this.mapData;
}
setMapRef(array: Uint32Array): void {
if (array.length !== this.width * this.height) {
logger.warn(
@@ -229,6 +286,18 @@ export class MapLayer
});
}
getMapRef(): IMapLayerData {
return this.mapData;
}
setTriggerRef(triggers: Map<number, number>): void {
this.triggerMap = triggers;
}
getTriggerRef(): ReadonlyMap<number, number> {
return this.triggerMap;
}
protected createController(
hook: Partial<IMapLayerHooks>
): IMapLayerHookController {
+239 -136
View File
@@ -1,5 +1,6 @@
import { logger } from '@motajs/common';
import { SaveCompression } from '../common';
import { ITileStore } from '../store';
import {
ILayerState,
ILayerStateSave,
@@ -31,6 +32,8 @@ export class MapStore implements IMapStore {
/** 自动分区激活器开关 */
private autoActivitorEnabled: boolean = false;
constructor(private readonly tileStore: ITileStore) {}
//#region 楼层管理
createLayerState(id: string, width: number, height: number): ILayerState {
@@ -39,7 +42,7 @@ export class MapStore implements IMapStore {
} else {
this.maps.push(id);
}
const state = new LayerState(width, height);
const state = new LayerState(this.tileStore, width, height);
// 若 refData 已存在,新楼层直接视为全脏
if (this.refData !== null) {
state.setDirty(true);
@@ -117,6 +120,10 @@ export class MapStore implements IMapStore {
this.lastFloorId = id;
}
/**
* 根据 maps 下标查找其所属的分区
* @param idx 楼层在 maps 中的下标
*/
private findAreaByIndex(idx: number): MapArea | null {
for (const area of this.areaList) {
for (const interval of area) {
@@ -128,6 +135,11 @@ export class MapStore implements IMapStore {
return null;
}
/**
* 批量设置一个分区内所有楼层的激活状态
* @param area 目标分区
* @param active 要设置的激活状态
*/
private setAreaActive(area: MapArea, active: boolean): void {
for (const interval of area) {
for (let i = interval.start; i <= interval.end; i++) {
@@ -169,7 +181,7 @@ export class MapStore implements IMapStore {
//#endregion
//#region 存档及压缩
//#region 存档工具
compareWith(ref: Map<string, Map<number, Uint32Array>>): void {
if (this.refData !== null) return;
@@ -202,6 +214,116 @@ export class MapStore implements IMapStore {
}
}
/**
* 提取楼层内所有图层的静态触发器覆盖映射
* @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) {
@@ -211,77 +333,119 @@ export class MapStore implements IMapStore {
return { floors };
}
/**
* 以低压缩方式序列化所有激活楼层
*/
private saveLowCompression(): IMapStoreSave {
const floors = new Map<string, ILayerStateSave>();
for (const [id, state] of this.mapData) {
if (!state.active) continue;
// 非 dirty 或 dirty 但与参考基准完全一致 → 空 layers(读档时从参考基准恢复)
if (
!state.isDirty() ||
(this.refData && this.isStateEqualToRef(id, state))
) {
floors.set(id, {
background: state.getBackground(),
layers: new Map()
});
} else {
floors.set(id, this.saveLayerStateFull(state));
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));
}
}
}
return { floors };
}
private saveHighCompression(): IMapStoreSave {
const floors = new Map<string, ILayerStateSave>();
for (const [id, state] of this.mapData) {
if (!state.active) continue;
if (!state.isDirty()) {
floors.set(id, {
background: state.getBackground(),
layers: new Map()
});
continue;
} 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));
}
}
const refFloor = this.refData?.get(id);
const layersMap = new Map<number, IMapLayerSave>();
for (const layer of state.layerList) {
const refArray = refFloor?.get(layer.zIndex);
const rows = this.diffRows(layer, refArray);
if (rows.size === 0 && refArray) continue; // 与参考完全一致
layersMap.set(layer.zIndex, {
width: layer.width,
height: layer.height,
rows
});
}
floors.set(id, {
background: state.getBackground(),
layers: layersMap
});
}
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, layerStateSave] of state.floors) {
for (const [id, save] of state.floors) {
const cur = this.mapData.get(id);
if (!cur) {
logger.warn(122, id);
continue;
}
cur.setBackground(layerStateSave.background);
cur.setBackground(save.background);
for (const layer of cur.layerList) {
const layerSave = layerStateSave.layers.get(layer.zIndex);
if (!layerSave?.fullMap) continue;
layer.setMapRef(new Uint32Array(layerSave.fullMap));
// 地图
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);
}
cur.setDirty(false);
}
}
@@ -289,6 +453,7 @@ export class MapStore implements IMapStore {
* LowCompression 读档:
* - layers 有数据(dirty 楼层)→ fullMap 直接转移所有权
* - layers 为空(非 dirty 楼层)→ 从参考基准恢复
* @param state 整体地图存档数据
*/
private loadLowCompression(state: IMapStoreSave): void {
if (!this.refData) {
@@ -298,7 +463,7 @@ export class MapStore implements IMapStore {
for (const [id, cur] of this.mapData) {
cur.setActiveStatus(state.floors.has(id));
}
for (const [id, layerStateSave] of state.floors) {
for (const [id, save] of state.floors) {
const cur = this.mapData.get(id);
const refFloor = this.refData.get(id);
if (!cur) {
@@ -309,21 +474,26 @@ export class MapStore implements IMapStore {
logger.warn(124, id);
continue;
}
cur.setBackground(layerStateSave.background);
cur.setBackground(save.background);
let shouldDirty = false;
for (const layer of cur.layerList) {
const layerSave = layerStateSave.layers.get(layer.zIndex);
// 地图
const layerSave = save.layers.get(layer.zIndex);
if (layerSave?.fullMap) {
layer.setMapRef(layerSave.fullMap);
layer.setMapRef(new Uint32Array(layerSave.fullMap));
shouldDirty = true;
} else {
const refArray = refFloor?.get(layer.zIndex);
const refArray = refFloor.get(layer.zIndex);
if (!refArray) {
logger.warn(124, id);
return;
}
layer.setMapRef(new Uint32Array(refArray));
}
// 触发器
this.loadTriggers(save, layer);
}
cur.setDirty(false);
cur.setDirty(shouldDirty);
}
}
@@ -331,6 +501,7 @@ export class MapStore implements IMapStore {
* HighCompression 读档:
* - layers 有数据(dirty 楼层)→ 以参考基准为底,叠加差分行
* - layers 为空(非 dirty 楼层)或图层无变化(rows 缺失)→ 从参考基准恢复
* @param state 整体地图存档数据
*/
private loadHighCompression(state: IMapStoreSave): void {
if (!this.refData) {
@@ -340,7 +511,7 @@ export class MapStore implements IMapStore {
for (const [id, cur] of this.mapData) {
cur.setActiveStatus(state.floors.has(id));
}
for (const [id, layerStateSave] of state.floors) {
for (const [id, save] of state.floors) {
const cur = this.mapData.get(id);
const refFloor = this.refData.get(id);
if (!cur) {
@@ -351,34 +522,32 @@ export class MapStore implements IMapStore {
logger.warn(124, id);
continue;
}
cur.setBackground(layerStateSave.background);
let isMapDirty = true;
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 = layerStateSave.layers.get(layer.zIndex);
// 地图
const layerSave = save.layers.get(layer.zIndex);
if (!layerSave?.rows || layerSave.rows.size === 0) {
// 图层无变化或非 dirty 楼层,从参考基准恢复
layer.setMapRef(new Uint32Array(refArray));
} else {
// 以参考基准为底,叠加差分行
isMapDirty = false;
const size = layer.width * layer.height;
const buf = new Uint32Array(size);
if (refArray) buf.set(refArray.subarray(0, size));
shouldDirty = true;
const buf = new Uint32Array(refArray);
for (const [rowIdx, rowData] of layerSave.rows) {
buf.set(
rowData.subarray(0, layer.width),
rowIdx * layer.width
);
buf.set(rowData, rowIdx * layer.width);
}
layer.setMapRef(buf);
}
// 触发器
this.loadTriggers(save, layer);
}
cur.setDirty(isMapDirty);
cur.setDirty(shouldDirty);
}
}
@@ -402,71 +571,5 @@ export class MapStore implements IMapStore {
}
}
//#region 内部方法
/**
* 将楼层所有图层全量序列化(NoCompression / LowCompression 用)
*/
private saveLayerStateFull(state: LayerState): ILayerStateSave {
const layersMap = new Map<number, IMapLayerSave>();
for (const layer of state.layerList) {
const arr = layer.getMapRef().array;
layersMap.set(layer.zIndex, {
width: layer.width,
height: layer.height,
fullMap: new Uint32Array(arr)
});
}
return { background: state.getBackground(), layers: layersMap };
}
/**
* 仅返回与参考基准不同的行(HighCompression 用)
*/
private diffRows(
layer: IMapLayer,
refArray?: Uint32Array
): Map<number, Uint32Array> {
const rows = new Map<number, Uint32Array>();
const arr = layer.getMapRef().array;
if (refArray) {
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));
}
}
} else {
for (let row = 0; row < layer.height; row++) {
const start = row * layer.width;
const end = start + layer.width;
rows.set(row, new Uint32Array(arr.subarray(start, end)));
}
}
return rows;
}
/**
* 判断楼层所有图层是否与参考基准完全一致(LowCompression 去误判用)
*/
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;
for (let i = 0; i < cur.length; i++) {
if (cur[i] !== refArray[i]) return false;
}
}
return true;
}
//#endregion
}
+70 -18
View File
@@ -7,6 +7,7 @@ import {
IRoleFaceBinder,
ISaveableContent
} from '../common';
import { ITileStore } from '../store';
//#region 静态图层
@@ -103,6 +104,33 @@ export interface IMapLayer extends IHookable<
*/
getBlock(x: number, y: number): number;
/**
* 获取指定点的静态图块对应的有效触发器类型,若手动覆盖不存在则回退到图块默认触发器
* @param x 图块横坐标
* @param y 图块纵坐标
*/
getTriggerType(x: number, y: number): number;
/**
* 设置指定点静态图块的触发器
* @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 地图图块数组
@@ -130,11 +158,31 @@ export interface IMapLayer extends IHookable<
height: number
): Uint32Array;
/**
* 直接替换内部图块数组引用,跳过拷贝,高性能但风险较高。
* 一般仅供 `MapStore` 读档时内部使用,外部正常情况下不应调用。
* 调用方需确保传入数组的长度与 `width * height` 匹配,
* 且调用后不得再持有或修改传入的数组。
* @param array 地图数组,会直接替换内部引用
*/
setMapRef(array: Uint32Array): void;
/**
* 获取整个地图的地图数组,是对内部数组的直接引用
*/
getMapRef(): IMapLayerData;
/**
* 直接设置内部触发器映射对象,一般仅供内部存读档使用,外部正常情况下不应调用
* @param triggers 触发器映射
*/
setTriggerRef(triggers: Map<number, number>): void;
/**
* 获取静态触发器覆盖映射,一般仅供内部存档逻辑使用
*/
getTriggerRef(): ReadonlyMap<number, number>;
/**
* 设置地图纵深,会影响渲染的遮挡顺序
* @param zIndex 纵深
@@ -155,15 +203,6 @@ export interface IMapLayer extends IHookable<
* @param y 门纵坐标
*/
closeDoor(num: number, x: number, y: number): Promise<void>;
/**
* 直接替换内部图块数组引用,跳过拷贝,高性能但风险较高。
* 一般仅供 `MapStore` 读档时内部使用,外部正常情况下不应调用。
* 调用方需确保传入数组的长度与 `width * height` 匹配,
* 且调用后不得再持有或修改传入的数组。
* @param array 地图数组,会直接替换内部引用
*/
setMapRef(array: Uint32Array): void;
}
//#endregion
@@ -225,6 +264,8 @@ export interface ILayerStateHooks extends IHookBase {
export interface ILayerState extends IHookable<ILayerStateHooks> {
/** 地图列表 */
readonly layerList: Set<IMapLayer>;
/** 当前楼层共享的图块定义 store */
readonly tileStore: ITileStore;
/** 此楼层是否处于激活状态 */
readonly active: boolean;
/** 此楼层的地图宽度 */
@@ -316,7 +357,6 @@ export interface ILayerState extends IHookable<ILayerStateHooks> {
//#region 楼层管理
/** 单个 MapLayer 的存档数据 */
export interface IMapLayerSave {
readonly width: number;
readonly height: number;
@@ -332,27 +372,27 @@ export interface IMapLayerSave {
readonly fullMap?: Uint32Array;
}
/** 单个楼层的存档数据 */
export interface ILayerStateSave {
/** 楼层背景 */
readonly background: number;
/** key = zIndex,value = 对应图层存档数据 */
readonly layers: ReadonlyMap<number, IMapLayerSave>;
/** 静态触发器覆盖映射,仅在存在覆盖时写入 */
readonly triggers: ReadonlyMap<number, ReadonlyMap<number, number>>;
}
/** 整个 MapStore 的存档数据 */
export interface IMapStoreSave {
/** key = 楼层 id,只包含 active 的楼层,inactive 的楼层不写入,读档时无需处理 */
readonly floors: ReadonlyMap<string, ILayerStateSave>;
}
/** 单段闭区间 [start, end],start 和 end 均为 maps 下标 */
export interface IMapAreaInterval {
/** 区域起始索引,包含 */
readonly start: number;
/** 区域结束索引,包含 */
readonly end: number;
}
/** 一个区域由一个或多个独立区间组成 */
export type MapArea = IMapAreaInterval[];
export interface IMapStore extends ISaveableContent<IMapStoreSave> {
@@ -504,21 +544,25 @@ export interface IDynamicLayer extends IHookable<IDynamicLayerHooks> {
* @param y 纵坐标
* @returns 创建的动态图块引用
*/
transferToDynamic(x: number, y: number): IDynamicTile;
transferToDynamic(
x: number,
y: number,
keepTrigger?: boolean
): IDynamicTile;
/**
* 将动态图块还原为静态图块。坐标越界则警告并放弃,
* 否则写回静态图层并触发 {@link IDynamicLayerHooks.onDeleteTile}
* @param tile 要还原的动态图块
*/
transferToStatic(tile: IDynamicTile): void;
transferToStatic(tile: IDynamicTile, keepTrigger?: boolean): void;
/**
* 仅当目标位置静态图块为 0(空白)时才还原为静态图块,否则不转换
* @param tile 要还原的动态图块
* @returns 是否转换成功
*/
transferToStaticIfSafe(tile: IDynamicTile): boolean;
transferToStaticIfSafe(tile: IDynamicTile, keepTrigger?: boolean): boolean;
/**
* 删除指定动态图块,触发 {@link IDynamicLayerHooks.onDeleteTile} 钩子。
@@ -557,6 +601,8 @@ export interface IDynamicLayer extends IHookable<IDynamicLayerHooks> {
export interface IDynamicTile extends IObjectMovable {
/** 当前图块数字 */
readonly num: number;
/** 当前动态图块携带的触发器类型,-1 表示无触发器 */
readonly triggerType: number;
/** 当前图块所属的动态图层 */
readonly layer: IDynamicLayer;
/** 当前动态图块的移动器 */
@@ -568,6 +614,12 @@ export interface IDynamicTile extends IObjectMovable {
*/
setFaceDirection(direction: FaceDirection): number;
/**
* 设置当前动态图块的触发器类型
* @param type 触发器类型
*/
setTriggerType(type: number): void;
/**
* 直接删除此图块
*/