Quick answer
Get the current room Mineral, require mineralAmount > 0, find an owned active Extractor on the exact Mineral tile, wait for extractor.cooldown === 0, require an active WORK part and free Creep Store capacity, then call creep.harvest(mineral). Move only on ERR_NOT_IN_RANGE, and treat OK as a scheduled action that still needs later Store and cooldown verification.
Mineral harvesting requirements
- The room Mineral object must currently be visible.
mineral.mineralAmountmust be greater than zero.- An Extractor must be built on the Mineral coordinate.
- The Extractor must be yours and active.
- The Extractor cooldown must be zero.
- The Creep needs at least one active
WORKpart. - The Creep needs free capacity for the Mineral type.
- The Creep must be adjacent before the harvest can execute.
Mineral harvesting is not the same as early Source harvesting. The Extractor is an additional structure gate and becomes available at RCL 6.
Read the current Mineral state
function getRoomMineral(room) {
if (!room) {
return null;
}
return room.find(FIND_MINERALS)[0] || null;
}
Use the live object fields instead of maintaining a separate countdown:
function describeMineral(mineral) {
if (!mineral) {
return null;
}
return {
id: mineral.id,
mineralType: mineral.mineralType,
mineralAmount: mineral.mineralAmount,
ticksToRegeneration:
mineral.ticksToRegeneration ?? null,
x: mineral.pos.x,
y: mineral.pos.y,
roomName: mineral.pos.roomName
};
}
ticksToRegeneration is meaningful during the depleted regeneration state. Do not decrement a copied Memory value and assume it remains synchronized with the object.
Find the Extractor on the Mineral tile
function findExtractorForMineral(room, mineral) {
if (!room || !mineral) {
return null;
}
const structures = room.lookForAt(
LOOK_STRUCTURES,
mineral.pos.x,
mineral.pos.y
);
return structures.find(structure =>
structure.structureType === STRUCTURE_EXTRACTOR
) || null;
}
A room-level find() result does not prove that the selected Extractor belongs to this Mineral. The coordinate lookup preserves the actual structure relationship.
Build a testable harvest plan
function evaluateMineralHarvest(input) {
if (!input.mineralExists) {
return { ready: false, reason: 'mineral-missing' };
}
if (
!Number.isFinite(input.mineralAmount)
|| input.mineralAmount <= 0
) {
return { ready: false, reason: 'mineral-depleted' };
}
if (!input.extractorExists || !input.extractorOnMineral) {
return { ready: false, reason: 'extractor-missing' };
}
if (!input.extractorOwned || !input.extractorActive) {
return { ready: false, reason: 'extractor-inactive' };
}
if (
!Number.isInteger(input.extractorCooldown)
|| input.extractorCooldown > 0
) {
return { ready: false, reason: 'extractor-not-ready' };
}
if (
!Number.isInteger(input.activeWorkParts)
|| input.activeWorkParts <= 0
) {
return { ready: false, reason: 'no-active-work-part' };
}
if (
!Number.isFinite(input.freeCapacity)
|| input.freeCapacity <= 0
) {
return { ready: false, reason: 'creep-full' };
}
if (!input.isNearMineral) {
return { ready: false, reason: 'move-to-mineral' };
}
return { ready: true, reason: 'ready' };
}
This pure plan makes local branches testable, but the official action return code remains the same-tick authority.
Complete Mineral harvester example
function runMineralHarvester(creep) {
if (!creep || creep.spawning === true) {
return { status: 'creep-unavailable' };
}
const mineral = getRoomMineral(creep.room);
if (!mineral) {
return { status: 'mineral-missing' };
}
if (mineral.mineralAmount <= 0) {
return {
status: 'mineral-depleted',
ticksToRegeneration:
mineral.ticksToRegeneration ?? null
};
}
const extractor = findExtractorForMineral(
creep.room,
mineral
);
if (!extractor) {
return { status: 'extractor-missing' };
}
const plan = evaluateMineralHarvest({
mineralExists: true,
mineralAmount: mineral.mineralAmount,
extractorExists: true,
extractorOnMineral:
extractor.pos.isEqualTo(mineral.pos),
extractorOwned: extractor.my === true,
extractorActive: extractor.isActive() === true,
extractorCooldown: extractor.cooldown,
activeWorkParts:
creep.getActiveBodyparts(WORK),
freeCapacity: creep.store.getFreeCapacity(
mineral.mineralType
),
isNearMineral: creep.pos.isNearTo(mineral.pos)
});
if (plan.reason === 'move-to-mineral') {
return {
status: 'moving-to-mineral',
result: creep.moveTo(mineral, {
range: 1,
reusePath: 10
})
};
}
if (!plan.ready) {
return {
status: plan.reason,
mineralId: mineral.id,
extractorId: extractor.id
};
}
const before = {
gameTick: Game.time,
mineralAmount: mineral.mineralAmount,
creepAmount: creep.store.getUsedCapacity(
mineral.mineralType
),
extractorCooldown: extractor.cooldown
};
const result = creep.harvest(mineral);
return {
status: result === OK
? 'harvest-scheduled'
: 'harvest-rejected',
result,
mineralId: mineral.id,
mineralType: mineral.mineralType,
extractorId: extractor.id,
before
};
}
module.exports.loop = function () {
const creep = Game.creeps.Miner1;
if (!creep) {
return;
}
const outcome = runMineralHarvester(creep);
if (
outcome.status === 'harvest-rejected'
|| Game.time % 100 === 0
) {
console.log(JSON.stringify({
type: 'mineral-harvester-status',
creepName: creep.name,
...outcome
}));
}
};
Respect Extractor cooldown
A successful Mineral harvest places the Extractor into cooldown. Reissuing harvest() while the cooldown is positive only produces repeated ERR_TIRED results. Read the structure field each tick and let the worker wait, deliver, or perform another explicit task.
function extractorReady(extractor) {
return Boolean(
extractor
&& extractor.my === true
&& extractor.isActive() === true
&& extractor.cooldown === 0
);
}
Handle depletion and regeneration
When mineralAmount reaches zero, stop assigning harvest actions. Store the current observation for diagnostics, but use the live ticksToRegeneration field rather than treating an old Memory countdown as truth.
function mineralAvailability(mineral) {
if (!mineral) {
return { available: false, reason: 'missing' };
}
if (mineral.mineralAmount <= 0) {
return {
available: false,
reason: 'regenerating',
ticksToRegeneration:
mineral.ticksToRegeneration ?? null
};
}
return {
available: true,
reason: 'available',
mineralAmount: mineral.mineralAmount
};
}
Verify the next tick
Save the Mineral ID, Extractor ID, Creep name, mineral type, Store amount and game tick. On a later tick, recover the objects and compare:
- Creep Store for the Mineral type;
mineral.mineralAmount;extractor.cooldown;- the original action return code.
Other Creep actions or dropped resources can affect totals, so describe the observed deltas rather than claiming causality from one number alone.
Handle return codes
| Code | Meaning here | Review |
|---|---|---|
OK | Harvest scheduled | Store, amount and cooldown later |
ERR_BUSY | Creep still spawning | creep.spawning |
ERR_NOT_FOUND | No Extractor on the Mineral tile | Same-tile lookup |
ERR_NOT_ENOUGH_RESOURCES | Mineral depleted | mineralAmount and regeneration |
ERR_INVALID_TARGET | Target is not harvestable | Object type and visibility |
ERR_NOT_IN_RANGE | Creep not adjacent | Move to range 1 |
ERR_TIRED | Extractor cooling down | extractor.cooldown |
ERR_NO_BODYPART | No active WORK part | Current body damage |
Debugging checklist
- Confirm the room and Mineral are visible.
- Read current
mineralAmount. - Find structures at the Mineral coordinates.
- Confirm Extractor ownership and activity.
- Wait for cooldown zero.
- Count active WORK parts.
- Check capacity for the exact Mineral type.
- Move only when out of range.
- Save the action result and before snapshot.
- Verify Store, amount and cooldown later.
Scope and next steps
This guide does not design the Miner body, haul minerals, schedule regeneration, operate Labs, value minerals, or sell them. Continue with Storage Energy logistics and the existing Lab guides.
Frequently asked questions
Can a Creep harvest without CARRY?
The action may still produce resources, but without available Store capacity they can drop on the ground. This example requires capacity to keep the logistics explicit.
Why not hard-code the regeneration time?
The live object exposes the current remaining regeneration state. Reading it avoids drift between Memory and the game object.
Should multiple WORK parts ignore cooldown?
No. More active WORK parts can increase one harvest amount, but the Extractor still controls how often Mineral harvesting can occur.