DEFENSE · TOWER HEAL PRIORITY

How to Make Towers Heal the Creep That Needs It Most

CURATED PATH

These links are maintained for a concrete prerequisite, failure mode, debugging step, or next lesson instead of generic topic similarity.

Defense / Combat

Recommended next steps

Return to Tower attack when hostiles appearThreat priorityVerify healing results with Event LogRuntime evidenceCalculate range falloff and multi-Tower healingHealing estimateDiagnosticsContinue diagnosis
Verification statusChinese source article: Reviewed in full · Official docs: Checked — StructureTower.heal(), whole-room range, falloff, TOWER_ENERGY_COST and return codes · Priority boundary: Hit ratio, missing hits and range are a project ordering policy, not an official medical or combat priority

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official docs
Checked — StructureTower.heal(), whole-room range, falloff, TOWER_ENERGY_COST and return codes
Priority boundary
Hit ratio, missing hits and range are a project ordering policy, not an official medical or combat priority
Execution boundary
OK schedules healing; target hits and actual range-adjusted effect require later observation
JavaScript syntax
Passed
Offline healing review
Passed — valid injuries, hit ratio, missing hits, nearest-Tower range, stable ties, Energy and activity states
Screeps Console test
Pending
Live Tower heal, falloff, Tower power effects, over-heal and multi-target allocation test
Pending
Last verified
August 28, 2026

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), keep each raw result, and verify target hits on a later tick.

const result = tower.heal(target);

if (result === OK) {
  // The heal action was scheduled.
}

Keep the returned code even when it is OK. OK means the action was accepted and scheduled; it does not prove how many hits the target will have after tick resolution.

The current Tower-heal baseline costs TOWER_ENERGY_COST = 10 Energy per action. Healing is 400 at range 5 or less, falls linearly through range 20, and is 100 at range 20 or farther. A Tower can target valid Creeps across its room, so distance does not produce ERR_NOT_IN_RANGE.

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

StructureTower.heal() itself accepts a Creep or PowerCreep in the same room. This controller intentionally queries owned regular Creeps only with FIND_MY_CREEPS; it also ignores dead or malformed objects and avoids spending 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)
  }));
  const acceptedCount = results.filter(
    item => item.result === OK
  ).length;

  return {
    status: acceptedCount === results.length
      ? 'heal-scheduled'
      : acceptedCount > 0
        ? 'heal-partial'
        : 'heal-rejected',
    snapshot,
    results
  };
}

The aggregate status never replaces the raw Tower results: heal-partial means at least one Tower returned OK and at least one did not.

function getTowerHealingState() {
  if (!Memory.towerHealing) {
    Memory.towerHealing = {};
  }

  return Memory.towerHealing;
}

function saveTowerHealingAttempt(state, room, outcome) {
  if (!outcome.snapshot || !outcome.results) {
    return;
  }

  state.pendingObservation = {
    tick: outcome.snapshot.gameTick,
    roomName: room.name,
    targetId: outcome.snapshot.targetId,
    targetName: outcome.snapshot.targetName,
    hitsBefore: outcome.snapshot.hits,
    results: outcome.results.map(item => ({
      towerId: item.towerId,
      range: item.range,
      result: item.result
    }))
  };
}

function reviewPreviousTowerHealing(state) {
  const pending = state.pendingObservation;
  if (!pending || pending.tick >= Game.time) {
    return null;
  }

  const target = Game.getObjectById(pending.targetId);
  const observation = {
    scheduledTick: pending.tick,
    observedTick: Game.time,
    roomName: pending.roomName,
    targetId: pending.targetId,
    targetName: pending.targetName,
    hitsBefore: pending.hitsBefore,
    hitsNow: target ? target.hits : null,
    targetVisible: Boolean(target),
    results: pending.results
  };

  state.lastObservation = observation;
  delete state.pendingObservation;
  return observation;
}

module.exports.loop = function () {
  const state = getTowerHealingState();
  reviewPreviousTowerHealing(state);

  const roomName = state.roomName;
  if (typeof roomName !== 'string' || roomName.length === 0) {
    console.log(
      'Tower healing is not configured. Set '
      + 'Memory.towerHealing.roomName to a visible owned room name.'
    );
    return;
  }

  const room = Game.rooms[roomName];
  if (!room) {
    console.log(
      'Tower healing room is not visible: ' + roomName
    );
    return;
  }

  const outcome = runTowerHealing(room);
  saveTowerHealingAttempt(state, room, outcome);

  if (
    outcome.status === 'heal-partial'
    || outcome.status === 'heal-rejected'
  ) {
    console.log(JSON.stringify({
      type: 'tower-heal-action',
      roomName,
      ...outcome
    }));
  }
};

Set Memory.towerHealing.roomName to your own visible owned room name before enabling this loop; the example does not assume any specific room. The tracker keeps only one pending heal attempt and one latest later-tick observation. It stores IDs, hit snapshots and raw return codes rather than caching a Creep object across ticks.

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 can use range-adjusted healing, active Tower power effects and already assigned healing. If it is trying to predict combat outcomes rather than only current missing hits, expected incoming damage is another useful input. 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

The complete example moves the previous attempt into Memory.towerHealing.lastObservation on a later tick. Compare hitsBefore with hitsNow only when targetVisible is true; a missing target does not prove the heal failed. Healing may also 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

CodeMeaningReview
OKHealing scheduledTarget hits later
ERR_NOT_OWNERTower not yoursSelection and ownership
ERR_NOT_ENOUGH_ENERGYInsufficient EnergyTower Store / Energy
ERR_INVALID_TARGETCreep invalidRefresh current target
ERR_RCL_NOT_ENOUGHTower inactiveRCL and isActive()

Debugging checklist

  • Use FIND_MY_CREEPS for this controller 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.
  • Inspect every raw heal return code, including partial failure.
  • Compare later net hits only when the target is observable.

Scope and next steps

This guide does not integrate Power Creeps into the controller, build a full range-adjusted healing allocator, calculate Tower power effects, predict incoming damage, split healing or prevent over-heal. 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.

Official documentation

SOURCE AND SCOPE

Review the source, evidence, or next system

This English guide keeps the technical scope and verification boundaries of its Chinese source. The verification record shows which checks were completed and which runtime observations are still pending.