Part of the Architecture Reference.
TargetingSystem implements two-phase target locking with support for building pull, sight ranges,
minimum range blind spots, and invisibility.
- Unlocked phase (moving): scans for closest enemy within sight range. Retargets freely every tick.
- Locked phase (in attack range,
combat.targetLocked=true): stays locked on current target. Only unlocks when target becomes invalid (dead, out of retention range, invisible, etc.). - Retention range =
sightRange * 1.5f + collisionRadii-- target stays valid at 1.5x sight range before being dropped - Building-targeting troops (
targetOnlyBuildings=true) always retarget to closest building regardless of lock state
All ranges use edge-to-edge distance (center-to-center minus collision radii):
effectiveSightRange = sightRange + attacker.collisionRadius + target.collisionRadius
effectiveMinRange = minimumRange + attacker.collisionRadius + target.collisionRadius
Default sight range: 5.5 tiles (per Combat component).
canTarget() applies these filters in order:
targetOnlyBuildings-- ignores non-building entities (Giant, Hog Rider)targetOnlyTroops-- ignores buildings (Ram Rider bola)ignoreTargetsWithBuff-- skips targets with specific buff (Ram Rider + BolaSnare)TargetTypevsMovementTypematching:ALL-> any targetGROUND-> GROUND or BUILDINGAIR-> AIR onlyBUILDINGS-> BUILDING only
- Invisible troops (Royal Ghost in stealth) cannot be targeted by units
- Still hit by AOE spells and piercing projectiles
StealthAbilitycontrols visibility via fade timer
Targeting skips: inactive towers, entities without combat, deploying troops, tunneling troops, deploying buildings.
Key file: core/.../combat/TargetingSystem.java
CombatSystem handles attack execution, projectile management, and damage application.
For each entity with a Combat component:
- Skip inactive/waking towers, deploying entities, knocked-back entities
- If no target: reset attack state, return
- If out of attack range: unlock target, cancel ongoing attack
- Lock target (in range)
- If cooldown > 0: wait
- If charged (via
ChargeAbility): skip windup, execute immediately - Start attack: initiate windup timer
- If
attackDashTime > 0(Bat): lunge toward target during windup
- If
- Windup complete: execute attack
Threshold: RANGED_THRESHOLD = 2.0f tiles. Range >= 2.0 uses projectiles.
Melee attack:
- Apply pre-damage effects (e.g. CURSE)
- Deal damage (adjusted for crown tower via
crownTowerDamagePercent) - If
aoeRadius > 0:AoeDamageService.applySpellDamage()centered on attacker or target (selfAsAoeCenter) - Apply
buffOnDamage(melee only) - Check REFLECT ability on target
Ranged attack:
scatter != null+multipleProjectiles > 1:fireScatterProjectiles()(Hunter shotgun)- Otherwise:
createAttackProjectile()viaProjectileFactory, spawn intoGameState.projectiles - If returning projectile (Executioner): disable combat until projectile returns
baseDamage = damageOverride > 0 ? damageOverride : effectiveDamage
baseDamage += ChargeHandler.getChargeDamage(ability, baseDamage)
baseDamage += BuffAllyHandler.processGiantBuffHit(attacker, target, combat)
Crown tower adjustment via DamageUtil.adjustForCrownTower():
effectiveDamage = max(1, baseDamage * (100 + crownTowerDamagePercent) / 100)
Example: Miner with crownTowerDamagePercent = -75 deals 25% damage to towers.
Units with multipleTargets > 1 (ElectroWizard hits 2):
- After primary attack, find extra targets within attack range (closest first)
- Each extra target receives full damage + buff-on-damage
- If not enough extra targets, fallback shots hit primary again
Units with kamikaze=true (Battle Ram): die immediately after executing their attack.
loadTime-- troops accumulate charge while idle/moving/deploying, capped atloadTime- First attack windup:
max(0, attackCooldown - accumulatedLoadTime) - Load time preloading: spawned troops start with
accumulatedLoadTime = loadTime(fully charged) - Exception:
noPreload=true(Sparky) starts at 0 - Combat timer ticking (cooldown, windup, load time accumulation) is handled by
CombatSystem, not by entity update methods
Combat.attackSequence enables multi-hit combos (Berserker). attackSequenceIndex cycles through
the sequence, each entry may override damage.
areaEffectOnHit: spawn heal zone (BattleHealer)attackPushBack > 0: recoil knockback on attacker (Firecracker)kamikaze: attacker dies
Key files:
core/.../combat/CombatSystem.javacore/.../combat/AoeDamageService.javacore/.../combat/ProjectileSystem.javacore/.../combat/KnockbackHelper.javacore/.../combat/DamageUtil.javacore/.../component/Combat.java
AbilitySystem dispatches to type-specific AbilityHandler implementations via a registry map.
Runs before CombatSystem so damage modifications apply on the current tick.
Runtime state for all ability types lives in AbilityComponent.
| Type | Record Class | Handler | Example Units |
|---|---|---|---|
| CHARGE | ChargeAbility |
ChargeHandler |
Prince, Dark Prince, Ram Rider |
| VARIABLE_DAMAGE | VariableDamageAbility |
VariableDamageHandler |
Inferno Tower, Inferno Dragon |
| DASH | DashAbility |
DashHandler |
Bandit, Mega Knight |
| HOOK | HookAbility |
HookHandler |
Fisherman |
| REFLECT | ReflectAbility |
ReflectHandler |
Electro Giant |
| TUNNEL | TunnelAbility |
TunnelHandler |
Miner, Goblin Drill |
| STEALTH | StealthAbility |
StealthHandler |
Royal Ghost |
| HIDING | HidingAbility |
HidingHandler |
Tesla |
| BUFF_ALLY | BuffAllyAbility |
BuffAllyHandler |
Rune Giant (GiantBuffer) |
| RANGED_ATTACK | RangedAttackAbility |
RangedAttackHandler |
Goblin Machine |
CHARGE -- Builds charge from continuous uninterrupted movement. Charged first attack deals
chargeDamage bonus and applies speedMultiplier while moving. Resets on attack, stun, knockback,
or freeze. ChargeHandler.getChargeDamage() and consumeCharge() are static helpers called by
CombatSystem.
VARIABLE_DAMAGE -- Damage escalates through ordered stages while attacking the same target. Each
VariableDamageStage has a duration and damage value. Resets to stage 0 when target changes or on
stun/freeze. Sets Combat.damageOverride.
DASH -- States: IDLE -> DASHING -> LANDING. Acquisition range [dashMinRange, dashMaxRange].
Provides invulnerability during flight (dashImmuneTime). DASH_SPEED = 15 tiles/sec. Landing deals
dashDamage in dashRadius with optional dashPushback.
HOOK -- States: IDLE -> WINDING_UP -> PULLING -> DRAGGING_SELF. Pulls target toward Fisherman (
buildings cannot be pulled, skips to DRAGGING_SELF). Speed base: raw speed / 60.0.
ModifierSource.ABILITY_HOOK persists through StatusEffect resets.
REFLECT -- Passive. Called by CombatSystem on melee hit. Reflects reflectDamage back to
attacker within reflectRadius, with optional status effect (reflectBuff). Adjusts for
reflectCrownTowerDamagePercent.
TUNNEL -- States: INACTIVE -> TUNNELING -> EMERGED. 8-directional underground movement with
waypoint-based routing. Runs during deployment (unlike other abilities). TunnelMorphHandler
converts tunneling troop into building on arrival.
STEALTH -- While not attacking: fadeTime ticks toward invisibility. While attacking:
attackGracePeriod before becoming visible. Spawns invisible with pre-filled fade timer. Invisible
troops are untargetable but hittable by AOE.
HIDING -- States: HIDDEN -> REVEALING -> UP -> HIDING -> HIDDEN. HIDDEN: combat disabled, not targetable. REVEALING: targetable, combat disabled. UP: normal operation. Target detection cancels hide transition back to UP.
BUFF_ALLY -- Every cooldown seconds (after initial actionDelay), targets maxTargets
closest friendly troops within searchRange. Applies damage buff; buffed troops deal addedDamage
bonus every attackAmountth attack. persistAfterDeath seconds keeps buff active after source
dies. damageMultipliers list provides per-projectile/unit overrides.
RANGED_ATTACK -- Independent secondary ranged attack for dual-attack units. States: IDLE ->
WINDING_UP -> ATTACK_DELAY -> COOLDOWN. Fires projectiles independently from primary CombatSystem
attack with its own targetType filtering.
DashState: IDLE, DASHING, LANDINGHookState: IDLE, WINDING_UP, PULLING, DRAGGING_SELFTunnelState: INACTIVE, TUNNELING, EMERGEDHidingState: HIDDEN, REVEALING, UP, HIDINGRangedAttackState: IDLE, WINDING_UP, ATTACK_DELAY, COOLDOWN
Key files:
core/.../ability/AbilitySystem.javacore/.../ability/AbilityComponent.javacore/.../ability/AbilityType.javacore/.../ability/AbilityData.java(sealed interface with 10 record permits)core/.../ability/*Handler.java(10 handler files)