RESOURCE RECOVERY · DROPPED ENERGY

How to Make a Screeps Creep Pick Up Dropped Energy

CURATED PATH

These links are maintained for a concrete prerequisite, failure mode, debugging step, or next lesson instead of generic topic similarity.

Room Economy / Recovery

Recommended next steps

Recover resources from Tombstones and RuinsRecovery branch
Verification statusChinese source article: Reviewed in full · Official docs: Checked — Creep.pickup(), Resource, FIND_DROPPED_RESOURCES · API and constants: Checked

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official docs
Checked — Creep.pickup(), Resource, FIND_DROPPED_RESOURCES
API and constants
Checked
JavaScript syntax
Passed
Offline logic review
Passed — capacity, resource type, amount, path, stable sorting
Live decay and competition test
Pending
Official engine source
Checked August 16, 2026 — Creep.pickup() validates ownership, spawning, dropped-Resource identity, Store fullness, and range without an active-CARRY preflight
Processor boundary
Checked — pickup applies min(current free Store capacity, current dropped amount), so partial collection is automatic
Body-part boundary
Corrected — active CARRY is retained only as diagnostic context, not presented as an official pickup return-code requirement
Screeps Console test
Pending — no zero-active-CARRY pickup transcript is claimed
Live competition test
Pending — no real-shard competing pickup or decay trace is claimed
Last editorial review
August 16, 2026

Quick answer

creep.pickup(resource) works only on a dropped Resource object. Filter FIND_DROPPED_RESOURCES for RESOURCE_ENERGY, reject zero or invalid target amounts, confirm that the Creep's Store has free capacity, and choose a reachable target. Active CARRY count can be useful diagnostic context, but the current pickup() submission path does not use getActiveBodyparts(CARRY) as an API preflight. If pickup() returns ERR_NOT_IN_RANGE, move within range 1 and retry on a later tick.

pickup() is only for ground Resource objects

Resource locationAPI
Ground Resourcecreep.pickup(resource)
Container or Storagecreep.withdraw(target, resourceType)
Tombstone or Ruin Storecreep.withdraw(target, resourceType)
Sourcecreep.harvest(source)

Use the Container-withdrawal guide for stored Energy. Passing a ground Resource to withdraw() uses the wrong object type.

Dropped resources decay

The Chinese source article records the official ground-resource decay rule as:

ceil(amount / 1000) per tick

A pile's current amount is a snapshot, not a promise about the amount that will remain when the Creep arrives. Other Creeps can also collect the pile first. Target selection should therefore be repeated or revalidated on later ticks.

Calculate collectible amount

function getCollectibleAmount(
  resourceAmount,
  freeCapacity
) {
  if (
    !Number.isFinite(resourceAmount)
    || !Number.isFinite(freeCapacity)
    || resourceAmount <= 0
    || freeCapacity <= 0
  ) {
    return 0;
  }

  return Math.min(
    resourceAmount,
    freeCapacity
  );
}

pickup() has no amount argument. The current processor calculates the collected amount as the smaller of the Creep's current free Store capacity and the Resource object's current amount. If the pile contains more than the Creep can carry, only what fits is collected and the rest remains on the ground.

Store capacity is the pickup boundary; active CARRY is diagnostic context

The checked Creep.pickup() submission implementation rejects a Creep whose Store is already full, but it does not separately reject the call because getActiveBodyparts(CARRY) is zero. That distinction matters when documenting the API: body damage can explain why a Creep's current Store capacity looks the way it does, but the pickup contract itself is expressed through the Creep's Store and the action result.

An accepted OK still does not freeze the earlier Resource amount. At processing time, the engine rechecks the target and current Store usage, then applies min(free capacity, current resource amount). Competition or changing state can therefore make the actual collected amount smaller than the snapshot used for target ranking.

Choose a useful reachable pile

The source article uses a deliberate strategy: prioritize the amount the Creep can actually collect, then prefer the shorter path, then use the Resource ID as a stable tie-breaker.

function selectDroppedEnergy(creep) {
  const free =
    creep.store.getFreeCapacity();

  if (!Number.isFinite(free) || free <= 0) {
    return null;
  }

  const resources = creep.room.find(
    FIND_DROPPED_RESOURCES,
    {
      filter: resource =>
        resource.resourceType ===
          RESOURCE_ENERGY
        && Number.isFinite(resource.amount)
        && resource.amount > 0
    }
  );

  const candidates = [];

  for (const resource of resources) {
    const path = creep.pos.findPathTo(
      resource,
      {
        range: 1,
        ignoreCreeps: true
      }
    );

    if (
      path.length === 0
      && !creep.pos.inRangeTo(resource, 1)
    ) {
      continue;
    }

    candidates.push({
      resource,
      collectible: Math.min(
        resource.amount,
        free
      ),
      pathLength: path.length
    });
  }

  return candidates.sort((left, right) => {
    if (
      left.collectible !== right.collectible
    ) {
      return (
        right.collectible
        - left.collectible
      );
    }

    if (
      left.pathLength !== right.pathLength
    ) {
      return (
        left.pathLength
        - right.pathLength
      );
    }

    return left.resource.id.localeCompare(
      right.resource.id
    );
  })[0]?.resource ?? null;
}

