Reference Verse compiles

automated_turret_device: Smart Sentinels That React to Your Game State

The Automated Turret device is a self-aiming sentinel that hunts players within a configurable radius. Out of the box it works fine — but from Verse you can dynamically crank up its damage mid-round, shrink its detection cone for a stealth section, force it to drop its current target, and even swap its behaviour when a player changes class. This article shows you every runtime knob the API exposes and how to wire them into real game scenarios.

Updated Examples verified on the live UEFN compiler
Watch the Knotone_device_per_class in ~90 seconds.

Overview

The automated_turret_device is a placeable Creative device that automatically tracks and fires at players (or AI) who enter its range. Without any code it works as a static hazard, but the Verse API lets you:

  • Tune lethality at runtimeSetDamage scales shots from a tickle to an instant-kill.
  • Resize the threat bubbleSetActivationRange and SetTargetRange control when the turret wakes up and how far it can lock on.
  • Interrupt targetingClearTarget drops the current lock and forces a fresh scan.
  • Query who it is shootingGetTarget returns the current victim as ?agent.
  • React to class changesClassChangedEvent from the Class Selector UI device fires whenever a player picks a new class, letting you reconfigure turrets on the fly.

Reach for this device whenever you need a programmable defensive hazard: boss-room guardians that get harder each wave, stealth missions where turrets go blind in safe zones, or asymmetric modes where one team's class makes turrets ignore them.

API Reference

(API surface could not be resolved for this device.)

Walkthrough

Scenario: Escalating Boss Room

A player enters a boss arena. A turret starts at low damage and short range. Every 15 seconds the turret becomes more dangerous. When the player changes to the "Tank" class (index 1) the turret drops its current target and resets — giving the Tank a brief window to reposition.

using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/Patchwork }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Conversations }

boss_room_manager := class<concrete>(creative_device):

    # ── Editable references — drag devices here in UEFN ──────────────────
    @editable
    Turret : automated_turret_device = automated_turret_device{}

    @editable
    ClassSelector : class_selector_ui_device = class_selector_ui_device{}

    # ── Escalation settings ───────────────────────────────────────────────
    # Starting values
    InitialDamage    : float = 10.0
    InitialActRange  : float = 15.0
    InitialTgtRange  : float = 12.0

    # How much each stat grows per escalation tick
    DamageStep       : float = 15.0
    RangeStep        : float = 8.0

    # Seconds between escalation ticks
    EscalationInterval : float = 15.0

    # ── Internal mutable state ────────────────────────────────────────────
    var CurrentDamage   : float = 10.0
    var CurrentActRange : float = 15.0
    var CurrentTgtRange : float = 12.0

    # ── Entry point ───────────────────────────────────────────────────────
    OnBegin<override>()<suspends> : void =
        # Apply initial stats
        Turret.SetDamage(InitialDamage)
        Turret.SetActivationRange(InitialActRange)
        Turret.SetTargetRange(InitialTgtRange)

        set CurrentDamage   = InitialDamage
        set CurrentActRange = InitialActRange
        set CurrentTgtRange = InitialTgtRange

        # Subscribe to class-change events so Tank players get a reprieve
        ClassSelector.ClassChangedEvent.Subscribe(OnClassChanged)

        # Run the escalation loop concurrently
        spawn { EscalationLoop() }

    # ── Escalation loop ───────────────────────────────────────────────────
    EscalationLoop()<suspends> : void =
        loop:
            Sleep(EscalationInterval)

            set CurrentDamage   = CurrentDamage   + DamageStep
            set CurrentActRange = CurrentActRange + RangeStep
            set CurrentTgtRange = CurrentTgtRange + RangeStep

            # Clamp ranges to the API maximum of 100 m
            var ClampedAct : float = CurrentActRange
            var ClampedTgt : float = CurrentTgtRange
            if (CurrentActRange > 100.0):
                set ClampedAct = 100.0
            if (CurrentTgtRange > 100.0):
                set ClampedTgt = 100.0

            Turret.SetDamage(CurrentDamage)
            Turret.SetActivationRange(ClampedAct)
            Turret.SetTargetRange(ClampedTgt)

    # ── Class-change handler ──────────────────────────────────────────────
    # Signature matches listenable(tuple(agent, int))
    OnClassChanged(Payload : tuple(agent, int)) : void =
        ChangingAgent := Payload(0)
        ClassIndex := Payload(1)

        # Class index 1 = "Tank" — give them a targeting reset window
        if (ClassIndex = 1):
            Turret.ClearTarget()```

### Line-by-line explanation

| Lines | What's happening |
|---|---|
| `@editable Turret` | Declares the turret so UEFN can wire the placed device to this field. Without `@editable` the field is just a default-constructed stub — it won't control anything in the level. |
| `@editable ClassSelector` | Same pattern for the Class Selector UI device whose `ClassChangedEvent` we need. |
| `Turret.SetDamage(InitialDamage)` | Sets per-shot damage to 10. The parameter is `float`, so `10.0` not `10`. |
| `Turret.SetActivationRange(InitialActRange)` | The turret wakes up when a player enters 15 m. Clamped by the API to 2100 m. |
| `Turret.SetTargetRange(InitialTgtRange)` | Separate from activation  the turret can wake up at 15 m but only lock on within 12 m. |
| `ClassSelector.ClassChangedEvent.Subscribe(OnClassChanged)` | Hooks the handler. The event fires `tuple(agent, int)` so the handler must accept exactly that type. |
| `spawn { EscalationLoop() }` | Runs the loop concurrently so `OnBegin` returns and the game starts. |
| `Sleep(EscalationInterval)` | Suspends the loop fiber for 15 seconds each tick. |
| `Turret.ClearTarget()` | Forces the turret to drop its lock and re-scan. If the Tank is still in range it will likely be reacquired, but the brief gap lets them dodge. |

## Common patterns

### Pattern 1 — Query the current target and react

Use `GetTarget` to check whether the turret is actively shooting someone, then take action (e.g. warn the targeted player).

```verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

