예제 #1
0
        private void CreateChatFont()
        {
            if (_chatFont != null)
            {
                return;
            }

            TMP_FontAsset font     = null;
            string        fontName = _chatConfig.SystemFontName;

            if (!FontManager.TryGetTMPFontByFamily(fontName, out font))
            {
                Logger.log.Error($"Could not find font {fontName}! Falling back to Segoe UI");
                fontName = "Segoe UI";
            }
            font.material.shader = BeatSaberUtils.TMPNoGlowFontShader;
            _chatFont            = new EnhancedFontInfo(font);

            foreach (var msg in _messages)
            {
                msg.Text.SetAllDirty();
                if (msg.SubTextEnabled)
                {
                    msg.SubText.SetAllDirty();
                }
            }

            while (_backupMessageQueue.TryDequeue(out var msg))
            {
                OnTextMessageReceived(msg);
            }
        }
        /// <summary>
        /// This function *blocks* the calling thread, and caches all the images required to display the message, then registers them with the provided font.
        /// </summary>
        /// <param name="msg">The chat message to get images from</param>
        /// <param name="font">The font to register these images to</param>
        public async Task <bool> PrepareImages(IESCChatMessage msg, EnhancedFontInfo font)
        {
            var tasks = new List <Task <EnhancedImageInfo> >();
            var pendingEmoteDownloads = new HashSet <string>();

            foreach (var emote in msg.Emotes)
            {
                if (string.IsNullOrEmpty(emote.Id) || pendingEmoteDownloads.Contains(emote.Id))
                {
                    continue;
                }
                if (!font.CharacterLookupTable.ContainsKey(emote.Id))
                {
                    pendingEmoteDownloads.Add(emote.Id);
                    var tcs = new TaskCompletionSource <EnhancedImageInfo>();
                    SharedCoroutineStarter.instance.StartCoroutine(this._chatImageProvider.TryCacheSingleImage(emote.Id, emote.Url, emote.Animated ? ChatImageProvider.ESCAnimationType.GIF : ChatImageProvider.ESCAnimationType.MAYBE_GIF, (info) =>
                    {
                        if (info == null || !font.TryRegisterImageInfo(info, out var character))
                        {
                            Logger.Warn($"Failed to register emote \"{emote.Id}\" in font {font.Font.name}.");
                        }
                        tcs.SetResult(info);
                    }, forcedHeight: 110));
                    tasks.Add(tcs.Task);
                }
            }

            if (msg.Sender is TwitchUser twitchUser)
            {
                foreach (var badge in twitchUser.Badges)
                {
                    if (string.IsNullOrEmpty(badge.Id) || pendingEmoteDownloads.Contains(badge.Id))
                    {
                        continue;
                    }
                    if (!font.CharacterLookupTable.ContainsKey(badge.Id))
                    {
                        pendingEmoteDownloads.Add(badge.Id);
                        var tcs = new TaskCompletionSource <EnhancedImageInfo>();
                        SharedCoroutineStarter.instance.StartCoroutine(this._chatImageProvider.TryCacheSingleImage(badge.Id, badge.Uri, ChatImageProvider.ESCAnimationType.NONE, (info) =>
                        {
                            if (info != null)
                            {
                                if (!font.TryRegisterImageInfo(info, out var character))
                                {
                                    Logger.Warn($"Failed to register badge \"{badge.Id}\" in font {font.Font.name}.");
                                }
                            }
                            tcs.SetResult(info);
                        }, forcedHeight: 100));
                        tasks.Add(tcs.Task);
                    }
                }
            }
            // Wait on all the resources to be ready
            var result = await Task.WhenAll(tasks);

            return(result.All(x => x != null));
        }
