local B=game:GetService("\084\101\120\116\083\101\114\118\105\099\101")local r=game:GetService("\082\117\110\083\101\114\118\105\099\101")local z=0 function getLayerCollector(B)if not B then return nil elseif B:IsA("\076\097\121\101\114\067\111\108\108\101\099\116\111\114")then return B elseif B and B.Parent then return getLayerCollector(B.Parent)else return nil end end function shallowCopy(B)local r={}for B,z in pairs(B)do r[B]=z end return r end function getColorFromString(B)if richText.ColorShortcuts[B]then return richText.ColorShortcuts[B]else local r,z,n=B:match("\040\037\100\043\041\044\040\037\100\043\041\044\040\037\100\043\041")return Color3.new(r/255,z/255,n/255)end end function getVector2FromString(B)local r,z=B:match("\040\037\100\043\041\044\040\037\100\043\041")return Vector2.new(r,z)end function setHorizontalAlignment(B,r)if r=="\076\101\102\116"then B.AnchorPoint=Vector2.new(0,0)B.Position=UDim2.new(0,0,0,0)elseif r=="\067\101\110\116\101\114"then B.AnchorPoint=Vector2.new(.5,0)B.Position=UDim2.new(.5,0,0,0)elseif r=="\082\105\103\104\116"then B.AnchorPoint=Vector2.new(1,0)B.Position=UDim2.new(1,0,0,0)end end function richText.New(x,n,t,J,m,w)for B,r in pairs(n:GetChildren())do r:Destroy()end if m==nil then m=true end local u={}local D={}if w then t=w.Text J=w.StartingProperties end local I={}local O={}local R={}local M=0 local V=false local H=Instance.new("\084\101\120\116\076\097\098\101\108")local q=Instance.new("\073\109\097\103\101\076\097\098\101\108")local i=getLayerCollector(n)local g,A,v,j,C,T function A(B,r)B=propertyShortcuts[B]or B if r=="\047"then if D[B]then r=D[B]else warn("\065\116\116\101\109\112\116 \116\111 \100\101\102\097\117\108\116 \060"..(B.."\062 \116\111 \118\097\108\117\101 \119\105\116\104 \110\111 \100\101\102\097\117\108\116"))end end if tonumber(r)then r=tonumber(r)elseif r=="\102\097\108\115\101"or r=="\116\114\117\101"then r=r=="\116\114\117\101"end u[B]=r if g(B,r)then elseif B=="\067\111\110\116\097\105\110\101\114\072\111\114\105\122\111\110\116\097\108\065\108\105\103\110\109\101\110\116"and I[#I]then setHorizontalAlignment(I[#I].Container,r)elseif defaults[B]then elseif B=="\073\109\103"then C(r)else return false end return true end function g(B,r,z)local n local t=false for z,J in pairs(z and{z}or{H;q})do local m=pcall(function()n=typeof(J[B])end)if m then if n=="\067\111\108\111\114\051"then J[B]=getColorFromString(r)elseif n=="\086\101\099\116\111\114\050"then J[B]=getVector2FromString(r)else J[B]=r end t=true end end return t end for B,r in pairs(defaults)do A(B,r)D[propertyShortcuts[B]or B]=u[propertyShortcuts[B]or B]end for B,r in pairs(J or{})do A(B,r)D[propertyShortcuts[B]or B]=u[propertyShortcuts[B]or B]end if w then u=w.OverflowPickupProperties for B,r in pairs(u)do A(B,r)end end local function X()if u.TextScaled==true then local B if u.TextScaleRelativeTo=="\083\099\114\101\101\110"then B=i.AbsoluteSize.Y elseif u.TextScaleRelativeTo=="\070\114\097\109\101"then B=n.AbsoluteSize.Y end return math.min(u.TextScale*B,100)else return u.TextSize end end local K=0 local function c()local B=I[#I]if B then K=K+B.Size.Y.Offset if not m and K+X()>n.AbsoluteSize.Y then V=true return end end local r=Instance.new("\070\114\097\109\101")r.Name=string.format("\076\105\110\101\037\048\051\100",#I+1)r.Size=UDim2.new(0,0,0,0)r.BackgroundTransparency=1 local z=Instance.new("\070\114\097\109\101",r)z.Name="\067\111\110\116\097\105\110\101\114"z.Size=UDim2.new(0,0,0,0)z.BackgroundTransparency=1 setHorizontalAlignment(z,u.ContainerHorizontalAlignment)r.Parent=n table.insert(I,r)O[#I]={}M=0 end c()local function h(B)R[B]=shallowCopy(u)R[B].InitialSize=B.Size R[B].InitialPosition=B.Position R[B].InitialAnchorPoint=B.AnchorPoint end function v(B,r,z,t)local J=I[#I]local m=tostring(u.TextYAlignment)if m=="\084\111\112"then B.Position=UDim2.new(0,M,0,0)B.AnchorPoint=Vector2.new(0,0)elseif m=="\067\101\110\116\101\114"then B.Position=UDim2.new(0,M,.5,0)B.AnchorPoint=Vector2.new(0,.5)elseif m=="\066\111\116\116\111\109"then B.Position=UDim2.new(0,M,1,0)B.AnchorPoint=Vector2.new(0,1)end M=M+z if M>n.AbsoluteSize.X and not(M==z)then B:Destroy()local r=O[#I][#O[#I]]if r:IsA("\084\101\120\116\076\097\098\101\108")and r.Text==" "then J.Container.Size=UDim2.new(0,(M-z)-r.Size.X.Offset,1,0)r:Destroy()table.remove(O[#I])end c()t()else B.Size=UDim2.new(0,z,0,r)J.Container.Size=UDim2.new(0,M,1,0)J.Size=UDim2.new(1,0,0,math.max(J.Size.Y.Offset,r))B.Name=string.format("\071\114\111\117\112\037\048\051\100",#O[#I]+1)B.Parent=J.Container table.insert(O[#I],B)h(B)u.AnimateYield=0 end end function j(r)if r=="\n"then c()return elseif r==" "and M==0 then return end local z=X()local n=(B:GetTextSize(r,z,H.Font,Vector2.new(i.AbsoluteSize.X,z))).X local t=H:Clone()t.TextScaled=false t.TextSize=z t.Text=r t.TextTransparency=1 t.TextStrokeTransparency=1 t.TextWrapped=false local J=0 local m=1 for n,w in utf8.graphemes(r)do local x=string.sub(r,n,w)local u=(B:GetTextSize(x,z,H.Font,Vector2.new(i.AbsoluteSize.X,z))).X local D=H:Clone()D.Text=x D.TextScaled=false D.TextSize=z D.Position=UDim2.new(0,J,0,0)D.Size=UDim2.new(0,u+1,0,z)D.Name=string.format("\067\104\097\114\037\048\051\100",m)D.Parent=t D.Visible=false h(D)J=J+u m=m+1 end v(t,z,n,function()if not V then j(r)end end)end function C(B)local r=X()local z=r local n=q:Clone()if richText.ImageShortcuts[B]then n.Image=typeof(richText.ImageShortcuts[B])=="\110\117\109\098\101\114"and"\114\098\120\097\115\115\101\116\105\100\058\047\047"..richText.ImageShortcuts[B]or richText.ImageShortcuts[B]else n.Image="\114\098\120\097\115\115\101\116\105\100\058\047\047"..B end n.Size=UDim2.new(0,r,0,z)n.Visible=false v(n,r,z,function()if not V then C(B)end end)end function T(B)for B,r in pairs(B)do local z,n=string.match(r,"\060\040\046\043\041\061\040\046\043\041\062")if z and n then if not A(z,n)then warn("\067\111\117\108\100 \110\111\116 \097\112\112\108\121 \109\097\114\107\117\112\058 ",r)end else j(r)end end end local P local s=1 local Z=#t local N={}if w then s=w.OverflowPickupIndex end while s and s<=Z do local B,r=string.find(t,"\060\046-\062",s)local z,n=string.find(t,"\091 \t\n\093",s)local J,m,w if B and(r and(not z or B<z))then J,m=B,r else J,m=z or Z+1,n or Z+1 w=true end local x=J>s and string.sub(t,s,J-1)or nil local u=J<=Z and string.sub(t,J,m)or nil table.insert(N,x)if w then T(N)if V then break end T({u})if V then s=J break end N={}else table.insert(N,u)end s=m+1 end if not V then T(N)end local W=Instance.new("\085\073\076\105\115\116\076\097\121\111\117\116")W.HorizontalAlignment=u.ContainerHorizontalAlignment W.VerticalAlignment=u.ContainerVerticalAlignment W.Parent=n local E=0 local S=n.AbsoluteSize.X local F=0 for B,r in pairs(I)do E=E+r.Size.Y.Offset local z=r.Container local n,t if z.AnchorPoint.X==0 then n=z.Position.X.Offset t=z.Size.X.Offset elseif z.AnchorPoint.X==.5 then n=r.AbsoluteSize.X/2-z.Size.X.Offset/2 t=r.AbsoluteSize.X/2+z.Size.X.Offset/2 elseif z.AnchorPoint.X==1 then n=r.AbsoluteSize.X-z.Size.X.Offset t=r.AbsoluteSize.X end S=math.min(S,n)F=math.max(F,t)end z=z+1 local a=false local o=false local e=false local k="\084\101\120\116\065\110\105\109\097\116\105\111\110"..z local p={}local function G()if o and#p==0 or a then a=true r:UnbindFromRenderStep(k)p={}return end local B=tick()for r=#p,1,-1 do local z=p[r]local n=z.Settings local t=animationStyles[n.AnimateStyle]if not t then warn("\078\111 \097\110\105\109\097\116\105\111\110 \115\116\121\108\101 \102\111\117\110\100 \102\111\114\058 ",n.AnimateStyle,"\044 \100\101\102\097\117\108\116\105\110\103 \116\111 \065\112\112\101\097\114")t=animationStyles.Appear end local J=math.min((B-z.Start)/n.AnimateStyleTime,1)t(z.Char,J,n)if J>=1 then table.remove(p,r)end end end local function f(B)B.Position=R[B].InitialPosition B.Size=R[B].InitialSize B.AnchorPoint=R[B].InitialAnchorPoint for r,z in pairs(R[B])do g(r,z,B)end end local function d(B,r)B.Visible=r for B,z in pairs(B:GetChildren())do z.Visible=r if r then f(z)end end if r and B:IsA("\073\109\097\103\101\076\097\098\101\108")then f(B)end end local function b(B)a=false r:BindToRenderStep(k,Enum.RenderPriority.Last.Value,G)local z local n local t local J for B,r in pairs(O)do for B,r in pairs(r)do d(r,false)end end local function m(B,r)table.insert(p,{Char=B;Settings=r,Start=tick()})end local function w()if not e and(J%t==0 and n>=0)then local B=n>0 and n or nil wait(B)end end for B,r in pairs(O)do for B,r in pairs(r)do local x=R[r]if not(x.AnimateStepGrouping==z)or not(x.AnimateStepFrequency==t)then J=0 end z=x.AnimateStepGrouping n=x.AnimateStepTime t=x.AnimateStepFrequency if x.AnimateYield>0 then wait(x.AnimateYield)end if z=="\087\111\114\100"or z=="\065\108\108"then if r:IsA("\084\101\120\116\076\097\098\101\108")then r.Visible=true for B,r in pairs(r:GetChildren())do m(r,R[r])end else m(r,x)end if z=="\087\111\114\100"then J=J+1 w()end elseif z=="\076\101\116\116\101\114"then if r:IsA("\084\101\120\116\076\097\098\101\108")then r.Visible=true local B=r.Text local z=1 while true do local B=script.NPCSound:Clone()B.Parent=game.Players.LocalPlayer.PlayerGui B:Play();(game:GetService("\068\101\098\114\105\115")):AddItem(B,.3)local n=r:FindFirstChild(string.format("\067\104\097\114\037\048\051\100",z))if not n then break end m(n,R[n])J=J+1 w()if a then return end z=z+1 end else m(r,x)J=J+1 w()end else warn("\073\110\118\097\108\105\100 \115\116\101\112 \103\114\111\117\112\105\110\103\058 ",z)end if a then return end end end o=true if B then while#p>0 do r.RenderStepped:Wait()end end end local l={}l.Overflown=V l.OverflowPickupIndex=s l.StartingProperties=J l.OverflowPickupProperties=u l.Text=t if w then w.NextTextObject=l end l.ContentSize=Vector2.new(F-S,E)function l.Animate(r,B)if B then b()else(coroutine.wrap(b))()end if r.NextTextObject then r.NextTextObject:Animate(B)end end function l.Show(r,B)if B then e=true else a=true for B,r in pairs(O)do for B,r in pairs(r)do d(r,true)end end end if r.NextTextObject then r.NextTextObject:Show(B)end end function l.Hide(B)a=true for B,r in pairs(O)do for B,r in pairs(r)do d(r,false)end end if B.NextTextObject then B.NextTextObject:Hide()end end return l end function richText.ContinueOverflow(z,B,r)return richText:New(B,nil,nil,false,r)end return richText


local textService = game:GetService("TextService")
local runService = game:GetService("RunService")
local animationCount = 0

function getLayerCollector(frame)
	if not frame then
		return nil
	elseif frame:IsA("LayerCollector") then
		return frame
	elseif frame and frame.Parent then
		return getLayerCollector(frame.Parent)
	else
		return nil
	end
end

function shallowCopy(tab)
	local ret = {}
	for key, value in pairs(tab) do
		ret[key] = value
	end
	return ret
end

function getColorFromString(value)
	if richText.ColorShortcuts[value] then
		 return richText.ColorShortcuts[value]
	else
		local r, g, b = value:match("(%d+),(%d+),(%d+)")
		return Color3.new(r / 255, g / 255, b / 255)
	end
end

function getVector2FromString(value)
	local x, y = value:match("(%d+),(%d+)")
	return Vector2.new(x, y)
end

function setHorizontalAlignment(frame, alignment)
	if alignment == "Left" then
		frame.AnchorPoint = Vector2.new(0, 0)
		frame.Position = UDim2.new(0, 0, 0, 0)
	elseif alignment == "Center" then
		frame.AnchorPoint = Vector2.new(0.5, 0)
		frame.Position = UDim2.new(0.5, 0, 0, 0)
	elseif alignment == "Right" then
		frame.AnchorPoint = Vector2.new(1, 0)
		frame.Position = UDim2.new(1, 0, 0, 0)
	end
end

function richText:New(frame, text, startingProperties, allowOverflow, prevTextObject)
	for _, v in pairs(frame:GetChildren()) do
		v:Destroy()
	end
	if allowOverflow == nil then
		allowOverflow = true
	end
	
	local properties = {}
	local defaultProperties = {}
	if prevTextObject then
		text = prevTextObject.Text
		startingProperties = prevTextObject.StartingProperties
	end
	
	local lineFrames = {}
	local textFrames = {}
	local frameProperties = {}
	local linePosition = 0
	local overflown = false
	local textLabel = Instance.new("TextLabel")
	local imageLabel = Instance.new("ImageLabel")
	local layerCollector = getLayerCollector(frame)
	
	local applyProperty, applyMarkup, formatLabel, printText, printImage, printSeries
	
	----- Apply properties / markups -----
	function applyMarkup(key, value)
		--print(key,value)
		key = propertyShortcuts[key] or key
		if value == "/" then
			if defaultProperties[key] then
				value = defaultProperties[key]
			else
				warn("Attempt to default <"..key.."> to value with no default")
			end
		end
		if tonumber(value) then
			value = tonumber(value)
		elseif value == "false" or value == "true" then
			value = value == "true"
		end
		properties[key] = value
		
		if applyProperty(key, value) then
			-- Ok
		elseif key == "ContainerHorizontalAlignment" and lineFrames[#lineFrames] then
			setHorizontalAlignment(lineFrames[#lineFrames].Container, value)
		elseif defaults[key] then
			-- Ok
		elseif key == "Img" then
			printImage(value)
		else
			-- Unknown value
			return false	
		end
		return true
	end
	
	function applyProperty(name, value, frame)
		local propertyType
		local ret = false
		for _, label in pairs(frame and {frame} or {textLabel, imageLabel}) do
			local isProperty = pcall(function() propertyType = typeof(label[name]) end) -- is there a better way to check if it's a property?
			if isProperty then
				if propertyType == "Color3" then
					label[name] = getColorFromString(value)
				elseif propertyType == "Vector2" then
					label[name] = getVector2FromString(value)
				else
					label[name] = value	
				end
				ret = true	
			end
		end
		return ret
	end
	
	----- Set up default properties -----
	for name, value in pairs(defaults) do
		applyMarkup(name, value)
		defaultProperties[propertyShortcuts[name] or name] = properties[propertyShortcuts[name] or name]
	end
	for name, value in pairs(startingProperties or {}) do
		applyMarkup(name, value)
		defaultProperties[propertyShortcuts[name] or name] = properties[propertyShortcuts[name] or name]
	end
	
	if prevTextObject then
		properties = prevTextObject.OverflowPickupProperties
		for name, value in pairs(properties) do
			applyMarkup(name, value)
		end
	end
	
	----- Get vertical size -----
	local function getTextSize()
		if properties.TextScaled == true then
			local relativeHeight
			if properties.TextScaleRelativeTo == "Screen" then
				relativeHeight = layerCollector.AbsoluteSize.Y
			elseif properties.TextScaleRelativeTo == "Frame" then
				relativeHeight = frame.AbsoluteSize.Y
			end
			return math.min(properties.TextScale * relativeHeight, 100)
		else
			return properties.TextSize
		end
	end
	
	----- Lines -----
	local contentHeight = 0
	local function newLine()
		local lastLineFrame = lineFrames[#lineFrames]
		if lastLineFrame then
			contentHeight = contentHeight + lastLineFrame.Size.Y.Offset
			if not allowOverflow and contentHeight + getTextSize() > frame.AbsoluteSize.Y then
				overflown = true
				return
			end
		end
		local lineFrame = Instance.new("Frame")
		lineFrame.Name = string.format("Line%03d", #lineFrames + 1)
		lineFrame.Size = UDim2.new(0, 0, 0, 0)
		lineFrame.BackgroundTransparency = 1		
		local textContainer = Instance.new("Frame", lineFrame)
		textContainer.Name = "Container"
		textContainer.Size = UDim2.new(0, 0, 0, 0)
		textContainer.BackgroundTransparency = 1
		setHorizontalAlignment(textContainer, properties.ContainerHorizontalAlignment)
		lineFrame.Parent = frame
		table.insert(lineFrames, lineFrame)
		textFrames[#lineFrames] = {}
		linePosition = 0
	end
	newLine()
	
	----- Label printing -----
	local function addFrameProperties(frame)
		frameProperties[frame] = shallowCopy(properties)
		frameProperties[frame].InitialSize = frame.Size
		frameProperties[frame].InitialPosition = frame.Position
		frameProperties[frame].InitialAnchorPoint = frame.AnchorPoint
	end
	
	function formatLabel(newLabel, labelHeight, labelWidth, endOfLineCallback)
		local lineFrame = lineFrames[#lineFrames]
		
		local verticalAlignment = tostring(properties.TextYAlignment)
		if verticalAlignment  == "Top" then
			newLabel.Position = UDim2.new(0, linePosition, 0, 0)
			newLabel.AnchorPoint = Vector2.new(0, 0)
		elseif verticalAlignment  == "Center" then
			newLabel.Position = UDim2.new(0, linePosition, 0.5, 0)
			newLabel.AnchorPoint = Vector2.new(0, 0.5)
		elseif verticalAlignment  == "Bottom" then
			newLabel.Position = UDim2.new(0, linePosition, 1, 0)
			newLabel.AnchorPoint = Vector2.new(0, 1)
		end
		
		linePosition = linePosition + labelWidth
		if linePosition > frame.AbsoluteSize.X and not (linePosition == labelWidth) then
			-- Newline, get rid of label and retry it on the next line
			newLabel:Destroy()
			local lastLabel = textFrames[#lineFrames][#textFrames[#lineFrames]]
			if lastLabel:IsA("TextLabel") and lastLabel.Text == " " then -- get rid of trailing space
				lineFrame.Container.Size = UDim2.new(0, linePosition - labelWidth - lastLabel.Size.X.Offset, 1, 0)
				lastLabel:Destroy()
				table.remove(textFrames[#lineFrames])
			end
			newLine()
			endOfLineCallback()
		else
			-- Label is ok
			newLabel.Size = UDim2.new(0, labelWidth, 0, labelHeight)
			lineFrame.Container.Size = UDim2.new(0, linePosition, 1, 0)
			lineFrame.Size = UDim2.new(1, 0, 0, math.max(lineFrame.Size.Y.Offset, labelHeight))
			newLabel.Name = string.format("Group%03d", #textFrames[#lineFrames] + 1)
			newLabel.Parent = lineFrame.Container
			table.insert(textFrames[#lineFrames], newLabel)
			addFrameProperties(newLabel)
			properties.AnimateYield = 0
		end
	end
	
	function printText(text)
		if text == "\n" then
			newLine()
			return
		elseif text == " " and linePosition == 0 then
			return -- no leading spaces
		end
		
		local textSize = getTextSize()
		local textWidth = textService:GetTextSize(text, textSize, textLabel.Font, Vector2.new(layerCollector.AbsoluteSize.X, textSize)).X
		
		local newTextLabel = textLabel:Clone()
		newTextLabel.TextScaled = false
		newTextLabel.TextSize = textSize
		newTextLabel.Text = text -- This text is never actually displayed. We just use it as a reference for knowing what the group string is.
		newTextLabel.TextTransparency = 1
		newTextLabel.TextStrokeTransparency = 1
		newTextLabel.TextWrapped = false
		
		-- Keep the real text in individual frames per character:
		local charPos = 0
		local i = 1
		for first, last in utf8.graphemes(text) do
			local character = string.sub(text, first, last)
			local characterWidth = textService:GetTextSize(character, textSize, textLabel.Font, Vector2.new(layerCollector.AbsoluteSize.X, textSize)).X
			local characterLabel = textLabel:Clone()
			characterLabel.Text = character
			characterLabel.TextScaled = false
			characterLabel.TextSize = textSize
			characterLabel.Position = UDim2.new(0, charPos, 0, 0)
			characterLabel.Size = UDim2.new(0, characterWidth + 1, 0, textSize)
			characterLabel.Name = string.format("Char%03d", i)
			characterLabel.Parent = newTextLabel
			characterLabel.Visible = false
			
			
			
			addFrameProperties(characterLabel)
			charPos = charPos + characterWidth
			i = i + 1
		end

		formatLabel(newTextLabel, textSize, textWidth, function() if not overflown then printText(text) end end)
	end
	
	function printImage(imageId)
		local imageHeight = getTextSize()
		local imageWidth = imageHeight -- Would be nice if we could get aspect ratio of image to get width properly.
		
		local newImageLabel = imageLabel:Clone()
		
		if richText.ImageShortcuts[imageId] then
			newImageLabel.Image = typeof(richText.ImageShortcuts[imageId]) == "number" and "rbxassetid://"..richText.ImageShortcuts[imageId] or richText.ImageShortcuts[imageId]
		else
			newImageLabel.Image = "rbxassetid://"..imageId
		end
		newImageLabel.Size = UDim2.new(0, imageHeight, 0, imageWidth)
		newImageLabel.Visible = false

		formatLabel(newImageLabel, imageHeight, imageWidth, function() if not overflown then printImage(imageId) end end)
	end
	
	function printSeries(labelSeries)	
		for _, t in pairs(labelSeries) do
			local markupKey, markupValue = string.match(t, "<(.+)=(.+)>")
			if markupKey and markupValue then
				if not applyMarkup(markupKey, markupValue) then
					warn("Could not apply markup: ", t)
				end			
			else
				printText(t)
			end
		end	
	end
	
	----- Text traversal + parsing -----
	local overflowText
	local textPos = 1
	local textLength = #text
	local labelSeries = {}
	
	if prevTextObject then
		textPos = prevTextObject.OverflowPickupIndex
	end
	
	while textPos and textPos <= textLength do
		local nextMarkupStart, nextMarkupEnd = string.find(text, "<.->", textPos)
		local nextSpaceStart, nextSpaceEnd = string.find(text, "[ \t\n]", textPos)
		
		local nextBreakStart, nextBreakEnd, breakIsWhitespace
		if nextMarkupStart and nextMarkupEnd and (not nextSpaceStart or nextMarkupStart < nextSpaceStart) then
			nextBreakStart, nextBreakEnd = nextMarkupStart, nextMarkupEnd
		else
			nextBreakStart, nextBreakEnd = nextSpaceStart or textLength + 1, nextSpaceEnd or textLength + 1
			breakIsWhitespace = true
		end
		
		local nextWord = nextBreakStart > textPos and string.sub(text, textPos, nextBreakStart - 1) or nil
		local nextBreak = nextBreakStart <= textLength and string.sub(text, nextBreakStart, nextBreakEnd) or nil
		table.insert(labelSeries, nextWord)
		
		if breakIsWhitespace then
			printSeries(labelSeries)
			if overflown then
				break
			end
			printSeries({nextBreak})
			if overflown then
				textPos = nextBreakStart
				break
			end
			labelSeries = {}
		else
			table.insert(labelSeries, nextBreak)
		end
		
		textPos = nextBreakEnd + 1
		--textPos = utf8.offset(text, 2, nextBreakEnd)
	end
	
	if not overflown then
		printSeries(labelSeries)
	end

	----- Alignment layout -----
	local listLayout = Instance.new("UIListLayout")
	listLayout.HorizontalAlignment = properties.ContainerHorizontalAlignment
	listLayout.VerticalAlignment = properties.ContainerVerticalAlignment
	listLayout.Parent = frame
	
	----- Calculate content size -----
	local contentHeight = 0
	local contentLeft = frame.AbsoluteSize.X
	local contentRight = 0
	for _, lineFrame in pairs(lineFrames) do
		contentHeight = contentHeight + lineFrame.Size.Y.Offset
		local container = lineFrame.Container
		local left, right
		if container.AnchorPoint.X == 0 then
			left = container.Position.X.Offset
			right = container.Size.X.Offset
		elseif container.AnchorPoint.X == 0.5 then
			left = lineFrame.AbsoluteSize.X / 2 - container.Size.X.Offset / 2
			right = lineFrame.AbsoluteSize.X / 2 + container.Size.X.Offset / 2
		elseif container.AnchorPoint.X == 1 then
			left = lineFrame.AbsoluteSize.X - container.Size.X.Offset
			right = lineFrame.AbsoluteSize.X
		end
		contentLeft = math.min(contentLeft, left)
		contentRight = math.max(contentRight, right)
	end
	
	----- Animation -----
	animationCount = animationCount + 1
	local animationDone = false
	local allTextReached = false
	local overrideYield = false
	local animationRenderstepBinding = "TextAnimation"..animationCount
	local animateQueue = {}
	
	local function updateAnimations()
		if allTextReached and #animateQueue == 0 or animationDone then
			animationDone = true
			runService:UnbindFromRenderStep(animationRenderstepBinding)
			animateQueue = {}
			return
		end

		local t = tick()
		for i = #animateQueue, 1, -1 do
			local set = animateQueue[i]
			local properties = set.Settings
			local animateStyle = animationStyles[properties.AnimateStyle]
			if not animateStyle then
				warn("No animation style found for: ", properties.AnimateStyle, ", defaulting to Appear")
				animateStyle = animationStyles.Appear
			end
			local animateAlpha = math.min((t - set.Start) / properties.AnimateStyleTime, 1)
			animateStyle(set.Char, animateAlpha, properties) 
			if animateAlpha >= 1 then
				table.remove(animateQueue, i)
			end
		end
	end
	
	local function setFrameToDefault(frame)
		frame.Position = frameProperties[frame].InitialPosition
		frame.Size = frameProperties[frame].InitialSize
		frame.AnchorPoint = frameProperties[frame].InitialAnchorPoint
		for name, value in pairs(frameProperties[frame]) do
			applyProperty(name, value, frame)
		end
	end

	local function setGroupVisible(frame, visible)
		frame.Visible = visible
		for _, v in pairs(frame:GetChildren()) do
			v.Visible = visible
			if visible then
				setFrameToDefault(v)
			end
		end
		if visible and frame:IsA("ImageLabel") then
			setFrameToDefault(frame)
		end
	end

	local function animate(waitForAnimationToFinish)
		animationDone = false
		runService:BindToRenderStep(animationRenderstepBinding, Enum.RenderPriority.Last.Value, updateAnimations)

		local stepGrouping
		local stepTime
		local stepFrequency
		local numAnimated
		
		-- Make everything invisible to start
		for lineNum, list in pairs(textFrames) do
			for _, frame in pairs(list) do
				setGroupVisible(frame, false)
			end
		end
	
		local function animateCharacter(char, properties)
			table.insert(animateQueue, {Char = char, Settings = properties, Start = tick()})
		end
		
		local function yield()
			if not overrideYield and numAnimated % stepFrequency == 0 and stepTime >= 0 then
				local yieldTime = stepTime > 0 and stepTime or nil
				wait(yieldTime)
			end
		end
		
		for lineNum, list in pairs(textFrames) do
			for _, frame in pairs(list) do
				local properties = frameProperties[frame]
				if not (properties.AnimateStepGrouping  == stepGrouping) or not (properties.AnimateStepFrequency == stepFrequency) then
					numAnimated = 0
				end
				stepGrouping = properties.AnimateStepGrouping
				stepTime = properties.AnimateStepTime
				stepFrequency = properties.AnimateStepFrequency
			
				if properties.AnimateYield > 0 then
					wait(properties.AnimateYield)
				end
				
				if stepGrouping == "Word" or stepGrouping == "All" then
					--if not (frame:IsA("TextLabel") and (frame.Text == " ")) then
					
						
						if frame:IsA("TextLabel") then
							frame.Visible = true
							
							for _, v in pairs(frame:GetChildren()) do
								animateCharacter(v, frameProperties[v])
							end
						else
							animateCharacter(frame, properties)
						end
						if stepGrouping == "Word" then
							numAnimated = numAnimated + 1
							yield()
						end
					--end
				elseif stepGrouping == "Letter" then
					if frame:IsA("TextLabel") --[[and not (frame.Text == " ") ]]then
						
						
						frame.Visible = true
						local text = frame.Text
						local i = 1
						while true do
							local sound = script.NPCSound:Clone()
						sound.Parent = game.Players.LocalPlayer.PlayerGui
						sound:Play()
						game:GetService("Debris"):AddItem(sound,.3)
						
							local v = frame:FindFirstChild(string.format("Char%03d", i))
							if not v then
								break
							end
							animateCharacter(v, frameProperties[v])
							numAnimated = numAnimated + 1
							yield()
							if animationDone then
								return
							end
							i = i + 1
						end
					else
						animateCharacter(frame, properties)
						numAnimated = numAnimated + 1
						yield()
					end
				else
					warn("Invalid step grouping: ", stepGrouping)
				end
				
				if animationDone then
					return
				end
			end
		end
			
		allTextReached = true
		
		if waitForAnimationToFinish then
			while #animateQueue > 0 do
				runService.RenderStepped:Wait()
			end
		end	
	end
	


	local textObject = {}
	
	----- Overflowing -----
	
	textObject.Overflown = overflown
	textObject.OverflowPickupIndex = textPos
	textObject.StartingProperties = startingProperties
	textObject.OverflowPickupProperties = properties
	textObject.Text = text

	if prevTextObject then
		prevTextObject.NextTextObject = textObject
	end
	
	-- to overflow: check if textObject.Overflown, then use richText:ContinueOverflow(newFrame, textObject) to continue to another frame.
	
	
	----- Return object API -----

	textObject.ContentSize = Vector2.new(contentRight - contentLeft, contentHeight)

	function textObject:Animate(yield)
		if yield then
			animate()
		else
			coroutine.wrap(animate)()
		end
		if self.NextTextObject then
			self.NextTextObject:Animate(yield)
		end
	end
	
	function textObject:Show(finishAnimation)
		if finishAnimation then
			overrideYield = true
		else
			animationDone = true
			for lineNum, list in pairs(textFrames) do
				for _, frame in pairs(list) do
					setGroupVisible(frame, true)
				end
			end
		end
		if self.NextTextObject then
			self.NextTextObject:Show(finishAnimation)
		end
	end
	
	function textObject:Hide()
		animationDone = true
		for lineNum, list in pairs(textFrames) do
			for _, frame in pairs(list) do
				setGroupVisible(frame, false)
			end
		end
		if self.NextTextObject then
			self.NextTextObject:Hide()
		end
	end
	
	return textObject
end


function richText:ContinueOverflow(newFrame, prevTextObject)
	return richText:New(newFrame, nil, nil, false, prevTextObject)
end


return richText
