local Players = game:GetService("Players") local ServerScriptService = game:GetService("ServerScriptService") local ReplicatedStorage = game:GetService("ReplicatedStorage") local ServerStorage = game:GetService("ServerStorage") local Data = ReplicatedStorage.Data local BotData = require(Data.BotData) local DataTypes = require(Data.DataTypes) local GameState = require(Data.GameState) local sData = ServerScriptService.Data local DataManager = require(sData.DataManager) local ValueNames = require(sData.ValueNames) local sModules = ServerScriptService.Modules local Bot = require(sModules.Classes.GameObject.Bot) local VotingHandlerServer = require(sModules.VotingHandlerServer) local TurnManager = require(sModules.TurnManager) local ObjectManager = require(sModules.ObjectManager) local MapManager = require(sModules.Map.MapManager) local FerrUtils = require(ReplicatedStorage.Shared.SharedUtils.FerrUtils) local rBotUtils = require(ReplicatedStorage.Shared.SharedUtils.sharedBotUtils) local BotUtils = require(sModules.Utils.BotUtils) local Remote = ReplicatedStorage.Remote local rev_statusRemoteEvent = Remote.StatusRemoteEvent local rev_timerRemoteEvent = Remote.TimerRemoteEvent local rev_UpdateGameState = Remote.UpdateGameState local Round = {} Round.__index = Round function getPlaying() : {Player} return game:GetService("CollectionService"):GetTagged("PLAYING") end function Round.new() local self = setmetatable({}, Round) if game:GetService("RunService"):IsStudio() then self.IntermissionTime = 1 else self.IntermissionTime = 1 end self.MapVotingTime = 100 self.GraceTime = 1 self.DecisionTime = 15 self.Status = "" self.RoundResults = {} self:ChangeGameState(GameState.LOBBY) self.Timer = 0 return self end function Round:ChangeStatus(newText : DataStoreListingPages) self.Status = newText self:UpdateState() end function Round:FireTo(remote,players,...) players = players or game.Players:GetPlayers() for i,v in pairs(players) do remote:FireClient(v,...) end end function Round:ChangeGameState(newState : number) self.GameState = newState shared.Phase = newState self:FireTo(rev_UpdateGameState,getPlaying(),newState) end function Round:UpdateState() rev_statusRemoteEvent:FireAllClients(self.Status .. ": " .. self.Timer) end function Round:EnoughPlayers() return #Players:GetPlayers() >= 2 or game:GetService("RunService"):IsStudio() and script:GetAttribute("StudioEnoughPlayersOverride") end function Round:AllPlayersReady() return #getPlaying() == TurnManager.GetReadyPlayers() end function Round:WaitFor(sec : number,condition,...) for index = sec,0,-1 do self.Timer = index self:UpdateState() task.wait(.5) if self.Break then self.Break = false self.Timer = 0 self:UpdateState() break end end end function Round:Halt() repeat task.wait() until self.Break == false end function Round:Intermission() self:ChangeGameState(GameState.LOBBY) self:ChangeStatus("Intermission") self:WaitFor(self.IntermissionTime) end function Round:ChooseMap() workspace.Map:ClearAllChildren() while task.wait() do self:ChangeStatus("Choose Map") VotingHandlerServer.StartVoting(1,"MapData",workspace.Lobby["Map Voting Stands"]:GetChildren()) self:WaitFor(self.MapVotingTime) local winnerIndex,winnerData : DataTypes.VoteOptionData = VotingHandlerServer.EndVoting() print(winnerIndex) print(winnerData) MapManager.SpawnMap(winnerData.Name) break end self:ChangeStatus("Loading map...") self:WaitFor(MapManager.MAP_LOAD_TIME) end function Round:BringPlayers() local pick = "PlasmaBot" for _,plr in pairs(game.Players:GetPlayers()) do local char = plr.Character if not char then continue end char:PivotTo(workspace.BoxOfDoom.PrimaryPart.CFrame) local newBot = Bot.new(plr,pick,char) newBot:_Init() plr:AddTag("PLAYING") end BotUtils.SpawnBots(getPlaying()) end Round.END_TYPES = { WIN = 1, DRAW = 2, } function Round:ShouldRoundEnd() local max = #getPlaying() if game:GetService("RunService"):IsStudio() then return end local alive = {} for i,id in pairs(#getPlaying()) do local b = ObjectManager.Get(id) if b.Components.Health:IsAlive() then table.insert(alive,id) else ObjectManager.Destroy(id) end end if #alive == 0 then return Round.END_TYPES.DRAW end if #alive == 1 then return Round.END_TYPES.WIN,alive[1] end end function Round:GracePeriod() self:ChangeStatus("Grace period") self:ChangeGameState(GameState.GRACE) self:WaitFor(self.GraceTime) end function Round:TryStopEarlyAim() task.spawn(function() repeat task.wait() until self.GameState == GameState.RESOLVING or self:AllPlayersReady() if self.GameState ~= GameState.RESOLVING then self.Break = true end self:Halt() end) end function Round:StartAimPhase() --for i,v in pairs(workspace.Playing:GetChildren()) do -- v.PrimaryPart.Anchored = true --end self:ChangeStatus("Decision making") self:ChangeGameState(GameState.AIMING) for i,plr in pairs(getPlaying()) do local primary = rBotUtils.GetBotRoot(plr.UserId) local rot = primary.Orientation primary.Orientation = Vector3.new(0,0,rot.Z) end TurnManager.collectInputs() self:TryStopEarlyAim() self:WaitFor(self.DecisionTime) end function Round:ShouldStopEarlyResolve() for _,gameObject in pairs(ObjectManager.GameObject) do if not gameObject.model or not gameObject.model.Parent then continue end local vel = gameObject.model.PrimaryPart.AssemblyLinearVelocity local horizontalVel = Vector3.new(vel.X, 0, vel.Z).Magnitude local verticalVel = math.abs(vel.Y) if horizontalVel > 0.1 or verticalVel > 0.5 or not rBotUtils.IsObjectOnFloor(gameObject.model) then return false end end return true end function Round:TryStopEarlyResolve() task.spawn(function() local stableTime = 0 print(self.GameState) while self.GameState == GameState.RESOLVING do task.wait(0.1) if self:ShouldStopEarlyResolve() then stableTime += 0.1 if stableTime >= 0.5 then -- must be stable for 0.5s self.Break = true break end else stableTime = 0 end end self:Halt() end) end function Round:StartResolvePhase() self:ChangeGameState(GameState.RESOLVING) self:TryStopEarlyResolve() for _, v : Model in pairs(workspace.Bots:GetChildren()) do local root = v.PrimaryPart root:SetNetworkOwner(nil) v:PivotTo(v:GetPivot()) root.Velocity = Vector3.zero root.Anchored = false end TurnManager.resolve() self:ChangeStatus("Resolve") self:WaitFor(10) end function Round:Start() local endType,winner = nil,nil while task.wait() do endType,winner = self:ShouldRoundEnd() if endType then return endType,winner end self:StartAimPhase() self:StartResolvePhase() end end function Round:Results(endtype,winner : number) for i,v in pairs(getPlaying()) do v:RemoveTag("PLAYING") end ObjectManager.DestroyAll() MapManager.DestroyMap() local text if endtype == Round.END_TYPES.WIN then local winnerName pcall(function() winnerName = game.Players:GetNameFromUserIdAsync(winner) end) winnerName = winnerName or "Studio guy" text = winnerName .. " has won!" pcall(function() DataManager.AddValue(game.Players:GetPlayerByUserId(winner),ValueNames.Wins,1) end) else text = "It's a draw!" end for i,v in pairs(game.Players:GetPlayers()) do v:LoadCharacterAsync() end self:ChangeGameState(GameState.RESULTS) self:ChangeStatus(text) self:WaitFor(5) end function Round:Initiate() while true do local result = self:EnoughPlayers() if not result then self:ChangeStatus("Not enough players") return end self:Intermission() self:ChooseMap() self:BringPlayers() self:GracePeriod() local endType,winner = self:Start() self:Results(endType,winner) end end return Round