2020-06-28 17:34:10 +00:00
|
|
|
import { GameRoot, enumLayer } from "./root";
|
2020-05-09 14:45:23 +00:00
|
|
|
import { globalConfig } from "../core/config";
|
|
|
|
import { Vector } from "../core/vector";
|
|
|
|
import { Entity } from "./entity";
|
|
|
|
import { createLogger } from "../core/logging";
|
|
|
|
import { BaseItem } from "./base_item";
|
|
|
|
import { MapChunkView } from "./map_chunk_view";
|
2020-05-14 19:54:11 +00:00
|
|
|
import { randomInt } from "../core/utils";
|
|
|
|
import { BasicSerializableObject, types } from "../savegame/serialization";
|
2020-05-09 14:45:23 +00:00
|
|
|
|
|
|
|
const logger = createLogger("map");
|
|
|
|
|
2020-05-14 19:54:11 +00:00
|
|
|
export class BaseMap extends BasicSerializableObject {
|
|
|
|
static getId() {
|
|
|
|
return "Map";
|
|
|
|
}
|
|
|
|
|
|
|
|
static getSchema() {
|
|
|
|
return {
|
|
|
|
seed: types.uint,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-05-09 14:45:23 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {GameRoot} root
|
|
|
|
*/
|
|
|
|
constructor(root) {
|
2020-05-14 19:54:11 +00:00
|
|
|
super();
|
2020-05-09 14:45:23 +00:00
|
|
|
this.root = root;
|
|
|
|
|
2020-05-14 19:54:11 +00:00
|
|
|
this.seed = 0;
|
|
|
|
|
2020-05-09 14:45:23 +00:00
|
|
|
/**
|
|
|
|
* Mapping of 'X|Y' to chunk
|
|
|
|
* @type {Map<string, MapChunkView>} */
|
|
|
|
this.chunksById = new Map();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the given chunk by index
|
|
|
|
* @param {number} chunkX
|
|
|
|
* @param {number} chunkY
|
|
|
|
*/
|
|
|
|
getChunk(chunkX, chunkY, createIfNotExistent = false) {
|
|
|
|
const chunkIdentifier = chunkX + "|" + chunkY;
|
|
|
|
let storedChunk;
|
|
|
|
|
|
|
|
if ((storedChunk = this.chunksById.get(chunkIdentifier))) {
|
|
|
|
return storedChunk;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (createIfNotExistent) {
|
|
|
|
const instance = new MapChunkView(this.root, chunkX, chunkY);
|
|
|
|
this.chunksById.set(chunkIdentifier, instance);
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets or creates a new chunk if not existent for the given tile
|
|
|
|
* @param {number} tileX
|
|
|
|
* @param {number} tileY
|
|
|
|
* @returns {MapChunkView}
|
|
|
|
*/
|
|
|
|
getOrCreateChunkAtTile(tileX, tileY) {
|
2020-06-27 08:51:52 +00:00
|
|
|
const chunkX = Math.floor(tileX / globalConfig.mapChunkSize);
|
|
|
|
const chunkY = Math.floor(tileY / globalConfig.mapChunkSize);
|
2020-05-09 14:45:23 +00:00
|
|
|
return this.getChunk(chunkX, chunkY, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a chunk if not existent for the given tile
|
|
|
|
* @param {number} tileX
|
|
|
|
* @param {number} tileY
|
|
|
|
* @returns {MapChunkView?}
|
|
|
|
*/
|
|
|
|
getChunkAtTileOrNull(tileX, tileY) {
|
2020-06-27 08:51:52 +00:00
|
|
|
const chunkX = Math.floor(tileX / globalConfig.mapChunkSize);
|
|
|
|
const chunkY = Math.floor(tileY / globalConfig.mapChunkSize);
|
2020-05-09 14:45:23 +00:00
|
|
|
return this.getChunk(chunkX, chunkY, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if a given tile is within the map bounds
|
|
|
|
* @param {Vector} tile
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
isValidTile(tile) {
|
|
|
|
if (G_IS_DEV) {
|
|
|
|
assert(tile instanceof Vector, "tile is not a vector");
|
|
|
|
}
|
|
|
|
return Number.isInteger(tile.x) && Number.isInteger(tile.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the tile content of a given tile
|
|
|
|
* @param {Vector} tile
|
2020-06-28 17:34:10 +00:00
|
|
|
* @param {enumLayer} layer
|
2020-05-09 14:45:23 +00:00
|
|
|
* @returns {Entity} Entity or null
|
|
|
|
*/
|
2020-06-28 17:34:10 +00:00
|
|
|
getTileContent(tile, layer) {
|
2020-05-09 14:45:23 +00:00
|
|
|
if (G_IS_DEV) {
|
|
|
|
this.internalCheckTile(tile);
|
|
|
|
}
|
|
|
|
const chunk = this.getChunkAtTileOrNull(tile.x, tile.y);
|
2020-06-28 17:34:10 +00:00
|
|
|
return chunk && chunk.getLayerContentFromWorldCoords(tile.x, tile.y, layer);
|
2020-05-09 14:45:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the lower layers content of the given tile
|
|
|
|
* @param {number} x
|
|
|
|
* @param {number} y
|
|
|
|
* @returns {BaseItem=}
|
|
|
|
*/
|
|
|
|
getLowerLayerContentXY(x, y) {
|
|
|
|
return this.getOrCreateChunkAtTile(x, y).getLowerLayerFromWorldCoords(x, y);
|
|
|
|
}
|
|
|
|
|
2020-06-28 17:34:10 +00:00
|
|
|
/**
|
|
|
|
* Returns the tile content of a given tile
|
|
|
|
* @param {number} x
|
|
|
|
* @param {number} y
|
|
|
|
* @param {enumLayer} layer
|
|
|
|
* @returns {Entity} Entity or null
|
|
|
|
*/
|
|
|
|
getLayerContentXY(x, y, layer) {
|
|
|
|
const chunk = this.getChunkAtTileOrNull(x, y);
|
|
|
|
return chunk && chunk.getLayerContentFromWorldCoords(x, y, layer);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the tile contents of a given tile
|
|
|
|
* @param {number} x
|
|
|
|
* @param {number} y
|
|
|
|
* @returns {Array<Entity>} Entity or null
|
|
|
|
*/
|
|
|
|
getLayersContentsMultipleXY(x, y) {
|
|
|
|
const chunk = this.getChunkAtTileOrNull(x, y);
|
|
|
|
return chunk && chunk.getLayersContentsMultipleFromWorldCoords(x, y);
|
|
|
|
}
|
|
|
|
|
2020-05-09 14:45:23 +00:00
|
|
|
/**
|
|
|
|
* Checks if the tile is used
|
|
|
|
* @param {Vector} tile
|
2020-06-30 06:24:56 +00:00
|
|
|
* @param {enumLayer} layer
|
2020-05-09 14:45:23 +00:00
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2020-06-30 06:24:56 +00:00
|
|
|
isTileUsed(tile, layer) {
|
2020-05-09 14:45:23 +00:00
|
|
|
if (G_IS_DEV) {
|
|
|
|
this.internalCheckTile(tile);
|
|
|
|
}
|
|
|
|
const chunk = this.getChunkAtTileOrNull(tile.x, tile.y);
|
2020-06-30 06:24:56 +00:00
|
|
|
return chunk && chunk.getLayerContentFromWorldCoords(tile.x, tile.y, layer) != null;
|
2020-05-09 14:45:23 +00:00
|
|
|
}
|
|
|
|
|
2020-05-27 12:30:59 +00:00
|
|
|
/**
|
|
|
|
* Checks if the tile is used
|
|
|
|
* @param {number} x
|
|
|
|
* @param {number} y
|
2020-06-30 06:24:56 +00:00
|
|
|
* @param {enumLayer} layer
|
2020-05-27 12:30:59 +00:00
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2020-06-30 06:24:56 +00:00
|
|
|
isTileUsedXY(x, y, layer) {
|
2020-05-27 12:30:59 +00:00
|
|
|
const chunk = this.getChunkAtTileOrNull(x, y);
|
2020-06-30 06:24:56 +00:00
|
|
|
return chunk && chunk.getLayerContentFromWorldCoords(x, y, layer) != null;
|
2020-05-27 12:30:59 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 14:45:23 +00:00
|
|
|
/**
|
|
|
|
* Sets the tiles content
|
|
|
|
* @param {Vector} tile
|
|
|
|
* @param {Entity} entity
|
|
|
|
*/
|
|
|
|
setTileContent(tile, entity) {
|
|
|
|
if (G_IS_DEV) {
|
|
|
|
this.internalCheckTile(tile);
|
|
|
|
}
|
|
|
|
|
2020-06-28 17:34:10 +00:00
|
|
|
this.getOrCreateChunkAtTile(tile.x, tile.y).setLayerContentFromWorldCords(
|
|
|
|
tile.x,
|
|
|
|
tile.y,
|
|
|
|
entity,
|
|
|
|
entity.layer
|
|
|
|
);
|
2020-05-09 14:45:23 +00:00
|
|
|
|
|
|
|
const staticComponent = entity.components.StaticMapEntity;
|
|
|
|
assert(staticComponent, "Can only place static map entities in tiles");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Places an entity with the StaticMapEntity component
|
|
|
|
* @param {Entity} entity
|
|
|
|
*/
|
|
|
|
placeStaticEntity(entity) {
|
|
|
|
assert(entity.components.StaticMapEntity, "Entity is not static");
|
|
|
|
const staticComp = entity.components.StaticMapEntity;
|
|
|
|
const rect = staticComp.getTileSpaceBounds();
|
|
|
|
for (let dx = 0; dx < rect.w; ++dx) {
|
|
|
|
for (let dy = 0; dy < rect.h; ++dy) {
|
|
|
|
const x = rect.x + dx;
|
|
|
|
const y = rect.y + dy;
|
2020-06-28 17:34:10 +00:00
|
|
|
this.getOrCreateChunkAtTile(x, y).setLayerContentFromWorldCords(x, y, entity, entity.layer);
|
2020-05-09 14:45:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes an entity with the StaticMapEntity component
|
|
|
|
* @param {Entity} entity
|
|
|
|
*/
|
|
|
|
removeStaticEntity(entity) {
|
|
|
|
assert(entity.components.StaticMapEntity, "Entity is not static");
|
|
|
|
const staticComp = entity.components.StaticMapEntity;
|
|
|
|
const rect = staticComp.getTileSpaceBounds();
|
|
|
|
for (let dx = 0; dx < rect.w; ++dx) {
|
|
|
|
for (let dy = 0; dy < rect.h; ++dy) {
|
|
|
|
const x = rect.x + dx;
|
|
|
|
const y = rect.y + dy;
|
2020-06-28 17:34:10 +00:00
|
|
|
this.getOrCreateChunkAtTile(x, y).setLayerContentFromWorldCords(x, y, null, entity.layer);
|
2020-05-09 14:45:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Internal
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks a given tile for validty
|
|
|
|
* @param {Vector} tile
|
|
|
|
*/
|
|
|
|
internalCheckTile(tile) {
|
|
|
|
assert(tile instanceof Vector, "tile is not a vector: " + tile);
|
|
|
|
assert(tile.x % 1 === 0, "Tile X is not a valid integer: " + tile.x);
|
|
|
|
assert(tile.y % 1 === 0, "Tile Y is not a valid integer: " + tile.y);
|
|
|
|
}
|
|
|
|
}
|