@screeps/engine
Version:
This is a module for Screeps standalone server. See [main repository](https://github.com/screeps/screeps) for more info.
381 lines (334 loc) • 12.9 kB
JavaScript
var utils = require('../utils'),
driver = utils.getRuntimeDriver(),
C = driver.constants,
_ = require('lodash'),
pathUtils = require('./path-utils'),
Heap = pathUtils.Heap,
OpenClosed = pathUtils.OpenClosed;
const kRouteGrid = 30;
exports.makeMap = function(runtimeData, register, globals) {
var heap, openClosed, parents;
var originX, originY;
var toX, toY;
let _visual;
const accessibleRooms = JSON.parse(runtimeData.accessibleRooms);
function describeExits(roomName) {
if(!/^(W|E)\d+(N|S)\d+$/.test(roomName)) {
return null;
}
var [x,y] = utils.roomNameToXY(roomName);
var gridItem = runtimeData.mapGrid.gridData[`${x},${y}`];
if(!gridItem) {
return null;
}
var exits = {};
if(gridItem.t) {
exits[C.TOP] = utils.getRoomNameFromXY(x,y-1);
}
if(gridItem.b) {
exits[C.BOTTOM] = utils.getRoomNameFromXY(x,y+1);
}
if(gridItem.l) {
exits[C.LEFT] = utils.getRoomNameFromXY(x-1,y);
}
if(gridItem.r) {
exits[C.RIGHT] = utils.getRoomNameFromXY(x+1,y);
}
return exits;
}
function xyToIndex(xx, yy) {
let ox = originX - xx;
let oy = originY - yy;
if (ox < 0 || ox >= kRouteGrid * 2 || oy < 0 || oy >= kRouteGrid * 2) {
return;
}
return ox * kRouteGrid * 2 + oy;
}
function indexToXY(index) {
return [ originX - Math.floor(index / (kRouteGrid * 2)), originY - index % (kRouteGrid * 2) ];
}
function heuristic(xx, yy) {
return Math.abs(xx - toX) + Math.abs(yy - toY);
}
const map = {
findRoute(fromRoom, toRoom, opts) {
if(_.isObject(fromRoom)) {
fromRoom = fromRoom.name;
}
if(_.isObject(toRoom)) {
toRoom = toRoom.name;
}
if(fromRoom == toRoom) {
return [];
}
if(!/(W|E)\d+(N|S)\d+$/.test(fromRoom) || !/(W|E)\d+(N|S)\d+$/.test(toRoom)) {
return C.ERR_NO_PATH;
}
var [fromX, fromY] = utils.roomNameToXY(fromRoom);
[toX, toY] = utils.roomNameToXY(toRoom);
if (fromX == toX && fromY == toY) {
return [];
}
originX = fromX + kRouteGrid;
originY = fromY + kRouteGrid;
// Init path finding structures
if (heap) {
heap.clear();
openClosed.clear();
} else {
heap = new Heap(Math.pow(kRouteGrid * 2, 2), Float64Array);
openClosed = new OpenClosed(Math.pow(kRouteGrid * 2, 2));
}
if (!parents) {
parents = new Uint16Array(Math.pow(kRouteGrid * 2, 2));
}
var fromIndex = xyToIndex(fromX, fromY);
heap.push(fromIndex, heuristic(fromX, fromY));
var routeCallback = (opts && opts.routeCallback) || function() { return 1; };
// Astar
while (heap.size()) {
// Pull node off heap
let index = heap.min();
let fcost = heap.minPriority();
// Close this node
heap.pop();
openClosed.close(index);
// Calculate costs
let [ xx, yy ] = indexToXY(index);
let hcost = heuristic(xx, yy);
let gcost = fcost - hcost;
// Reached destination?
if (hcost === 0) {
let route = [];
while (index !== fromIndex) {
let [ xx, yy ] = indexToXY(index);
index = parents[index];
let [ nx, ny ] = indexToXY(index);
let dir;
if (nx < xx) {
dir = C.FIND_EXIT_RIGHT;
} else if (nx > xx) {
dir = C.FIND_EXIT_LEFT;
} else if (ny < yy) {
dir = C.FIND_EXIT_BOTTOM;
} else {
dir = C.FIND_EXIT_TOP;
}
route.push({
exit: dir,
room: utils.getRoomNameFromXY(xx, yy),
});
}
route.reverse();
return route;
}
// Add neighbors
let fromRoomName = utils.getRoomNameFromXY(xx, yy);
let exits = describeExits(fromRoomName);
for (let dir in exits) {
// Calculate costs and check if this node was already visited
let roomName = exits[dir];
let graphKey = fromRoomName+ ':'+ roomName;
let [ xx, yy ] = utils.roomNameToXY(roomName);
let neighborIndex = xyToIndex(xx, yy);
if (neighborIndex === undefined || openClosed.isClosed(neighborIndex)) {
continue;
}
let cost = Number(routeCallback(roomName, fromRoomName)) || 1;
if (cost === Infinity) {
continue;
}
let fcost = gcost + heuristic(xx, yy) + cost;
// Add to or update heap
if (openClosed.isOpen(neighborIndex)) {
if (heap.priority(neighborIndex) > fcost) {
heap.update(neighborIndex, fcost);
parents[neighborIndex] = index;
}
} else {
heap.push(neighborIndex, fcost);
openClosed.open(neighborIndex);
parents[neighborIndex] = index;
}
}
}
return C.ERR_NO_PATH;
},
findExit(fromRoom, toRoom, opts) {
var route = this.findRoute(fromRoom, toRoom, opts);
if(!_.isArray(route)) {
return route;
}
if(!route.length) {
return C.ERR_INVALID_ARGS;
}
return route[0].exit;
},
describeExits,
isRoomAvailable(roomName) {
register.deprecated('Method `Game.map.isRoomAvailable` is deprecated and will be removed. Please use `Game.map.getRoomStatus` instead.');
if(!/^(W|E)\d+(N|S)\d+$/.test(roomName)) {
return false;
}
return _.contains(accessibleRooms, roomName);
},
getRoomStatus(roomName) {
if(!/^(W|E)\d+(N|S)\d+$/.test(roomName)) {
return undefined;
}
if(!runtimeData.roomStatusData) {
throw new Error('No runtime status data');
}
if(!_.isUndefined(runtimeData.roomStatusData.closed[roomName])) {
return { status: 'closed', timestamp: runtimeData.roomStatusData.closed[roomName] };
}
if(!_.isUndefined(runtimeData.roomStatusData.novice[roomName])) {
return { status: 'novice', timestamp: runtimeData.roomStatusData.novice[roomName] };
}
if(!_.isUndefined(runtimeData.roomStatusData.respawn[roomName])) {
return { status: 'respawn', timestamp: runtimeData.roomStatusData.respawn[roomName] };
}
if(_.contains(accessibleRooms, roomName)) {
return { status: 'normal', timestamp: null };
}
return { status: 'closed', timestamp: null };
},
getTerrainAt(x, y, roomName) {
register.deprecated('Method `Game.map.getTerrainAt` is deprecated and will be removed. Please use a faster method `Game.map.getRoomTerrain` instead.');
if(_.isObject(x)) {
y = x.y;
roomName = x.roomName;
x = x.x;
}
// check if coordinates are out of bounds
if(x < 0 || x > 49 || y < 0 || y > 49) {
return undefined;
}
if(!runtimeData.staticTerrainData || !runtimeData.staticTerrainData[roomName]) {
return undefined;
}
var terrain = runtimeData.staticTerrainData[roomName][y*50+x];
if(terrain & C.TERRAIN_MASK_WALL) {
return 'wall'
}
if(terrain & C.TERRAIN_MASK_SWAMP) {
return 'swamp';
}
return 'plain';
},
getRoomTerrain(roomName) {
return new globals.Room.Terrain(roomName);
},
getRoomLinearDistance(roomName1, roomName2, continuous) {
return utils.calcRoomsDistance(roomName1, roomName2, continuous);
},
getWorldSize() {
return driver.getWorldSize();
},
};
Object.defineProperties(map, {
visual: {
enumerable: true,
get() {
if(!_visual) {
_visual = {};
Object.defineProperties(_visual, {
circle: {
value: function(pos, style) {
if(!(pos instanceof globals.RoomPosition)) {
throw new Error('Invalid pos, RoomPosition expected')
}
globals.console.addVisual("map", {
t: 'c',
x: pos.x, y: pos.y, n: pos.roomName,
s: style || {}});
return this;
}
},
line: {
value: function(pos1, pos2, style) {
if(!(pos1 instanceof globals.RoomPosition)) {
throw new Error('Invalid pos1, RoomPosition expected')
}
if(!(pos2 instanceof globals.RoomPosition)) {
throw new Error('Invalid pos2, RoomPosition expected')
}
globals.console.addVisual("map", {
t: 'l',
x1: pos1.x, y1: pos1.y, n1: pos1.roomName,
x2: pos2.x, y2: pos2.y, n2: pos2.roomName,
s: style || {}});
return this;
}
},
rect: {
value: function(pos, w, h, style) {
if(!(pos instanceof globals.RoomPosition)) {
throw new Error('Invalid pos, RoomPosition expected')
}
globals.console.addVisual("map", {
t: 'r',
x: pos.x, y: pos.y, n: pos.roomName,
w, h,
s: style || {}});
return this;
}
},
poly: {
value: function(points, style) {
if(_.isArray(points) && _.some(points)) {
points = points.map(i => {
const p = i.pos || i;
return {x: p.x, y: p.y, n: p.roomName};
});
globals.console.addVisual("map", {
t: 'p',
points,
s: style || {}});
}
return this;
}
},
text: {
value: function(text, pos, style) {
if(!(pos instanceof globals.RoomPosition)) {
throw new Error('Invalid pos , RoomPosition expected')
}
globals.console.addVisual("map", {
t: 't',
text,
x: pos.x, y: pos.y, n: pos.roomName,
s: style || {}});
return this;
}
},
clear: {
value: function () {
globals.console.clearVisual("map");
return this;
}
},
getSize: {
value: function() {
return globals.console.getVisualSize("map");
}
},
export: {
value: function(){
return globals.console.getVisual("map");
}
},
import: {
value: function(data){
globals.console.addVisual("map", "" + data);
return this;
}
}
});
}
return _visual;
}
}
});
return map;
};