Hi there,
In my current project, I’m working on a match system, and most of the code for this system is located inside ReplicatedStorage. Within the modules, I’m using RunService:IsServer() to separate client and server logic.
However, I’m not entirely sure if this setup is secure or if it could potentially be exploited in a way that would affect other players’ gameplay. I’d really appreciate any guidance on whether this approach is safe or if I should consider restructuring things differently.
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local SoundService = game:GetService("SoundService")
local TweenService = game:GetService("TweenService")
local isServer = RunService:IsServer()
local SyncVoteTally = ReplicatedStorage.Remotes.Game.SyncVoteTally
local CastThemeVote = ReplicatedStorage.Remotes.Game.CastThemeVote
local IntermissionTimerSync = ReplicatedStorage.Remotes.Game.IntermissionTimerSync
local ThemeSelected = ReplicatedStorage.Remotes.Game.ThemeSelected
local StateManager = require(script.StateManager)
local VotingSystem = require(script.VotingSystem)
local TimerManager = require(script.TimerManager)
local UIManager = require(script.UIManager)
local Constants = require(script.Constants)
local GameSystem = {
Server = {},
Client = {},
Constants = Constants
}
function GameSystem.Client:FadeSound(soundName, targetVolume, duration, callback)
if isServer then return end
local sound = SoundService:FindFirstChild(soundName)
if not sound then
warn("Sound not found:", soundName)
if callback then callback() end
return
end
local audioPrefs = ReplicatedStorage:FindFirstChild("Modules") and
ReplicatedStorage.Modules:FindFirstChild("Preferences") and
ReplicatedStorage.Modules.Preferences:FindFirstChild("Audio")
local isEnabled = true
if audioPrefs then
local audioModule = require(audioPrefs)
if soundName == "LobbyMusic" or soundName == "TeamSelector" then
isEnabled = audioModule:GetValue("LobbyMusic")
elseif soundName == "FightMusic" then
isEnabled = audioModule:GetValue("FightMusic")
end
end
if targetVolume <= 0 then
if not sound.Playing then
if callback then callback() end
return
end
local tweenInfo = TweenInfo.new(
duration,
Enum.EasingStyle.Linear,
Enum.EasingDirection.Out
)
local volumeGoal = {Volume = 0}
local tween = TweenService:Create(sound, tweenInfo, volumeGoal)
tween:Play()
tween.Completed:Connect(function()
sound.Playing = false
if callback then callback() end
end)
return tween
else
if not sound.Playing then
sound.Volume = 0
sound.Playing = true
end
if not isEnabled then
if callback then callback() end
return
end
local tweenInfo = TweenInfo.new(
duration,
Enum.EasingStyle.Linear,
Enum.EasingDirection.Out
)
local volumeGoal = {Volume = targetVolume}
local tween = TweenService:Create(sound, tweenInfo, volumeGoal)
tween:Play()
tween.Completed:Connect(function()
if callback then callback() end
end)
return tween
end
end
function GameSystem.Client:UpdateSounds(newState, previousState)
if isServer then return end
if newState == "Waiting" or newState == "Intermission" then
self:FadeSound("TeamSelector", 0, 1, function()
self:FadeSound("FightMusic", 0, 1)
end)
self:FadeSound("LobbyMusic", 1.5, 2)
elseif newState == "InGame" then
if previousState == "Intermission" then
self:FadeSound("LobbyMusic", 0, 2, function()
if StateManager.Client:GetCurrentState() == "InGame" then
self:FadeSound("TeamSelector", 1.5, 2)
end
end)
if not self.teamSelectionCompletionListener then
self.teamSelectionCompletionListener = ReplicatedStorage.Remotes.Game.TeamSelectionComplete.OnClientEvent:Connect(function()
self:FadeSound("TeamSelector", 0, 2, function()
if StateManager.Client:GetCurrentState() == "InGame" then
self:FadeSound("FightMusic", 1.5, 2)
end
end)
end)
end
end
elseif newState == "Ending" then
self:FadeSound("LobbyMusic", 0, 2)
self:FadeSound("TeamSelector", 0, 2)
self:FadeSound("FightMusic", 0, 2)
end
end
function GameSystem.Server:Initialize()
if not isServer then return end
StateManager.Server:Initialize()
VotingSystem.Server:Initialize()
if not ReplicatedStorage.Remotes.Game:FindFirstChild("TeamSelectionComplete") then
local teamSelectionComplete = Instance.new("RemoteEvent")
teamSelectionComplete.Name = "TeamSelectionComplete"
teamSelectionComplete.Parent = ReplicatedStorage.Remotes.Game
end
Players.PlayerAdded:Connect(function(player)
self:SyncNewPlayer(player)
local shouldStartIntermission = StateManager.Server:CheckPlayers()
if shouldStartIntermission then
self:StartIntermission()
end
end)
Players.PlayerRemoving:Connect(function(player)
VotingSystem.Server:RemovePlayerVote(player)
task.defer(function()
local stateChanged = StateManager.Server:CheckPlayers()
if stateChanged == false then
TimerManager.Server:StopAllTimers()
end
end)
end)
StateManager.Server:ChangeState("Waiting")
StateManager.Server:CheckPlayers()
self:StartPeriodicSync()
end
function GameSystem.Server:StartTeamSelection()
if not isServer then return end
self.teams = {
Red = {},
Blue = {}
}
self.usedSpawns = {
Red = {},
Blue = {}
}
self.playerSelectedTeam = {}
if not self.teamSelectionHandler then
self.teamSelectionHandler = ReplicatedStorage.Remotes.Game.TeamSelection.OnServerEvent:Connect(function(player, team)
if self.playerSelectedTeam[player.UserId] then
return
end
if team == "Red" or team == "Blue" then
for teamName, players in pairs(self.teams) do
for i, plr in ipairs(players) do
if plr == player then
table.remove(self.teams[teamName], i)
break
end
end
end
table.insert(self.teams[team], player)
player:SetAttribute("Team", team)
self.playerSelectedTeam[player.UserId] = team
ReplicatedStorage.Remotes.Game.TeamSelection:FireClient(player, team, true)
end
end)
end
TimerManager.Server:StartTeamSelectionTimer(function()
self:FinalizeTeamSelection()
end)
end
function GameSystem.Server:TeleportPlayersToSpawns()
if not isServer then return end
local arenaSpawns = Workspace:FindFirstChild("Arena") and Workspace.Arena:FindFirstChild("Spawns")
if not arenaSpawns then
warn("Arena spawns not found in Workspace.Arena.Spawns")
return
end
for team, players in pairs(self.teams) do
local teamSpawns = arenaSpawns:FindFirstChild(team)
if not teamSpawns then
warn("Team spawns not found for team: " .. team)
continue
end
local availableSpawns = {}
for _, spawn in pairs(teamSpawns:GetChildren()) do
if spawn:IsA("BasePart") and not self.usedSpawns[team][spawn.Name] then
table.insert(availableSpawns, spawn)
end
end
for _, player in ipairs(players) do
if #availableSpawns == 0 then
warn("No available spawns for team: " .. team)
break
end
local randomIndex = math.random(1, #availableSpawns)
local selectedSpawn = availableSpawns[randomIndex]
local character = player.Character or player.CharacterAdded:Wait()
if character and character:FindFirstChild("HumanoidRootPart") then
character:SetPrimaryPartCFrame(CFrame.new(selectedSpawn.Position + Vector3.new(0, 3, 0)))
self.usedSpawns[team][selectedSpawn.Name] = true
table.remove(availableSpawns, randomIndex)
self:AddTeamHighlight(character, team)
end
end
end
UIManager.Server:UpdateScoreboardData(self.teams)
end
function GameSystem.Server:AddTeamHighlight(character, team)
if not isServer then return end
local highlight = Instance.new("Highlight")
highlight.Name = "TeamHighlight"
highlight.FillTransparency = 1
if team == "Red" then
highlight.OutlineColor = Color3.fromRGB(255, 0, 0)
elseif team == "Blue" then
highlight.OutlineColor = Color3.fromRGB(67, 67, 255)
end
highlight.Parent = character
end
function GameSystem.Server:FinalizeTeamSelection()
if not isServer then return end
if self.teamSelectionHandler then
self.teamSelectionHandler:Disconnect()
self.teamSelectionHandler = nil
end
self:TeleportPlayersToSpawns()
ReplicatedStorage.Remotes.Game.TeamSelectionComplete:FireAllClients()
self:StartGameplay()
end
function GameSystem.Client:StartTeamTransition()
if isServer then return end
local player = Players.LocalPlayer
if not player then return end
local playerGui = player:WaitForChild("PlayerGui")
local gameGui = playerGui:WaitForChild("Game")
local inGameFrame = gameGui:WaitForChild("InGame")
local transitionFrame = inGameFrame:WaitForChild("Transition")
local scoreboardFrame = inGameFrame:WaitForChild("Scoreboard")
scoreboardFrame.Visible = false
transitionFrame.Visible = true
transitionFrame.Position = UDim2.new(-1.998, 0, 0.196, 0)
self:FadeSound("TeamSelector", 0, 2)
self:FadeSound("FightMusic", 1.5, 2)
self.teamSelectionComplete = true
local tweenInfo = TweenInfo.new(
1.5,
Enum.EasingStyle.Cubic,
Enum.EasingDirection.Out
)
local positionGoal = {Position = UDim2.new(4, 0, 0.605, 0)}
local tween = TweenService:Create(transitionFrame, tweenInfo, positionGoal)
tween:Play()
tween.Completed:Connect(function()
self:StartTeamCameraSequence()
end)
end
function GameSystem.Client:StartTeamCameraSequence()
if isServer then return end
local player = Players.LocalPlayer
if not player then return end
local character = player.Character
if not character then return end
local humanoid = character:FindFirstChild("Humanoid")
if not humanoid then return end
local originalCameraType = workspace.CurrentCamera.CameraType
workspace.CurrentCamera.CameraType = Enum.CameraType.Scriptable
local arena = workspace:FindFirstChild("Arena")
if not arena then
warn("Arena not found, cannot perform camera sequence")
workspace.CurrentCamera.CameraType = originalCameraType
self:FinishTeamTransition()
return
end
local spawns = arena:FindFirstChild("Spawns")
if not spawns then
warn("Spawns not found in Arena, cannot perform camera sequence")
workspace.CurrentCamera.CameraType = originalCameraType
self:FinishTeamTransition()
return
end
local redSpawns = spawns:FindFirstChild("Red")
if not redSpawns then
warn("Red team spawns not found")
workspace.CurrentCamera.CameraType = originalCameraType
self:FinishTeamTransition()
return
end
local redStartCamera = redSpawns:FindFirstChild("StartCamera")
local redEndCamera = redSpawns:FindFirstChild("EndCamera")
if not redStartCamera or not redEndCamera then
warn("Red team camera points not found")
workspace.CurrentCamera.CameraType = originalCameraType
self:FinishTeamTransition()
return
end
local blueSpawns = spawns:FindFirstChild("Blue")
if not blueSpawns then
warn("Blue team spawns not found")
workspace.CurrentCamera.CameraType = originalCameraType
self:FinishTeamTransition()
return
end
local blueStartCamera = blueSpawns:FindFirstChild("StartCamera")
local blueEndCamera = blueSpawns:FindFirstChild("EndCamera")
if not blueStartCamera or not blueEndCamera then
warn("Blue team camera points not found")
workspace.CurrentCamera.CameraType = originalCameraType
self:FinishTeamTransition()
return
end
local redLookTarget = self:CalculateTeamLookDirection("Red")
local blueLookTarget = self:CalculateTeamLookDirection("Blue")
local camera = workspace.CurrentCamera
camera.CFrame = CFrame.new(redStartCamera.Position, redLookTarget)
self:AnimateCameraFromTo(redStartCamera.CFrame, redEndCamera.CFrame, redLookTarget, 3, function()
task.delay(1, function()
camera.CFrame = CFrame.new(blueStartCamera.Position, blueLookTarget)
self:AnimateCameraFromTo(blueStartCamera.CFrame, blueEndCamera.CFrame, blueLookTarget, 3, function()
task.delay(1, function()
workspace.CurrentCamera.CameraType = originalCameraType
self:FinishTeamTransition()
end)
end)
end)
end)
end
function GameSystem.Client:AnimateCameraToPoint(targetCFrame, lookTarget, duration, callback)
if isServer then
if callback then callback() end
return
end
local camera = workspace.CurrentCamera
local startCFrame = camera.CFrame
local startTime = tick()
local targetPosition = targetCFrame.Position
local targetLookCFrame = CFrame.new(targetPosition, lookTarget)
if self.cameraUpdateConnection then
self.cameraUpdateConnection:Disconnect()
self.cameraUpdateConnection = nil
end
self.cameraUpdateConnection = RunService.RenderStepped:Connect(function()
local elapsed = tick() - startTime
local alpha = math.min(elapsed / duration, 1)
alpha = -(math.cos(math.pi * alpha) - 1) / 2
camera.CFrame = startCFrame:Lerp(targetLookCFrame, alpha)
if alpha >= 1 then
if self.cameraUpdateConnection then
self.cameraUpdateConnection:Disconnect()
self.cameraUpdateConnection = nil
end
if callback then
callback()
end
end
end)
end
function GameSystem.Client:AnimateCameraFromTo(startCFrame, endCFrame, lookTarget, duration, callback)
if isServer then
if callback then callback() end
return
end
local camera = workspace.CurrentCamera
local startTime = tick()
if self.cameraUpdateConnection then
self.cameraUpdateConnection:Disconnect()
self.cameraUpdateConnection = nil
end
self.cameraUpdateConnection = RunService.RenderStepped:Connect(function()
local elapsed = tick() - startTime
local alpha = math.min(elapsed / duration, 1)
alpha = -(math.cos(math.pi * alpha) - 1) / 2
local currentPosition = startCFrame.Position:Lerp(endCFrame.Position, alpha)
camera.CFrame = CFrame.new(currentPosition, lookTarget)
if alpha >= 1 then
if self.cameraUpdateConnection then
self.cameraUpdateConnection:Disconnect()
self.cameraUpdateConnection = nil
end
if callback then
callback()
end
end
end)
end
function GameSystem.Client:FinishTeamTransition()
if isServer then return end
local player = Players.LocalPlayer
if not player then return end
local playerGui = player:WaitForChild("PlayerGui")
local gameGui = playerGui:WaitForChild("Game")
local inGameFrame = gameGui:WaitForChild("InGame")
local transitionFrame = inGameFrame:WaitForChild("Transition")
local scoreboardFrame = inGameFrame:WaitForChild("Scoreboard")
transitionFrame.Visible = false
scoreboardFrame.Visible = true
end
function GameSystem.Client:CalculateTeamLookDirection(teamName)
if isServer then return Vector3.new(0, 0, -1) end
local arena = workspace:FindFirstChild("Arena")
if not arena then return Vector3.new(0, 0, -1) end
local spawns = arena:FindFirstChild("Spawns")
if not spawns then return Vector3.new(0, 0, -1) end
local teamSpawns = spawns:FindFirstChild(teamName)
if not teamSpawns then return Vector3.new(0, 0, -1) end
local centerPosition = Vector3.new(0, 0, 0)
local count = 0
for _, player in pairs(Players:GetPlayers()) do
if player:GetAttribute("Team") == teamName then
local character = player.Character
if character and character:FindFirstChild("HumanoidRootPart") then
centerPosition = centerPosition + character.HumanoidRootPart.Position
count = count + 1
end
end
end
if count == 0 then
for _, spawn in pairs(teamSpawns:GetChildren()) do
if spawn:IsA("BasePart") and not (spawn.Name == "StartCamera" or spawn.Name == "EndCamera") then
centerPosition = centerPosition + spawn.Position
count = count + 1
end
end
end
if count > 0 then
centerPosition = centerPosition / count
centerPosition = centerPosition + Vector3.new(0, 1.5, 0)
return centerPosition
end
return teamSpawns.Position + Vector3.new(0, 1.5, 0)
end
function GameSystem.Server:StartGameplay()
if not isServer then return end
end
function GameSystem.Server:SyncNewPlayer(player)
if not isServer then return end
local currentState = StateManager.Server:GetCurrentState()
StateManager.Server:ChangeState(currentState, player)
local votesData = VotingSystem.Server:GetVotesData()
if votesData then
SyncVoteTally:FireClient(player, votesData.themeVotes)
if votesData.playerVotes[player.UserId] then
CastThemeVote:FireClient(player, votesData.playerVotes[player.UserId])
end
end
if currentState == "Intermission" then
local timeLeft = TimerManager.Server:GetIntermissionTimeLeft()
if timeLeft then
IntermissionTimerSync:FireClient(player, math.max(0, math.floor(timeLeft)))
end
end
local selectedTheme = StateManager.Server:GetSelectedTheme()
if selectedTheme then
ThemeSelected:FireClient(player, selectedTheme)
end
if self.teams then
for teamName, players in pairs(self.teams) do
for _, plr in ipairs(players) do
if plr == player then
player:SetAttribute("Team", teamName)
break
end
end
end
end
end
function GameSystem.Server:StartPeriodicSync()
if not isServer then return end
if self.syncLoop then
self.syncLoop:Disconnect()
end
self.syncTimer = 0
self.syncLoop = RunService.Heartbeat:Connect(function(dt)
self.syncTimer = self.syncTimer + dt
if self.syncTimer >= 5 then
self.syncTimer = 0
for _, player in pairs(Players:GetPlayers()) do
self:SyncNewPlayer(player)
end
end
end)
end
function GameSystem.Server:StartIntermission()
if not isServer then return end
VotingSystem.Server:ResetVotes()
TimerManager.Server:StartIntermissionTimer(function()
if #Players:GetPlayers() >= Constants.MIN_PLAYERS then
local winningTheme = VotingSystem.Server:GetWinningTheme()
StateManager.Server:SetSelectedTheme(winningTheme)
self:StartGame(winningTheme)
else
StateManager.Server:ChangeState("Waiting")
end
end)
end
function GameSystem.Server:StartGame(selectedTheme)
if not isServer then return end
StateManager.Server:ChangeState("InGame")
self:StartTeamSelection()
TimerManager.Server:StartGameTimer(function()
self:EndGame()
end)
end
function GameSystem.Server:EndGame()
if not isServer then return end
StateManager.Server:ChangeState("Ending")
TimerManager.Server:StartEndingTimer(function()
if #Players:GetPlayers() >= Constants.MIN_PLAYERS then
StateManager.Server:ChangeState("Intermission")
self:StartIntermission()
else
StateManager.Server:ChangeState("Waiting")
end
end)
end
function GameSystem.Client:Initialize()
if isServer then return end
StateManager.Client:Initialize()
VotingSystem.Client:Initialize()
TimerManager.Client:Initialize()
UIManager.Client:Initialize()
local audioPrefs = ReplicatedStorage:FindFirstChild("Modules") and
ReplicatedStorage.Modules:FindFirstChild("Preferences") and
ReplicatedStorage.Modules.Preferences:FindFirstChild("Audio")
if audioPrefs then
local audioModule = require(audioPrefs)
ReplicatedStorage.Remotes.Preferences.Update.Event:Connect(function(category, key, value)
if category == "Audio" then
local currentState = StateManager.Client:GetCurrentState()
if key == "LobbyMusic" then
local lobbyMusic = SoundService:FindFirstChild("LobbyMusic")
local teamSelector = SoundService:FindFirstChild("TeamSelector")
if currentState == "Waiting" or currentState == "Intermission" then
if lobbyMusic and lobbyMusic.Playing then
lobbyMusic.Volume = value and 1.5 or 0
end
elseif currentState == "InGame" then
if teamSelector and teamSelector.Playing then
teamSelector.Volume = value and 1.5 or 0
end
end
elseif key == "FightMusic" then
local fightMusic = SoundService:FindFirstChild("FightMusic")
if currentState == "InGame" and fightMusic and fightMusic.Playing then
fightMusic.Volume = value and 1.5 or 0
end
end
end
end)
end
StateManager.Client.onStateChanged.Event:Connect(function(newState, previousState)
UIManager.Client:UpdateUI(newState, previousState)
self:UpdateSounds(newState, previousState)
end)
local lastVotesData = {}
VotingSystem.Client.onVotesUpdated.Event:Connect(function(votesData)
lastVotesData = votesData
UIManager.Client:UpdateVotesDisplay(votesData, VotingSystem.Client:GetCurrentVote())
end)
VotingSystem.Client.onVoteConfirmed.Event:Connect(function(confirmedTheme)
UIManager.Client:UpdateVotesDisplay(lastVotesData, confirmedTheme)
end)
TimerManager.Client.onIntermissionTimeUpdated.Event:Connect(function(timeLeft)
UIManager.Client:UpdateIntermissionTime(timeLeft)
end)
UIManager.Client:SetupThemeButtons(function(theme)
VotingSystem.Client:VoteForTheme(theme)
end)
UIManager.Client:UpdateUI(StateManager.Client:GetCurrentState())
if not self.teamSelectionCompletionListener then
self.teamSelectionCompletionListener = ReplicatedStorage.Remotes.Game.TeamSelectionComplete.OnClientEvent:Connect(function()
self:StartTeamTransition()
end)
end
local currentState = StateManager.Client:GetCurrentState()
self:UpdateSounds(currentState, nil)
self.scoreboardUpdateLoop = RunService.RenderStepped:Connect(function()
local playerTeam = Players.LocalPlayer:GetAttribute("Team")
if playerTeam then
UIManager.Client:UpdateTeamScoreboard(playerTeam)
local oppositeTeam = playerTeam == "Red" and "Blue"
UIManager.Client:UpdateTeamScoreboard(oppositeTeam)
end
end)
self:SetupAudioPreferenceListeners()
end
function GameSystem.Client:SetupAudioPreferenceListeners()
if isServer then return end
local audioPrefs = ReplicatedStorage:FindFirstChild("Modules") and
ReplicatedStorage.Modules:FindFirstChild("Preferences") and
ReplicatedStorage.Modules.Preferences:FindFirstChild("Audio")
if not audioPrefs then return end
local audioModule = require(audioPrefs)
local lobbyEnabled = audioModule:GetValue("LobbyMusic")
local fightEnabled = audioModule:GetValue("FightMusic")
if not self.audioPreferenceConnection then
self.audioPreferenceConnection = ReplicatedStorage.Remotes.Preferences.Update.Event:Connect(function(category, key, value)
if category ~= "Audio" then return end
local currentState = StateManager.Client:GetCurrentState()
if key == "LobbyMusic" then
local lobbyMusic = SoundService:FindFirstChild("LobbyMusic")
local teamSelector = SoundService:FindFirstChild("TeamSelector")
if lobbyMusic and lobbyMusic.Playing then
if currentState == "Waiting" or currentState == "Intermission" then
lobbyMusic.Volume = value and 1.5 or 0
end
end
if teamSelector and teamSelector.Playing then
if currentState == "InGame" and not self.teamSelectionComplete then
teamSelector.Volume = value and 1.5 or 0
end
end
elseif key == "FightMusic" then
local fightMusic = SoundService:FindFirstChild("FightMusic")
if fightMusic and fightMusic.Playing then
if currentState == "InGame" and self.teamSelectionComplete then
fightMusic.Volume = value and 1.5 or 0
end
end
end
end)
end
end
function GameSystem.Server:Cleanup()
if not isServer then return end
TimerManager.Server:StopAllTimers()
if self.syncLoop then
self.syncLoop:Disconnect()
self.syncLoop = nil
end
if self.teamSelectionHandler then
self.teamSelectionHandler:Disconnect()
self.teamSelectionHandler = nil
end
self.teams = nil
self.usedSpawns = nil
end
function GameSystem.Client:Cleanup()
if isServer then return end
StateManager.Client:Cleanup()
VotingSystem.Client:Cleanup()
TimerManager.Client:Cleanup()
UIManager.Client:Cleanup()
if self.teamSelectionCompletionListener then
self.teamSelectionCompletionListener:Disconnect()
self.teamSelectionCompletionListener = nil
end
if self.audioPreferenceConnection then
self.audioPreferenceConnection:Disconnect()
self.audioPreferenceConnection = nil
end
if self.scoreboardUpdateLoop then
self.scoreboardUpdateLoop:Disconnect()
self.scoreboardUpdateLoop = nil
end
local lobbyMusic = SoundService:FindFirstChild("LobbyMusic")
local teamSelector = SoundService:FindFirstChild("TeamSelector")
local fightMusic = SoundService:FindFirstChild("FightMusic")
if lobbyMusic and lobbyMusic.Playing then
self:FadeSound("LobbyMusic", 0, 1)
end
if teamSelector and teamSelector.Playing then
self:FadeSound("TeamSelector", 0, 1)
end
if fightMusic and fightMusic.Playing then
self:FadeSound("FightMusic", 0, 1)
end
self.teamSelectionComplete = nil
end
return GameSystem