screeps-cartographer
    Preparing search index...

    screeps-cartographer

    docs

    screeps-cartographer

    Cartographer is an advanced (and open source) movement library for Screeps.

    • Flexible caching of paths in heap, Memory, or (eventually) segments
    • Path to the closest of multiple targets
    • Fully configurable to fit your needs
    • Can trigger repathing when creeps are stuck
    • Can cache custom paths for road-building or reuse with Cartographer's moveByPath
    • Can set movement priorities to allow higher-priority minions to path before lower-priority minions
    • Traffic management moves stationary minions out of the way of moving ones, while allowing them to keep range to a target
    • Enhanced findRoute reduces PathFinder search space with intelligent heuristics to produce optimal paths
    • Dynamically re-routes around specific targets with minimal repathing
    • Automatically tracks and paths through intrashard (but not intershard) portals.
    • [x] Replacement for stock moveTo with configurable caching and full PathFinder options
    • [x] Point-of-interest path caching for remotes & local economy movement
    • [x] Traffic management with creep prioritization & shoving
    • [x] Long-distance travel with better paths than Game.map.findRoute yields

    For an example, see src/tests/index.ts.

    Call preTick() at the beginning of your loop, and (to enable traffic management) call reconcileTraffic() at the end of it:

    import { preTick } from 'screeps-cartographer';
    const loop = () => {
    preTick();
    // your code goes here
    reconcileTraffic();
    };

    Call moveTo(), passing in a creep and the target:

    // target with .pos
    moveTo(creep, creep.room.storage)
    // target room position
    moveTo(creep, new RoomPosition(20, 24, 'W2N5'))
    // target room position with range
    moveTo(creep, { pos: new RoomPosition(25, 25, 'W2N5'), range: 10 })
    // list of room positions (will move to closest)
    moveTo(creep, [
    new RoomPosition(20, 20, 'W2N5'),
    new RoomPosition(20, 21, 'W2N5'),
    new RoomPosition(20, 22, 'W2N5'),
    new RoomPosition(20, 23, 'W2N5'),
    ])
    // list of room positions with range (will move to closest)
    moveTo(creep, [
    { pos: new RoomPosition(20, 20, 'W2N5'), range: 3 }
    { pos: new RoomPosition(20, 23, 'W2N5'), range: 3 }
    ])

    You can also pass in options, including some custom flags and anything supported by PathFinder options.

    // flee from target
    moveTo(creep, { pos: hostileCreep.pos, range: 3 }, { flee: true });
    // repath after a certain number of ticks, like stock moveTo
    moveTo(creep, creep.room.storage, { reusePath: 5 });
    // path around creeps (default is false)
    moveTo(creep, creep.room.storage, { avoidCreeps: true });
    // set repath interval when creep is stuck, and fallback
    // options for the repath
    moveTo(creep, creep.room.storage, { avoidCreeps: false, repathIfStuck: 5 }, { avoidCreeps: true });
    // don't path around structures (default is true)
    moveTo(creep, creep.room.storage, { avoidObstacleStructures: false });
    // set custom terrain values
    moveTo(creep, creep.room.storage, { roadCost: 5, plainCost: 1, swampCost: 1 });

    You can find the full list of extra options here.

    Instead of calling moveTo each time, you may find it more efficient to save a cached path and reuse it for multiple creeps. One common example would be pathing between storage and remote sources.

    // create initial path to remote source
    const path1 = cachePath(storage.room.name + source.id + '1', storage.pos, { pos: source.pos, range: 1 });
    const harvestPos = path1[path1.length - 1];
    // create secondary path, avoiding the road, for empty haulers
    cachePath(
    storage.room.name + source.id + '2',
    storage.pos,
    { pos: path1[path1.length - 2], range: 0 }, // rejoin the first path just before the harvester
    {
    roadCost: 1,
    plainCost: 1,
    swampCost: 1,
    roomCallback(room) {
    const cm = new PathFinder.CostMatrix();
    if (room === harvestPos.roomName) {
    // harvest pos is not pathable because a creep will be here
    cm.set(harvestPos.x, harvestPos.y, 255);
    }
    for (const pos of path1) {
    if (pos.roomName === room) cm.set(pos.x, pos.y, 50);
    }
    return cm;
    }
    }
    );

    // build roads
    const path = getCachedPath(storage.room.name + source.id + '1');
    path.forEach(pos => pos.createConstructionSite(STRUCTURE_ROAD));

    // move to remote source
    moveByPath(haulerCreep, storage.room.name + source.id + '2');
    // return home from remote source, following path
    moveByPath(haulerCreep, storage.room.name + source.id + '1', { reverse: true });

    Traffic management can be enabled by simply including the reconcileTraffic function in your main loop, after all creep movement has been requested:

    export const loop = () => {
    runCreepLogic();
    reconcileTraffic();
    };

    If reconcileTraffic is not included, creeps will simply default to unmanaged movement. Be careful to run potentially expensive operations after reconcileTraffic to avoid running out of bucket - your creeps will not move if you run out of CPU in a tick before reconcileTraffic gets a chance to run.

    reconcileTraffic will only manage creeps that use Cartographer to move; it is not compatible with Creep prototype move methods, so will cause issues if you try to mix both approaches. It's best to pick one or the other for all creep movement.

    If you have a specialized use case (e.g. quads/duos) and need low-level movement control, you can invoke the Cartographer-provided move() function to move a creep and track it for traffic management.

    Finally, you can use the blockSquare() function to push creeps out of a square even if nothing else is moving there (for clearing space at spawn, or to lay down a construction site, for example).

    All move functions accept a priority option:

    moveTo(creep, controller, { priority: 10 });
    

    Creeps with a higher priority will be given preference over creeps with a lower priority: if both want to path to the same square on a given tick, the one with a higher priority will move and the one with a lower priority will not (also saving its intent cost).

    Cartographer uses the builtin findRoute as a starting point, but the shortest route (by room count) is not always the best path (by tiles traversed). With some intelligent heuristics, it adds a few rooms to the route that may allow PathFinder to take a shortcut. PathFinder's search is then constrained to those selected rooms.

    To control the selected route, you can set weights for types of rooms:

    moveTo(
    creep,
    { pos: new RoomPosition(25, 25, 'W20N20'), range: 20 },
    {
    defaultRoomCost: 1,
    sourceKeeperRoomCost: 10,
    highwayRoomCost: 2
    }
    );

    You can also provide a custom callback to provide weights or avoid certain rooms:

    moveTo(
    creep,
    { pos: new RoomPosition(25, 25, 'W20N20'), range: 20 },
    {
    routeCallback: (room: string) => {
    if (hostileRooms.includes(room)) {
    return Infinity;
    }
    return undefined;
    }
    }
    );

    You can specify an avoidTargets callback to re-route around hostile creeps:

    moveTo(creep, storage.pos, {
    avoidTargets(room) {
    return Game.rooms[room]?.find(FIND_HOSTILE_CREEPS).map(creep => ({ pos: creep.pos, range: 3 })) ?? [];
    }
    });

    You can import the config object and override default settings, if desired:

    import { config } from 'screeps-cartographer';
    config.DEFAULT_MOVE_OPTS.routeCallback = room => {
    if (Memory.rooms[room].isHostile) return Infinity;
    return;
    };

    Cartographer includes a super-minimal Screeps bot which will maintain a spawn and generate scouts to collect room intelligence. This allows roads to be generated and visualized for debugging purposes, and also enables integration tests to catch regressions.

    To run the tests, simply run the build and upload the contents of dist_test/main.js to Screeps. The tests will reset and run again automatically after a global reset. Test output is logged to the console.

    A number of configuration files have been provided to facilitate testing on a dedicated, containerized environment using Docker Compose with steamless-client and Jomik's server. To use this test environment.

    1. Install Docker and Docker Compose
    2. Make copies of the following configuration files:
    • .env.example
      • Create a copy of this file named .env and update the values
      • See the READMEs for steamless client and Jomik's server for more information
    • config.sample.yml
      • Create a copy of this file named config.yml
      • This config is usable as-is
    • screeps.sample.json
      • Create a copy of this file named screeps.json
    1. Initialize the test environment:
      ./reset-docker.sh
      
    2. Open steamless-client to the test server using the following URL: http://localhost:8080/(http://localhost:21025)/#!/
    3. Sign in via Steam and create your account.
    4. While signed in, create a password for your account using the following URL: http://localhost:8080/(http://localhost:21025)/#!/account/password
    5. Update screeps.json with the username and password of the account you just created.
    6. Claim a room and place your spawn
    7. Finally, upload the test code:
      npm run test-pserver
      

    To stop the test environment:

    docker compose down