private void AppendLabel(string Label, string Value, StringBuilder Markdown) { bool First = true; Markdown.Append("| "); Markdown.Append(MarkdownDocument.Encode(Label)); Markdown.Append(" | "); if (Value.Length > 2 && Value.StartsWith(":") && Value.EndsWith(":") && EmojiUtilities.TryGetEmoji(Value.Substring(1, Value.Length - 2), out EmojiInfo _)) { Markdown.Append(Value); } else { foreach (string Row in Value.Trim().Replace("\r\n", "\n").Replace("\r", "\n").Split('\n')) { if (First) { First = false; } else { Markdown.Append("<br/>"); } Markdown.Append(MarkdownDocument.Encode(Row)); } } Markdown.AppendLine(" |"); }
public async Task JumboAsync( [Summary("The emoji to jumbofy.")] string emoji) { var emojiUrl = EmojiUtilities.GetUrl(emoji); try { var client = HttpClientFactory.CreateClient(); var req = await client.GetStreamAsync(emojiUrl); await Context.Channel.SendFileAsync(req, Path.GetFileName(emojiUrl), Context.User.Mention); try { await Context.Message.DeleteAsync(); } catch (HttpRequestException ex) { Log.Warning(ex, "Couldn't delete message after jumbofying."); } } catch (HttpRequestException ex) { Log.Warning(ex, "Failed jumbofying emoji"); await ReplyAsync($"Sorry {Context.User.Mention}, I don't recognize that emoji."); } }
private static void ProcessingThread(byte[] gifData, GifInfo frameInfo) { var gifImage = EmojiUtilities.byteArrayToImage(gifData); var dimension = new System.Drawing.Imaging.FrameDimension(gifImage.FrameDimensionsList[0]); int frameCount = gifImage.GetFrameCount(dimension); frameInfo.frameCount = frameCount; frameInfo.initialized = true; int index = 0; int firstDelayValue = -1; for (int i = 0; i < frameCount; i++) { gifImage.SelectActiveFrame(dimension, i); System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(gifImage.Width, gifImage.Height); System.Drawing.Graphics.FromImage(bitmap).DrawImage(gifImage, System.Drawing.Point.Empty); LockBitmap frame = new LockBitmap(bitmap); frame.LockBits(); FrameInfo currentFrame = new FrameInfo(bitmap.Width, bitmap.Height); if (currentFrame.colors == null) { currentFrame.colors = new Color32[frame.Height * frame.Width]; } for (int x = 0; x < frame.Width; x++) { for (int y = 0; y < frame.Height; y++) { System.Drawing.Color sourceColor = frame.GetPixel(x, y); currentFrame.colors[(frame.Height - y - 1) * frame.Width + x] = new Color32(sourceColor.R, sourceColor.G, sourceColor.B, sourceColor.A); } } int delayPropertyValue = BitConverter.ToInt32(gifImage.GetPropertyItem(20736).Value, index); if (firstDelayValue == -1) { firstDelayValue = delayPropertyValue; } if (delayPropertyValue != firstDelayValue) { frameInfo.isDelayConsistent = false; } currentFrame.delay = delayPropertyValue * 10; frameInfo.frames.Add(currentFrame); index += 4; Thread.Sleep(Globals.IsAtMainMenu ? 0 : 10); } }
private static void ProcessingThread(byte[] gifData, GifInfo frameInfo) { var gifImage = EmojiUtilities.byteArrayToImage(gifData); var dimension = new System.Drawing.Imaging.FrameDimension(gifImage.FrameDimensionsList[0]); int frameCount = gifImage.GetFrameCount(dimension); frameInfo.frameCount = frameCount; frameInfo.dimension = dimension; frameInfo.initialized = true; int index = 0; for (int i = 0; i < frameCount; i++) { gifImage.SelectActiveFrame(dimension, i); System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(gifImage.Width, gifImage.Height); System.Drawing.Graphics.FromImage(bitmap).DrawImage(gifImage, System.Drawing.Point.Empty); LockBitmap frame = new LockBitmap(bitmap); frame.LockBits(); FrameInfo currentFrame = new FrameInfo(bitmap.Width, bitmap.Height); if (currentFrame.colors == null) { currentFrame.colors = new Color32[frame.Height * frame.Width]; } for (int x = 0; x < frame.Width; x++) { for (int y = 0; y < frame.Height; y++) { System.Drawing.Color sourceColor = frame.GetPixel(x, y); currentFrame.colors[(frame.Height - y - 1) * frame.Width + x] = new Color32(sourceColor.R, sourceColor.G, sourceColor.B, sourceColor.A); } } int delayPropertyValue = BitConverter.ToInt32(gifImage.GetPropertyItem(20736).Value, index); // If the delay property is 0, assume that it's a 10fps emote if (delayPropertyValue == 0) { delayPropertyValue = 10; } currentFrame.delay = (float)delayPropertyValue / 100.0f; frameInfo.frames.Add(currentFrame); index += 4; Thread.Sleep(0); } }
public static void OverlayImage(CustomText currentMessage, ImageInfo imageInfo) { CachedSpriteData cachedTextureData = imageInfo.cachedSprite; // 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; var messageWithoutHtml = htmlRegex.Replace(currentMessage.text, (m) => m.Groups[1].Value); foreach (int i in EmojiUtilities.IndexOfAll(messageWithoutHtml, Char.ConvertFromUtf32(imageInfo.swapChar))) { CustomImage image = null, shadow = null; try { if (i > 0 && i < currentMessage.text.Length) { image = ChatHandler.Instance.imagePool.Alloc(); image.cachedTextureData = cachedTextureData; image.spriteIndex = imageInfo.textureIndex; image.imageType = imageInfo.imageType; image.sprite = cachedTextureData.sprite; image.preserveAspect = false; if (image.sprite) { image.sprite.texture.wrapMode = TextureWrapMode.Clamp; } image.rectTransform.SetParent(currentMessage.rectTransform, false); image.rectTransform.localScale = new Vector3(0.064f, 0.064f, 0.064f); image.rectTransform.sizeDelta = new Vector2(cachedTextureData.width, cachedTextureData.height); if (imageInfo.imageType == ImageType.Emoji || imageInfo.imageType == ImageType.Badge) { image.rectTransform.pivot = new Vector2(0, 0.21f); } else { image.rectTransform.pivot = new Vector2(0, 0.3f); } TextGenerator textGen = currentMessage.cachedTextGenerator; Vector3 pos = new Vector3(textGen.verts[i * 4].position.x, textGen.verts[i * 4].position.y); image.rectTransform.position = currentMessage.gameObject.transform.TransformPoint(pos / pixelsPerUnit + new Vector3(0, 0, -0.1f)); if (animatedEmote) { cachedTextureData.animInfo?.animData?.IncRefs(); image.material = cachedTextureData.animInfo.imageMaterial; if (ChatConfig.instance.DrawShadows && cachedTextureData.animInfo.shadowMaterial != null) { // 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 = noGlowMaterialUI; if (ChatConfig.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()}!"); } } }
public static void Parse(ChatMessage chatMessage) { _messageBuilder.Clear(); // Emojis chatMessage.displayMsg = EmojiUtilities.emojiRegex.Replace(chatMessage.displayMsg, (m) => { string emojiIndex = EmojiUtilities.WebParseEmojiRegExMatchEvaluator(m); string replaceString = m.Value; // Couldn't find a match, replace it with a space if (emojiIndex == String.Empty) { return(" "); } emojiIndex += ".png"; if (!ImageDownloader.CachedTextures.ContainsKey(emojiIndex)) { ImageDownloader.Instance.Queue(new TextureDownloadInfo(emojiIndex, ImageType.Emoji, chatMessage.origMessage.id)); } if (!_emojiInfoByName.TryGetValue(emojiIndex, out var emojiInfo)) { emojiInfo = new EmoteInfo() { imageType = ImageType.Emoji, swapChar = imgPlacementDesignator++, swapString = replaceString, textureIndex = emojiIndex }; _emojiInfoByName[emojiIndex] = emojiInfo; } chatMessage.parsedEmotes[emojiInfo.textureIndex] = emojiInfo; var cachedSprite = emojiInfo.GetCachedSprite(); return($"\ufeff{Char.ConvertFromUtf32(emojiInfo.swapChar)}{emojiInfo.spacingString}"); }); _messageBuilder.Append(chatMessage.displayMsg); bool isActionMessage = false; if (chatMessage.origMessage is TwitchMessage) { chatMessage.origMessage.Twitch.ParseEmotes(ref imgPlacementDesignator, ref _messageBuilder, out isActionMessage, chatMessage); } else if (chatMessage.origMessage is YouTubeMessage) { chatMessage.origMessage.YouTube.Parse(ref imgPlacementDesignator, ref _messageBuilder, chatMessage); } else if (chatMessage.origMessage is BilibiliMessage) { /*Plugin.Log("Is bilibli message!");*/ chatMessage.origMessage.Bilibili.Parse(ref imgPlacementDesignator, ref _messageBuilder, chatMessage); } /*Plugin.Log("Parse 1");*/ string displayColor = ""; try { displayColor = chatMessage.origMessage.user.color; } catch (Exception ex) { Plugin.Log(ex.ToString()); } /*Plugin.Log("displayColor: " + displayColor);*/ int nameHash = chatMessage.origMessage.user.displayName.GetHashCode(); /*Plugin.Log("nameHash: " + nameHash); * Plugin.Log("Parse 2");*/ // If the user doesn't already have a displayColor, generate one and store it for later user if (string.IsNullOrEmpty(displayColor) && !_userColors.TryGetValue(nameHash, out displayColor)) { // Generate a random color Random rand = new Random(nameHash); int r = rand.Next(255); int g = rand.Next(255); int b = rand.Next(255); // Convert it to a pastel color System.Drawing.Color pastelColor = Drawing.GetPastelShade(System.Drawing.Color.FromArgb(255, r, g, b)); int argb = ((int)pastelColor.R << 16) + ((int)pastelColor.G << 8) + (int)pastelColor.B; string colorString = String.Format("#{0:X6}", argb) + "FF"; _userColors.Add(nameHash, colorString); displayColor = colorString; } /*Plugin.Log("Parse 3");*/ // Set the final displayColor to our message chatMessage.displayColor = displayColor; /*Plugin.Log("Parse 4");*/ _messageBuilder.Insert(0, $"<color={chatMessage.displayColor}><b>{chatMessage.origMessage.user.displayName}</b>{(!isActionMessage ? "</color>:" : "")} "); _messageBuilder.Append($"{(isActionMessage ? "</color>" : "")}"); /*Plugin.Log("Parse 5");*/ if (chatMessage.origMessage is TwitchMessage) { chatMessage.origMessage.Twitch.ParseBadges(ref imgPlacementDesignator, ref _messageBuilder, chatMessage); } /*Plugin.Log("Parse 6");*/ // Finally, push the final message into the render queue chatMessage.displayMsg = _messageBuilder.ToString(); /*Plugin.Log("The message in queue is " + chatMessage.displayMsg);*/ ChatHandler.RenderQueue.Enqueue(chatMessage); }
public static async void Parse(ChatMessage newChatMessage) { // Setup local variables char swapChar = (char)0xE000; List <EmoteInfo> parsedEmotes = new List <EmoteInfo>(); List <BadgeInfo> parsedBadges = new List <BadgeInfo>(); bool isActionMessage = false; if (newChatMessage.origMessage is TwitchMessage) { // If this is a twitch message, check if they used /me and color it accordingly isActionMessage = newChatMessage.displayMsg.Substring(1).StartsWith("ACTION") && newChatMessage.displayMsg[0] == (char)0x1; if (isActionMessage) { newChatMessage.displayMsg = newChatMessage.displayMsg.TrimEnd((char)0x1).Substring(8); } } StringBuilder emojilessMessageBuilder = new StringBuilder(newChatMessage.displayMsg); // Parse and download any emojis included in the message var matches = EmojiUtilities.GetEmojisInString(newChatMessage.displayMsg); if (matches.Count > 0) { List <string> foundEmojis = new List <string>(); foreach (Match m in matches) { string emojiIndex = EmojiUtilities.WebParseEmojiRegExMatchEvaluator(m); string replaceString = m.Value; // Build up a copy of the message with no emojis so we can parse out our twitch emotes properly emojilessMessageBuilder = emojilessMessageBuilder.Replace(m.Value, " "); if (emojiIndex != String.Empty) { emojiIndex += ".png"; if (!ImageDownloader.CachedTextures.ContainsKey(emojiIndex)) { ImageDownloader.Instance.Queue(new TextureDownloadInfo(emojiIndex, ImageType.Emoji, newChatMessage.origMessage.id)); } if (!foundEmojis.Contains(emojiIndex)) { foundEmojis.Add(emojiIndex); EmoteInfo swapInfo = new EmoteInfo(); swapInfo.imageType = ImageType.Emoji; swapInfo.isEmoji = true; swapInfo.swapChar = swapChar; swapInfo.swapString = replaceString; swapInfo.textureIndex = emojiIndex; parsedEmotes.Add(swapInfo); swapChar++; } } } parsedEmotes = parsedEmotes.OrderByDescending(o => o.swapString.Length).ToList(); Thread.Sleep(5); } if (newChatMessage.origMessage is TwitchMessage) { // Parse and download any twitch emotes in the message var emotes = _emoteRegex.Matches(newChatMessage.origMessage.Twitch.emotes); if (emotes.Count > 0) { string emojilessMessage = emojilessMessageBuilder.ToString(); foreach (Match e in emotes) { string emoteIndex = $"T{e.Groups["EmoteIndex"].Value}"; if (!ImageDownloader.CachedTextures.ContainsKey(emoteIndex)) { ImageDownloader.Instance.Queue(new TextureDownloadInfo(emoteIndex, ImageType.Twitch, newChatMessage.origMessage.id)); } int startReplace = Convert.ToInt32(e.Groups["StartIndex"].Value); int endReplace = Convert.ToInt32(e.Groups["EndIndex"].Value); string emoteName = emojilessMessage.Substring(startReplace, endReplace - startReplace + 1); EmoteInfo swapInfo = new EmoteInfo(); swapInfo.swapChar = swapChar; swapInfo.swapString = emoteName; swapInfo.textureIndex = emoteIndex; swapInfo.imageType = ImageType.Twitch; parsedEmotes.Add(swapInfo); swapChar++; } Thread.Sleep(5); } // Parse and download any twitch badges included in the message var badges = _badgeRegex.Matches(newChatMessage.origMessage.user.Twitch.badges); if (badges.Count > 0) { foreach (Match b in badges) { string badgeName = $"{b.Groups["BadgeName"].Value}{b.Groups["BadgeVersion"].Value}"; string badgeIndex = string.Empty; if (ImageDownloader.TwitchBadgeIDs.ContainsKey(badgeName)) { badgeIndex = ImageDownloader.TwitchBadgeIDs[badgeName]; if (!ImageDownloader.CachedTextures.ContainsKey(badgeIndex)) { ImageDownloader.Instance.Queue(new TextureDownloadInfo(badgeIndex, ImageType.Badge, newChatMessage.origMessage.id)); } BadgeInfo swapInfo = new BadgeInfo(); swapInfo.swapChar = swapChar; swapInfo.textureIndex = badgeIndex; swapInfo.imageType = ImageType.Badge; parsedBadges.Add(swapInfo); swapChar++; } } Thread.Sleep(5); } // Parse and download any BTTV/FFZ emotes and cheeremotes in the message string[] msgParts = newChatMessage.displayMsg.Split(' ').Distinct().ToArray(); foreach (string w in msgParts) { string word = w; //Plugin.Log($"WORD: {word}"); string textureIndex = String.Empty; ImageType imageType = ImageType.None; if (ImageDownloader.BTTVEmoteIDs.ContainsKey(word)) { if (ChatConfig.Instance.ShowBTTVEmotes) { textureIndex = $"B{ImageDownloader.BTTVEmoteIDs[word]}"; imageType = ImageType.BTTV; } } else if (ImageDownloader.BTTVAnimatedEmoteIDs.ContainsKey(word)) { if (ChatConfig.Instance.ShowBTTVEmotes) { textureIndex = $"AB{ImageDownloader.BTTVAnimatedEmoteIDs[word]}"; imageType = ImageType.BTTV_Animated; } } else if (ImageDownloader.FFZEmoteIDs.ContainsKey(word)) { if (ChatConfig.Instance.ShowFFZEmotes) { textureIndex = $"F{ImageDownloader.FFZEmoteIDs[word]}"; imageType = ImageType.FFZ; } } else if (newChatMessage.origMessage.Twitch.bits > 0 && EmojiUtilities.cheermoteRegex.IsMatch(word.ToLower())) { Match match = EmojiUtilities.cheermoteRegex.Match(word.ToLower()); string prefix = match.Groups["Prefix"].Value; if (ImageDownloader.TwitchCheermoteIDs.ContainsKey(prefix)) { int bits = Convert.ToInt32(match.Groups["Value"].Value); string tier = ImageDownloader.TwitchCheermoteIDs[prefix].GetTier(bits); textureIndex = $"{prefix}{tier}"; imageType = ImageType.Cheermote; } } if (imageType != ImageType.None) { if (!ImageDownloader.CachedTextures.ContainsKey(textureIndex)) { ImageDownloader.Instance.Queue(new TextureDownloadInfo(textureIndex, imageType, newChatMessage.origMessage.id)); } EmoteInfo swapInfo = new EmoteInfo(); swapInfo.imageType = imageType; swapInfo.swapChar = swapChar; swapInfo.swapString = word; swapInfo.textureIndex = textureIndex; parsedEmotes.Add(swapInfo); swapChar++; } } Thread.Sleep(5); } else if (newChatMessage.origMessage is YouTubeMessage) { if (!ImageDownloader.CachedTextures.ContainsKey(newChatMessage.origMessage.user.YouTube.profileImageUrl)) { ImageDownloader.Instance.Queue(new TextureDownloadInfo(newChatMessage.origMessage.user.YouTube.profileImageUrl, ImageType.YouTube_Profile, newChatMessage.origMessage.id, true)); } BadgeInfo swapInfo = new BadgeInfo(); swapInfo.swapChar = swapChar; swapInfo.textureIndex = newChatMessage.origMessage.user.YouTube.profileImageUrl; swapInfo.imageType = ImageType.YouTube_Profile; parsedBadges.Add(swapInfo); swapChar++; } string[] parts = newChatMessage.displayMsg.Split(' '); // Replace each emote with a unicode character from a private range; we'll draw the emote at the position of this character later on foreach (EmoteInfo e in parsedEmotes.Where(e => !e.isEmoji)) { var cachedSprite = e.GetCachedSprite(); string extraInfo = String.Empty; if (e.imageType == ImageType.Cheermote) { // Insert an animated cheermote into the message Match cheermote = EmojiUtilities.cheermoteRegex.Match(e.swapString); string numBits = cheermote.Groups["Value"].Value; extraInfo = $"\u200A<color={ImageDownloader.TwitchCheermoteIDs[cheermote.Groups["Prefix"].Value].GetColor(Convert.ToInt32(numBits))}>\u200A<size=3><b>{numBits}</b></size></color>\u200A"; } // Replace any instances of the swapString we find in the message string replaceString = $"\ufeff{Char.ConvertFromUtf32(e.swapChar)}{e.spacingString}{extraInfo}"; for (int i = 0; i < parts.Length; i++) { if (parts[i] == e.swapString) { parts[i] = replaceString; } } } // Then replace our emojis after all the emotes are handled, since these aren't sensitive to spacing StringBuilder sb = new StringBuilder(string.Join(" ", parts)); foreach (EmoteInfo e in parsedEmotes.Where(e => e.isEmoji)) { var cachedSprite = e.GetCachedSprite(); sb.Replace(e.swapString, $"{Char.ConvertFromUtf32(e.swapChar)}{e.spacingString}"); } newChatMessage.displayMsg = sb.ToString(); Thread.Sleep(5); //// TODO: Re-add tagging, why doesn't unity have highlighting in its default rich text markup? //// Highlight messages that we've been tagged in //if (Plugin._twitchUsername != String.Empty && msg.Contains(Plugin._twitchUsername)) { // msg = $"<mark=#ffff0050>{msg}</mark>"; //} string displayColor = newChatMessage.origMessage.user.color; int nameHash = newChatMessage.origMessage.user.displayName.GetHashCode(); // If the user doesn't already have a displayColor, generate one and store it for later user if (string.IsNullOrEmpty(displayColor) && !_userColors.TryGetValue(nameHash, out displayColor)) { // Generate a random color Random rand = new Random(nameHash); int r = rand.Next(255); int g = rand.Next(255); int b = rand.Next(255); // Convert it to a pastel color System.Drawing.Color pastelColor = Drawing.GetPastelShade(System.Drawing.Color.FromArgb(255, r, g, b)); int argb = ((int)pastelColor.R << 16) + ((int)pastelColor.G << 8) + (int)pastelColor.B; string colorString = String.Format("#{0:X6}", argb) + "FF"; _userColors.Add(nameHash, colorString); displayColor = colorString; } // Set the final displayColor to our message newChatMessage.displayColor = displayColor; // Add the users name to the message with the correct color newChatMessage.displayMsg = $"<color={newChatMessage.displayColor}><b>{newChatMessage.origMessage.user.displayName}</b>{(!isActionMessage?"</color>:":"")} {newChatMessage.displayMsg}{(isActionMessage?"</color>":"")}"; // Prepend the users badges to the front of the message StringBuilder badgeStr = new StringBuilder(); if (parsedBadges.Count > 0) { parsedBadges.Reverse(); foreach (BadgeInfo b in parsedBadges) { var cachedSprite = b.GetCachedSprite(); badgeStr.Insert(0, $"{Char.ConvertFromUtf32(b.swapChar)}{b.spacingString}\u00A0"); } } badgeStr.Insert(0, "\uFEFF"); badgeStr.Append(newChatMessage.displayMsg); // Finally, store our final message, parsedEmotes and parsedBadges; then render the message newChatMessage.displayMsg = badgeStr.ToString(); newChatMessage.parsedEmotes = parsedEmotes; newChatMessage.parsedBadges = parsedBadges; ChatHandler.RenderQueue.Enqueue(newChatMessage); }
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 EmojiUtilities.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 (ChatConfig.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 (ChatConfig.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()}!"); } } }