UNPKG

@screeps/engine

Version:

This is a module for Screeps standalone server. See [main repository](https://github.com/screeps/screeps) for more info.

1,433 lines (1,178 loc) 49.1 kB
var _ = require('lodash'), utils = require('./../utils'), driver = utils.getDriver(), C = driver.constants, pathfinding = require('@screeps/pathfinding'); var abs = Math.abs, min = Math.min, max = Math.max; var runtimeData, intents, register, globals; var positionsSetCacheCounter, createdFlagNames, createdSpawnNames, privateStore, createdConstructionSites; function getPathfinder(id, opts) { opts = opts || {}; _.defaults(opts, {maxOps: 2000, heuristicWeight: 1}); var key = `${opts.maxOps},${opts.heuristicWeight}`; if(!privateStore[id].pfFinders[key]) { privateStore[id].pfFinders[key] = new pathfinding.AStarFinder({ diagonalMovement: 1, maxOpsLimit: opts.maxOps, heuristic: pathfinding.Heuristic.chebyshev, weight: opts.heuristicWeight }); } return privateStore[id].pfFinders[key]; } function makePathfindingGrid(id, opts, endNodesKey) { opts = opts || {}; var rows = new Array(50), obstacleTypes = _.clone(C.OBSTACLE_OBJECT_TYPES); if(opts.ignoreDestructibleStructures) { obstacleTypes = _.without(obstacleTypes, 'constructedWall','spawn','extension', 'link','storage','observer','tower','powerBank','powerSpawn','lab','terminal'); } if(opts.ignoreCreeps) { obstacleTypes = _.without(obstacleTypes, 'creep'); } for(var y=0; y<50; y++) { rows[y] = new Array(50); for(var x=0; x<50; x++) { rows[y][x] = x == 0 || y == 0 || x == 49 || y == 49 ? 11 : 2; //var terrainCode = register.terrainByRoom.spatial[id][y][x]; var terrainCode = runtimeData.staticTerrainData[id][y*50+x]; if(terrainCode & C.TERRAIN_MASK_WALL) { rows[y][x] = 0; } if ((terrainCode & C.TERRAIN_MASK_SWAMP) && rows[y][x] == 2) { rows[y][x] = 10; } } } register.objectsByRoomKeys[id].forEach((key) => { var object = register.objectsByRoom[id][key]; if (_.contains(obstacleTypes, object.type) || !opts.ignoreDestructibleStructures && object.type == 'rampart' && !object.isPublic && object.user != runtimeData.user._id || !opts.ignoreDestructibleStructures && object.type == 'constructionSite' && object.user == runtimeData.user._id && _.contains(C.OBSTACLE_OBJECT_TYPES, object.structureType)) { rows[object.y][object.x] = 0; } if (object.type == 'road' && rows[object.y][object.x] > 0) { rows[object.y][object.x] = 1; } }); if(opts.ignore) { if(!_.isArray(opts.ignore)) { throw new Error('option `ignore` is not an array'); } _.forEach(opts.ignore, (i, key) => { if(!i) { return; } if(i.pos) { rows[i.pos.y][i.pos.x] = rows[i.pos.y][i.pos.x] > 2 ? 2 : rows[i.pos.y][i.pos.x]; } if(_.isObject(i) && !_.isUndefined(i.x) && !(i instanceof globals.RoomPosition)) { opts.ignore[key] = new globals.RoomPosition(i.x, i.y, id); } if(!_.isUndefined(i.x)) { rows[i.y][i.x] = rows[i.y][i.x] > 2 ? 2 : rows[i.y][i.x]; } }); } if(opts.avoid) { if(!_.isArray(opts.avoid)) { throw new Error('option `avoid` is not an array'); } _.forEach(opts.avoid, (i, key) => { if(!i) { return; } if(i.pos) { rows[i.pos.y][i.pos.x] = 0; } if(_.isObject(i) && !_.isUndefined(i.x) && !(i instanceof globals.RoomPosition)) { opts.avoid[key] = new globals.RoomPosition(i.x, i.y, id); } if(!_.isUndefined(i.x)) { rows[i.y][i.x] = 0; } }); } if(endNodesKey) { _.forEach(privateStore[id].pfEndNodes[endNodesKey], (i) => { if(!_.isUndefined(i.x)) { rows[i.y][i.x] = 999; } else if(!_.isUndefined(i.pos)) { rows[i.pos.y][i.pos.x] = 999; } }); } return new pathfinding.Grid(50, 50, rows); } function getPathfindingGrid(id, opts, endNodesKey) { var gridName = 'grid'; opts = opts || {}; if(opts.ignoreCreeps) { gridName += '_ignoreCreeps' } if(opts.ignoreDestructibleStructures) { gridName += '_ignoreDestructibleStructures' } if(_.isNumber(endNodesKey)) { gridName += '_endNodes'+endNodesKey; } if(opts.avoid) { gridName += '_avoid'+privateStore[id].positionsSetCache.key(opts.avoid); } if(opts.ignore) { gridName += '_ignore'+privateStore[id].positionsSetCache.key(opts.ignore); } if(!privateStore[id].pfGrid[gridName]) privateStore[id].pfGrid[gridName] = makePathfindingGrid(id, opts, endNodesKey); return privateStore[id].pfGrid[gridName].clone(); } function makePathfindingGrid2(id, opts) { opts = opts || {}; var costs = new globals.PathFinder.CostMatrix(); var obstacleTypes = _.clone(C.OBSTACLE_OBJECT_TYPES); obstacleTypes.push('portal'); if(opts.ignoreDestructibleStructures) { obstacleTypes = _.without(obstacleTypes, 'constructedWall','spawn','extension', 'link','storage','observer','tower','powerBank','powerSpawn','lab','terminal'); } if(opts.ignoreCreeps || register.rooms[id].controller && register.rooms[id].controller.safeMode && register.rooms[id].controller.my) { obstacleTypes = _.without(obstacleTypes, 'creep'); } if(register.objectsByRoomKeys[id]) { register.objectsByRoomKeys[id].forEach((key) => { var object = register.objectsByRoom[id][key]; if (_.contains(obstacleTypes, object.type) || !opts.ignoreCreeps && register.rooms[id].controller && register.rooms[id].controller.safeMode && register.rooms[id].controller.my && object.type == 'creep' && object.user == runtimeData.user._id || !opts.ignoreDestructibleStructures && object.type == 'rampart' && !object.isPublic && object.user != runtimeData.user._id || !opts.ignoreDestructibleStructures && object.type == 'constructionSite' && object.user == runtimeData.user._id && _.contains(C.OBSTACLE_OBJECT_TYPES, object.structureType)) { costs.set(object.x, object.y, 0xFF); } if (object.type == 'swamp' && costs.get(object.x, object.y) == 0) { costs.set(object.x, object.y, opts.ignoreRoads ? 5 : 10); } if (!opts.ignoreRoads && object.type == 'road' && costs.get(object.x, object.y) < 0xFF) { costs.set(object.x, object.y, 1); } }); } return costs; } function getPathfindingGrid2(id, opts) { if(!privateStore[id]) { return new globals.PathFinder.CostMatrix(); } var gridName = 'grid2'; opts = opts || {}; if(opts.ignoreCreeps) { gridName += '_ignoreCreeps'; } if(opts.ignoreDestructibleStructures) { gridName += '_ignoreDestructibleStructures'; } if(opts.ignoreRoads) { gridName += '_ignoreRoads'; } if(!privateStore[id].pfGrid[gridName]) privateStore[id].pfGrid[gridName] = makePathfindingGrid2(id, opts); return privateStore[id].pfGrid[gridName]; } function _findPath2(id, fromPos, toPos, opts) { opts = opts || {}; if(fromPos.isEqualTo(toPos)) { return opts.serialize ? '' : []; } if(opts.avoid) { register.deprecated('`avoid` option cannot be used when `PathFinder.use()` is enabled. Use `costCallback` instead.'); opts.avoid = undefined; } if(opts.ignore) { register.deprecated('`ignore` option cannot be used when `PathFinder.use()` is enabled. Use `costCallback` instead.'); opts.ignore = undefined; } if(opts.maxOps === undefined && (opts.maxRooms === undefined || opts.maxRooms > 1) && fromPos.roomName != toPos.roomName) { opts.maxOps = 20000; } var searchOpts = { roomCallback: function(roomName) { var costMatrix = getPathfindingGrid2(roomName, opts); if(typeof opts.costCallback == 'function') { costMatrix = costMatrix.clone(); var resultMatrix = opts.costCallback(roomName, costMatrix); if(resultMatrix instanceof globals.PathFinder.CostMatrix) { costMatrix = resultMatrix; } } return costMatrix; }, maxOps: opts.maxOps, maxRooms: opts.maxRooms }; if(!opts.ignoreRoads) { searchOpts.plainCost = 2; searchOpts.swampCost = 10; } var ret = globals.PathFinder.search(fromPos, {range: Math.max(1,opts.range || 0), pos: toPos}, searchOpts); if(!opts.range && (ret.path.length && ret.path[ret.path.length-1].isNearTo(toPos) && !ret.path[ret.path.length-1].isEqualTo(toPos) || !ret.path.length && fromPos.isNearTo(toPos))) { ret.path.push(toPos); } var curX = fromPos.x, curY = fromPos.y; var resultPath = []; for(let i=0; i<ret.path.length; i++) { let pos = ret.path[i]; if(pos.roomName != id) { break; } let result = { x: pos.x, y: pos.y, dx: pos.x - curX, dy: pos.y - curY, direction: utils.getDirection(pos.x - curX, pos.y - curY) }; curX = result.x; curY = result.y; resultPath.push(result); } if(opts.serialize) { return utils.serializePath(resultPath); } return resultPath; } function _findClosestByPath2(fromPos, objects, opts) { opts = opts || {}; if(_.isNumber(objects)) { objects = register.rooms[fromPos.roomName].find(objects, {filter: opts.filter}); } else if(opts.filter) { objects = _.filter(objects, opts.filter); } if(!objects.length) { return null; } var objectOnSquare = _.find(objects, obj => fromPos.isEqualTo(obj)); if(objectOnSquare) { return objectOnSquare; } var goals = _.map(objects, i => { if(i.pos) { i = i.pos; } return {range: 1, pos: i}; }); if(opts.avoid) { register.deprecated('`avoid` option cannot be used when `PathFinder.use()` is enabled. Use `costCallback` instead.'); } if(opts.ignore) { register.deprecated('`ignore` option cannot be used when `PathFinder.use()` is enabled. Use `costCallback` instead.'); } var searchOpts = { roomCallback: function(roomName) { if(register.objectsByRoom[roomName]) { var costMatrix = getPathfindingGrid2(roomName, opts); if(typeof opts.costCallback == 'function') { costMatrix = costMatrix.clone(); var resultMatrix = opts.costCallback(roomName, costMatrix); if(resultMatrix instanceof globals.PathFinder.CostMatrix) { costMatrix = resultMatrix; } } return costMatrix; } }, maxOps: opts.maxOps, maxRooms: 1 }; if(!opts.ignoreRoads) { searchOpts.plainCost = 2; searchOpts.swampCost = 10; } var ret = globals.PathFinder.search(fromPos, goals, searchOpts); var result = null; var lastPos = fromPos; if(ret.path.length) { lastPos = ret.path[ret.path.length-1]; } objects.forEach(obj => { if(lastPos.isNearTo(obj)) { result = obj; } }); return result; } exports.make = function(_runtimeData, _intents, _register, _globals) { runtimeData = _runtimeData; intents = _intents; register = _register; globals = _globals; positionsSetCacheCounter = 1; createdFlagNames = []; createdSpawnNames = []; privateStore = {}; createdConstructionSites = 0; if(globals.Room) { return; } var data = (id) => { if(!runtimeData.rooms[id]) { throw new Error("Could not find a room with name "+id); } return runtimeData.rooms[id]; }; /** * Room * @param id * @returns {number} * @constructor */ var Room = register.wrapFn(function(id) { var objectData = data(id); var gameInfo, gameId = id, match = id.match(/survival_(.*)$/); if(match) { gameId = match[1]; } if(runtimeData.games && gameId in runtimeData.games) { gameInfo = runtimeData.games[gameId]; } this.name = id; this.energyAvailable = 0; this.energyCapacityAvailable = 0; this.survivalInfo = gameInfo; privateStore[id] = { pfGrid: {}, pfFinders: {}, pfEndNodes: {}, pfDijkstraFinder: new pathfinding.DijkstraFinder({diagonalMovement: 1}), pathCache: {}, positionsSetCache: { cache: {}, key(array) { if (!_.isArray(array)) { return 0; } var positionsArray = _.map(array, (i) => { if (i && i.pos) { return i.pos; } if(_.isObject(i) && !_.isUndefined(i.x) && !(i instanceof globals.RoomPosition)) { return new globals.RoomPosition(i.x, i.y, id); } return i; }); var key = _.findKey(this.cache, (objects) => { return positionsArray.length == objects.length && _.every(positionsArray, (j) => _.any(objects, (object) => { if(!_.isObject(j) || !j.isEqualTo) { throw new Error('Invalid position '+j+', check your `opts` property'); } return j.isEqualTo(object); })); }); if (key === undefined) { key = positionsSetCacheCounter++; this.cache[key] = _.clone(array); } else { key = parseInt(key); } return key; } }, lookTypeRegisters: { creep: register.byRoom[id].creeps, energy: register.byRoom[id].energy, resource: register.byRoom[id].energy, source: register.byRoom[id].sources, mineral: register.byRoom[id].minerals, structure: register.byRoom[id].structures, flag: register.byRoom[id].flags, constructionSite: register.byRoom[id].constructionSites, nuke: register.byRoom[id].nukes }, lookTypeSpatialRegisters: { creep: register.byRoom[id].spatial.creeps, energy: register.byRoom[id].spatial.energy, resource: register.byRoom[id].spatial.energy, source: register.byRoom[id].spatial.sources, mineral: register.byRoom[id].spatial.minerals, structure: register.byRoom[id].spatial.structures, flag: register.byRoom[id].spatial.flags, constructionSite: register.byRoom[id].spatial.constructionSites, nuke: register.byRoom[id].spatial.nukes } }; this.visual = new globals.RoomVisual(id); }); Room.serializePath = register.wrapFn(function(path) { return utils.serializePath(path); }); Room.deserializePath = register.wrapFn(function(str) { return utils.deserializePath(str); }); Room.prototype.toString = register.wrapFn(function() { return `[room ${this.name}]`; }); Room.prototype.toJSON = register.wrapFn(function() { var result = {}; for(var i in this) { if(i[0] == '_' || _.contains(['toJSON','toString','controller','storage','terminal'],i)) { continue; } result[i] = this[i]; } return result; }); Object.defineProperty(Room.prototype, 'memory', { get: function() { if(_.isUndefined(globals.Memory.rooms) || globals.Memory.rooms === 'undefined') { globals.Memory.rooms = {}; } if(!_.isObject(globals.Memory.rooms)) { return undefined; } return globals.Memory.rooms[this.name] = globals.Memory.rooms[this.name] || {}; }, set: function(value) { if(_.isUndefined(globals.Memory.rooms) || globals.Memory.rooms === 'undefined') { globals.Memory.rooms = {}; } if(!_.isObject(globals.Memory.rooms)) { throw new Error('Could not set room memory'); } globals.Memory.rooms[this.name] = value; } }); Room.prototype.find = register.wrapFn(function(type, opts) { var result = []; opts = opts || {}; if(type === C.FIND_DROPPED_ENERGY) { register.deprecated('FIND_DROPPED_ENERGY constant is considered deprecated and will be removed soon. Please use FIND_DROPPED_RESOURCES instead.'); type = C.FIND_DROPPED_RESOURCES; } if(register.findCache[type] && register.findCache[type][this.name]) { result = register.findCache[type][this.name]; } else { switch (type) { case C.FIND_EXIT: register.findCache[type] = register.findCache[type] || {}; register.findCache[type][this.name] = this.find(C.FIND_EXIT_TOP, opts) .concat(this.find(C.FIND_EXIT_BOTTOM, opts)) .concat(this.find(C.FIND_EXIT_RIGHT, opts)) .concat(this.find(C.FIND_EXIT_LEFT, opts)); return _.clone(register.findCache[type][this.name]); case C.FIND_EXIT_TOP: case C.FIND_EXIT_RIGHT: case C.FIND_EXIT_BOTTOM: case C.FIND_EXIT_LEFT: register.findCache[type] = register.findCache[type] || {}; var exits = []; for (var i = 0; i < 50; i++) { var x=0,y=0; if(type == C.FIND_EXIT_LEFT || type == C.FIND_EXIT_RIGHT) { y = i; } else { x = i; } if(type == C.FIND_EXIT_RIGHT) { x = 49; } if(type == C.FIND_EXIT_BOTTOM) { y = 49; } exits.push(!(runtimeData.staticTerrainData[this.name][y*50+x] & C.TERRAIN_MASK_WALL)); } result = _.reduce(exits, (accum, i, key) => { if (i) { if (type == C.FIND_EXIT_TOP) { accum.push(this.getPositionAt(key, 0)); } if (type == C.FIND_EXIT_BOTTOM) { accum.push(this.getPositionAt(key, 49)); } if (type == C.FIND_EXIT_LEFT) { accum.push(this.getPositionAt(0, key)); } if (type == C.FIND_EXIT_RIGHT) { accum.push(this.getPositionAt(49, key)); } } return accum; }, []); register.findCache[type][this.name] = result; break; } } if(opts.filter) { result = _.filter(result, opts.filter); } else { result = _.clone(result); } return result; }); function _lookSpatialRegister (id, typeName, x,y, outArray, withCoords) { var item; if(typeName == 'terrain') { var result = 'plain'; var terrainCode = runtimeData.staticTerrainData[id][y*50+x]; if(terrainCode & C.TERRAIN_MASK_SWAMP) { result = 'swamp'; } if(terrainCode & C.TERRAIN_MASK_WALL) { result = 'wall'; } if(outArray) { item = {type: 'terrain', terrain: result}; if(withCoords) { item.x = x; item.y = y; } outArray.push(item); return; } return [result]; } if(x < 0 || y < 0 || x > 49 || y > 49) { throw new Error('look coords are out of bounds'); } var typeResult = privateStore[id].lookTypeSpatialRegisters[typeName][y][x]; if(typeResult) { if(outArray) { typeResult.forEach((i) => { item = {type: typeName}; item[typeName] = i; if(withCoords) { item.x = x; item.y = y; } outArray.push(item); }); return; } return _.clone(typeResult); } return []; } function _lookAreaMixedRegister(id, type, top, left, bottom, right, withType, asArray, result) { var typeRegister = privateStore[id].lookTypeRegisters[type], keys = typeRegister && Object.keys(typeRegister); if(type != 'terrain' && keys.length < (bottom-top+1)*(right-left+1)) { // by objects var checkInside = (i) => { return (!i.pos && i.roomName == id || i.pos && i.pos.roomName == id) && i.pos && i.pos.y >= top && i.pos.y <= bottom && i.pos.x >= left && i.pos.x <= right || !i.pos && i.y >= top && i.y <= bottom && i.x >= left && i.x <= right; }; var item; keys.forEach((key) => { var obj = typeRegister[key]; if(checkInside(obj)) { if(withType) { item = {type: type}; item[type] = obj; if(asArray) { result.push({x: obj.x || obj.pos.x, y: obj.y || obj.pos.y, type, [type]: obj}); } else { result[obj.y || obj.pos.y][obj.x || obj.pos.x].push(item); } } else { if(asArray) { result.push({x: obj.x || obj.pos.x, y: obj.y || obj.pos.y, [type]: obj}); } else { result[obj.y || obj.pos.y][obj.x || obj.pos.x] = result[obj.y || obj.pos.y][obj.x || obj.pos.x] || []; result[obj.y || obj.pos.y][obj.x || obj.pos.x].push(obj); } } } }); } else { // spatial for (var y = top; y <= bottom; y++) { for (var x = left; x <= right; x++) { if(asArray) { _lookSpatialRegister(id, type, x, y, result, true); } else { if (result[y][x]) { _lookSpatialRegister(id, type, x, y, result[y][x]); } else { result[y][x] = _lookSpatialRegister(id, type, x, y, undefined); } } } } } } Room.prototype.lookAt = register.wrapFn(function(firstArg, secondArg) { var [x,y] = utils.fetchXYArguments(firstArg, secondArg, globals), result = []; _lookSpatialRegister(this.name, C.LOOK_CREEPS, x,y, result); _lookSpatialRegister(this.name, C.LOOK_ENERGY, x,y, result); _lookSpatialRegister(this.name, C.LOOK_RESOURCES, x,y, result); _lookSpatialRegister(this.name, C.LOOK_SOURCES, x,y, result); _lookSpatialRegister(this.name, C.LOOK_MINERALS, x,y, result); _lookSpatialRegister(this.name, C.LOOK_STRUCTURES, x,y, result); _lookSpatialRegister(this.name, C.LOOK_FLAGS, x,y, result); _lookSpatialRegister(this.name, C.LOOK_CONSTRUCTION_SITES, x,y, result); _lookSpatialRegister(this.name, C.LOOK_TERRAIN, x,y, result); _lookSpatialRegister(this.name, C.LOOK_NUKES, x,y, result); return result; }); Room.prototype.lookForAt = register.wrapFn(function(type, firstArg, secondArg) { var [x,y] = utils.fetchXYArguments(firstArg, secondArg, globals); if(type != 'terrain' && !(type in privateStore[this.name].lookTypeSpatialRegisters)) { return C.ERR_INVALID_ARGS; } return _lookSpatialRegister(this.name, type, x,y); }); Room.prototype.lookAtArea = register.wrapFn(function(top, left, bottom, right, asArray) { var result = asArray ? [] : {}; if(!asArray) { for (var y = top; y <= bottom; y++) { result[y] = {}; for (var x = left; x <= right; x++) { result[y][x] = []; } } } _lookAreaMixedRegister(this.name, C.LOOK_CREEPS, top, left, bottom, right, true, asArray, result); _lookAreaMixedRegister(this.name, C.LOOK_ENERGY, top, left, bottom, right, true, asArray, result); _lookAreaMixedRegister(this.name, C.LOOK_RESOURCES, top, left, bottom, right, true, asArray, result); _lookAreaMixedRegister(this.name, C.LOOK_SOURCES, top, left, bottom, right, true, asArray, result); _lookAreaMixedRegister(this.name, C.LOOK_MINERALS, top, left, bottom, right, true, asArray, result); _lookAreaMixedRegister(this.name, C.LOOK_STRUCTURES, top, left, bottom, right, true, asArray, result); _lookAreaMixedRegister(this.name, C.LOOK_FLAGS, top, left, bottom, right, true, asArray, result); _lookAreaMixedRegister(this.name, C.LOOK_CONSTRUCTION_SITES, top, left, bottom, right, true, asArray, result); _lookAreaMixedRegister(this.name, C.LOOK_TERRAIN, top, left, bottom, right, true, asArray, result); _lookAreaMixedRegister(this.name, C.LOOK_NUKES, top, left, bottom, right, true, asArray, result); return result; }); Room.prototype.lookForAtArea = register.wrapFn(function(type, top, left, bottom, right, asArray) { var result = asArray ? [] : {}; if(!asArray) { for (var y = top; y <= bottom; y++) { result[y] = {}; } } _lookAreaMixedRegister(this.name, type, top, left, bottom, right, false, asArray, result); return result; }); Room.prototype.findPath = register.wrapFn(function(fromPos, toPos, opts) { if(fromPos.roomName != this.name) { return opts.serialize ? '' : []; } if(register._useNewPathFinder) { return _findPath2(this.name, fromPos, toPos, opts); } var fromX = fromPos.x, fromY = fromPos.y, path, cacheKeySuffix = ''; opts = _.clone(opts || {}); if(opts.ignoreCreeps) { cacheKeySuffix += '_ignoreCreeps' } if(opts.ignoreDestructibleStructures) { cacheKeySuffix += '_ignoreDestructibleStructures' } if(opts.avoid) { cacheKeySuffix += '_avoid'+privateStore[this.name].positionsSetCache.key(opts.avoid); } if(opts.ignore) { cacheKeySuffix += '_ignore'+privateStore[this.name].positionsSetCache.key(opts.ignore); } if(_.isNumber(toPos)) { if(!privateStore[this.name].pfEndNodes[toPos]) { return opts.serialize ? '' : []; } var grid = getPathfindingGrid(this.name, opts, toPos); path = privateStore[this.name].pfDijkstraFinder.findPath(fromX, fromY, -999, -999, grid); } else { if(toPos.roomName != this.name) { return opts.serialize ? '' : []; } var toX = toPos.x, toY = toPos.y, cacheKey = `${fromX},${fromY},${toX},${toY}${cacheKeySuffix}`; if(privateStore[this.name].pathCache[cacheKey]) { return opts.serialize ? utils.serializePath(privateStore[this.name].pathCache[cacheKey]) : _.cloneDeep(privateStore[this.name].pathCache[cacheKey]); } if (fromX == toX && fromY == toY) { return opts.serialize ? '' : []; } if (fromX < 0 || fromY < 0 || toX < 0 || toY < 0 || fromX >= 50 || fromY >= 50 || toX >= 50 || toY >= 50) { return opts.serialize ? '' : []; } if (abs(fromX - toX) < 2 && abs(fromY - toY) < 2) { var result = [{ x: toX, y: toY, dx: toX - fromX, dy: toY - fromY, direction: utils.getDirection(toX - fromX, toY - fromY) }]; return opts.serialize ? utils.serializePath(result) : result; } var grid = getPathfindingGrid(this.name, opts), finder = getPathfinder(this.name, opts); grid.setWalkableAt(toX, toY, true); path = finder.findPath(fromX, fromY, toX, toY, grid); } path.splice(0,1); var curX = fromX, curY = fromY; var resultPath = _.map(path, (step) => { var result = { x: step[0], y: step[1], dx: step[0] - curX, dy: step[1] - curY, direction: utils.getDirection(step[0] - curX, step[1] - curY) }; curX = result.x; curY = result.y; return result; }); if(resultPath.length > 0) { var lastStep = resultPath[resultPath.length-1], cacheKey = `${fromX},${fromY},${lastStep.x},${lastStep.y}${cacheKeySuffix}`; privateStore[this.name].pathCache[cacheKey] = _.cloneDeep(resultPath); } if(opts.serialize) { return utils.serializePath(resultPath); } return resultPath; }); Room.prototype.getPositionAt = register.wrapFn(function(x,y) { if(x < 0 || x > 49 || y < 0 || y > 49) { return null; } return new globals.RoomPosition(x,y,this.name); }); Room.prototype.createFlag = register.wrapFn(function(firstArg, secondArg, name, color, secondaryColor) { var [x,y] = utils.fetchXYArguments(firstArg, secondArg, globals); if(_.isUndefined(x) || _.isUndefined(y) || x < 0 || x > 49 || y < 0 || y > 49) { return C.ERR_INVALID_ARGS; } if(_.size(globals.Game.flags) >= C.FLAGS_LIMIT) { return C.ERR_FULL; } if(_.isObject(firstArg)) { secondaryColor = color; color = name; name = secondArg; } if(!color) { color = C.COLOR_WHITE; } if(!secondaryColor) { secondaryColor = color; } if(!_.contains(C.COLORS_ALL, color)) { return C.ERR_INVALID_ARGS; } if(!_.contains(C.COLORS_ALL, secondaryColor)) { return C.ERR_INVALID_ARGS; } if(!name) { var cnt = 1; do { name = 'Flag'+cnt; cnt++; } while(_.any(register.flags, {name}) || createdFlagNames.indexOf(name) != -1); } if(_.any(register.flags, {name}) || createdFlagNames.indexOf(name) != -1) { return C.ERR_NAME_EXISTS; } if(name.length > 60) { return C.ERR_INVALID_ARGS; } createdFlagNames.push(name); globals.Game.flags[name] = new globals.Flag(name, color, secondaryColor, this.name, x, y); intents.pushByName('room', 'createFlag', {roomName: this.name, x, y, name, color, secondaryColor}); return name; }); Room.prototype.createConstructionSite = register.wrapFn(function(firstArg, secondArg, structureType) { var [x,y] = utils.fetchXYArguments(firstArg, secondArg, globals); if(_.isUndefined(x) || _.isUndefined(y) || x < 0 || x > 49 || y < 0 || y > 49) { return C.ERR_INVALID_ARGS; } if(_.isString(secondArg) && _.isUndefined(structureType)) { structureType = secondArg; } if(!C.CONSTRUCTION_COST[structureType]) { return C.ERR_INVALID_ARGS; } if(this.controller && this.controller.level > 0 && !this.controller.my) { return C.ERR_RCL_NOT_ENOUGH; } if(!utils.checkControllerAvailability(structureType, register.objectsByRoom[this.name], this.controller)) { return C.ERR_RCL_NOT_ENOUGH; } if(!utils.checkConstructionSite(register.objectsByRoom[this.name], structureType, x, y) || !utils.checkConstructionSite(runtimeData.staticTerrainData[this.name], structureType, x, y)) { return C.ERR_INVALID_TARGET; } if(_(runtimeData.userObjects).filter({type: 'constructionSite'}).size() + createdConstructionSites >= C.MAX_CONSTRUCTION_SITES) { return C.ERR_FULL; } var intent = {roomName: this.name, x, y, structureType}; if(structureType == 'spawn') { var cnt = 1, name; do { name = "Spawn" + cnt; cnt++; } while (_.any(register.spawns, {name}) || _.any(register.constructionSites, {structureType: 'spawn', name}) || createdSpawnNames.indexOf(name) != -1); createdSpawnNames.push(name); intent.name = name; } createdConstructionSites++; intents.pushByName('room', 'createConstructionSite', intent); return C.OK; }); Room.prototype.getEndNodes = register.wrapFn(function(type, opts) { var key; opts = opts || {}; if(_.isUndefined(type)) { throw new Error('Find type cannot be undefined'); } if(!opts.filter && _.isNumber(type)) { key = type; } else { if(_.isNumber(type)) { type = this.find(type, opts); } key = privateStore[this.name].positionsSetCache.key(type); privateStore[this.name].pfEndNodes[key] = privateStore[this.name].positionsSetCache.cache[key]; } if(!privateStore[this.name].pfEndNodes[key]) { privateStore[this.name].pfEndNodes[key] = _.clone(type); if (_.isNumber(type)) { privateStore[this.name].pfEndNodes[key] = this.find(type, opts); } } return {key, objects: privateStore[this.name].pfEndNodes[key]}; }); Room.prototype.findExitTo = register.wrapFn(function(room) { return register.map.findExit(this.name, room); }); globals.Room = Room; /** * RoomVisual * @param id * @returns {object} * @constructor */ var RoomVisual = register.wrapFn(function(roomName) { this.roomName = roomName; }); RoomVisual.prototype.circle = register.wrapFn(function(x,y,style) { if(typeof x == 'object') { style = y; y = x.y; x = x.x; } globals.console.addVisual(this.roomName, {t: 'c', x,y,s:style}); return this; }); RoomVisual.prototype.line = register.wrapFn(function(x1,y1,x2,y2,style) { if(typeof x1 == 'object' && typeof y1 == 'object') { style = x2; x2 = y1.x; y2 = y1.y; y1 = x1.y; x1 = x1.x; } globals.console.addVisual(this.roomName, {t: 'l', x1,y1,x2,y2,s:style}); return this; }); RoomVisual.prototype.rect = register.wrapFn(function(x,y,w,h,style) { if(typeof x == 'object') { style = h; h = w; w = y; y = x.y; x = x.x; } globals.console.addVisual(this.roomName, {t: 'r', x,y,w,h,s:style}); return this; }); RoomVisual.prototype.poly = register.wrapFn(function(points,style) { points = points.map(i => i.x !== undefined ? [i.x, i.y] : i); globals.console.addVisual(this.roomName, {t: 'p', points,s:style}); return this; }); RoomVisual.prototype.text = register.wrapFn(function(text,x,y,style) { if(typeof x == 'object') { style = y; y = x.y; x = x.x; } globals.console.addVisual(this.roomName, {t: 't', text,x,y,s:style}); return this; }); RoomVisual.prototype.getSize = register.wrapFn(function() { return globals.console.getVisualSize(this.roomName); }); RoomVisual.prototype.clear = register.wrapFn(function() { globals.console.clearVisual(this.roomName); return this; }); globals.RoomVisual = RoomVisual; }; exports.makePos = function(_register) { register = _register; if(globals.RoomPosition) { return; } /** * RoomPosition * @param x * @param y * @param roomName * @constructor */ var RoomPosition = register.wrapFn(function(x, y, roomName) { x = +x; y = +y; if(_.isNaN(x) || _.isNaN(y) || !_.isString(roomName)) { throw new Error('invalid arguments in RoomPosition constructor'); } this.x = x; this.y = y; this.roomName = roomName; }); RoomPosition.prototype.toString = register.wrapFn(function() { return `[room ${this.roomName} pos ${this.x},${this.y}]`; }); RoomPosition.prototype.inRangeTo = register.wrapFn(function(firstArg, secondArg, thirdArg) { var x = firstArg, y = secondArg, range = thirdArg, roomName = this.roomName; if(_.isUndefined(thirdArg)) { var pos = firstArg; if(pos.pos) { pos = pos.pos; } x = pos.x; y = pos.y; roomName = pos.roomName; range = secondArg; } return abs(x - this.x) <= range && abs(y - this.y) <= range && roomName == this.roomName; }); RoomPosition.prototype.isNearTo = register.wrapFn(function(firstArg, secondArg) { var [x,y,roomName] = utils.fetchXYArguments(firstArg, secondArg, globals); return abs(x - this.x) <= 1 && abs(y - this.y) <= 1 && (!roomName || roomName == this.roomName); }); RoomPosition.prototype.getDirectionTo = register.wrapFn(function(firstArg, secondArg) { var [x,y,roomName] = utils.fetchXYArguments(firstArg, secondArg, globals); if(!roomName || roomName == this.roomName) { return utils.getDirection(x - this.x, y - this.y); } var [thisRoomX, thisRoomY] = utils.roomNameToXY(this.roomName); var [thatRoomX, thatRoomY] = utils.roomNameToXY(roomName); return utils.getDirection(thatRoomX*50 + x - thisRoomX*50 - this.x, thatRoomY*50 + y - thisRoomY*50 - this.y); }); RoomPosition.prototype.findPathTo = register.wrapFn(function(firstArg, secondArg, opts) { var [x,y,roomName] = utils.fetchXYArguments(firstArg, secondArg, globals), room = register.rooms[this.roomName]; if(_.isObject(secondArg)) { opts = _.clone(secondArg); } opts = opts || {}; roomName = roomName || this.roomName; if(!room) { throw new Error(`Could not access room ${this.roomName}`); } if(roomName == this.roomName || register._useNewPathFinder) { return room.findPath(this, new globals.RoomPosition(x,y,roomName), opts); } else { var exitDir = room.findExitTo(roomName); if(exitDir < 0) { return []; } var exit = this.findClosestByPath(exitDir, opts); if(!exit) { return []; } return room.findPath(this, exit, opts); } }); RoomPosition.prototype.findClosestByPath = register.wrapFn(function(type, opts) { opts = _.clone(opts || {}); var room = register.rooms[this.roomName]; if(!room) { throw new Error(`Could not access room ${this.roomName}`); } if(_.isUndefined(type)) { return null; } if(register._useNewPathFinder) { return _findClosestByPath2(this, type, opts); } opts.serialize = false; var result = null, isNear, endNodes = room.getEndNodes(type, opts); if(!opts.algorithm) { var minH, sumH = 0; endNodes.objects.forEach((i) => { var x = i.x, y = i.y, roomName = i.roomName; if(i.pos) { x = i.pos.x; y = i.pos.y; roomName = i.pos.roomName; } var h = max(abs(this.x - x), abs(this.y - y)); if(_.isUndefined(minH) || minH > h) { minH = h; } sumH += h; }); opts.algorithm = sumH > minH*10 ? 'dijkstra' : 'astar'; } if(opts.algorithm == 'dijkstra') { isNear = 1; endNodes.objects.forEach((i) => { var distance = this.isEqualTo(i) ? -1 : this.isNearTo(i) ? 0 : 1; if(distance < isNear) { result = i; isNear = distance; } }); if(isNear == 1) { var path = room.findPath(this, endNodes.key, opts); if(path.length > 0) { var lastStep = path[path.length-1], lastStepPos = room.getPositionAt(lastStep.x, lastStep.y); result = _.find(endNodes.objects, (i) => lastStepPos.isEqualTo(i)); } } } if(opts.algorithm == 'astar') { endNodes.objects.forEach((i) => { var path, distance = this.isEqualTo(i) ? -1 : this.isNearTo(i) ? 0 : (path = this.findPathTo(i, opts)) && path.length > 0 && room.getPositionAt(path[path.length - 1].x, path[path.length - 1].y).isNearTo(i) ? path.length : undefined; if ((_.isUndefined(isNear) || distance <= isNear) && !_.isUndefined(distance)) { isNear = distance; result = i; } }); } return result; }); RoomPosition.prototype.findInRange = register.wrapFn(function(type, range, opts) { var room = register.rooms[this.roomName]; if(!room) { throw new Error(`Could not access room ${this.roomName}`); } opts = _.clone(opts || {}); var objects = [], result = []; if(_.isNumber(type)) { objects = room.find(type, opts); } if(_.isArray(type)) { objects = opts.filter ? _.filter(type, opts.filter) : type; } objects.forEach((i) => { if(this.inRangeTo(i, range)) { result.push(i); } }); return result; }); RoomPosition.prototype.findClosestByRange = register.wrapFn(function(type, opts) { var room = register.rooms[this.roomName]; if(!room) { throw new Error(`Could not access room ${this.roomName}`); } opts = _.clone(opts || {}); var objects = [], result = []; if(_.isNumber(type)) { objects = room.find(type, opts); } if(_.isArray(type)) { objects = opts.filter ? _.filter(type, opts.filter) : type; } var closest = null, minRange = Infinity; objects.forEach((i) => { var range = this.getRangeTo(i); if(range < minRange) { minRange = range; closest = i; } }); return closest; }); RoomPosition.prototype.isEqualTo = register.wrapFn(function(firstArg, secondArg) { var [x,y,roomName] = utils.fetchXYArguments(firstArg, secondArg, globals); return x == this.x && y == this.y && (!roomName || roomName == this.roomName); }); RoomPosition.prototype.getRangeTo = register.wrapFn(function(firstArg, secondArg) { var [x,y,roomName] = utils.fetchXYArguments(firstArg, secondArg, globals); if(roomName && roomName != this.roomName) { return Infinity; } return max(abs(this.x - x), abs(this.y - y)); }); RoomPosition.prototype.look = register.wrapFn(function() { var room = register.rooms[this.roomName]; if(!room) { throw new Error(`Could not access room ${this.roomName}`); } return room.lookAt(this); }); RoomPosition.prototype.lookFor = register.wrapFn(function(type) { if(type == 'terrain') { return [register.map.getTerrainAt(this)]; } var room = register.rooms[this.roomName]; if(!room) { throw new Error(`Could not access room ${this.roomName}`); } return room.lookForAt(type, this); }); RoomPosition.prototype.createFlag = register.wrapFn(function(name, color, secondaryColor) { var room = register.rooms[this.roomName]; if(!room) { throw new Error(`Could not access room ${this.roomName}`); } return room.createFlag(this, name, color, secondaryColor); }); RoomPosition.prototype.createConstructionSite = register.wrapFn(function(structureType) { var room = register.rooms[this.roomName]; if(!room) { throw new Error(`Could not access room ${this.roomName}`); } return room.createConstructionSite(this, structureType); }); globals.RoomPosition = RoomPosition; /** * RoomObject * @param room * @param x * @param y * @constructor */ var RoomObject = register.wrapFn(function(x, y, room) { this.room = register.rooms[room]; this.pos = new globals.RoomPosition(x,y,room); }); globals.RoomObject = RoomObject; };