Reference Devices compiles

Capstone: AweShucks Town — the Hub That Wires the Island Together

Every zone of this island taught you one piece: South Shores gave you the phase enum and SpawnProp helper, North Jungle gave you shared helper modules and a custom event bus, and Center Village gave you the IslandProgress badge schema and the portal gatekeeper. This capstone wires ALL of it into one brain device — the lobby loop, the while-you-wait CTF event, the Tour the Town quest, persistent badges, and five gated matchmaking portals — then walks you through publishing the town so the whole archipelago goes live.

Updated Examples verified on the live UEFN compiler

Professor AweShucks adjusts his spectacles and unrolls the master blueprint. Every lesson you have taken on this island — the Shell Hunt's phase enum, the Codewood helpers, the badge profiles, the gatekeeper, the live portals — was a plank in ONE bridge. Today you nail them together. AweShucks Town stops being a collection of lessons and becomes the hub island: a published, persistent town square where players wash ashore, play a plaza CTF round while they wait, tour the booths, and step through badge-gated portals into every other zone. This is the final integration and publish walkthrough.

Overview — the hub architecture

The town runs on ONE brain device plus four small modules, each imported from an earlier lesson:

awe_shucks_town_hub (creative_device — the brain)
 ├── ShellHuntKit        (South Shores L9/L14) — game_phase enum + SpawnProp helper
 ├── CodewoodKit         (North Jungle L21/L28) — shared helpers + hub_bus event bus
 ├── IslandProgress      (Center Village L12) — persistable badge schema, island-wide
 └── portal_gatekeeper   (Center Village L13) — ONE access decision for every booth

Data flows in one direction: devices signal the brain, the brain consults the modules, the modules read/write the persistent BadgeMap. No booth ever decides its own access — every open-or-refuse decision funnels through portal_gatekeeper.CheckAccess. When something goes wrong with gating, you know exactly where to look.

What you will build

The whole capstone — the piece that wires the island together:

  • Lobby loop (L7): a Waiting -> Ready -> Launch -> Reset state machine driven by the South Shores game_phase enum, announced by the town-crier HUD device.
  • Plaza CTF event (L8/L9/L11): two capture_item_spawner_device flags, team scores tallied in Verse, captures paid out as persistent town coins.
  • Tour the Town quest (L10): a tracker_device assigned to every arriving player, with a coin payout on completion.
  • Badge persistence (L12): the island-wide IslandProgress schema — one weak_map(player, island_badges) profile that survives sessions and gates the paid zones.
  • Gated portal booths (L13/L16): interact buttons in front of matchmaking_portal_devices; free booths always open, paid booths require the previous zone's badge.
  • Publish (L17): the settings pass and Creator Portal steps that put the town live.

Imports from other zones

This capstone EXPLICITLY reuses three earlier deliverables. In your real project these live in their own .verse files; the walkthrough block below inlines them so the whole hub compiles as one file you can read top to bottom.

Module Taught in What the hub uses
ShellHuntKit South Shores L9 (module) + L14 (enum) + L10 (SpawnProp) game_phase enum, PhaseLabel, SpawnCelebrationProp
CodewoodKit North Jungle L21 (custom modules) + L28 (custom events) ScoreLine helper, the hub_bus event bus
IslandProgress Center Village L12 (persistence) island_badges persistable class, BadgeMap, HasBadge, AddCoins
portal_gatekeeper Center Village L13 (gating module) CheckAccess — the ONE gate function

Walkthrough

The full hub, one file

using { /Fortnite.com/Devices }
using { /Fortnite.com/Game }
using { /Fortnite.com/Playspaces }
using { /Fortnite.com/Teams }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/Diagnostics }

# Turns a plain string into a localizable message for HUD devices.
HubMsg<localizes>(Text : string) : message = "{Text}"

# ══ Imported from SOUTH SHORES (L9 module + L14 enum + L10 SpawnProp) ══
ShellHuntKit<public> := module:
    using { /Fortnite.com/Devices }
    using { /UnrealEngine.com/Temporary/SpatialMath }

    # The lobby phases — the same enum pattern the Shell Hunt shipped.
    game_phase<public> := enum:
        Waiting
        Ready
        Launch
        Reset

    # One readable town-crier line per phase.
    PhaseLabel<public>(Phase : game_phase) : string =
        case (Phase):
            game_phase.Waiting => "Waiting for islanders to wash ashore..."
            game_phase.Ready => "Round starting - take your team pads!"
            game_phase.Launch => "Plaza CTF is LIVE - capture the flag!"
            game_phase.Reset => "Resetting the plaza..."

    # The SpawnProp helper from the Shell Hunt celebration.
    SpawnCelebrationProp<public>(Asset : creative_prop_asset, Where : vector3) : ?creative_prop =
        Result := SpawnProp(Asset, Where, IdentityRotation())
        Result(0)

