Quick answer
Creep.attack(target) schedules a short-range melee attack from one of your Creeps. The target must be adjacent, and the attacker needs at least one active ATTACK body part.
The safest beginner pattern is small: choose one hostile, move toward it until range 1, call attack(), preserve the return code, then inspect later state only after the tick has been processed. An OK return means the operation was scheduled successfully; it is not itself proof that the target already lost hits.
Start with the minimal move-and-attack loop
This loop is enough to teach the API boundary without hiding it behind a combat system:
const attacker = Game.creeps.Guard1;
if (attacker) {
const target = attacker.pos.findClosestByRange(FIND_HOSTILE_CREEPS);
if (target) {
const attackResult = attacker.attack(target);
if (attackResult === ERR_NOT_IN_RANGE) {
const moveResult = attacker.moveTo(target);
if (moveResult !== OK) {
console.log(JSON.stringify({ stage: 'move', moveResult }));
}
} else if (attackResult !== OK) {
console.log(JSON.stringify({ stage: 'attack', attackResult }));
}
}
}
The first attack() call is useful because its return value tells you whether range is the current blocker. When it returns ERR_NOT_IN_RANGE, moveTo() schedules movement instead; the loop tries the attack again on a later tick.
If movement itself is accepted but the Creep still does not make progress, keep that as a separate movement problem and use the moveTo() debugging guide.
Treat melee range as exactly one square
The current official API requires the target to be on an adjacent square. In Screeps terms, that is range 1. You can inspect the distance before submitting the attack:
const range = attacker.pos.getRangeTo(target);
if (range > 1) {
const moveResult = attacker.moveTo(target);
console.log(JSON.stringify({ range, moveResult }));
} else {
const attackResult = attacker.attack(target);
console.log(JSON.stringify({ range, attackResult }));
}
This version does not intentionally generate ERR_NOT_IN_RANGE; it uses the position check to choose between movement and attack. Both styles are valid. The important part is that you do not treat melee as a range-3 action or repeatedly call attack() from across the room.
For a broader explanation of action distance and ERR_NOT_IN_RANGE, see Use the Correct Action Range.
Read every documented return code
The current official Creep.attack() table documents six outcomes:
| Return | Meaning | What to inspect next |
|---|---|---|
OK | The attack operation was scheduled successfully. | Wait for processed state before making a damage claim. |
ERR_NOT_OWNER | You do not own the attacking Creep. | Check which object your code resolved as the attacker. |
ERR_BUSY | The attacker is still being spawned. | Do not run active combat logic until spawning is complete. |
ERR_INVALID_TARGET | The object is not a valid attackable target. | Check the resolved object type and whether your target reference is still current. |
ERR_NOT_IN_RANGE | The target is farther than the adjacent melee range. | Move toward the target and retry on a later tick. |
ERR_NO_BODYPART | The attacker has no usable ATTACK body part. | Inspect the current body, including destroyed parts. |
Do not collapse these into a single falsey result. The return code tells you which boundary failed now, while later game state tells you what was actually processed.
Check active ATTACK parts before combat
The body reference lists an unboosted ATTACK part at 30 melee attack power. Boosts can modify that value, and destroyed body parts no longer contribute. For a simple readiness check, use getActiveBodyparts(ATTACK):
const activeAttackParts = attacker.getActiveBodyparts(ATTACK);
if (activeAttackParts === 0) {
console.log(JSON.stringify({
name: attacker.name,
reason: 'no-active-attack-parts'
}));
}
This check is useful before target selection because it separates “this Creep cannot currently perform melee attacks” from range and target problems. It is not a full damage calculator; boosted parts, target mitigation, Ramparts, healing, and other combat effects belong to a larger combat model.
Remember Ramparts and automatic hit-back
Two official mechanics can make a simple melee exchange look surprising:
- Rampart protection changes what is hit. If the target is inside a Rampart, the API says the Rampart is attacked instead.
- An attacked melee Creep can hit back automatically. If the target is a Creep with
ATTACKparts and is not inside a Rampart, it automatically hits back at the attacker.
That means “my call returned OK” does not imply “only the enemy lost hits.” Inspect both sides of a melee contact when survival matters.
Use a bounded helper without building a combat framework
A small helper can keep the decision order explicit: validate the attacker, select one hostile Creep, move when outside range 1, and attack only when adjacent.
function scheduleMeleeAttack(attacker) {
if (!attacker || !attacker.my) {
return { ok: false, stage: 'validate', reason: 'invalid-attacker' };
}
if (attacker.spawning) {
return { ok: false, stage: 'validate', reason: 'spawning' };
}
if (attacker.getActiveBodyparts(ATTACK) === 0) {
return { ok: false, stage: 'validate', reason: 'no-attack-part' };
}
const target = attacker.pos.findClosestByRange(FIND_HOSTILE_CREEPS);
if (!target) {
return { ok: false, stage: 'select', reason: 'no-hostile-creep' };
}
const range = attacker.pos.getRangeTo(target);
if (range > 1) {
const moveResult = attacker.moveTo(target);
return {
ok: moveResult === OK,
stage: 'move',
code: moveResult,
targetId: target.id,
range
};
}
const attackResult = attacker.attack(target);
return {
ok: attackResult === OK,
stage: 'attack',
code: attackResult,
targetId: target.id,
range
};
}
This helper deliberately owns only one melee decision. It does not choose military objectives, path through multiple rooms, coordinate healers, estimate boosted damage, or decide whether a fight is strategically good.
Separate scheduled intent from observed damage
OK is current-tick command evidence. Damage is processed game state. If you need to know whether an exact attack was processed, keep the attacker and target identity plus the tick, then inspect the next available state rather than treating the return code as a completed outcome.
A lower target.hits value on a later tick proves that the target lost hits, but it may not prove that your one melee attack was the only cause. Towers, ranged Creeps, other melee Creeps, and other damage can share the same interval. When exact action attribution matters, use the room event log and match the relevant attack event rather than inferring causality from net hits alone. The dedicated Room.getEventLog() guide covers that evidence boundary.
Debug a melee attacker in the right order
- Resolve the attacker. Confirm the Creep exists, is yours, and is no longer spawning.
- Check active
ATTACKparts. Zero means the Creep cannot currently perform this melee action. - Resolve one valid target. Start with
FIND_HOSTILE_CREEPSwhen learning the API instead of mixing hostile structures and Creeps into one selector. - Check range. If it is greater than 1, solve movement first.
- Preserve the exact
attack()return code. Do not replace six distinct outcomes with a boolean. - Inspect Rampart and hit-back behavior. The object losing hits may not be the object you first expected.
- Verify processed state later. Use event evidence when exact attribution matters.
Know what this guide does not own
This page is specifically about one Creep's melee attack() action. It does not replace:
- Tower.attack() room-defense automation;
- ranged Creep combat with
rangedAttack()orrangedMassAttack(); - boost planning, damage simulation, healer coordination, or squad pathing;
- room-level target priorities and military strategy.
Keeping those concerns separate makes a failed melee call much easier to diagnose.
FAQ
Can Creep.attack() target structures?
Yes. The documented target types are Creep, PowerCreep, and Structure. This guide uses hostile Creeps in its minimal selector so the first example stays focused.
Can I call attack() before moveTo()?
Yes. The official example calls attack() first and reacts to ERR_NOT_IN_RANGE by calling moveTo(). A pre-range-check pattern is also valid when you want to avoid intentionally generating that return code.
Does one ATTACK part always deal 30 damage?
An unboosted active ATTACK part has 30 melee attack power in the current body-part reference. Boosts can increase attack power, and the target's protection or other combat mechanics can affect the observed result.