Part of the Architecture Reference.
DeploymentSystem processes card play requests through a two-phase pipeline.
PLACEMENT_SYNC_DELAY = 1.0fseconds -- server sync delay before spawningSTAGGER_DELAY = 0.1fseconds -- delay between multi-unit troop spawns
- Request phase:
queueAction(player, action)adds to thread-safeConcurrentLinkedQueue. On drain,player.tryPlayCard()spends elixir and cycles hand. - Sync phase:
PendingDeployment.remainingDelaycounts down from 1.0s. - Stagger phase (multi-unit troops only): One unit spawned per stagger tick (0.1s apart). Deploy effects and spawn projectiles fire only on the first unit.
- Buildings and spells spawn all at once after sync (no stagger).
- Deploy effects:
Card.deployEffectfires anAreaEffectat deploy position on first unit spawn (e.g. ElectroWizard zap on landing) - Spawn projectiles:
Card.spawnProjectilefires a projectile at deploy position (e.g. MegaKnight landing damage) - Tunnel buildings:
DeploymentSystemcallsentityFactory.spawnTunnelBuilding()for Miner/Goblin Drill; the tunneling troop morphs into a building on arrival viaTunnelMorphHandler - Variant resolution: Some cards resolve to different forms based on game state (e.g. MergeMaiden mounted vs. normal, determined by pre-spend elixir)
Key files:
core/.../engine/DeploymentSystem.java
Two systems handle spawning: SpawnerSystem for live/periodic spawning, DeathHandler for
death-triggered mechanics.
SpawnerComponent uses a two-state FSM:
- WAITING_FOR_WAVE: counts down
spawnPauseTime(orspawnStartTimefor initial delay) - SPAWNING_WAVE: counts down
spawnIntervalbetween units within a wave
Configuration: spawnPauseTime (delay between waves), spawnInterval (delay within wave),
unitsPerWave, spawnStartTime (initial delay).
Pausing behavior:
- Stunned/frozen buildings: timer pauses but does NOT reset; resumes from same point
spawnOnAggro=true: timer only ticks when enemies detected withinaggroDetectionRange- Deploying entities: timer does not tick
Spawn limits: spawnLimit caps total spawned units; destroyAtLimit=true kills parent when limit
reached.
Clone propagation: if parent is clone, spawned children are also clones (1 HP).
Units with health=0 in stats are treated as bombs:
- Spawned with 1 HP and
selfDestruct=true - Deploy timer counts down (bomb fall animation)
- After deployment completes,
SpawnerSystemkills the bomb - Death triggers AOE damage via
DeathHandler - Examples: BalloonBomb (3.0s deploy), GiantSkeletonBomb
DeathHandler.onDeath() executes in this order:
- Elixir grants (owner): Building's
manaOnDeathviaElixirCollectorComponent - Death damage AOE:
AoeDamageService.applySpellDamage()in radius + knockback - Death spawns: immediate or delayed via
DeathSpawnEntrylist- Immediate spawns (
spawnDelay=0): fire viaSpawnFactory.doSpawn() - Delayed spawns (
spawnDelay>0): queued topendingDeathSpawns, ticked byprocessDelayedSpawns() - Formation positioning via
FormationLayout.calculateOffset()(circular layout) - Clone status propagated from parent to children
- Immediate spawns (
- Death area effect: spawns
AreaEffectentity (e.g. RageBarbarianBottle drops Rage zone) - Elixir grants (opponent):
manaOnDeathForOpponentgrants elixir to enemy (Elixir Golem) - Death projectile: fires projectile at death location (Phoenix -> PhoenixFireball), may spawn character on impact
- Curse spawns: CURSE effects trigger character spawn for the applying team (Mother Witch -> Cursed Hog)
SpawnFactory.doSpawn() constructs the entity:
- Level scales HP, damage, shield via
LevelScaling.scaleCard() - Clone handling: 1 HP, shield capped to 1
- Combat component: built if damage > 0 or projectile exists
- Preload:
accumulatedLoadTime = noPreload ? 0 : loadTime - Deploy time: from TroopStats for bombs, from parameter for death spawns, else 0
- Wires
SpawnerComponentfor death mechanics, bomb behavior, live spawn capability
FormationLayout.calculateOffset() for N units in a circle:
- N == 1: offset (0, 0)
- N > 1: evenly spaced on circle with
spawnRadius; odd N starts at angle PI/2 (top), even N at angle 0 (right) TILE_SCALE = 355.0fconverts raw CSV summonRadius to tile units
Key files:
core/.../entity/SpawnerSystem.javacore/.../entity/SpawnFactory.javacore/.../entity/DeathHandler.javacore/.../component/SpawnerComponent.javacore/.../util/FormationLayout.java
TransformationSystem checks alive troops each tick for HP thresholds and replaces them with a new
form.
- For each
TroopwithtransformConfig != nulland!transformed:- Skip if deploying
- Check
health.percentage() * 100 <= config.healthPercent()
- On threshold:
transform(troop, config):- Capture old position, current HP, level, rarity
old.markDead()-- suppresses death handlers- Remove old entity from
GameState - Scale new form's stats by level/rarity
- Build replacement
Troopwithtransformed=true - Proportional HP carryover (capped to new form's max)
- Reset attack cooldown/load time
- New form's movement stats and death spawns apply
Example: GoblinDemolisher at 50% HP -> kamikaze form with shorter lifetime, different movement stats, and its own death spawns.
Key file: core/.../engine/TransformationSystem.java