Overview
A Verse map is a container of key-value pairs where every key must be unique. When you write a map literal with duplicate keys, only the last value is kept — earlier entries are silently overwritten:
WordCount:[string]int = map{
"apple" => 0,
"apple" => 1,
"apple" => 2 # only this survives; WordCount["apple"] = 2
}
This uniqueness contract is enforced at compile time for literals and at runtime for dynamic updates. It means you can use a map as a deduplication ledger: once a key exists, you know that thing has happened exactly once (or N times, if the value is a counter).
When to reach for it:
- Track which players have stepped on a trigger (each
agentkey is unique). - Count how many times each zone has been visited without worrying about duplicate entries.
- Store per-player state (score, flag, status) keyed by
agent. - Deduplicate events fired by multiple overlapping trigger devices.
Types that CAN be map keys: int, float, string, bool, logic, enums, <unique> classes, and agent.
Types that CANNOT: plain classes (without <unique>), interfaces (without <unique>), function types, type, false.
API Reference
trigger_device
Used to relay events to other linked devices.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse). Inherited members are merged from trigger_base_device.
trigger_device<public> := class<concrete><final>(trigger_base_device):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
TriggeredEvent |
TriggeredEvent<public>:listenable(?agent) |
Signaled when an agent triggers this device. Sends the agent that used this device. Returns false if no agent triggered the action (ex: it was triggered through code). |
Methods (call these to make the device act):
| Method | Signature | Description |
|---|---|---|
Trigger |
Trigger<public>(Agent:agent):void |
Triggers this device with Agent being passed as the agent that triggered the action. Use an agent reference when this device is setup to require one (for instance, you want to trigger the device only with a particular agent. |
Trigger |
Trigger<public>():void |
Triggers this device, causing it to activate its TriggeredEvent event. |
Enable |
Enable<public>():void |
Enables this device. |
Disable |
Disable<public>():void |
Disables this device. |
SetMaxTriggerCount |
SetMaxTriggerCount<public>(MaxCount:int):void |
Sets the maximum amount of times this device can trigger. * 0 can be used to indicate no limit on trigger count. * MaxCount is clamped between [0,20]. |
GetMaxTriggerCount |
GetMaxTriggerCount<public>()<transacts>:int |
Gets the maximum amount of times this device can trigger. * 0 indicates no limit on trigger count. |
GetTriggerCountRemaining |
GetTriggerCountRemaining<public>()<transacts>:int |
Returns the number of times that this device can still be triggered before hitting GetMaxTriggerCount. Returns 0 if GetMaxTriggerCount is unlimited. |
SetResetDelay |
SetResetDelay<public>(Time:float):void |
Sets the time (in seconds) after triggering, before the device can be triggered again (if MaxTrigger count allows). |
GetResetDelay |
GetResetDelay<public>()<transacts>:float |
Gets the time (in seconds) before the device can be triggered again (if MaxTrigger count allows). |
SetTransmitDelay |
SetTransmitDelay<public>(Time:float):void |
Sets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered. |
GetTransmitDelay |
GetTransmitDelay<public>()<transacts>:float |
Gets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered. |
hud_message_device
Used to show custom HUD messages to one or more
agents.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
hud_message_device<public> := class<concrete><final>(creative_device_base):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
ShowMessageEvent |
ShowMessageEvent<public>:listenable(agent) |
Called when a Message has been Shown on-screen. Returns an Agent if it was Shown on a specified Agent's screen. |
HideMessageEvent |
HideMessageEvent<public>:listenable(agent) |
Called when a Message has been Hidden on-screen. Returns an Agent if it was Hidden from a specified Agent's screen. |
ClearAllMessagesEvent |
ClearAllMessagesEvent<public>:listenable(agent) |
Called when all queued Messages from all players that are affected by this HUD Message Device have been cleared. |
Methods (call these to make the device act):
| Method | Signature | Description |
|---|---|---|
Show |
Show<public>(Agent:agent):void |
Shows the currently set HUD Message on Agents screen. Will replace any previously active message. Use this when the device is setup to target specific agents. |
Show |
Show<public>():void |
Shows the currently set Message HUD message on screen. Will replace any previously active message. |
Hide |
Hide<public>():void |
Hides the HUD message. |
Hide |
Hide<public>(Agent:agent):void |
Hides the currently set HUD Message on Agents screen. Use this when the device is setup to target specific agents. |
Show |
Show<public>(Agent:agent, Message:message, ?DisplayTime:float |
Displays a Custom message to a specific Agent that you define.Setting DisplayTime to 0.0 will display the HUD message persistently.If not defined, or less than 0.0 the message will show for the time set on the device. |
Show |
Show<public>(Message:message, ?DisplayTime:float |
Displays a Custom message that you define for all PlayersSetting DisplayTime to 0.0 will display the HUD message persistently.If not defined, or less than 0.0 the message will show for the time set on the device. |
SetDisplayTime |
SetDisplayTime<public>(Time:float):void |
Sets the time (in seconds) the HUD message will be displayed. 0.0 will display the HUD message persistently. |
GetDisplayTime |
GetDisplayTime<public>()<transacts>:float |
Returns the time (in seconds) for which the HUD message will be displayed. 0.0 means the message is displayed persistently. |
SetText |
SetText<public>(Text:message):void |
Sets the Message to be displayed when the HUD message is activated. Text is clamped to 150 characters. |
ClearAllMessages |
ClearAllMessages<public>():void |
Clears all queued Messages from all players that are affected by this HUD Message Device. |
Walkthrough
Scenario: Pirate Cove — Three Treasure Triggers, One HUD Scoreboard
Your island has three buried-treasure spots along the cove shore. Each spot has a trigger_device (players walk over it to "dig up" the chest). A hud_message_device shows the crew how many unique chests each player has found. Because a player might accidentally walk over the same trigger twice, you use a [agent][int] map to count unique discoveries per player — the map-key guarantee means each player key only ever appears once, and you update the integer value to track their running total.
using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
using { /Verse.org/Verse }
# Localized message helpers
FoundChest<localizes>(N:int):message = "Treasure found! You have {N} chest(s) this run."
AlreadyFound<localizes>():message = "You already looted this chest!"
AllFound<localizes>():message = "LEGENDARY HAUL! All 3 chests found — you are the Pirate King!"
pirate_cove_treasure_manager := class(creative_device):
# ── Devices placed on the island ──────────────────────────────────────────
@editable ChestTrigger1 : trigger_device = trigger_device{}
@editable ChestTrigger2 : trigger_device = trigger_device{}
@editable ChestTrigger3 : trigger_device = trigger_device{}
@editable ScoreHUD : hud_message_device = hud_message_device{}
# ── Per-player chest count: [agent]int ────────────────────────────────────
# Keys are unique agents; value = number of UNIQUE chests found this session.
var PlayerChestCount : [agent]int = map{}
# Track which (player, chest-index) pairs have already been looted.
# Key: agent Value: set encoded as [int]int (chest index => 1)
var PlayerLootedChests : [agent][int]int = map{}
OnBegin<override>()<suspends>:void =
# Each trigger fires at most 3 times total (one per player visit is fine;
# we deduplicate per-player in code). Reset delay = 0.5 s so rapid
# re-entries don't spam the event.
ChestTrigger1.SetMaxTriggerCount(0) # 0 = unlimited
ChestTrigger1.SetResetDelay(0.5)
ChestTrigger2.SetMaxTriggerCount(0)
ChestTrigger2.SetResetDelay(0.5)
ChestTrigger3.SetMaxTriggerCount(0)
ChestTrigger3.SetResetDelay(0.5)
# Display time: 3 seconds per message
ScoreHUD.SetDisplayTime(3.0)
# Subscribe handlers
ChestTrigger1.TriggeredEvent.Subscribe(OnChest1Triggered)
ChestTrigger2.TriggeredEvent.Subscribe(OnChest2Triggered)
ChestTrigger3.TriggeredEvent.Subscribe(OnChest3Triggered)
# ── Shared logic ──────────────────────────────────────────────────────────
HandleChestFound(MaybeAgent : ?agent, ChestIndex : int):void =
# Unwrap the optional agent (trigger fires with ?agent)
if (A := MaybeAgent?):
# Ensure the player has a loot-record map; if not, create one.
PlayerLooted := if (Existing := PlayerLootedChests[A]) then Existing else map{}
# Check if this chest was already looted by this player.
# map key uniqueness: if ChestIndex key exists, they already got it.
if (PlayerLooted[ChestIndex] = 1):
# Already looted — show a gentle reminder
ScoreHUD.Show(A, AlreadyFound())
else:
# New chest! Record it. Build a new map with the chest index added.
var NewPlayerLooted : [int]int = map{ChestIndex => 1}
# Merge existing looted chests into the new map
for (Idx -> Val : PlayerLooted):
set NewPlayerLooted = map{Idx => Val}
set PlayerLootedChests = map{A => NewPlayerLooted}
# Update the running total for this player
PrevCount := if (C := PlayerChestCount[A]) then C else 0
NewCount := PrevCount + 1
set PlayerChestCount = map{A => NewCount}
# Show score HUD
if (NewCount >= 3):
ScoreHUD.Show(A, AllFound(), ?DisplayTime := 6.0)
else:
ScoreHUD.Show(A, FoundChest(NewCount))
# ── Per-trigger event handlers ────────────────────────────────────────────
OnChest1Triggered(MaybeAgent : ?agent):void =
HandleChestFound(MaybeAgent, 1)
OnChest2Triggered(MaybeAgent : ?agent):void =
HandleChestFound(MaybeAgent, 2)
OnChest3Triggered(MaybeAgent : ?agent):void =
HandleChestFound(MaybeAgent, 3)```
### Line-by-line explanation
| Lines | What's happening |
|---|---|
| `@editable` fields | Every device you want to call from Verse must be declared as an editable field on the class. You then wire them up in the UEFN editor. |
| `var PlayerChestCount : [agent]int` | A mutable map keyed by `agent`. Because keys are unique, each player appears at most once — no duplicate entries possible. |
| `SetMaxTriggerCount(0)` | `0` means unlimited triggers. We handle deduplication ourselves in the map. |
| `SetResetDelay(0.5)` | Half-second cooldown prevents event spam from a player standing on the trigger. |
| `SetDisplayTime(3.0)` | Tells the HUD device to auto-hide messages after 3 seconds. |
| `TriggeredEvent.Subscribe(OnChest1Triggered)` | Subscribes a class-scope method. The handler signature matches `listenable(?agent)`. |
| `if (A := MaybeAgent?)` | Unwraps the `?agent` — mandatory before you can use it as a map key. |
| `PlayerLooted[ChestIndex] = 1` | Fails (failable expression) if the key doesn't exist — that's how we detect "already looted". |
| `set PlayerChestCount = map{A => NewCount}` | Rebuilds the map with the updated value. Because `A` is unique, any previous entry for this agent is overwritten. |
| `ScoreHUD.Show(A, AllFound(), ?DisplayTime := 6.0)` | Shows a custom message to a specific agent with a custom display time override. |
## Common patterns
### Pattern 1 — Programmatic trigger + HUD broadcast (no agent required)
Sometimes you want to fire a trigger from code (e.g., a timed event) and broadcast a message to everyone. This pattern uses `Trigger()` (no agent) and `Show()` (no agent).
```verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Verse }
TideWarning<localizes>():message = "The tide is rising! Head for the clifftop!"
tide_alarm_manager := class(creative_device):
@editable TideAlarmTrigger : trigger_device = trigger_device{}
@editable TideHUD : hud_message_device = hud_message_device{}
# Track how many tide warnings have fired this session: [int]int
# Key = wave number (unique), Value = timestamp placeholder (1)
var WavesFired : [int]int = map{}
var WaveNumber : int = 0
OnBegin<override>()<suspends>:void =
TideAlarmTrigger.SetMaxTriggerCount(5) # max 5 tide warnings per game
TideAlarmTrigger.SetTransmitDelay(1.0) # 1-second delay before linked devices respond
TideHUD.SetDisplayTime(4.0)
# Subscribe to know when the trigger fires (even from code)
TideAlarmTrigger.TriggeredEvent.Subscribe(OnTideAlarm)
# Simulate tide rising every 30 seconds
loop:
Sleep(30.0)
Remaining := TideAlarmTrigger.GetTriggerCountRemaining()
if (Remaining > 0):
TideAlarmTrigger.Trigger() # fire with no agent
OnTideAlarm(MaybeAgent : ?agent):void =
# Record this wave; map key uniqueness ensures no duplicate wave numbers
set WaveNumber = WaveNumber + 1
set WavesFired = map{WaveNumber => 1}
# Broadcast to all players — no agent version
TideHUD.Show(TideWarning())
Pattern 2 — Enable/Disable a trigger based on map state, hide HUD per-player
This pattern shows Enable/Disable on the trigger and Hide(Agent) on the HUD — useful for a lagoon gate that locks after the first player passes through.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Verse }
GateOpen<localizes>():message = "The lagoon gate is open — swim through!"
GateLocked<localizes>():message = "The gate has closed behind you."
lagoon_gate_manager := class(creative_device):
@editable LagoonGateTrigger : trigger_device = trigger_device{}
@editable GateHUD : hud_message_device = hud_message_device{}
# Map of agents who have passed through: [agent]logic
# logic keys are unique per agent — once set to true, they passed.
var PassedThrough : [agent]logic = map{}
OnBegin<override>()<suspends>:void =
# Only one player can open the gate per game session
LagoonGateTrigger.SetMaxTriggerCount(1)
LagoonGateTrigger.SetResetDelay(0.0)
GateHUD.SetDisplayTime(0.0) # persistent until hidden manually
LagoonGateTrigger.TriggeredEvent.Subscribe(OnGateTriggered)
OnGateTriggered(MaybeAgent : ?agent):void =
if (A := MaybeAgent?):
# Check map: if agent key already exists, they already passed
if (PassedThrough[A]):
GateHUD.Hide(A) # hide any lingering message for this agent
else:
# Record passage — map key uniqueness: this agent is now in the ledger
set PassedThrough = map{A => true}
GateHUD.Show(A, GateOpen())
# Disable the trigger so no one else can open it this session
LagoonGateTrigger.Disable()
Sleep(5.0)
GateHUD.Show(A, GateLocked())
Sleep(3.0)
GateHUD.Hide(A)
Pattern 3 — ClearAllMessages and re-enable trigger for a new round
This pattern shows ClearAllMessages() and Enable() — handy for resetting the island between rounds.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Verse }
RoundStart<localizes>(N:int):message = "Round {N} — find the hidden cove!"
cove_round_manager := class(creative_device):
@editable RoundStartTrigger : trigger_device = trigger_device{}
@editable RoundHUD : hud_message_device = hud_message_device{}
# Round number tracked in a map so each round key is unique
var RoundLog : [int]int = map{} # round number => 1
var CurrentRound : int = 0
OnBegin<override>()<suspends>:void =
RoundStartTrigger.SetMaxTriggerCount(0) # unlimited rounds
RoundStartTrigger.SetResetDelay(2.0)
RoundHUD.SetDisplayTime(5.0)
RoundStartTrigger.TriggeredEvent.Subscribe(OnRoundStart)
OnRoundStart(MaybeAgent : ?agent):void =
# Clear all lingering HUD messages from the previous round
RoundHUD.ClearAllMessages()
# Advance round counter; map key uniqueness records each round once
set CurrentRound = CurrentRound + 1
set RoundLog = map{CurrentRound => 1}
# Broadcast new round message to all players
RoundHUD.Show(RoundStart(CurrentRound))
# Re-enable the trigger in case it was disabled by another system
RoundStartTrigger.Enable()
Gotchas
1. Duplicate keys silently overwrite — this is the feature, not a bug
When you write set MyMap = map{A => NewValue} for an agent key that already exists, the old value is replaced. This is exactly what makes maps useful as per-player ledgers. But if you accidentally rebuild a map without carrying over existing entries, you will lose data. Always iterate over the old map and merge when you need to preserve prior entries.
2. Accessing a missing key is failable — always guard it
# WRONG — crashes if A has no entry yet
Count := PlayerChestCount[A]
# CORRECT — use if/else to provide a default
Count := if (C := PlayerChestCount[A]) then C else 0
3. ?agent must be unwrapped before use as a key
TriggeredEvent is a listenable(?agent). The ?agent is an option type — you cannot use it directly as a map key. Always unwrap first:
OnTriggered(MaybeAgent : ?agent):void =
if (A := MaybeAgent?): # A is now agent, safe to use as map key
set MyMap = map{A => 1}
4. message parameters require localized values — not raw strings
Methods like Show(Agent, Message:message, ...) and SetText(Text:message) take message, not string. Declare a localizes helper:
MyLabel<localizes>(S:string):message = "{S}"
# Then pass: ScoreHUD.Show(A, MyLabel("Hello!"))
There is no StringToMessage function in Verse.
5. SetMaxTriggerCount clamps between 0 and 20
Passing a value outside [0, 20] is silently clamped. Use 0 for unlimited. If you need more than 20 discrete trigger events, manage the count yourself in a map.
6. GetTriggerCountRemaining returns 0 when the limit is unlimited
If GetMaxTriggerCount() returns 0 (unlimited), then GetTriggerCountRemaining() also returns 0 — not a large number. Check GetMaxTriggerCount() first to distinguish "unlimited" from "exhausted".
7. Verse does not auto-convert int ↔ float
SetResetDelay and SetDisplayTime take float. Write 0.5 not 0 or you will get a type error. Similarly, SetMaxTriggerCount takes int — pass 3 not 3.0.