Hello!
I wrote some code for a race system, but I am having trouble debugging it and adding new features to it due to its unreadablity. I’m looking for some suggestions for how I can reformat this code to be more readable. It is a ModuleScript that is accessed by a few other scripts, creating a front-end/back-end system. Thanks!
The code :
BadgeService = game:GetService("BadgeService")
FadeToBlack = require(game.StarterGui.FadeToBlackGoBrr.Fade)
local RaceManager = {}
RaceManager.firstPlaceLoot = {
crystals = 3,
XP = 50,
aetherDust = 5
}
RaceManager.secondPlaceLoot = {
crystals = 2,
XP = 25,
aetherDust = 3
}
RaceManager.thirdPlaceLoot = {
crystals = 1,
XP = 10,
aetherDust = 2
}
TweenService = game:GetService("TweenService")
RaceManager.playersRequiredForRace = 1
--When a player joins the race:
RaceManager.JoinRace = function(raceName, playerName)
--Get the neccessary objects (player, race folder, attribute(s)):
local raceFolder = game.Workspace:WaitForChild(raceName.."Race")
local playerObject = game.Players:WaitForChild(playerName)
local raceAttribute = nil
local progressAttribute = nil
local startTimeAttribute = nil
--If isRacing is not true, join the race. If isRacing is true, let the player know they can't join:
if raceFolder.IsRacing.Value == false then
--Create the race attribute:
raceAttribute = playerObject:SetAttribute("RaceName", raceName)
--Create the race progress attribute:
progressAttribute = playerObject:SetAttribute("RaceProgress", 0)
--Create the player time attribute:
startTimeAttribute = playerObject:SetAttribute("StartTime", 0)
--Increase the value holding the number of players in the race:
raceFolder.PlayersInRace.Value += 1
FadeToBlack.FadeToBlackWithWait(game.Players:FindFirstChild(playerName), .25, 0.75)
wait(0.25)
game.ReplicatedStorage.JoinLeaveRace:Fire(playerName, "Joining")
game.Workspace:WaitForChild(playerName):SetPrimaryPartCFrame(raceFolder.StartBlock.CFrame * CFrame.Angles(0, math.rad(180), 0))
wait(.1)
game.Workspace:WaitForChild(playerName).PrimaryPart.Anchored = true
playerObject.PlayerGui.LevelGui.Enabled = false
return true
else
return false
end
end
--When the player leaves a race:
RaceManager.LeaveRace = function(playerName)
--Get the neccessary objects (player, race folder):
local playerObject = game.Players:WaitForChild(playerName)
local raceName = playerObject:GetAttribute("RaceName")
local raceFolder = game.Workspace:WaitForChild(raceName.."Race")
--Remove 1 from the value holding the number of players in the race. If less than 1, stop the race entirely:
raceFolder.PlayersInRace.Value -= 1
--raceFolder.PlayersRacing:FindFirstChild(playerName.."Completed"):Destroy()
if raceFolder.PlayersInRace.Value <= 1 then
raceFolder.IsRacing.Value = false
raceFolder.PlayersInRace.Value = 0
end
FadeToBlack.FadeToBlackWithWait(game.Players:FindFirstChild(playerName), .25, 1.5)
wait(0.25)
game.ReplicatedStorage.JoinLeaveRace:Fire(playerName, "Leaving")
wait(0.125)
--Delete the player's race attribute to avoid unintentional race joins:
playerObject:SetAttribute("RaceName", nil)
playerObject:SetAttribute("RaceProgress", nil)
if playerObject:FindFirstChild(raceName.."Checkpoints") then
playerObject:WaitForChild(raceName.."Checkpoints"):Destroy()
end
local currentPlayers = 0
for index, playerLooped in pairs(game.Players:GetPlayers()) do
if playerLooped:GetAttribute("RaceName") ~= nil then
currentPlayers += 1
end
end
if currentPlayers == 0 then
raceFolder.IsRacing.Value = false
raceFolder.PlayersInRace.Value = 0
end
game.Workspace:WaitForChild(playerName):SetPrimaryPartCFrame(raceFolder.ReturnBlock.CFrame)
game.Workspace:WaitForChild(playerName).PrimaryPart.Anchored = false
--playerObject.PlayerGui.LevelGui.Enabled = true
return true
end
--When a player finishes a race:
local playersCompleted = 0
RaceManager.FinishRace = function(playerWhoFinishedName, raceName, isAI)
if isAI == false then
local player = game.Players:FindFirstChild(playerWhoFinishedName)
local raceFolder = game.Workspace:WaitForChild(raceName.."Race")
local playersRacing = raceFolder.PlayersRacing
local playerPosition = 0
local playerTime = os.clock() - player:GetAttribute("StartTime")
local awardedCrystals
local awardedAetherDust
local awardedXP
for index, playerCompleted in pairs(playersRacing:GetChildren()) do
if playerCompleted.Value == true then
playersCompleted += 1
end
end
playersRacing:FindFirstChild(playerWhoFinishedName.."Completed").Value = true
playerPosition = playersCompleted + 1
if player.Data.RaceTimes:FindFirstChild(raceName.."Race").Value > playerTime then
player.Data.RaceTimes:FindFirstChild(raceName.."Race").Value = playerTime
end
--Do stuff with the placing:
if playersCompleted == 0 then
player:WaitForChild("Data"):WaitForChild("Crystals").Value += RaceManager.firstPlaceLoot.crystals * RaceManager.GetCheckpoints(raceName)
player:WaitForChild("Data"):WaitForChild("XP").Value += RaceManager.firstPlaceLoot.XP
player:WaitForChild("Data"):WaitForChild("AetherDust").Value += RaceManager.firstPlaceLoot.aetherDust
awardedCrystals = RaceManager.firstPlaceLoot.crystals * RaceManager.GetCheckpoints(raceName)
awardedXP = RaceManager.firstPlaceLoot.XP
awardedAetherDust = RaceManager.firstPlaceLoot.aetherDust
--Give Certified Racer badge:
BadgeService:AwardBadge(player.UserId, 2127816704)
if player.Data.Settings.ProMode.Value == true then
BadgeService:AwardBadge(player.UserId, 2127816715)
end
elseif playersCompleted == 1 then
player:WaitForChild("Data"):WaitForChild("Crystals").Value += RaceManager.secondPlaceLoot.crystals * RaceManager.GetCheckpoints(raceName)
player:WaitForChild("Data"):WaitForChild("XP").Value += RaceManager.secondPlaceLoot.XP
player:WaitForChild("Data"):WaitForChild("AetherDust").Value += RaceManager.secondPlaceLoot.aetherDust
awardedCrystals = RaceManager.secondPlaceLoot.crystals * RaceManager.GetCheckpoints(raceName)
awardedXP = RaceManager.secondPlaceLoot.XP
awardedAetherDust = RaceManager.secondPlaceLoot.aetherDust
elseif playersCompleted == 2 then
player:WaitForChild("Data"):WaitForChild("Crystals").Value += RaceManager.thirdPlaceLoot.crystals * RaceManager.GetCheckpoints(raceName)
player:WaitForChild("Data"):WaitForChild("XP").Value += RaceManager.thirdPlaceLoot.XP
player:WaitForChild("Data"):WaitForChild("AetherDust").Value += RaceManager.thirdPlaceLoot.aetherDust
awardedCrystals = RaceManager.thirdPlaceLoot.crystals * RaceManager.GetCheckpoints(raceName)
awardedXP = RaceManager.thirdPlaceLoot.XP
awardedAetherDust = RaceManager.thirdPlaceLoot.aetherDust
end
game.ReplicatedStorage.FinishedRace:Fire(playerWhoFinishedName, playerPosition, playerTime, awardedCrystals, awardedXP, awardedAetherDust)
game.ReplicatedStorage.FinishV2Race:FireClient(player, playerPosition, playerTime, awardedCrystals, awardedXP, awardedAetherDust)
wait(0.1)
raceFolder.PlayersInRace.Value += 1
FadeToBlack.FadeToBlackWithWait(player, .25, 0.25)
wait(0.25)
RaceManager.LeaveRace(playerWhoFinishedName)
return playerPosition
else
playersCompleted += 1
end
end
RaceManager.GetCurrentRankings = function(raceName)
local rankings = {}
--Players:
for index, player in pairs(game.Players:GetPlayers()) do
if player:GetAttribute("RaceName") == raceName then
local playerProgress = math.floor(player:GetAttribute("RaceProgress") * 100) / 100
local playerRaceData = {
player.Name,
playerProgress
}
table.insert(rankings, playerRaceData)
end
end
--AI:
for index, AI in pairs(game.Workspace:FindFirstChild(raceName.."Race").AI:GetChildren()) do
local AIProgress = math.floor(AI:GetAttribute("RaceProgress") * 100) / 100
local AIRaceData = {
AI.Name,
AIProgress
}
table.insert(rankings, AIRaceData)
end
table.sort(rankings, function(a,b)
return a[2] > b[2]
end)
return rankings
end
RaceManager.GetRankingOfPlayer = function(playerName, raceName)
local player = game.Players:FindFirstChild(playerName)
local rankingTable = RaceManager.GetCurrentRankings(raceName)
for index, playerData in pairs(rankingTable) do
if playerData[1] == playerName then
return index
end
end
end
RaceManager.CheckpointCollection = function(raceName)
local raceFolder = game.Workspace:WaitForChild(raceName.."Race")
local numberOfCheckpoints = #raceFolder.Checkpoints:GetChildren() - 2
for index, checkpoint in raceFolder.Checkpoints:GetChildren() do
if checkpoint.Name ~= "CloneInto" and checkpoint.Name ~= "CreatePath" then
checkpoint.Detector.Detector.Touched:Connect(function(hitObject)
local checkpointNumber = tonumber(string.sub(checkpoint.Name, 11, #checkpoint.Name))
if hitObject.Name == "HumanoidRootPart" and hitObject.Parent:GetAttribute("IsAI") == nil then
local hitPlayer = game.Players:FindFirstChild(hitObject.Parent.Name)
if hitPlayer:GetAttribute("RaceName") == raceName then
if checkpointNumber == 1 then
if checkpointNumber == numberOfCheckpoints or hitPlayer:FindFirstChild(raceName.."Checkpoints"):FindFirstChild(checkpointNumber + 1).Value ~= true then
hitPlayer:FindFirstChild(raceName.."Checkpoints"):FindFirstChild(checkpointNumber).Value = true
RaceManager.RacePercentage = checkpointNumber / numberOfCheckpoints
game.ReplicatedStorage.CheckpointCollected:Fire(hitPlayer.Name, RaceManager.GetRankingOfPlayer(hitPlayer.Name, raceName), raceName, #raceFolder.AI:GetChildren() + #raceFolder.PlayersRacing:GetChildren())
game.ReplicatedStorage.CollectCheckpointV2Race:FireClient(hitPlayer, RaceManager.GetRankingOfPlayer(hitPlayer.Name, raceName), raceName, checkpointNumber)
hitPlayer:SetAttribute("RaceProgress", checkpointNumber / numberOfCheckpoints)
end
elseif hitPlayer:FindFirstChild(raceName.."Checkpoints"):FindFirstChild(checkpointNumber - 1).Value == true then
if checkpointNumber == numberOfCheckpoints or hitPlayer:FindFirstChild(raceName.."Checkpoints"):FindFirstChild(checkpointNumber + 1).Value ~= true then
hitPlayer:FindFirstChild(raceName.."Checkpoints"):FindFirstChild(checkpointNumber).Value = true
game.ReplicatedStorage.CheckpointCollected:Fire(hitPlayer.Name, RaceManager.GetRankingOfPlayer(hitPlayer.Name, raceName), raceName, #raceFolder.AI:GetChildren() + #raceFolder.PlayersRacing:GetChildren())
game.ReplicatedStorage.CollectCheckpointV2Race:FireClient(hitPlayer, RaceManager.GetRankingOfPlayer(hitPlayer.Name, raceName), raceName, checkpointNumber)
if checkpointNumber == numberOfCheckpoints then
RaceManager.FinishRace(hitPlayer.Name, raceName, false)
end
hitPlayer:SetAttribute("RaceProgress", checkpointNumber / numberOfCheckpoints)
end
end
end
elseif hitObject.Parent:GetAttribute("IsAI") == true then
hitObject.Parent:SetAttribute("RaceProgress", checkpointNumber / numberOfCheckpoints)
if checkpointNumber == numberOfCheckpoints then
RaceManager.FinishRace("lol get rekt it's AI", raceName, true)
hitObject.Parent:Destroy()
end
end
end)
end
end
end
RaceManager.GetCheckpoints = function(raceName)
--Gets the number of checkpoints in the race:
local raceFolder = game.Workspace:WaitForChild(raceName.."Race")
local numberOfCheckpoints = #raceFolder.Checkpoints:GetChildren() - 2
return numberOfCheckpoints
end
RaceManager.StartRace = function(raceName)
--Get the neccessary objects (race folder, table of players who are in the race):
local raceFolder = game.Workspace:WaitForChild(raceName.."Race")
local playersInRace = {}
local numberOfCheckpoints = RaceManager.GetCheckpoints(raceName)
local AIFolder = game.ServerStorage.AIDrones:GetChildren()
local numberOfAI = 3
playersCompleted = 0
--Check if there are enough players in the race to start. If not, stop the function:
if raceFolder.PlayersInRace.Value < RaceManager.playersRequiredForRace then
return false
end
for index, playerRacingValue in pairs(raceFolder.PlayersRacing:GetChildren()) do
playerRacingValue:Destroy()
end
for index, AI in pairs(raceFolder.AI:GetChildren()) do
AI:Destroy()
end
for index = 1, numberOfAI, 1 do
local AIDrone = AIFolder[math.random(1, #AIFolder)]:Clone()
AIDrone.Parent = raceFolder.AI
AIDrone.PrimaryPart.Anchored = true
AIDrone:SetPrimaryPartCFrame(raceFolder.StartBlock.CFrame)
AIDrone.Target.Value = raceFolder.Checkpoints.Checkpoint1.Detector.Detector.Position
AIDrone:SetAttribute("RaceProgress", 0)
for partNumber, part in pairs(AIDrone.Struts:GetChildren()) do
part.Color = Color3.fromRGB(175, 0, 255)
end
end
--Loop through the players to find the ones that have joined the race:
for index, player in pairs(game.Players:GetPlayers()) do
if player:GetAttribute("RaceName") then
if player:GetAttribute("RaceName") == raceName then
table.insert(playersInRace, player)
local playerRacingValue = Instance.new("BoolValue")
playerRacingValue.Name = player.Name.."Completed"
playerRacingValue.Parent = raceFolder.PlayersRacing
playerRacingValue.Value = false
end
end
end
--Set the isRacing value in the race folder to true, preventing more players from joining:
raceFolder.IsRacing.Value = true
--For each of the players, start the race:
for index, player in pairs(playersInRace) do
local character = game.Workspace:WaitForChild(player.Name)
character.HumanoidRootPart.Anchored = true
local countdown = coroutine.wrap(function()
--Create the checkpoint folder:
local checkpointFolder = Instance.new("Folder")
checkpointFolder.Parent = player
checkpointFolder.Name = raceName.."Checkpoints"
--Create the checkpoint values:
for index = 1, numberOfCheckpoints, 1 do
local checkpointValue = Instance.new("BoolValue")
checkpointValue.Name = index
checkpointValue.Parent = checkpointFolder
end
--Do the countdown:
local raceGui = player.PlayerGui.RaceGuiV2
local info = TweenInfo.new(0.2)
local inTween = TweenService:Create(raceGui.Countdown, info, {Size = UDim2.new(0.15, 0, 0.3, 0)})
local outTween = TweenService:Create(raceGui.Countdown, info, {Size = UDim2.new(0, 0, 0, 0)})
raceGui.Countdown.Visible = true
raceGui.Countdown.TextContent.Text = "3"
inTween:Play()
wait(0.8)
outTween:Play()
wait(0.2)
raceGui.Countdown.TextContent.Text = "2"
inTween:Play()
wait(0.8)
outTween:Play()
wait(0.2)
raceGui.Countdown.TextContent.Text = "1"
inTween:Play()
wait(0.8)
outTween:Play()
wait(0.2)
character.HumanoidRootPart.Anchored = false
player:SetAttribute("StartTime", os.clock())
raceGui.Countdown.TextContent.Text = "Go!"
inTween:Play()
wait(0.8)
outTween:Play()
raceGui.Countdown.Visible = false
end)
countdown()
end
wait(3)
for index, AIDrone in pairs(raceFolder.AI:GetChildren()) do
AIDrone.PrimaryPart.Anchored = false
end
RaceManager.CheckpointCollection(raceName)
end
return RaceManager