How do I make a tower defense game with round system, just like “Hero City Tower Defense,” in UEFN?

Hey everyone, I need help on how to make a tower defense game in UEFN and I trying to make it like “Hero City Tower Defense,” which it’s created by vysena, but it’s different then that. So I got 30 trigger devices, 31 tracker devices which is 30 trackers for EliminateEnemies and 1 tracker for MainRound, 1 timer device for 30 second readyup, 3 end game devices which is 1 is EndRoundDevice to end the game at round 30, 1 is Exit Button from TitleScreenDialog, and 1 is End Game for game over, 30 cinematic sequence devices, and etc. So is there something that I did wrong here? So I’ll share my two verse scripts for you to look at, and let me know if you have any thoughts, tips, and advice, and I wish I can show you the whole video example, but it would aloud the upload being too big.

tower_defense_manager script:

using { /Fortnite.com/Devices }

using { /Fortnite.com/Characters }

using { /Verse.org/Simulation }

using { /UnrealEngine.com/Temporary/Diagnostics }

using { /UnrealEngine.com/Temporary/SpatialMath }

using { /UnrealEngine.com/Temporary/UI }

using { /Fortnite.com/UI }

# Device to manage tower defense rounds and settings

tower_defense_manager := class(creative_device):

\# Dialogs & Round infrastructure

@editable

TowerDefenseDialog : popup_dialog_device = popup_dialog_device{}

@editable

@editable

Rounds : \[\]cinematic_sequence_device = array{

    \# 30 placeholder entries for assignment in UEFN

    cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{},

    cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{},

    cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{},

    cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{},

    cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{},

    cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}

}



\# 30 levels prerounds

@editable

Levels : \[\]trigger_device = array{

    \# 30 placeholder entries for assignment in UEFN

    trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{},

    trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{},

    trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{},

    trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{},

    trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{},

    trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{}

}



\# NPC Spawners

@editable

FastNPCSpawner : npc_spawner_device = npc_spawner_device{}

@editable

MediumNPCSpawner : npc_spawner_device = npc_spawner_device{}

@editable

StrongNPCSpawner : npc_spawner_device = npc_spawner_device{}



@editable

MainRound : tracker_device = tracker_device{}

@editable

EliminateEnemies : \[\]tracker_device = array{}



\# Stats

@editable

PointsStat : stat_creator_device = stat_creator_device{}

@editable

CoinsStat : stat_creator_device = stat_creator_device{}



@editable

ReadyUpTimer : timer_device = timer_device{}



@editable

FixedPointCamera : gameplay_camera_fixed_point_device = gameplay_camera_fixed_point_device{}

@editable

ThirdPersonControls : gameplay_controls_third_person_device = gameplay_controls_third_person_device{}

@editable

Objective : objective_device = objective_device{}



@editable

Player1Spawner : player_spawner_device = player_spawner_device{}

@editable

Player2Spawner : player_spawner_device = player_spawner_device{}

@editable

Player3Spawner : player_spawner_device = player_spawner_device{}

@editable

Player4Spawner : player_spawner_device = player_spawner_device{}



\# Round timer and logic

var CurrentRound : int = 1

var EliminationCount : int = 0

var SpawnLimitsPerRound : \[\]spawn_limits = array{}

ReadyDuration : float = 30.0 # seconds (30 second)

var Players : \[\]player = array{}

var RoundActive : logic = false



var IsRoundActive : logic = false



var DialogShown : logic = false

var RoundStarted : logic = false

var DialogSubscription : ?cancelable = false



var CurrentRoundIndex : int = 0

var AgentsInZone : \[agent\]logic = map{}

var PlayerDialogSubscriptions : \[player\]cancelable = map{}

var ActiveDialogPlayer : ?player = false