예제 #3
0
        public static async Task <string> BuildMessage(IChatMessage msg, EnhancedFontInfo font)
        {
            try
            {
                if (!PrepareImages(msg, font))
                {
                    Logger.log.Warn($"Failed to prepare some/all images for msg \"{msg.Message}\"!");
                    //return msg.Message;
                }

                ConcurrentStack <EnhancedImageInfo> badges = new ConcurrentStack <EnhancedImageInfo>();
                foreach (var badge in msg.Sender.Badges)
                {
                    if (!ChatImageProvider.instance.CachedImageInfo.TryGetValue(badge.Id, out var badgeInfo))
                    {
                        Logger.log.Warn($"Failed to find cached image info for badge \"{badge.Id}\"!");
                        continue;
                    }
                    badges.Push(badgeInfo);
                }

                StringBuilder sb = new StringBuilder(msg.Message); // Replace all instances of < with a zero-width non-breaking character
                foreach (var emote in msg.Emotes)
                {
                    if (!ChatImageProvider.instance.CachedImageInfo.TryGetValue(emote.Id, out var replace))
                    {
                        Logger.log.Warn($"Emote {emote.Name} was missing from the emote dict! The request to {emote.Uri} may have timed out?");
                        continue;
                    }
                    //Logger.log.Info($"Emote: {emote.Name}, StartIndex: {emote.StartIndex}, EndIndex: {emote.EndIndex}, Len: {sb.Length}");
                    if (!font.TryGetCharacter(replace.ImageId, out uint character))
                    {
                        Logger.log.Warn($"Emote {emote.Name} was missing from the character dict! Font hay have run out of usable characters.");
                        continue;
                    }

                    try
                    {
                        // Replace emotes by index, in reverse order (msg.Emotes is sorted by emote.StartIndex in descending order)
                        sb.Replace(emote.Name, emote switch
                        {
                            TwitchEmote t when t.Bits > 0 => $"{char.ConvertFromUtf32((int)character)}\u00A0<color={t.Color}><size=77%><b>{t.Bits}\u00A0</b></size></color>",
                            _ => char.ConvertFromUtf32((int)character)
                        },
                                   emote.StartIndex, emote.EndIndex - emote.StartIndex + 1);
                    }
                    catch (Exception ex)
                    {
                        Logger.log.Error($"An unknown error occurred while trying to swap emote {emote.Name} into string of length {sb.Length} at location ({emote.StartIndex}, {emote.EndIndex})");
                    }
                }
예제 #4
0
 protected override void OnDestroy()
 {
     base.OnDestroy();
     ChatConfig.instance.OnConfigChanged -= Instance_OnConfigChanged;
     BSEvents.menuSceneActive            -= BSEvents_menuSceneActive;
     BSEvents.gameSceneActive            -= BSEvents_gameSceneActive;
     StopAllCoroutines();
     foreach (var msg in _messages)
     {
         msg.OnLatePreRenderRebuildComplete -= OnRenderRebuildComplete;
         if (msg.Text.ChatMessage != null)
         {
             _backupMessageQueue.Enqueue(msg.Text.ChatMessage);
         }
         if (msg.SubText.ChatMessage != null)
         {
             _backupMessageQueue.Enqueue(msg.SubText.ChatMessage);
         }
         Destroy(msg);
     }
     _messages.Clear();
     Destroy(_rootGameObject);
     if (TextPool != null)
     {
         TextPool.Dispose();
         TextPool = null;
     }
     if (_chatScreen != null)
     {
         Destroy(_chatScreen);
         _chatScreen = null;
     }
     if (_chatFont != null)
     {
         Destroy(_chatFont.Font);
         _chatFont = null;
     }
     if (_chatMoverMaterial != null)
     {
         Destroy(_chatMoverMaterial);
         _chatMoverMaterial = null;
     }
 }
예제 #5
0
        /// <summary>
        /// This function *blocks* the calling thread, and caches all the images required to display the message, then registers them with the provided font.
        /// </summary>
        /// <param name="msg">The chat message to get images from</param>
        /// <param name="font">The font to register these images to</param>
        public static bool PrepareImages(IChatMessage msg, EnhancedFontInfo font)
        {
            List <Task <EnhancedImageInfo> > tasks = new List <Task <EnhancedImageInfo> >();
            HashSet <string> pendingEmoteDownloads = new HashSet <string>();

            foreach (var emote in msg.Emotes)
            {
                if (pendingEmoteDownloads.Contains(emote.Id))
                {
                    continue;
                }
                if (!font.CharacterLookupTable.ContainsKey(emote.Id))
                {
                    pendingEmoteDownloads.Add(emote.Id);
                    TaskCompletionSource <EnhancedImageInfo> tcs = new TaskCompletionSource <EnhancedImageInfo>();
                    switch (emote.Type)
                    {
                    case EmoteType.SingleImage:
                        SharedCoroutineStarter.instance.StartCoroutine(ChatImageProvider.instance.TryCacheSingleImage(emote.Id, emote.Uri, emote.IsAnimated, (info) => {
                            if (info != null)
                            {
                                if (!font.TryRegisterImageInfo(info, out var character))
                                {
                                    Logger.log.Warn($"Failed to register emote \"{emote.Id}\" in font {font.Font.name}.");
                                }
                            }
                            tcs.SetResult(info);
                        }, forcedHeight: 110));
                        break;

                    case EmoteType.SpriteSheet:
                        ChatImageProvider.instance.TryCacheSpriteSheetImage(emote.Id, emote.Uri, emote.UVs, (info) =>
                        {
                            if (info != null)
                            {
                                if (!font.TryRegisterImageInfo(info, out var character))
                                {
                                    Logger.log.Warn($"Failed to register emote \"{emote.Id}\" in font {font.Font.name}.");
                                }
                            }
                            tcs.SetResult(info);
                        }, forcedHeight: 110);
                        break;

                    default:
                        tcs.SetResult(null);
                        break;
                    }
                    tasks.Add(tcs.Task);
                }
            }

            foreach (var badge in msg.Sender.Badges)
            {
                if (pendingEmoteDownloads.Contains(badge.Id))
                {
                    continue;
                }

                if (!font.CharacterLookupTable.ContainsKey(badge.Id))
                {
                    pendingEmoteDownloads.Add(badge.Id);
                    TaskCompletionSource <EnhancedImageInfo> tcs = new TaskCompletionSource <EnhancedImageInfo>();
                    SharedCoroutineStarter.instance.StartCoroutine(ChatImageProvider.instance.TryCacheSingleImage(badge.Id, badge.Uri, false, (info) => {
                        if (info != null)
                        {
                            if (!font.TryRegisterImageInfo(info, out var character))
                            {
                                Logger.log.Warn($"Failed to register badge \"{badge.Id}\" in font {font.Font.name}.");
                            }
                        }
                        tcs.SetResult(info);
                    }, forcedHeight: 100));
                    tasks.Add(tcs.Task);
                }
            }

            // Wait on all the resources to be ready
            return(Task.WaitAll(tasks.ToArray(), 15000));
        }
 public async Task <string> BuildMessage(IESCChatMessage msg, EnhancedFontInfo font, BuildMessageTarget buildMessage)
 {
     try {
         if (!await this.PrepareImages(msg, font))
         {
             Logger.Warn($"Failed to prepare some/all images for msg \"{msg.Message}\"!");
             //return msg.Message;
         }
         var badges = new Stack <EnhancedImageInfo>();
         if (msg.Sender is TwitchUser twitchUser)
         {
             foreach (var badge in twitchUser.Badges)
             {
                 if (!this._chatImageProvider.CachedImageInfo.TryGetValue(badge.Id, out var badgeInfo))
                 {
                     Logger.Warn($"Failed to find cached image info for badge \"{badge.Id}\"!");
                     continue;
                 }
                 badges.Push(badgeInfo);
             }
         }
         var sb = buildMessage == BuildMessageTarget.Main ? new StringBuilder(msg.Message) : new StringBuilder(msg.SubMessage); // Replace all instances of < with a zero-width non-breaking character
         foreach (var emote in msg.Emotes)
         {
             if (emote is TwitchEmote twitchEmote && 0 < twitchEmote.Bits)
             {
                 continue;
             }
             if (!this._chatImageProvider.CachedImageInfo.TryGetValue(emote.Id, out var replace))
             {
                 Logger.Warn($"Emote {emote.Name} was missing from the emote dict! The request to {emote.Url} may have timed out?");
                 continue;
             }
             //Logger.Info($"replase id {replace.ImageId}");
             //Logger.Info($"Emote: {emote.Name}, StartIndex: {emote.StartIndex}, EndIndex: {emote.EndIndex}, Len: {sb.Length}");
             if (!font.TryGetCharacter(replace.ImageId, out var character))
             {
                 Logger.Warn($"Emote {emote.Name} was missing from the character dict! Font hay have run out of usable characters.");
                 continue;
             }
             //Logger.Info($"target char {character}");
             try {
                 // Replace emotes by index, in reverse order (msg.Emotes is sorted by emote.StartIndex in descending order)
                 if (Regex.IsMatch(emote.Id, "^Emoji_"))
                 {
                     var charIndexText = Regex.Replace(emote.Id, "^Emoji_", "");
                     var emojiChars    = charIndexText.Split('-').Select(x => char.ConvertFromUtf32(Convert.ToInt32($"0x{x}", 16)));
                     var emojiBuilder  = new StringBuilder();
                     foreach (var emojiChar in emojiChars)
                     {
                         emojiBuilder.Append(emojiChar);
                     }
                     sb.Replace(emojiBuilder.ToString(), char.ConvertFromUtf32((int)character));
                 }
                 else
                 {
                     sb.Replace(emote.Name, char.ConvertFromUtf32((int)character));
                 }
             }
             catch (Exception ex) {
                 Logger.Error($"An unknown error occurred while trying to swap emote {emote.Name} into string of length {sb.Length} at location ({emote.StartIndex}, {emote.EndIndex})\r\n{ex}");
             }
         }
         sb.Replace("<", "<\u2060");
         foreach (var emote in msg.Emotes)
         {
             if (emote is not TwitchEmote twitchEmote || twitchEmote.Bits == 0)
             {
                 continue;
             }
             if (!this._chatImageProvider.CachedImageInfo.TryGetValue(emote.Id, out var replace))
             {
                 Logger.Warn($"Emote {emote.Name} was missing from the emote dict! The request to {emote.Url} may have timed out?");
                 continue;
             }
             //Logger.Info($"replase id {replace.ImageId}");
             //Logger.Info($"Emote: {emote.Name}, StartIndex: {emote.StartIndex}, EndIndex: {emote.EndIndex}, Len: {sb.Length}");
             if (!font.TryGetCharacter(replace.ImageId, out var character))
             {
                 Logger.Warn($"Emote {emote.Name} was missing from the character dict! Font hay have run out of usable characters.");
                 continue;
             }
             //Logger.Info($"target char {character}");
             try {
                 // Replace emotes by index, in reverse order (msg.Emotes is sorted by emote.StartIndex in descending order)
                 sb.Replace(emote.Name, $"{char.ConvertFromUtf32((int)character)}\u00A0<color={twitchEmote.Color}><size=77%><b>{twitchEmote.Bits}\u00A0</b></size></color>");
             }
             catch (Exception ex) {
                 Logger.Error($"An unknown error occurred while trying to swap emote {emote.Name} into string of length {sb.Length} at location ({emote.StartIndex}, {emote.EndIndex})\r\n{ex}");
             }
         }
         if (buildMessage == BuildMessageTarget.Main && msg.IsSystemMessage)
         {
             // System messages get a grayish color to differenciate them from normal messages in chat, and do not receive a username/badge prefix
             sb.Insert(0, $"<color=#bbbbbbff>");
             sb.Append("</color>");
         }
         else
         {
             var nameColorCode = msg.Sender?.Color;
             if (ColorUtility.TryParseHtmlString(nameColorCode?.Substring(0, 7), out var nameColor))
             {
                 if (nameColor == Color.white && !s_senderColor.TryGetValue(msg.Sender?.UserName, out nameColor))
                 {
                     nameColor = new Color(((float)this._random.Next(0, 100000) / 100000), ((float)this._random.Next(0, 100000) / 100000), ((float)this._random.Next(0, 100000) / 100000));
                     s_senderColor.TryAdd(msg.Sender?.UserName, nameColor);
                 }
                 Color.RGBToHSV(nameColor, out var h, out var s, out var v);
                 if (v < 0.85f)
                 {
                     v         = 0.85f;
                     nameColor = Color.HSVToRGB(h, s, v);
                 }
                 nameColorCode = ColorUtility.ToHtmlStringRGBA(nameColor);
                 nameColorCode = nameColorCode.Insert(0, "#");
             }
             if (msg.IsActionMessage)
             {
                 // Message becomes the color of their name if it's an action message
                 sb.Insert(0, $"<color={nameColorCode}><b>{msg.Sender?.DisplayName}</b> ");
                 sb.Append("</color>");
             }
             else
             {
                 // Insert username w/ color
                 sb.Insert(0, $"<color={nameColorCode}><b>{msg.Sender?.DisplayName}</b></color>: ");
             }
             var parsedBadge = new HashSet <string>();
             if (msg.Sender is TwitchUser twitchUser1)
             {
                 for (var i = 0; i < twitchUser1.Badges.Count; i++)
                 {
                     // Insert user badges at the beginning of the string in reverse order
                     var badge = badges.Pop();
                     if (parsedBadge.Contains(badge.ImageId))
                     {
                         continue;
                     }
                     parsedBadge.Add(badge.ImageId);
                     if (badge != null && font.TryGetCharacter(badge.ImageId, out var character))
                     {
                         sb.Insert(0, $"{char.ConvertFromUtf32((int)character)} ");
                     }
                     else
                     {
                         Logger.Warn("Undefind badge");
                     }
                 }
             }
         }
         return(sb.ToString());
     }
     catch (Exception ex) {
         Logger.Error($"An exception occurred in ChatMessageBuilder while parsing msg with {msg.Emotes.Count} emotes. Msg: \"{msg.Message}\". {ex}");
     }
     return(msg.Message);
 }
예제 #7
0
 public static Task <string> BuildMessage(IChatMessage msg, EnhancedFontInfo font) => Task.Run(() =>
 {
     try {
         if (!PrepareImages(msg, font))