public static CustomText InitText(string text, Color textColor, float fontSize, Vector2 sizeDelta, Vector3 position, Quaternion rotation, Transform parent, TextAnchor textAlign, Material mat = null)
        {
            GameObject newGameObj = new GameObject();
            CustomText tmpText    = newGameObj.AddComponent <CustomText>();

            var scaler = newGameObj.AddComponent <CanvasScaler>();

            scaler.dynamicPixelsPerUnit = Plugin.PixelsPerUnit;

            var shadow = newGameObj.AddComponent <Shadow>();
            //shadow.effectDistance = new Vector2(0.5f, -0.5f);

            var mcs = tmpText.gameObject.AddComponent <ContentSizeManager>();

            mcs.transform.SetParent(tmpText.rectTransform, false);
            mcs.Init();
            var fitter = tmpText.gameObject.AddComponent <ContentSizeFitter>();

            fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;

            tmpText.color = textColor;
            tmpText.rectTransform.SetParent(parent.transform, false);
            tmpText.rectTransform.localPosition = position;
            tmpText.rectTransform.localRotation = rotation;
            tmpText.rectTransform.sizeDelta     = sizeDelta;
            tmpText.rectTransform.pivot         = new Vector2(0, 0);
            tmpText.supportRichText             = true;
            tmpText.text     = text;
            tmpText.font     = cachedSystemFonts[fontUseIndex];
            tmpText.fontSize = 10;

            // For some reason when too many text elems are using the same font asset, shit starts to get wonky.
            // If anyone knows how to fix this without this ghetto shit let me know... would be much appreciated :)
            // (If you want to see what I'm talking about remove this shit below, and go in any asian chat)
            fontUseCount++;
            if (fontUseCount >= MaxFontUsages)
            {
                fontUseCount = 0;
                fontUseIndex++;
            }

            tmpText.verticalOverflow     = VerticalWrapMode.Overflow;
            tmpText.alignment            = textAlign;
            tmpText.horizontalOverflow   = HorizontalWrapMode.Wrap;
            tmpText.resizeTextForBestFit = false;
            tmpText.alignByGeometry      = true;

            if (mat)
            {
                tmpText.material = mat;
            }

            return(tmpText);
        }
        public static void OverlayEmote(CustomText currentMessage, char swapChar, Material noGlowMaterialUI, AnimationController animationController, CachedSpriteData cachedSpriteInfo)
        {
            // Don't even try to overlay an emote if it's not been cached properly
            if (cachedSpriteInfo == null || (cachedSpriteInfo.sprite == null && cachedSpriteInfo.animationInfo == null))
            {
                //Plugin.Log("Sprite was not fully cached!");
                return;
            }

            bool animatedEmote = cachedSpriteInfo.animationInfo != null;

            foreach (int i in Utilities.IndexOfAll(currentMessage.text, Char.ConvertFromUtf32(swapChar)))
            {
                try {
                    if (i > 0 && i < currentMessage.text.Count() - 1 && currentMessage.text[i - 1] == ' ' && currentMessage.text[i + 1] == ' ')
                    {
                        GameObject newGameObject = new GameObject();
                        var        image         = newGameObject.AddComponent <Image>();
                        image.material = noGlowMaterialUI;

                        var shadow = newGameObject.AddComponent <Shadow>();

                        if (animatedEmote)
                        {
                            var animatedImage = newGameObject.AddComponent <AnimatedSprite>();
                            animatedImage.Init(image, cachedSpriteInfo.animationInfo, animationController);
                        }
                        else
                        {
                            image.sprite = cachedSpriteInfo.sprite;
                            image.sprite.texture.wrapMode = TextureWrapMode.Clamp;
                        }

                        image.rectTransform.SetParent(currentMessage.rectTransform, false);
                        image.preserveAspect          = true;
                        image.rectTransform.sizeDelta = new Vector2(7.0f, 7.0f);
                        image.rectTransform.pivot     = new Vector2(0, 0);

                        var textGen = currentMessage.cachedTextGenerator;
                        var pos     = new Vector3(textGen.verts[i * 4 + 3].position.x, textGen.verts[i * 4 + 3].position.y);
                        image.rectTransform.position = currentMessage.gameObject.transform.TransformPoint(pos / Plugin.PixelsPerUnit - new Vector3(image.preferredWidth / Plugin.PixelsPerUnit + 2.5f, image.preferredHeight / Plugin.PixelsPerUnit + 0.7f));
                        currentMessage.emoteRenderers.Add(image);
                    }
                }
                catch (Exception e) {
                    Plugin.Log($"Exception {e.Message} occured when trying to overlay emote at index {i.ToString()}!");
                }
            }
        }
        public static IEnumerator Initialize(Transform parent)
        {
            var        tmpImageSpacing = "\u200A";
            CustomText tmpText         = InitText(tmpImageSpacing, Color.clear, Config.Instance.ChatScale, new Vector2(Config.Instance.ChatWidth, 1), new Vector3(0, -100, 0), new Quaternion(0, 0, 0, 0), parent, TextAnchor.UpperLeft, false);

            yield return(null);

            while (tmpText.preferredWidth < 5.3f)
            {
                tmpText.text += "\u200A";
                yield return(null);
            }
            imageSpacingWidth = tmpText.preferredWidth;
            //Plugin.Log($"Preferred width was {tmpText.preferredWidth.ToString()} with {tmpText.text.Length.ToString()} spaces");
            imageSpacing = tmpText.text;
            GameObject.Destroy(tmpText.gameObject);
        }
        public static void Initialize(Transform parent)
        {
            for (int i = 0; i < Plugin.Instance.Config.MaxMessages + 1; i += MaxFontUsages)
            {
                cachedSystemFonts.Add(LoadSystemFont(Plugin.Instance.Config.FontName));
            }

            CustomText tmpText = InitText(spriteSpacing, Color.white.ColorWithAlpha(0), 10, new Vector2(1000, 1000), new Vector3(0, -100, 0), new Quaternion(0, 0, 0, 0), parent, TextAnchor.MiddleLeft);

            while (tmpText.preferredWidth < 4)
            {
                tmpText.text += " ";
            }
            spriteSpacing = tmpText.text;
            GameObject.Destroy(tmpText.gameObject);
            //Plugin.Log($"Sprite Spacing: {tmpText.preferredWidth.ToString()}, NumSpaces: {spriteSpacing.Count().ToString()}");
        }
        public static CustomText InitText(string text, Color textColor, float fontSize, Vector2 sizeDelta, Vector3 position, Quaternion rotation, Transform parent, TextAnchor textAlign, bool wrapText, Material mat = null)
        {
            GameObject newGameObj = new GameObject("CustomText");
            CustomText tmpText    = newGameObj.AddComponent <CustomText>();

            if (wrapText)
            {
                var mcs = newGameObj.AddComponent <ContentSizeManager>();
                mcs.transform.SetParent(tmpText.rectTransform, false);
                mcs.Init();
                var fitter = newGameObj.AddComponent <ContentSizeFitter>();
                fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
            }
            newGameObj.AddComponent <Shadow>();

            CanvasScaler scaler = newGameObj.AddComponent <CanvasScaler>();

            scaler.dynamicPixelsPerUnit = pixelsPerUnit;
            tmpText.rectTransform.SetParent(parent, false);
            tmpText.rectTransform.localPosition = position;
            tmpText.rectTransform.localRotation = rotation;
            tmpText.rectTransform.pivot         = new Vector2(0, 0);
            tmpText.rectTransform.sizeDelta     = sizeDelta;
            tmpText.supportRichText             = true;
            tmpText.font                 = LoadSystemFont(Config.Instance.FontName);
            tmpText.text                 = text;
            tmpText.fontSize             = 230;
            tmpText.verticalOverflow     = VerticalWrapMode.Overflow;
            tmpText.alignment            = textAlign;
            tmpText.horizontalOverflow   = HorizontalWrapMode.Wrap;
            tmpText.color                = textColor;
            tmpText.material.renderQueue = 3001;
            //tmpText.resizeTextForBestFit = true;

            if (mat)
            {
                tmpText.material = mat;
            }

            return(tmpText);
        }
        public static void OverlayImage(CustomText currentMessage, ImageInfo imageInfo)
        {
            CachedSpriteData cachedTextureData = ImageDownloader.CachedTextures.ContainsKey(imageInfo.textureIndex) ? ImageDownloader.CachedTextures[imageInfo.textureIndex] : null;

            // If cachedTextureData is null, the emote will be overlayed at a later time once it's finished being cached
            if (cachedTextureData == null || (cachedTextureData.sprite == null && cachedTextureData.animInfo == null))
            {
                return;
            }

            bool animatedEmote = cachedTextureData.animInfo != null;

            foreach (int i in Utilities.IndexOfAll(currentMessage.text, Char.ConvertFromUtf32(imageInfo.swapChar)))
            {
                CustomImage image = null, shadow = null;
                try
                {
                    if (i > 0 && i < currentMessage.text.Count())
                    {
                        image                     = ChatHandler.Instance.imagePool.Alloc();
                        image.spriteIndex         = imageInfo.textureIndex;
                        image.imageType           = imageInfo.imageType;
                        image.rectTransform.pivot = new Vector2(0, 0);
                        image.sprite              = cachedTextureData.sprite;
                        image.preserveAspect      = false;
                        if (image.sprite)
                        {
                            image.sprite.texture.wrapMode = TextureWrapMode.Clamp;
                        }

                        image.rectTransform.SetParent(currentMessage.rectTransform, false);

                        float aspectRatio = cachedTextureData.width / cachedTextureData.height;
                        if (aspectRatio > 1)
                        {
                            image.rectTransform.localScale = new Vector3(0.064f * aspectRatio, 0.064f, 0.064f);
                        }
                        else
                        {
                            image.rectTransform.localScale = new Vector3(0.064f, 0.064f, 0.064f);
                        }

                        TextGenerator textGen = currentMessage.cachedTextGenerator;
                        Vector3       pos     = new Vector3(textGen.verts[i * 4 + 3].position.x, textGen.verts[i * 4 + 3].position.y);
                        image.rectTransform.position       = currentMessage.gameObject.transform.TransformPoint(pos / pixelsPerUnit - new Vector3(cachedTextureData.width / pixelsPerUnit + 2.5f, cachedTextureData.height / pixelsPerUnit + 1f) + new Vector3(0, 0, -0.1f));
                        image.rectTransform.localPosition -= new Vector3(imageSpacingWidth / 2.3f, 0);

                        if (animatedEmote)
                        {
                            image.material = cachedTextureData.animInfo.imageMaterial;
                            //image.shadow.enabled = false;
                            if (Config.Instance.DrawShadows)
                            {
                                // Add a shadow to our animated image (the regular unity shadows won't work with this material)
                                shadow                          = ChatHandler.Instance.imagePool.Alloc();
                                shadow.material                 = cachedTextureData.animInfo.shadowMaterial;
                                shadow.sprite                   = null;
                                shadow.spriteIndex              = imageInfo.textureIndex;
                                shadow.imageType                = imageInfo.imageType;
                                shadow.rectTransform.pivot      = new Vector2(0, 0);
                                shadow.rectTransform.localScale = image.rectTransform.localScale;
                                shadow.rectTransform.SetParent(currentMessage.rectTransform, false);
                                shadow.rectTransform.position       = image.rectTransform.position;
                                shadow.rectTransform.localPosition += new Vector3(0.6f, -0.6f, 0.05f);

                                shadow.enabled = true;
                                currentMessage.emoteRenderers.Add(shadow);
                            }
                        }
                        else
                        {
                            image.material = Drawing.noGlowMaterialUI;
                            if (Config.Instance.DrawShadows)
                            {
                                image.shadow.enabled = true;
                            }
                        }
                        image.enabled = true;
                        currentMessage.emoteRenderers.Add(image);
                    }
                }
                catch (Exception e)
                {
                    if (image)
                    {
                        ChatHandler.Instance.imagePool.Free(image);
                    }

                    Plugin.Log($"Exception {e.ToString()} occured when trying to overlay emote at index {i.ToString()}!");
                }
            }
        }