Scripting

Entities & hooks

Inspect live towers, bloons and projectiles, edit them by ID, and intercept native behaviour.

These interfaces are local-only, like the rest of the script runtime. When nothing listens, the native path is unchanged: no entity snapshots or script calls are made.

Inspect live entities

fn on_new_game(ctx) {
    [#{op: "inspect", query: #{kind: "towers", offset: 0, limit: 8, tag: "my-list"}}]
}
fn on_query(ctx) {
    for tower in ctx.event.items {
        // tower.id is a stable entity ID, not an array index.
    }
    []
}

Snapshot fields

KindFields
Towerid, tower (definition ID), position, tiers, pops, damage, target, point
Bloonid, layer, bloon (definition ID), hp, position, distance, path
Projectileid, owner (tower ID or 0), position, velocity, damage, pierce, remaining

Positions are native world coordinates. Projectile velocity is world units per step; remaining is seconds. Position and remaining can be null for native infinities.

Edit by ID

#{op: "entity_patch", patch: #{kind: "bloons", id: b.id, layer: b.layer, fields: #{hp: 50}}}
#{op: "entity_patch", patch: #{kind: "projectiles", id: p.id, fields: #{damage: 10, pierce: 20, remaining: 2.0, velocity: [1.0, 0.0]}}}
#{op: "entity_patch", patch: #{kind: "towers", id: t.id, fields: #{target: "Strong"}}}
#{op: "damage_bloon", id: b.id, layer: b.layer, amount: 5}

Change or cancel native behaviour

Interceptors return a map (event callbacks return arrays):

fn before_damage(ctx) {
    if ctx.event.bloon.bloon == "Red" { return #{cancel: true}; }
    #{damage: ctx.event.damage * 2}
}
fn before_fire(ctx) { #{damage: ctx.event.damage * 2, count: 3} }
fn before_place(ctx) { #{cancel: ctx.event.tower == "BananaFarm"} }
fn before_sell(ctx) { #{cancel: ctx.event.tower == "DartMonkey"} }
fn before_upgrade(ctx) { #{cancel: ctx.event.paragon} }
InterceptorEventCan return
before_damagebloon snapshot, damagedamage (rounded to native integer), cancel
before_fireowner, position, direction (radians), count, damage, speed, pierce, tiersany of those, or cancel
before_placetower, position, costcancel
before_selltower snapshotcancel
before_upgradetower, paragon, path/cost or extracancel

Turn hooks on and off

fn on_toggle(ctx) {
    this.enabled = !this.enabled;
    [#{op: "listen", event: "before_fire", enabled: this.enabled}]
}
// Apply the last shield charge, then stop receiving damage callbacks:
fn before_damage(ctx) { #{cancel: true, unsubscribe: true} }

All declared callbacks start subscribed. listen pauses or resumes one of your own callbacks after the current one, keeping state and widgets. An interceptor can return unsubscribe: true to mute only itself after its decision. When nobody listens, the native hook skips script execution and snapshots entirely.

See Entity Lab for a complete example.

Unofficial fan project. Not affiliated with Ninja Kiwi. rusttd6.com