RESOURCE RECOVERY · DROPPED ENERGY

How to Make a Screeps Creep Pick Up Dropped Energy

Filter FIND_DROPPED_RESOURCES for Energy, rank valid piles by collectible amount and path length, handle decay and changing targets, and inspect pickup() and movement results.

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
Screeps Console test
Pending
Live decay and competition test
Pending
Last verified
July 25, 2026

Quick answer

creep.pickup(resource) works only on a dropped Resource object. Filter FIND_DROPPED_RESOURCES for RESOURCE_ENERGY, reject zero or invalid amounts, confirm the Creep has active CARRY capacity, and choose a reachable target. 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. If the pile contains more than the Creep can carry, a successful action collects only what fits and leaves the rest on the ground.

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;
  }

  if (creep.getActiveBodyparts(CARRY) <= 0) {
    console.log(
      'Collector1 has no active CARRY part.'
    );
    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.
  • Confirm active CARRY capacity.
  • 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 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.