diff --git a/Assets/PerfectWorld/ScriptableObjects/ChatSystems/ChatSystemSO.asset b/Assets/PerfectWorld/ScriptableObjects/ChatSystems/ChatSystemSO.asset index fc7419f643..a463931134 100644 --- a/Assets/PerfectWorld/ScriptableObjects/ChatSystems/ChatSystemSO.asset +++ b/Assets/PerfectWorld/ScriptableObjects/ChatSystems/ChatSystemSO.asset @@ -47,3 +47,4 @@ MonoBehaviour: iconName: icon: {fileID: 0} prefix: '!#' + maxRawCharactersPerMessage: 80 diff --git a/Assets/PerfectWorld/Scripts/Chat/ChatWireTmpCodec.cs b/Assets/PerfectWorld/Scripts/Chat/ChatWireTmpCodec.cs index 5b510f63cc..cce4fa46b7 100644 --- a/Assets/PerfectWorld/Scripts/Chat/ChatWireTmpCodec.cs +++ b/Assets/PerfectWorld/Scripts/Chat/ChatWireTmpCodec.cs @@ -11,6 +11,7 @@ namespace BrewMonster.Scripts.Chat public static class ChatWireTmpCodec { private static readonly Regex SpriteTagRegex = new Regex(@"]*>", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex OrphanSpriteFragmentRegex = new Regex(@"^\s*sprite\s[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Compiled); /// /// Tạo một đoạn wire marshal cho một emotion (set:index) — gửi server đúng protocol. @@ -45,19 +46,40 @@ namespace BrewMonster.Scripts.Chat int last = 0; foreach (Match m in SpriteTagRegex.Matches(tmpBody)) { - sb.Append(tmpBody, last, m.Index - last); + AppendSanitizedPlainText(sb, tmpBody, last, m.Index - last); string tag = m.Value; if (TryMatchSpriteTagToEmotion(map, tag, out int es, out int ei)) sb.Append(BuildMarshaledEmotionWire(es, ei)); else sb.Append(tag); last = m.Index + m.Length; + + // Defensive: TMP_InputField can transiently expose an orphan fragment like + // `sprite anim="..."` right after a valid tag when input updates race. + // Skip it so we do not leak malformed rich-text into wire text. + var orphan = OrphanSpriteFragmentRegex.Match(tmpBody, last); + if (orphan.Success) + last += orphan.Length; } - sb.Append(tmpBody, last, tmpBody.Length - last); + AppendSanitizedPlainText(sb, tmpBody, last, tmpBody.Length - last); return sb.ToString(); } + private static void AppendSanitizedPlainText(StringBuilder sb, string source, int start, int length) + { + if (length <= 0) + return; + + int end = start + length; + for (int i = start; i < end; i++) + { + char ch = source[i]; + if (!AUICommon.IsEditboxItemCode(ch)) + sb.Append(ch); + } + } + /// /// Khớp tag với EmotionTMPTagBuilder — duyệt (set,index) đủ nhỏ. /// Match tag to EmotionTMPTagBuilder output — brute-force over (set,index) within reasonable bounds. diff --git a/Assets/PerfectWorld/Scripts/Editor.meta b/Assets/PerfectWorld/Scripts/Chat/Editor.meta similarity index 100% rename from Assets/PerfectWorld/Scripts/Editor.meta rename to Assets/PerfectWorld/Scripts/Chat/Editor.meta diff --git a/Assets/PerfectWorld/Scripts/Editor/EmotionAtlasConverterCore.cs b/Assets/PerfectWorld/Scripts/Chat/Editor/EmotionAtlasConverterCore.cs similarity index 100% rename from Assets/PerfectWorld/Scripts/Editor/EmotionAtlasConverterCore.cs rename to Assets/PerfectWorld/Scripts/Chat/Editor/EmotionAtlasConverterCore.cs diff --git a/Assets/PerfectWorld/Scripts/Editor/EmotionAtlasConverterCore.cs.meta b/Assets/PerfectWorld/Scripts/Chat/Editor/EmotionAtlasConverterCore.cs.meta similarity index 100% rename from Assets/PerfectWorld/Scripts/Editor/EmotionAtlasConverterCore.cs.meta rename to Assets/PerfectWorld/Scripts/Chat/Editor/EmotionAtlasConverterCore.cs.meta diff --git a/Assets/PerfectWorld/Scripts/Editor/EmotionAtlasConverterWindow.cs b/Assets/PerfectWorld/Scripts/Chat/Editor/EmotionAtlasConverterWindow.cs similarity index 100% rename from Assets/PerfectWorld/Scripts/Editor/EmotionAtlasConverterWindow.cs rename to Assets/PerfectWorld/Scripts/Chat/Editor/EmotionAtlasConverterWindow.cs diff --git a/Assets/PerfectWorld/Scripts/Editor/EmotionAtlasConverterWindow.cs.meta b/Assets/PerfectWorld/Scripts/Chat/Editor/EmotionAtlasConverterWindow.cs.meta similarity index 100% rename from Assets/PerfectWorld/Scripts/Editor/EmotionAtlasConverterWindow.cs.meta rename to Assets/PerfectWorld/Scripts/Chat/Editor/EmotionAtlasConverterWindow.cs.meta diff --git a/Assets/PerfectWorld/Scripts/Chat/ScriptableObjects/ChatSystemSO.cs b/Assets/PerfectWorld/Scripts/Chat/ScriptableObjects/ChatSystemSO.cs index 5eda930a5a..afbbf0bb89 100644 --- a/Assets/PerfectWorld/Scripts/Chat/ScriptableObjects/ChatSystemSO.cs +++ b/Assets/PerfectWorld/Scripts/Chat/ScriptableObjects/ChatSystemSO.cs @@ -23,5 +23,34 @@ namespace BrewMonster.Scripts.ChatUI [Header("Channel Icons")] public List channelIcons = new List(); + + [Header("Message Limits")] + [Tooltip("Giới hạn số ký tự thô cho một tin nhắn. <= 0 nghĩa là không giới hạn. " + + "TMP tag như cũng được tính theo đúng số ký tự của tag.")] + public int maxRawCharactersPerMessage = 60; + + /// + /// Đếm số ký tự thô (raw) của nội dung đang nhập. + /// Count raw characters from current input text. + /// + public int CountRawCharacters(string rawText) + { + if (string.IsNullOrEmpty(rawText)) + return 0; + return rawText.Length; + } + + /// + /// Kiểm tra có thể dùng thêm reserveChars mà vẫn không vượt giới hạn ký tự thô. + /// Check whether reserveChars can be added without exceeding raw character limit. + /// + public bool CanUseRawCharacters(string rawText, int reserveChars, out int usedChars, out int limitChars) + { + limitChars = maxRawCharactersPerMessage; + usedChars = CountRawCharacters(rawText) + Mathf.Max(0, reserveChars); + if (limitChars <= 0) + return true; + return usedChars <= limitChars; + } } } diff --git a/Assets/PerfectWorld/Scripts/Chat/UI/EmojiPickerUI.cs b/Assets/PerfectWorld/Scripts/Chat/UI/EmojiPickerUI.cs index 1d66a15304..e6c1e27b31 100644 --- a/Assets/PerfectWorld/Scripts/Chat/UI/EmojiPickerUI.cs +++ b/Assets/PerfectWorld/Scripts/Chat/UI/EmojiPickerUI.cs @@ -164,8 +164,9 @@ namespace BrewMonster.Scripts.ChatUI TryAutoWireIfNeeded(); if (_chatInput != null) { - EmotionTMPTagBuilder.InsertEmotionTag(_chatInput.inputField, _emotionSpriteMap, emotionSet, - emotionIndex); + // Keep wire body as source of truth to avoid TMP tag race/corruption + // when player taps emoji repeatedly. + _chatInput.InsertEmoji(emotionSet, emotionIndex); Debug.Log("[Cuong] HandleEmojiClicked."); } else diff --git a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab index 78f8491a61..851b8d357e 100644 --- a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab +++ b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab @@ -277,7 +277,7 @@ MonoBehaviour: _gridContent: {fileID: 7741139062211830239} _cellPrefab: {fileID: 4685469653980001717, guid: 616f563fe32927844be2a17dbb408331, type: 3} _cellSize: {x: 64, y: 64} - _cellSpacing: {x: 4, y: 4} + _cellSpacing: {x: 0, y: 4} _columnCount: 8 _chatInput: {fileID: 5889020827802476297} _chatSystemUI: {fileID: 2621697629504226575} @@ -746,7 +746,7 @@ MonoBehaviour: m_PressedTrigger: Pressed m_SelectedTrigger: Selected m_DisabledTrigger: Disabled - m_Interactable: 0 + m_Interactable: 1 m_TargetGraphic: {fileID: 2689844286198437230} m_OnClick: m_PersistentCalls: @@ -1061,7 +1061,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 1, g: 1, b: 1, a: 0.392} + m_Color: {r: 1, g: 1, b: 1, a: 0} m_RaycastTarget: 1 m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 diff --git a/Assets/Scripts/ChatInputHandler.cs b/Assets/Scripts/ChatInputHandler.cs index 2de306ac4c..1c8fff69d2 100644 --- a/Assets/Scripts/ChatInputHandler.cs +++ b/Assets/Scripts/ChatInputHandler.cs @@ -259,6 +259,9 @@ namespace BrewMonster.Scripts.ChatUI FilterBadWords(ref strText); _chatWireBody = strText; + if (!CanUseRawCharacters(0)) + return; + if (HandleDebugCommand(routingLine)) return; @@ -752,6 +755,14 @@ namespace BrewMonster.Scripts.ChatUI string segment = ChatWireTmpCodec.BuildMarshaledEmotionWire(emotionSet, emotionIndex); if (string.IsNullOrEmpty(segment)) return; + + int reserveChars = 0; + if (_spriteMap != null && EmotionTMPTagBuilder.TryBuildEmotionTag(_spriteMap, emotionSet, emotionIndex, out string tag)) + reserveChars = tag.Length; + + if (!CanUseRawCharacters(reserveChars)) + return; + _chatWireBody += segment; RefreshInputDisplayFromWire(); } @@ -766,5 +777,18 @@ namespace BrewMonster.Scripts.ChatUI { AppendEmotionWire(emotionSet, emotionIndex); } + + private bool CanUseRawCharacters(int reserveChars) + { + if (chatSystem == null) + return true; + + string rawBody = ExtractMessageBodyFromVisual(inputField != null ? inputField.text ?? "" : ""); + if (chatSystem.CanUseRawCharacters(rawBody, reserveChars, out int usedChars, out int limitChars)) + return true; + + //AddChatMessage($"Tin nhan vuot gioi han: {usedChars}/{limitChars} ky tu.", ChatChannel.GP_CHAT_MISC); + return false; + } } }