Reference Verse compiles

map lookup: Treasure Coordinates on the Pirate Dock

Verse's built-in `map` type lets you store and retrieve values by key in amortized O(1) time — perfect for tracking per-player state, zone names, reward tiers, or any data that needs instant lookup. On our cel-shaded pirate dock, we'll use a map to remember which treasure chest trigger each player has already claimed, then wire `trigger_device` events to gate repeat looting and fire cinematic reveals.

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

Overview

A map in Verse is an immutable associative container that maps keys to values. You declare one with map{ Key => Value, … } and read from it with MyMap[Key] — a failable expression that succeeds when the key exists and fails (without crashing) when it doesn't. A var map can be reassigned entirely to add or remove entries.

Maps shine whenever you need to:

  • Remember per-player state ("has this agent already looted chest 3?")
  • Translate a trigger or zone ID into a human-readable name or reward tier
  • Store configuration tables (delay times, score values) keyed by a string label

On our sun-drenched pirate dock the map is the ship's manifest: every trigger_device represents a chest on the dock, and the map records which player last opened each one so we can disable repeat triggers and fire a cinematic reveal only for the first looter.

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.

player

Full public surface, resolved verbatim from the live Epic digest (Verse.digest.verse). Inherited members are merged from agent.

player<native><public> := class<unique><persistent><module_scoped_var_weak_map_key><epic_internal>(agent):

vector3

3-dimensional vector with float components.

Full public surface, resolved verbatim from the live Epic digest (Verse.digest.verse).

vector3<native><public> := struct<concrete><computes><persistable><uht_comparable>:

Walkthrough

Scenario: Three treasure chests sit on a cel-shaded dock. Each chest is a trigger_device. When a player steps on a trigger the Verse device:

  1. Looks up whether that player already looted this chest.
  2. If not, records the player in the map, disables the trigger so no one else can loot it, and fires a cinematic.
  3. If yes, re-enables the trigger with a short reset delay (the chest respawns after 30 s).
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Native }

# Localized helper — trigger_device.Trigger() needs no message param,
# but we keep this around for any future UI text.
ChestLabel<localizes>(S : string) : message = "{S}"