target_watcher := class<concrete>(creative_device):

    @editable
    Turret : automated_turret_device = automated_turret_device{}

    @editable
    WarningNotifier : hud_message_device = hud_message_device{}

    OnBegin<override>()<suspends> : void =
        spawn { WatchTargetLoop() }

    WatchTargetLoop()<suspends> : void =
        loop:
            Sleep(2.0)   # poll every 2 seconds

            # GetTarget returns ?agent — must unwrap before use
            if (Target := Turret.GetTarget()?):
                # Turret has a live target — show the warning to that agent
                WarningNotifier.Show(Target)
            else:
                # No target — hide the warning for everyone
                WarningNotifier.Hide()

Key point: GetTarget returns ?agent (an option). The if (Target := Turret.GetTarget()?) pattern unwraps it — the body only runs when there really is a target.


Pattern 2 — Dynamic range based on game phase

Narrow the turret's range during a stealth phase, then restore full lethality when the alarm sounds.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

stealth_phase_controller := class<concrete>(creative_device):

    @editable
    Turret : automated_turret_device = automated_turret_device{}

    @editable
    AlarmTrigger : trigger_device = trigger_device{}

    # Stealth phase: very short range so players can sneak past
    StealthActivationRange : float = 4.0
    StealthTargetRange     : float = 3.0

    # Combat phase: full threat
    CombatActivationRange  : float = 40.0
    CombatTargetRange      : float = 35.0

    OnBegin<override>()<suspends> : void =
        # Start in stealth mode
        Turret.SetActivationRange(StealthActivationRange)
        Turret.SetTargetRange(StealthTargetRange)

        AlarmTrigger.TriggeredEvent.Subscribe(OnAlarmTriggered)

    OnAlarmTriggered(TriggeringAgent : ?agent) : void =
        # Alarm tripped — switch to full combat ranges
        Turret.SetActivationRange(CombatActivationRange)
        Turret.SetTargetRange(CombatTargetRange)
        # Drop whatever the turret was watching so it re-evaluates
        Turret.ClearTarget()

Key point: SetTargetRange below 2 m effectively disables targeting. Use that trick to create a "blind" turret that still physically exists but won't lock on.


Pattern 3 — Respond to every class change island-wide

Use ClassChangedEvent alone (no turret needed) to log or react to every class swap across the whole session.

using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/Patchwork }
using { /Verse.org/Simulation }

class_change_logger := class<concrete>(creative_device):

    @editable
    ClassSelector : class_and_team_selector_device = class_and_team_selector_device{}

    @editable
    HeavyTurret : automated_turret_device = automated_turret_device{}

    @editable
    LightTurret : automated_turret_device = automated_turret_device{}

    OnBegin<override>()<suspends> : void =
        ClassSelector.ClassChangedEvent.Subscribe(OnAnyClassChanged)

    # Handler receives tuple(agent, int) — destructure immediately
    OnAnyClassChanged(Payload : tuple(agent, int)) : void =
        (_, ClassIndex) := Payload
        # Class 0 = Scout  → light turret only
        # Class 2 = Heavy  → both turrets at max damage
        if (ClassIndex = 2):
            HeavyTurret.SetDamage(100.0)
            LightTurret.SetDamage(100.0)
        else:
            HeavyTurret.SetDamage(25.0)
            LightTurret.SetDamage(10.0)

Gotchas

1. Every device field MUST be @editable and wired in UEFN

Declaring Turret : automated_turret_device = automated_turret_device{} without @editable gives you a freshly constructed stub — not the device you placed in the level. Methods called on it silently do nothing. Always add @editable and drag the placed device into the property slot.

2. SetActivationRange and SetTargetRange are clamped to 2–100 m

Passing 0.0 or 1.5 won't crash, but the API silently clamps to 2.0. If you want to effectively disable targeting, set SetTargetRange to a value below 2.0 — the docs confirm this disables targeting. Don't rely on passing 0.0 to "turn off" the turret; use Disable() from creative_device_base instead.

3. GetTarget returns ?agent — always unwrap

GetTarget():?agent can return false (no target). Calling methods on the result without unwrapping is a compile error. Always use:

if (T := Turret.GetTarget()?):
    # safe to use T here

4. ClassChangedEvent handler signature is tuple(agent, int), not (agent, int) separately

The event is listenable(tuple(agent, int)). Your handler must accept a single tuple(agent, int) parameter and destructure it inside:

OnClassChanged(Payload : tuple(agent, int)) : void =
    (A, Idx) := Payload

Declaring OnClassChanged(A : agent, Idx : int) will not match the listenable signature and will fail to compile.

5. SetDamage takes a float, not an int

Verse does not auto-convert int to float. Write SetDamage(50.0), never SetDamage(50) — the latter is a type mismatch compile error.

6. ClearTarget may immediately reacquire the same target

The docs warn: if the current target is still in range it will likely be reacquired. For a meaningful reprieve, combine ClearTarget with a temporary SetTargetRange reduction, then restore range after a Sleep.

Build your own lesson with one_device_per_class

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →