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 part | Purpose in the round trip |
|---|---|
WORK | Harvests Energy from the Source. |
CARRY | Creates Store capacity and allows the Creep to carry and transfer Energy. |
MOVE | Moves 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 state | Mode | Expected result |
|---|---|---|
| The Creep is not full. | Harvest | It approaches the Source and collects Energy. |
| The Store becomes full. | Deliver | creep.memory.delivering becomes true. |
| The Spawn is out of range. | Deliver | transfer() returns ERR_NOT_IN_RANGE and movement is scheduled. |
| The Creep is beside the Spawn. | Deliver | transfer() returns OK; verify the Store on a later tick. |
| Some Energy remains after unloading. | Deliver | The previous delivery state is preserved. |
| The Creep begins a tick with zero Energy. | Harvest | The 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 code | Beginner meaning | Response in this lesson |
|---|---|---|
OK | The transfer was scheduled. | Inspect both Stores on a later tick. |
ERR_NOT_IN_RANGE | The Creep is not adjacent to the Spawn. | Call moveTo(spawn). |
ERR_FULL | The target cannot receive more of the resource. | Check the Spawn's free Energy capacity. |
ERR_NOT_ENOUGH_RESOURCES | The 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_RANGEwhile the Creep approaches the Spawn; - observe
OKwhen 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.