Custom Round Logic: A Verse Revenge-Round Handicap System
Tutorialintermediatecompiles

Custom Round Logic: A Verse Revenge-Round Handicap System

Updated intermediateCode verified

What you'll learn

  • Session-scoped weak_map: how to store per-player data that survives a round reset by keying it on GetSession().
  • The logic type as a flag: using true/false to mark which players are handicapped, tested with if (Flag?):.
  • Reading persisted data in OnBegin: OnBegin runs at the start of every round, so it's the place to check the flag and react.
  • Real player-UI calls: GetPlayerUI[], building a canvas with a text_block, and AddWidget to actually show the handicap on screen.

How it works

The round-reset trap

When a round ends, most state is wiped. A plain var on your device won't reliably survive into the next round. The fix is a weak_map keyed on the current session.

Session-scoped weak maps

GetSession() (from /Verse.org/Simulation) returns the session for the current match. The docs note its result "can be used with weak_map to implement global variables." We declare a module-level weak_map(session, loser_table) — a map from a session to a tiny loser_table class that holds a [player]logic flag map. (The value type of a weak_map must be a named type — writing the map type [player]logic inline inside weak_map(...) doesn't parse — so we wrap it in a one-field class.) Because it's keyed on the session, the same entry is visible across rounds within the same match.

Fallible map access is the key idea

LoserFlags[Session] and Table.Flags[Player] are fallible index operations — they use [] and MUST live in a failure context (an if/for condition or a <decides> body). They fail (rather than crash) when no entry exists yet. That's why IsLoser is marked <decides>: it simply states the two lookups, and failure means "this player isn't flagged."

The flow

  1. OnBegin runs at round start. For every currently-flagged player, we push a HUD widget warning them they're handicapped.
  2. A trigger ("the loser buzzer") lets you flag a player as the loser for next round — it writes true into the map.
  3. Because the data is session-scoped, the next round's OnBegin sees the flag and shows the UI.

Why logic, not bool

Verse's boolean type is logic with values true/false. You test it with if (Flag?):. There is no bool, and you never write = true — you interrogate the value with ?.

Let's build it

Drag one trigger_device into your island and link it below. Step on it (or wire a button to it) to mark the last player who touched it as "the loser" for the next round. On the next round's start, that player gets a real HUD widget warning.

using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/Diagnostics }

# weak_map's value must be a NAMED type — an inline [player]logic map type
# won't parse inside weak_map(...) — so we wrap the flag map in a tiny class.
loser_table := class:
    Flags : [player]logic = map{}

# Session-scoped storage: for each session (round chain), a per-player logic flag.
# Keyed on session so the flags SURVIVE a round reset within the same match.
var LoserFlags : weak_map(session, loser_table) = map{}

round_handicap_manager := class<concrete>(creative_device):

    # Step on this (or wire a button to it) to flag the toucher as next round's loser.
    @editable
    LoserBuzzer : trigger_device = trigger_device{}

    # Localized text helper — message fields never take raw strings.
    HandicapText<localizes>(S : string) : message = "{S}"

    # OnBegin runs at the START of every round -> perfect place to react to the flag.
    OnBegin<override>()<suspends>: void =
        # Show the handicap UI to anyone already flagged from a previous round.
        for (Player : GetPlayspace().GetPlayers(), IsLoser[Player]):
            ShowHandicapUI(Player)
        # Listen for new losers being flagged this round.
        LoserBuzzer.TriggeredEvent.Subscribe(OnBuzzed)

    # Fallible: succeeds only when the player has a flag set to true.
    # Both [] lookups are fallible index ops, legal inside this <decides> body.
    IsLoser(Player : player)<decides><transacts>: void =
        Table := LoserFlags[GetSession()]  # fails if no table for this session yet
        Flag := Table.Flags[Player]        # fails if player not present
        Flag?                              # fails if the stored logic is false

    # Trigger handler: mark the triggering agent as the loser for next round.
    OnBuzzed(MaybeAgent : ?agent): void =
        if (Agent := MaybeAgent?, Player := player[Agent]):
            Session := GetSession()
            # Read the existing flag map for this session (or start a fresh one).
            var Flags : [player]logic = map{}
            if (Existing := LoserFlags[Session]):
                set Flags = Existing.Flags
            # Add this player as true, then write the table back to the session.
            if (set Flags[Player] = true, set LoserFlags[Session] = loser_table{Flags := Flags}):
                # Give immediate feedback to the player who just got flagged.
                ShowHandicapUI(Player)

    # REAL player-UI: build a canvas with a warning text_block and add it to the HUD.
    ShowHandicapUI(Player : player): void =
        if (PlayerUI := GetPlayerUI[Player]):
            Warning := text_block:
                DefaultText := HandicapText("HANDICAP: you lost last round!")
            Canvas := canvas:
                Slots := array:
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.1}, Maximum := vector2{X := 0.5, Y := 0.1}}
                        Offsets := margin{Top := 0.0, Left := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.0}
                        Widget := Warning
            PlayerUI.AddWidget(Canvas)
            Print("Showed handicap UI to a flagged player.")

Try it yourself

  • Multi-round test: Set Total Rounds to 2+ in Island Settings. Step on the buzzer in Round 1, then watch the HUD warning appear at the start of Round 2.
  • Clear the flag: Add a second trigger whose handler sets set Flags[Player] = false (then writes the map back) so a player can "redeem" themselves.
  • Real handicap: Instead of just text, apply a gameplay effect in ShowHandicapUI — reduce a stat, disable a class, or spawn near the back of the line.
  • Reset on game end: Because there's no API for the current round, add an @editable TotalRounds : int and clear LoserFlags when the match's final round begins.

Recap

You stored a per-player logic flag in a session-keyed weak_map so it persists across rounds within a match. OnBegin reads that flag at the start of each round via a <decides> helper (the [] lookups are fallible index ops that fail cleanly when there's no entry), and for every flagged player you built a real canvas + text_block and called GetPlayerUI[] / AddWidget to push a live HUD warning. Remember: [] means fallible access inside a failure context, () means an ordinary call — and Verse booleans are logic, tested with ?.

Verse source files

Check your understanding

Test yourself with an interactive quiz and track your progress + earn XP — free for members.

Turn this into a guided course

Add custom-round-logic-using-verse to your free study plan — we'll suggest related pages and stitch the lot into one compile-checked, self-guided lesson with worked examples and quizzes.

Original tutorial generated by Verse Island from the Verse/UEFN knowledge base, with references to the Epic Games sources above. Code is validated against the knowledge base.

Comments

    Sign in to vote, comment, or suggest an edit.Sign in