This is a module which you can use to store and keep track of active servers
Code (Paste this into a ModuleScript):
local Players = game:GetService("Players")
local TeleportService = game:GetService("TeleportService")
local MemStoreService = game:GetService("MemoryStoreService")
local MessagingService = game:GetService("MessagingService")
local SERVER_REGISTERED_EXPIRATION = 432_000 -- 5 days
local ServerInfoStore = MemStoreService:GetSortedMap("_ReservedServerInformation")
local Server = {}
Server.__index = Server
function Server:MessageAsync(topic: string, ...)
local success = pcall(MessagingService.PublishAsync, MessagingService, self.PrivateServerId..topic, {...})
return success
end
function Server:BindToMessageAsync(topic: string, fn: (sentTime: number, ...) -> ()): RBXScriptConnection
local c = MessagingService:SubscribeAsync(self.PrivateServerId..topic, function(data)
fn(data.Sent, unpack(data.Data))
end)
table.insert(self._bound, c)
return c
end
function Server:TeleportAsync(players: {Player}): (boolean, TeleportAsyncResult | string)
local success, why = pcall(TeleportService.TeleportAsync, TeleportService, self.PlaceId, players, self._options)
return success, why
end
--! Should only be called inside <code>DataModel:BindToClose(callback) </code> callback !
function Server:CloseAsync(reason: string?)
self:MessageAsync("Closing")
end
export type Server = typeof(Server) & {
UserIds: {number},
PrivateServerId: string,
PlaceId: number,
CreatedTime: number,
IsClosed: boolean,
OnPlayerJoined: ((joinTimestamp: number, userId: number) -> ())?,
OnPlayerLeft: ((leftTimestamp: number, userId: number) -> ())?,
OnClose: ((closeTimestamp: number, reason: string?) -> ())?
}
local function server_new(teleportToOptions, placeId, userIds, privateServerId, createdTime): Server
local server = setmetatable({
_options = teleportToOptions,
_bound = {},
UserIds = userIds,
PrivateServerId = privateServerId,
PlaceId = placeId,
CreatedTime = createdTime,
IsClosed = false
}, Server)
local onJoin = server:BindToMessageAsync("PlayerJoined", function(sent, userId)
local index = #server.UserIds + 1
server.UserIds[index] = userId
if server.OnPlayerJoined then
server.OnPlayerJoined(sent, userId)
end
end)
local onLeft = server:BindToMessageAsync("PlayerLeft", function(sent, userId)
local index = table.find(server.UserIds, userId)
if not index then return end
server.UserIds[index] = nil
if server.OnPlayerLeft then
server.OnPlayerLeft(sent, userId)
end
end)
local onClose
onClose = server:BindToMessageAsync("Closing", function(sent, reason)
onClose:Disconnect()
onJoin:Disconnect()
onLeft:Disconnect()
teleportToOptions:Destroy()
for _, c in server._bound do
if not c.Connected then continue end
c:Disconnect()
end
table.clear(server._bound)
if server.OnClose then
server.OnClose(sent, reason)
end
ServerInfoStore:RemoveAsync(server.PrivateServerId)
setmetatable(server, nil)
table.clear(server)
server.IsClosed = true
end)
return server
end
local function create(placeId: number, players: {Player}): (boolean, Server?)
local createdTime = DateTime.now().UnixTimestampMillis
local userIds = {}
for _, player in players do
if not player then continue end
table.insert(userIds, player.UserId)
end
local options = Instance.new("TeleportOptions")
options.ShouldReserveServer = true
local success, result = pcall(TeleportService.TeleportAsync, TeleportService, placeId, players, options)
if not success then
warn(`Server creation for place id {placeId} failed: {result}`)
return false, nil
end
ServerInfoStore:SetAsync(result.PrivateServerId, {
userIds = userIds,
placeId = placeId,
accessCode = result.ReservedServerAccessCode,
}, SERVER_REGISTERED_EXPIRATION, createdTime)
local teleportToOptions = Instance.new("TeleportOptions")
teleportToOptions.ReservedServerAccessCode = result.ReservedServerAccessCode
return true, server_new(teleportToOptions, placeId, userIds, result.PrivateServerId, createdTime)
end
local function retrieve(sortDirection: Enum.SortDirection, minCreatedTimeMillis: number?, maxCreatedTimeMillis: number?): {{any}}
local lowerBound = if minCreatedTimeMillis then {sortKey = minCreatedTimeMillis} else nil
local upperBound = if maxCreatedTimeMillis then {sortKey = maxCreatedTimeMillis} else nil
local servers = {}
while true do
local entries = ServerInfoStore:GetRangeAsync(sortDirection, 100, lowerBound, upperBound)
for _, pair in entries do
local createdTime = pair.sortKey
local privateServerId = pair.key
local info = pair.value
local accessCode = info.accessCode
local options = Instance.new("TeleportOptions")
options.ReservedServerAccessCode = accessCode
table.insert(servers, {options, info.placeId, info.userIds, privateServerId, createdTime})
end
if #entries < 100 then break end
local last = entries[#entries]
lowerBound = {key = last.key, sortKey = last.sortKey}
end
return servers
end
local runningServerInstance: Server?
if game.PrivateServerId ~= "" then
local info, createdTime = ServerInfoStore:GetAsync(game.PrivateServerId)
if not info or not createdTime then
error("Exception: Info or CreatedTime not found for running server (Maybe server was not created with this module?)")
end
local options = Instance.new("TeleportOptions")
options.ReservedServerAccessCode = info.accessCode
runningServerInstance = setmetatable({
_options = options,
_bound = {},
UserIds = info.userIds,
PrivateServerId = game.PrivateServerId,
PlaceId = game.PlaceId,
CreatedTime = createdTime,
IsClosed = false
}, Server)
Players.PlayerAdded:Connect(function(player)
local index = #runningServerInstance.UserIds + 1
runningServerInstance.UserIds[index] = player.UserId
if runningServerInstance.OnPlayerJoined then
runningServerInstance.OnPlayerJoined(player.UserId)
end
runningServerInstance:MessageAsync("PlayerJoined", player.UserId)
ServerInfoStore:UpdateAsync(game.PrivateServerId, function(value, sortKey)
value.userIds[index] = player.UserId
return value, sortKey
end, SERVER_REGISTERED_EXPIRATION - ((DateTime.now().UnixTimestampMillis - createdTime) / 1000))
end)
Players.PlayerRemoving:Connect(function(player)
local index = table.find(runningServerInstance.UserIds, player.UserId)
if not index then return end
table.remove(runningServerInstance.UserIds, index)
if runningServerInstance.OnPlayerLeft then
runningServerInstance.OnPlayerLeft(player.UserId)
end
runningServerInstance:MessageAsync("PlayerLeft", player.UserId)
ServerInfoStore:UpdateAsync(game.PrivateServerId, function(value, sortKey)
table.remove(value.userIds, index)
return value, sortKey
end, SERVER_REGISTERED_EXPIRATION - ((DateTime.now().UnixTimestampMillis - createdTime) / 1000))
end)
end
return {
create = create,
retrieve = retrieve,
running = runningServerInstance,
fromInfo = server_new,
ServerInfoStore = ServerInfoStore
}
Usage:
local Server = require(path.to.this.module)
--....
-- Maybe this happens after a certain time has passed after waiting in a lobby
local success, server = Server.create(123456, players)
if not success then return end
-- Later, when another lobby tries to join the same game maybe
server:TeleportAsync(players)
-- To get all currently running Servers
local Server = require(path.to.this.module)
local serverInfo = Server.retrieve(Enum.SortDirection.Descending)
-- Have to instantiate a new Server because .retrieve only gets the informations
local firstServer = Server.fromInfo(unpack(serverInfo[1]))
-- Maybe you want the server to do some event
firstServer:MessageAsync("DoEvent", 123)
-- Maybe you also wanna see when a player is added to the server
function firstServer.OnPlayerJoined(userId)
print(`Player {userId} has successfully joined server {firstServer.PrivateServerId}!`)
end
-- Now lets say you wanna recieve messages from a reserved server
local Server = require(path.to.this.module)
local running = Server.running
running:BindToMessageAsync("DoEvent", ...)
print("Done event: ", ...)
end)
I wrote this bored out of my mind while at school so uh don’t expect it to work perfectly xd Just wanted to share something I thought would be useful
Let me know down in the replies if you encounter any issues