Simple 1v1 Round Manager in Verse
Module — 4 files
These files compile together (same module folder).
player_data_table.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Player Data Table for storing player stats
# Stores player statistics such as Score, Kills, Deaths, Wins, and Losses
# Tracks player performance in matches and sessions
player_data_table := class<final><persistable>():
Version<public>: int = 1
Score<public>: int = 0
Kills<public>: int = 0
Deaths<public>: int = 0
K_D<public>: float = 0.0
Wins<public>: int = 0
Losses<public>: int = 0
# Copy data from existing table (removed <constructor> - not allowed on class methods)
# Use this pattern: player_data_table{Version := X, Score := Y, ...}
# Calculate K/D ratio (simple division)
CalculateKD<public>():float =
if (Deaths > 0):
# Convert to float and divide
KillsFloat := Kills * 1.0
DeathsFloat := Deaths * 1.0
KillsFloat / DeathsFloat
else:
Kills * 1.0
player_stats_manager.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Manages and updates player_data_table instances for players
player_stats_manager := class():
var PlayerStatsMap<private>:weak_map(player, player_data_table) = map{}
# Return the player_data_table for the provided Agent
GetPlayerStats<public>(Agent:agent)<decides><transacts>:player_data_table=
if:
Player := player[Agent]
PlayerStatsTable := PlayerStatsMap[Player]
then:
PlayerStatsTable
else:
player_data_table{}
# Initialize stats for all current players
InitializeAllPlayers<public>(Players:[]player)<decides>:void =
for (Player : Players):
# Try to initialize, ignore if it fails (player already has stats)
if:
not PlayerStatsMap[Player]
NewTable := player_data_table{}
then:
set PlayerStatsMap[Player] = NewTable
# Initialize stats for the given player
InitializePlayer<public>(Player:player)<decides>:void=
# Only initialize if player doesn't have stats yet
if (not PlayerStatsMap[Player]):
set PlayerStatsMap[Player] = player_data_table{}
# Add to the given Agent's score
AddScore<public>(Agent:agent, NewScore:int)<decides>:void=
if:
Player := player[Agent]
PlayerStatsTable := PlayerStatsMap[Player]
then:
CurrentScore := PlayerStatsTable.Score
set PlayerStatsMap[Player] = player_data_table{
Version := PlayerStatsTable.Version,
Score := CurrentScore + NewScore,
Kills := PlayerStatsTable.Kills,
Deaths := PlayerStatsTable.Deaths,
K_D := PlayerStatsTable.K_D,
Wins := PlayerStatsTable.Wins,
Losses := PlayerStatsTable.Losses
}
else:
Print("Unable to record player score")
# Add to the given Agent's kills
AddKill<public>(Agent:agent)<decides>:void=
if:
Player := player[Agent]
PlayerStatsTable := PlayerStatsMap[Player]
then:
CurrentKills := PlayerStatsTable.Kills
NewKills := CurrentKills + 1
# Calculate new K/D
NewKD := if (PlayerStatsTable.Deaths > 0):
(NewKills * 1.0) / (PlayerStatsTable.Deaths * 1.0)
else:
NewKills * 1.0
# Update table with new kills and K/D
set PlayerStatsMap[Player] = player_data_table{
Version := PlayerStatsTable.Version,
Score := PlayerStatsTable.Score,
Kills := NewKills,
Deaths := PlayerStatsTable.Deaths,
K_D := NewKD,
Wins := PlayerStatsTable.Wins,
Losses := PlayerStatsTable.Losses
}
else:
Print("Unable to record player kill")
# Add to the given Agent's deaths
AddDeath<public>(Agent:agent)<decides>:void=
if:
Player := player[Agent]
PlayerStatsTable := PlayerStatsMap[Player]
then:
CurrentDeaths := PlayerStatsTable.Deaths
NewDeaths := CurrentDeaths + 1
# Calculate new K/D
NewKD := if (NewDeaths > 0):
(PlayerStatsTable.Kills * 1.0) / (NewDeaths * 1.0)
else:
PlayerStatsTable.Kills * 1.0
# Update table with new deaths and K/D
set PlayerStatsMap[Player] = player_data_table{
Version := PlayerStatsTable.Version,
Score := PlayerStatsTable.Score,
Kills := PlayerStatsTable.Kills,
Deaths := NewDeaths,
K_D := NewKD,
Wins := PlayerStatsTable.Wins,
Losses := PlayerStatsTable.Losses
}
else:
Print("Unable to record player death")
# Add to the given Agent's wins
AddWin<public>(Agent:agent)<decides>:void=
if:
Player := player[Agent]
PlayerStatsTable := PlayerStatsMap[Player]
then:
CurrentWins := PlayerStatsTable.Wins
set PlayerStatsMap[Player] = player_data_table{
Version := PlayerStatsTable.Version,
Score := PlayerStatsTable.Score,
Kills := PlayerStatsTable.Kills,
Deaths := PlayerStatsTable.Deaths,
K_D := PlayerStatsTable.K_D,
Wins := CurrentWins + 1,
Losses := PlayerStatsTable.Losses
}
else:
Print("Unable to record player win")
# Add to the given Agent's losses
AddLoss<public>(Agent:agent)<decides>:void=
if:
Player := player[Agent]
PlayerStatsTable := PlayerStatsMap[Player]
then:
CurrentLosses := PlayerStatsTable.Losses
set PlayerStatsMap[Player] = player_data_table{
Version := PlayerStatsTable.Version,
Score := PlayerStatsTable.Score,
Kills := PlayerStatsTable.Kills,
Deaths := PlayerStatsTable.Deaths,
K_D := PlayerStatsTable.K_D,
Wins := PlayerStatsTable.Wins,
Losses := CurrentLosses + 1
}
else:
Print("Unable to record player loss")
# Print player stats for debugging
PrintPlayerStats<public>(Agent:agent):void=
if:
Player := player[Agent]
PlayerStatsTable := PlayerStatsMap[Player]
then:
Print("Stats - Score: {PlayerStatsTable.Score}, Kills: {PlayerStatsTable.Kills}, Deaths: {PlayerStatsTable.Deaths}, K/D: {PlayerStatsTable.K_D}, Wins: {PlayerStatsTable.Wins}, Losses: {PlayerStatsTable.Losses}")
else:
Print("No stats found for player")
Game_Manager.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# 1v1 Combat Game Manager
# Manages overall game state, player tracking, and match flow
Game_Manager := class(creative_device):
# Player stats manager with weak_map
var PlayerStatsMgr<public>:player_stats_manager = player_stats_manager{}
# Game state tracking
var Player1: ?agent = false
var Player2: ?agent = false
var MatchActive: logic = false
var Player1Health: int = 100
var Player2Health: int = 100
# Player 1 Stats
var Player1Kills: int = 0
var Player1Deaths: int = 0
var Player1Wins: int = 0
var Player1Losses: int = 0
var Player1Score: int = 0
# Player 2 Stats
var Player2Kills: int = 0
var Player2Deaths: int = 0
var Player2Wins: int = 0
var Player2Losses: int = 0
var Player2Score: int = 0
# XP and scoring
var XPPerKill: int = 100
# Editable Accolades device (set in UEFN editor)
@editable var AccoladesDevice: ?accolades_device = false
# Debug: enable to auto-award a test kill after match start
@editable var EnableTestKill: logic = false
# Runs when the device is started in a running game
OnBegin<override>()<suspends>:void=
Print("Combat 1v1 Game Manager Initialized")
WaitForPlayers()
# Wait for 2 players to join and start the match
WaitForPlayers()<suspends>:void=
Print("Waiting for 2 players to join...")
loop:
# If we haven't assigned Player1/Player2 yet, grab the first two players in the playspace
if (not (Player1? and Player2?)):
TryAssignPlayers()
# Once we have 2 players, initialize persistent stats and start the match
if (Player1? and Player2?):
AllPlayers := GetPlayspace().GetPlayers()
# Ensure each player has a stats table entry (weak_map) before we record anything
if:
PlayerStatsMgr.InitializeAllPlayers[AllPlayers]
then:
Print("Initialized player stat tables")
else:
Print("InitializeAllPlayers failed")
StartMatch()
break
Sleep(0.5)
# Assign Player1/Player2 from the first two players in the playspace (simple learning-example approach)
TryAssignPlayers():void=
AllPlayers := GetPlayspace().GetPlayers()
for (P : AllPlayers):
if (A := agent[P]):
if (not Player1?):
set Player1 = A
Print("Assigned Player1")
else if (not Player2?):
set Player2 = A
Print("Assigned Player2")
break
# Start the combat match
StartMatch()<suspends>:void=
set MatchActive = true
Print("Match started! Player 1 vs Player 2")
Print("Player 1 Health: {Player1Health} | Player 2 Health: {Player2Health}")
# Stats tracking ready (using class vars)
# Debug: optionally trigger a test kill after the match starts
if (EnableTestKill = true):
TestAwardKill(3.0)
# Apply damage to an agent and handle elimination
ApplyDamage(Target:agent, Damage:int):void=
# Only process damage during an active match
if (not MatchActive = true):
return
# Determine which player was hit
if (Player1? and Target = Player1?):
set Player1Health = Player1Health - Damage
Print("Player1 took {Damage} damage, health now {Player1Health}")
if (Player1Health <= 0):
OnPlayerEliminated(Player1, Player2, false)
else:
if (Player2? and Target = Player2?):
set Player2Health = Player2Health - Damage
Print("Player2 took {Damage} damage, health now {Player2Health}")
if (Player2Health <= 0):
OnPlayerEliminated(Player2, Player1, true)
else:
Print("ApplyDamage: Target is not a tracked player")
# Handle player elimination: Victim died, Winner won
OnPlayerEliminated(Victim:?agent, Winner:?agent, WinnerIsPlayer1:logic):void=
if (WinnerIsPlayer1 = true):
Print("Player eliminated. Winner is Player 1")
else:
Print("Player eliminated. Winner is Player 2")
# Update stats (using direct vars)
if (Victim = Player1):
set Player1Deaths += 1
set Player1Losses += 1
else if (Victim = Player2):
set Player2Deaths += 1
set Player2Losses += 1
if (Winner = Player1):
set Player1Kills += 1
set Player1Wins += 1
set Player1Score += XPPerKill
else if (Winner = Player2):
set Player2Kills += 1
set Player2Wins += 1
set Player2Score += XPPerKill
# Also record stats into PlayerStatsMgr (persistent weak_map tables)
if (V := Victim?):
if: PlayerStatsMgr.AddDeath[V] then: {}
if: PlayerStatsMgr.AddLoss[V] then: {}
if (W := Winner?):
if: PlayerStatsMgr.AddKill[W] then: {}
if: PlayerStatsMgr.AddWin[W] then: {}
if: PlayerStatsMgr.AddScore[W, XPPerKill] then: {}
# End the round
EndRound()
# Give an accolade to the provided agent using the Accolades device
GiveAccoladeTo(Target:?agent)<suspends>:void=
if (not AccoladesDevice?):
Print("GiveAccoladeTo: no AccoladesDevice assigned")
return
if (not Target?):
Print("GiveAccoladeTo: no target agent to award")
return
# Note: Calling AccoladesDevice.Award requires specific effect context
# Uncomment and adapt if your Verse build supports it:
# if (AccoladesDevice?.Award(Target?)) then: Print("Accolade awarded")
Print("GiveAccoladeTo: Would award accolade to target (device call commented)")
# End the round, set match inactive
EndRound():void=
set MatchActive = false
Print("Round ended! Prepare for next round or reset.")
# Print final stats
Print("=== MATCH STATS ===")
Print("Player 1 - Kills: {Player1Kills}, Deaths: {Player1Deaths}, Score: {Player1Score}")
Print("Player 2 - Kills: {Player2Kills}, Deaths: {Player2Deaths}, Score: {Player2Score}")
Print("Player 1 K/D: {CalculateKD(Player1Kills, Player1Deaths)} | Player 2 K/D: {CalculateKD(Player2Kills, Player2Deaths)}")
# Calculate K/D ratio
CalculateKD(Kills:int, Deaths:int):float=
if (Deaths > 0):
KillsFloat := Kills * 1.0
DeathsFloat := Deaths * 1.0
return KillsFloat / DeathsFloat
else:
return Kills * 1.0
# Debug helper: wait DelaySeconds then award a kill to Player1 (for testing)
TestAwardKill(DelaySeconds:float)<suspends>:void=
Print("TestAwardKill: waiting {DelaySeconds} seconds...")
Sleep(DelaySeconds)
if (Player1? and Player2?):
Print("TestAwardKill: simulating Player1 eliminating Player2")
OnPlayerEliminated(Player2, Player1, true)
else:
Print("TestAwardKill: Players not present")
player_Leaderboard.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Leaderboard manager that displays player stats from player_stats_manager
player_Leaderboard := class(creative_device):
# Reference to the Game_Manager to read live stats (assign in UEFN editor)
@editable var GameManager:?Game_Manager = false
# Reference to the stats manager (will be replaced with GameManager.PlayerStatsMgr if GameManager is assigned)
var StatsManager<public>:player_stats_manager = player_stats_manager{}
# HUD Message device to display stats (assign in UEFN editor)
# Note: Use HUD Message Device for dynamic text, not Billboard
@editable var HudMessageDevice:?hud_message_device = false
# Optional switch device to toggle stats display
@editable var ShowStatsSwitch:?switch_device = false
# Control whether stats are displayed
var StatsDisplayEnabled:logic = true
# Update interval in seconds
@editable var UpdateInterval:float = 5.0
# Runs when device starts
OnBegin<override>()<suspends>:void=
Print("Leaderboard Manager Initialized")
# Use the stats manager instance owned by the GameManager (so we read the same weak_map)
if (GM := GameManager?):
set StatsManager = GM.PlayerStatsMgr
else:
Print("Leaderboard: GameManager not assigned; using local StatsManager (may be empty)")
# Setup switch listener if available
if (Switch := ShowStatsSwitch?):
Switch.TurnedOnEvent.Subscribe(OnSwitchActivated)
Switch.TurnedOffEvent.Subscribe(OnSwitchDeactivated)
StartLeaderboardUpdates()
# Called when switch is activated (turned on)
OnSwitchActivated(Agent:agent):void=
set StatsDisplayEnabled = true
Print("Stats display enabled")
# Called when switch is deactivated (turned off)
OnSwitchDeactivated(Agent:agent):void=
set StatsDisplayEnabled = false
Print("Stats display disabled")
ClearBillboard()
# Continuously update the leaderboard
StartLeaderboardUpdates()<suspends>:void=
loop
Sign in to download module
Copy-paste each file above is always free.