Here, some useful functions to manage your teams :)

Haven’t tested in game yet. Feedback appreciated

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Random }
using { /Fortnite.com/Characters }
using { /Fortnite.com/Game }

team_handler := class:
    Index:int
    Device:team_manager
    HandlerTrigger(MaybeAgent:?agent):void=
        if (Agent := MaybeAgent?):
            Device.SetTeam(Agent,Index + 1)

team_manager := class(creative_device):  
    @editable TeamSelectors<private>:[]class_and_team_selector_device = array{}
    @editable ClassCheckTriggers<private>:[]trigger_device = array{}
    @editable RoundManager<private>:round_manager = round_manager{}

    # Store the team number for each agent in a map
    var TeamMap<private>:[agent]int = map{}
    # On join in progress, go to this team
    #
    # ! Make sure to set ISLAND SETTING default class to 5 as well so that on the start of round 1
    # everyone is put in this team using LoadTeamFromClass() !
    TeamOnProgressJoin<private>:int = 5

    # If 1 team remains, set this to the team number of the winners
    var WinningTeam<public>:?int = false
    # When set to false newly joining players will be killed and put into spectator mode
    var AllowJoin<public>:logic = true

    OnBegin<override>()<suspends>:void={
        SubscribeAll()
        LoadTeamFromClass()
    }
    # Get team number of agent
    GetTeam<public>(Agent:agent)<transacts>:int={
        if(TeamNumber := TeamMap[Agent]):
            return TeamNumber
        else:
            return -1
    }
    # TODO, update function when persistance is out
    # Set team for an agent using TeamSelector & update the TeamMap
    SetTeam<public>(Agent:agent,Team:int):void={
        if(TeamSelector := TeamSelectors[Team-1]):
            TeamSelector.ChangeTeam(Agent)
            if(set TeamMap[Agent] = Team){}
        else:
            Print("ERROR: Unable to switch agent to team {Team} in Team_Manager/SetTeam")
    }   
    # Eliminate Agent
    KillAgent<public>(InAgent:agent):void={
        if(FortCharacter := InAgent.GetFortCharacter[]):
            FortCharacter.Damage(200.0)
    }
    # Get all Agents in the Experience
    GetAllAgents<public>()<transacts>:[]agent={
        return Self.GetPlayspace().GetPlayers()
    } 
    # Get a random Agent that has not been eliminated
    GetRandomActiveAgent<public>()<decides><transacts>:agent={
        ActiveAgents := for:
            Agent:GetAllAgents()
            FortCharacter := Agent.GetFortCharacter[]
            FortCharacter.IsActive[]
        do:
            Agent
        Index := GetRandomInt(0,ActiveAgents.Length)
        return ActiveAgents[Index]
    } 
    # Get a list of Agents that are on this team
    GetAgentsInTeam<public>(Team:int):[]agent={
        Agents := for:
            Agent:GetAllAgents()
            GetTeam(Agent) = Team
        do:
            Agent
        return Agents
    }


    # TODO, update function when persistance is out
    # Subscribe all devices
    SubscribeAll<private>():void={   
        Self.GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded)
        Self.GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)

        for(X -> Trigger:ClassCheckTriggers):
            Handler := team_handler{Index := X,Device := Self}
            Trigger.TriggeredEvent.Subscribe(Handler.HandlerTrigger)
        
        for(InAgent:GetAllAgents()):
            if(FortCharacter := InAgent.GetFortCharacter[]):
                FortCharacter.EliminatedEvent().Subscribe(OnPlayerEliminated)          
    }
    # Handle player joining the island
    OnPlayerAdded<private>(Agent:agent):void={
        if(RoundManager.RoundNumber <= 1 and AllowJoin?):
            SetTeam(Agent, TeamOnProgressJoin)
        else:
            # Players that join after round 2 should spectate
            KillAgent(Agent)
    }
    # Handle player leaving the island
    OnPlayerRemoved<private>(Agent:agent):void={
        RemoveAgentFromMap(Agent) # TODO check if it maybe already auto removes from map on leave
        # Check if only 1 team remains on remove
        LMS()
    }
    # TODO, update function when persistance is out
    # On Round start set all players to the right team
    LoadTeamFromClass<private>():void={
        for:
            Trigger:ClassCheckTriggers
            Agent:GetAllAgents()
        do:
            Trigger.Trigger(Agent)      
    }
    # Set member variable WinningTeam to winner. Use in main.verse to end the round
    LMS<private>()<transacts>:void={
        # Put the teamsize for each team in a map from TeamNumber => TeamSize
        var TeamCounterMap:[int]int = map{}
        for:
            Agent:GetAllAgents()
            FortCharacter := Agent.GetFortCharacter[]
            FortCharacter.IsActive[]
        do:
            if(set TeamCounterMap[GetTeam(Agent)] += 1){}

        # Count the teams in the map
        TeamsAlive := for:
            TeamSize:TeamCounterMap
            TeamSize >= 1
        do:
            +1
        
        # On LMS, set WinningTeam value
        if(TeamsAlive = 1):
            if (Agent := GetRandomActiveAgent[]):
                set WinningTeam = option{GetTeam(Agent)}
    }
    # Check LMS on Eliminated
    OnPlayerEliminated<private>(Result:elimination_result):void={
        # Check if only 1 team remains on remove
        LMS()
    }
    # Removes an element from the given map and returns a new map without that element
    RemoveAgentFromMap<private>(PlayerToRemove:agent):[agent]int=
        var NewMap:[agent]int = map{}
        for (Player -> Team:TeamMap, Player <> PlayerToRemove):
            set NewMap = ConcatenateMaps(NewMap, map{Player => Team})
        return NewMap
        

