Quick answer
Find current non-owned Creeps, remove users allowed by your own reviewed policy, score active combat and Controller-threatening parts, select a deterministic target by threat, nearest-Tower range and name, then let only owned active Towers with at least TOWER_ENERGY_COST Energy call tower.attack(target). Save every return code and refresh the target on every tick.
FIND_HOSTILE_CREEPS is not a diplomacy policy
FIND_HOSTILE_CREEPS is a game query, not an alliance database. It cannot know whether another player is an ally, temporary visitor, scout or permitted task participant.
Memory.defense ??= {};
Memory.defense.allowedUsers ??= [];
function getAttackableHostiles(room) {
const allowedUsers = new Set(
Array.isArray(Memory.defense?.allowedUsers)
? Memory.defense.allowedUsers
: []
);
return room.find(FIND_HOSTILE_CREEPS, {
filter: creep =>
typeof creep.owner?.username === 'string'
&& !allowedUsers.has(creep.owner.username)
});
}
An allowed-user list is operationally dangerous when stale or entered incorrectly. Production diplomacy may need expiration, room scope, incident overrides and event-log review.
Build an explainable threat score
This baseline scores current active parts rather than original body length:
function getTowerThreatScore(creep) {
if (!creep) {
return 0;
}
return (
creep.getActiveBodyparts(ATTACK) * 5
+ creep.getActiveBodyparts(RANGED_ATTACK) * 5
+ creep.getActiveBodyparts(HEAL) * 4
+ creep.getActiveBodyparts(CLAIM) * 3
+ creep.getActiveBodyparts(WORK) * 2
);
}
The weights are a project policy. WORK can dismantle structures and CLAIM can threaten Controller state, so a filter that checks only ATTACK can miss meaningful threats.
Select one deterministic target
function selectTowerAttackTarget(towers, hostiles) {
if (towers.length === 0 || hostiles.length === 0) {
return null;
}
return [...hostiles].sort((left, right) => {
const threatDifference =
getTowerThreatScore(right)
- getTowerThreatScore(left);
if (threatDifference !== 0) {
return threatDifference;
}
const leftRange = Math.min(
...towers.map(tower =>
tower.pos.getRangeTo(left)
)
);
const rightRange = Math.min(
...towers.map(tower =>
tower.pos.getRangeTo(right)
)
);
if (leftRange !== rightRange) {
return leftRange - rightRange;
}
return left.name.localeCompare(right.name);
})[0] || null;
}
The name tie-breaker has no combat meaning. It prevents an otherwise equal sort from changing unpredictably. This version focuses all available Towers on one target and does not estimate overkill.
Require active Towers with Energy
function getAvailableDefenseTowers(room) {
return room.find(FIND_MY_STRUCTURES, {
filter: structure =>
structure.structureType === STRUCTURE_TOWER
&& structure.isActive() === true
&& structure.store.getUsedCapacity(
RESOURCE_ENERGY
) >= TOWER_ENERGY_COST
});
}
The preflight removes obviously unavailable Towers, but another module can still try to assign a different action in the same tick. A complete room defense should have one Tower dispatcher.
Complete Tower attack example
function runTowerAttack(room) {
if (!room) {
return { status: 'room-not-visible' };
}
const towers = getAvailableDefenseTowers(room);
if (towers.length === 0) {
return { status: 'no-available-tower' };
}
const hostiles = getAttackableHostiles(room);
const target = selectTowerAttackTarget(
towers,
hostiles
);
if (!target) {
return { status: 'no-attack-target' };
}
const snapshot = {
gameTick: Game.time,
targetId: target.id,
targetName: target.name,
owner: target.owner?.username || null,
hits: target.hits,
hitsMax: target.hitsMax,
threatScore: getTowerThreatScore(target)
};
const results = towers.map(tower => ({
towerId: tower.id,
range: tower.pos.getRangeTo(target),
energyBefore: tower.store.getUsedCapacity(
RESOURCE_ENERGY
),
result: tower.attack(target)
}));
return {
status: results.some(item => item.result === OK)
? 'attack-scheduled'
: 'attack-rejected',
snapshot,
results
};
}
module.exports.loop = function () {
const room = Game.rooms.W1N1;
if (!room) {
return;
}
const outcome = runTowerAttack(room);
if (
outcome.status === 'attack-rejected'
|| Game.time % 50 === 0
) {
console.log(JSON.stringify({
type: 'tower-attack-status',
roomName: room.name,
...outcome
}));
}
};
Refresh targets every tick
A target can die, leave, move, enter protection, lose active parts, receive a boost or change diplomatic status. Do not store the full object in Memory. Store an ID and diagnostic snapshot only when needed, then recover and re-score current objects.
function recoverPreviousTowerTarget(targetId) {
return typeof targetId === 'string'
? Game.getObjectById(targetId)
: null;
}
Understand distance and effect
Tower range covers the room, so attack does not use ERR_NOT_IN_RANGE. Actual damage falls with distance, and active Tower power effects may modify it. This guide uses range only as a tie-breaker and does not promise a kill in one volley.
Verify the next tick
Compare the stored target ID, hits and Tower Energy with current visible state. A missing target may mean death or loss of visibility; a smaller hits delta than expected may reflect healing, damage reduction, range or other simultaneous actions.
Handle return codes
| Code | Meaning | Review |
|---|---|---|
OK | Attack scheduled | Target hits later |
ERR_NOT_OWNER | Tower not yours | Selection and ownership |
ERR_NOT_ENOUGH_ENERGY | Insufficient Tower Energy | Store and competing actions |
ERR_INVALID_TARGET | Target invalid | Refresh target this tick |
ERR_RCL_NOT_ENOUGH | Tower inactive | RCL and isActive() |
Debugging checklist
- Refresh the visible room.
- Review allowed usernames.
- Use active body parts.
- Keep threat weights documented.
- Use deterministic target ties.
- Require active owned Towers.
- Check
TOWER_ENERGY_COST. - Use one Tower dispatcher per tick.
- Save every attack return code.
- Verify damage without claiming guaranteed kills.
Scope and next steps
This guide does not model boosts, exact falloff damage, overkill, split fire, Power Creeps, Rampart protection, event-driven diplomacy or Safe Mode. Continue with Tower healing priorities.
Frequently asked questions
Why focus all Towers on one target?
It is deterministic and easy to verify. Advanced defense should estimate assigned damage and split Towers when focus fire would waste Energy.
Why include WORK and CLAIM?
They can threaten structures and Controller control even when the Creep has no direct ATTACK part.
Should an allowed user always be ignored?
Not necessarily. A stronger system can revoke permission after hostile actions or limit access to specific rooms and tasks.