# ══ Imported from NORTH JUNGLE (L21 shared helpers + L28 custom events) ══
CodewoodKit<public> := module:
    using { /Verse.org/Simulation }

    # HUD text helper the Mod Rally used for its scoreboard lines.
    ScoreLine<public>(Label : string, Value : int) : string =
        "{Label}: {Value}"

    # The custom event bus — decouples booths and HUD from the hub brain.
    hub_bus<public> := class:
        # Fired when a zone portal opens for a specific islander.
        PortalOpenedEvent<public> : event(agent) = event(agent){}
        # Fired every time the lobby state machine changes phase.
        PhaseChangedEvent<public> : event(ShellHuntKit.game_phase) = event(ShellHuntKit.game_phase){}

# ══ Shared ISLAND-WIDE: the IslandProgress persistence schema (L12) ══
IslandProgress<public> := module:
    using { /Verse.org/Simulation }

    # Which capstone badge a portal can require.
    zone_badge<public> := enum:
        ShellHunt
        ModRally
        VolcanoSurvival
        SunkenDive

    # One persistable profile per player. Fields may be ADDED later
    # (with defaults) but NEVER removed or retyped once published.
    island_badges<public> := class<final><persistable>:
        Version<public> : int = 1
        ShellHunt<public> : logic = false
        ModRally<public> : logic = false
        VolcanoSurvival<public> : logic = false
        SunkenDive<public> : logic = false
        TownCoins<public> : int = 0

    # Module-scoped weak_map: loads on join, saves on every write.
    var BadgeMap<public> : weak_map(player, island_badges) = map{}

    # Succeeds-as-true when `Player` has earned `Badge`.
    HasBadge<public>(Player : player, Badge : zone_badge)<transacts> : logic =
        if (Badges := BadgeMap[Player]):
            case (Badge):
                zone_badge.ShellHunt => Badges.ShellHunt
                zone_badge.ModRally => Badges.ModRally
                zone_badge.VolcanoSurvival => Badges.VolcanoSurvival
                zone_badge.SunkenDive => Badges.SunkenDive
        else:
            false

    # Adds town coins to a player's persistent profile.
    # Persistable values are snapshots: rebuild, then write back.
    AddCoins<public>(Player : player, Amount : int)<transacts> : void =
        if (Badges := BadgeMap[Player]):
            if (set BadgeMap[Player] = island_badges{Version := Badges.Version, ShellHunt := Badges.ShellHunt, ModRally := Badges.ModRally, VolcanoSurvival := Badges.VolcanoSurvival, SunkenDive := Badges.SunkenDive, TownCoins := Badges.TownCoins + Amount}) {}

# ══ CENTER VILLAGE L13: the portal gatekeeper module ══
# THE module to inspect when a paid portal opens for a badge-less player:
# every booth decision on the island flows through this ONE function.
portal_gatekeeper<public> := module:
    using { /Verse.org/Simulation }

    # Free booths pass `false` (no requirement); paid booths name a badge.
    CheckAccess<public>(Player : player, MaybeRequired : ?IslandProgress.zone_badge)<transacts> : logic =
        if (Required := MaybeRequired?):
            IslandProgress.HasBadge(Player, Required)
        else:
            true

