Reference Verse compiles

trigger_device & hud_message_device: Pirate Cove Secrets in Verse

A sun-drenched pirate cove hides its secrets well — but with `trigger_device` and `hud_message_device` you can make the island *talk* when a player steps on the right plank. This article teaches you how to subscribe to trigger events, show custom HUD messages, and use Verse failure contexts (`if`, `or`, `not`) to guard every step safely.

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

Overview

failure-context-required is not a placed device — it's the single most common compiler error a new Verse creator hits, and it comes straight from the heart of the language. In Verse, an expression that might fail (like unwrapping an optional player, checking team membership, or indexing an array) can only be written inside a failure context: a place where the language already knows what to do on both success and failure.

Think of the sunny cove dock: a weapon crate should only open for a player who is (1) actually present — a real agent unwrapped from an ?agent payload — and (2) on the correct team. Each of those checks can fail. If you write them out in the open, the compiler stops you with "This expression must appear within a failure context." The fix is never to guess an API — it's to wrap the failable work in a context like an if.

Reach for this knowledge whenever you subscribe to an event that hands you a ?agent, whenever you call a <decides> method like IsOnTeam, or whenever you index into an array. All of those are failable and all of them need a home.

API Reference

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

Walkthrough

Here's the cove-dock crate. A PlayerAddedEvent from the playspace fires when someone arrives on the island. We check whether that arriving player is on the crate's owner team — a failable IsOnTeam[...] call — and only then unlock the crate prop. Every failable step lives inside an if failure context.

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

# The sunny cove-dock crate gate.
cove_crate_gate := class(creative_device):

    # The crate prop sitting on the dock. Hidden until an allowed player arrives.
    @editable
    CratePropMesh : creative_prop = creative_prop{}

    OnBegin<override>()<suspends>:void =
        # Grab the playspace and its team collection ONCE.
        Playspace := GetPlayspace()
        TeamCollection := Playspace.GetTeamCollection()

        # Subscribe to arrivals. PlayerAddedEvent hands us a `player` payload.
        Playspace.PlayerAddedEvent().Subscribe(OnPlayerArrived)

        # Also check everyone already on the island at start.
        for (P : Playspace.GetPlayers()):
            OnPlayerArrived(P)

    # Event handler: this is a METHOD at class scope, subscribed above.
    OnPlayerArrived(Arrival : player) : void =
        Playspace := GetPlayspace()
        TeamCollection := Playspace.GetTeamCollection()

        # THIS is the failure context. Every failable call lives inside the if.
        # GetTeam[...] fails if the player is on no team; IsOnTeam[...] is <decides>.
        # GetTeams() returns an array we index with [0] — indexing is failable too.
        if:
            AllTeams := TeamCollection.GetTeams()
            FirstTeam := AllTeams[0]
            TeamCollection.IsOnTeam[Arrival, FirstTeam]
        then:
            # Only reached when ALL of the above succeeded.
            OpenCrate()

    OpenCrate() : void =
        # A non-failable side effect — safe outside a failure context.
        CratePropMesh.Show()

Line by line:

  • @editable CratePropMesh : creative_prop = creative_prop{} — you MUST declare the prop as an editable field to touch it from Verse; a bare reference is 'Unknown identifier'.
  • Playspace.PlayerAddedEvent().Subscribe(OnPlayerArrived)PlayerAddedEvent() is a listenable(player), so the handler receives a player, not a ?player. We subscribe a class method.
  • Inside OnPlayerArrived, the if: block is the failure context. AllTeams[0] (array indexing), and IsOnTeam[Arrival, FirstTeam] (a <decides> method, written with []) are all failable.
  • If any of the three lines in the if fails, the whole context fails and then: never runs — the crate stays shut. That is failure driving control flow.
  • OpenCrate() calls CratePropMesh.Show(), a plain non-failable effect, so it lives safely outside any failure context.

Common patterns

Pattern 1 — Unwrapping an ?agent payload. Many device events hand you an optional agent. The unwrap A := MaybeAgent? is failable and needs a failure context (the if).

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

dock_trigger_gate := class(creative_device):

    @editable
    DockTrigger : trigger_device = trigger_device{}

    @editable
    RewardProp : creative_prop = creative_prop{}

    OnBegin<override>()<suspends>:void =
        DockTrigger.TriggeredEvent.Subscribe(OnStepped)

    # TriggeredEvent hands a ?agent. Unwrapping it is failable.
    OnStepped(MaybeAgent : ?agent) : void =
        if (Agent := MaybeAgent?):
            # Inside the failure context we now have a real agent.
            RewardProp.Show()

Pattern 2 — for as a failure context with a filter. The iteration and filter clauses of a for are themselves a failure context, so you can drop failable checks right into the loop head.

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

shore_team_counter := class(creative_device):

    @editable
    CountedProp : creative_prop = creative_prop{}

    OnBegin<override>()<suspends>:void =
        Playspace := GetPlayspace()
        TeamCollection := Playspace.GetTeamCollection()
        if (FirstTeam := TeamCollection.GetTeams()[0]):
            # The filter `TeamCollection.IsOnTeam[P, FirstTeam]` is failable
            # and legal because a `for` head IS a failure context.
            for (P : Playspace.GetPlayers(), TeamCollection.IsOnTeam[P, FirstTeam]):
                ShowForOne()

    ShowForOne() : void =
        CountedProp.Show()

Pattern 3 — a <decides> helper function is its own failure context. The body of a function marked <decides> is a failure context, so failable expressions are allowed directly in it; callers must then invoke it with [].

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

clifftop_vault := class(creative_device):

    @editable
    VaultDoor : creative_prop = creative_prop{}

    OnBegin<override>()<suspends>:void =
        Playspace := GetPlayspace()
        Playspace.PlayerAddedEvent().Subscribe(OnArrived)

    # <decides> makes this function's body a failure context.
    IsAllowed(P : player)<transacts><decides>:void =
        Playspace := GetPlayspace()
        TeamCollection := Playspace.GetTeamCollection()
        Team := TeamCollection.GetTeam[P]        # failable, allowed here
        TeamCollection.IsOnTeam[P, Team]         # failable, allowed here

    OnArrived(P : player) : void =
        # Call the <decides> helper with [] inside an if failure context.
        if (IsAllowed[P]):
            VaultDoor.Show()

Gotchas

  • <decides> means brackets, not parens. A method like IsOnTeam and GetTeam are <decides>, so you call them IsOnTeam[...] / GetTeam[...], and only inside a failure context. Using () on them is a compile error.
  • Array indexing can fail. MyArray[0] fails if the array is empty. That's why GetTeams()[0] must sit inside an if/for head — it isn't 'safe'.
  • ? unwrap is failable. MaybeAgent? on a ?agent needs a failure context; you cannot use the result until an if (A := MaybeAgent?): succeeds.
  • Effects don't belong in the condition of an if unless failable. Put non-failable side effects (Show(), Hide()) in the then: body, not in the failure-context condition.
  • PlayerAddedEvent() gives a player, not ?agent. No unwrap needed there — but that same player still needs a failure context when you check its team.
  • Whole context rolls back on failure. If a later line in an if condition fails, earlier speculative effects are undone — this is intended, not a bug. Rely on it for all-or-nothing gates.
  • There is no StringToMessage. If a device method wants a message, declare MyText<localizes>(S:string):message = "{S}" and pass MyText("...") — unrelated to failure contexts but a frequent companion error.

Guides & scripts that use player

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

Build your own lesson with player

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 →