hobgoblin
Version:
A framework for writing roguelike games in JavaScript, implementing ROT.js
310 lines (276 loc) • 10.1 kB
JavaScript
// @size should be square number of lots for a city.
Game.Map = function(width, height, depth, player) {
// Used for drawing to various displays
this._tiles = this._generateTiles(width, height, depth);
// Cache dimensions
this._depth = this._tiles.length;
this._width = this._tiles[0].length;
this._height = this._tiles[0][0].length;
// Setup the field of visions
this._fov = [];
this.setupFov();
// Create a table which will hold the items
this._items = {};
// Create the engine and scheduler
this._scheduler = new ROT.Scheduler.Speed();
this._engine = new ROT.Engine(this._scheduler);
// Setup the explored array
this._explored = new Array(this._depth);
this._setupExploredArray();
// Create a table which will hold the entities
this._entities = {};
// Add monsters here
for (var z = 0; z < this._depth; z++)
for (var i = 0; i < 10; i++)
this.addEntityAtRandomPosition(Game.EntityRepository.createRandom(), z);
// Add the Player
this.addEntityAtRandomPosition(player, 0);
};
// Standard getters
Game.Map.prototype.getDepth = function() {
return this._depth;
};
Game.Map.prototype.getWidth = function() {
return this._width;
};
Game.Map.prototype.getHeight = function() {
return this._height;
};
Game.Map.prototype.getScheduler = function() {
return this._scheduler;
};
Game.Map.prototype.getEngine = function() {
return this._engine;
};
Game.Map.prototype.getEntities = function() {
return this._entities;
};
Game.Map.prototype.getPlayer = function() {
return this._player;
};
Game.Map.prototype._generateTiles = function(width, height, depth) {
var tiles = new Array(depth);
var dungeon = new ROT.Map.Digger(width, height);
// Instantiate the arrays to be multi-dimension
for (var z = 0; z < depth; z++) {
// Create a new cave at each level
dungeon.create(function(x, y, wall) {
if(!tiles[z])
tiles[z] = new Array(width);
if(!tiles[z][x])
tiles[z][x] = new Array(height);
tiles[z][x][y] = wall ? Game.TileRepository.create('wall') : Game.TileRepository.create('floor');
});
}
// Place stairs
// Note, this does not actually ensure that stairs will be placed
for(var level = 0; level < depth - 1; level++) {
var floorX = null,
floorY = null,
tries = 0;
do {
floorX = Math.floor(Math.random() * width);
floorY = Math.floor(Math.random() * height);
tries++;
} while((tiles[level][floorX][floorY].describe() !== 'floor' ||
tiles[level + 1][floorX][floorY].describe() !== 'floor') &&
tries < 1000);
if(tries < 1000 && floorX !== null && floorY !== null) {
tiles[level][floorX][floorY] = Game.TileRepository.create('stairsDown');
tiles[level + 1][floorX][floorY] = Game.TileRepository.create('stairsUp');
}
}
return tiles;
};
// For just adding actors to the scheduler
Game.Map.prototype.schedule = function(actor) {
if('act' in actor) {
this._scheduler.add(actor, true);
}
if('_map' in actor) {
actor._map = this;
}
};
// Entities
Game.Map.prototype.addEntity = function(entity) {
// Set the entity's map
entity.setMap(this);
// Add the entity to the map's list of entities
this.updateEntityPosition(entity);
// Check to see if the entity is an actor
// If so, add them to the scheduler
if(entity.hasMixin('Actor')) {
this._scheduler.add(entity, true);
}
// If the entity is the player, set the player.
if (entity.hasMixin(Game.EntityMixins.PlayerActor)) {
this._player = entity;
}
};
Game.Map.prototype.addEntityAtRandomPosition = function(entity, z) {
var position = this.getRandomFloorPosition(z);
entity.setX(position.x);
entity.setY(position.y);
entity.setZ(position.z);
this.addEntity(entity);
};
Game.Map.prototype.getEntityAt = function(x, y, z) {
// Get the entity based on position key
return this._entities[x + ',' + y + ',' + z];
};
Game.Map.prototype.getEntitiesWithinRadius = function(centerX, centerY, centerZ, radius) {
var results = [];
// Determine the bounds...
var leftX = centerX - radius;
var rightX = centerX + radius;
var topY = centerY - radius;
var bottomY = centerY + radius;
for (var key in this._entities) {
var entity = this._entities[key];
if (entity.getX() >= leftX &&
entity.getX() <= rightX &&
entity.getY() >= topY &&
entity.getY() <= bottomY &&
entity.getZ() == centerZ) {
results.push(entity);
}
}
return results;
};
Game.Map.prototype.removeEntity = function(entity) {
// Find the entity in the list of entities if it is present
var key = entity.getX() + ',' + entity.getY() + ',' + entity.getZ();
if(this._entities[key] == entity) {
delete this._entities[key];
}
// If the entity is an actor, remove them from the scheduler
if (entity.hasMixin('Actor')) {
this._scheduler.remove(entity);
}
// If the entity is the player, update the player field.
if (entity.hasMixin(Game.EntityMixins.PlayerActor)) {
this._player = undefined;
}
};
Game.Map.prototype.updateEntityPosition = function(entity, oldX, oldY, oldZ) {
// Delete the old key if it is the same entity and we have old positons
if(typeof oldX === 'number') {
var oldKey = oldX + "," + oldY + "," + oldZ;
if(this._entities[oldKey] == entity) {
delete this._entities[oldKey];
}
}
// Make sure the entity's position is within bounds
if (entity.getX() < 0 || entity.getX() >= this._width ||
entity.getY() < 0 || entity.getY() >= this._height ||
entity.getZ() < 0 || entity.getZ() >= this._depth) {
throw new Error("Entity's position is out of bounds.");
}
// Sanity check to make sure there is no entity at the new position
var key = entity.getX() + "," + entity.getY() + "," + entity.getZ();
if (this._entities[key]) {
throw new Error('Tried to add an entity at an occupied position.');
}
// Add the entity to the table of entities
this._entities[key] = entity;
};
// Floors
Game.Map.prototype.isEmptyFloor = function(x, y, z) {
// Check if the tile is floor and also has no entity
return this.getTile(x, y, z).describe() == 'floor' && !this.getEntityAt(x, y, z);
};
Game.Map.prototype.getRandomFloorPosition = function(z) {
var x, y;
do {
x = Math.floor(Math.random() * this._width);
y = Math.floor(Math.random() * this._height);
} while(!this.isEmptyFloor(x, y, z));
return {x: x, y: y, z: z};
};
// Tiles
// Gets the tile for a given coordinate set
Game.Map.prototype.getTile = function(x, y, z) {
// Make sure we are inside the bounds.
//If we aren't, return null tile.
if (x < 0 || x >= this._width || y < 0 || y >= this._height || z < 0 || z >= this._depth) {
return Game.TileRepository.create('null');
} else {
return this._tiles[z][x][y] || Game.TileRepository.create('null');
}
};
// FOV
Game.Map.prototype.setupFov = function() {
// Keep this in 'map' variable so that we don't lose it.
var map = this;
// Iterate through each depth level, setting up the field of vision
for (var z = 0; z < this._depth; z++) {
// We have to put the following code in it's own scope to prevent the
// depth variable from being hoisted out of the loop.
(function() {
// For each depth, we need to create a callback which figures out
// if light can pass through a given tile.
var depth = z;
map._fov.push(new ROT.FOV.PreciseShadowcasting(function(x, y) {
return !map.getTile(x, y, depth).isBlockingLight();
}));
})();
}
};
Game.Map.prototype.getFov = function(depth) {
return this._fov[depth];
};
// Explored Areas
Game.Map.prototype._setupExploredArray = function() {
for (var z = 0; z < this._depth; z++) {
this._explored[z] = new Array(this._width);
for (var x = 0; x < this._width; x++) {
this._explored[z][x] = new Array(this._height);
for (var y = 0; y < this._height; y++) {
this._explored[z][x][y] = false;
}
}
}
};
Game.Map.prototype.setExplored = function(x, y, z, state) {
// Only update if the tile is within bounds
if (this.getTile(x, y, z).describe() !== 'null') {
this._explored[z][x][y] = state;
}
};
Game.Map.prototype.isExplored = function(x, y, z) {
// Only return the value if within bounds
if (this.getTile(x, y, z).describe() !== 'null') {
return this._explored[z][x][y];
} else {
return false;
}
};
// Items - TODO: move this?
Game.Map.prototype.getItemsAt = function(x, y, z) {
return this._items[x + ',' + y + ',' + z];
};
Game.Map.prototype.setItemsAt = function(x, y, z, items) {
// If our items array is empty, then delete the key from the table.
var key = x + ',' + y + ',' + z;
if (items.length === 0) {
if (this._items[key]) {
delete this._items[key];
}
} else {
// Simply update the items at that key
this._items[key] = items;
}
};
Game.Map.prototype.addItem = function(x, y, z, item) {
// If we already have items at that position, simply append the item to the list of items.
var key = x + ',' + y + ',' + z;
if (this._items[key]) {
this._items[key].push(item);
} else {
this._items[key] = [item];
}
};
Game.Map.prototype.addItemAtRandomPosition = function(item, z) {
var position = this.getRandomFloorPosition(z);
this.addItem(position.x, position.y, position.z, item);
};