Help with revamping my RoundModule to be more efficient and expandable

I’ve been working on a Tag Game, got my code checked by another scripter. They said I overcomplicated things, I would like some help on revamping my code. (First time revamping code)

Any help would be appreciated!

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Remotes = ReplicatedStorage:WaitForChild("Remotes")

local StatusEvent = Remotes:WaitForChild("StatusEvent")
local ServerEvent = Remotes:WaitForChild("ServerMessages")
local CameraEvent = Remotes:WaitForChild("CameraEvent")

local Round_Duration = 80 --80
local Intermission_Duration = 20 --20
local Tag_Wait = 5 --5
local Preview_Over = false

local Min_Players = 2

local Main_Taggers = {}
local modifierMod = require(script.Parent:WaitForChild("ModifierMod"))

local random = Random.new()

local round = {}

local randoms = {
	" hated life",
	" raged quit",
	" said the bad word, PECK!",
	" was taken by The Snatcher",
	" disconnected",
	" got fried",
	" is so Ohio Skibidi fan-boy from 'Skibidi Toilet'",
	"'s mom said they were adopted",
	" is a try hard",
	" cried",
	" is feeling rainbow",
	" likes farts",
	" doesn't touch grass",
	" forgot they were playing",
	" was afk",
	" didn't know how to run",
	" forgot how to do basic arithmetic",
	" was challenged with and lost",
	" was embarrassed in front of their crush",
	" cried because they failed runner test at Falcon High",
	" didn't get accepted to Talon University",
	" failed the math test at Falcon High",
	"'s crush rejected them",
	" got called a walker",
	" had a heart attack",
	" didn't know what was going on",
	" had a stroke",
	" spoke terrible chinese",
	" disliked all Roblox's tweets",
	" became CaseOh",
	" is from Ohio",
	" is a furry",
	" said 'UwU'",
	" feels as if they are being stared into their soul",
	" does not like having a seizure, he/she stopped leaving their house and felt disappointed because they still received seizure. The next day, he called upon god asking, 'Oh God, how shall I stop having seizures?', he never responded.",
	" is from the Netherlands",
	"'s legs are broken from the waist up",
	".Humanoid.Health = 0",
	".Humanoid:TakeDamage(999)",
	":Destroy()",
	":PivotTo(game.Workspace.Baseplate:GetPivot())",
	":Kick(''bruh'')",
	" is in slow motion",
	" is a cannibal",
	" makes 1 cent per hour";
}

function giveTool(player)
	
	local toolInst = player.Tool
	
	if toolInst then
		
		local tool = ReplicatedStorage:WaitForChild("Tools"):FindFirstChild(toolInst.Value)
		
		if tool then
			
			local clone = tool:Clone()
			clone.Parent = player.Backpack
			
		end
		
	end
	
end

