UNPKG

@screeps/engine

Version:

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

346 lines (279 loc) 10.3 kB
const _ = require('lodash'), utils = require('../../utils'), driver = utils.getDriver(), C = driver.constants; let terrains = {}; const RoomPosition = 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.isEqualTo = function(p) { return p.x == this.x && p.y == this.y && p.roomName == this.roomName; }; RoomPosition.prototype.getRangeTo = function(p) { return p.roomName == this.roomName ? utils.dist(p, this) : Infinity; }; RoomPosition.prototype.getDirectionTo = function(p) { if(p.roomName == this.roomName) { return utils.getDirection(p.x - this.x, p.y - this.y); } const [thisRoomX, thisRoomY] = utils.roomNameToXY(this.roomName); const [thatRoomX, thatRoomY] = utils.roomNameToXY(p.roomName); return utils.getDirection(thatRoomX*50 + p.x - thisRoomX*50 - this.x, thatRoomY*50 + p.y - thisRoomY*50 - this.y); }; RoomPosition.prototype.lookFor = function(type) { if(type != C.LOOK_TERRAIN) { return null; } if(!terrains[this.roomName]) { // disallow movement via unknown terrain return 'wall'; } const terrainStrings = ['plain', 'wall', 'swamp', 'wall']; return [terrainStrings[terrains[this.roomName][50*this.y+this.x]]]; }; const packLocal = function(x, y) { let uint32 = 0; uint32 <<= 6; uint32 |= x; uint32 <<= 6; uint32 |= y; return String.fromCharCode(32+uint32); }; RoomPosition.prototype.sPackLocal = function() { return packLocal(this.x, this.y); }; RoomPosition.sUnpackLocal = function(packed, roomName) { let uint32 = packed.codePointAt(0); if(uint32 < 32) { throw new Error(`Invalid uint value ${uint32}`) } uint32 -= 32; const y = uint32 & 0x3f; uint32 >>>= 6; const x = uint32 & 0x3f; uint32 >>>= 6; return new RoomPosition(x, y, roomName); }; const CostMatrix = function() { this._bits = new Uint8Array(2500); }; CostMatrix.prototype.set = function(xx, yy, val) { xx = xx|0; yy = yy|0; this._bits[xx * 50 + yy] = Math.min(Math.max(0, val), 255); }; CostMatrix.prototype.get = function(xx, yy) { xx = xx|0; yy = yy|0; return this._bits[xx * 50 + yy]; }; CostMatrix.prototype.clone = function() { const newMatrix = new CostMatrix; newMatrix._bits = new Uint8Array(this._bits); return newMatrix; }; function packPath(roomPositions) { return _.reduce(roomPositions, (path, position) => `${path}${position.sPackLocal()}`, ''); } const defaultCostMatrix = function defaultCostMatrix(roomId, opts, creep, roomObjects) { if(creep.room != roomId) { // disallow movement via unknown terrain return false; } const costs = new CostMatrix(); let obstacleTypes = _.clone(C.OBSTACLE_OBJECT_TYPES); obstacleTypes.push(C.STRUCTURE_PORTAL); if(opts.ignoreDestructibleStructures) { obstacleTypes = _.without(obstacleTypes, 'constructedWall','rampart','spawn','extension', 'link','storage','observer','tower','powerBank','powerSpawn','lab','terminal'); } if(opts.ignoreCreeps) { obstacleTypes = _.without(obstacleTypes, 'creep'); } _.forEach(roomObjects, function(object) { if( _.contains(obstacleTypes, object.type) || (!opts.ignoreDestructibleStructures && object.type == 'rampart' && !object.isPublic && object.user != creep.user) || (!opts.ignoreDestructibleStructures && object.type == 'constructionSite' && object.user == creep.user && _.contains(C.OBSTACLE_OBJECT_TYPES, object.structureType)) ) { costs.set(object.x, object.y, Infinity); } 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) < Infinity) { costs.set(object.x, object.y, 1); } }); return costs; }; const findPath = function findPath(source, target, opts, scope) { const {roomTerrain, roomObjects} = scope; terrains = {[source.room]: roomTerrain}; const roomCallback = function(roomName) { let costMatrix = defaultCostMatrix(roomName, opts, source, roomObjects); if(typeof opts.costCallback == 'function') { costMatrix = costMatrix.clone(); const resultMatrix = opts.costCallback(roomName, costMatrix); if(resultMatrix instanceof CostMatrix) { costMatrix = resultMatrix; } } return costMatrix; }; const searchOpts = _.clone(opts); searchOpts.maxRooms = 1; searchOpts.roomCallback = roomCallback; if(!searchOpts.ignoreRoads) { searchOpts.plainCost = 2; searchOpts.swampCost = 10; } const fromPos = new RoomPosition(source.x, source.y, source.room); const ret = driver.pathFinder.search( fromPos, target, searchOpts ); if(target instanceof RoomPosition && !opts.range && (ret.path.length && ret.path[ret.path.length-1].getRangeTo(target) === 1 || !ret.path.length && fromPos.getRangeTo(target) === 1)) { ret.path.push(target); } return ret; }; const flee = function flee(creep, hostiles, range, opts, scope) { const danger = hostiles.map(c => { return { pos: new RoomPosition(c.x, c.y, c.room), range: range }}); const result = findPath(creep, danger, {flee: true}, scope); if(!_.some(result.path)) { return 0; } const fleePosition = result.path[0]; return utils.getDirection(fleePosition.x - creep.x, fleePosition.y - creep.y); }; const moveTo = function moveTo(creep, target, opts, scope) { const {bulk, gameTime} = scope; opts = opts || {}; if(_.isUndefined(opts.reusePath)) { opts.reusePath = 5; } if(_.isUndefined(opts.range)) { opts.range = 0; } if(utils.dist(creep, target) <= opts.range) { return 0; } const targetPosition = new RoomPosition(target.x, target.y, target.room); if( !creep['memory_move'] || !creep['memory_move']['dest'] || !creep['memory_move']['time'] || (creep['memory_move']['dest'] != targetPosition.sPackLocal()) || (gameTime > (creep['memory_move']['time'] + opts.reusePath))) { const result = findPath( creep, { range: opts.range, pos: new RoomPosition(target.x, target.y, target.room) }, opts, scope ); if(!result.path) { return 0; } const memory_move = { dest: targetPosition.sPackLocal(), path: packPath(result.path), time: gameTime }; bulk.update(creep, {memory_move}); } const direction = nextDirectionByPath(creep, creep['memory_move']['path']); if(direction) { bulk.update(creep, { memory_move: { lastMove: gameTime}}); } return direction; }; const walkTo = function (creep, target, opts, context) { const { scope, intents } = context; const { gameTime, bulk, roomObjects } = scope; const direction = moveTo(creep, target, opts, scope); if(!direction) { return direction; } const offsets = utils.getOffsetsByDirection(direction); const creepAhead = _.find(roomObjects, {type: 'creep', user: creep.user, x: creep.x+offsets[0], y: creep.y+offsets[1]}); if(creepAhead && (!creepAhead['memory_move'] || (creepAhead['memory_move']['lastMove'] && (creepAhead['memory_move']['lastMove']+1<gameTime)))) { intents.set(creepAhead._id, 'move', {direction: utils.getDirection(creep.x-creepAhead.x, creep.y-creepAhead.y)}); bulk.update(creepAhead, { memory_move: { dest: null, time: null, path: null, lastMove: gameTime }}); } intents.set(creep._id, 'move', {direction}); }; const findClosestByPath = function findClosestByPath(fromPos, objects, opts, scope) { if(!_.some(objects)) { return null; } const {roomTerrain} = scope; terrains = {[fromPos.room]: roomTerrain}; opts = opts || {}; if(_.isUndefined(opts.range)) { opts.range = 0; } const objectHere = _.find(objects, obj => utils.dist(fromPos, obj)==0); if(objectHere) { return objectHere; } const goals = _.map(objects, i => { return {range: 1, pos: new RoomPosition(i.x, i.y, i.room)}; }); const ret = findPath( fromPos, goals, opts, scope ); if(!ret.path) { return null; } let result = null; let lastPos = fromPos; if(ret.path.length) { lastPos = ret.path[ret.path.length-1]; } objects.forEach(obj => { if(utils.dist(lastPos, obj) <= 1) { result = obj; } }); return result; }; const nextDirectionByPath = function(creep, path) { const currentPositionIndex = path.indexOf(packLocal(creep.x, creep.y)); if(currentPositionIndex == path.length - 1) { return 0; } let nextPosition = undefined; if(currentPositionIndex < 0) { const firstPosition = RoomPosition.sUnpackLocal(path[0], creep.room); if(utils.dist(creep, firstPosition) <= 1) { nextPosition = firstPosition; } } else { nextPosition = RoomPosition.sUnpackLocal(path[1+currentPositionIndex], creep.room); } if(!nextPosition) { return 0; } return utils.getDirection(nextPosition.x - creep.x, nextPosition.y - creep.y); }; const hasActiveBodyparts = function hasActiveBodyparts(creep, part) { return !!creep.body && _.some(creep.body, p => (p.hits > 0) && (p.type==part)); }; module.exports.findPath = findPath; module.exports.findClosestByPath = findClosestByPath; module.exports.moveTo = moveTo; module.exports.walkTo = walkTo; module.exports.flee = flee; module.exports.RoomPosition = RoomPosition; module.exports.CostMatrix = CostMatrix; module.exports.hasActiveBodyparts = hasActiveBodyparts;