local function Y(Y,p,r,b,h)this.ChatBubble[Y]=createChatBubbleMain(p,h)this.ChatBubbleWithTail[Y]=createChatBubbleWithTail(p,UDim2.new(.5,-CHAT_BUBBLE_TAIL_HEIGHT,1,b and-1 or 0),UDim2.new(0,30,0,CHAT_BUBBLE_TAIL_HEIGHT),h)this.ScalingChatBubbleWithTail[Y]=createScaledChatBubbleWithTail(p,.5,UDim2.new(-0.5,0,0,b and-1 or 0),h)end Y(BubbleColor.WHITE,"\117\105\047\100\105\097\108\111\103\095\119\104\105\116\101","\117\105\047\099\104\097\116\066\117\098\098\108\101\095\119\104\105\116\101\095\110\111\116\105\102\121\095\098\107\103",false,Rect.new(5,5,15,15))Y(BubbleColor.BLUE,"\117\105\047\100\105\097\108\111\103\095\098\108\117\101","\117\105\047\099\104\097\116\066\117\098\098\108\101\095\098\108\117\101\095\110\111\116\105\102\121\095\098\107\103",true,Rect.new(7,7,33,33))Y(BubbleColor.RED,"\117\105\047\100\105\097\108\111\103\095\114\101\100","\117\105\047\099\104\097\116\066\117\098\098\108\101\095\114\101\100\095\110\111\116\105\102\121\095\098\107\103",true,Rect.new(7,7,33,33))Y(BubbleColor.GREEN,"\117\105\047\100\105\097\108\111\103\095\103\114\101\101\110","\117\105\047\099\104\097\116\066\117\098\098\108\101\095\103\114\101\101\110\095\110\111\116\105\102\121\095\098\107\103",true,Rect.new(7,7,33,33))function this.SanitizeChatLine(p,Y)if getMessageLength(Y)>MaxChatMessageLengthExclusive then local p=utf8.offset(Y,(MaxChatMessageLengthExclusive+getMessageLength(ELIPSES))+1)-1 return string.sub(Y,1,p)else return Y end end local function p(Y)local p=Instance.new("\066\105\108\108\098\111\097\114\100\071\117\105")p.Adornee=Y p.Size=UDim2.new(0,BILLBOARD_MAX_WIDTH,0,BILLBOARD_MAX_HEIGHT)p.StudsOffset=Vector3.new(0,1.5,2)p.Parent=BubbleChatScreenGui local r=Instance.new("\070\114\097\109\101")r.Name="\066\105\108\108\098\111\097\114\100\070\114\097\109\101"r.Size=UDim2.new(1,0,1,0)r.Position=UDim2.new(0,0,-0.5,0)r.BackgroundTransparency=1 r.Parent=p local b=nil b=r.ChildRemoved:connect(function()if#r:GetChildren()<=1 then b:disconnect()p:Destroy()end end);(this:CreateSmallTalkBubble(BubbleColor.WHITE)).Parent=r return p end function this.CreateBillboardGuiHelper(b,Y,r)if Y and not(this.CharacterSortedMsg:Get(Y)).BillboardGui then if not r then if Y:IsA("\066\097\115\101\080\097\114\116")then local r=p(Y);(this.CharacterSortedMsg:Get(Y)).BillboardGui=r return end end if Y:IsA("\077\111\100\101\108")then local r=Y:FindFirstChild("\072\101\097\100")if r and r:IsA("\066\097\115\101\080\097\114\116")then local b=p(r);(this.CharacterSortedMsg:Get(Y)).BillboardGui=b end end end end local function r(Y)if not Y then return 100000 end return(Y.Position-game.Workspace.CurrentCamera.CoordinateFrame.p).magnitude end local function b(Y)if Y and PlayersService.LocalPlayer.Character then return Y:IsDescendantOf(PlayersService.LocalPlayer.Character)end end function this.SetBillboardLODNear(p,Y)local r=b(Y.Adornee)Y.Size=UDim2.new(0,BILLBOARD_MAX_WIDTH,0,BILLBOARD_MAX_HEIGHT)Y.StudsOffset=Vector3.new(0,r and 1.5 or 2.5,r and 2 or.1)Y.Enabled=true local h=Y.BillboardFrame:GetChildren()for Y=1,#h,1 do h[Y].Visible=true end Y.BillboardFrame.SmallTalkBubble.Visible=false end function this.SetBillboardLODDistant(p,Y)local r=b(Y.Adornee)Y.Size=UDim2.new(4,0,3,0)Y.StudsOffset=Vector3.new(0,3,r and 2 or.1)Y.Enabled=true local h=Y.BillboardFrame:GetChildren()for Y=1,#h,1 do h[Y].Visible=false end Y.BillboardFrame.SmallTalkBubble.Visible=true end function this.SetBillboardLODVeryFar(p,Y)Y.Enabled=false end function this.SetBillboardGuiLOD(b,Y,p)if not p then return end if p:IsA("\077\111\100\101\108")then local Y=p:FindFirstChild("\072\101\097\100")if not Y then p=p.PrimaryPart else p=Y end end local h=r(p)if h<NEAR_BUBBLE_DISTANCE then this:SetBillboardLODNear(Y)elseif h>=NEAR_BUBBLE_DISTANCE and h<MAX_BUBBLE_DISTANCE then this:SetBillboardLODDistant(Y)else this:SetBillboardLODVeryFar(Y)end end function this.CameraCFrameChanged(Y)for Y,p in pairs(this.CharacterSortedMsg:GetData())do local r=p.BillboardGui if r then this:SetBillboardGuiLOD(r,Y)end end end function this.CreateBubbleText(r,Y,p)local b=Instance.new("\084\101\120\116\076\097\098\101\108")b.Name="\066\117\098\098\108\101\084\101\120\116"b.BackgroundTransparency=1 if UserFixBubbleChatText then b.Size=UDim2.fromScale(1,1)else b.Position=UDim2.new(0,CHAT_BUBBLE_WIDTH_PADDING/2,0,0)b.Size=UDim2.new(1,-CHAT_BUBBLE_WIDTH_PADDING,1,0)end b.Font=CHAT_BUBBLE_FONT b.ClipsDescendants=true b.TextWrapped=true b.FontSize=CHAT_BUBBLE_FONT_SIZE b.Text=Y b.Visible=false b.AutoLocalize=p if UserFixBubbleChatText then local Y=Instance.new("\085\073\080\097\100\100\105\110\103")Y.PaddingTop=UDim.new(0,CHAT_BUBBLE_PADDING)Y.PaddingRight=UDim.new(0,CHAT_BUBBLE_PADDING)Y.PaddingBottom=UDim.new(0,CHAT_BUBBLE_PADDING)Y.PaddingLeft=UDim.new(0,CHAT_BUBBLE_PADDING)Y.Parent=b end return b end function this.CreateSmallTalkBubble(p,Y)local r=this.ScalingChatBubbleWithTail[Y]:Clone()r.Name="\083\109\097\108\108\084\097\108\107\066\117\098\098\108\101"r.AnchorPoint=Vector2.new(0,.5)r.Position=UDim2.new(0,0,.5,0)r.Visible=false local b=this:CreateBubbleText("\046\046\046")b.TextScaled=true b.TextWrapped=false b.Visible=true b.Parent=r return r end function this.UpdateChatLinesForOrigin(r,Y,p)local b=(this.CharacterSortedMsg:Get(Y)).Fifo local h=b:Size()local D=b:GetData()if#D<=1 then return end for Y=#D-1,1,-1 do local r=D[Y]local b=r.RenderBubble if not b then return end local g=(h-Y)+1 if g>1 then local Y=b:FindFirstChild("\067\104\097\116\066\117\098\098\108\101\084\097\105\108")if Y then Y:Destroy()end local p=b:FindFirstChild("\066\117\098\098\108\101\084\101\120\116")if p then p.TextTransparency=.5 end end local P=UDim2.new(b.Position.X.Scale,b.Position.X.Offset,1,(p-b.Size.Y.Offset)-CHAT_BUBBLE_TAIL_HEIGHT)b:TweenPosition(P,Enum.EasingDirection.Out,Enum.EasingStyle.Bounce,.1,true)p=(p-b.Size.Y.Offset)-CHAT_BUBBLE_TAIL_HEIGHT end end function this.DestroyBubble(r,Y,p)if not Y then return end if Y:Empty()then return end local b=(Y:Front()).RenderBubble if not b then Y:PopFront()return end spawn(function()while(Y:Front()).RenderBubble~=p do wait()end b=(Y:Front()).RenderBubble local r=0 local h=b:FindFirstChild("\066\117\098\098\108\101\084\101\120\116")local D=b:FindFirstChild("\067\104\097\116\066\117\098\098\108\101\084\097\105\108")while b and b.ImageTransparency<1 do r=wait()if b then local Y=r*CHAT_BUBBLE_FADE_SPEED b.ImageTransparency=b.ImageTransparency+Y if h then h.TextTransparency=h.TextTransparency+Y end if D then D.ImageTransparency=D.ImageTransparency+Y end end end if b then b:Destroy()Y:PopFront()end end)end function this.CreateChatLineRender(D,Y,p,r,b,h)if not Y then return end if not(this.CharacterSortedMsg:Get(Y)).BillboardGui then this:CreateBillboardGuiHelper(Y,r)end local g=(this.CharacterSortedMsg:Get(Y)).BillboardGui if g then local Y=this.ChatBubbleWithTail[p.BubbleColor]:Clone()Y.Visible=false local r=this:CreateBubbleText(p.Message,h)r.Parent=Y Y.Parent=g.BillboardFrame p.RenderBubble=Y local D=TextService:GetTextSize(r.Text,CHAT_BUBBLE_FONT_SIZE_INT,CHAT_BUBBLE_FONT,Vector2.new(BILLBOARD_MAX_WIDTH,BILLBOARD_MAX_HEIGHT))local P=D.Y/CHAT_BUBBLE_FONT_SIZE_INT if UserFixBubbleChatText then local b=math.ceil(D.X+CHAT_BUBBLE_PADDING*2)local h=P*CHAT_BUBBLE_LINE_HEIGHT Y.Size=UDim2.fromOffset(0,0)Y.Position=UDim2.fromScale(.5,1)Y:TweenSizeAndPosition(UDim2.fromOffset(b,h),UDim2.new(.5,-b/2,1,-h),Enum.EasingDirection.Out,Enum.EasingStyle.Elastic,.1,true,function()r.Visible=true end)this:SetBillboardGuiLOD(g,p.Origin)this:UpdateChatLinesForOrigin(p.Origin,-h)else local b=math.max((D.X+CHAT_BUBBLE_WIDTH_PADDING)/BILLBOARD_MAX_WIDTH,.1)Y.Size=UDim2.new(0,0,0,0)Y.Position=UDim2.new(.5,0,1,0)local h=P*CHAT_BUBBLE_LINE_HEIGHT Y:TweenSizeAndPosition(UDim2.new(b,0,0,h),UDim2.new((1-b)/2,0,1,-h),Enum.EasingDirection.Out,Enum.EasingStyle.Elastic,.1,true,function()r.Visible=true end)this:SetBillboardGuiLOD(g,p.Origin)this:UpdateChatLinesForOrigin(p.Origin,-h)end delay(p.BubbleDieDelay,function()this:DestroyBubble(b,Y)end)end end function this.OnPlayerChatMessage(b,Y,p,r)if not this:BubbleChatEnabled()then return end local h=PlayersService.LocalPlayer local D=h~=nil and Y~=h local g=this:SanitizeChatLine(p)local P=createPlayerChatLine(Y,g,not D)if Y and P.Origin then local p=(this.CharacterSortedMsg:Get(P.Origin)).Fifo p:PushBack(P)this:CreateChatLineRender(Y.Character,P,true,p,false)end end function this.OnGameChatMessage(b,Y,p,r)local h=PlayersService.LocalPlayer local D=h~=nil and h.Character~=Y local g=BubbleColor.WHITE if r==Enum.ChatColor.Blue then g=BubbleColor.BLUE elseif r==Enum.ChatColor.Green then g=BubbleColor.GREEN elseif r==Enum.ChatColor.Red then g=BubbleColor.RED end local P=this:SanitizeChatLine(p)local m=createGameChatLine(Y,P,not D,g);(this.CharacterSortedMsg:Get(m.Origin)).Fifo:PushBack(m)if UserShouldLocalizeGameChatBubble then this:CreateChatLineRender(Y,m,false,(this.CharacterSortedMsg:Get(m.Origin)).Fifo,true)else this:CreateChatLineRender(Y,m,false,(this.CharacterSortedMsg:Get(m.Origin)).Fifo,false)end end function this.BubbleChatEnabled(Y)if UserRoactBubbleChatBeta then return false end local p=ChatService:FindFirstChild("\067\108\105\101\110\116\067\104\097\116\077\111\100\117\108\101\115")if p then local Y=p:FindFirstChild("\067\104\097\116\083\101\116\116\105\110\103\115")if Y then local p=require(Y)if p.BubbleChatEnabled~=nil then return p.BubbleChatEnabled end end end return PlayersService.BubbleChat end function this.ShowOwnFilteredMessage(Y)local p=ChatService:FindFirstChild("\067\108\105\101\110\116\067\104\097\116\077\111\100\117\108\101\115")if p then local Y=p:FindFirstChild("\067\104\097\116\083\101\116\116\105\110\103\115")if Y then Y=require(Y)return Y.ShowUserOwnFilteredMessage end end return false end function findPlayer(Y)for p,r in pairs(PlayersService:GetPlayers())do if r.Name==Y then return r end end end ChatService.Chatted:connect(function(Y,p,r)this:OnGameChatMessage(Y,p,r)end)local h=nil if game.Workspace.CurrentCamera then h=(game.Workspace.CurrentCamera:GetPropertyChangedSignal("\067\070\114\097\109\101")):connect(function(Y)this:CameraCFrameChanged()end)end game.Workspace.Changed:connect(function(Y)if Y=="\067\117\114\114\101\110\116\067\097\109\101\114\097"then if h then h:disconnect()end if game.Workspace.CurrentCamera then h=(game.Workspace.CurrentCamera:GetPropertyChangedSignal("\067\070\114\097\109\101")):connect(function(Y)this:CameraCFrameChanged()end)end end end)local D=nil function getAllowedMessageTypes()if D then return D end local Y=ChatService:FindFirstChild("\067\108\105\101\110\116\067\104\097\116\077\111\100\117\108\101\115")if Y then local p=Y:FindFirstChild("\067\104\097\116\083\101\116\116\105\110\103\115")if p then p=require(p)if p.BubbleChatMessageTypes then D=p.BubbleChatMessageTypes return D end end local r=Y:FindFirstChild("\067\104\097\116\067\111\110\115\116\097\110\116\115")if r then r=require(r)D={r.MessageTypeDefault;r.MessageTypeWhisper}end return D end return{"\077\101\115\115\097\103\101";"\087\104\105\115\112\101\114"}end function checkAllowedMessageType(Y)local p=getAllowedMessageTypes()for r=1,#p,1 do if p[r]==Y.MessageType then return true end end return false end local g=ReplicatedStorage:WaitForChild("\068\101\102\097\117\108\116\067\104\097\116\083\121\115\116\101\109\067\104\097\116\069\118\101\110\116\115")local P=g:WaitForChild("\079\110\077\101\115\115\097\103\101\068\111\110\101\070\105\108\116\101\114\105\110\103")local m=g:WaitForChild("\079\110\078\101\119\077\101\115\115\097\103\101")m.OnClientEvent:connect(function(Y,p)if not checkAllowedMessageType(Y)then return end local r=findPlayer(Y.FromSpeaker)if not r then return end if not Y.IsFiltered or Y.FromSpeaker==LocalPlayer.Name then if Y.FromSpeaker~=LocalPlayer.Name or this:ShowOwnFilteredMessage()then return end end this:OnPlayerChatMessage(r,Y.Message,nil)end)P.OnClientEvent:connect(function(Y,p)if not checkAllowedMessageType(Y)then return end local r=findPlayer(Y.FromSpeaker)if not r then return end if Y.FromSpeaker==LocalPlayer.Name and not this:ShowOwnFilteredMessage()then return end this:OnPlayerChatMessage(r,Y.Message,nil)end)

local function initChatBubbleType(chatBubbleType, fileName, imposterFileName, isInset, sliceRect)
	this.ChatBubble[chatBubbleType] = createChatBubbleMain(fileName, sliceRect)
	this.ChatBubbleWithTail[chatBubbleType] = createChatBubbleWithTail(fileName, UDim2.new(0.5, -CHAT_BUBBLE_TAIL_HEIGHT, 1, isInset and -1 or 0), UDim2.new(0, 30, 0, CHAT_BUBBLE_TAIL_HEIGHT), sliceRect)
	this.ScalingChatBubbleWithTail[chatBubbleType] = createScaledChatBubbleWithTail(fileName, 0.5, UDim2.new(-0.5, 0, 0, isInset and -1 or 0), sliceRect)
end

initChatBubbleType(BubbleColor.WHITE,	"ui/dialog_white",	"ui/chatBubble_white_notify_bkg", 	false,	Rect.new(5,5,15,15))
initChatBubbleType(BubbleColor.BLUE,	"ui/dialog_blue",	"ui/chatBubble_blue_notify_bkg",	true, 	Rect.new(7,7,33,33))
initChatBubbleType(BubbleColor.RED,		"ui/dialog_red",	"ui/chatBubble_red_notify_bkg",		true,	Rect.new(7,7,33,33))
initChatBubbleType(BubbleColor.GREEN,	"ui/dialog_green",	"ui/chatBubble_green_notify_bkg",	true,	Rect.new(7,7,33,33))

function this:SanitizeChatLine(msg)
	if getMessageLength(msg) > MaxChatMessageLengthExclusive then
		local byteOffset = utf8.offset(msg, MaxChatMessageLengthExclusive + getMessageLength(ELIPSES) + 1) - 1
		return string.sub(msg, 1, byteOffset)
	else
		return msg
	end
end

local function createBillboardInstance(adornee)
	local billboardGui = Instance.new("BillboardGui")
	billboardGui.Adornee = adornee
	billboardGui.Size = UDim2.new(0, BILLBOARD_MAX_WIDTH, 0, BILLBOARD_MAX_HEIGHT)
	billboardGui.StudsOffset = Vector3.new(0, 1.5, 2)
	billboardGui.Parent = BubbleChatScreenGui

	local billboardFrame = Instance.new("Frame")
	billboardFrame.Name = "BillboardFrame"
	billboardFrame.Size = UDim2.new(1, 0, 1, 0)
	billboardFrame.Position = UDim2.new(0, 0, -0.5, 0)
	billboardFrame.BackgroundTransparency = 1
	billboardFrame.Parent = billboardGui

	local billboardChildRemovedCon = nil
	billboardChildRemovedCon = billboardFrame.ChildRemoved:connect(function()
		if #billboardFrame:GetChildren() <= 1 then
			billboardChildRemovedCon:disconnect()
			billboardGui:Destroy()
		end
	end)

	this:CreateSmallTalkBubble(BubbleColor.WHITE).Parent = billboardFrame

	return billboardGui
end

function this:CreateBillboardGuiHelper(instance, onlyCharacter)
	if instance and not this.CharacterSortedMsg:Get(instance)["BillboardGui"] then
		if not onlyCharacter then
			if instance:IsA("BasePart") then
				-- Create a new billboardGui object attached to this player
				local billboardGui = createBillboardInstance(instance)
				this.CharacterSortedMsg:Get(instance)["BillboardGui"] = billboardGui
				return
			end
		end

		if instance:IsA("Model") then
			local head = instance:FindFirstChild("Head")
			if head and head:IsA("BasePart") then
				-- Create a new billboardGui object attached to this player
				local billboardGui = createBillboardInstance(head)
				this.CharacterSortedMsg:Get(instance)["BillboardGui"] = billboardGui
			end
		end
	end
end

local function distanceToBubbleOrigin(origin)
	if not origin then return 100000 end

	return (origin.Position - game.Workspace.CurrentCamera.CoordinateFrame.p).magnitude
end

local function isPartOfLocalPlayer(adornee)
	if adornee and PlayersService.LocalPlayer.Character then
		return adornee:IsDescendantOf(PlayersService.LocalPlayer.Character)
	end
end

function this:SetBillboardLODNear(billboardGui)
	local isLocalPlayer = isPartOfLocalPlayer(billboardGui.Adornee)
	billboardGui.Size = UDim2.new(0, BILLBOARD_MAX_WIDTH, 0, BILLBOARD_MAX_HEIGHT)
	billboardGui.StudsOffset = Vector3.new(0, isLocalPlayer and 1.5 or 2.5, isLocalPlayer and 2 or 0.1)
	billboardGui.Enabled = true
	local billChildren = billboardGui.BillboardFrame:GetChildren()
	for i = 1, #billChildren do
		billChildren[i].Visible = true
	end
	billboardGui.BillboardFrame.SmallTalkBubble.Visible = false
end

function this:SetBillboardLODDistant(billboardGui)
	local isLocalPlayer = isPartOfLocalPlayer(billboardGui.Adornee)
	billboardGui.Size = UDim2.new(4, 0, 3, 0)
	billboardGui.StudsOffset = Vector3.new(0, 3, isLocalPlayer and 2 or 0.1)
	billboardGui.Enabled = true
	local billChildren = billboardGui.BillboardFrame:GetChildren()
	for i = 1, #billChildren do
		billChildren[i].Visible = false
	end
	billboardGui.BillboardFrame.SmallTalkBubble.Visible = true
end

function this:SetBillboardLODVeryFar(billboardGui)
	billboardGui.Enabled = false
end

function this:SetBillboardGuiLOD(billboardGui, origin)
	if not origin then return end

	if origin:IsA("Model") then
		local head = origin:FindFirstChild("Head")
		if not head then origin = origin.PrimaryPart
		else origin = head end
	end

	local bubbleDistance = distanceToBubbleOrigin(origin)

	if bubbleDistance < NEAR_BUBBLE_DISTANCE then
		this:SetBillboardLODNear(billboardGui)
	elseif bubbleDistance >= NEAR_BUBBLE_DISTANCE and bubbleDistance < MAX_BUBBLE_DISTANCE then
		this:SetBillboardLODDistant(billboardGui)
	else
		this:SetBillboardLODVeryFar(billboardGui)
	end
end

function this:CameraCFrameChanged()
	for index, value in pairs(this.CharacterSortedMsg:GetData()) do
		local playerBillboardGui = value["BillboardGui"]
		if playerBillboardGui then this:SetBillboardGuiLOD(playerBillboardGui, index) end
	end
end

function this:CreateBubbleText(message, shouldAutoLocalize)
	local bubbleText = Instance.new("TextLabel")
	bubbleText.Name = "BubbleText"
	bubbleText.BackgroundTransparency = 1

	if UserFixBubbleChatText then
		bubbleText.Size = UDim2.fromScale(1, 1)
	else
		bubbleText.Position = UDim2.new(0, CHAT_BUBBLE_WIDTH_PADDING / 2, 0, 0)
		bubbleText.Size = UDim2.new(1, -CHAT_BUBBLE_WIDTH_PADDING, 1, 0)
	end

	bubbleText.Font = CHAT_BUBBLE_FONT
	bubbleText.ClipsDescendants = true
	bubbleText.TextWrapped = true
	bubbleText.FontSize = CHAT_BUBBLE_FONT_SIZE
	bubbleText.Text = message
	bubbleText.Visible = false
	bubbleText.AutoLocalize = shouldAutoLocalize

	if UserFixBubbleChatText then
		local padding = Instance.new("UIPadding")
		padding.PaddingTop = UDim.new(0, CHAT_BUBBLE_PADDING)
		padding.PaddingRight = UDim.new(0, CHAT_BUBBLE_PADDING)
		padding.PaddingBottom = UDim.new(0, CHAT_BUBBLE_PADDING)
		padding.PaddingLeft = UDim.new(0, CHAT_BUBBLE_PADDING)
		padding.Parent = bubbleText
	end

	return bubbleText
end

function this:CreateSmallTalkBubble(chatBubbleType)
	local smallTalkBubble = this.ScalingChatBubbleWithTail[chatBubbleType]:Clone()
	smallTalkBubble.Name = "SmallTalkBubble"
	smallTalkBubble.AnchorPoint = Vector2.new(0, 0.5)
	smallTalkBubble.Position = UDim2.new(0, 0, 0.5, 0)
	smallTalkBubble.Visible = false
	local text = this:CreateBubbleText("...")
	text.TextScaled = true
	text.TextWrapped = false
	text.Visible = true
	text.Parent = smallTalkBubble

	return smallTalkBubble
end

function this:UpdateChatLinesForOrigin(origin, currentBubbleYPos)
	local bubbleQueue = this.CharacterSortedMsg:Get(origin).Fifo
	local bubbleQueueSize = bubbleQueue:Size()
	local bubbleQueueData = bubbleQueue:GetData()
	if #bubbleQueueData <= 1 then return end

	for index = (#bubbleQueueData - 1), 1, -1 do
		local value = bubbleQueueData[index]
		local bubble = value.RenderBubble
		if not bubble then return end
		local bubblePos = bubbleQueueSize - index + 1

		if bubblePos > 1 then
			local tail = bubble:FindFirstChild("ChatBubbleTail")
			if tail then tail:Destroy() end
			local bubbleText = bubble:FindFirstChild("BubbleText")
			if bubbleText then bubbleText.TextTransparency = 0.5 end
		end

		local udimValue = UDim2.new( bubble.Position.X.Scale, bubble.Position.X.Offset,
									1, currentBubbleYPos - bubble.Size.Y.Offset - CHAT_BUBBLE_TAIL_HEIGHT)
		bubble:TweenPosition(udimValue, Enum.EasingDirection.Out, Enum.EasingStyle.Bounce, 0.1, true)
		currentBubbleYPos = currentBubbleYPos - bubble.Size.Y.Offset - CHAT_BUBBLE_TAIL_HEIGHT
	end
end

function this:DestroyBubble(bubbleQueue, bubbleToDestroy)
	if not bubbleQueue then return end
	if bubbleQueue:Empty() then return end

	local bubble = bubbleQueue:Front().RenderBubble
	if not bubble then
		bubbleQueue:PopFront()
	 	return
	end

	spawn(function()
		while bubbleQueue:Front().RenderBubble ~= bubbleToDestroy do
			wait()
		end

		bubble = bubbleQueue:Front().RenderBubble

		local timeBetween = 0
		local bubbleText = bubble:FindFirstChild("BubbleText")
		local bubbleTail = bubble:FindFirstChild("ChatBubbleTail")

		while bubble and bubble.ImageTransparency < 1 do
			timeBetween = wait()
			if bubble then
				local fadeAmount = timeBetween * CHAT_BUBBLE_FADE_SPEED
				bubble.ImageTransparency = bubble.ImageTransparency + fadeAmount
				if bubbleText then bubbleText.TextTransparency = bubbleText.TextTransparency + fadeAmount end
				if bubbleTail then bubbleTail.ImageTransparency = bubbleTail.ImageTransparency + fadeAmount end
			end
		end

		if bubble then
			bubble:Destroy()
			bubbleQueue:PopFront()
		end
	end)
end

function this:CreateChatLineRender(instance, line, onlyCharacter, fifo, shouldAutoLocalize)
	if not instance then return end

	if not this.CharacterSortedMsg:Get(instance)["BillboardGui"] then
		this:CreateBillboardGuiHelper(instance, onlyCharacter)
	end

	local billboardGui = this.CharacterSortedMsg:Get(instance)["BillboardGui"]
	if billboardGui then
		local chatBubbleRender = this.ChatBubbleWithTail[line.BubbleColor]:Clone()
		chatBubbleRender.Visible = false
		local bubbleText = this:CreateBubbleText(line.Message, shouldAutoLocalize)

		bubbleText.Parent = chatBubbleRender
		chatBubbleRender.Parent = billboardGui.BillboardFrame

		line.RenderBubble = chatBubbleRender

		local currentTextBounds = TextService:GetTextSize(
				bubbleText.Text, CHAT_BUBBLE_FONT_SIZE_INT, CHAT_BUBBLE_FONT,
				Vector2.new(BILLBOARD_MAX_WIDTH, BILLBOARD_MAX_HEIGHT))
		local numOflines = (currentTextBounds.Y / CHAT_BUBBLE_FONT_SIZE_INT)

		if UserFixBubbleChatText then
			-- Need to use math.ceil to round up on retina displays
			local width = math.ceil(currentTextBounds.X + CHAT_BUBBLE_PADDING * 2)
			local height = numOflines * CHAT_BUBBLE_LINE_HEIGHT

			-- prep chat bubble for tween
			chatBubbleRender.Size = UDim2.fromOffset(0, 0)
			chatBubbleRender.Position = UDim2.fromScale(0.5, 1)

			chatBubbleRender:TweenSizeAndPosition(
				UDim2.fromOffset(width, height),
				UDim2.new(0.5, -width / 2, 1, -height),
				Enum.EasingDirection.Out,
				Enum.EasingStyle.Elastic,
				0.1,
				true,
				function()
					bubbleText.Visible = true
				end
			)

			-- todo: remove when over max bubbles
			this:SetBillboardGuiLOD(billboardGui, line.Origin)
			this:UpdateChatLinesForOrigin(line.Origin, -height)
		else
			local bubbleWidthScale = math.max((currentTextBounds.X + CHAT_BUBBLE_WIDTH_PADDING) / BILLBOARD_MAX_WIDTH, 0.1)

			-- prep chat bubble for tween
			chatBubbleRender.Size = UDim2.new(0, 0, 0, 0)
			chatBubbleRender.Position = UDim2.new(0.5, 0, 1, 0)

			local newChatBubbleOffsetSizeY = numOflines * CHAT_BUBBLE_LINE_HEIGHT

			chatBubbleRender:TweenSizeAndPosition(UDim2.new(bubbleWidthScale, 0, 0, newChatBubbleOffsetSizeY),
													UDim2.new( (1 - bubbleWidthScale) / 2, 0, 1, -newChatBubbleOffsetSizeY),
													Enum.EasingDirection.Out, Enum.EasingStyle.Elastic, 0.1, true,
													function() bubbleText.Visible = true end)

			-- todo: remove when over max bubbles
			this:SetBillboardGuiLOD(billboardGui, line.Origin)
			this:UpdateChatLinesForOrigin(line.Origin, -newChatBubbleOffsetSizeY)
		end

		delay(line.BubbleDieDelay, function()
			this:DestroyBubble(fifo, chatBubbleRender)
		end)
	end
end

function this:OnPlayerChatMessage(sourcePlayer, message, targetPlayer)

	if not this:BubbleChatEnabled() then return end

	local localPlayer = PlayersService.LocalPlayer
	local fromOthers = localPlayer ~= nil and sourcePlayer ~= localPlayer

	local safeMessage = this:SanitizeChatLine(message)

	local line = createPlayerChatLine(sourcePlayer, safeMessage, not fromOthers)

	if sourcePlayer and line.Origin then
		local fifo = this.CharacterSortedMsg:Get(line.Origin).Fifo
		fifo:PushBack(line)
		--Game chat (badges) won't show up here
		this:CreateChatLineRender(sourcePlayer.Character, line, true, fifo, false)
	end
end

function this:OnGameChatMessage(origin, message, color)
	local localPlayer = PlayersService.LocalPlayer
	local fromOthers = localPlayer ~= nil and (localPlayer.Character ~= origin)

	local bubbleColor = BubbleColor.WHITE

	if color == Enum.ChatColor.Blue then bubbleColor = BubbleColor.BLUE
	elseif color == Enum.ChatColor.Green then bubbleColor = BubbleColor.GREEN
	elseif color == Enum.ChatColor.Red then bubbleColor = BubbleColor.RED end

	local safeMessage = this:SanitizeChatLine(message)
	local line = createGameChatLine(origin, safeMessage, not fromOthers, bubbleColor)

	this.CharacterSortedMsg:Get(line.Origin).Fifo:PushBack(line)
	if UserShouldLocalizeGameChatBubble then
		this:CreateChatLineRender(origin, line, false, this.CharacterSortedMsg:Get(line.Origin).Fifo, true)
	else
		this:CreateChatLineRender(origin, line, false, this.CharacterSortedMsg:Get(line.Origin).Fifo, false)
	end
end

function this:BubbleChatEnabled()
	if UserRoactBubbleChatBeta then
		return false
	end
	local clientChatModules = ChatService:FindFirstChild("ClientChatModules")
	if clientChatModules then
		local chatSettings = clientChatModules:FindFirstChild("ChatSettings")
		if chatSettings then
			local chatSettings = require(chatSettings)
			if chatSettings.BubbleChatEnabled ~= nil then
				return chatSettings.BubbleChatEnabled
			end
		end
	end
	return PlayersService.BubbleChat
end

function this:ShowOwnFilteredMessage()
	local clientChatModules = ChatService:FindFirstChild("ClientChatModules")
	if clientChatModules then
		local chatSettings = clientChatModules:FindFirstChild("ChatSettings")
		if chatSettings then
			chatSettings = require(chatSettings)
			return chatSettings.ShowUserOwnFilteredMessage
		end
	end
	return false
end

function findPlayer(playerName)
	for i,v in pairs(PlayersService:GetPlayers()) do
		if v.Name == playerName then
			return v
		end
	end
end

ChatService.Chatted:connect(function(origin, message, color) this:OnGameChatMessage(origin, message, color) end)

local cameraChangedCon = nil
if game.Workspace.CurrentCamera then
	cameraChangedCon = game.Workspace.CurrentCamera:GetPropertyChangedSignal("CFrame"):connect(function(prop) this:CameraCFrameChanged() end)
end

game.Workspace.Changed:connect(function(prop)
	if prop == "CurrentCamera" then
		if cameraChangedCon then cameraChangedCon:disconnect() end
		if game.Workspace.CurrentCamera then
			cameraChangedCon = game.Workspace.CurrentCamera:GetPropertyChangedSignal("CFrame"):connect(function(prop) this:CameraCFrameChanged() end)
		end
	end
end)


local AllowedMessageTypes = nil

function getAllowedMessageTypes()
	if AllowedMessageTypes then
		return AllowedMessageTypes
	end
	local clientChatModules = ChatService:FindFirstChild("ClientChatModules")
	if clientChatModules then
		local chatSettings = clientChatModules:FindFirstChild("ChatSettings")
		if chatSettings then
			chatSettings = require(chatSettings)
			if chatSettings.BubbleChatMessageTypes then
				AllowedMessageTypes = chatSettings.BubbleChatMessageTypes
				return AllowedMessageTypes
			end
		end
		local chatConstants = clientChatModules:FindFirstChild("ChatConstants")
		if chatConstants then
			chatConstants = require(chatConstants)
			AllowedMessageTypes = {chatConstants.MessageTypeDefault, chatConstants.MessageTypeWhisper}
		end
		return AllowedMessageTypes
	end
	return {"Message", "Whisper"}
end

function checkAllowedMessageType(messageData)
	local allowedMessageTypes = getAllowedMessageTypes()
	for i = 1, #allowedMessageTypes do
		if allowedMessageTypes[i] == messageData.MessageType then
			return true
		end
	end
	return false
end

local ChatEvents = ReplicatedStorage:WaitForChild("DefaultChatSystemChatEvents")
local OnMessageDoneFiltering = ChatEvents:WaitForChild("OnMessageDoneFiltering")
local OnNewMessage = ChatEvents:WaitForChild("OnNewMessage")

OnNewMessage.OnClientEvent:connect(function(messageData, channelName)
	if not checkAllowedMessageType(messageData) then
		return
	end

	local sender = findPlayer(messageData.FromSpeaker)
	if not sender then
		return
	end

	if not messageData.IsFiltered or messageData.FromSpeaker == LocalPlayer.Name then
		if messageData.FromSpeaker ~= LocalPlayer.Name or this:ShowOwnFilteredMessage() then
			return
		end
	end

	this:OnPlayerChatMessage(sender, messageData.Message, nil)
end)

OnMessageDoneFiltering.OnClientEvent:connect(function(messageData, channelName)
	if not checkAllowedMessageType(messageData) then
		return
	end

	local sender = findPlayer(messageData.FromSpeaker)
	if not sender then
		return
	end

	if messageData.FromSpeaker == LocalPlayer.Name and not this:ShowOwnFilteredMessage() then
		return
	end

	this:OnPlayerChatMessage(sender, messageData.Message, nil)
end)
