td

CommandsAndEvents
Login

CommandsAndEvents

Home · Architecture · Combat · Extensions · Known gaps · Source index

Commands and Events

Why commands exist

Behavior hooks often need to affect entities other than the one currently borrowed. src/command.rs turns those writes into data. A pass appends commands, releases its vector/grid borrows, and then src/world.rs applies every command at one non-reentrant mutation point.

hook mutates its local entity
  + queues cross-world effects
            |
            v
      CommandBuffer
            |
    pass borrow ends
            |
            v
     apply_commands
      |          |
 World mutation  GameEvent list
                     |
                     v
              deliver_events

Command set

Command Applied behavior Status
SpawnProjectile Allocate a new ID and append Live, but no current behavior emits one
SpawnPool Append without allocating an ID Callable; resulting pools are unwired
SpawnEnemy Allocate a new ID and append Live-capable
Damage Resolve against current enemy, record meter, emit kill event if lethal Live
ApplyStatus Append boxed status to current enemy Live
Despawn Remove matching enemy and projectile IDs Live
GrantResource Add WP as player XP; ignore SP and TP WP live; SP/TP stubs
Emit Append a game event to the current event wave Live
SpawnAnimation Allocate ID and append Live
Replicate Rebuild an enemy of same kind/tier, clone behaviors, relocate, allocate ID Live through blob splitting

CommandBuffer offers one convenience method per variant. There is no validation, deduplication, scheduling timestamp, cancellation, or stable ordering beyond vector insertion order.

Damage application

Command::Damage looks up the target by ID. Missing targets are silently ignored. For a present target it calls Enemy::apply_damage, creates an EnemyKilled(Corpse) event when lethal, and records actual post-resistance damage in the tower's delivery-specific meter only when source is Tower.

The command carries a DamageMap, not a scalar. Each kind resolves flat/increased/more math before resistance. The DamageRegistry's boxed DamageBehavior is not called in this path.

Event set and delivery

Event Handler
EnemyKilled(Corpse) Spawn 2.5-second death animation; grant corpse XP as WP
DespawnEnemy(EntityId) Queue Despawn
Custom(String) No action

Events are delivered in waves. Each wave gets a fresh command buffer, a fresh enemy snapshot/grid rebuild, and a forked RNG. Event handlers queue commands; applying those commands produces the next wave.

events[0]
  -> handle all, queue commands[0]
  -> apply, collect events[1]
  -> handle all, queue commands[1]
  -> ...
  -> stop when empty or after 8 waves

The eight-wave guard prevents unbounded cascades. Remaining events are silently dropped; the suggested logging/assertion exists only as a comment.

Ordering guarantees

Replication semantics

Replicate(Enemy) finds the live source by ID, copies its kind, tier, and cloned behavior list, asks EnemyRegistry for a fresh template, replaces the template behaviors with the clones, places the child, allocates an ID, and appends directly.

This preserves mutated behavior state such as the decremented SplitOnDeath generation counter. It resets other enemy state to registry defaults: HP, XP, statuses, resistances, velocity, killer, drawing, and radius come from the factory. If the source is already absent, replication does nothing.

Current edge cases

See Frame Lifecycle for when buffers drain and Behavior System for how hooks receive command access.

Home · Architecture · Combat · Extensions · Known gaps · Source index