Help on custom Chat Bubble script

So i am working on a custom chat script and i am making custom bubbles and i am currently struggling on animations. My script works but after the first 2 bubbles it doesn’t get removed and it removes a little decoration on all bubbles instead it supposed to remove the last 3. It is similar to Roblox Chat System like UUID’s to make a report system. Here is my FULL script:

-- Config
local Debug		= false
local SDS		= game.ServerScriptService.ExperienceChat.Main.ServerDebug
local SDebug	= SDS.Value

-- Services
local G = game
local Players				= G:GetService("Players")
local ReplicatedStorage		= G:GetService("ReplicatedStorage")
local TextService			= G:GetService("TextService")
local TweenService			= G:GetService("TweenService")

-- Folders
local Main_Remote			= ReplicatedStorage:WaitForChild("ExperienceChat"):WaitForChild("remote")
local Main_Module			= ReplicatedStorage:WaitForChild("ExperienceChat"):WaitForChild("module")
local FrameChat_Remote		= ReplicatedStorage:WaitForChild("ExperienceChat"):WaitForChild("FrameChat"):WaitForChild("remote")
local BubbleChat_Remote		= ReplicatedStorage:WaitForChild("ExperienceChat"):WaitForChild("BubbleChat"):WaitForChild("remote")

-- Remotes
local ReceiveText			= FrameChat_Remote:WaitForChild("ReceiveText")
local SendText				= FrameChat_Remote:WaitForChild("SendText")
local MessageBlocker		= FrameChat_Remote:WaitForChild("MessageBlocker")
local SendBubble			= BubbleChat_Remote:WaitForChild("SendBubble")
local LocalDebug			= Main_Remote:WaitForChild("LocalDebug")
local ServerDebug			= Main_Remote:WaitForChild("ServerDebug")
local RunCommand			= Main_Remote:WaitForChild("RunCommand")
local Notifier				= Main_Remote:WaitForChild("Notifier")


LocalDebug.OnServerEvent:Connect(function(Player)
	if Debug == false then
		Debug = true
	else
		Debug = false
	end
end)

SDS.Changed:Connect(function()
	SDebug = SDS.Value
end)

local function generateRandomString()
	local function getRandomSection(length)
		local section = ""
		for i = 1, length do
			local charType = math.random(1, 2)
			if charType == 1 then
				section = section .. string.char(math.random(65, 90))
			else
				section = section .. math.random(0, 9)
			end
		end
		return section
	end
	return getRandomSection(8) .. "-" .. getRandomSection(4) .. "-" .. getRandomSection(4) .. "-" .. getRandomSection(4) .. "-" .. getRandomSection(12)
end

