import { IDataCommon, IMapRawData, SaveCompression, shouldReplay } from '@user/data-common'; import { ITileStore } from '@user/data-common'; import { IGameMap, IGameMapSave, IMapState, IMapStoreSave, MapArea } from './types'; import { GameMap } from './gameMap'; import { logger } from '@motajs/common'; export class MapState implements IMapState { /** 楼层 id 到状态对象的映射 */ private readonly mapData: Map = new Map(); /** 所有楼层 id 的有序数组 */ readonly maps: string[] = []; /** 是否已经调用过 compareWith 设置参考基准 */ private compared: boolean = false; /** 分区列表 */ private areaList: Set = new Set(); /** 上一次调用 notifyEnterFloor 传入的楼层 id */ private lastFloorId: string | null = null; /** 自动分区激活器开关 */ private autoActivitorEnabled: boolean = false; constructor( private readonly tileStore: ITileStore, public readonly state: IDataCommon ) {} //#region 楼层管理 fromRaw(raw: IMapRawData): IGameMap | null { let length = 0; const entries = Object.entries(raw.map); for (const [_, map] of Object.entries(raw.map)) { if (length > 0 && map.length !== length) { logger.error( 60, map.length.toString(), length.toString(), raw.floorId ); return null; } else { length = map.length; } } if (length % raw.width !== 0) { logger.error( 61, length.toString(), raw.width.toString(), raw.floorId ); return null; } // 设置楼层图块数据 const height = length / raw.width; const state = this.createMap(raw.floorId, raw.width, height); for (const [zIndex, map] of entries) { const z = Number(zIndex); const layer = state.addLayer(); const alias = raw.layerAlias[z]; layer.setMapRef(new Uint32Array(map)); layer.setZIndex(z); state.setLayerAlias(layer, alias); // 设置坐标点事件 const events = raw.events[z]; for (const [index, tileEvents] of Object.entries(events)) { const indexNum = Number(index); const x = indexNum % raw.width; const y = Math.floor(indexNum / raw.width); const eventView = layer.event(x, y)!; for (const [priority, id] of Object.entries(tileEvents)) { const priorityNum = Number(priority); eventView.set(priorityNum, id); } eventView.markPure(); } if (alias === 'event') { state.setEventLayer(layer); } } return state; } @shouldReplay('Creating map should be replayed.') createMap(id: string, width: number, height: number): IGameMap { if (this.mapData.has(id)) { logger.warn(121, id); } else { this.maps.push(id); } const tile = this.tileStore; const state = new GameMap(this.state, tile, id, width, height); // 若已设置参考基准,新楼层直接视为全脏 if (this.compared) { state.markDirty(true); } this.mapData.set(id, state); return state; } @shouldReplay('Setting map list should be replayed.') setMapList(maps: string[]): void { this.maps.length = 0; this.maps.push(...maps); } getMap(id: string): IGameMap | null { return this.mapData.get(id) ?? null; } getActiveMap(id: string): IGameMap | null { const state = this.mapData.get(id); if (!state || !state.active) return null; return state; } //#endregion //#region 分区管理 setArea(areas: Set): void { this.areaList = areas; } @shouldReplay('Activating area should be replayed.') activeArea(id: string): void { const idx = this.maps.indexOf(id); if (idx === -1) return; const area = this.findAreaByIndex(idx); if (!area) return; this.setAreaActive(area, true); } @shouldReplay('Deactivating area should be replayed.') deactiveArea(id: string): void { const idx = this.maps.indexOf(id); if (idx === -1) return; const area = this.findAreaByIndex(idx); if (!area) return; this.setAreaActive(area, false); } useAutoActivitor(enable: boolean): void { this.autoActivitorEnabled = enable; } notifyEnterFloor(id: string): void { if (!this.autoActivitorEnabled) return; const idx = this.maps.indexOf(id); if (idx === -1) return; const area = this.findAreaByIndex(idx); if (!area) return; if (this.lastFloorId !== null) { this.deactiveArea(this.lastFloorId); } this.activeArea(id); this.lastFloorId = id; } /** * 根据 maps 下标查找其所属的分区 * @param idx 楼层在 maps 中的下标 */ private findAreaByIndex(idx: number): MapArea | null { for (const area of this.areaList) { for (const interval of area) { if (idx >= interval.start && idx <= interval.end) { return area; } } } 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++) { const floorId = this.maps[i]; if (floorId !== undefined) { this.setMapActiveStatus(floorId, active); } } } } //#endregion //#region active 管理 isMapActive(id: string): boolean { return this.mapData.get(id)?.active ?? false; } @shouldReplay('Setting map active status should be replayed.') setMapActiveStatus(id: string, active: boolean): void { this.mapData.get(id)?.setActiveStatus(active); } *iterateActiveMaps(): Iterable<[string, IGameMap]> { for (const [id, state] of this.mapData) { if (state.active) yield [id, state]; } } *iterateInactiveMaps(): Iterable<[string, IGameMap]> { for (const [id, state] of this.mapData) { if (!state.active) yield [id, state]; } } iterateAllMaps(): Iterable<[string, IGameMap]> { return this.mapData; } //#endregion //#region 存档对比 compareWith(ref: Map>): void { if (this.compared) return; this.compared = true; for (const [id, state] of this.mapData) { const refFloor = ref.get(id); if (!refFloor) { state.markDirty(true); continue; } state.compareWith(refFloor); } } //#region 存读档 saveState(compression: SaveCompression): IMapStoreSave { const floors = new Map(); for (const [id, state] of this.mapData) { if (!state.active) continue; floors.set(id, state.saveState(compression)); } return { floors }; } loadState(state: IMapStoreSave, compression: SaveCompression): void { if (compression !== SaveCompression.NoCompression && !this.compared) { 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); if (!cur) { logger.warn(122, id); continue; } cur.loadState(save, compression); } } //#endregion }