This is a “fill the Creep” strategy. A path-first strategy may be more appropriate for urgent cleanup or danger avoidance. Do not claim that one ranking is always optimal.

Complete focused example

State impact: this script may move Collector1 and collect Energy from one ground Resource. It does not reserve targets for other Creeps and does not deliver the collected Energy.

function selectDroppedEnergy(creep) {
  const free =
    creep.store.getFreeCapacity();

  if (!Number.isFinite(free) || free <= 0) {
    return null;
  }

  const resources = creep.room.find(
    FIND_DROPPED_RESOURCES,
    {
      filter: resource =>
        resource.resourceType ===
          RESOURCE_ENERGY
        && Number.isFinite(resource.amount)
        && resource.amount > 0
    }
  );

  const candidates = [];

  for (const resource of resources) {
    const path = creep.pos.findPathTo(
      resource,
      {
        range: 1,
        ignoreCreeps: true
      }
    );

    if (
      path.length === 0
      && !creep.pos.inRangeTo(resource, 1)
    ) {
      continue;
    }

    candidates.push({
      resource,
      collectible: Math.min(
        resource.amount,
        free
      ),
      pathLength: path.length
    });
  }

  return candidates.sort((left, right) => {
    if (
      left.collectible !== right.collectible
    ) {
      return (
        right.collectible
        - left.collectible
      );
    }

    if (
      left.pathLength !== right.pathLength
    ) {
      return (
        left.pathLength
        - right.pathLength
      );
    }

    return left.resource.id.localeCompare(
      right.resource.id
    );
  })[0]?.resource ?? null;
}

module.exports.loop = function () {
  const creep = Game.creeps.Collector1;

  if (!creep || creep.spawning) {
    return;
  }

  const target = selectDroppedEnergy(creep);

  if (!target) {
    return;
  }

  const pickupResult = creep.pickup(target);

  if (pickupResult === ERR_NOT_IN_RANGE) {
    const moveResult = creep.moveTo(target, {
      range: 1,
      reusePath: 5
    });

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

    return;
  }

  if (pickupResult !== OK) {
    console.log(JSON.stringify({
      type: 'dropped-energy-pickup-failed',
      creepName: creep.name,
      resourceId: target.id,
      amountSeen: target.amount,
      pickupResult
    }));
  }
};

Why the target can change every tick

  • Another Creep may collect it first.
  • Decay may reduce or remove the pile.
  • The remaining amount may be lower than the earlier snapshot.
  • A path that looked open may change.
  • The object may no longer be in a visible room.

When a task must remember the target, save only resource.id. On a later tick, call Game.getObjectById(), handle null, verify resourceType and amount, and select a replacement when needed. Review the Memory guide before adding that state.

Return-code troubleshooting

ResultLikely meaningResponse
OKThe pickup command was accepted.Inspect Store and Resource on the next tick.
ERR_NOT_OWNERThe Creep is not yours.Check the selected Creep.
ERR_BUSYThe Creep is spawning.Wait.
ERR_INVALID_TARGETThe target is not a current valid Resource.Refresh or reselect it.
ERR_FULLThe Creep has no free capacity.Switch to delivery.
ERR_NOT_IN_RANGEThe target is farther than range 1.Move closer and retry later.

Use the English error-code reference to separate movement failures from pickup failures.

Debugging checklist

  • Use FIND_DROPPED_RESOURCES.
  • Filter specifically for RESOURCE_ENERGY.
  • Reject non-finite or non-positive amounts.
  • Gate pickup work by current Store free capacity; use active CARRY count only as additional body-damage diagnostic context.
  • Do not expect pickup() to accept an amount argument.
  • Reject unreachable candidates.
  • Save pickupResult and moveResult separately.
  • Expect decay and same-tick competition.
  • Save only the target ID when using Memory.
  • Add target reservation before assigning many collectors to the same pile.

Scope and next steps

This article does not cover Tombstones, Ruins, Containers, Storage, multi-resource scavenging, multi-Creep reservations, cross-room recovery, danger avoidance, or delivery after collection. Use the delivery guide when the Collector must unload Energy.

Frequently asked questions

Should I use withdraw() for a ground Resource?

No. Use pickup() for a dropped Resource and withdraw() for a compatible Store target.

Does pickup() accept an amount?

No. The target holds the resource type and amount; the Creep takes only what fits.

Why can the target disappear?

It may be collected, decay away, or leave visibility. Revalidate it every tick.

Why prioritize collectible amount before path length?

That ranking tries to fill the Creep. Path-first ranking can be better for urgent cleanup, so the strategy should match the task.

Official documentation

SOURCE AND SCOPE

Review the source, evidence, or next system

This English guide keeps the technical scope and verification boundaries of its Chinese source. The verification record shows which checks were completed and which runtime observations are still pending.