2021-07-05 17:25:23 +02:00
|
|
|
import type { ITiledMap, ITiledMapLayer, ITiledMapProperty } from "../Map/ITiledMap";
|
2021-05-12 14:30:12 +02:00
|
|
|
import { flattenGroupLayersMap } from "../Map/LayersFlattener";
|
2021-05-18 15:41:16 +02:00
|
|
|
import TilemapLayer = Phaser.Tilemaps.TilemapLayer;
|
2021-05-28 12:13:10 +02:00
|
|
|
import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes";
|
2021-11-02 10:51:02 +01:00
|
|
|
import { GameMapProperties } from "./GameMapProperties";
|
2020-08-30 15:44:22 +02:00
|
|
|
|
2021-06-25 18:14:40 +02:00
|
|
|
export type PropertyChangeCallback = (
|
|
|
|
newValue: string | number | boolean | undefined,
|
|
|
|
oldValue: string | number | boolean | undefined,
|
|
|
|
allProps: Map<string, string | boolean | number>
|
|
|
|
) => void;
|
2020-08-30 15:44:22 +02:00
|
|
|
|
2021-10-29 16:44:51 +02:00
|
|
|
export type layerChangeCallback = (
|
|
|
|
layersChangedByAction: Array<ITiledMapLayer>,
|
2021-11-24 15:29:12 +01:00
|
|
|
allLayersOnNewPosition: Array<ITiledMapLayer>
|
2021-10-29 16:44:51 +02:00
|
|
|
) => void;
|
|
|
|
|
2020-08-30 15:44:22 +02:00
|
|
|
/**
|
|
|
|
* A wrapper around a ITiledMap interface to provide additional capabilities.
|
|
|
|
* It is used to handle layer properties.
|
|
|
|
*/
|
|
|
|
export class GameMap {
|
2021-10-29 16:44:51 +02:00
|
|
|
// oldKey is the index of the previous tile.
|
|
|
|
private oldKey: number | undefined;
|
|
|
|
// key is the index of the current tile.
|
2021-06-19 15:24:27 +02:00
|
|
|
private key: number | undefined;
|
|
|
|
private lastProperties = new Map<string, string | boolean | number>();
|
2021-10-29 16:44:51 +02:00
|
|
|
private propertiesChangeCallbacks = new Map<string, Array<PropertyChangeCallback>>();
|
|
|
|
private enterLayerCallbacks = Array<layerChangeCallback>();
|
|
|
|
private leaveLayerCallbacks = Array<layerChangeCallback>();
|
2021-06-28 09:33:13 +02:00
|
|
|
private tileNameMap = new Map<string, number>();
|
2021-06-19 15:17:28 +02:00
|
|
|
|
2021-07-05 17:25:23 +02:00
|
|
|
private tileSetPropertyMap: { [tile_index: number]: Array<ITiledMapProperty> } = {};
|
2021-05-12 14:30:12 +02:00
|
|
|
public readonly flatLayers: ITiledMapLayer[];
|
2021-05-18 15:41:16 +02:00
|
|
|
public readonly phaserLayers: TilemapLayer[] = [];
|
2020-08-30 15:44:22 +02:00
|
|
|
|
2021-06-25 18:14:40 +02:00
|
|
|
public exitUrls: Array<string> = [];
|
2021-06-19 15:17:28 +02:00
|
|
|
|
2021-06-25 17:35:42 +02:00
|
|
|
public hasStartTile = false;
|
|
|
|
|
2021-06-25 18:14:40 +02:00
|
|
|
public constructor(
|
|
|
|
private map: ITiledMap,
|
|
|
|
phaserMap: Phaser.Tilemaps.Tilemap,
|
|
|
|
terrains: Array<Phaser.Tilemaps.Tileset>
|
|
|
|
) {
|
2021-05-12 14:30:12 +02:00
|
|
|
this.flatLayers = flattenGroupLayersMap(map);
|
|
|
|
let depth = -2;
|
|
|
|
for (const layer of this.flatLayers) {
|
2021-06-25 18:14:40 +02:00
|
|
|
if (layer.type === "tilelayer") {
|
2021-05-18 15:41:16 +02:00
|
|
|
this.phaserLayers.push(phaserMap.createLayer(layer.name, terrains, 0, 0).setDepth(depth));
|
2021-05-12 14:30:12 +02:00
|
|
|
}
|
2021-06-25 18:14:40 +02:00
|
|
|
if (layer.type === "objectgroup" && layer.name === "floorLayer") {
|
2021-05-26 10:58:25 +02:00
|
|
|
depth = DEPTH_OVERLAY_INDEX;
|
2021-05-12 14:30:12 +02:00
|
|
|
}
|
|
|
|
}
|
2021-06-19 15:17:28 +02:00
|
|
|
for (const tileset of map.tilesets) {
|
2021-06-25 18:14:40 +02:00
|
|
|
tileset?.tiles?.forEach((tile) => {
|
2021-06-19 15:17:28 +02:00
|
|
|
if (tile.properties) {
|
2021-06-25 18:14:40 +02:00
|
|
|
this.tileSetPropertyMap[tileset.firstgid + tile.id] = tile.properties;
|
|
|
|
tile.properties.forEach((prop) => {
|
2021-11-02 10:51:02 +01:00
|
|
|
if (prop.name == GameMapProperties.NAME && typeof prop.value == "string") {
|
2021-06-28 09:33:13 +02:00
|
|
|
this.tileNameMap.set(prop.value, tileset.firstgid + tile.id);
|
|
|
|
}
|
2021-11-02 10:51:02 +01:00
|
|
|
if (prop.name == GameMapProperties.EXIT_URL && typeof prop.value == "string") {
|
2021-06-19 15:17:28 +02:00
|
|
|
this.exitUrls.push(prop.value);
|
2021-11-02 10:51:02 +01:00
|
|
|
} else if (prop.name == GameMapProperties.START) {
|
2021-06-25 18:14:40 +02:00
|
|
|
this.hasStartTile = true;
|
2021-06-19 15:17:28 +02:00
|
|
|
}
|
2021-06-25 18:14:40 +02:00
|
|
|
});
|
2021-06-19 15:17:28 +02:00
|
|
|
}
|
2021-06-25 18:14:40 +02:00
|
|
|
});
|
2021-06-19 15:17:28 +02:00
|
|
|
}
|
2020-08-30 15:44:22 +02:00
|
|
|
}
|
|
|
|
|
2021-07-05 17:25:23 +02:00
|
|
|
public getPropertiesForIndex(index: number): Array<ITiledMapProperty> {
|
2021-06-23 12:32:36 +02:00
|
|
|
if (this.tileSetPropertyMap[index]) {
|
2021-06-25 18:14:40 +02:00
|
|
|
return this.tileSetPropertyMap[index];
|
2021-06-23 12:32:36 +02:00
|
|
|
}
|
2021-06-25 18:14:40 +02:00
|
|
|
return [];
|
2021-06-23 12:32:36 +02:00
|
|
|
}
|
|
|
|
|
2021-10-29 16:44:51 +02:00
|
|
|
private getLayersByKey(key: number): Array<ITiledMapLayer> {
|
2021-11-24 15:29:12 +01:00
|
|
|
return this.flatLayers.filter((flatLayer) => flatLayer.type === "tilelayer" && flatLayer.data[key] !== 0);
|
2021-10-29 16:44:51 +02:00
|
|
|
}
|
|
|
|
|
2020-08-30 15:44:22 +02:00
|
|
|
/**
|
|
|
|
* Sets the position of the current player (in pixels)
|
|
|
|
* This will trigger events if properties are changing.
|
|
|
|
*/
|
|
|
|
public setPosition(x: number, y: number) {
|
2021-10-29 16:44:51 +02:00
|
|
|
this.oldKey = this.key;
|
|
|
|
|
2020-08-30 15:44:22 +02:00
|
|
|
const xMap = Math.floor(x / this.map.tilewidth);
|
|
|
|
const yMap = Math.floor(y / this.map.tileheight);
|
|
|
|
const key = xMap + yMap * this.map.width;
|
2021-10-29 16:44:51 +02:00
|
|
|
|
2020-08-30 15:44:22 +02:00
|
|
|
if (key === this.key) {
|
|
|
|
return;
|
|
|
|
}
|
2021-10-29 16:44:51 +02:00
|
|
|
|
2020-08-30 15:44:22 +02:00
|
|
|
this.key = key;
|
2021-10-29 16:44:51 +02:00
|
|
|
|
|
|
|
this.triggerAllProperties();
|
|
|
|
this.triggerLayersChange();
|
2021-08-27 15:05:14 +02:00
|
|
|
}
|
2020-08-30 15:44:22 +02:00
|
|
|
|
2021-10-29 16:44:51 +02:00
|
|
|
private triggerAllProperties(): void {
|
2021-08-27 15:05:14 +02:00
|
|
|
const newProps = this.getProperties(this.key ?? 0);
|
2020-08-30 15:44:22 +02:00
|
|
|
const oldProps = this.lastProperties;
|
2021-02-18 11:32:37 +01:00
|
|
|
this.lastProperties = newProps;
|
2020-08-30 15:44:22 +02:00
|
|
|
|
|
|
|
// Let's compare the 2 maps:
|
|
|
|
// First new properties vs oldProperties
|
|
|
|
for (const [newPropName, newPropValue] of newProps.entries()) {
|
|
|
|
const oldPropValue = oldProps.get(newPropName);
|
|
|
|
if (oldPropValue !== newPropValue) {
|
2020-10-16 19:13:26 +02:00
|
|
|
this.trigger(newPropName, oldPropValue, newPropValue, newProps);
|
2020-08-30 15:44:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const [oldPropName, oldPropValue] of oldProps.entries()) {
|
|
|
|
if (!newProps.has(oldPropName)) {
|
|
|
|
// We found a property that disappeared
|
2020-10-16 19:13:26 +02:00
|
|
|
this.trigger(oldPropName, oldPropValue, undefined, newProps);
|
2020-08-30 15:44:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-29 16:44:51 +02:00
|
|
|
private triggerLayersChange() {
|
|
|
|
const layersByOldKey = this.oldKey ? this.getLayersByKey(this.oldKey) : [];
|
|
|
|
const layersByNewKey = this.key ? this.getLayersByKey(this.key) : [];
|
|
|
|
|
|
|
|
const enterLayers = new Set(layersByNewKey);
|
|
|
|
const leaveLayers = new Set(layersByOldKey);
|
|
|
|
|
2021-11-24 15:29:12 +01:00
|
|
|
enterLayers.forEach((layer) => {
|
2021-10-29 16:44:51 +02:00
|
|
|
if (leaveLayers.has(layer)) {
|
|
|
|
leaveLayers.delete(layer);
|
|
|
|
enterLayers.delete(layer);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (enterLayers.size > 0) {
|
|
|
|
const layerArray = Array.from(enterLayers);
|
|
|
|
for (const callback of this.enterLayerCallbacks) {
|
|
|
|
callback(layerArray, layersByNewKey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (leaveLayers.size > 0) {
|
|
|
|
const layerArray = Array.from(leaveLayers);
|
|
|
|
for (const callback of this.leaveLayerCallbacks) {
|
|
|
|
callback(layerArray, layersByNewKey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-19 15:24:27 +02:00
|
|
|
public getCurrentProperties(): Map<string, string | boolean | number> {
|
2021-02-10 11:08:57 +01:00
|
|
|
return this.lastProperties;
|
|
|
|
}
|
|
|
|
|
2021-06-19 15:24:27 +02:00
|
|
|
private getProperties(key: number): Map<string, string | boolean | number> {
|
|
|
|
const properties = new Map<string, string | boolean | number>();
|
2020-08-30 15:44:22 +02:00
|
|
|
|
2021-05-12 14:30:12 +02:00
|
|
|
for (const layer of this.flatLayers) {
|
2021-06-25 18:14:40 +02:00
|
|
|
if (layer.type !== "tilelayer") {
|
2020-08-30 15:44:22 +02:00
|
|
|
continue;
|
|
|
|
}
|
2021-06-19 15:46:32 +02:00
|
|
|
|
|
|
|
let tileIndex: number | undefined = undefined;
|
|
|
|
if (layer.data) {
|
|
|
|
const tiles = layer.data as number[];
|
|
|
|
if (tiles[key] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
2021-06-25 18:14:40 +02:00
|
|
|
tileIndex = tiles[key];
|
2020-08-30 15:44:22 +02:00
|
|
|
}
|
2021-06-19 15:46:32 +02:00
|
|
|
|
2020-08-30 15:44:22 +02:00
|
|
|
// There is a tile in this layer, let's embed the properties
|
|
|
|
if (layer.properties !== undefined) {
|
|
|
|
for (const layerProperty of layer.properties) {
|
|
|
|
if (layerProperty.value === undefined) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
properties.set(layerProperty.name, layerProperty.value);
|
|
|
|
}
|
|
|
|
}
|
2021-06-19 15:17:28 +02:00
|
|
|
|
|
|
|
if (tileIndex) {
|
2021-06-25 18:14:40 +02:00
|
|
|
this.tileSetPropertyMap[tileIndex]?.forEach((property) => {
|
2021-06-20 19:14:04 +02:00
|
|
|
if (property.value) {
|
2021-06-25 18:14:40 +02:00
|
|
|
properties.set(property.name, property.value);
|
2021-06-20 19:14:04 +02:00
|
|
|
} else if (properties.has(property.name)) {
|
2021-06-25 18:14:40 +02:00
|
|
|
properties.delete(property.name);
|
2021-06-20 19:14:04 +02:00
|
|
|
}
|
2021-06-25 18:14:40 +02:00
|
|
|
});
|
2021-06-19 15:17:28 +02:00
|
|
|
}
|
2020-08-30 15:44:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return properties;
|
|
|
|
}
|
|
|
|
|
2021-06-25 18:14:40 +02:00
|
|
|
public getMap(): ITiledMap {
|
2021-05-18 15:41:16 +02:00
|
|
|
return this.map;
|
|
|
|
}
|
|
|
|
|
2021-07-05 17:25:23 +02:00
|
|
|
private getTileProperty(index: number): Array<ITiledMapProperty> {
|
2021-07-07 14:42:17 +02:00
|
|
|
if (this.tileSetPropertyMap[index]) {
|
|
|
|
return this.tileSetPropertyMap[index];
|
|
|
|
}
|
|
|
|
return [];
|
2021-06-24 11:31:29 +02:00
|
|
|
}
|
|
|
|
|
2021-06-25 18:14:40 +02:00
|
|
|
private trigger(
|
|
|
|
propName: string,
|
|
|
|
oldValue: string | number | boolean | undefined,
|
|
|
|
newValue: string | number | boolean | undefined,
|
|
|
|
allProps: Map<string, string | boolean | number>
|
|
|
|
) {
|
2021-10-29 16:44:51 +02:00
|
|
|
const callbacksArray = this.propertiesChangeCallbacks.get(propName);
|
2020-08-30 15:44:22 +02:00
|
|
|
if (callbacksArray !== undefined) {
|
|
|
|
for (const callback of callbacksArray) {
|
2020-10-16 19:13:26 +02:00
|
|
|
callback(newValue, oldValue, allProps);
|
2020-08-30 15:44:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a callback called when the user moves to a tile where the property propName is different from the last tile the user was on.
|
|
|
|
*/
|
|
|
|
public onPropertyChange(propName: string, callback: PropertyChangeCallback) {
|
2021-10-29 16:44:51 +02:00
|
|
|
let callbacksArray = this.propertiesChangeCallbacks.get(propName);
|
2020-08-30 15:44:22 +02:00
|
|
|
if (callbacksArray === undefined) {
|
|
|
|
callbacksArray = new Array<PropertyChangeCallback>();
|
2021-10-29 16:44:51 +02:00
|
|
|
this.propertiesChangeCallbacks.set(propName, callbacksArray);
|
2020-08-30 15:44:22 +02:00
|
|
|
}
|
|
|
|
callbacksArray.push(callback);
|
|
|
|
}
|
2021-05-12 14:30:12 +02:00
|
|
|
|
2021-10-29 16:44:51 +02:00
|
|
|
/**
|
|
|
|
* Registers a callback called when the user moves inside another layer.
|
|
|
|
*/
|
|
|
|
public onEnterLayer(callback: layerChangeCallback) {
|
|
|
|
this.enterLayerCallbacks.push(callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a callback called when the user moves outside another layer.
|
|
|
|
*/
|
|
|
|
public onLeaveLayer(callback: layerChangeCallback) {
|
|
|
|
this.leaveLayerCallbacks.push(callback);
|
|
|
|
}
|
|
|
|
|
2021-05-12 14:30:12 +02:00
|
|
|
public findLayer(layerName: string): ITiledMapLayer | undefined {
|
2021-05-28 12:13:10 +02:00
|
|
|
return this.flatLayers.find((layer) => layer.name === layerName);
|
2021-05-12 14:30:12 +02:00
|
|
|
}
|
|
|
|
|
2021-05-18 15:41:16 +02:00
|
|
|
public findPhaserLayer(layerName: string): TilemapLayer | undefined {
|
2021-05-28 12:13:10 +02:00
|
|
|
return this.phaserLayers.find((layer) => layer.layer.name === layerName);
|
2021-05-18 15:41:16 +02:00
|
|
|
}
|
|
|
|
|
2021-07-07 14:26:53 +02:00
|
|
|
public findPhaserLayers(groupName: string): TilemapLayer[] {
|
|
|
|
return this.phaserLayers.filter((l) => l.layer.name.includes(groupName));
|
|
|
|
}
|
|
|
|
|
2021-06-25 18:14:40 +02:00
|
|
|
public addTerrain(terrain: Phaser.Tilemaps.Tileset): void {
|
2021-05-20 17:09:10 +02:00
|
|
|
for (const phaserLayer of this.phaserLayers) {
|
|
|
|
phaserLayer.tileset.push(terrain);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-28 09:33:13 +02:00
|
|
|
private putTileInFlatLayer(index: number, x: number, y: number, layer: string): void {
|
2021-06-24 11:31:29 +02:00
|
|
|
const fLayer = this.findLayer(layer);
|
2021-07-02 14:35:28 +02:00
|
|
|
if (fLayer == undefined) {
|
2021-07-07 14:42:17 +02:00
|
|
|
console.error("The layer '" + layer + "' that you want to change doesn't exist.");
|
2021-06-28 09:33:13 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-07-02 14:35:28 +02:00
|
|
|
if (fLayer.type !== "tilelayer") {
|
2021-07-07 14:42:17 +02:00
|
|
|
console.error(
|
|
|
|
"The layer '" +
|
|
|
|
layer +
|
|
|
|
"' that you want to change is not a tilelayer. Tile can only be put in tilelayer."
|
|
|
|
);
|
2021-06-24 11:31:29 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-06-29 16:50:33 +02:00
|
|
|
if (typeof fLayer.data === "string") {
|
2021-07-07 14:42:17 +02:00
|
|
|
console.error("Data of the layer '" + layer + "' that you want to change is only readable.");
|
2021-06-29 16:50:33 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-07-07 14:42:17 +02:00
|
|
|
fLayer.data[x + y * fLayer.width] = index;
|
2021-06-24 11:31:29 +02:00
|
|
|
}
|
|
|
|
|
2021-07-07 14:42:17 +02:00
|
|
|
public putTile(tile: string | number | null, x: number, y: number, layer: string): void {
|
2021-06-28 09:33:13 +02:00
|
|
|
const phaserLayer = this.findPhaserLayer(layer);
|
2021-07-02 14:35:28 +02:00
|
|
|
if (phaserLayer) {
|
2021-07-07 14:42:17 +02:00
|
|
|
if (tile === null) {
|
|
|
|
phaserLayer.putTileAt(-1, x, y);
|
|
|
|
return;
|
|
|
|
}
|
2021-06-28 09:33:13 +02:00
|
|
|
const tileIndex = this.getIndexForTileType(tile);
|
2021-07-02 14:35:28 +02:00
|
|
|
if (tileIndex !== undefined) {
|
2021-06-28 09:33:13 +02:00
|
|
|
this.putTileInFlatLayer(tileIndex, x, y, layer);
|
|
|
|
const phaserTile = phaserLayer.putTileAt(tileIndex, x, y);
|
|
|
|
for (const property of this.getTileProperty(tileIndex)) {
|
2021-11-02 10:51:02 +01:00
|
|
|
if (property.name === GameMapProperties.COLLIDES && property.value) {
|
2021-06-28 09:33:13 +02:00
|
|
|
phaserTile.setCollision(true);
|
|
|
|
}
|
|
|
|
}
|
2021-07-02 14:35:28 +02:00
|
|
|
} else {
|
2021-07-07 14:42:17 +02:00
|
|
|
console.error("The tile '" + tile + "' that you want to place doesn't exist.");
|
2021-06-28 09:33:13 +02:00
|
|
|
}
|
2021-07-02 14:35:28 +02:00
|
|
|
} else {
|
2021-07-07 17:06:23 +02:00
|
|
|
console.error("The layer '" + layer + "' does not exist (or is not a tilelaye).");
|
2021-06-28 09:33:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private getIndexForTileType(tile: string | number): number | undefined {
|
|
|
|
if (typeof tile == "number") {
|
|
|
|
return tile;
|
|
|
|
}
|
|
|
|
return this.tileNameMap.get(tile);
|
|
|
|
}
|
2021-08-27 15:05:14 +02:00
|
|
|
|
|
|
|
public setLayerProperty(
|
|
|
|
layerName: string,
|
|
|
|
propertyName: string,
|
|
|
|
propertyValue: string | number | undefined | boolean
|
|
|
|
) {
|
|
|
|
const layer = this.findLayer(layerName);
|
|
|
|
if (layer === undefined) {
|
|
|
|
console.warn('Could not find layer "' + layerName + '" when calling setProperty');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (layer.properties === undefined) {
|
|
|
|
layer.properties = [];
|
|
|
|
}
|
|
|
|
const property = layer.properties.find((property) => property.name === propertyName);
|
|
|
|
if (property === undefined) {
|
|
|
|
if (propertyValue === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
layer.properties.push({ name: propertyName, type: typeof propertyValue, value: propertyValue });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (propertyValue === undefined) {
|
|
|
|
const index = layer.properties.indexOf(property);
|
|
|
|
layer.properties.splice(index, 1);
|
|
|
|
}
|
|
|
|
property.value = propertyValue;
|
|
|
|
|
2021-10-29 16:44:51 +02:00
|
|
|
this.triggerAllProperties();
|
|
|
|
this.triggerLayersChange();
|
2021-08-27 15:05:14 +02:00
|
|
|
}
|
2021-09-15 15:28:55 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Trigger all the callbacks (used when exiting a map)
|
|
|
|
*/
|
|
|
|
public triggerExitCallbacks(): void {
|
|
|
|
const emptyProps = new Map<string, string | boolean | number>();
|
|
|
|
for (const [oldPropName, oldPropValue] of this.lastProperties.entries()) {
|
|
|
|
// We found a property that disappeared
|
|
|
|
this.trigger(oldPropName, oldPropValue, undefined, emptyProps);
|
|
|
|
}
|
|
|
|
}
|
2020-08-30 15:44:22 +02:00
|
|
|
}
|