Quick answer
Do not repair while an attack target or injured owned Creep needs the Tower. Require an owned active Tower with at least repairReserve + TOWER_ENERGY_COST Energy, filter ordinary damaged structures below a documented hit-ratio threshold, exclude Walls and Ramparts, select the lowest ratio then nearest range and stable ID, call tower.repair(target), and verify structure hits later.
Run repair after attack and healing
The Tower API does not build a defense priority for you. Use one dispatcher that makes the order explicit:
function chooseTowerAction(input) {
if (input.attackTarget) {
return 'attack';
}
if (input.healTarget) {
return 'heal';
}
if (input.repairTarget) {
return 'repair';
}
return 'idle';
}
This guide treats attack before heal before repair as a project policy. A room may choose a different emergency order, but multiple independent modules should not silently compete for the same Tower.
Use hit ratio for ordinary structures
A fixed hit number has different meaning for Roads, Extensions, Spawns and Storage. A ratio makes the baseline comparable:
function getStructureHitRatio(structure) {
if (
!structure
|| !Number.isFinite(structure.hits)
|| !Number.isFinite(structure.hitsMax)
|| structure.hitsMax <= 0
) {
return null;
}
return structure.hits / structure.hitsMax;
}
const TOWER_REPAIR_RATIO_LIMIT = 0.8;
The value 0.8 is an example room policy, not an official recommendation. Repairs may be postponed or raised depending on defense risk and Energy income.
Protect reserve plus action cost
const TOWER_REPAIR_ENERGY_RESERVE = 500;
function towerCanSpendOnRepair(tower, reserve) {
const energy = tower.store.getUsedCapacity(
RESOURCE_ENERGY
);
return tower.isActive() === true
&& energy >= reserve + TOWER_ENERGY_COST;
}
Checking only energy >= reserve permits one repair action to cross the intended floor. The reserve itself is a policy and should reflect expected attacks and refill time.
Select a deterministic repair target
function selectTowerRepairTarget(
towers,
structures,
ratioLimit
) {
const candidates = structures.filter(structure => {
const ratio = getStructureHitRatio(structure);
return Number.isFinite(ratio)
&& structure.hits < structure.hitsMax
&& ratio < ratioLimit
&& structure.structureType !== STRUCTURE_WALL
&& structure.structureType !== STRUCTURE_RAMPART;
});
if (towers.length === 0 || candidates.length === 0) {
return null;
}
return [...candidates].sort((left, right) => {
const ratioDifference =
getStructureHitRatio(left)
- getStructureHitRatio(right);
if (ratioDifference !== 0) {
return ratioDifference;
}
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.id.localeCompare(right.id);
})[0] || null;
}
This version prioritizes damage ratio before range and does not assign extra business value to a Spawn over a Road. Add structure importance only as a documented room policy.
Complete Tower repair example
function runTowerRepair(room, options = {}) {
if (!room) {
return { status: 'room-not-visible' };
}
const attackTarget = room.find(
FIND_HOSTILE_CREEPS
)[0] || null;
if (attackTarget) {
return { status: 'attack-priority' };
}
const healTarget = room.find(FIND_MY_CREEPS, {
filter: creep =>
creep.hits > 0
&& creep.hits < creep.hitsMax
})[0] || null;
if (healTarget) {
return { status: 'heal-priority' };
}
const reserve = Number.isFinite(options.reserve)
? Math.max(0, options.reserve)
: TOWER_REPAIR_ENERGY_RESERVE;
const ratioLimit = Number.isFinite(options.ratioLimit)
? Math.max(0, Math.min(1, options.ratioLimit))
: TOWER_REPAIR_RATIO_LIMIT;
const towers = room.find(FIND_MY_STRUCTURES, {
filter: structure =>
structure.structureType === STRUCTURE_TOWER
&& towerCanSpendOnRepair(structure, reserve)
});
if (towers.length === 0) {
return { status: 'repair-energy-protected' };
}
const target = selectTowerRepairTarget(
towers,
room.find(FIND_STRUCTURES),
ratioLimit
);
if (!target) {
return { status: 'no-repair-target' };
}
const snapshot = {
gameTick: Game.time,
targetId: target.id,
structureType: target.structureType,
hits: target.hits,
hitsMax: target.hitsMax,
hitRatio: getStructureHitRatio(target),
ratioLimit,
reserve
};
const results = towers.map(tower => ({
towerId: tower.id,
range: tower.pos.getRangeTo(target),
energyBefore: tower.store.getUsedCapacity(
RESOURCE_ENERGY
),
result: tower.repair(target)
}));
return {
status: results.some(item => item.result === OK)
? 'repair-scheduled'
: 'repair-rejected',
snapshot,
results
};
}
module.exports.loop = function () {
const room = Game.rooms.W1N1;
if (!room) {
return;
}
const outcome = runTowerRepair(room, {
ratioLimit: TOWER_REPAIR_RATIO_LIMIT,
reserve: TOWER_REPAIR_ENERGY_RESERVE
});
if (
outcome.status === 'repair-rejected'
|| Game.time % 100 === 0
) {
console.log(JSON.stringify({
type: 'tower-repair-status',
roomName: room.name,
...outcome
}));
}
};
Keep Walls and Ramparts separate
Walls and Ramparts often use absolute target bands, emergency minimums and escalating budgets rather than an ordinary structure ratio. They are excluded here so an almost empty fortification does not permanently consume every idle Tower action.
function isOrdinaryRepairTarget(structure) {
return structure.structureType !== STRUCTURE_WALL
&& structure.structureType !== STRUCTURE_RAMPART;
}
Understand multi-Tower over-repair
All Towers in this baseline target one structure. That can exceed its missing hits. A production allocator should estimate range-adjusted repair, active Tower power effects, assigned repair and remaining need before allocating another Tower.
Verify repair later
Recover the structure by ID and compare hits with the saved snapshot. Other Creeps or Towers can also repair it, and damage can occur in the same period, so the net hit delta is not perfect attribution.
Handle return codes
| Code | Meaning | Review |
|---|---|---|
OK | Repair scheduled | Structure hits later |
ERR_NOT_OWNER | Tower not yours | Selection and ownership |
ERR_NOT_ENOUGH_ENERGY | Insufficient Energy | Reserve, cost and competing actions |
ERR_INVALID_TARGET | Structure invalid | Refresh current target |
ERR_RCL_NOT_ENOUGH | Tower inactive | RCL and isActive() |
Debugging checklist
- Exit when an attack target exists.
- Exit when an injured owned Creep exists.
- Use one Tower action dispatcher.
- Require reserve plus
TOWER_ENERGY_COST. - Document the ratio threshold.
- Exclude Walls and Ramparts.
- Use stable ratio, range and ID ordering.
- Save every repair return code.
- Watch for over-repair.
- Verify net hits later.
Scope and next steps
This guide does not maintain Walls or Ramparts, predict exact repair output, model Tower power effects, assign structure business values, coordinate haulers or prevent over-repair. Return to the English article library for construction and defense topics.
Frequently asked questions
Why not use one fixed hit threshold?
Ordinary structures have different maximum hits, so one number can represent minor Road damage but severe Spawn damage.
Can Tower repair return ERR_NOT_IN_RANGE?
No. Towers act across their room; distance changes effect rather than producing that range error.
Should every idle Tower repair?
Only according to your room budget. A defense reserve can be more valuable than repairing minor damage immediately.