This script allows for one server to take control as the master server, it also allows for cross-server events or ‘tasks’
in the example all non-master servers will send a print task to the master server
ServerScriptService - Script
if game:GetService("RunService"):IsStudio() then warn("[Studio]") repeat wait(60) until false end
local MemStore = game:GetService("MemoryStoreService")
local HttpService = game:GetService("HttpService")
local DSS = game:GetService("DataStoreService")
local ServerIndex = MemStore:GetSortedMap("ServerIndex")
local LocalTaskQueue = MemStore:GetQueue(game.JobId)
local Debugging = true
local function Shutdown(Message)
game.Players.PlayerAdded:Connect(function(Player)
Player:Kick(Message or "Server shutting down.")
end)
for _, Player in pairs(game.Players:GetPlayers()) do
Player:Kick(Message or "Server shutting down.")
end
end
local function DebugPrint(Message)
if not Debugging then return end
print(Message)
end
local function ProcessTask(Task)
DebugPrint("Processing Task: "..Task.Name)
Task.Name = Task.Name:lower()
if Task.Name == "shutdown" then
if not Task.Data then Shutdown() return end
if type(Task.Data) ~= "string" then Shutdown() return end
Shutdown(Task.Data)
elseif Task.Name == "print" then
if not Task.Data then return end
if type(Task.Data) ~= "string" then return end
print(Task.Data)
end
end
local Tasks = {}
local IS_MASTER = false
local function AddFunctions(Server)
if Server.JobId == game.JobId then
Server["IsLocal"] = true
Server["Shutdown"] = function()
Shutdown()
end
Server["Send"] = function(Task)
Task.Sender = "Local"
table.insert(Tasks, Task)
end
else
Server["IsLocal"] = false
Server["Send"] = function(TaskObj, Settings)
if not TaskObj then return false end
TaskObj.Sender = game.JobId
if not Settings then Settings = {} end
if Settings["Tries"] == nil then Settings["Tries"] = 3 end
if Settings["Interval"] == nil then Settings["Interval"] = 3 end
if Settings["Expiration"] == nil then Settings["Expiration"] = 60 end
local s, e
repeat
s, e = pcall(function()
MemStore:GetQueue(Server.JobId):AddAsync(HttpService:JSONEncode(TaskObj), Settings.Expiration)
if not s then Settings.Tries -= 1 wait(Settings.Interval) end
end)
until
s or Settings.Tries == 0
return s
end
Server.Shutdown = function(Message)
local Task = {
["Name"] = "Shutdown",
["Data"] = Message
}
Server.Send(Task)
end
end
end
local Servers = {}
local RunningTasks = false
local function GetServers()
local lServers = {}
local startFrom = nil
while true do
local items = ServerIndex:GetRangeAsync(Enum.SortDirection.Ascending, 100, startFrom)
for _, item in ipairs(items) do
item.value = HttpService:JSONDecode(item.value)
AddFunctions(item.value)
lServers[item.key] = item.value
end
if #items < 100 then break end
startFrom = items[#items].key
end
local Longest = nil
for JobId, Server in pairs(lServers) do
if Longest == nil then
Longest = Server
continue
end
if Longest.Runtime < Server.Runtime then
Longest = Server
end
end
IS_MASTER = Longest.JobId == game.JobId
Servers = lServers
return Servers
end
local ConnectionTicks = {}
game.Players.PlayerAdded:Connect(function(Player)
ConnectionTicks[Player.UserId] = tick()
end)
game.Players.PlayerRemoving:Connect(function(Player)
ConnectionTicks[Player.UserId] = nil
end)
local function GetPlayers()
local Plyrs = {}
for _, Player in pairs(game:GetService("Players"):GetPlayers()) do
table.insert(Plyrs, {
["UserId"] = Player.UserId,
["Name"] = Player.Name,
["Ping"] = math.round(Player:GetNetworkPing()*1000),
["Connection"] = math.round(tick() - (ConnectionTicks[Player.UserId] or tick())),
["Age"] = Player.AccountAge,
})
end
return Plyrs
end
local FPST = 0
local AvgServerFPS = 0
local FPSI = 0
game:GetService("RunService").Heartbeat:Connect(function(step)
FPST += 1 / step
FPSI += 1
if FPSI == 10 then
AvgServerFPS = FPST/10
FPSI = 0
FPST = 0
end
end)
local function RegisterSelf()
local ServerObj = {
["JobId"] = game.JobId,
["Players"] = GetPlayers(),
["Runtime"] = math.round(time()),
["FPS"] = math.round(AvgServerFPS),
["Master"] = IS_MASTER,
}
ServerIndex:SetAsync(game.JobId, HttpService:JSONEncode(ServerObj), 60)
DebugPrint("Registered self as: "..HttpService:JSONEncode(ServerObj))
end
game:BindToClose(function()
local s, e
repeat
s, e = pcall(function()
ServerIndex:RemoveAsync(game.JobId)
end)
if not s then wait(3) end
until
s
end)
local LastTicks = {
["RegisterSelf"] = tick()-10,
["GetServers"] = tick()-10,
}
while wait(1) do
if tick() - LastTicks["RegisterSelf"] >= 10 then
LastTicks["RegisterSelf"] = tick()
RegisterSelf()
end
if tick() - LastTicks["GetServers"] >= 10 then
LastTicks["GetServers"] = tick()
local Master = nil
for JobId, Server in pairs(GetServers()) do
DebugPrint("JobId: "..Server.JobId..", Players: "..#Server.Players..", Master: "..(Server.Master and "Y" or "N"))
if not Master and Server.Master then Master = Server end
end
if Master then
if not Master.IsLocal then
local Task = {
["Name"] = "Print",
["Data"] = "Task from: "..game.JobId
}
Master.Send(Task)
DebugPrint("Sent task to master")
end
end
end
pcall(function()
local items, id = LocalTaskQueue:ReadAsync(1, false, 0)
if #items > 0 then
local Task = HttpService:JSONDecode(items[1])
if Task then
table.insert(Tasks, Task)
end
LocalTaskQueue:RemoveAsync(id)
end
end)
for _, Task in pairs(Tasks) do
ProcessTask(Task)
end
end
Usage Notes
-
Tasks are processed in the function ‘ProcessTask’, here is where you would add your own custom tasks.
-
The eldest server will be the master server.
-
Update frequency can be changed in the main loop near the end of the script
-
The server ‘obj’ contains .Players, .IsLocal, .Send(), .Shutdown(), .JobId, .FPS, .Master and .Runtime
-
The player ‘obj’ contains .Age, .UserId, .Name, .Ping and .Connection
-
Server.FPS is the server’s average frames per second.
-
Player.Connection is how long the player has been connected to the server (seconds)
Send(TaskObj, SettingsObj):
local TaskObj = {
["Name"] = "TaskName",
["Data"] = nil
}
local SettingsObj = {
["Tries"] = 0,
["Interval"] = 0,
["Expiration"] = 0,
}