The round manager used:

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

round_handler := class:
    Index:int
    Device:round_manager
    HandlerRoundStart():void=
        set Device.RoundNumber = Index + 1
        
round_manager := class(creative_device):    
    @editable Rounds<private>:[]round_settings_device = array{}
    var RoundNumber:int = 0

    OnBegin<override>()<suspends>:void={
        SubscribeAll()
    }
    # Everything that subscribes goes here
    SubscribeAll<private>():void={     
        for(X -> CurrentRound:Rounds){
            Handler := round_handler{Index := X,Device := Self}
            CurrentRound.RoundBeginEvent.Subscribe(Handler.HandlerRoundStart)
        } 
    }
6 Likes

Thank you for this Finest! , This really helpful!

1 Like

Thanks for sharing!

I have a custom score setup with teams in my map and I need to set which teams wins the round so I was wondering about how the WinningTeam works? Can you specify which team wins with this, and if so, how would I do that at the end of the round?
You mention in the code “# Set member variable WinningTeam to winner. Use in main.verse to end the round” but what is in main.verse?

In all my maps I always have a main.verse that contains the main game loop. It is the file where everything comes together, granting loot, grant kill rewards, end round.

I recently added this to the teammanager verse file:

# Completes on LMS, returns the Last Standing Team
    AwaitLMS<public>()<suspends>:int={
        loop:
            if(TeamNumber := WinningTeam?):
                return TeamNumber
            Sleep(0.01)
    }

An in my main I do this:
Onbegin:
Grant guns, teleport players, generate storm
and at the end I call:

WinningTeam := TeamManager.AwaitLMS()
            EndRound(WinningTeam)  

EndRound in main:

# End the round, grant rewards etc
    EndRound(WinningTeam:int)<suspends>:void={
        VoteManager.GrantIDItem()
        SettingsManager.GrantIDItems()
        Sleep(0.0)
        if(WinningAgent := TeamManager.GetRandomActiveAgent[]):
            EndRoundTracker.Complete(WinningAgent)
    }  
# Tracker has settings to end round on complete and grant activating team the win.

WinningTeam is the team number of the last team standing, for example Team 15 gives 15.
Teams are saved as an int in TeamMap. In my map I let players select a team in round 1, when selected they will switch to a class too. I read this class at round start using triggers to set them back to the correct team, as teams reset in between rounds.

2 Likes

Wonderful, thank you so much for the help!

What devices do we have to assign to the TeamSelectors?

You dont need them anymore, I found a better way to set and get team:


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

using { config }