function freezePlayer(player)
	
	local r = 0

	for i, p2 in pairs(Players:GetPlayers()) do

		if p2:GetAttribute("IsRunner") == true then

			r += 1

		end

	end

	if r == 1 then

		workspace:SetAttribute("GameRunning", false)

		round.EndGame()

	end
	
	if player:GetAttribute("IsFrozen") == false and player:GetAttribute("IsTagger") == false then
		
		player:SetAttribute("IsFrozen", true)

		player.Character.HumanoidRootPart.Anchored = true

		task.wait(12)

		if player:GetAttribute("IsFrozen") == true and player:GetAttribute("IsTagger") == false then
			ServerEvent:FireAllClients(player.Name .. randoms[math.random(1, #randoms)])
			round.SetTagger(player)
		end

		task.wait(0.1)

		local r = 0

		for i, p2 in pairs(Players:GetPlayers()) do

			if p2:GetAttribute("IsRunner") == true then

				r += 1

			end

		end

		if r == 0 then

			workspace:SetAttribute("GameRunning", false)

			round.EndGame()

		end
		
	end
	
end

function round.SetTagger(player)
	
	player:SetAttribute("IsRunner", false)
	player:SetAttribute("IsFrozen", false)
	player:SetAttribute("IsTagger", true)

	local oldModel = player.Character
	local cloneModel = script:WaitForChild("Tagger"):Clone()

	local pivot = oldModel.HumanoidRootPart.CFrame

	player.Character = cloneModel
	cloneModel.Parent = workspace
	
	oldModel:Destroy()

	local playerCharacter = player.Character
	playerCharacter:SetPrimaryPartCFrame(pivot)

	if workspace:GetAttribute("TaggerWait") == true then playerCharacter.HumanoidRootPart.Anchored = true end

	if workspace.Modifiers:FindFirstChild("NEXTBOTS!!!") then

		local nextbotUI = script.NextbotGUI:Clone()
		nextbotUI.Parent = playerCharacter

		playerCharacter.Humanoid.WalkSpeed += 12
		playerCharacter.Humanoid.JumpPower += 24

		for i, v in pairs(playerCharacter:GetChildren()) do

			if v:IsA("BasePart") then

				v.Transparency = 1

			end

		end

	end

	playerCharacter.HumanoidRootPart.Touched:Connect(function(hit)

		if hit.Name == "HumanoidRootPart" then

			local p = Players:GetPlayerFromCharacter(hit.Parent)

			if p then

				if workspace:GetAttribute("TaggerWait") == false then
					
					if workspace:GetAttribute("GameMode") ~= "Death Tag Mode" then
						
						if p:GetAttribute("IsRunner") ~= false or p:GetAttribute("IsTagger") ~= true or p:GetAttribute("IsFrozen") == false then

							if p:GetAttribute("IsRunner") == true and p:GetAttribute("IsFrozen") ~= true then

								player.leaderstats.Coins.Value += 5
								game.Workspace.Taggedohno:Play()

							end

							if workspace:GetAttribute("GameMode") == "Freeze Tag Mode" then

								if p:GetAttribute("IsFrozen") ~= true and p:GetAttribute("IsTagger") ~= true then

									freezePlayer(p)

								end

							else

								if p:GetAttribute("IsRunner") ~= false and p:GetAttribute("IsTagger") ~= true then

									ServerEvent:FireAllClients(p.Name .. randoms[math.random(1, #randoms)])
									round.SetTagger(p)

								end

							end

							local runners = 0

							for i, p2 in pairs(Players:GetPlayers()) do

								if p2:GetAttribute("IsRunner") == true then

									runners += 1

								end

							end

							if runners == 0 then

								workspace:SetAttribute("GameRunning", false)

								round.EndGame()

							end

						end
						
					else
						
						p.Character:SetPrimaryPartCFrame(workspace.NewLobby.Spawn.CFrame)
						p:SetAttribute("IsRunner", false)
						p:SetAttribute("IsTagger", false)
						p:SetAttribute("IsFrozen", false)
						
						local runners = 0

						for i, p2 in pairs(Players:GetPlayers()) do

							if p2:GetAttribute("IsRunner") == true then

								runners += 1

							end

						end

						if runners == 0 then

							workspace:SetAttribute("GameRunning", false)

							round.EndGame()

						end
						
					end

				end

			end

		end

	end)
	
end

function round.StartGame()

	if workspace:GetAttribute("GameRunning") == true then return end

	workspace:SetAttribute("GameRunning", true)
	workspace:SetAttribute("TaggerWait", true)

	local c = math.random(1, 3)
	if c == 1 then

		workspace:SetAttribute("GameMode", "Normal Mode")

	elseif c == 2 then

		workspace:SetAttribute("GameMode", "Freeze Tag Mode")
		
	elseif c == 3 then
		
		workspace:SetAttribute("GameMode", "Death Tag Mode")

	end

	local allMaps = workspace.Maps:GetChildren()
	local map

	repeat

		map = allMaps[random:NextInteger(1, #allMaps)]
		wait()

	until map.Name ~= "PlaceholderMusic"

	if map:FindFirstChild("Music") then

		map.Music.TimePosition = 0
		map.Music:Play()

	else

		workspace.Maps.PlaceholderMusic.TimePosition = 0
		workspace.Maps.PlaceholderMusic:Play()

	end
	if map:FindFirstChild("ForceFirst") then
		
		CameraEvent:FireAllClients("Activate")
		
	else
		
		CameraEvent:FireAllClients("Unactivate")
		
	end

	Round_Duration = map.TimeLength.Value

	workspace:SetAttribute("SelectedMap", map.Name)
	game.Lighting.ClockTime = map.Time.Value

	if math.random(1, 8) == 1 then

		modifierMod.GetRandomization()

	end

	local allPlayers = Players:GetPlayers()
	local halfPlayers = math.round(#allPlayers / 2)

	for i, p in pairs(Players:GetPlayers()) do

		if p.AFKBool.Value == true then

			halfPlayers -= 1

		end

	end

	local prevPlayer = nil

	for i=1, halfPlayers do

		local randomPlayer

		repeat

			randomPlayer = allPlayers[random:NextInteger(1, #allPlayers)]

			wait()

		until randomPlayer ~= prevPlayer

		table.insert(Main_Taggers, randomPlayer.Name)

		round.SetTagger(randomPlayer)

		prevPlayer = randomPlayer

		if workspace:GetAttribute("GameMode") == "Death Tag Mode" then

			break

		end

	end
	
	task.wait(0.5)

	Preview_Over = false
	Remotes:WaitForChild("TogglePreview"):FireAllClients(map.Name)

	Remotes:WaitForChild("TogglePreview").OnServerEvent:Connect(function(player)

		Preview_Over = true

	end)

	for i, player in pairs(allPlayers) do
	
		if workspace.Modifiers:FindFirstChild("X-Ray Vision") then

			local c = script.Highlight:Clone()
			c.Parent = player.Character

		end
		
		giveTool(player)

		player:SetAttribute("IsPlaying", true)
		player.Character:PivotTo(CFrame.new(map.Spawn.Position + Vector3.new(0, 3, 0)))

		if player:GetAttribute("IsTagger") ~= true then

			player.Character.Torso.Color = Color3.fromRGB(0, 0, 255)
			player:SetAttribute("IsRunner", true)
			
			player.Character.HumanoidRootPart.Touched:Connect(function(hit)
				
				if hit.Name ~= "HumanoidRootPart" then return end
				if player:GetAttribute("IsRunner") ~= true then return end
				
				local p = Players:GetPlayerFromCharacter(hit.Parent)
				
				if p then
					
					if p:GetAttribute("IsFrozen") == true and p:GetAttribute("IsRunner") == true then
						
						p:SetAttribute("IsFrozen", false)
						hit.Parent.HumanoidRootPart.Anchored = false
						
					end
					
				end
				
			end)

		else

			player.Character.Torso.Color = Color3.fromRGB(255, 0, 0)

		end

	end

	repeat

		wait()

	until Preview_Over == true
	
	if workspace:GetAttribute("GameMode") == "Death Tag Mode" then
		
		Tag_Wait = 10
		
	end

	for i=Tag_Wait, 1, -1 do
		
		if workspace:GetAttribute("GameRunning") == false then break end
		
		StatusEvent:FireAllClients("Tagger Wait: " .. i)

		task.wait(1)

	end

	for i, p in pairs(Players:GetPlayers()) do
		
		if workspace:GetAttribute("GameRunning") == false then break end

		if p:GetAttribute("IsTagger") == true then

			p.Character.HumanoidRootPart.Anchored = false

		end

	end

	workspace:SetAttribute("TaggerWait", false)

	for i=Round_Duration, 1, -1 do

		StatusEvent:FireAllClients("Game Time: " .. i)

		task.wait(1)

		if workspace:GetAttribute("GameRunning") == false then

			break

		end

	end

	if map:FindFirstChild("Music") then

		map.Music.TimePosition = 0
		map.Music:Stop()

	else

		workspace.Maps.PlaceholderMusic.TimePosition = 0
		workspace.Maps.PlaceholderMusic:Stop()

	end
	
	if workspace:GetAttribute("GameRunning") == true then round.EndGame() end

end

function round.EndGame()

	StatusEvent:FireAllClients("Game Over!")
	CameraEvent:FireAllClients("Unactivate")
	
	local runners = 0

	for i, player in pairs(Players:GetPlayers()) do

		if player:GetAttribute("IsRunner") == true then

			runners += 1

		end

	end

	if runners == 1 then

		for i, player in pairs(Players:GetPlayers()) do

			if player:GetAttribute("IsRunner") == true then

				player.Accessories["Uno Reverse"].Value = true

			end

		end
		
	else
		
		for i, p in pairs(Main_Taggers) do

			if Players:FindFirstChild(p) then

				if game.PrivateServerId ~= "" then

					Players:FindFirstChild(p).leaderstats.Wins.Value += 3

				else

					Players:FindFirstChild(p).leaderstats.Wins.Value += 1

				end

				local coinsAMT = 15

				if workspace.Modifiers:FindFirstChild("2x Coins") then

					coinsAMT *= 2

				end
				if workspace.Modifiers:FindFirstChild("3x Coins") then

					coinsAMT *= 3

				end
				if workspace:GetAttribute("GameMode") == "Death Tag Mode" then

					coinsAMT *= 2

				end

				Players:FindFirstChild(p).leaderstats.Coins.Value += coinsAMT

			end

		end

	end
	
	Preview_Over = false
	Main_Taggers = {}

	for i, player in pairs(Players:GetPlayers()) do
		
		player.Character:SetPrimaryPartCFrame(workspace.NewLobby.Spawn.CFrame)
		player.Character.HumanoidRootPart.Anchored = false
		
		if workspace:GetAttribute("GameRunning") == true then

			if player:GetAttribute("IsRunner") == true then

				if game.PrivateServerId ~= "" then

					player.leaderstats.Wins.Value += 3

				else

					player.leaderstats.Wins.Value += 1

				end

				local coinsAMT = 30

				if workspace.Modifiers:FindFirstChild("2x Coins") then

					coinsAMT *= 2

				end
				if workspace.Modifiers:FindFirstChild("3x Coins") then

					coinsAMT *= 3

				end
				if workspace:GetAttribute("GameMode") == "Death Tag Mode" then

					coinsAMT *= 2

				end

				player.leaderstats.Coins.Value += coinsAMT

			end

		end

		if player:GetAttribute("IsRunner") == true or player:GetAttribute("IsTagger") == true then

			player.leaderstats.Rounds.Value += 1

		end

		player:SetAttribute("IsTagger", false)
		player:SetAttribute("IsRunner", false)
		player:SetAttribute("IsFrozen", false)
		player:SetAttribute("IsPlaying", false)

		if player.AFKBool.Value == false then

			player:LoadCharacter()

		end

	end

	workspace:SetAttribute("GameRunning", false)
	workspace:SetAttribute("GameMode", "None")
	workspace:SetAttribute("TaggerWait", false)
	workspace:SetAttribute("SelectedMap", "None")
	workspace.GameInfo.Juggernaut.Value = nil
	game.Lighting.ClockTime = 14.5

	workspace.Modifiers:ClearAllChildren()

	game.StarterPlayer.CameraMode = Enum.CameraMode.Classic
	game.Lighting.Invert.Enabled = false
	workspace.Gravity = 196.2
	game.Lighting.FogEnd = 100000

	round.Intermission()

end

function round.Intermission()

	while #Players:GetPlayers() < Min_Players do

		StatusEvent:FireAllClients("Not Enough Players! (2 Required)")

		task.wait()

	end

	for i=Intermission_Duration, 1, -1 do

		StatusEvent:FireAllClients("Intermission Time: " .. i)

		task.wait(1)
		
		StatusEvent:FireAllClients("Intermission Time: " .. i)
		
		if workspace:GetAttribute("GameRunning") == true then break end

	end
	
	if workspace:GetAttribute("GameRunning") ~= true then
		
		if #Players:GetPlayers() >= Min_Players then

			round.StartGame()

		else

			round.Intermission()	

		end
		
	end


end

Players.PlayerRemoving:Connect(function(player)

	local runners = 0
	for i, v in pairs(Players:GetPlayers()) do

		if v:GetAttribute("IsRunner") == true then

			runners += 1

		end

	end

	if runners == 0 then

		workspace:SetAttribute("GameRunning", false)
		round.EndGame()

	end

end)

return round

Definitely the worst thing about this script are double spaces between the lines.
This is not how a good code is formatted.
This will help: Roblox Lua Style guide

I would split these long functions into smaller ones so that it’s easy to see what’s going on just by taking a quick look.
For example, round.EndGame() is long, contains multiple loops, and handles multiple things by itself. Not very clear to read for me.

The code contains some unnecessary instructions which can be quickly simplified, for example:

for i, p in pairs(Main_Taggers) do
    if Players:FindFirstChild(p) then
        if game.PrivateServerId ~= "" then
            Players:FindFirstChild(p).leaderstats.Wins.Value += 3
        else
            Players:FindFirstChild(p).leaderstats.Wins.Value += 1
        end

        -- ...
    end
end

why not

for i, p in pairs(Main_Taggers) do
    local player = Players:FindFirstChild(p)

    if not player then
        continue
    end
    
    local winsIncrement = (game.PrivateServerId == "" and 1 or 3)
    player.leaderstats.Wins.Value += winsIncrement

    -- ...
end

An example of how you can fix a pyramid of instructions and remove repeated code.
Btw. Players:FindFirstChild(p).leaderstats.Wins.Value += 1 doesn’t make any sense.
I mean, the Players:FindFirstChild(p) part.
FindFirstChild() returns nil if it doesn’t find the instance, so if it doesn’t find the given player name in Players, it will be nil.leaderstats.Wins.Value anyway (resulting in an error).
You either do simply Players[p].leaderstats.Wins.Value or an if statement checking if the FindFirstChild() found the player.

Your variable names could also be improved, for example variable r or variable p may not be very clear to another programmer reading your code.
Short name is not always the best name.

I hope it helps, and wish you best of luck with further development!
Let me know if you have any questions. :slight_smile:

Just asking, does any other function seem like it could be simplified?

Yeah, for example in SetTagger() you have quite a lot of code and also Touched event handler.
You could move the whole code from inside the function connected to the Touched signal to a new function and name it something along the lines of handleCollision.

In object-oriented programming there’s a very popular set of general principles called SOLID.
The first letter of this abbreviation stands for Single-responsibility principle, and it says that one function should be only responsible for one specific task.
While implementing OOP into Roblox scripts may not be the best idea for organizing your code, taking some inspiration from this principle would still be beneficial.

For instance, let’s say you have a function preparePlayer(), which is supposed to prepare the player for new match.
It turns out the function is long because preparing the player means basically giving them items, assigning to a team, updating timers etc.
You can split this one big function into giveItems(), assignToTeam(), updateTimers() and so on.
This is way better than having one big chunk of code.

Right now, I’m helping a friend with some code for their game, do you mind attempting to revamp a bit of my module? If not, it’s fine.