SendBubble.OnServerEvent:Connect(function(player, Username, Text, ID)
	if Debug then
		local message = "[Debug] Received bubble request from: ".. Username.. '", Text: "'.. Text.. '" | Server'
		local Type = print
		Notifier:FireClient(player, message, Type)
	end
	if SDebug then
		print("[ServerDebug] @".. Username, 'fired a bubble request: "'.. Text.. '" | SendBubble')
	end

	local random = generateRandomString()
	local BubbleTemplate = player:WaitForChild("ExperienceChat"):WaitForChild("Template"):WaitForChild("BubbleFrame")

	if not BubbleTemplate then
		if SDebug then
			warn("[Debug] Could not find Bubble Template for @".. Username.. " | SendBubble")
		end
		return
	end

	local Bubble = BubbleTemplate:Clone()
	Bubble.Name = "Bubble" .. ID .. "-{" .. random .. "}"

	local Character = game.Workspace:FindFirstChild(Username)
	local Head = Character and Character:FindFirstChild("Head")
	local BubbleGui = Head and Head:FindFirstChildOfClass("BillboardGui")
	local BubbleFrame = BubbleGui and BubbleGui:FindFirstChild("BubbleChatList")

	if not BubbleFrame then
		if SDebug then
			warn("[ServerDebug] Could not find BubbleChatList for @".. Username.. " | SendBubble")
		end
		return
	end

	-- Clean up old bubbles
	for _, child in ipairs(BubbleFrame:GetChildren()) do
		local deadlineValue = child:FindFirstChild("DeadlineLayout")
		if deadlineValue then
			local deadlineNumber = tonumber(deadlineValue.Value) or 0
			if deadlineNumber >= 4 then
				if Debug then
					print("[Debug] Removing old bubble:", child.Name)
				end
				child:Destroy()
			else
				deadlineValue.Value = tostring(deadlineNumber + 1)
			end
		end
	end

	-- Update the positions of the bubbles
	local function updateBubblesYPosition()
		local bubbles = {}

		-- Collect all bubbles that are frames and have a DeadlineLayout
		for _, bubble in ipairs(BubbleFrame:GetChildren()) do
			if bubble:IsA("Frame") and bubble:FindFirstChild("DeadlineLayout") then
				table.insert(bubbles, bubble)
			end
		end

		-- Sort bubbles based on DeadlineLayout value
		table.sort(bubbles, function(a, b)
			local aDeadline = tonumber(a:FindFirstChild("DeadlineLayout") and a.DeadlineLayout.Value) or 0
			local bDeadline = tonumber(b:FindFirstChild("DeadlineLayout") and b.DeadlineLayout.Value) or 0
			return aDeadline < bDeadline
		end)

		local startY = 1
		local spacing = 0.25
		local tweens = {}  -- Store tweens here

		-- Destroy the old Layout to reset the positions
		local Layout = BubbleFrame:FindFirstChild("Layout")
		if Layout then
			Layout:Destroy()
			if SDebug then
				print("[ServerDebug] Disabled UIListLayout for @".. Username, "| SendBubble")
			end
		end

		-- Pre-calculate the target positions for all bubbles
		for i, bubble in ipairs(bubbles) do
			local newY = startY - ((i - 1) * spacing)
			local deadlineValue = tonumber(bubble:FindFirstChild("DeadlineLayout") and bubble.DeadlineLayout.Value) or 0

			-- Handle bubbles with deadlineValue == 1 (skip caret destruction for these)
			if deadlineValue == 1 then
				bubble.Position = UDim2.new(0.5, 0, 1, 0)
				print("[Fixed] Position for", bubble.Name, "set to", bubble.Position)
			else
				-- Destroy caret for non-deadlineValue 1 bubbles
				local caret = bubble:FindFirstChild("Caret")
				if caret then
					print("[Debug] Destroying Caret for bubble:", bubble.Name)
					caret:Destroy()
				end

				-- Create and store tween for the position
				local tweenInfo = TweenInfo.new(0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
				local tweenGoal = { Position = UDim2.new(0.5, 0, newY, 0) }

				local tween = TweenService:Create(bubble, tweenInfo, tweenGoal)
				table.insert(tweens, tween)  -- Store the tween
				print("[Fixed] Tweened position for", bubble.Name, "to", bubble.Position)
			end
		end

		-- Play all the tweens simultaneously
		for _, tween in ipairs(tweens) do
			tween:Play()
		end

		-- Function to check if all tweens are completed
		local function checkTweensCompletion()
			for _, tween in ipairs(tweens) do
				tween.Completed:Wait()
				if tween.PlaybackState ~= Enum.PlaybackState.Completed then
					return false
				end
			end
			return true
		end

		-- Retry logic until tweens complete
		while not checkTweensCompletion() do
			warn("[ServerDebug] Tweens didn't complete properly. Retrying...")
			-- Re-run the function and reset tweens
			tweens = {}
			updateBubblesYPosition()  -- Keep retrying by calling the function until successful
		end

		-- After successful completion, create the new Layout
		local LayoutCopy = player:WaitForChild("ExperienceChat"):WaitForChild("Template"):WaitForChild("BubbleChatGui"):WaitForChild("BubbleChatList"):WaitForChild("Layout"):Clone()
		LayoutCopy.Parent = BubbleFrame
		if SDebug then
			print("[ServerDebug] Enabled UIListLayout for @".. Username, "| SendBubble")
		end
	end

	-- Parent the new bubble and set the text
	Bubble.Parent = BubbleFrame
	local BubbleText = Bubble.ChatBubbleFrame.Text
	BubbleText.Text = Text

	-- Calculate and set bubble size based on the text size
	local textSize = TextService:GetTextSize(Text, BubbleText.TextSize, BubbleText.Font, Vector2.new(math.huge, math.huge))
	local BaseXoffset = 16
	local Max_Width = 21 * 15  -- Maximum width (21 characters max)
	local NewXoffset = math.min(textSize.X + BaseXoffset, Max_Width)

	local BaseYoffset = 16
	local Yoffset_PerLine = 20
	local Max_Lines = 8
	local NumLines = math.min(math.ceil(textSize.X / Max_Width), Max_Lines)
	local NewYoffset = BaseYoffset + NumLines * Yoffset_PerLine

	-- Apply new bubble size
	Bubble.Size = UDim2.new(0, NewXoffset, 0, NewYoffset)
	print("[Debug] Set bubble size to", Bubble.Size)

	-- Call function to update bubble positions (WITH TWEENS)
	updateBubblesYPosition()

	-- Destroy the bubble after 10 seconds, with fade-out effect
	task.delay(10, function()
		if Bubble then
			print("[Debug] Destroying bubble:", Bubble.Name)

			local chatBubbleFrame = Bubble:FindFirstChild("ChatBubbleFrame")
			local text = chatBubbleFrame and chatBubbleFrame:FindFirstChild("Text")

			if chatBubbleFrame and text then
				local fadeOutBackgroundTween = TweenService:Create(
					chatBubbleFrame,
					TweenInfo.new(1, Enum.EasingStyle.Linear, Enum.EasingDirection.Out),
					{ BackgroundTransparency = 1 }
				)
				fadeOutBackgroundTween:Play()

				local fadeOutTextTween = TweenService:Create(
					text,
					TweenInfo.new(1, Enum.EasingStyle.Linear, Enum.EasingDirection.Out),
					{ TextTransparency = 1 }
				)
				fadeOutTextTween:Play()

				chatBubbleFrame.BackgroundTransparency = 0.1
				text.TextTransparency = 0.1

				fadeOutBackgroundTween.Completed:Wait()
				fadeOutTextTween.Completed:Wait()

				Bubble:Destroy()
			end
		end
	end)

end)

The error is after the “updateBubblesYPosition” function

First error; it removes a little decoration under all the bubbles instead it should remove the last 3 one’s, it does work for the first 2 but then it breaks apart:

for i, bubble in ipairs(bubbles) do
			local newY = startY - ((i - 1) * spacing)
			local deadlineValue = tonumber(bubble:FindFirstChild("DeadlineLayout") and bubble.DeadlineLayout.Value) or 0

			-- Handle bubbles with deadlineValue == 1 (skip caret destruction for these)
			if deadlineValue == 1 then
				bubble.Position = UDim2.new(0.5, 0, 1, 0)
				print("[Fixed] Position for", bubble.Name, "set to", bubble.Position)
			else
				-- Destroy caret for non-deadlineValue 1 bubbles
				local caret = bubble:FindFirstChild("Caret")
				if caret then
					print("[Debug] Destroying Caret for bubble:", bubble.Name)
					caret:Destroy()
				end

				-- Create and store tween for the position
				local tweenInfo = TweenInfo.new(0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
				local tweenGoal = { Position = UDim2.new(0.5, 0, newY, 0) }

				local tween = TweenService:Create(bubble, tweenInfo, tweenGoal)
				table.insert(tweens, tween)  -- Store the tween
				print("[Fixed] Tweened position for", bubble.Name, "to", bubble.Position)
			end
		end

Second error; removing the bubbles doesn’t work after the first 2 bubbles while testing:

task.delay(10, function()
		if Bubble then
			print("[Debug] Destroying bubble:", Bubble.Name)

			local chatBubbleFrame = Bubble:FindFirstChild("ChatBubbleFrame")
			local text = chatBubbleFrame and chatBubbleFrame:FindFirstChild("Text")

			if chatBubbleFrame and text then
				local fadeOutBackgroundTween = TweenService:Create(
					chatBubbleFrame,
					TweenInfo.new(1, Enum.EasingStyle.Linear, Enum.EasingDirection.Out),
					{ BackgroundTransparency = 1 }
				)
				fadeOutBackgroundTween:Play()

				local fadeOutTextTween = TweenService:Create(
					text,
					TweenInfo.new(1, Enum.EasingStyle.Linear, Enum.EasingDirection.Out),
					{ TextTransparency = 1 }
				)
				fadeOutTextTween:Play()

				chatBubbleFrame.BackgroundTransparency = 0.1
				text.TextTransparency = 0.1

				fadeOutBackgroundTween.Completed:Wait()
				fadeOutTextTween.Completed:Wait()

				Bubble:Destroy()
			end
		end
	end)
1 Like