// // Here it is. function find(start, options) { // Sanity check if (!(start instanceof WorldPosition)) { throw new Error('Must use WorldPosition in pathfinder'); } // Build a high-level plan of rooms to search let targets = options.targets ? options.targets : [ options.target ]; targetFlee = options.flee; targetRange = options.range; let maxCost = options.maxCost || 1000; if (!options.flee) { roomPlan = planPath(start, targets, parents, options); if (!roomPlan) { return; } } else { if (targets.length !== 1) { throw new Error('Can\'t flee or range from multiple targets'); } roomPlan = [ { targets: targets, position: start, roomName: start.getRoomName(), } ]; } // Initialize heap and open/close list heap = new Heap(roomPlan.length * 2500); openClosed = new OpenClosed(roomPlan.length * 2500); if (parents.length < roomPlan.length * 2500) { parents = new Uint16Array(roomPlan.length * 2500); } // Initialize cost functions currentCostContextId = -1; heuristicContextId = -1; avoidCreeps = Boolean(options.avoidCreeps); entityBits = ( ((options.ignoreCreeps || avoidCreeps) ? 0 : EntityData.FRIENDLY_CREEP | EntityData.HOSTILE_CREEP) | ((options.ignoreHostileStructures || options.destructive) ? 0 : EntityData.HOSTILE_STRUCTURE) ); if (options.weightRatio <= 0.2) { roadCost = 2; plainCost = 1; swampCost = 1; } else if (options.weightRatio > 1) { roadCost = 1; plainCost = 2; swampCost = 10; } else { roadCost = 2; plainCost = 1; swampCost = 5; } if (options.roadCost) { roadCost = options.roadCost; } if (options.swampCost) { swampCost = options.swampCost; } if (options.plainCost) { plainCost = options.plainCost; } terrainDataInstances = new Array(roomPlan.length); entityDataInstances = new Array(roomPlan.length); dangerDataInstances = new Array(roomPlan.length); destructionDataInstances = new Array(roomPlan.length); target = undefined; roomPlan.forEach(function(plan, ii) { let pos = plan.position || (plan.heuristic instanceof WorldLine ? plan.heuristic.pos : plan.heuristic); if (plan.targets) { if (plan.targets.length === 1) { target = plan.targets[0]; } else { target = plan.targets; } } let roomId = Math.floor(pos.xx / 50) * kWorldSize * 2 + Math.floor(pos.yy / 50); rooms[ii] = roomId; reverseRooms[roomId] = ii + 1; terrainDataInstances[ii] = TerrainData.factory(plan.roomName); if (options.safe) { dangerDataInstances[ii] = DangerData.factory(plan.roomName); } if (ii === roomPlan.length - 1) { // Don't bother with extra data if it's not the current room let room = Game.rooms[plan.roomName]; if (room) { if (entityBits) { entityDataInstances[ii] = EntityData.factory(room, options.ignoreTask); } if (options.destructive) { destructionDataInstances[ii] = DestructionData.factory(room); } } } }); // Other stuff var opsRemaining; if (options.maxOps) { opsRemaining = options.maxOps; } else { opsRemaining = Math.min(1000, heuristic(start.xx, start.yy) * 200); } opsRemaining = options.maxOps || 1000; var minNodeCost = Infinity; var minNode; heuristicWeight = Math.min(roadCost, plainCost, swampCost) * 1.2; // Make sure destination tiles are walkable. This also inializes `indexFromPos` via `look` let alteredData = []; !options.skipWalkable && !options.flee && target && (Array.isArray(target) ? target : [ target ]).forEach(function(target) { if (look(target.xx, target.yy) === Infinity) { let xx = target.xx % 50, yy = target.yy % 50; let tmp = terrainData.look(xx, yy); if (tmp === TerrainData.WALL) { alteredData.push([ terrainData, xx, yy, tmp ]); terrainData.poke(xx, yy, TerrainData.PLAIN); } if (entityData) { tmp = entityData.look(xx, yy); if (tmp & entityBits) { alteredData.push([ entityData, xx, yy, tmp ]); entityData.poke(xx, yy, 0); } } } }); // Mark unwalkable tiles if (options.avoid) { options.avoid.forEach(function(pos) { let tmp = look(pos.xx, pos.yy); alteredData.push([ terrainData, pos.xx % 50, pos.yy % 50, tmp ]); terrainData.poke(pos.xx % 50, pos.yy % 50, TerrainData.WALL); }); } // First iteration of A* done upfront w/ no JPS var index = indexFromPos(start); openClosed.close(index); // heap.add(index, heuristic(start.xx, start.yy)); (uncomment for basic A*) for (var dir = 1; dir <= 8; ++dir) { var neighbor = start.getPositionInDirection(dir); // Check for portal nodes on start if (start.xx % 50 === 49) { if (neighbor.xx % 50 === 0 && start.yy !== neighbor.yy) { continue; } } else if (start.xx % 50 === 0) { if (neighbor.xx % 50 === 49 && start.yy !== neighbor.yy) { continue; } } else if (start.yy % 50 === 49) { if (neighbor.yy % 50 === 0 && start.xx !== neighbor.xx) { continue; } } else if (start.yy % 50 === 0) { if (neighbor.yy % 50 === 49 && start.xx !== neighbor.xx) { continue; } } // Add all neighbors to heap var cost = look(neighbor.xx, neighbor.yy); if (cost === Infinity) { continue; } var neighborIndex = indexFromPos(neighbor); heap.add(neighborIndex, cost + heuristic(neighbor.xx, neighbor.yy)); openClosed.open(neighborIndex); parents[neighborIndex] = index; } // Begin JPS A* let lastRoomIndex = undefined; while (heap.length() && --opsRemaining) { ++global.pf; // Pull cheapest open node off the (c)heap var index = heap.min(); var fcost = heap.minCost(); if (fcost > maxCost) { break; } // Close this node heap.remove() openClosed.close(index); // Calculate costs var pos = posFromIndex(index); var hcost = heuristic(pos.xx, pos.yy); var gcost = fcost - hcost; var cost = look(pos.xx, pos.yy); // console.log(String(pos), 'g('+ gcost+ ') + h('+ hcost+ ') = f('+ fcost+ ')'); // New room? Throw away the heap let roomIndex = Math.floor(index / (50 * 50)); if (lastRoomIndex === undefined) { lastRoomIndex = roomIndex; } else if (lastRoomIndex > roomIndex) { heap.release(); heap = new Heap(roomPlan.length * 2500); lastRoomIndex = roomIndex; } // Reached destination? if (checkDestination(pos.xx, pos.yy, true)) { minNode = index; minNodeCost = 0; break; } else if (hcost < minNodeCost) { minNodeCost = hcost; minNode = index; } // Check for special room portal rules let borderNeighbors; let parent = posFromIndex(parents[index]); let dx = pos.xx > parent.xx ? 1 : (pos.xx < parent.xx ? -1 : 0); let dy = pos.yy > parent.yy ? 1 : (pos.yy < parent.yy ? -1 : 0); if (pos.xx % 50 === 0) { if (dx === -1) { borderNeighbors = [ new WorldPosition(pos.xx - 1, pos.yy) ]; } else if (dx === 1) { borderNeighbors = [ new WorldPosition(pos.xx + 1, pos.yy - 1), new WorldPosition(pos.xx + 1, pos.yy), new WorldPosition(pos.xx + 1, pos.yy + 1), ]; } } else if (pos.xx % 50 === 49) { if (dx === 1) { borderNeighbors = [ new WorldPosition(pos.xx + 1, pos.yy) ]; } else if (dx === -1) { borderNeighbors = [ new WorldPosition(pos.xx - 1, pos.yy - 1), new WorldPosition(pos.xx - 1, pos.yy), new WorldPosition(pos.xx - 1, pos.yy + 1), ]; } } else if (pos.yy % 50 === 0) { if (dy === -1) { borderNeighbors = [ new WorldPosition(pos.xx, pos.yy - 1) ]; } else if (dy === 1) { borderNeighbors = [ new WorldPosition(pos.xx - 1, pos.yy + 1), new WorldPosition(pos.xx, pos.yy + 1), new WorldPosition(pos.xx + 1, pos.yy + 1), ]; } } else if (pos.yy % 50 === 49) { if (dy === 1) { borderNeighbors = [ new WorldPosition(pos.xx, pos.yy + 1) ]; } else if (dy === -1) { borderNeighbors = [ new WorldPosition(pos.xx - 1, pos.yy - 1), new WorldPosition(pos.xx, pos.yy - 1), new WorldPosition(pos.xx + 1, pos.yy - 1), ]; } } if (borderNeighbors) { for (let neighbor of borderNeighbors) { let ncost = look(neighbor.xx, neighbor.yy); if (ncost === Infinity) { continue; } let neighborIndex = indexFromPos(neighbor); let isOpen = openClosed.get(neighborIndex); if (isOpen === false) { continue; } let fcost = gcost + ncost + heuristic(neighbor.xx, neighbor.yy); if (isOpen === true) { if (heap.costs[neighborIndex] > fcost) { heap.update(neighborIndex, fcost); parents[neighborIndex] = index; } } else { openClosed.open(neighborIndex); heap.add(neighborIndex, fcost); parents[neighborIndex] = index; } } continue; } // Regular search if (false) { // Basic A* for (var dir = 1; dir <= 8; ++dir) { var neighbor = pos.getPositionInDirection(dir); var acost = look(neighbor.xx, neighbor.yy); if (acost === Infinity) { continue; } var neighborIndex = indexFromPos(neighbor); let isOpen = openClosed.get(neighborIndex); if (isOpen === false) { continue; } let ncost = gcost + acost + heuristic(neighbor.xx, neighbor.yy); if (isOpen === true) { if (heap.costs[neighborIndex] > ncost) { heap.update(neighborIndex, ncost); parents[neighborIndex] = index; } } else { openClosed.open(neighborIndex); heap.add(neighborIndex, ncost); parents[neighborIndex] = index; } } } else { // JPS var parentGCost = fcost - heuristic(pos.xx, pos.yy); var tmp; if (dx !== 0) { if (dy !== 0) { tmp = look(pos.xx, pos.yy + dy); if (tmp !== Infinity) { jumpNeighbor(pos, index, pos.xx, pos.yy + dy, parentGCost, cost, tmp); } tmp = look(pos.xx + dx, pos.yy); if (tmp !== Infinity) { jumpNeighbor(pos, index, pos.xx + dx, pos.yy, parentGCost, cost, tmp); } tmp = look(pos.xx + dx, pos.yy + dy); if (tmp !== Infinity) { jumpNeighbor(pos, index, pos.xx + dx, pos.yy + dy, parentGCost, cost, tmp); } if (look(pos.xx - dx, pos.yy) !== cost) { jumpNeighbor(pos, index, pos.xx - dx, pos.yy + dy, parentGCost, cost, look(pos.xx - dx, pos.yy + dy)); } if (look(pos.xx, pos.yy - dy) !== cost) { jumpNeighbor(pos, index, pos.xx + dx, pos.yy - dy, parentGCost, cost, look(pos.xx + dx, pos.yy - dy)); } } else { tmp = look(pos.xx + dx, pos.yy); if (tmp !== Infinity) { jumpNeighbor(pos, index, pos.xx + dx, pos.yy, parentGCost, cost, tmp); } if (look(pos.xx, pos.yy + 1) !== cost) { jumpNeighbor(pos, index, pos.xx + dx, pos.yy + 1, parentGCost, cost, look(pos.xx + dx, pos.yy + 1)); } if (look(pos.xx, pos.yy - 1) !== cost) { jumpNeighbor(pos, index, pos.xx + dx, pos.yy - 1, parentGCost, cost, look(pos.xx + dx, pos.yy - 1)); } } } else { tmp = look(pos.xx, pos.yy + dy); if (tmp !== Infinity) { jumpNeighbor(pos, index, pos.xx, pos.yy + dy, parentGCost, cost, tmp); } if (look(pos.xx + 1, pos.yy) !== cost) { jumpNeighbor(pos, index, pos.xx + 1, pos.yy + dy, parentGCost, cost, look(pos.xx + 1, pos.yy + dy)); } if (look(pos.xx - 1, pos.yy) !== cost) { jumpNeighbor(pos, index, pos.xx - 1, pos.yy + dy, parentGCost, cost, look(pos.xx - 1, pos.yy + dy)); } } } } // Reconstruct path from A* graph let ret; if (minNode && (minNodeCost === 0 || !options.strict)) { let index = minNode; let pos = posFromIndex(index); let ii = 0; let path = [], roads = [], swamps = []; while (!pos.isEqualTo(start)) { path.push(pos); checkRoadAndSwamp(pos, ii++, roads, swamps); index = parents[index]; var next = posFromIndex(index); if (!next.isNearTo(pos)) { var dir = pos.getDirectionTo(next); do { pos = pos.getPositionInDirection(dir); path.push(pos); checkRoadAndSwamp(pos, ii++, roads, swamps); } while (!pos.isNearTo(next)); } pos = next; } ret = new PathInfo(path, swamps, roads); ret.reverse(); } else { ret = false; } // Cleanup alteredData.forEach(function(data) { data[0].poke(data[1], data[2], data[3]); }); for (let ii = 0; ii < roomPlan.length; ++ii) { reverseRooms[rooms[ii]] = 0; } // TODO: This is a hack which clears out the reverse room lookup table every time you search for a // path. This shouldn't be needed because the above loop clears it out, but very rarely it doesn't // work and causes weird errors on the next path search. I have no idea why! reverseRooms = new Uint8Array(Math.pow(kWorldSize * 2, 2) + 1); heap.release(); openClosed.release(); return ret; }
function plan(start, targets, parents, options) { // Init state heap = new Heap(2500); openClosed = new OpenClosed(2500); indexSize = 0; indexMap = {}; reverseIndexMap = new Map(); let maxCost = options.maxCost || 1000; // Start node must be visible if (TerrainData.factory(start.getRoomName()) === undefined) { return; } // Initial A* node heap.add(indexFromEntity(start), heuristic(start, targets)); // Do it let minNode; let minCost = Infinity; search: while (heap.length()) { // Pull a node off the heap let index = heap.min(); let entity = entityFromIndex(index); let gcost = heap.minCost(); // not yet gcost, but will be adjusted below heap.remove(); if (gcost > maxCost) { continue; } let hcost = heuristic(entity, targets); openClosed.close(index); // Convert whatever is on the heap to a WorldPosition let roomName; let entryPosition; if (entity instanceof WorldPosition) { // The first and last iteration will be a WorldPosition gcost -= hcost; roomName = entity.getRoomName(); entryPosition = entity; for (let target of targets) { if (target === entity) { // Found a target minNode = index; break search; } } } else { // If it's a RoomPortal we need to calculate where we left the last room from now gcost -= hcost - 1; let neighbor = entity.neighbor(); if (!neighbor) { // Try to explore room portals when incomplete map data is encountered if (minCost > hcost) { minCost = hcost; minNode = index; } continue; } roomName = neighbor.roomName; entryPosition = moveToNextRoom(calculateExitPosition(entity, entityFromIndex(parents[index]), targets)); // This injects the entry position into the parent chain let entryPositionIndex = indexFromEntity(entryPosition); parents[entryPositionIndex] = index; index = entryPositionIndex; } // If any of the targets are in this room we can add them to the heap for (let target of targets) { if (target.getRoomName() === roomName) { let targetIndex = indexFromEntity(target); if (visit(targetIndex, gcost + entryPosition.getRangeTo(target))) { // heuristic() is 0 since these are all targets parents[targetIndex] = index; } } } // Now add all the room portals to the heap let terrainData = TerrainData.factory(roomName); for (let edge of terrainData.edges) { for (let portal of edge) { let neighbor = portal.neighbor(); if (neighbor) { openClosed.close(indexFromEntity(neighbor)); } let portalIndex = indexFromEntity(portal); let exitCost; if (entryPosition instanceof WorldLine) { // Exit cost should be the same for the whole exit line exitCost = portal.edge.getRangeTo(entryPosition.pos); } else { exitCost = portal.edge.getRangeTo(entryPosition); } if (terrainData.controller !== undefined || terrainData.sources.length !== 0) { exitCost = Math.floor(exitCost / kHighwayDiscount); } let fcost = gcost + exitCost + heuristic(portal, targets); if (visit(portalIndex, fcost)) { parents[portalIndex] = index; } } } } // Reconstruct plan let rooms; let entity = entityFromIndex(minNode); let lastPosition; let hoffset = 0; if (entity instanceof RoomPortal) { // incomplete path due to unknown terrain let entry = entityFromIndex(parents[minNode]); let entryPosition = calculateExitPosition(entity, entry, targets); lastPosition = entryPosition; if (options.strict) { return; } rooms = [ { portal: entity, heuristic: entryPosition, hoffset: 0, roomName: entry.getRoomName(), } ]; } else { lastPosition = entity; rooms = [ { targets: targets.filter(function(target) { return target.getRoomName() === entity.getRoomName(); }), position: entity, roomName: entity.getRoomName(), } ]; } let index = parents[minNode]; entity = entityFromIndex(index); while (entity !== start) { index = parents[index]; let portal = entityFromIndex(index); let lastPortal = portal.neighbor(); if (lastPosition instanceof WorldPosition) { hoffset += lastPortal.edge.getRangeTo(lastPosition) + 1; } else if (lastPosition instanceof WorldLine) { hoffset += lastPortal.edge.getRangeTo(lastPosition.pos) + 1; } else { throw new Error('Rogue RoomPortal?'); } lastPosition = moveToNextRoom(entity); rooms.push({ portal: portal, heuristic: lastPosition, hoffset: hoffset, roomName: portal.roomName, }); index = parents[index]; entity = entityFromIndex(index); } // Cleanup heap.release(); openClosed.release(); indexMap = reverseIndexMap = undefined; return rooms; }