pirate_dock_loot_manager := class(creative_device):

    # ── Editable device references ──────────────────────────────────
    @editable ChestA : trigger_device = trigger_device{}
    @editable ChestB : trigger_device = trigger_device{}
    @editable ChestC : trigger_device = trigger_device{}

    @editable CinematicReveal : cinematic_sequence_device = cinematic_sequence_device{}

    # ── Internal state ───────────────────────────────────────────────
    # Maps a chest index (0/1/2) to the agent who looted it.
    # We use ?agent so an empty chest slot is represented by false.
    var LootedBy : [int]agent = map{}

    # ── Lifecycle ────────────────────────────────────────────────────
    OnBegin<override>()<suspends> : void =
        # Each chest may only fire once before we manually re-enable it.
        ChestA.SetMaxTriggerCount(1)
        ChestB.SetMaxTriggerCount(1)
        ChestC.SetMaxTriggerCount(1)

        # Give each chest a 30-second respawn window.
        ChestA.SetResetDelay(30.0)
        ChestB.SetResetDelay(30.0)
        ChestC.SetResetDelay(30.0)

        # Subscribe handlers — each closure captures its chest index.
        ChestA.TriggeredEvent.Subscribe(OnChestATriggered)
        ChestB.TriggeredEvent.Subscribe(OnChestBTriggered)
        ChestC.TriggeredEvent.Subscribe(OnChestCTriggered)

    # ── Event handlers ───────────────────────────────────────────────
    OnChestATriggered(MaybeAgent : ?agent) : void =
        HandleChestLooted(0, MaybeAgent)

    OnChestBTriggered(MaybeAgent : ?agent) : void =
        HandleChestLooted(1, MaybeAgent)

    OnChestCTriggered(MaybeAgent : ?agent) : void =
        HandleChestLooted(2, MaybeAgent)

    # ── Core logic ───────────────────────────────────────────────────
    HandleChestLooted(ChestIndex : int, MaybeAgent : ?agent) : void =
        # Unwrap the optional agent — TriggeredEvent sends ?agent.
        if (Looter := MaybeAgent?):
            # MAP LOOKUP: has this chest already been claimed?
            if (LootedBy[ChestIndex]):
                # Key exists → chest was already looted by someone.
                # Re-enable with a fresh trigger count so it can respawn.
                EnableChestByIndex(ChestIndex)
            else:
                # Key absent → first loot! Record the player.
                set LootedBy = ConcatenateMaps(LootedBy, map{ ChestIndex => Looter })
                # Play the cinematic reveal for this player.
                CinematicReveal.Play(Looter)
                # Disable the chest so it can't be triggered again
                # until the reset delay expires.
                DisableChestByIndex(ChestIndex)

    EnableChestByIndex(Index : int) : void =
        if (Index = 0):
            ChestA.Enable()
            ChestA.SetMaxTriggerCount(1)
        else if (Index = 1):
            ChestB.Enable()
            ChestB.SetMaxTriggerCount(1)
        else if (Index = 2):
            ChestC.Enable()
            ChestC.SetMaxTriggerCount(1)

    DisableChestByIndex(Index : int) : void =
        if (Index = 0):
            ChestA.Disable()
        else if (Index = 1):
            ChestB.Disable()
        else if (Index = 2):
            ChestC.Disable()```

### Line-by-line highlights

| Lines | What's happening |
|---|---|
| `var LootedBy : [int]agent = map{}` | Declares a mutable map keyed by `int` (chest index) whose values are `agent`. Starts empty. |
| `ChestA.SetMaxTriggerCount(1)` | Limits the trigger to one fire before it needs to be manually re-enabled. |
| `ChestA.SetResetDelay(30.0)` | After the trigger fires, it waits 30 s before it can fire again (if re-enabled). |
| `ChestA.TriggeredEvent.Subscribe(OnChestATriggered)` | Registers our handler; the event sends `?agent`. |
| `if (Looter := MaybeAgent?)` | Unwraps `?agent` — mandatory before using it as `agent`. |
| `if (LootedBy[ChestIndex])` | **Map lookup** — succeeds when the key exists (chest already looted), fails silently when absent. |
| `set LootedBy = LootedBy.Concat(map{ ChestIndex => Looter })` | Immutable-map pattern: build a new map with the extra entry and reassign the `var`. |
| `CinematicReveal.Play(Looter)` | Fires the cinematic sequence for the specific player who opened the chest. |

## Common patterns

### Pattern 1 — Zone-name lookup: translate a trigger index into a dock location name

Use a `[int]string` map as a static config table. Look up the zone name when a trigger fires and use it to gate a score device.

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

dock_zone_announcer := class(creative_device):

    @editable ZoneTrigger : trigger_device = trigger_device{}
    @editable ScoreDevice  : score_manager_device = score_manager_device{}

    # Static map: trigger index → zone label
    ZoneNames : [int]string = map{
        0 => "Crow's Nest",
        1 => "Gangplank",
        2 => "Lagoon Cove",
        3 => "Clifftop Cannon"
    }

    OnBegin<override>()<suspends> : void =
        ZoneTrigger.SetMaxTriggerCount(0)   # 0 = unlimited triggers
        ZoneTrigger.SetTransmitDelay(0.5)   # half-second broadcast delay
        ZoneTrigger.TriggeredEvent.Subscribe(OnZoneEntered)

    OnZoneEntered(MaybeAgent : ?agent) : void =
        if (A := MaybeAgent?):
            # MAP LOOKUP — get the zone name for index 2 (Lagoon Cove)
            if (ZoneName := ZoneNames[2]):
                # Zone found — award points via score device
                ScoreDevice.Activate(A)
            # If the key were absent the if-block is simply skipped.

Pattern 2 — Delay config map: different reset delays per chest rarity

Store per-rarity reset delays in a [string]float map and apply them at runtime using SetResetDelay.

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

rarity_delay_configurator := class(creative_device):

    @editable CommonChest : trigger_device = trigger_device{}
    @editable RareChest   : trigger_device = trigger_device{}
    @editable LegendaryChest : trigger_device = trigger_device{}

    # MAP: rarity string → respawn delay in seconds
    RespawnDelays : [string]float = map{
        "common"    => 10.0,
        "rare"      => 20.0,
        "legendary" => 45.0
    }

    OnBegin<override>()<suspends> : void =
        # Apply delays from the map — lookup each rarity key.
        if (D := RespawnDelays["common"]):
            CommonChest.SetResetDelay(D)
            CommonChest.SetMaxTriggerCount(0)  # unlimited

        if (D := RespawnDelays["rare"]):
            RareChest.SetResetDelay(D)
            RareChest.SetMaxTriggerCount(3)    # only 3 loots per match

        if (D := RespawnDelays["legendary"]):
            LegendaryChest.SetResetDelay(D)
            LegendaryChest.SetMaxTriggerCount(1)  # one-time legendary

        # Subscribe to legendary chest to fire a cinematic
        LegendaryChest.TriggeredEvent.Subscribe(OnLegendaryLooted)

    OnLegendaryLooted(MaybeAgent : ?agent) : void =
        # Disable the legendary chest immediately after first loot
        LegendaryChest.Disable()

Pattern 3 — Trigger-count inspector: read current map state and GetTriggerCountRemaining

Query the map to decide whether to re-enable a trigger, and use GetTriggerCountRemaining to confirm capacity before calling Trigger() programmatically.

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

chest_inspector := class(creative_device):

    @editable WatchedChest  : trigger_device = trigger_device{}
    @editable ResetTrigger  : trigger_device = trigger_device{}

    # Tracks how many times each chest index has been looted this round.
    var LootCount : [int]int = map{}

    OnBegin<override>()<suspends> : void =
        WatchedChest.SetMaxTriggerCount(5)   # up to 5 loots
        WatchedChest.SetResetDelay(5.0)
        WatchedChest.TriggeredEvent.Subscribe(OnChestHit)
        ResetTrigger.TriggeredEvent.Subscribe(OnResetRequested)

    OnChestHit(MaybeAgent : ?agent) : void =
        # Increment loot count for chest 0 in the map.
        var NewCount : int = 1
        if (Existing := LootCount[0]):
            set NewCount = Existing + 1
        set LootCount = LootCount.Concat(map{ 0 => NewCount })

    OnResetRequested(MaybeAgent : ?agent) : void =
        # Use GetTriggerCountRemaining to decide whether to fire programmatically.
        Remaining := WatchedChest.GetTriggerCountRemaining()
        if (Remaining > 0):
            # Still has capacity — fire it via code (no agent needed here).
            WatchedChest.Trigger()
        else:
            # Exhausted — re-enable and reset the count map.
            WatchedChest.Enable()
            WatchedChest.SetMaxTriggerCount(5)
            set LootCount = map{}

Gotchas

1. Map lookup is failable — always wrap in if

MyMap[Key] is a failable expression. Calling it outside an if or if block is a compile error. Always write:

if (Value := MyMap[Key]):
    # use Value

If the key is absent the block is simply skipped — no exception, no crash.

2. Maps are immutable — reassign the whole var to "add" entries

Verse maps are value types. You cannot mutate them in place. The idiomatic pattern is:

set MyMap = MyMap.Concat(map{ NewKey => NewValue })

If you forget var on the field declaration the compiler will refuse the set.

3. TriggeredEvent sends ?agent, not agent

The event signature is listenable(?agent). Your handler must accept ?agent and unwrap it:

OnTriggered(MaybeAgent : ?agent) : void =
    if (A := MaybeAgent?):
        # A is now a usable agent

Passing a handler typed (agent):void will not compile.

4. SetMaxTriggerCount is clamped to [0, 20]; 0 means unlimited

Passing 21 silently clamps to 20. Pass 0 when you want infinite triggers. Check the current cap with GetMaxTriggerCount() before logic that depends on it.

5. message vs string — localized text parameters

Any API that accepts message (e.g. future UI calls) will not accept a raw string. Declare a localizes helper:

MyLabel<localizes>(S : string) : message = "{S}"

There is no StringToMessage function in Verse.

6. int and float do not auto-convert

SetResetDelay takes float. Passing an int literal without .0 is a type error. Write 30.0, not 30.

Guides & scripts that use trigger_device

Step-by-step tutorials that put this object to work.

Build your own lesson with trigger_device

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 →