OnBegin<override>()<suspends>:void=

    \# Initialize spawn limits for each round

    set SpawnLimitsPerRound = array{

        spawn_limits{FastNPC := 30, MediumNPC := 10, StrongNPC := 3, DurationMinutes := 4.0},  # Round 1

        spawn_limits{FastNPC := 60, MediumNPC := 20, StrongNPC := 5, DurationMinutes := 8.0},  # Round 2

        spawn_limits{FastNPC := 100, MediumNPC := 30, StrongNPC := 8, DurationMinutes := 12.0},  # Round 3

        spawn_limits{FastNPC := 140, MediumNPC := 40, StrongNPC := 10, DurationMinutes := 16.0},  # Round 4

        spawn_limits{FastNPC := 180, MediumNPC := 50, StrongNPC := 12, DurationMinutes := 20.0},  # Round 5

        spawn_limits{FastNPC := 230, MediumNPC := 60, StrongNPC := 15,DurationMinutes := 24.0},  # Round 6

        spawn_limits{FastNPC := 270, MediumNPC := 75, StrongNPC := 18, DurationMinutes := 28.0},  # Round 7

        spawn_limits{FastNPC := 315, MediumNPC := 90, StrongNPC := 20, DurationMinutes := 32.0},  # Round 8

        spawn_limits{FastNPC := 360, MediumNPC := 105, StrongNPC := 22, DurationMinutes := 36.0},  # Round 9

        spawn_limits{FastNPC := 400, MediumNPC := 120, StrongNPC := 24, DurationMinutes := 40.0},  # Round 10

        spawn_limits{FastNPC := 440, MediumNPC := 130, StrongNPC := 26, DurationMinutes := 44.0},  # Round 11

        spawn_limits{FastNPC := 480, MediumNPC := 140, StrongNPC := 28, DurationMinutes := 48.0},  # Round 12

        spawn_limits{FastNPC := 520, MediumNPC := 150, StrongNPC := 30, DurationMinutes := 52.0},  # Round 13

        spawn_limits{FastNPC := 560, MediumNPC := 160, StrongNPC := 32, DurationMinutes := 56.0},  # Round 14

        spawn_limits{FastNPC := 600, MediumNPC := 175, StrongNPC := 35, DurationMinutes := 60.0},  # Round 15

        spawn_limits{FastNPC := 640, MediumNPC := 190, StrongNPC := 38, DurationMinutes := 64.0},  # Round 16

        spawn_limits{FastNPC := 680, MediumNPC := 205, StrongNPC := 40, DurationMinutes := 68.0},  # Round 17

        spawn_limits{FastNPC := 720, MediumNPC := 220, StrongNPC := 42, DurationMinutes := 72.0},  # Round 18

        spawn_limits{FastNPC := 760, MediumNPC := 235, StrongNPC := 44, DurationMinutes := 76.0},  # Round 19

        spawn_limits{FastNPC := 800, MediumNPC := 250, StrongNPC := 46, DurationMinutes := 80.0},  # Round 20

        spawn_limits{FastNPC := 840, MediumNPC := 265, StrongNPC := 48, DurationMinutes := 84.0},  # Round 21

        spawn_limits{FastNPC := 880, MediumNPC := 280, StrongNPC := 50, DurationMinutes := 88.0},  # Round 22

        spawn_limits{FastNPC := 920, MediumNPC := 295, StrongNPC := 52, DurationMinutes := 92.0},  # Round 23

        spawn_limits{FastNPC := 960, MediumNPC := 310, StrongNPC := 54, DurationMinutes := 96.0},  # Round 24

        spawn_limits{FastNPC := 1000, MediumNPC := 325, StrongNPC := 56, DurationMinutes := 100.0},  # Round 25

        spawn_limits{FastNPC := 1040, MediumNPC := 340, StrongNPC := 58, DurationMinutes := 104.0},  # Round 26

        spawn_limits{FastNPC := 1080, MediumNPC := 355, StrongNPC := 60, DurationMinutes := 108.0},  # Round 27

        spawn_limits{FastNPC := 1120, MediumNPC := 370, StrongNPC := 62, DurationMinutes := 112.0},  # Round 28

        spawn_limits{FastNPC := 1160, MediumNPC := 385, StrongNPC := 64, DurationMinutes := 116.0},  # Round 29

        spawn_limits{FastNPC := 1200, MediumNPC := 400, StrongNPC := 66, DurationMinutes := 120.0} # Round 30

    }



    \# Save player list

    set Players = GetPlayspace().GetPlayers()



    \# Start: disable all spawners (idle)

    FastNPCSpawner.Disable()

    MediumNPCSpawner.Disable()

    StrongNPCSpawner.Disable()

    ReadyUpTimer.Disable()



    \# Subscribe to the 1Player spawner's spawn event

    Player1Spawner.SpawnedEvent.Subscribe(OnPlayer1Spawned)

    Player2Spawner.SpawnedEvent.Subscribe(OnOtherPlayerSpawned)

    Player3Spawner.SpawnedEvent.Subscribe(OnOtherPlayerSpawned)

    Player4Spawner.SpawnedEvent.Subscribe(OnOtherPlayerSpawned)



    \# Subscribe to dialog button interaction

    TowerDefenseDialog.RespondingButtonEvent.Subscribe(OnTowerDefenseButtonPressed)



    \# Subscribe to ReadyUpTimer's SuccessEvent (fires when timer ends)

    ReadyUpTimer.SuccessEvent.Subscribe(OnReadyUpTimerExpired)

    set Self.DialogSubscription = option{Self.TowerDefenseDialog.RespondingButtonEvent.Subscribe(Self.OnDialogButtonPressed)}

    \# Notice: No call to Self.TowerDefenseDialog.Show() or Enable() here