# ══ THE HUB BRAIN: lobby, CTF, quest, badges, and portals — wired together ══
awe_shucks_town_hub := class(creative_device):

    # ── Plaza lobby ──
    @editable
    LobbyTimer : timer_device = timer_device{}

    @editable
    TownCrier : hud_message_device = hud_message_device{}

    @editable
    MinPlayers : int = 2

    # ── While-you-wait plaza CTF (L11) ──
    @editable
    RedBaseFlag : capture_item_spawner_device = capture_item_spawner_device{}

    @editable
    BlueBaseFlag : capture_item_spawner_device = capture_item_spawner_device{}

    @editable
    RoundSeconds : float = 180.0

    @editable
    CoinsPerCapture : int = 5

    # ── Tour the Town onboarding quest (L10) ──
    @editable
    TourTracker : tracker_device = tracker_device{}

    @editable
    CoinsForTour : int = 10

    # ── Portal booths: interact button + live matchmaking portal (L13/L16) ──
    @editable
    SouthBoothButton : button_device = button_device{}

    @editable
    SouthPortal : matchmaking_portal_device = matchmaking_portal_device{}

    @editable
    JungleBoothButton : button_device = button_device{}

    @editable
    JunglePortal : matchmaking_portal_device = matchmaking_portal_device{}

    @editable
    VolcanoBoothButton : button_device = button_device{}

    @editable
    VolcanoPortal : matchmaking_portal_device = matchmaking_portal_device{}

    @editable
    CovesBoothButton : button_device = button_device{}

    @editable
    CovesPortal : matchmaking_portal_device = matchmaking_portal_device{}

    # ── Celebration prop, spawned with the South Shores helper ──
    @editable
    ConfettiCannonAsset : creative_prop_asset = DefaultCreativePropAsset

    # Live state
    var CurrentPhase : ShellHuntKit.game_phase = ShellHuntKit.game_phase.Waiting
    var Scores : [team]int = map{}
    Bus : CodewoodKit.hub_bus = CodewoodKit.hub_bus{}

    OnBegin<override>()<suspends> : void =
        # EVERY portal starts CLOSED; only the gatekeeper opens them.
        SouthPortal.Disable()
        JunglePortal.Disable()
        VolcanoPortal.Disable()
        CovesPortal.Disable()

        # Booth buttons -> one shared, gated handler each.
        SouthBoothButton.InteractedWithEvent.Subscribe(OnSouthBooth)
        JungleBoothButton.InteractedWithEvent.Subscribe(OnJungleBooth)
        VolcanoBoothButton.InteractedWithEvent.Subscribe(OnVolcanoBooth)
        CovesBoothButton.InteractedWithEvent.Subscribe(OnCovesBooth)

        # CTF flags -> the shared capture handler.
        RedBaseFlag.ItemCapturedEvent.Subscribe(OnFlagCaptured)
        BlueBaseFlag.ItemCapturedEvent.Subscribe(OnFlagCaptured)

        # Quest completion pays out coins.
        TourTracker.CompleteEvent.Subscribe(OnTourComplete)

        # Greet everyone already here, then every later arrival.
        for (Islander : GetPlayspace().GetPlayers()):
            WelcomePlayer(Islander)
        GetPlayspace().PlayerAddedEvent().Subscribe(WelcomePlayer)

        # The hub heartbeat: run lobby rounds forever.
        loop:
            RunLobbyRound()

    # ── Lobby state machine (L7): Waiting -> Ready -> Launch -> Reset ──
    RunLobbyRound()<suspends> : void =
        SetPhase(ShellHuntKit.game_phase.Waiting)
        loop:
            if (GetPlayspace().GetPlayers().Length >= MinPlayers):
                break
            Sleep(5.0)
        SetPhase(ShellHuntKit.game_phase.Ready)
        LobbyTimer.Reset()
        LobbyTimer.Start()
        LobbyTimer.SuccessEvent.Await()
        SetPhase(ShellHuntKit.game_phase.Launch)
        RunCtfRound()
        SetPhase(ShellHuntKit.game_phase.Reset)
        Sleep(8.0)

    SetPhase(NewPhase : ShellHuntKit.game_phase) : void =
        set CurrentPhase = NewPhase
        Bus.PhaseChangedEvent.Signal(NewPhase)
        TownCrier.Show(HubMsg(ShellHuntKit.PhaseLabel(NewPhase)), ?DisplayTime := 4.0)

    # ── Plaza CTF round (L8/L11): scores accumulate via subscription ──
    RunCtfRound()<suspends> : void =
        set Scores = map{}
        Sleep(RoundSeconds)
        AnnounceWinner()

    OnFlagCaptured(Scorer : agent) : void =
        TeamCollection := GetPlayspace().GetTeamCollection()
        if (ScoringTeam := TeamCollection.GetTeam[Scorer]):
            Current := Scores[ScoringTeam] or 0
            if (set Scores[ScoringTeam] = Current + 1) {}
            Print(CodewoodKit.ScoreLine("Captures", Current + 1))
        # Captures pay town coins into the persistent profile (L12).
        if (Player := player[Scorer]):
            IslandProgress.AddCoins(Player, CoinsPerCapture)

    AnnounceWinner() : void =
        var Best : int = 0
        for (Captures : Scores):
            if (Captures > Best):
                set Best = Captures
        TownCrier.Show(HubMsg("Round over! Winning team took {Best} flag(s)."), ?DisplayTime := 6.0)
        # Confetti via the South Shores SpawnProp helper.
        Above := GetTransform().Translation + vector3{X := 0.0, Y := 0.0, Z := 300.0}
        MaybeCannon := ShellHuntKit.SpawnCelebrationProp(ConfettiCannonAsset, Above)
        if (MaybeCannon?):
            Print("Confetti cannon spawned over the plaza!")

    # ── Welcome + quest (L3/L10/L12) ──
    WelcomePlayer(Islander : player) : void =
        TownCrier.Show(Islander, HubMsg("Welcome to AweShucks Town! Tour the booths to find your next adventure."), ?DisplayTime := 6.0)
        TourTracker.Assign(Islander)
        # First visit? Mint a fresh persistent badge profile.
        if (not IslandProgress.BadgeMap[Islander]):
            if (set IslandProgress.BadgeMap[Islander] = IslandProgress.island_badges{}) {}

    OnTourComplete(Agent : agent) : void =
        if (Player := player[Agent]):
            IslandProgress.AddCoins(Player, CoinsForTour)
        TownCrier.Show(Agent, HubMsg("Tour complete! Coins added to your island purse."), ?DisplayTime := 5.0)

    # ── Portal booths -> the gatekeeper (L13/L16) ──
    OnSouthBooth(Agent : agent) : void =
        # South Shores is FREE: no badge required, pass the empty option.
        TryOpenPortal(Agent, SouthPortal, false, "South Shores")

    OnJungleBooth(Agent : agent) : void =
        TryOpenPortal(Agent, JunglePortal, option{IslandProgress.zone_badge.ShellHunt}, "Codewood Jungle")

    OnVolcanoBooth(Agent : agent) : void =
        TryOpenPortal(Agent, VolcanoPortal, option{IslandProgress.zone_badge.ModRally}, "Cinder Volcano")

    OnCovesBooth(Agent : agent) : void =
        TryOpenPortal(Agent, CovesPortal, option{IslandProgress.zone_badge.VolcanoSurvival}, "Whisper Coves")

    # ONE shared gate for every booth on the island.
    TryOpenPortal(Agent : agent, Portal : matchmaking_portal_device, Required : ?IslandProgress.zone_badge, ZoneName : string) : void =
        if (Player := player[Agent]):
            if (portal_gatekeeper.CheckAccess(Player, Required)?):
                Portal.Enable()
                Bus.PortalOpenedEvent.Signal(Agent)
                TownCrier.Show(Agent, HubMsg("The {ZoneName} portal hums to life. Step through!"), ?DisplayTime := 4.0)
            else:
                TownCrier.Show(Agent, HubMsg("This portal is sealed - earn the previous zone's badge first!"), ?DisplayTime := 4.0)

