GETTING STARTED · BEGINNER LESSON 5 OF 12

How to Make a Screeps Creep Deliver Energy to a Spawn

Make one Creep transfer Energy to a named Spawn, preserve delivery mode across ticks, and complete its first Source-to-Spawn round trip.

Verification statusChinese source: Read in full · Official documentation: Checked · API and constants: Checked

VERIFICATION

Evidence and test status

Chinese source
Read in full
Official documentation
Checked
API and constants
Checked
JavaScript syntax
Checked
Offline state logic
Passed
Screeps Console
Pending — replace both example names with live objects
Live multi-tick test
Pending — no live round-trip result is claimed
Last verified
July 27, 2026
Publication status
Ready

What you will complete in this lesson

This lesson has two connected results. First, a Creep that already carries Energy will move to a named Spawn and transfer its load. Then you will add one Memory state so the same worker can repeat the complete route:

Source → Creep Store → Spawn → back to the Source.

Lesson boundary: this code controls one named Creep, the first visible Source, and one named Spawn. When the Spawn is full, the Creep waits. Extension priorities and multi-Creep room logistics belong in later lessons.

What the worker needs

Complete the previous Energy harvesting lesson before adding delivery. The full round-trip worker uses:

Body partPurpose in the round trip
WORKHarvests Energy from the Source.
CARRYCreates Store capacity and allows the Creep to carry and transfer Energy.
MOVEMoves between the Source and Spawn.

The one-way transfer example itself does not use WORK, but the complete harvesting-and-delivery loop does.

Copy the real Creep and Spawn names

State impact: read-only. Run these commands in the Console:

Object.keys(Game.creeps);
Object.keys(Game.spawns);

The examples use Harvester1 and Spawn1. Replace both values with exact names from your account. Names are case-sensitive.

Use this read-only inspection before changing the main loop:

const creep = Game.creeps['Harvester1'];
const spawn = Game.spawns['Spawn1'];

console.log(JSON.stringify({
  creepFound: Boolean(creep),
  spawnFound: Boolean(spawn),
  spawning: creep ? creep.spawning : null,
  activeWork: creep ? creep.getActiveBodyparts(WORK) : null,
  activeCarry: creep ? creep.getActiveBodyparts(CARRY) : null,
  activeMove: creep ? creep.getActiveBodyparts(MOVE) : null,
  carriedEnergy: creep
    ? creep.store.getUsedCapacity(RESOURCE_ENERGY)
    : null,
  spawnFreeEnergy: spawn
    ? spawn.store.getFreeCapacity(RESOURCE_ENERGY)
    : null,
  range: creep && spawn
    ? creep.pos.getRangeTo(spawn)
    : null
}));

Continue after confirming that both objects exist, the Creep has finished spawning, and the required body parts are active.

Verify one delivery with minimal code

State impact: this script may move one Creep and transfer its carried Energy into the named Spawn. Start with a Creep that already carries Energy.

const CREEP_NAME = 'Harvester1';
const SPAWN_NAME = 'Spawn1';

module.exports.loop = function () {
  const creep = Game.creeps[CREEP_NAME];
  const spawn = Game.spawns[SPAWN_NAME];

  if (!creep || !spawn || creep.spawning) return;

  const transferResult = creep.transfer(
    spawn,
    RESOURCE_ENERGY
  );

  if (transferResult === ERR_NOT_IN_RANGE) {
    creep.moveTo(spawn);
  }
};

The direction is Creep → Spawn. transfer() does not take Energy out of the Spawn. That opposite direction uses withdraw().

The action rule matches the harvesting lesson:

try the work action → move only when it returns ERR_NOT_IN_RANGE → retry on a later tick.

Keep delivery mode until the Creep is empty

A rule based only on free capacity is not enough. After a partial unload, the Creep has free capacity again and could turn back toward the Source while still carrying Energy.

Store one boolean decision in creep.memory.delivering:

const usedEnergy =
  creep.store.getUsedCapacity(RESOURCE_ENERGY) ?? 0;

const freeEnergyCapacity =
  creep.store.getFreeCapacity(RESOURCE_ENERGY) ?? 0;

if (usedEnergy === 0) {
  creep.memory.delivering = false;
} else if (freeEnergyCapacity === 0) {
  creep.memory.delivering = true;
}

These boundaries produce three useful states:

  • empty Store → harvest;
  • full Store → deliver;
  • partially full Store → preserve the previous mode.

That final rule is the important one. A worker that has started delivering remains in delivery mode until its Store reaches zero.

Run the complete harvesting-and-delivery loop

The following version checks both named objects, preserves the delivery state, waits when the Spawn has no free Energy capacity, and records temporary evidence every five ticks.

State impact: it calls harvest(), transfer(), and moveTo(), and writes creep.memory.delivering. Replace both example names before saving it.

const CREEP_NAME = 'Harvester1';
const SPAWN_NAME = 'Spawn1';
const DEBUG_ENERGY_LOOP = true;

