/**
 * To start using Traveler, require it in main.js:
 * Example: var Traveler = require('Traveler.js');
 */ export class Traveler { private static structureMatrixCache : { [ roomName ] : CostMatrix ; } = { } ; private static creepMatrixCache : { [ roomName ] : CostMatrix ; } = { } ; private static creepMatrixTick : number ; private static structureMatrixTick : number ; param creep param destination param options returns { number } public static travelTo ( creep , destination , options = { } ) { if ( ! destination ) { return ERR_INVALID_ARGS ; } if ( creep . fatigue > 0 ) { Traveler . circle ( creep . pos , "aqua" , .3 ) ; return ERR_TIRED ; } destination = this . normalizePos ( destination ) ; let rangeToDestination = creep . pos . getRangeTo ( destination ) ; if ( options . range && rangeToDestination <= options . range ) { return OK ; } else if ( rangeToDestination <= 1 ) { if ( rangeToDestination === 1 && ! options . range ) { let direction = creep . pos . getDirectionTo ( destination ) ; if ( options . returnData ) { options . returnData . nextPos = destination ; options . returnData . path = direction . toString ( ) ; } return creep . move ( direction ) ; } return OK ; } if ( ! creep . memory . _trav ) { delete creep . memory . _travel ; creep . memory . _trav = { } ; } let travelData = creep . memory . _trav as TravelData ; let state = this . deserializeState ( travelData , destination ) ; if ( this . isStuck ( creep , state ) ) { state . stuckCount ++ ; Traveler . circle ( creep . pos , "magenta" , state . stuckCount * .2 ) ; } else { state . stuckCount = 0 ; } if ( ! options . stuckValue ) { options . stuckValue = DEFAULT_STUCK_VALUE ; } if ( state . stuckCount >= options . stuckValue && Math . random ( ) > .5 ) { options . ignoreCreeps = false ; options . freshMatrix = true ; delete travelData . path ; } if ( ! this . samePos ( state . destination , destination ) ) { if ( options . movingTarget && state . destination . isNearTo ( destination ) ) { travelData . path += state . destination . getDirectionTo ( destination ) ; state . destination = destination ; } else { delete travelData . path ; } } if ( options . repath && Math . random ( ) < options . repath ) { delete travelData . path ; } let newPath = false ; if ( ! travelData . path ) { newPath = true ; if ( creep . spawning ) { return ERR_BUSY ; } state . destination = destination ; let cpu = Game . cpu . getUsed ( ) ; let ret = this . findTravelPath ( creep . pos , destination , options ) ; let cpuUsed = Game . cpu . getUsed ( ) - cpu ; state . cpu = _ . round ( cpuUsed + state . cpu ) ; if ( state . cpu > REPORT_CPU_THRESHOLD ) { console . log ( `TRAVELER: heavy cpu use: ${ creep . name }, cpu: ${ state . cpu } origin: ${ creep . pos }, dest: ${ destination }` ) ; } let color = "orange" ; if ( ret . incomplete ) { color = "red" ; } if ( options . returnData ) { options . returnData . pathfinderReturn = ret ; } travelData . path = Traveler . serializePath ( creep . pos , ret . path , color ) ; state . stuckCount = 0 ; } this . serializeState ( creep , destination , state , travelData ) ; if ( ! travelData . path || travelData . path . length === 0 ) { return ERR_NO_PATH ; } if ( state . stuckCount === 0 && ! newPath ) { travelData . path = travelData . path . substr ( 1 ) ; } let nextDirection = parseInt ( travelData . path [ 0 ] , 10 ) ; if ( options . returnData ) { if ( nextDirection ) { let nextPos = Traveler . positionAtDirection ( creep . pos , nextDirection ) ; if ( nextPos ) { options . returnData . nextPos = nextPos ; } } options . returnData . state = state ; options . returnData . path = travelData . path ; } return creep . move ( nextDirection ) ; } param destination returns { any } public static normalizePos ( destination ) { if ( ! ( destination instanceof RoomPosition ) ) { return destination . pos ; } return destination ; } param roomName returns { RoomMemory | number } public static checkAvoid ( roomName ) { return Memory . rooms && Memory . rooms [ roomName ] && Memory . rooms [ roomName ] . avoid ; } param pos returns { boolean } public static isExit ( pos ) { return pos . x === 0 || pos . y === 0 || pos . x === 49 || pos . y === 49 ; } param pos1 param pos2 returns { boolean } public static sameCoord ( pos1 , pos2 ) { return pos1 . x === pos2 . x && pos1 . y === pos2 . y ; } param pos1 param pos2 returns { boolean } public static samePos ( pos1 , pos2 ) { return this . sameCoord ( pos1 , pos2 ) && pos1 . roomName === pos2 . roomName ; } param pos param color param opacity public static circle ( pos , color , opacity ? ) { new RoomVisual ( pos . roomName ) . circle ( pos , { radius : .45 , fill : "transparent" , stroke : color , strokeWidth : .15 , opacity : opacity } ) ; } param room public static updateRoomStatus ( room ) { if ( ! room ) { return ; } if ( room . controller ) { if ( room . controller . owner && ! room . controller . my ) { room . memory . avoid = 1 ; } else { delete room . memory . avoid ; } } } param origin param destination param options returns { PathfinderReturn } public static findTravelPath ( origin , destination , options = { } ) { _ . defaults ( options , { ignoreCreeps : true , maxOps : DEFAULT_MAXOPS , range : 1 , } ) ; if ( options . movingTarget ) { options . range = 0 ; } origin = this . normalizePos ( origin ) ; destination = this . normalizePos ( destination ) ; let originRoomName = origin . roomName ; let destRoomName = destination . roomName ; let roomDistance = Game . map . getRoomLinearDistance ( origin . roomName , destination . roomName ) ; let allowedRooms = options . route ; if ( ! allowedRooms && ( options . useFindRoute || ( options . useFindRoute === undefined && roomDistance > 2 ) ) ) { let route = this . findRoute ( origin . roomName , destination . roomName , options ) ; if ( route ) { allowedRooms = route ; } } let roomsSearched = 0 ; let callback = ( roomName ) : CostMatrix | boolean => { if ( allowedRooms ) { if ( ! allowedRooms [ roomName ] ) { return false ; } } else if ( ! options . allowHostile && Traveler . checkAvoid ( roomName ) && roomName !== destRoomName && roomName !== originRoomName ) { return false ; } roomsSearched ++ ; let matrix ; let room = Game . rooms [ roomName ] ; if ( room ) { if ( options . ignoreStructures ) { matrix = new PathFinder . CostMatrix ( ) ; if ( ! options . ignoreCreeps ) { Traveler . addCreepsToMatrix ( room , matrix ) ; } } else if ( options . ignoreCreeps || roomName !== originRoomName ) { matrix = this . getStructureMatrix ( room , options . freshMatrix ) ; } else { matrix = this . getCreepMatrix ( room ) ; } if ( options . obstacles ) { matrix = matrix . clone ( ) ; for ( let obstacle of options . obstacles ) { if ( obstacle . pos . roomName !== roomName ) { continue ; } matrix . set ( obstacle . pos . x , obstacle . pos . y , 0xff ) ; } } } if ( options . roomCallback ) { if ( ! matrix ) { matrix = new PathFinder . CostMatrix ( ) ; } let outcome = options . roomCallback ( roomName , matrix . clone ( ) ) ; if ( outcome !== undefined ) { return outcome ; } } return matrix as CostMatrix ; } ; let ret = PathFinder . search ( origin , { pos : destination , range : options . range ! } , { maxOps : options . maxOps , maxRooms : options . maxRooms , plainCost : options . offRoad ? 1 : options . ignoreRoads ? 1 : 2 , swampCost : options . offRoad ? 1 : options . ignoreRoads ? 5 : 10 , roomCallback : callback , } ) ; if ( ret . incomplete && options . ensurePath ) { if ( options . useFindRoute === undefined ) { if ( roomDistance <= 2 ) { console . log ( `TRAVELER: path failed without findroute, trying with options.useFindRoute = true` ) ; console . log ( `from: ${ origin }, destination: ${ destination }` ) ; options . useFindRoute = true ; ret = this . findTravelPath ( origin , destination , options ) ; console . log ( `TRAVELER: second attempt was ${ ret . incomplete ? "not " : "" }successful` ) ; return ret ; } } else { } } return ret ; } param origin param destination param options returns { { } } public static findRoute ( origin , destination , options = { } ) { let restrictDistance = options . restrictDistance || Game . map . getRoomLinearDistance ( origin , destination ) + 10 ; let allowedRooms = { [ origin ] : true , [ destination ] : true } ; let highwayBias = 1 ; if ( options . preferHighway ) { highwayBias = 2.5 ; if ( options . highwayBias ) { highwayBias = options . highwayBias ; } } let ret = Game . map . findRoute ( origin , destination , { routeCallback : ( roomName ) => { if ( options . routeCallback ) { let outcome = options . routeCallback ( roomName ) ; if ( outcome !== undefined ) { return outcome ; } } let rangeToRoom = Game . map . getRoomLinearDistance ( origin , roomName ) ; if ( rangeToRoom > restrictDistance ) { return Number . POSITIVE_INFINITY ; } if ( ! options . allowHostile && Traveler . checkAvoid ( roomName ) && roomName !== destination && roomName !== origin ) { return Number . POSITIVE_INFINITY ; } let parsed ; if ( options . preferHighway ) { parsed = /^[WE]([0-9]+)[NS]([0-9]+)$/ . exec ( roomName ) as any ; let isHighway = ( parsed [ 1 ] % 10 === 0 ) || ( parsed [ 2 ] % 10 === 0 ) ; if ( isHighway ) { return 1 ; } } if ( ! options . allowSK && ! Game . rooms [ roomName ] ) { if ( ! parsed ) { parsed = /^[WE]([0-9]+)[NS]([0-9]+)$/ . exec ( roomName ) as any ; } let fMod = parsed [ 1 ] % 10 ; let sMod = parsed [ 2 ] % 10 ; let isSK = ! ( fMod === 5 && sMod === 5 ) && ( ( fMod >= 4 ) && ( fMod <= 6 ) ) && ( ( sMod >= 4 ) && ( sMod <= 6 ) ) ; if ( isSK ) { return 10 * highwayBias ; } } return highwayBias ; } , } ) ; if ( ! _ . isArray ( ret ) ) { console . log ( `couldn't findRoute to ${ destination }` ) ; return ; } for ( let value of ret ) { allowedRooms [ value . room ] = true ; } return allowedRooms ; } param origin param destination returns { number } public static routeDistance ( origin , destination ) { let linearDistance = Game . map . getRoomLinearDistance ( origin , destination ) ; if ( linearDistance >= 32 ) { return linearDistance ; } let allowedRooms = this . findRoute ( origin , destination ) ; if ( allowedRooms ) { return Object . keys ( allowedRooms ) . length ; } } param room param freshMatrix returns { any } public static getStructureMatrix ( room , freshMatrix ? ) { if ( ! this . structureMatrixCache [ room . name ] || ( freshMatrix && Game . time !== this . structureMatrixTick ) ) { this . structureMatrixTick = Game . time ; let matrix = new PathFinder . CostMatrix ( ) ; this . structureMatrixCache [ room . name ] = Traveler . addStructuresToMatrix ( room , matrix , 1 ) ; } return this . structureMatrixCache [ room . name ] ; } param room returns { any } public static getCreepMatrix ( room ) { if ( ! this . creepMatrixCache [ room . name ] || Game . time !== this . creepMatrixTick ) { this . creepMatrixTick = Game . time ; this . creepMatrixCache [ room . name ] = Traveler . addCreepsToMatrix ( room , this . getStructureMatrix ( room , true ) . clone ( ) ) ; } return this . creepMatrixCache [ room . name ] ; } param room param matrix param roadCost returns { CostMatrix } public static addStructuresToMatrix ( room , matrix , roadCost ) { let impassibleStructures = [ ] ; for ( let structure of room . find < Structure > ( FIND_STRUCTURES ) ) { if ( structure instanceof StructureRampart ) { if ( ! structure . my && ! structure . isPublic ) { impassibleStructures . push ( structure ) ; } } else if ( structure instanceof StructureRoad ) { matrix . set ( structure . pos . x , structure . pos . y , roadCost ) ; } else if ( structure instanceof StructureContainer ) { matrix . set ( structure . pos . x , structure . pos . y , 5 ) ; } else { impassibleStructures . push ( structure ) ; } } for ( let site of room . find < ConstructionSite > ( FIND_MY_CONSTRUCTION_SITES ) ) { if ( site . structureType === STRUCTURE_CONTAINER || site . structureType === STRUCTURE_ROAD || site . structureType === STRUCTURE_RAMPART ) { continue ; } matrix . set ( site . pos . x , site . pos . y , 0xff ) ; } for ( let structure of impassibleStructures ) { matrix . set ( structure . pos . x , structure . pos . y , 0xff ) ; } return matrix ; } param room param matrix returns { CostMatrix } public static addCreepsToMatrix ( room , matrix ) { room . find < Creep > ( FIND_CREEPS ) . forEach ( ( creep ) => matrix . set ( creep . pos . x , creep . pos . y , 0xff ) ) ; return matrix ; } param startPos param path param color returns { string } public static serializePath ( startPos , path , color = "orange" ) { let serializedPath = "" ; let lastPosition = startPos ; this . circle ( startPos , color ) ; for ( let position of path ) { if ( position . roomName === lastPosition . roomName ) { new RoomVisual ( position . roomName ) . line ( position , lastPosition , { color : color , lineStyle : "dashed" } ) ; serializedPath += lastPosition . getDirectionTo ( position ) ; } lastPosition = position ; } return serializedPath ; } param origin param direction returns { RoomPosition } public static positionAtDirection ( origin , direction ) { let offsetX = [ 0 , 0 , 1 , 1 , 1 , 0 , - 1 , - 1 , - 1 ] ; let offsetY = [ 0 , - 1 , - 1 , 0 , 1 , 1 , 1 , 0 , - 1 ] ; let x = origin . x + offsetX [ direction ] ; let y = origin . y + offsetY [ direction ] ; if ( x > 49 || x < 0 || y > 49 || y < 0 ) { return ; } return new RoomPosition ( x , y , origin . roomName ) ; } param cleanup public static patchMemory ( cleanup = false ) { if ( ! Memory . empire ) { return ; } if ( ! Memory . empire . hostileRooms ) { return ; } let count = 0 ; for ( let roomName in Memory . empire . hostileRooms ) { if ( Memory . empire . hostileRooms [ roomName ] ) { if ( ! Memory . rooms [ roomName ] ) { Memory . rooms [ roomName ] = { } as any ; } Memory . rooms [ roomName ] . avoid = 1 ; count ++ ; } if ( cleanup ) { delete Memory . empire . hostileRooms [ roomName ] ; } } if ( cleanup ) { delete Memory . empire . hostileRooms ; } console . log ( `TRAVELER: room avoidance data patched for ${ count } rooms` ) ; } private static deserializeState ( travelData , destination ) { let state = { } as TravelState ; if ( travelData . state ) { state . lastCoord = { x : travelData . state [ STATE_PREV_X ] , y : travelData . state [ STATE_PREV_Y ] } ; state . cpu = travelData . state [ STATE_CPU ] ; state . stuckCount = travelData . state [ STATE_STUCK ] ; state . destination = new RoomPosition ( travelData . state [ STATE_DEST_X ] , travelData . state [ STATE_DEST_Y ] , travelData . state [ STATE_DEST_ROOMNAME ] ) ; } else { state . cpu = 0 ; state . destination = destination ; } return state ; } private static serializeState ( creep , destination , state , travelData ) { travelData . state = [ creep . pos . x , creep . pos . y , state . stuckCount , state . cpu , destination . x , destination . y , destination . roomName ] ; } private static isStuck ( creep , state ) { let stuck = false ; if ( state . lastCoord !== undefined ) { if ( this . sameCoord ( creep . pos , state . lastCoord ) ) { stuck = true ; } else if ( this . isExit ( creep . pos ) && this . isExit ( state . lastCoord ) ) { stuck = true ; } } return stuck ; } } const REPORT_CPU_THRESHOLD = 1000 ; const DEFAULT_MAXOPS = 20000 ; const DEFAULT_STUCK_VALUE = 2 ; const STATE_PREV_X = 0 ; const STATE_PREV_Y = 1 ; const STATE_STUCK = 2 ; const STATE_CPU = 3 ; const STATE_DEST_X = 4 ; const STATE_DEST_Y = 5 ; const STATE_DEST_ROOMNAME = 6 ; Creep . prototype . travelTo = function ( destination , options ? ) { return Traveler . travelTo ( this , destination , options ) ; } ;