Reading the brain, top to bottom

Piece What it does
HubMsg<localizes> The one string-to-message bridge every HUD call uses.
ShellHuntKit.game_phase The South Shores enum, reused verbatim as the lobby state machine's states.
CodewoodKit.hub_bus Custom event(...) fields (L28). The brain Signals; any future device — a lore screen, a music controller — just Subscribes. No new wiring in the brain.
IslandProgress.BadgeMap The island-wide persistence schema. weak_map(player, island_badges) at module scope loads on join and saves on every successful set.
portal_gatekeeper.CheckAccess The single yes/no for every booth. Free booth = empty option (false), paid booth = option{zone_badge...}.
OnBegin Disables all portals, subscribes every device event, greets current + future players, then loops RunLobbyRound forever.
RunLobbyRound Waits for MinPlayers, runs the ready countdown on the timer_device (SuccessEvent.Await()), launches the CTF round, resets.
OnFlagCaptured The proven CTF pattern: resolve the scorer's team via GetTeamCollection().GetTeam[...], tally in a [team]int map, pay persistent coins.
TryOpenPortal The gatekeeper in action: check access, Enable() the matchmaking_portal_device on success, explain the refusal on failure.

Device wiring checklist

In the UEFN editor, place and assign — the Details panel of awe_shucks_town_hub must show NO unassigned references:

  1. Timer DeviceLobbyTimer — duration = your ready-up countdown (30s works), Success on Timer End enabled.
  2. HUD Message DeviceTownCrier — default display time is overridden per call from Verse.
  3. 2x Capture Item SpawnerRedBaseFlag / BlueBaseFlag — one per base, flag item, Capture Area linked per base.
  4. Tracker DeviceTourTracker — target value = number of booths (5), Show on HUD enabled; wire each booth's button to Increment via the tracker's receiver, or call Increment from Verse if you prefer one brain.
  5. 4x Button Device → the four *BoothButton fields — one in front of each portal arch.
  6. 4x Matchmaking Portal Device → the four *Portal fields — each configured with its target island code (the zone capstones you published in earlier lessons). They start disabled from Verse, so their editor Enabled at Game Start setting can stay default.
  7. Team Settings & Inventory devices (L9) — two teams, spawn pads flanking the plaza.
  8. ConfettiCannonAsset — pick any celebratory prop from your project's Content Browser.