module.exports.loop = function () {
  const creep = Game.creeps[CREEP_NAME];
  const spawn = Game.spawns[SPAWN_NAME];

  if (!creep) {
    console.log(
      CREEP_NAME +
      ' was not found. Check the name and capitalization.'
    );
    return;
  }

  if (!spawn) {
    console.log(
      SPAWN_NAME +
      ' was not found. Check the name and capitalization.'
    );
    return;
  }

  if (creep.spawning) return;

  const usedEnergy =
    creep.store.getUsedCapacity(RESOURCE_ENERGY) ?? 0;

  const freeEnergyCapacity =
    creep.store.getFreeCapacity(RESOURCE_ENERGY) ?? 0;

  if (usedEnergy === 0) {
    creep.memory.delivering = false;
  } else if (freeEnergyCapacity === 0) {
    creep.memory.delivering = true;
  }

  let action = 'harvest';
  let actionResult = null;
  let moveResult = null;
  let target = null;
  let status = 'running';

  if (creep.memory.delivering) {
    action = 'transfer';
    target = spawn;

    const spawnFreeEnergy =
      spawn.store.getFreeCapacity(RESOURCE_ENERGY) ?? 0;

    if (spawnFreeEnergy <= 0) {
      status = 'waiting-for-spawn-capacity';
    } else {
      const amount = Math.min(usedEnergy, spawnFreeEnergy);

      actionResult = creep.transfer(
        spawn,
        RESOURCE_ENERGY,
        amount
      );

      if (actionResult === ERR_NOT_IN_RANGE) {
        moveResult = creep.moveTo(spawn, { reusePath: 5 });
      }
    }
  } else {
    const source = creep.room.find(FIND_SOURCES)[0];

    if (!source) {
      console.log(
        'No visible Source was found in ' +
        creep.room.name +
        '.'
      );
      return;
    }

    target = source;
    actionResult = creep.harvest(source);

    if (actionResult === ERR_NOT_IN_RANGE) {
      moveResult = creep.moveTo(source, { reusePath: 5 });
    }
  }

  if (DEBUG_ENERGY_LOOP && Game.time % 5 === 0) {
    console.log(JSON.stringify({
      tick: Game.time,
      creep: CREEP_NAME,
      mode: creep.memory.delivering
        ? 'deliver'
        : 'harvest',
      action: action,
      status: status,
      actionResult: actionResult,
      moveResult: moveResult,
      range: target
        ? creep.pos.getRangeTo(target)
        : null,
      carriedEnergy: usedEnergy,
      spawnFreeEnergy:
        spawn.store.getFreeCapacity(RESOURCE_ENERGY)
    }));
  }

  if (
    actionResult !== null &&
    actionResult !== OK &&
    actionResult !== ERR_NOT_IN_RANGE &&
    actionResult !== ERR_FULL &&
    actionResult !== ERR_NOT_ENOUGH_RESOURCES
  ) {
    console.log(
      CREEP_NAME +
      ' ' +
      action +
      ' returned ' +
      actionResult +
      '.'
    );
  }

  if (
    moveResult !== null &&
    moveResult !== OK &&
    moveResult !== ERR_TIRED
  ) {
    console.log(
      CREEP_NAME +
      ' moveTo() returned ' +
      moveResult +
      '.'
    );
  }
};

The explicit amount is the smaller of the Creep's carried Energy and the Spawn's free Energy capacity. This makes a partial unload visible instead of hiding it inside the action call.

What the round trip looks like across ticks

The exact route and tick numbers depend on the room. The expected state sequence is:

Start-of-tick stateModeExpected result
The Creep is not full.HarvestIt approaches the Source and collects Energy.
The Store becomes full.Delivercreep.memory.delivering becomes true.
The Spawn is out of range.Delivertransfer() returns ERR_NOT_IN_RANGE and movement is scheduled.
The Creep is beside the Spawn.Delivertransfer() returns OK; verify the Store on a later tick.
Some Energy remains after unloading.DeliverThe previous delivery state is preserved.
The Creep begins a tick with zero Energy.HarvestThe state switches to false and the Creep returns to the Source.

The values read near the start of the loop describe the current tick's state. An action returning OK is scheduled successfully, but the resulting Store change should be verified on a later tick.

Four transfer results to understand first

Return codeBeginner meaningResponse in this lesson
OKThe transfer was scheduled.Inspect both Stores on a later tick.
ERR_NOT_IN_RANGEThe Creep is not adjacent to the Spawn.Call moveTo(spawn).
ERR_FULLThe target cannot receive more of the resource.Check the Spawn's free Energy capacity.
ERR_NOT_ENOUGH_RESOURCESThe Creep does not carry the requested amount.Re-read its Store and transfer amount.

Use the English return-code reference when another result appears. For example, ERR_INVALID_TARGET means the selected object cannot receive that resource.

Fix the three most common problems

The Creep or Spawn is not found

Run Object.keys(Game.creeps) and Object.keys(Game.spawns) again. Replace both tutorial names exactly, including capitalization.

The full Creep never leaves the Source

Log usedEnergy, freeEnergyCapacity, and creep.memory.delivering. The state should become true only when free Energy capacity reaches zero.

The Creep waits near the Spawn or returns too early

First inspect the Spawn's free capacity and actionResult. A full Spawn is a capacity condition, not necessarily a movement failure. When the Spawn accepts only part of the load, do not overwrite delivery mode merely because the Creep has free capacity again.

Completion check

You have completed this lesson when you can do all of the following:

  • replace both example names with real objects;
  • explain why transfer() moves Energy from the Creep to the Spawn;
  • observe ERR_NOT_IN_RANGE while the Creep approaches the Spawn;
  • observe OK when the transfer can be scheduled;
  • explain why the delivery state is preserved while the Store is partially full;
  • confirm that carried Energy falls and Spawn Energy rises on later ticks;
  • observe the worker return to harvesting only after it becomes empty.

Continue to Creep body parts

Your first worker can now complete a basic Energy round trip. The next lesson explains why missing or damaged body parts can stop harvesting, carrying, or movement.

Understand WORK, CARRY, and MOVE →

Return to the English beginner roadmap to review the complete twelve-lesson sequence.

Official sources

SOURCE AND SCOPE

Review the source, evidence, or next system

This English guide is rewritten for a focused search intent while preserving the technical scope and verification boundaries of its Chinese source. Live-room evidence is claimed only when the verification record says it exists.