import { Hookable, HookController, IHookController, logger } from '@motajs/common'; import { IGameMap, IGameMapHooks, IGameMapSave, IMapLayer, IMapLayerHookController, IMapLayerHooks, IMapLayerSave, IResizableMapLayer } from './types'; import { IDataCommon, ILocationIndexer, ITileStore, MapLocIndexer, SaveCompression } from '@user/data-common'; import { MapLayer } from './mapLayer'; export class GameMap extends Hookable implements IGameMap { readonly layerList: Set = new Set(); /** 图层到图层别名映射 */ readonly layerAliasMap: WeakMap = new WeakMap(); /** 图层别名到图层的映射 */ readonly aliasLayerMap: Map = new Map(); /** 背景图块 */ private backgroundTile: number = 0; /** 图层钩子映射 */ private layerHookMap: Map = new Map(); /** 坐标索引器 */ readonly indexer: ILocationIndexer = new MapLocIndexer(); active: boolean = false; eventLayer: IMapLayer | null = null; /** 楼层自身脏标记 */ private selfDirty: boolean = false; constructor( public readonly state: IDataCommon, public readonly tileStore: ITileStore, public width: number, public height: number ) { super(); this.indexer.setWidth(width); } addLayer(): IMapLayer { const array = new Uint32Array(this.width * this.height); const layer = new MapLayer(array, this.width, this.height, this); this.layerList.add(layer); this.forEachHook(hook => { hook.onUpdateLayer?.(this.layerList); }); const controller = layer.addHook(new StateMapLayerHook(this, layer)); this.layerHookMap.set(layer, controller); controller.load(); return layer; } removeLayer(layer: IMapLayer): void { this.layerList.delete(layer as IResizableMapLayer); const alias = this.layerAliasMap.get(layer); if (alias) { const symbol = Symbol.for(alias); this.aliasLayerMap.delete(symbol); this.layerAliasMap.delete(layer); } this.forEachHook(hook => { hook.onUpdateLayer?.(this.layerList); }); const controller = this.layerHookMap.get(layer); if (!controller) return; controller.unload(); this.layerHookMap.delete(layer); } hasLayer(layer: IMapLayer): boolean { return this.layerList.has(layer as IResizableMapLayer); } setLayerAlias(layer: IMapLayer, alias: string): void { const symbol = Symbol.for(alias); if (this.aliasLayerMap.has(symbol)) { logger.warn(84, alias); return; } this.layerAliasMap.set(layer, alias); this.aliasLayerMap.set(symbol, layer); } getLayerByAlias(alias: string): IMapLayer | null { const symbol = Symbol.for(alias); return this.aliasLayerMap.get(symbol) ?? null; } getLayerAlias(layer: IMapLayer): string | undefined { return this.layerAliasMap.get(layer); } resizeLayer( width: number, height: number, keepBlock: boolean = false ): void { this.width = width; this.height = height; this.indexer.setWidth(width); for (const layer of this.layerList) { if (keepBlock) { layer.resize(width, height); } else { layer.resize2(width, height); } } } setBackground(tile: number): void { this.backgroundTile = tile; this.forEachHook(hook => { hook.onChangeBackground?.(tile); }); } getBackground(): number { return this.backgroundTile; } setActiveStatus(active: boolean): void { this.active = active; } setEventLayer(layer: IMapLayer | null): void { if (!layer) { this.eventLayer = null; } else { if (!this.layerList.has(layer as IResizableMapLayer)) { logger.warn(131); return; } this.eventLayer = layer; } } dirty(): boolean { if (this.selfDirty) return true; for (const layer of this.layerList) { if (layer.dirty()) return true; } return false; } markDirty(dirty: boolean): void { this.selfDirty = dirty; } compareWith(data: Map): 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; if (save.pointEvents && save.pointEvents.size > 0) return false; return true; } saveState(compression: SaveCompression): IGameMapSave { const layers = new Map(); 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( hook: Partial ): IHookController { return new HookController(this, hook); } } class StateMapLayerHook implements Partial { constructor( readonly state: GameMap, readonly layer: IMapLayer ) {} onResize(width: number, height: number): void { this.state.forEachHook(hook => { hook.onResizeLayer?.(this.layer, width, height); }); } }