OnOtherPlayerSpawned(Player: agent): void =

    Print("Other player spawned, dialog not shown and not subscribed.")



OnPlayer1Spawned(Player: agent): void =

    \# Show dialog and subscribe only for the player spawned from 1Player spawner

    if (FirstPlayer := player\[Player\]):

        TowerDefenseDialog.Show(FirstPlayer)

        Subscription := TowerDefenseDialog.RespondingButtonEvent.Subscribe(OnDialogButtonPressed)

        if (set PlayerDialogSubscriptions\[FirstPlayer\] = Subscription) {}

        set ActiveDialogPlayer = option{FirstPlayer}

        Print("Waiting for round start...")



OnDialogButtonPressed(Agent:agent, ButtonIndex:int):void =

    if (ActivePlayer := ActiveDialogPlayer?, Agent = ActivePlayer, ButtonIndex = 0):

        if (ButtonIndex = 0 and Self.RoundStarted = false):

            set Self.RoundStarted = true

            if (DialogSub := Self.DialogSubscription?):

                DialogSub.Cancel()

            set Self.DialogSubscription = false

            if (ButtonIndex = 0):

                \# When ready-up begins, keep round/spawners/timer disabled

                FastNPCSpawner.Disable()

                MediumNPCSpawner.Disable()

                StrongNPCSpawner.Disable()

                for (Cinematic : Rounds):

                    Cinematic.Stop()

                ReadyUpTimer.Enable()

                ReadyUpTimer.Start()

                \# Optionally, show a UI message to notify the ready up countdown has started



                \# Enable and show MainRound + EliminateEnemies for all players

                for (Player : GetPlayspace().GetPlayers()):

                    MainRound.Assign(Player)

                    MainRound.SetTitleText(StringToMessage("Complete Rounds"))

                    MainRound.SetDescriptionText(StringToMessage("Complete all 30 rounds and defend the item from aliens."))

                    MainRound.SetValue(0)  # Start at round 1



            \# Dismiss dialog and clean up only for the active player

            \# Only the 1Player spawner player can trigger start logic!

           if (Subscription := PlayerDialogSubscriptions\[ActivePlayer\]):

                Subscription.Cancel()

                TowerDefenseDialog.Hide(ActivePlayer)

            set PlayerDialogSubscriptions = map{}

            set ActiveDialogPlayer = false

            Print("Game Started!")

            \# Proceed with starting the game...

            \# Switch camera back to Fortnite's default third-person view

            Self.FixedPointCamera.Disable()

            Self.ThirdPersonControls.Enable()

           

            \# Reset camera for all players

            for (Player : GetPlayspace().GetPlayers()):

                FixedPointCamera.Disable()

                TowerDefenseDialog.Hide(Player)

            set PlayerDialogSubscriptions = map{}

            \# Proceed with starting the game...



            \# Subscribe trigger events for Levels

            for (LevelTrigger : Levels):

                LevelTrigger.TriggeredEvent.Subscribe(OnLevelTriggered)



