So as a commission for The Garrison, I have made a Tic Tac Toe game:
This game works perfectly fine in the test place, I cannot break it no matter what I do. I’ve tried all sorts of edge cases such as leaving at win etc. but the game will not break.
However, in the Garrison, it is completely broken:
It won’t even function for one round half the time before freezing, then you have to use the quit button to leave (there is an intentional wait in case of server lag)
So why might it only be working in the test place? I’ve watched the Garrison dev set it up in Discord vc, with the readme script so there should be no reason why it wouldn’t work in the Garrison.
Structure of stuff:
Extra notes:
- There are no errors when run
- There are no errors when it breaks
- It just freezes when it feels like it
Code snippets
MainController, inside the part
-- initialisation to avoid cross contamination
TTTID = math.random(1,2147483647)
script.Parent.Parent.TTT_ID.Value = TTTID
script.Parent.Parent.Name = tostring(TTTID)
script.Parent.Parent.Color = Color3.new(math.random(),math.random(),math.random())
RemoteFolder = Instance.new("Folder",game.ReplicatedStorage)
RemoteFolder.Name = tostring(TTTID)
remoteTable = {"ButtonClicked","Move","Quit","TextSet"}
for i = 1,#remoteTable,1 do
local remoteConcerned = Instance.new("RemoteEvent")
remoteConcerned.Name = remoteTable[i]
remoteConcerned.Parent = RemoteFolder
end
-- the actual running of script
player1Value = script.Parent.Parent.Player1
player2Value = script.Parent.Parent.Player2
lastMovedPlayer = nil
function prompt(player)
print("Player has joined")
if player.PlayerGui:FindFirstChild("TicTacToeUI") == nil then -- make sure the player doesn't already have the UI in them
if script.Parent.Parent.GameRunning.Value == false then
if player1Value.Value == nil then
-- set up player 1
player1Value.Value = player
local newGui = game.ServerStorage.TicTacToeUI:Clone()
newGui.Parent = player.PlayerGui
newGui.Enabled = true
gameFullCheck(0,0)
player1Value.Value.PlayerGui.TicTacToeUI.CorrectScript.Value = script
player1Value.Value.PlayerGui.TicTacToeUI.CorrectButtonPressed.Value = RemoteFolder.ButtonClicked
player1Value.Value.PlayerGui.TicTacToeUI.CorrectMoved.Value = RemoteFolder.Move
elseif player2Value.Value == nil and player ~= player1Value.Value then
-- set up player 2
player2Value.Value = player
local newGui = game.ServerStorage.TicTacToeUI:Clone()
newGui.Parent = player.PlayerGui
newGui.Enabled = true
gameFullCheck(0,0)
player2Value.Value.PlayerGui.TicTacToeUI.CorrectScript.Value = script
player2Value.Value.PlayerGui.TicTacToeUI.CorrectButtonPressed.Value = RemoteFolder.ButtonClicked
player2Value.Value.PlayerGui.TicTacToeUI.CorrectMoved.Value = RemoteFolder.Move
else
print("dont try and break me")
end
else
print("game full right now")
end
end
end
function gameFullCheck(wins1,wins2)
print("Game full check is running....")
if player1Value.Value ~= nil and player2Value.Value ~= nil then
print("Game is full, starting")
script.Parent.Enabled = false -- disable the proximity prompt, which should fix the bug of the game being spammed
script.Parent.Parent.BillboardGui.Player1.Text = tostring(player1Value.Value)
script.Parent.Parent.BillboardGui.Player2.Text = tostring(player2Value.Value)
script.Parent.Parent.BillboardGui.Enabled = true
gameTable = {
{nil,nil,nil},
{nil,nil,nil},
{nil,nil,nil}
}
local plr1UI = player1Value.Value.PlayerGui.TicTacToeUI
local plr2UI = player2Value.Value.PlayerGui.TicTacToeUI
plr1UI.Base.Status.Text = "You are playing against "..tostring(player2Value.Value)
plr1UI.Base.TTTBody.TicTacToeModules:SetAttribute("ImageId","http://www.roblox.com/asset/?id=12814238844")
plr1UI.Base.WinCounter.Text = "Wins needed: "..tostring(wins1).."/2"
plr1UI.PlayingAgainst.Value = player2Value.Value
plr1UI.CorrectScript.Value = script
plr1UI.CorrectButtonPressed.Value = RemoteFolder.ButtonClicked
plr1UI.CorrectMoved.Value = RemoteFolder.Move
plr1UI.CorrectQuit.Value = RemoteFolder.Quit
plr1UI.CorrectTextSet.Value = RemoteFolder.TextSet
plr2UI.Base.Status.Text = "You are playing against "..tostring(player1Value.Value)
plr2UI.Base.TTTBody.TicTacToeModules:SetAttribute("ImageId","http://www.roblox.com/asset/?id=12814239298")
plr2UI.Base.WinCounter.Text = "Wins needed: "..tostring(wins2).."/2"
plr2UI.PlayingAgainst.Value = player1Value.Value
plr2UI.CorrectScript.Value = script
plr2UI.CorrectButtonPressed.Value = RemoteFolder.ButtonClicked
plr2UI.CorrectMoved.Value = RemoteFolder.Move
plr2UI.CorrectQuit.Value = RemoteFolder.Quit
plr2UI.CorrectTextSet.Value = RemoteFolder.TextSet
script.Parent.Parent.GameRunning.Value = true
task.wait(2)
local randomNum = math.random(1,2)
if randomNum == 1 then
script.Parent.Parent.PlayerToMove.Value = player1Value.Value
plr1UI.Base.Status.Text = "Your turn!"
plr2UI.Base.Status.Text = "Waiting for other player to move..."
else
script.Parent.Parent.PlayerToMove.Value = player2Value.Value
plr2UI.Base.Status.Text = "Your turn!"
plr1UI.Base.Status.Text = "Waiting for other player to move..."
end
lastMovedPlayer = nil
-- start the game here
local timerScript = script.Parent.Parent.PlayerToMove.Value.PlayerGui.TicTacToeUI.TimerScript:Clone()
timerScript.Parent = script.Parent.Parent.PlayerToMove.Value.PlayerGui.TicTacToeUI.Base.Timer
timerScript.Enabled = true
RemoteFolder.Move:FireClient(script.Parent.Parent.PlayerToMove.Value)
else
if player1Value.Value ~= nil then
script.Parent.ObjectText = "1/2 Players"
else
script.Parent.ObjectText = "0/2 Players"
end
end
end
function addMove(playerWhoMoved,button)
if playerWhoMoved == lastMovedPlayer then
playerWhoMoved:Kick("lol imagine trying to exploit")
end
lastMovedPlayer = playerWhoMoved
playerWhoMoved.PlayerGui.TicTacToeUI.Base.Timer.TimerScript:Destroy()
playerWhoMoved.PlayerGui.TicTacToeUI.Base.Timer:SetAttribute("TimeLeft",30)
RemoteFolder.TextSet:FireClient(playerWhoMoved)
-- all params are object values except button which is a string
-- start by changing status of player who moved
local playingAgainst = nil
-- get other player securely
if player1Value.Value == playerWhoMoved then
playingAgainst = player2Value.Value
else
playingAgainst = player1Value.Value
end
local againstTimer = playingAgainst.PlayerGui.TicTacToeUI.TimerScript:Clone()
againstTimer.Parent = playingAgainst.PlayerGui.TicTacToeUI.Base.Timer
againstTimer.Enabled = true
game.ReplicatedStorage.TextSet:FireClient(playerWhoMoved)
local parentIndex = tonumber(string.sub(button,2,2))
local childIndex = tonumber(string.sub(button,1,1))
gameTable[parentIndex][childIndex] = playerWhoMoved
for r,x in ipairs(playingAgainst.PlayerGui.TicTacToeUI.Base.TTTBody:GetChildren()) do
if x.Name == button then
x.Image = playerWhoMoved.PlayerGui.TicTacToeUI.Base.TTTBody.TicTacToeModules:GetAttribute("ImageId")
end
end
-- check if game is won here
local wonValue = nil
for count1 = 1,#gameTable,1 do
if gameTable[count1][1] == gameTable[count1][2] and gameTable[count1][2] == gameTable[count1][3] and gameTable[count1][3] ~= nil and wonValue == nil then
playerWhoMoved.PlayerGui.TicTacToeUI.Base.Status.Text = "Game won by "..tostring(playerWhoMoved)
playingAgainst.PlayerGui.TicTacToeUI.Base.Status.Text = "Game won by "..tostring(playerWhoMoved)
print("player game win text set")
wonValue = tostring(playerWhoMoved)
task.wait(5)
end
end
if wonValue == nil then
for count2 = 1,#gameTable,1 do
if gameTable[1][count2] == gameTable[2][count2] and gameTable[2][count2] == gameTable[3][count2] and gameTable[1][count2] ~= nil then
playerWhoMoved.PlayerGui.TicTacToeUI.Base.Status.Text = "Game won by "..tostring(playerWhoMoved)
playingAgainst.PlayerGui.TicTacToeUI.Base.Status.Text = "Game won by "..tostring(playerWhoMoved)
wonValue = tostring(playerWhoMoved)
task.wait(5)
end
end
if gameTable[1][1] == gameTable[2][2] and gameTable[2][2] == gameTable[3][3] and gameTable[2][2] ~= nil then
playerWhoMoved.PlayerGui.TicTacToeUI.Base.Status.Text = "Game won by "..tostring(playerWhoMoved)
playingAgainst.PlayerGui.TicTacToeUI.Base.Status.Text = "Game won by "..tostring(playerWhoMoved)
wonValue = tostring(playerWhoMoved)
task.wait(5)
elseif gameTable[1][3] == gameTable[2][2] and gameTable[2][2] == gameTable[3][1] and gameTable[2][2] ~= nil then
playerWhoMoved.PlayerGui.TicTacToeUI.Base.Status.Text = "Game won by "..tostring(playerWhoMoved)
playingAgainst.PlayerGui.TicTacToeUI.Base.Status.Text = "Game won by "..tostring(playerWhoMoved)
wonValue = tostring(playerWhoMoved)
task.wait(5)
end
end
-- is full check
local full = true
local tie = false
for count5 = 1,#gameTable,1 do
for count6 = 1,#gameTable,1 do
if gameTable[count5][count6] == nil then
full = false
end
end
end
if full == true and wonValue == nil then
tie = true
playerWhoMoved.PlayerGui.TicTacToeUI.Base.Status.Text = "Tie"
playingAgainst.PlayerGui.TicTacToeUI.Base.Status.Text = "Tie"
task.wait(5)
end
local function restartRound()
print("Round restarting")
playerWhoMoved.PlayerGui.TicTacToeUI:Destroy()
playingAgainst.PlayerGui.TicTacToeUI:Destroy()
if player1Value:GetAttribute("Wins") < 2 and player2Value:GetAttribute("Wins") < 2 then
print("cloning gui")
local newGui = game.ServerStorage.TicTacToeUI:Clone()
newGui.Parent = playerWhoMoved.PlayerGui
newGui.Enabled = true
local newGui2 = game.ServerStorage.TicTacToeUI:Clone()
newGui2.Parent = playingAgainst.PlayerGui
newGui2.Enabled = true
gameFullCheck(player1Value:GetAttribute("Wins"),player2Value:GetAttribute("Wins"))
print("gui clone done")
else
wonValue = Instance.new("ObjectValue",script)
wonValue.Value = playerWhoMoved
wonValue.Parent = game.ServerScriptService.TTTWonDataAdd
-- wipe the thing clean
script.Parent.Parent.Name = tostring(TTTID)
game.ServerScriptService.RegenerateGame:SetAttribute("GameToWipe",tostring(TTTID))
end
end
-- swap players if nobody won
if wonValue == nil and full == false then
task.wait(0.5)
RemoteFolder.Move:FireClient(playingAgainst)
playerWhoMoved.PlayerGui.TicTacToeUI.Base.Status.Text = "Waiting for other player to move..."
playingAgainst.PlayerGui.TicTacToeUI.Base.Status.Text = "Your turn!"
else
if tie == false then
if playerWhoMoved == player1Value.Value then
player1Value:SetAttribute("Wins",player1Value:GetAttribute("Wins")+1)
else
player2Value:SetAttribute("Wins",player2Value:GetAttribute("Wins")+1)
end
end
restartRound()
end
end
function playerQuitValues(newValue)
print(newValue:GetFullName())
print("Quit remote has been fired, VALUES")
local playerWhoQuit = newValue.Value
print(tostring(playerWhoQuit:GetFullName()).." is the player quitting at the moment")
actuallyQuitTheDamnGameAlreadyFFS(playerWhoQuit)
end
function playerQuitNonValues(newValue)
print("Quit remote has been fired, NON VALUES")
local playerWhoQuit = newValue
if newValue:IsA("Value") then
playerWhoQuit = newValue.Value
end
print(tostring(playerWhoQuit:GetFullName()).." is the player quitting at the moment")
actuallyQuitTheDamnGameAlreadyFFS(playerWhoQuit)
end
function actuallyQuitTheDamnGameAlreadyFFS(playerWhoQuit)
local other = nil
if player1Value.Value == playerWhoQuit then
other = player2Value.Value
elseif player2Value.Value == playerWhoQuit then
other = player1Value.Value
else
other = nil
end
-- wipe the thing clean
script.Parent.Parent.Name = tostring(TTTID)
game.ServerScriptService.RegenerateGame:SetAttribute("GameToWipe",tostring(TTTID)) -- set an attribute to wipe the game
playerWhoQuit.PlayerGui.TicTacToeUI:Destroy()
print("destroyed gui successfully")
if other ~= nil then
other.PlayerGui.TicTacToeUI.Base.Status.Text = "Other player has quit."
task.wait(5)
if other.PlayerGui.TicTacToeUI ~= nil then
other.PlayerGui.TicTacToeUI:Destroy()
end
end
end
script.Parent.PromptButtonHoldEnded:Connect(prompt)
RemoteFolder.ButtonClicked.OnServerEvent:Connect(addMove)
script.ChildAdded:Connect(playerQuitValues)
RemoteFolder.Quit.OnServerEvent:Connect(playerQuitNonValues)
RefreshGame:
-- Warning: Do not move the Play part into a folder!
function checkPlayersInGame()
if script.Parent.Parent.Player2.Value ~= nil then
print("Checking for both players in game...")
local isOneInGame = false
local isTwoInGame = false
for r,x in ipairs(game.Players:GetChildren()) do
if x == script.Parent.Parent.Player1.Value then
isOneInGame = true
print("Player 1 is in the game")
elseif x == script.Parent.Parent.Player2.Value then
isTwoInGame = true
print("Player 2 is in the game")
end
end
if isOneInGame == false or isTwoInGame == false then
game.ServerScriptService.RegenerateGame:SetAttribute("GameToWipe",tostring(script.Parent.Parent.TTT_ID.Value)) -- set an attribute to wipe the game
end
end
end
function bypassCheckGameWiper(playerWhoDied)
warn("A player has died. Urgent checks are being run...")
if script.Parent.Parent.Player1.Value == playerWhoDied or script.Parent.Parent.Player2.Value == playerWhoDied then
game.ServerScriptService.RegenerateGame:SetAttribute("GameToWipe",tostring(script.Parent.Parent.TTT_ID.Value)) -- set an attribute to wipe the game
end
end
game.ReplicatedStorage.PlayerDied.OnServerEvent:Connect(bypassCheckGameWiper)
if script.Parent.Parent.Parent ~= game.ServerStorage then
while true do
task.wait(10)
checkPlayersInGame()
end
end
RegenerateGame:
function attributeReceived()
print(script:GetAttribute("GameToWipe"))
local partPosition = nil
for r,x in ipairs(game.Workspace:GetDescendants()) do
if x.Name == script:GetAttribute("GameToWipe") then
print("Found")
partPosition = x.Position
x:Destroy()
end
end
local newGamePart = game.ServerStorage.Play:Clone()
newGamePart.Parent = workspace
newGamePart.Position = partPosition
end
script:GetAttributeChangedSignal("GameToWipe"):Connect(attributeReceived)
Player Died Remote Script
local players = game:GetService("Players")
local player = players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoid = character:WaitForChild("Humanoid")
humanoid.Died:Connect(function()
game.ReplicatedStorage.PlayerDied:FireServer()
end)