I want to make it so 2 players from different servers can connect with eachoder and chat. When a player starts he is added to a queue where he can connect with another player and chat. If the players skip, they are both added to the queue. If one stops, he just stops and the other one is added to the queue. Same with leaving. They can chat with eachoder.
Im not sure why, spent a long time trying to debug this, there are many bugs. One bug is that if 2 players are connected and there is a third one in the queue, if one of the 2 players skip then they will both be connected to the third player. Leaving does not clear the queue as it should (you can still match with someone who left, no idea how), sometimes you can connect with players who stopped. I think its because both players try to connect at the same time.
I tried to make the queue be cleared in multiple places, but it didnt help. Spent a few hours spamming prints and deleting them from places but didnt find anything. Should I delete everything and remake the script? I put my whole script bc I have no idea where this happens. Is there a better way to do the matchmaking? Maybe im doing this completely wrong.
local MESSAGE_SIZE_LIMIT = 100
local MESSAGE_COOLDOWN = 1
local DURATION = 60*60*24 -- How long the queue will last for
-- Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MemoryService = game:GetService("MemoryStoreService")
local MessagingService = game:GetService("MessagingService")
local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")
local TextService = game:GetService("TextService")
local MarketplaceService = game:GetService("MarketplaceService")
-- Instances
local QueueStore = MemoryService:GetSortedMap("SessionQueue")
local Remotes = ReplicatedStorage.Remotes
local StartSessionRf = Remotes.StartSession
local StopSessionEvent = Remotes.StopSession
local SendMessageRf = Remotes.SendMessage
local ReceiveMessageEvent = Remotes.ReceiveMessage
local LoadAvatarEvent = Remotes.LoadAvatar
local SkipPlayerEvent = Remotes.SkipPlayer
local MatchChangedEvent = Remotes.MatchChanged
-- Constants
local SESSION_QUEUE_KEY = "ChatSessions"
local CONNECTION_TOPIC = "ConnectionFound"
local MESSAGE_PREFIX = "Session" -- Prefix for message topics
-- Variables
local Connections = {} -- [Player] = MessagingService subscription
local Sessions = {} -- [Player] = MatchedPlayerId
local MessageCooldowns = {} -- [Player] = last message time
-- Helper Functions
-- Gets the animation id
local function GetAnimationId(MarketplaceId)
return MarketplaceService:GetProductInfo(MarketplaceId).ProductId
end
-- Filters text using Roblox text filtering.
local function FilterText(Text, Player)
if not Text or Text == "" then return "" end
local Success, Result = pcall(function()
local Filtered = TextService:FilterStringAsync(Text, Player.UserId)
return Filtered:GetNonChatStringForUserAsync(Player.UserId)
end)
if Success then
return Result
else
warn("Error filtering text: " .. Result)
return "##########"
end
end
-- Gets the player in the server, their Id, and their match's Id.
local function GetPlayerAndMatch(Player1Id, Player2Id)
for _, Plr in Players:GetPlayers() do
if Plr.UserId == Player1Id or Plr.UserId == Player2Id then
local MatchId = (Plr.UserId == Player1Id) and Player2Id or Player1Id
return {Player = Plr, PlayerId = Plr.UserId, MatchId = MatchId}
end
end
return nil
end
-- Updates the queue with the data.
local function UpdateQueue(QueueData)
local Success, ErrorMsg = pcall(function()
QueueStore:SetAsync(SESSION_QUEUE_KEY, QueueData, DURATION)
end)
if not Success then
warn("Error updating queue: " .. tostring(ErrorMsg))
end
end
-- Connects the player and their match.
local function ConnectPlayers(Player1Id, Player2Id)
local MatchData = GetPlayerAndMatch(tonumber(Player1Id), tonumber(Player2Id))
if not MatchData then print("Could not connect players: no match data found!") return end
local Player = MatchData.Player
Sessions[Player] = MatchData.MatchId
-- May not be necessary but if a previous one fails for some reason this will help
local Success, QueueData = pcall(function()
return QueueStore:GetAsync(SESSION_QUEUE_KEY)
end)
if Success then
QueueData[tostring(Player1Id)] = nil
QueueData[tostring(Player2Id)] = nil
UpdateQueue(QueueData)
end
Connections[Player] = MessagingService:SubscribeAsync(MESSAGE_PREFIX .. Player.UserId, function(Message)
local FilteredMessage = FilterText(Message.Data, Player)
ReceiveMessageEvent:FireClient(Player, FilteredMessage)
print("MESSAGE RECEIVED")
end)
MatchChangedEvent:FireClient(Player, "Connected", MatchData.MatchId)
end
-- Main Functions
-- Adds player to queue or matches them with another player if it finds one.
local function OnStart(Player)
local PlayerId = Player.UserId
print("got player id")
local Success, QueueData = pcall(function()
return QueueStore:GetAsync(SESSION_QUEUE_KEY)
end)
if not Success then
warn("Failed to get queue")
return false
end
QueueData = QueueData or {}
print("did this")
-- Try to find a match
local MatchedPlayerId
for Id, _ in QueueData do
MatchedPlayerId = Id
QueueData[tostring(Id)] = nil
break
end
print("everything good so far")
if QueueData[tostring(PlayerId)] then
QueueData[tostring(PlayerId)] = nil
end
print("hmmm")
if MatchedPlayerId == tostring(PlayerId) then print("only found me") return false end
if MatchedPlayerId then
print("player found")
UpdateQueue(QueueData)
print("updated queue")
local MatchedPlayers = {MatchedPlayerId, PlayerId, true}
local EncodedData = HttpService:JSONEncode(MatchedPlayers)
local SuccessPublish, Error = pcall(function()
MessagingService:PublishAsync(CONNECTION_TOPIC, EncodedData)
end)
print("async published")
if not SuccessPublish then
warn("Error publishing match: " .. tostring(Error))
return false
end
else
print("no queue found")
QueueData[tostring(PlayerId)] = PlayerId
UpdateQueue(QueueData)
end
print("everything is good")
return true
end
-- Stops the session.
local function StopSession(Player)
-- Dosent seem possible but just to be safe
if not Player then warn("Player not found ... How?") return end
local PlayerId = Player.UserId
local Success, QueueData = pcall(function()
return QueueStore:GetAsync(SESSION_QUEUE_KEY)
end)
if Success and QueueData then
QueueData[tostring(PlayerId)] = nil
UpdateQueue(QueueData)
else
warn("There was an error while stopping!!!!")
end
-- Cleanup stuff
if Connections[Player] then
Connections[Player]:Disconnect()
Connections[Player] = nil
end
if Sessions[Player] then
local Data = {PlayerId, Sessions[Player], false}
local EncodedData = HttpService:JSONEncode(Data)
local Success, Error = pcall(function()
MessagingService:PublishAsync(CONNECTION_TOPIC, EncodedData)
end)
if not Success then
warn("Error while stopping session: " .. tostring(Error))
end
Sessions[Player] = nil
end
end
-- Sends the message to the match.
local function SendMessage(Player, Message)
-- Cooldown and size check
if MessageCooldowns[Player] and tick() - MessageCooldowns[Player] < MESSAGE_COOLDOWN or #Message > MESSAGE_SIZE_LIMIT then
return false
end
local MatchId = Sessions[Player]
if not MatchId then warn("No match found!") return false end
local FilteredMessage = FilterText(Message, Player)
local Success, Error = pcall(function()
MessagingService:PublishAsync(MESSAGE_PREFIX .. MatchId, FilteredMessage)
end)
if not Success then
warn("Error sending message: " .. tostring(Error))
return false
end
print("MESSAGE SENT!!!!!!!!!!!")
MessageCooldowns[Player] = tick()
return FilteredMessage
end
-- Loads the avatar on the dummy
local function LoadAvatar(Player, UserId)
local PlayerGui = Player.PlayerGui
local Avatars = PlayerGui.Avatars
local YourDummy = Avatars.YourAvatar.WorldModel.Dummy
local MatchDummy = Avatars.MatchedAvatar.WorldModel.Dummy
local DummyToApplyThisTo = nil
if not UserId then
UserId = Player.UserId
DummyToApplyThisTo = YourDummy
else
DummyToApplyThisTo = MatchDummy
end
local Animator = DummyToApplyThisTo.Humanoid.Animator
for _, animation in Animator:GetPlayingAnimationTracks() do
animation:Stop()
end
local Success, Result = pcall(function()
return Players:GetHumanoidDescriptionFromUserId(UserId)
end)
if not Success then
warn("Error while getting avatar description for " .. UserId .. ": " .. Result)
return
end
local NewHumanoidDescription = Result
DummyToApplyThisTo.Humanoid:ApplyDescription(NewHumanoidDescription)
local Animation = Instance.new("Animation")
Animation.AnimationId = "rbxassetid://10921259953"
Animation.Parent = Animator
Animator:LoadAnimation(Animation):Play()
end
-- Disconnects 2 players and starts a new session
local function DisconnectPlayers(Player)
StopSession(Player)
--[[
if Connections[Player] then
Connections[Player]:Disconnect()
Connections[Player] = nil
end
if Sessions[Player] then
Sessions[Player] = nil
MatchChangedEvent:FireClient(Player, "Disconnected")
end
local Success, QueueData = pcall(function()
return QueueStore:GetAsync(SESSION_QUEUE_KEY)
end)
QueueData[tostring(Player.UserId)] = nil
UpdateQueue(QueueData)
]]
print("Starting onstart function")
OnStart(Player)
end
-- Disconnects the players when one of them leaves
local function OnPlayerRemoving(Player)
if Sessions[Player] then
local Data = {Player.UserId, Sessions[Player], false}
local EncodedData = HttpService:JSONEncode(Data)
local Success, Error = pcall(function()
MessagingService:PublishAsync(CONNECTION_TOPIC, EncodedData)
end)
local QueueSuccess, QueueData = pcall(function()
return QueueStore:GetAsync(SESSION_QUEUE_KEY)
end)
if QueueSuccess then
QueueData[tostring(Player.UserId)] = nil
UpdateQueue(QueueData)
end
if not Success then
warn("Error disconnecting player: " .. tostring(Error))
end
end
end
-- Skips the player
local function SkipPlayer(Player)
print("skipping!")
if not Sessions[Player] then return false end -- no session
local Data = {Player.UserId, Sessions[Player], false}
local EncodedData = HttpService:JSONEncode(Data)
local Success, Error = pcall(function()
MessagingService:PublishAsync(CONNECTION_TOPIC, EncodedData)
end)
print("things are success")
if not Success then
warn("Error skipping player: " .. tostring(Error))
return false
end
print("before disconnect call in skip")
DisconnectPlayers(Player)
print("after disconnect call in skip")
return true
end
-- Runtime
MessagingService:SubscribeAsync(CONNECTION_TOPIC, function(SessionData)
local DecodedData = HttpService:JSONDecode(SessionData.Data)
local MatchData = GetPlayerAndMatch(tonumber(DecodedData[1]), tonumber(DecodedData[2]))
if not MatchData then return end
local PlayerInServer = MatchData.Player
if not PlayerInServer then return end
if not Sessions[PlayerInServer] and not Connections[PlayerInServer] then return end
if DecodedData[3] == true then
ConnectPlayers(DecodedData[1], DecodedData[2])
else
DisconnectPlayers(PlayerInServer)
end
end)
SendMessageRf.OnServerInvoke = SendMessage
StartSessionRf.OnServerInvoke = OnStart
SkipPlayerEvent.OnServerInvoke = SkipPlayer
StopSessionEvent.OnServerEvent:Connect(StopSession)
LoadAvatarEvent.OnServerEvent:Connect(LoadAvatar)
Players.PlayerRemoving:Connect(OnPlayerRemoving)