Publish walkthrough

  1. Island Settings pass: set max players (e.g. 16), enable Use Persistent Storage (required for weak_map persistence), set spawn behavior to your team pads, write the island description in Professor AweShucks' voice.
  2. Private playtest: Launch Session → verify the full loop: welcome message → tour quest on HUD → CTF captures print scores → paid booth refuses you → free booth opens.
  3. Badge test: publish (or session-join) a zone capstone, earn its badge, return to the hub, confirm the next portal now opens — the persistence survives the trip because both islands share the IslandProgress schema.
  4. Release: Project → Publish → fill the Creator Portal listing → submit for review. When the island code comes back, paste it into each ZONE island's return-portal so the loop closes both ways.

Common patterns

Pattern 1 — A zone island grants its badge

Drop this on each ZONE capstone island. When the victory trigger fires, it flips that zone's badge — and the hub's gatekeeper will honor it next visit, because both islands use the same persistable schema.

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

# Same shape as the hub's IslandProgress schema — persistence follows
# the schema, so matching class + fields means shared progress.
rally_badges := class<final><persistable>:
    Version : int = 1
    ModRally : logic = false

var RallyBadgeMap : weak_map(player, rally_badges) = map{}

rally_badge_granter := class(creative_device):
    @editable
    VictoryTrigger : trigger_device = trigger_device{}

    OnBegin<override>()<suspends> : void =
        VictoryTrigger.TriggeredEvent.Subscribe(OnVictory)

    OnVictory(MaybeAgent : ?agent) : void =
        if (Agent := MaybeAgent?, Player := player[Agent]):
            if (set RallyBadgeMap[Player] = rally_badges{Version := 1, ModRally := true}) {}
            Print("Mod Rally badge earned - the volcano portal will open at the hub.")

Pattern 2 — Walk-up booths instead of buttons

Prefer arches players walk through? Swap the button_device for a trigger_device volume — the handler shape changes from agent to ?agent, everything else stays.

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

walkup_booth := class(creative_device):
    @editable
    BoothZone : trigger_device = trigger_device{}

    @editable
    Portal : matchmaking_portal_device = matchmaking_portal_device{}

    OnBegin<override>()<suspends> : void =
        Portal.Disable()
        BoothZone.TriggeredEvent.Subscribe(OnEnterZone)

    OnEnterZone(MaybeAgent : ?agent) : void =
        if (Agent := MaybeAgent?):
            Portal.Enable()
            Print("Portal armed by walk-up.")

Where this goes next

This IS the capstone — AweShucks Town is the island the whole curriculum has been building toward. Everything imports INTO it: the South Shores Shell Hunt grants the ShellHunt badge that opens the jungle booth; the North Jungle Mod Rally grants ModRally for the volcano; the volcano survival run grants VolcanoSurvival for the coves; the sunken dive grants SunkenDive for The Deeps. The scene-graph lessons (L14/L15) reskin every booth with the portal_beacon_component prefab, and the published island code from this walkthrough is what every zone island's return portal points home to. Publish it, share the code, and watch players wash ashore. Professor AweShucks will take it from here — class dismissed, island open.

Build your own lesson with awe_shucks_town_hub

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 →