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 runtime —
SetDamagescales shots from a tickle to an instant-kill. - Resize the threat bubble —
SetActivationRangeandSetTargetRangecontrol when the turret wakes up and how far it can lock on. - Interrupt targeting —
ClearTargetdrops the current lock and forces a fresh scan. - Query who it is shooting —
GetTargetreturns the current victim as?agent. - React to class changes —
ClassChangedEventfrom 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 2–100 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.