OnLevelTriggered(Agent : ?agent): void =

    \# Each time any Level trigger fires, update trackers

    set CurrentRound += 1

    MainRound.SetValue(CurrentRound)



    \# Subscribe trigger events for Levels

    for (LevelTrigger : Levels):

        LevelTrigger.TriggeredEvent.Subscribe(OnLevelTriggered)



    Print("Level trigger activated, updated round trackers.")



\# Completely resets all triggers and cinematics, then enables/plays just one of each

ActivateSingleRound(RoundNumber : int):void =

    \# Disable ALL triggers

    for (Trigger : Levels): 

        Trigger.Disable()



    \# Stop ALL cinematics

    for (Cine : Rounds): 

        Cine.Stop()



    \# Enable the trigger for just this round if index is valid

    if (RoundNumber > 0 and RoundNumber <= Levels.Length):

        if (Level := Levels\[RoundNumber - 1\]): 

            Level.Enable()



    \# Play the cinematic for just this round if index is valid

    if (RoundNumber > 0 and RoundNumber <= Rounds.Length):

        if (Cine := Rounds\[RoundNumber - 1\]): 

            Cine.Play()



\# Advances to the next round. Only one trigger/cinematic is ever active at a time (for round 1..30)

AdvanceRound():void =

    if (CurrentRound < 30):

        set CurrentRound += 1

        ActivateSingleRound(CurrentRound)



EnableRoundDevices(RoundIndex:int):void =

    \# Enable the trigger for this round

    if (RoundIndex >= 0 and RoundIndex < Levels.Length):

        if (Trigger := Levels\[RoundIndex\]):

            Trigger.Enable()



    \# Play the cinematic for this round

    if (RoundIndex >= 0 and RoundIndex < Rounds.Length):

        if (Cinematic := Rounds\[RoundIndex\]):

            Cinematic.Play()



OnEliminateEnemiesComplete(Agent:agent):void =

    for (Player : GetPlayspace().GetPlayers()):

        for (Tracker : EliminateEnemies):

            Tracker.SetValue(0)

            Tracker.Assign(Player)

            Tracker.Remove(Player)

            Tracker.SetTitleText(StringToMessage("Alien Eliminations"))

            Tracker.SetDescriptionText(StringToMessage("Eliminate 43 aliens from reaching the item"))

            Tracker.CompleteEvent.Subscribe(OnEliminateEnemiesComplete)

            Print("Tracker completed for agent!")



    for (Tracker : EliminateEnemies):

        Tracker.CompleteEvent.Subscribe(OnEliminateEnemiesComplete)



StartNewRound(): void =

    \# Disable previous round if needed

    \# Enable new round devices

    EnableRoundDevices(CurrentRound - 1)

   

OnReadyUpTimerExpired(Agent: ?agent): void =

    \# After 30 seconds, enable round timer and spawners

    FastNPCSpawner.Enable()

    MediumNPCSpawner.Enable()

    StrongNPCSpawner.Enable()

    for (Cinematic : Rounds):

        Cinematic.Play()



OnTowerDefenseButtonPressed(Data:tuple(agent, int)):void=

    ButtonIndex := Data(1)

    case(ButtonIndex):

        \# 0: Start round, enable all spawners, move players, keep dialog shown

        0 => HandleStartRound()

        \_ => Print("Unknown button pressed")



HandleStartRound():void=

    if (RoundActive = false):

        set RoundActive = true

        Print("Round {CurrentRound} started.")

        \# Move all players to new location & keep dialog shown

        for (Player : GetPlayspace().GetPlayers()):

            if (Character := Player.GetFortCharacter\[\]):

                TargetLocation := vector3{X := 1000.0, Y := 1000.0, Z := 200.0}

                if (Character.TeleportTo\[TargetLocation, rotation{}\]):

                    Print("Moved player to target location at round start.")



StringToMessage<localizes>(InString: string): message = "{InString}"

round_system_manager script:

using { /Fortnite.com/Devices }

using { /Fortnite.com/Characters }

using { /Verse.org/Simulation }

using { /UnrealEngine.com/Temporary/Diagnostics }

using { /UnrealEngine.com/Temporary/SpatialMath }

using { /UnrealEngine.com/Temporary/UI }

