Quick answer
Find owned regular Creeps with 0 < hits < hitsMax, select the lowest hit ratio first, then the largest missing-hit count, nearest available Tower range and name as a stable tie-breaker. Let only owned active Towers with at least TOWER_ENERGY_COST Energy call tower.heal(target), save every result, and verify target hits on a later tick.
Filter valid injured Creeps
function getInjuredOwnedCreeps(room) {
return room.find(FIND_MY_CREEPS, {
filter: creep =>
Number.isFinite(creep.hits)
&& Number.isFinite(creep.hitsMax)
&& creep.hits > 0
&& creep.hitsMax > 0
&& creep.hits < creep.hitsMax
});
}
This scope excludes Power Creeps intentionally. It also ignores dead or malformed objects and avoids wasting actions on full-health targets.
Rank urgency before distance
function selectTowerHealTarget(towers, creeps) {
const injured = creeps.filter(creep =>
Number.isFinite(creep.hits)
&& Number.isFinite(creep.hitsMax)
&& creep.hits > 0
&& creep.hitsMax > 0
&& creep.hits < creep.hitsMax
);
if (towers.length === 0 || injured.length === 0) {
return null;
}
return [...injured].sort((left, right) => {
const ratioDifference =
left.hits / left.hitsMax
- right.hits / right.hitsMax;
if (ratioDifference !== 0) {
return ratioDifference;
}
const missingDifference =
(right.hitsMax - right.hits)
- (left.hitsMax - left.hits);
if (missingDifference !== 0) {
return missingDifference;
}
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;
}
These priorities are explainable but not universal. A combat system may consider incoming damage, role, boosts, retreat state, Rampart position or defender importance.
Require active Towers with Energy
function getAvailableHealingTowers(room) {
return room.find(FIND_MY_STRUCTURES, {
filter: structure =>
structure.structureType === STRUCTURE_TOWER
&& structure.isActive() === true
&& structure.store.getUsedCapacity(
RESOURCE_ENERGY
) >= TOWER_ENERGY_COST
});
}
A Tower action uses Energy and one Tower should have one dispatcher per tick. Preflight cannot stop a separate module from assigning attack or repair afterward.
Complete Tower healing example
function runTowerHealing(room) {
if (!room) {
return { status: 'room-not-visible' };
}
const towers = getAvailableHealingTowers(room);
if (towers.length === 0) {
return { status: 'no-available-tower' };
}
const target = selectTowerHealTarget(
towers,
getInjuredOwnedCreeps(room)
);
if (!target) {
return { status: 'no-injured-creep' };
}
const snapshot = {
gameTick: Game.time,
targetId: target.id,
targetName: target.name,
hits: target.hits,
hitsMax: target.hitsMax,
hitRatio: target.hits / target.hitsMax,
missingHits: target.hitsMax - target.hits
};
const results = towers.map(tower => ({
towerId: tower.id,
range: tower.pos.getRangeTo(target),
energyBefore: tower.store.getUsedCapacity(
RESOURCE_ENERGY
),
result: tower.heal(target)
}));
return {
status: results.some(item => item.result === OK)
? 'heal-scheduled'
: 'heal-rejected',
snapshot,
results
};
}
module.exports.loop = function () {
const room = Game.rooms.W1N1;
if (!room) {
return;
}
const outcome = runTowerHealing(room);
if (
outcome.status === 'heal-rejected'
|| Game.time % 100 === 0
) {
console.log(JSON.stringify({
type: 'tower-heal-status',
roomName: room.name,
...outcome
}));
}
};
Understand multi-Tower over-heal
This baseline focuses every available Tower on the same injured Creep. That is easy to inspect but can spend more Energy than necessary when one Tower would fill the missing hits.
function getMissingHits(creep) {
return Math.max(0, creep.hitsMax - creep.hits);
}
A more advanced allocator needs the range-adjusted healing amount, active power effects, already assigned healing and expected incoming damage. This article does not claim focus healing is optimal.
Keep one Tower action dispatcher
A common room policy is attack before heal before repair. That order is not enforced by the Tower API; your code must select one action for each Tower.
function chooseTowerMode(input) {
if (input.attackTarget) {
return 'attack';
}
if (input.healTarget) {
return 'heal';
}
if (input.repairTarget) {
return 'repair';
}
return 'idle';
}
Refresh targets every tick
The target may heal to full, die, leave the room, move closer to another Tower or receive new damage. Save only IDs and snapshots for diagnostics; run the selection again from current visible objects.
Verify healing later
Compare the saved hit count with the recovered Creep on a later tick. Healing may be partly or fully offset by damage in the same resolution window, so a final hit difference is evidence of net state rather than a perfect measurement of one Tower's contribution.
Handle return codes
| Code | Meaning | Review |
|---|---|---|
OK | Healing scheduled | Target hits later |
ERR_NOT_OWNER | Tower not yours | Selection and ownership |
ERR_NOT_ENOUGH_ENERGY | Insufficient Energy | Store and competing Tower actions |
ERR_INVALID_TARGET | Creep invalid | Refresh current target |
ERR_RCL_NOT_ENOUGH | Tower inactive | RCL and isActive() |
Debugging checklist
- Use
FIND_MY_CREEPSfor this scope. - Require valid positive hits and hitsMax.
- Exclude full-health Creeps.
- Document the hit-ratio policy.
- Use stable tie-breakers.
- Require active owned Towers.
- Check
TOWER_ENERGY_COST. - Use one action dispatcher.
- Save every heal return code.
- Verify net hits later.
Scope and next steps
This guide does not include Power Creeps, exact range healing, boost effects, incoming-damage prediction, split healing or over-heal prevention. Continue with Tower repair thresholds and reserves.
Frequently asked questions
Why not always heal the nearest Creep?
The nearest Creep may have only minor damage while a farther defender is close to death. This policy ranks urgency before distance.
Why use the name as the final tie-breaker?
It makes otherwise equal target selection stable without pretending the name has combat value.
Can attack and heal functions both run?
Separate functions can both be called, but a controlled system should choose one intended action per Tower and preserve the final return code.