!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