!strict local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local GameInfo = ReplicatedStorage:WaitForChild("GameInfo") :: Folder local Remotes = ReplicatedStorage:WaitForChild("Remotes") :: Folder local MainGame = Remotes:WaitForChild("MainGame") :: Folder local MatchState = MainGame:WaitForChild("MatchState") :: RemoteEvent local Modules = script.Parent local MapVoting = require(Modules:WaitForChild("MapVoting")) local MapManager = require(Modules:WaitForChild("MapManager")) local ModeManager = require(Modules:WaitForChild("ModeManager")) local PlayerManager = require(Modules:WaitForChild("PlayerManager")) type RBXConn = RBXScriptConnection export type GameController = { state: string, currentMap: Instance?, connections: { RBXConn }, SetState: (self: GameController, stateName: string) -> (), RunIntermission: (self: GameController) -> (), RunMatch: (self: GameController) -> (), Start: (self: GameController) -> (), } local INTERMISSION_DURATION: number = 25 local MIN_PLAYERS: number = 2 local MATCH_FALLBACK_DURATION: number = 180 local GameController = {} GameController.__index = GameController local function new(): GameController local self = setmetatable({ state = "Idle", currentMap = nil, connections = {}, }, GameController) :: GameController return self end function GameController:trackConnection(conn: RBXConn) local list = self.connections list[#list + 1] = conn end function GameController:cleanupConnections() local list = self.connections for i = 1, #list do local conn = list[i] if conn.Connected then conn:Disconnect() end end table.clear(list) end function GameController:playersReady(): boolean return #Players:GetPlayers() >= MIN_PLAYERS end function GameController:SetState(stateName: string) self.state = stateName MatchState:FireAllClients(stateName) local modeValue = GameInfo:FindFirstChild("Mode") if modeValue and modeValue:IsA("StringValue") then modeValue.Value = stateName end end local function setBool(folder: Instance, name: string, value: boolean) local obj = folder:FindFirstChild(name) if obj and obj:IsA("BoolValue") then obj.Value = value end end local function setString(folder: Instance, name: string, value: string) local obj = folder:FindFirstChild(name) if obj and obj:IsA("StringValue") then obj.Value = value end end local function setInt(folder: Instance, name: string, value: number) local obj = folder:FindFirstChild(name) if obj and obj:IsA("IntValue") then obj.Value = value end end function GameController:RunIntermission() self:SetState("Intermission") local duration = INTERMISSION_DURATION local elapsed = 0 while self.state == "Intermission" do local enoughPlayers = self:playersReady() local remaining = math.max(duration - elapsed, 0) if enoughPlayers then if remaining <= 0 then break end setString(GameInfo, "ModeInfo", "Next round in " .. tostring(remaining)) else setString(GameInfo, "ModeInfo", "Waiting for players...") end setBool(GameInfo, "OnGoing", false) setBool(GameInfo, "GradientBgAllowed", false) setBool(GameInfo, "GradientTextAllowed", false) setInt(GameInfo, "Countdown", remaining) if enoughPlayers and remaining <= 0 then break end task.wait(1) elapsed += 1 end end function GameController:RunMatch() self:SetState("PreMatch") local mapConfig = MapVoting.Select() local mapInstance = MapManager.Load(mapConfig) self.currentMap = mapInstance ModeManager.Apply(mapConfig) PlayerManager.Prepare(mapInstance) self:cleanupConnections() setBool(GameInfo, "OnGoing", true) setBool(GameInfo, "GradientBgAllowed", true) setBool(GameInfo, "GradientTextAllowed", true) setString(GameInfo, "ModeInfo", "Match in progress") setInt(GameInfo, "Countdown", 0) self:SetState("InGame") local ended = false local matchEndedSignal = MainGame:FindFirstChild("MatchEnded") if matchEndedSignal and matchEndedSignal:IsA("BindableEvent") then self:trackConnection(matchEndedSignal.Event:Connect(function() ended = true end)) end local elapsed = 0 while not ended and self.state == "InGame" do if #Players:GetPlayers() < MIN_PLAYERS then ended = true break end if elapsed >= MATCH_FALLBACK_DURATION then ended = true break end task.wait(1) elapsed += 1 end self:cleanupConnections() if self.currentMap then MapManager.Unload(self.currentMap) self.currentMap = nil end PlayerManager.Reset() self:SetState("PostMatch") setBool(GameInfo, "OnGoing", false) setString(GameInfo, "ModeInfo", "Round ended") setInt(GameInfo, "Countdown", 0) end function GameController:Start() while true do self:RunIntermission() if not self:playersReady() then continue end self:RunMatch() end end local controller = new() task.defer(function() controller:Start() end) return controller