team_manager := class(creative_device):  
    @editable RoundManager<private>:round_manager = round_manager{}

    @editable TrackerSaveTeam:tracker_device = tracker_device{}
    @editable SpectateSwitch:switch_device = switch_device{}
    @editable CheckSpectateStateTrigger:trigger_device = trigger_device{}
    @editable NewPlayerSwitch:switch_device = switch_device{}
    

    @editable NoClass<private>:class_and_team_selector_device = class_and_team_selector_device{}

    Logger<private>:log = log{Channel:=log_device}

    # If 1 team remains, set this to the team number of the winners
    var WinningTeam<public>:?int = false
    var PlayingAgents:[]agent = array{}
    var SpectateMap:[agent]logic = map{}

    OnBegin<override>()<suspends>:void={
        SubscribeAll()
        RoundManager.WaitForRoundBeginEvent()
        for(Agent:GetAllAgents()):
            if(FC := Agent.GetFortCharacter[]):
                FC.EliminatedEvent().Subscribe(OnPlayerEliminated)
            if(RoundManager.CurrentRound = 1):
                TrackerSaveTeam.ClearPersistence(Agent)
                SpectateSwitch.ClearPersistenceData(Agent)   
            else:
                SetTeam(Agent, TrackerSaveTeam.GetValue(Agent))
                SpectateSwitch.CheckState(Agent)  
    }
    # Get team number of agent
    GetTeam<public>(Agent:agent)<transacts>:int={
        TeamCollection := Self.GetPlayspace().GetTeamCollection()
        
        if(AgentTeam := TeamCollection.GetTeam[Agent]):
            for(I -> Team:TeamCollection.GetTeams()):
                if(AgentTeam = Team):
                    return I+1
        return -1
    }
    # Set team for an agent
    SetTeam<public>(Agent:agent,TeamNumber:int):void={
        TeamCollection := Self.GetPlayspace().GetTeamCollection()
        
        if(Team := TeamCollection.GetTeams()[TeamNumber-1]):
            if(TeamCollection.AddToTeam[Agent, Team]){}

        TrackerSaveTeam.SetValue(Agent,TeamNumber)
    }   
    #Heal agent for Amount
    HealAgent<public>(Agent:agent, Amount:float):void={
        if(FortCharacter := Agent.GetFortCharacter[]):
            HP := FortCharacter.GetHealth()
            Shield := FortCharacter.GetShield()

            HPMissing := FortCharacter.GetMaxHealth() - HP
            
            FortCharacter.SetHealth(Clamp(HP + Amount,0.0,FortCharacter.GetMaxHealth()))
            FortCharacter.SetShield(Clamp(Shield + Clamp(Amount - HPMissing,0.0,Amount),0.0,FortCharacter.GetMaxShield()))
    }
    # Get all Agents in the Experience, spectators too
    GetAllAgents<public>()<transacts>:[]agent={
        return Self.GetPlayspace().GetPlayers()
    } 
    # Gets all active agents in these teams: Teams
    GetAllActiveAgents<public>()<transacts>:[]agent={
        ActiveAgents := for:
            Agent:GetAllAgents()
            FortCharacter := Agent.GetFortCharacter[]
            FortCharacter.IsActive[]
        do:
            Agent
        return ActiveAgents
    }
    GetFightingAgents<public>()<transacts>:[]agent={
        Agents := for:
            Agent:GetAllAgents()
            FortCharacter := Agent.GetFortCharacter[]
            FortCharacter.IsActive[]
            
            #not EliminatedMap[Agent]?
        do:
            Agent
        return Agents
    }
    # Returns an array with all the teams that are still alive
    GetFightingTeams<public>()<transacts>:[]int={
        # Put the teamsize for each team in a map from TeamNumber => TeamSize
        var ActiveTeams:[]int = array{}
        for:
            Agent:GetFightingAgents()
            FortCharacter := Agent.GetFortCharacter[]
            FortCharacter.IsActive[]
        do:
            TeamNumber := GetTeam(Agent)
            set ActiveTeams += array{TeamNumber}
        
        return SortArray(RemoveDuplicates(ActiveTeams))
    }
    # Get a random Agent that has not been eliminated
    GetRandomActiveAgent<public>()<decides><transacts>:agent={
        ActiveAgents := GetAllActiveAgents()
        Index := GetRandomInt(0,ActiveAgents.Length - 1)
        return ActiveAgents[Index]
    } 
    # Get a random Agent
    GetRandomAgent<public>()<decides><transacts>:agent={
        Agents := GetAllAgents()
        Index := GetRandomInt(0,Agents.Length - 1)
        return Agents[Index]
    } 
    # Get a list of Agents that are on this team
    GetAgentsInTeam<public>(Team:int)<transacts>:[]agent={
        Agents := for:
            Agent:GetAllAgents()
            GetTeam(Agent) = Team
        do:
            Agent
        return Agents
    }
    CheckLMS<private>()<suspends>:void={
        Sleep(0.0)
        LMS()
    }
    # Set member variable WinningTeam to winner. Use in main.verse to end the round
    LMS<public>()<transacts>:?int={
        # On LMS, set WinningTeam value
        set WinningTeam = false
        PlayingTeams := RemoveFromArray(GetFightingTeams(), SpectateTeam)
        if(PlayingTeams.Length = 1):
            set WinningTeam = option{PlayingTeams[0]}
        else if(PlayingTeams.Length < 1):
            if(RandomAgent := GetRandomAgent[]):
                set WinningTeam = option{GetTeam(RandomAgent)}

        return WinningTeam
    }
    # Completes on LMS, returns the Last Standing Team
    AwaitLMS<public>()<suspends>:int={
        set WinningTeam = false
        spawn {CheckLMS()}

        loop:
            if(TeamNumber := WinningTeam?):
                return TeamNumber
            Sleep(0.01)
    }
    AwaitMaxPlayersLoaded()<suspends>:void={
        loop:
            Sleep(0.5)
            if(PlayingAgents.Length >= MaxPlayerCount):
                break
    }
    # Returns an array with all the teams that are still alive
    GetActiveTeams<public>()<transacts>:[]int={
        # Put the teamsize for each team in a map from TeamNumber => TeamSize
        var ActiveTeams:[]int = array{}
        for:
            Agent:GetAllAgents()
            FortCharacter := Agent.GetFortCharacter[]
            FortCharacter.IsActive[]
        do:
            TeamNumber := GetTeam(Agent)
            set ActiveTeams += array{TeamNumber}
        
        return SortArray(RemoveDuplicates(ActiveTeams))
    }


    # TODO, update function when persistance is out
    # Subscribe all devices
    SubscribeAll<private>():void={   
        NewPlayerSwitch.TurnedOnEvent.Subscribe(OnPlayerAdded)
        Self.GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)
        SpectateSwitch.TurnedOnEvent.Subscribe(SpectateButtonTurnedOn)
        SpectateSwitch.TurnedOffEvent.Subscribe(SpectateButtonTurnedOff)
    }
    SpectateButtonTurnedOn(Agent:agent):void={
        if(set SpectateMap[Agent] = true){}
    }
    SpectateButtonTurnOn(MaybeAgent:?agent):void={
        if(Agent := MaybeAgent?):
            if(set SpectateMap[Agent] = true){}
    }
    SpectateButtonTurnedOff(Agent:agent):void={
        if(set SpectateMap[Agent] = false){}
    }
    # Handle player joining the island
    OnPlayerAdded<private>(Agent:agent):void={
        SpectateSwitch.ClearPersistenceData(Agent)
        TrackerSaveTeam.ClearPersistence(Agent)
            
        if(FC := Agent.GetFortCharacter[]):
            FC.EliminatedEvent().Subscribe(OnPlayerEliminated)
    }
    # Handle player leaving the island
    OnPlayerRemoved<private>(Agent:agent):void={
        if(Temp := PlayingAgents.RemoveFirstElement[Agent]){set PlayingAgents = Temp}
        # Check if only 1 team remains on remove
        spawn {CheckLMS()}
    }
    # Check LMS on Eliminated
    OnPlayerEliminated<private>(Result:elimination_result):void={
        Print("Player eliminated in team manager")
        spawn {CheckLMS()}
    }
    RespawnAgents<public>()<suspends>:void={
        for(Agent:GetAllAgents()):
            #if(GetTeam(Agent) <> SpectateTeam and EliminatedMap[Agent]?):
            if(GetTeam(Agent) <> SpectateTeam and not Agent.GetFortCharacter[].IsActive[]):
                spawn{RespawnAgent(Agent)}
    }
    RespawnAgent<public>(Agent:agent)<suspends>:void={
        if(FortCharacter := Agent.GetFortCharacter[]):
            if(not FortCharacter.IsActive[]):
                Agent.Respawn(FortCharacter.GetTransform().Translation,FortCharacter.GetViewRotation())
            Sleep(0.0)
            NoClass.ChangeClass(Agent)
    }
1 Like

Wow, thanks you so much for the code is amazing. Do you know if there is a way to do GetClass and SetClass?

IDK