using { /Fortnite.com/UI }

# Struct to hold spawn limits for each NPC type per round

spawn_limits := struct:

FastNPC : int

MediumNPC : int

StrongNPC : int

DurationMinutes : float

# Manages round-based gameplay similar to COD Zombies

round_system_manager := class(creative_device):

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

# Struct to hold spawn limits for each NPC type per round
spawn_limits := struct:
    FastNPC : int
    MediumNPC : int
    StrongNPC : int
    DurationMinutes : float

# Manages round-based gameplay similar to COD Zombies
round_system_manager := class(creative_device):

    # Editable device references

    # 30 levels prerounds
    @editable
    Levels : []trigger_device = array{
        # 30 placeholder entries for assignment in UEFN
        trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{},
        trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{},
        trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{},
        trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{},
        trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{},
        trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{}, trigger_device{}
    }

    @editable
    ReadyUpTimer : timer_device = timer_device{}

    @editable
    MainRound : tracker_device = tracker_device{}

    @editable
    EliminateEnemies : []tracker_device = array{}

    @editable
    EndRoundDevice : end_game_device = end_game_device{}

    @editable
    Rounds : []cinematic_sequence_device = array{
        # 30 placeholder entries for assignment in UEFN
        cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{},
        cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{},
        cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{},
        cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{},
        cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{},
        cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}, cinematic_sequence_device{}
    }

    @editable
    FastNPCSpawner : npc_spawner_device = npc_spawner_device{}

    @editable
    MediumNPCSpawner : npc_spawner_device = npc_spawner_device{}

    @editable
    StrongNPCSpawner : npc_spawner_device = npc_spawner_device{}

    @editable
    TowerDefenseDialog : popup_dialog_device = popup_dialog_device{}

    # Internal state
    var CurrentRound : int = 0
    var EliminationCount : int = 0
    var SpawnLimitsPerRound : []spawn_limits = array{}
    var LevelTriggered : []logic = array{}
    var IsRoundActive : logic = false
    var Players : []player = array{}
    ReadyDuration : float = 30.0 # seconds (30 second)

    # Initialize the round system
    OnBegin<override>()<suspends> : void =
        # Initialize spawn limits for each round
        set SpawnLimitsPerRound = array{
            spawn_limits{FastNPC := 30, MediumNPC := 10, StrongNPC := 4, DurationMinutes := 4.0},  # Round 1
            spawn_limits{FastNPC := 60, MediumNPC := 20, StrongNPC := 5, DurationMinutes := 8.0},  # Round 2
            spawn_limits{FastNPC := 100, MediumNPC := 30, StrongNPC := 8, DurationMinutes := 12.0},  # Round 3
            spawn_limits{FastNPC := 140, MediumNPC := 40, StrongNPC := 10, DurationMinutes := 16.0},  # Round 4
            spawn_limits{FastNPC := 180, MediumNPC := 50, StrongNPC := 12, DurationMinutes := 20.0},  # Round 5
            spawn_limits{FastNPC := 230, MediumNPC := 60, StrongNPC := 15,DurationMinutes := 24.0},  # Round 6
            spawn_limits{FastNPC := 270, MediumNPC := 75, StrongNPC := 18, DurationMinutes := 28.0},  # Round 7
            spawn_limits{FastNPC := 315, MediumNPC := 90, StrongNPC := 20, DurationMinutes := 32.0},  # Round 8
            spawn_limits{FastNPC := 360, MediumNPC := 105, StrongNPC := 22, DurationMinutes := 36.0},  # Round 9
            spawn_limits{FastNPC := 400, MediumNPC := 120, StrongNPC := 24, DurationMinutes := 40.0},  # Round 10
            spawn_limits{FastNPC := 440, MediumNPC := 130, StrongNPC := 26, DurationMinutes := 44.0},  # Round 11
            spawn_limits{FastNPC := 480, MediumNPC := 140, StrongNPC := 28, DurationMinutes := 48.0},  # Round 12
            spawn_limits{FastNPC := 520, MediumNPC := 150, StrongNPC := 30, DurationMinutes := 52.0},  # Round 13
            spawn_limits{FastNPC := 560, MediumNPC := 160, StrongNPC := 32, DurationMinutes := 56.0},  # Round 14
            spawn_limits{FastNPC := 600, MediumNPC := 175, StrongNPC := 35, DurationMinutes := 60.0},  # Round 15
            spawn_limits{FastNPC := 640, MediumNPC := 190, StrongNPC := 38, DurationMinutes := 64.0},  # Round 16
            spawn_limits{FastNPC := 680, MediumNPC := 205, StrongNPC := 40, DurationMinutes := 68.0},  # Round 17
            spawn_limits{FastNPC := 720, MediumNPC := 220, StrongNPC := 42, DurationMinutes := 72.0},  # Round 18
            spawn_limits{FastNPC := 760, MediumNPC := 235, StrongNPC := 44, DurationMinutes := 76.0},  # Round 19
            spawn_limits{FastNPC := 800, MediumNPC := 250, StrongNPC := 46, DurationMinutes := 80.0},  # Round 20
            spawn_limits{FastNPC := 840, MediumNPC := 265, StrongNPC := 48, DurationMinutes := 84.0},  # Round 21
            spawn_limits{FastNPC := 880, MediumNPC := 280, StrongNPC := 50, DurationMinutes := 88.0},  # Round 22
            spawn_limits{FastNPC := 920, MediumNPC := 295, StrongNPC := 52, DurationMinutes := 92.0},  # Round 23
            spawn_limits{FastNPC := 960, MediumNPC := 310, StrongNPC := 54, DurationMinutes := 96.0},  # Round 24
            spawn_limits{FastNPC := 1000, MediumNPC := 325, StrongNPC := 56, DurationMinutes := 100.0},  # Round 25
            spawn_limits{FastNPC := 1040, MediumNPC := 340, StrongNPC := 58, DurationMinutes := 104.0},  # Round 26
            spawn_limits{FastNPC := 1080, MediumNPC := 355, StrongNPC := 60, DurationMinutes := 108.0},  # Round 27
            spawn_limits{FastNPC := 1120, MediumNPC := 370, StrongNPC := 62, DurationMinutes := 112.0},  # Round 28
            spawn_limits{FastNPC := 1160, MediumNPC := 385, StrongNPC := 64, DurationMinutes := 116.0},  # Round 29
            spawn_limits{FastNPC := 1200, MediumNPC := 400, StrongNPC := 66, DurationMinutes := 120.0} # Round 30
        }

        # Save player list
        set Players = GetPlayspace().GetPlayers()

        # Initialize first round
        EnableCurrentLevel()
        PlayCurrentRoundCinematic()
        ActivateSingleRound(CurrentRound)

        # Initial setup - disable all spawners and timers
        FastNPCSpawner.Disable()
        MediumNPCSpawner.Disable()
        StrongNPCSpawner.Disable()
        ReadyUpTimer.Disable()
       
        # Subscribe to device events
        ReadyUpTimer.SuccessEvent.Subscribe(OnReadyUpTimerComplete)

        # Start with ready up for round 1
        ReadyUpTimer.Enable()
        ReadyUpTimer.Start() # Start Ready Up at game start

    OnReadyUpTimerComplete(Agent:?agent):void =
        # When ready up ends, update spawner limits and start round timer
        UpdateSpawnerLimits()

    OnReadyUpEnded(Agent : ?agent) : void =
        # Subscribe to ReadyUpTimer
        ReadyUpTimer.SuccessEvent.Subscribe(OnReadyUpEnded)
        ReadyUpTimer.Start() # Start the 30s timer

        # Enable and show MainRound + EliminateEnemies for all players
        for (Player : GetPlayspace().GetPlayers()):
            MainRound.Assign(Player)
            MainRound.SetTitleText(StringToMessage("Complete Rounds"))
            MainRound.SetDescriptionText(StringToMessage("Complete all 30 rounds and defend the item from aliens."))
            MainRound.SetValue(0) # Start at round 1

        for (Player : GetPlayspace().GetPlayers()):
            MainRound.Remove(Player)


    OnLevelTriggered(Agent : ?agent): void =
        # Each time any Level trigger fires, update trackers
        set CurrentRound += 1
        MainRound.SetValue(CurrentRound)


        # Subscribe trigger events for Levels
        for (LevelTrigger : Levels):
            LevelTrigger.TriggeredEvent.Subscribe(OnLevelTriggered)


        Print("Level trigger activated, updated round trackers.")

    # Completely resets all triggers and cinematics, then enables/plays just one of each
    ActivateSingleRound(RoundNumber : int):void =
        # Disable ALL triggers
        for (Trigger : Levels): 
            Trigger.Disable()

        # Stop ALL cinematics
        for (Cine : Rounds): 
            Cine.Stop()
 
        # Enable the trigger for just this round if index is valid
        if (RoundNumber > 0 and RoundNumber <= Levels.Length):
            if (Level := Levels[RoundNumber - 1]): 
                Level.Enable()
 
        # Play the cinematic for just this round if index is valid
        if (RoundNumber > 0 and RoundNumber <= Rounds.Length):
            if (Cine := Rounds[RoundNumber - 1]): 
                Cine.Play()
 
    # Advances to the next round. Only one trigger/cinematic is ever active at a time (for round 1..30)
    AdvanceRound():void =
        if (CurrentRound < 30):
            set CurrentRound += 1
            ActivateSingleRound(CurrentRound)

    # Enable only the current round's level trigger, disable others
    EnableCurrentLevel():void =
        for (Index -> Level : Levels):
            if (Index = CurrentRound - 1):
                Level.Enable()
            else:
                Level.Disable()

    # Play the cinematic for the current round if it exists
    PlayCurrentRoundCinematic():void =
        if (CurrentRound - 1 >= 0 and CurrentRound - 1 < Rounds.Length):
            if (CurrentCinematic := Rounds[CurrentRound - 1]):
                CurrentCinematic.Play()

    EnableRoundDevices(RoundIndex:int):void =
        # Enable the trigger for this round
        if (RoundIndex >= 0 and RoundIndex < Levels.Length):
            if (Trigger := Levels[RoundIndex]):
                Trigger.Enable()
 
        # Play the cinematic for this round
        if (RoundIndex >= 0 and RoundIndex < Rounds.Length):
            if (Cinematic := Rounds[RoundIndex]):
                Cinematic.Play()

    OnEliminateEnemiesComplete(Agent:agent):void =
        for (Player : GetPlayspace().GetPlayers()):
            for (Tracker : EliminateEnemies):
                Tracker.SetValue(0)
                Tracker.Assign(Player)
                Tracker.Remove(Player)
                Tracker.SetTitleText(StringToMessage("Alien Eliminations"))
                Tracker.SetDescriptionText(StringToMessage("Eliminate 43 aliens from reaching the item"))
                Tracker.CompleteEvent.Subscribe(OnEliminateEnemiesComplete)
                Print("Tracker completed for agent!")

        for (Tracker : EliminateEnemies):
            Tracker.CompleteEvent.Subscribe(OnEliminateEnemiesComplete)

    # Handle ready up phase completion
    OnReadyUpComplete(Agent : ?agent) : void =
        if (IsRoundActive = false):
        StartRound()
        EndRound()

    # Call this when starting a new round
    StartNewRound():void =
        if (CurrentRound < 30):
            set CurrentRound += 1
            EnableCurrentLevel()
            PlayCurrentRoundCinematic()

    # Start a new round
    StartRound() : void =

        set IsRoundActive = true
        set CurrentRound += 1

        # Update round tracker
        MainRound.SetValue(CurrentRound)

        # Spawn enemies based on round
        SpawnEnemiesForRound()

        # Enable and spawn NPCs
        FastNPCSpawner.Enable()
        MediumNPCSpawner.Enable()
        StrongNPCSpawner.Enable()
        FastNPCSpawner.Spawn()
        MediumNPCSpawner.Spawn()
        StrongNPCSpawner.Spawn()

        # Play round start cinematic
        for (Cinematic : Rounds):
            Cinematic.Play()

        # Teleport players to start position
        for (Player : GetPlayspace().GetPlayers()):
            if (Character := Player.GetFortCharacter[]):
                TargetLocation := vector3{X := 1000.0, Y := 1000.0, Z := 200.0}
                if (Character.TeleportTo[TargetLocation, rotation{}]):
                    Print("Moved player to target location at round start.")

    # Spawn enemies scaled to current round
    SpawnEnemiesForRound() : void =
        # Calculate spawn counts based on round
        var FastCount : int = 30 + CurrentRound
        var MediumCount : int = 10 + CurrentRound
        var StrongCount : int = 0
        if (CurrentRound >= 3):
        set StrongCount = CurrentRound - 1

        # Spawn enemies
        for (Index := 10..FastCount):
            FastNPCSpawner.Spawn()

        for (Index := 5..MediumCount):
            MediumNPCSpawner.Spawn()

        for (Index := 2..StrongCount):
            StrongNPCSpawner.Spawn()

    OnDialogButtonPressed(Agent: agent, ButtonIndex: int): void =
        if (ButtonIndex = 0):
            # When ready-up begins, keep round/spawners/timer disabled
            FastNPCSpawner.Disable()
            MediumNPCSpawner.Disable()
            StrongNPCSpawner.Disable()
            for (Cinematic : Rounds):
                Cinematic.Stop()
            # Set durations to avoid incorrect settings
            ReadyUpTimer.SetMaxDuration(30.0) # 30 seconds ready-up
            ReadyUpTimer.Enable()
            ReadyUpTimer.Start()

    OnReadyUpTimerExpired(Agent: ?agent): void =
        # After 30 seconds, enable round timer and spawners
        FastNPCSpawner.Enable()
        MediumNPCSpawner.Enable()
        StrongNPCSpawner.Enable()
        for (Cinematic : Rounds):
            Cinematic.Play()
       
        set IsRoundActive = true
        set CurrentRound += 1
        MainRound.SetValue(CurrentRound)

        # Update round tracker and reset eliminations for all players
        set CurrentRound += 1
        for (Player : GetPlayspace().GetPlayers()):
            MainRound.SetValue(CurrentRound)

    # Handle round timer completion
    OnRoundTimerComplete(Agent : ?agent) : void =
        if (IsRoundActive = true):
        EndRound()

    # Handle all enemies eliminated
    OnAllEnemiesEliminated(Agent : agent) : void =
        if (IsRoundActive = true):
            EndRound()
           
    # End the current round
    EndRound() : void =
        set IsRoundActive = false
        # Stop all spawners
        FastNPCSpawner.DespawnAll(false)
        MediumNPCSpawner.DespawnAll(false)
        StrongNPCSpawner.DespawnAll(false)
        # Check if game should end
        if (CurrentRound >= Rounds.Length):
            EndGame()

        # Disable all spawners
        FastNPCSpawner.Disable()
        MediumNPCSpawner.Disable()
        StrongNPCSpawner.Disable()

        # End the game if this is Level 30
        if (CurrentRound = 30):
            RoundPlayers := GetPlayspace().GetPlayers()
            if (Players.Length > 0):
                if (FirstPlayer := Players[0]):
                    EndRoundDevice.Activate(FirstPlayer)

        else:
            # Start ready up phase for next round
            # Reset timers and start ReadyUp again
            ReadyUpTimer.Reset()
            ReadyUpTimer.Start()

    # End the game
    EndGame() : void =
        # Activate end game device for the first player
        if (FirstPlayer := GetPlayspace().GetPlayers()[0]):
            EndRoundDevice.Activate(FirstPlayer)

    # Start next round
    StartNextRound() : void =
        ReadyUpTimer.Start()

    # Update spawner limits based on current round
    UpdateSpawnerLimits():void =
        if (CurrentRound <= SpawnLimitsPerRound.Length):
            if (Limits := SpawnLimitsPerRound[CurrentRound - 1]):
                # Enable/disable spawners based on limits
                if (Limits.FastNPC > 0):
                    FastNPCSpawner.Enable()
                else:
                    FastNPCSpawner.Disable()
 
                if (Limits.MediumNPC > 0):
                    MediumNPCSpawner.Enable()
                else:
                    MediumNPCSpawner.Disable()
 
                if (Limits.StrongNPC > 0):
                    StrongNPCSpawner.Enable()
                else:
                    StrongNPCSpawner.Disable()


    StringToMessage<localizes>(InString: string): message = "{InString}"