Merge pull request 'feature/chat_01' (#318) from feature/chat_01 into develop
Reviewed-on: https://git.pthub.vn/Unity/perfect-world-unity/pulls/318
This commit is contained in:
@@ -1522,7 +1522,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 1}
|
||||
m_AnchorMax: {x: 0.5, y: 1}
|
||||
m_AnchoredPosition: {x: -187.8, y: -47.6}
|
||||
m_AnchoredPosition: {x: -187.8, y: -47.600098}
|
||||
m_SizeDelta: {x: 191, y: 60}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &6965363531247653623
|
||||
@@ -4825,6 +4825,7 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 8741686998992894603}
|
||||
- {fileID: 3488899534283412697}
|
||||
- {fileID: 4668526399359599056}
|
||||
- {fileID: 9056141770234008732}
|
||||
- {fileID: 6541409353547558602}
|
||||
- {fileID: 2907261990866691440}
|
||||
@@ -6262,6 +6263,7 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
inputField: {fileID: 9217902013627304316}
|
||||
chatSystem: {fileID: 0}
|
||||
_spriteMap: {fileID: 0}
|
||||
channelButtons: []
|
||||
--- !u!1 &3544484534608324905
|
||||
GameObject:
|
||||
@@ -8700,8 +8702,8 @@ MonoBehaviour:
|
||||
- {fileID: 2971821658315981769}
|
||||
- {fileID: 452969679978752531}
|
||||
- {fileID: 3647934876571221831}
|
||||
HpItemButton: {fileID: 7382895648940793749}
|
||||
MpItemButton: {fileID: 7614763976671640739}
|
||||
HpItemButton: {fileID: 0}
|
||||
MpItemButton: {fileID: 0}
|
||||
m_nCurPanel1: 1
|
||||
m_nCurPanel2: 1
|
||||
m_bShowAll1: 0
|
||||
@@ -10710,7 +10712,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0.5}
|
||||
m_AnchorMax: {x: 0, y: 0.5}
|
||||
m_AnchoredPosition: {x: 60.559204, y: -126.849976}
|
||||
m_AnchoredPosition: {x: 60.559204, y: -126.8501}
|
||||
m_SizeDelta: {x: 101.855, y: 57.3622}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &3863452661654338700
|
||||
@@ -11648,7 +11650,6 @@ MonoBehaviour:
|
||||
_btnTaskTrace: {fileID: 3253955040536933532}
|
||||
_taskTraceParent: {fileID: 2578159539417438268}
|
||||
_btnTeamList: {fileID: 540188344648694736}
|
||||
_lockviewList: {fileID: 0}
|
||||
--- !u!1 &7352847439676120744
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -18712,7 +18713,7 @@ PrefabInstance:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8435310359341866937, guid: b5a4a3ed5bf0e5a49ba0f89d26e1f36e, type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -25.335571
|
||||
value: -25.33545
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8579427623307909814, guid: b5a4a3ed5bf0e5a49ba0f89d26e1f36e, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
@@ -18948,6 +18949,14 @@ PrefabInstance:
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2621697629504226575, guid: a531b4b63ab8355419f297fe10d32abe, type: 3}
|
||||
propertyPath: miniChatContent
|
||||
value:
|
||||
objectReference: {fileID: 3171192075315926561}
|
||||
- target: {fileID: 2621697629504226575, guid: a531b4b63ab8355419f297fe10d32abe, type: 3}
|
||||
propertyPath: onOpenChatPanelButton
|
||||
value:
|
||||
objectReference: {fileID: 1884013949825403952}
|
||||
- target: {fileID: 2818704151482351807, guid: a531b4b63ab8355419f297fe10d32abe, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 0
|
||||
@@ -20714,6 +20723,124 @@ RectTransform:
|
||||
m_CorrespondingSourceObject: {fileID: 4475312012745311020, guid: c82978c3789dad44da354dc354c782b2, type: 3}
|
||||
m_PrefabInstance: {fileID: 8244659259478137406}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1001 &8343482093857271211
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 3233441867675090637}
|
||||
m_Modifications:
|
||||
- target: {fileID: 843061721837273778, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_Name
|
||||
value: prefab_MiniChat
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_Pivot.x
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_Pivot.y
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_AnchorMax.x
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_AnchorMin.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
--- !u!114 &1884013949825403952 stripped
|
||||
MonoBehaviour:
|
||||
m_CorrespondingSourceObject: {fileID: 7633421558195200411, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
m_PrefabInstance: {fileID: 8343482093857271211}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!224 &3171192075315926561 stripped
|
||||
RectTransform:
|
||||
m_CorrespondingSourceObject: {fileID: 6901861700741151626, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
m_PrefabInstance: {fileID: 8343482093857271211}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!224 &4668526399359599056 stripped
|
||||
RectTransform:
|
||||
m_CorrespondingSourceObject: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
|
||||
m_PrefabInstance: {fileID: 8343482093857271211}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1001 &8508566894086459119
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -12,7 +12,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: af2fa24fb63c4aa45bb99a711c857114, type: 3}
|
||||
m_Name: EmotionLibrarySpriteMap
|
||||
m_EditorClassIdentifier:
|
||||
Library: {fileID: 11400000, guid: 3011939e4e9c0ce4e83bc03a748fcf96, type: 2}
|
||||
TmpSpriteAsset: {fileID: 11400000, guid: c41005c129ba4d66911b75229fd70b45, type: 2}
|
||||
Library: {fileID: 11400000, guid: e4fbe1473e80a9c4794327d8bb11a4ab, type: 2}
|
||||
TmpSpriteAsset: {fileID: 11400000, guid: ee97404ddf0b24345809ec3b36f78e02, type: 2}
|
||||
PreferSpriteNameTag: 1
|
||||
DefaultAnimFps: 10
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using CSNetwork;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BrewMonster.Scripts.Chat
|
||||
{
|
||||
@@ -8,16 +10,44 @@ namespace BrewMonster.Scripts.Chat
|
||||
/// </summary>
|
||||
public sealed class ChatEmotionDisplayPipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Khi thiếu ký tự PUA trước payload, Unmarshal không chạy nhưng vẫn còn <0><set:index> (Serialize).
|
||||
/// When PUA prefix is missing, Unmarshal skips but <0><set:index> (Serialize) may remain.
|
||||
/// </summary>
|
||||
private static readonly Regex LooseMarshaledEmotionRegex = new Regex(
|
||||
@"<0><(\d+):(\d+)>",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private IEmotionSpriteMap _spriteMap = new StubEmotionSpriteMap();
|
||||
|
||||
/// <summary>
|
||||
/// Wire 正文(服务器 / 输入缓冲)→ TMP 显示用富文本 — 与 CECGameUIMan.AddChatMessage 内转换一致(不含 FilterBadWords)。
|
||||
/// Wire body (server / input buffer) → TMP rich text for display — same as CECGameUIMan.AddChatMessage (without FilterBadWords).
|
||||
/// </summary>
|
||||
public static string ConvertWireBodyToTmpDisplay(string wireBody, IEmotionSpriteMap map, int cEmotion)
|
||||
{
|
||||
if (string.IsNullOrEmpty(wireBody))
|
||||
return "";
|
||||
if (map == null)
|
||||
return wireBody;
|
||||
|
||||
var p = new ChatEmotionDisplayPipeline(map);
|
||||
string filtered = p.ApplyChannelEmotionFilter(wireBody, cEmotion);
|
||||
return p.ConvertInlineItemsToTmp(filtered);
|
||||
}
|
||||
|
||||
public ChatEmotionDisplayPipeline(IEmotionSpriteMap spriteMap = null)
|
||||
{
|
||||
if (spriteMap != null)
|
||||
_spriteMap = spriteMap;
|
||||
else
|
||||
BMLogger.LogWarning("[ChatEmotionDisplayPipeline] spriteMap is null — using StubEmotionSpriteMap. Call SetSpriteMap() or assign EmotionLibrarySpriteMap SO on CECUIManager.");
|
||||
}
|
||||
|
||||
public void SetSpriteMap(IEmotionSpriteMap spriteMap)
|
||||
{
|
||||
if (spriteMap == null)
|
||||
BMLogger.LogWarning("[ChatEmotionDisplayPipeline] SetSpriteMap() called with null — EmotionLibrarySpriteMap SO chưa được gán trên CECUIManager Inspector.");
|
||||
_spriteMap = spriteMap ?? new StubEmotionSpriteMap();
|
||||
}
|
||||
|
||||
@@ -35,15 +65,38 @@ namespace BrewMonster.Scripts.Chat
|
||||
{
|
||||
EditBoxItemsSet itemsSet = new EditBoxItemsSet();
|
||||
string displayText = AUICommon.UnmarshalEditBoxText(pszMsgAfterBadWordFilter, itemsSet);
|
||||
string tmpText;
|
||||
if (itemsSet.GetItemCount() <= 0)
|
||||
return pszMsgAfterBadWordFilter;
|
||||
tmpText = pszMsgAfterBadWordFilter;
|
||||
else
|
||||
{
|
||||
tmpText = displayText;
|
||||
tmpText = AUICommon.ConvertEmotionsToTMP(tmpText, itemsSet, _spriteMap);
|
||||
tmpText = AUICommon.ConvertCoordsToTMP(tmpText, itemsSet);
|
||||
tmpText = AUICommon.ConvertIvtrItemsToTMP(tmpText, itemsSet);
|
||||
tmpText = AUICommon.StripRemainingItemCodes(tmpText);
|
||||
}
|
||||
|
||||
string tmpText = displayText;
|
||||
tmpText = AUICommon.ConvertEmotionsToTMP(tmpText, itemsSet, _spriteMap);
|
||||
tmpText = AUICommon.ConvertCoordsToTMP(tmpText, itemsSet);
|
||||
tmpText = AUICommon.ConvertIvtrItemsToTMP(tmpText, itemsSet);
|
||||
tmpText = AUICommon.StripRemainingItemCodes(tmpText);
|
||||
return tmpText;
|
||||
return ReplaceLooseMarshaledEmotionTags(tmpText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fallback: wire emotion chỉ còn dạng Serialize <0><set:index> (không qua PUA) → TMP <sprite>.
|
||||
/// Fallback: emotion wire as Serialize-only <0><set:index> (no PUA) → TMP <sprite>.
|
||||
/// </summary>
|
||||
private string ReplaceLooseMarshaledEmotionTags(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text) || _spriteMap == null)
|
||||
return text;
|
||||
|
||||
return LooseMarshaledEmotionRegex.Replace(text, match =>
|
||||
{
|
||||
int set = int.Parse(match.Groups[1].Value);
|
||||
int index = int.Parse(match.Groups[2].Value);
|
||||
if (_spriteMap.TryGetSprite(set, index, out EmotionSpriteInfo info))
|
||||
return EmotionTMPTagBuilder.BuildSpriteTag(info);
|
||||
return match.Value;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using CSNetwork;
|
||||
|
||||
namespace BrewMonster.Scripts.Chat
|
||||
{
|
||||
/// <summary>
|
||||
/// Chat 输入/协议:wire(MarshalEditBoxText)↔ TMP <sprite> 显示。
|
||||
/// Chat input/protocol: wire (MarshalEditBoxText) ↔ TMP <sprite> display.
|
||||
/// </summary>
|
||||
public static class ChatWireTmpCodec
|
||||
{
|
||||
private static readonly Regex SpriteTagRegex = new Regex(@"<sprite\s[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// Tạo một đoạn wire marshal cho một emotion (set:index) — gửi server đúng protocol.
|
||||
/// Build one marshaled wire segment for a single emotion (set:index) — correct server protocol.
|
||||
/// </summary>
|
||||
public static string BuildMarshaledEmotionWire(int emotionSet, int emotionIndex)
|
||||
{
|
||||
var item = new EditBoxItemBase(AUICommon.EditboxItemType.enumEIEmotion);
|
||||
item.SetName("W");
|
||||
item.SetInfo(AUICommon.MarshalEmotionInfo(emotionSet, emotionIndex));
|
||||
var items = new EditBoxItemsSet();
|
||||
char c = items.AppendItem(item);
|
||||
if (c == '\0')
|
||||
return "";
|
||||
|
||||
string display = c.ToString();
|
||||
return AUICommon.MarshalEditBoxText(display, items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TMP 正文(无频道前缀)→ wire marshal(用于发送)。
|
||||
/// TMP body text (no channel prefix) → marshaled wire (for sending).
|
||||
/// </summary>
|
||||
public static string TmpBodyToWire(string tmpBody, IEmotionSpriteMap map)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tmpBody))
|
||||
return "";
|
||||
if (map == null)
|
||||
return tmpBody;
|
||||
|
||||
var sb = new StringBuilder(tmpBody.Length);
|
||||
int last = 0;
|
||||
foreach (Match m in SpriteTagRegex.Matches(tmpBody))
|
||||
{
|
||||
sb.Append(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;
|
||||
}
|
||||
|
||||
sb.Append(tmpBody, last, tmpBody.Length - last);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Khớp tag với EmotionTMPTagBuilder — duyệt (set,index) đủ nhỏ.
|
||||
/// Match tag to EmotionTMPTagBuilder output — brute-force over (set,index) within reasonable bounds.
|
||||
/// </summary>
|
||||
public static bool TryMatchSpriteTagToEmotion(IEmotionSpriteMap map, string spriteTag, out int emotionSet, out int emotionIndex)
|
||||
{
|
||||
emotionSet = 0;
|
||||
emotionIndex = 0;
|
||||
if (map == null || string.IsNullOrEmpty(spriteTag))
|
||||
return false;
|
||||
|
||||
string normalized = spriteTag.Trim();
|
||||
for (int s = 0; s < AUICommon.AUIMANAGER_MAX_EMOTIONGROUPS; s++)
|
||||
{
|
||||
for (int e = 0; e < 512; e++)
|
||||
{
|
||||
if (!EmotionTMPTagBuilder.TryBuildEmotionTag(map, s, e, out string built))
|
||||
continue;
|
||||
if (built == normalized)
|
||||
{
|
||||
emotionSet = s;
|
||||
emotionIndex = e;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wire → TMP 富文本(FilterEmotionSet + Unmarshal + ConvertInlineItemsToTmp)。
|
||||
/// Wire → TMP rich text (FilterEmotionSet + Unmarshal + ConvertInlineItemsToTmp).
|
||||
/// </summary>
|
||||
public static string WireBodyToTmpForDisplay(string wireBody, IEmotionSpriteMap map, int cEmotion)
|
||||
{
|
||||
return ChatEmotionDisplayPipeline.ConvertWireBodyToTmpDisplay(wireBody, map, cEmotion);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a9d1336ec810654dae6b1bb4076ae3a
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BrewMonster.Scripts.Chat.EmotionData
|
||||
@@ -16,6 +17,11 @@ namespace BrewMonster.Scripts.Chat.EmotionData
|
||||
public int CellHeight = 32;
|
||||
public Texture2D SourceAtlas;
|
||||
public string SourceTxtAssetPath = "";
|
||||
|
||||
[Tooltip("TMP_SpriteAsset tương ứng với atlas này (cell_0000…). Bắt buộc cho emoji động (nhiều frame). " +
|
||||
"TMP_SpriteAsset matching this atlas (cell_0000…). Required for animated emoji (multi-frame).")]
|
||||
public TMP_SpriteAsset TmpSpriteAsset;
|
||||
|
||||
public List<EmotionEntryData> Entries = new List<EmotionEntryData>();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,120 +1,134 @@
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BrewMonster.Scripts.Chat.EmotionData
|
||||
{
|
||||
/// <summary>
|
||||
/// Ánh xạ (emotionSet, emotionIndex) từ <see cref="EmotionLibrarySO"/> sang tag TMP.
|
||||
/// Gán asset này vào <see cref="CECUIManager"/> (field emotion) — <see cref="BrewMonster.UI.CECGameUIMan"/> không phải MonoBehaviour nên không kéo SO trên Inspector được.
|
||||
/// Maps (emotionSet, emotionIndex) from EmotionLibrarySO to TMP rich-text tag.
|
||||
///
|
||||
/// Atlas được cắt trái→phải, trên→dưới, sub-sprite đặt tên cell_XXXX.
|
||||
/// Số XXXX trong tên chính là index tuyến tính → không cần tra spriteCharacterTable.
|
||||
/// Atlas is sliced left-to-right, top-to-bottom, named cell_XXXX.
|
||||
/// The number XXXX in the name IS the linear index → no spriteCharacterTable lookup needed.
|
||||
///
|
||||
/// Cách dùng:
|
||||
/// 1) Tạo <see cref="EmotionLibrarySO"/> bằng Emotion Atlas Converter.
|
||||
/// 2) Tạo <see cref="TMP_SpriteAsset"/> từ cùng atlas (Window → TextMeshPro → Sprite Importer),
|
||||
/// đảm bảo tên sub-sprite khớp (vd. cell_0000) với atlas Multiple từ tool.
|
||||
/// 3) Gán TMP_SpriteAsset vào ô chat <see cref="TMPro.TextMeshProUGUI"/> (Sprite Asset / Additional).
|
||||
/// 4) Kéo SO này vào field trên GameObject có <see cref="CECUIManager"/> (Awake gọi SetEmotionSpriteMap).
|
||||
/// 1) Tạo EmotionLibrarySO bằng Emotion Atlas Converter.
|
||||
/// 2) Gán TMP_SpriteAsset cho từng set vào EmotionSetSnapshot.TmpSpriteAsset trong Library
|
||||
/// (chỉ cần cho emoji động nhiều frame hoặc khi PreferSpriteNameTag = false).
|
||||
/// 3) Kéo SO này vào field trên GameObject có CECUIManager (Awake gọi SetEmotionSpriteMap).
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "EmotionLibrarySpriteMap", menuName = "Perfect World/Chat/Emotion Library Sprite Map", order = 2)]
|
||||
public class EmotionLibrarySpriteMap : ScriptableObject, IEmotionSpriteMap
|
||||
{
|
||||
[Tooltip("Dữ liệu emotion đã convert (Entries + FrameSprites).")]
|
||||
[Tooltip("Dữ liệu emotion đã convert. Emotion data converted by the atlas tool.")]
|
||||
public EmotionLibrarySO Library;
|
||||
|
||||
[Tooltip("Sprite Asset dùng cho chat TMP — cùng tên sub-sprite với atlas (cell_XXXX). Bắt buộc cho emoji động (anim) hoặc khi không dùng name tag.")]
|
||||
public TMP_SpriteAsset TmpSpriteAsset;
|
||||
|
||||
[Tooltip("Nếu true và chỉ 1 frame: thử <sprite name=\"...\"> (không cần tra index). Động (nhiều frame) vẫn cần TmpSpriteAsset để tra index.")]
|
||||
[Tooltip("Nếu true và emoji chỉ 1 frame: dùng <sprite name=\"cell_XXXX\"> thay vì <sprite index=N>. " +
|
||||
"If true and single-frame: use <sprite name=\"cell_XXXX\"> instead of <sprite index=N>.")]
|
||||
public bool PreferSpriteNameTag = true;
|
||||
|
||||
[Tooltip("FPS mặc định cho <sprite anim> khi không suy ra được từ FrameTicks.")]
|
||||
[Tooltip("FPS mặc định cho <sprite anim> khi không suy ra được từ FrameTicks. " +
|
||||
"Default FPS for <sprite anim> when it cannot be derived from FrameTicks.")]
|
||||
public int DefaultAnimFps = 10;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// IEmotionSpriteMap
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
public bool TryGetSprite(int emotionSet, int emotionIndex, out EmotionSpriteInfo info)
|
||||
{
|
||||
info = default;
|
||||
|
||||
if (Library == null)
|
||||
{
|
||||
Debug.LogWarning($"[EmotionLibrarySpriteMap] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: Library == null. Gán EmotionLibrarySO vào field Library.");
|
||||
return false;
|
||||
}
|
||||
|
||||
EmotionSetSnapshot set = Library.GetSetOrNull(emotionSet);
|
||||
if (set == null || emotionIndex < 0 || emotionIndex >= set.Entries.Count)
|
||||
if (set == null)
|
||||
{
|
||||
Debug.LogWarning($"[EmotionLibrarySpriteMap] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: Không tìm thấy set index={emotionSet}. " +
|
||||
$"Sets hiện có: [{string.Join(", ", Library.Sets?.ConvertAll(s => s?.EmotionSetIndex.ToString() ?? "null") ?? new System.Collections.Generic.List<string>())}]");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (emotionIndex < 0 || emotionIndex >= set.Entries.Count)
|
||||
{
|
||||
Debug.LogWarning($"[EmotionLibrarySpriteMap] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: emotionIndex={emotionIndex} ngoài range [0, {set.Entries.Count - 1}].");
|
||||
return false;
|
||||
}
|
||||
|
||||
EmotionEntryData entry = set.Entries[emotionIndex];
|
||||
Sprite[] frames = entry.FrameSprites;
|
||||
if (frames == null || frames.Length == 0)
|
||||
return false;
|
||||
|
||||
if (frames.Length == 1)
|
||||
{
|
||||
Sprite s0 = frames[0];
|
||||
if (s0 == null)
|
||||
return false;
|
||||
Debug.LogWarning($"[EmotionLibrarySpriteMap] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: entry.FrameSprites null/empty. Chạy lại Emotion Atlas Converter để populate FrameSprites.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PreferSpriteNameTag && !string.IsNullOrEmpty(s0.name))
|
||||
// Dùng StartPos/NumFrames từ txt (khớp EmotionAtlasConverterCore: cellIndex = StartPos + f).
|
||||
// Use StartPos/NumFrames from txt (matches EmotionAtlasConverterCore: cellIndex = StartPos + f).
|
||||
// Không đọc Sprite.name — GetName() chỉ được gọi trên main thread (vd. gói chat từ network thread).
|
||||
// Do not read Sprite.name — GetName() is main-thread-only (e.g. chat packets on network thread).
|
||||
int startCell = entry.StartPos;
|
||||
int frameCount = frames.Length;
|
||||
if (startCell < 0 || frameCount < 1)
|
||||
{
|
||||
Debug.LogWarning($"[EmotionLibrarySpriteMap] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: StartPos={startCell} hoặc không có frame.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ── Single frame ──────────────────────────────────────────────────
|
||||
if (frameCount == 1)
|
||||
{
|
||||
if (frames[0] == null)
|
||||
{
|
||||
info = new EmotionSpriteInfo
|
||||
{
|
||||
UseSpriteName = true,
|
||||
SpriteName = s0.name,
|
||||
IsAnimated = false
|
||||
};
|
||||
Debug.LogWarning($"[EmotionLibrarySpriteMap] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: frames[0] == null.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PreferSpriteNameTag)
|
||||
{
|
||||
// <sprite name="cell_XXXX"> — TMP tra tên trong SpriteAsset được gán trên component.
|
||||
// <sprite name="cell_XXXX"> — TMP looks up the name in the SpriteAsset on the component.
|
||||
info = new EmotionSpriteInfo { UseSpriteName = true, SpriteName = FormatCellSpriteName(startCell), IsAnimated = false };
|
||||
return true;
|
||||
}
|
||||
|
||||
int idx = FindSpriteIndexInTmpAsset(s0.name);
|
||||
if (idx < 0)
|
||||
return false;
|
||||
|
||||
info = new EmotionSpriteInfo { SpriteIndex = idx, IsAnimated = false };
|
||||
info = new EmotionSpriteInfo { SpriteIndex = startCell, IsAnimated = false };
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TmpSpriteAsset == null)
|
||||
return false;
|
||||
// ── Multi-frame (animated) ────────────────────────────────────────
|
||||
int endCell = startCell + frameCount - 1;
|
||||
|
||||
int start = FindSpriteIndexInTmpAsset(frames[0]?.name);
|
||||
int end = FindSpriteIndexInTmpAsset(frames[frames.Length - 1]?.name);
|
||||
if (start < 0 || end < 0)
|
||||
return false;
|
||||
|
||||
int fps = EstimateFps(entry.FrameTicks, frames.Length);
|
||||
int fps = EstimateFps(entry.FrameTicks, frameCount);
|
||||
info = new EmotionSpriteInfo
|
||||
{
|
||||
SpriteIndex = start,
|
||||
IsAnimated = true,
|
||||
AnimEndFrame = end,
|
||||
AnimFPS = fps > 0 ? fps : DefaultAnimFps
|
||||
SpriteIndex = startCell,
|
||||
IsAnimated = true,
|
||||
AnimEndFrame = endCell,
|
||||
AnimFPS = fps > 0 ? fps : DefaultAnimFps
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
private int FindSpriteIndexInTmpAsset(string spriteName)
|
||||
{
|
||||
if (TmpSpriteAsset == null || string.IsNullOrEmpty(spriteName))
|
||||
return -1;
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Helpers
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
if (TmpSpriteAsset.spriteInfoList != null)
|
||||
{
|
||||
for (int i = 0; i < TmpSpriteAsset.spriteInfoList.Count; i++)
|
||||
{
|
||||
var s = TmpSpriteAsset.spriteInfoList[i];
|
||||
if (s != null && s.name == spriteName)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
/// <summary>
|
||||
/// Tên ô giống tool atlas: "cell_XXXX" (4 chữ số). Không đọc Unity <c>Sprite.name</c>.
|
||||
/// Same cell naming as atlas tool: "cell_XXXX" (4 digits). Does not read Unity Sprite.name.
|
||||
/// </summary>
|
||||
private static string FormatCellSpriteName(int cellIndex) => $"cell_{cellIndex:D4}";
|
||||
|
||||
private static int EstimateFps(int[] frameTicks, int numFrames)
|
||||
{
|
||||
if (frameTicks == null || numFrames < 2 || frameTicks.Length < numFrames)
|
||||
return 0;
|
||||
int last = frameTicks[numFrames - 1];
|
||||
int first = frameTicks[0];
|
||||
int span = last - first;
|
||||
int span = frameTicks[numFrames - 1] - frameTicks[0];
|
||||
if (span <= 0)
|
||||
return 0;
|
||||
return Mathf.Max(1, Mathf.RoundToInt((numFrames - 1) * 60f / span));
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BrewMonster.Scripts.Chat
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper tĩnh: chuyển EmotionSpriteInfo → TMP rich-text tag và insert vào TMP_InputField.
|
||||
/// Static helper: converts EmotionSpriteInfo → TMP rich-text tag and inserts it into a TMP_InputField.
|
||||
///
|
||||
/// Cách dùng điển hình / Typical usage:
|
||||
/// <code>
|
||||
/// // Khi người dùng bấm chọn emoji từ picker:
|
||||
/// // When user taps an emoji in the picker:
|
||||
/// EmotionTMPTagBuilder.InsertEmotionTag(_chatInputField, _spriteMap, emotionSet, emotionIndex);
|
||||
///
|
||||
/// // Hoặc chỉ lấy tag string rồi tự xử lý:
|
||||
/// // Or just get the tag string and handle it yourself:
|
||||
/// if (EmotionTMPTagBuilder.TryBuildEmotionTag(_spriteMap, set, idx, out string tag))
|
||||
/// Debug.Log(tag); // → "<sprite name="cell_0005">" hoặc "<sprite anim="4,7,10">"
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public static class EmotionTMPTagBuilder
|
||||
{
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Tag builders
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Tạo TMP rich-text tag từ EmotionSpriteInfo đã resolve.
|
||||
/// Build TMP rich-text tag from an already-resolved EmotionSpriteInfo.
|
||||
///
|
||||
/// Thứ tự ưu tiên / Priority:
|
||||
/// 1. UseSpriteName → <sprite name="…">
|
||||
/// 2. IsAnimated → <sprite anim="start,end,fps">
|
||||
/// 3. (fallback) → <sprite index=N>
|
||||
/// </summary>
|
||||
public static string BuildSpriteTag(EmotionSpriteInfo info)
|
||||
{
|
||||
if (info.UseSpriteName && !string.IsNullOrEmpty(info.SpriteName))
|
||||
return $"<sprite name=\"{info.SpriteName}\">";
|
||||
|
||||
if (info.IsAnimated)
|
||||
return $"<sprite anim=\"{info.SpriteIndex},{info.AnimEndFrame},{info.AnimFPS}\">";
|
||||
|
||||
return $"<sprite index={info.SpriteIndex}>";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tra bảng spriteMap rồi tạo TMP tag.
|
||||
/// Lookup spriteMap then build the TMP tag.
|
||||
/// </summary>
|
||||
/// <param name="spriteMap">Bảng ánh xạ emotion → sprite. Emotion-to-sprite mapping.</param>
|
||||
/// <param name="emotionSet">Chỉ số bộ (N trong Emotions{N}). Emotion set index.</param>
|
||||
/// <param name="emotionIndex">Chỉ số emotion trong bộ (dòng trong .txt). Emotion index within set.</param>
|
||||
/// <param name="tag">TMP tag được tạo ra nếu thành công. Built TMP tag on success.</param>
|
||||
/// <returns>true nếu tạo được tag, false nếu không tìm thấy emotion. true if tag was built.</returns>
|
||||
public static bool TryBuildEmotionTag(IEmotionSpriteMap spriteMap, int emotionSet, int emotionIndex, out string tag)
|
||||
{
|
||||
tag = string.Empty;
|
||||
if (spriteMap == null)
|
||||
{
|
||||
Debug.LogWarning("[Cuong] TryBuildEmotionTag spriteMap is null.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!spriteMap.TryGetSprite(emotionSet, emotionIndex, out EmotionSpriteInfo info))
|
||||
{
|
||||
Debug.LogWarning("[Cuong] TryBuildEmotionTag.");
|
||||
return false;
|
||||
}
|
||||
tag = BuildSpriteTag(info);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// TMP_InputField helpers
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Insert TMP emotion tag vào TMP_InputField tại vị trí caret hiện tại.
|
||||
/// Insert the TMP emotion tag into a TMP_InputField at the current caret position.
|
||||
/// </summary>
|
||||
/// <param name="inputField">InputField đang nhập chat. The active chat input field.</param>
|
||||
/// <param name="spriteMap">Bảng ánh xạ. Sprite map.</param>
|
||||
/// <param name="emotionSet">Chỉ số bộ. Emotion set index.</param>
|
||||
/// <param name="emotionIndex">Chỉ số emotion. Emotion index within set.</param>
|
||||
/// <returns>true nếu tag đã được insert. true if the tag was inserted.</returns>
|
||||
public static bool InsertEmotionTag(TMP_InputField inputField, IEmotionSpriteMap spriteMap, int emotionSet, int emotionIndex)
|
||||
{
|
||||
if (inputField == null)
|
||||
{
|
||||
BMLogger.LogWarning("[Cuong] InsertEmotionTag inputField is null.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryBuildEmotionTag(spriteMap, emotionSet, emotionIndex, out string tag))
|
||||
{
|
||||
BMLogger.LogWarning($"[Cuong] TryBuildEmotionTag false {emotionSet} {emotionIndex} {tag}");
|
||||
return false;
|
||||
}
|
||||
|
||||
InsertTagAtCaret(inputField, tag);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert tag tại vị trí caret, cập nhật caret sau khi insert.
|
||||
/// Insert tag at caret position, advance caret after insertion.
|
||||
/// </summary>
|
||||
public static void InsertTagAtCaret(TMP_InputField inputField, string tag)
|
||||
{
|
||||
if (inputField == null || string.IsNullOrEmpty(tag))
|
||||
return;
|
||||
|
||||
string text = inputField.text ?? string.Empty;
|
||||
int caret = Mathf.Clamp(inputField.caretPosition, 0, text.Length);
|
||||
|
||||
inputField.text = text.Insert(caret, tag);
|
||||
|
||||
// Đặt caret sau tag vừa insert — Move caret to end of inserted tag
|
||||
inputField.caretPosition = caret + tag.Length;
|
||||
inputField.ForceLabelUpdate();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Diagnostics
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Log toàn bộ emotion set/index có trong spriteMap ra Console — dùng debug.
|
||||
/// Log all emotion set/index entries in spriteMap to Console — for debugging.
|
||||
/// </summary>
|
||||
public static void DebugLogAllEntries(IEmotionSpriteMap spriteMap, int maxSets = 10, int maxEmotionsPerSet = 50)
|
||||
{
|
||||
if (spriteMap == null)
|
||||
{
|
||||
Debug.LogWarning("[Cuong] DebugLogAllEntries spriteMap is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine("[EmotionTMPTagBuilder] All entries:");
|
||||
|
||||
for (int s = 0; s < maxSets; s++)
|
||||
{
|
||||
for (int e = 0; e < maxEmotionsPerSet; e++)
|
||||
{
|
||||
if (spriteMap.TryGetSprite(s, e, out EmotionSpriteInfo info))
|
||||
sb.AppendLine($" set={s} idx={e} → {BuildSpriteTag(info)}");
|
||||
}
|
||||
}
|
||||
|
||||
BMLogger.Log(sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 200b07ddd8d07064b9e6896fda84dafe
|
||||
@@ -1,3 +1,5 @@
|
||||
using BrewMonster;
|
||||
|
||||
/// <summary>
|
||||
/// Stub实现 — 在没有真实TMP_SpriteAsset(emotion atlas)时使用的占位映射。
|
||||
/// Stub implementation used when no real TMP_SpriteAsset (emotion atlas) is available yet.
|
||||
@@ -15,7 +17,7 @@ public class StubEmotionSpriteMap : IEmotionSpriteMap
|
||||
public bool TryGetSprite(int emotionSet, int emotionIndex, out EmotionSpriteInfo info)
|
||||
{
|
||||
int startFrame = (emotionSet * EMOTIONS_PER_SET + emotionIndex) * FRAMES_PER_EMOTION;
|
||||
|
||||
BMLogger.Log("[Cuong] TryGetSprite");
|
||||
info = new EmotionSpriteInfo
|
||||
{
|
||||
SpriteIndex = startFrame,
|
||||
|
||||
@@ -17,21 +17,14 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
|
||||
public class ChatSystemlUI : MonoBehaviour
|
||||
{
|
||||
[Header("MiniChat")]
|
||||
public Button onOpenChatPanelButton;
|
||||
[Tooltip("Parent cho các dòng tin xem trước (nên có VerticalLayoutGroup).")]
|
||||
public RectTransform miniChatContent;
|
||||
[Tooltip("Null = dùng messagePrefab.")]
|
||||
public ChatMessageView miniMessagePrefab;
|
||||
[SerializeField] int miniChatPreviewLines = 5;
|
||||
[Tooltip("Tắt raycast trên dòng preview để click xuyên xuống onOpenChatPanelButton (TMP/Image mặc định chặn Button).")]
|
||||
[SerializeField] bool miniChatPassThroughClicks = true;
|
||||
|
||||
[Header("ChatPanelUI")] public ScrollRect scrollRect;
|
||||
[Tooltip("Nền/fullscreen block — bật/tắt cùng lúc với chatPanelUIGO (có thể để null).")]
|
||||
public GameObject BgGameObject;
|
||||
public GameObject chatPanelUIGO;
|
||||
public RectTransform content;
|
||||
public ChatMessageView messagePrefab;
|
||||
public Button closeChatPanelButton;
|
||||
public Button closeBGChatPanelButton;
|
||||
public Button emojiButton;
|
||||
public Button sendButton;
|
||||
|
||||
@@ -49,9 +42,6 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
private List<ChatMessageData> _messages = new();
|
||||
private List<ChatMessageView> _visibleViews = new();
|
||||
private List<ChatMessageData> _filteredMessagesCache = new();
|
||||
private readonly List<ChatMessageData> _miniFilterBuffer = new();
|
||||
private readonly List<ChatMessageView> _miniChatViews = new();
|
||||
|
||||
private ObjectPool<ChatMessageView> _pool;
|
||||
|
||||
private bool _userAtBottom = true;
|
||||
@@ -74,6 +64,7 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
EventBus.Subscribe<OnEventClearChat>(OnChatMessageClear);
|
||||
EventBus.Subscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
|
||||
EventBus.Subscribe<ChatChannelFilterChangedEvent>(OnChannelFilterChanged);
|
||||
EventBus.Subscribe<OpenChatPanelRequestedEvent>(OnOpenChatPanelRequested);
|
||||
_pool = new ObjectPool<ChatMessageView>(
|
||||
CreateItem,
|
||||
OnGetItem,
|
||||
@@ -86,13 +77,20 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
|
||||
scrollRect.onValueChanged.AddListener(OnScrollChanged);
|
||||
|
||||
SetChatPanelAndBgVisible(false);
|
||||
}
|
||||
|
||||
/// <summary>Bật/tắt panel chat và BG cùng trạng thái (2 GO tách nhưng luồng giống nhau).</summary>
|
||||
void SetChatPanelAndBgVisible(bool visible)
|
||||
{
|
||||
if (chatPanelUIGO != null)
|
||||
chatPanelUIGO.SetActive(false);
|
||||
chatPanelUIGO.SetActive(visible);
|
||||
if (BgGameObject != null)
|
||||
BgGameObject.SetActive(visible);
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
RefreshMiniChat();
|
||||
if (chatPanelUIGO == null || !chatPanelUIGO.activeSelf || _pool == null || content == null)
|
||||
return;
|
||||
|
||||
@@ -107,10 +105,10 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
|
||||
void WireUiButtons()
|
||||
{
|
||||
if (onOpenChatPanelButton != null)
|
||||
onOpenChatPanelButton.onClick.AddListener(OpenChatPanel);
|
||||
if (closeChatPanelButton != null)
|
||||
closeChatPanelButton.onClick.AddListener(CloseChatPanel);
|
||||
if (closeBGChatPanelButton != null)
|
||||
closeBGChatPanelButton.onClick.AddListener(CloseChatPanel);
|
||||
if (emojiButton != null)
|
||||
emojiButton.onClick.AddListener(ToggleEmojiPanel);
|
||||
if (closeEmojiPanelButton != null)
|
||||
@@ -121,10 +119,10 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (onOpenChatPanelButton != null)
|
||||
onOpenChatPanelButton.onClick.RemoveListener(OpenChatPanel);
|
||||
if (closeChatPanelButton != null)
|
||||
closeChatPanelButton.onClick.RemoveListener(CloseChatPanel);
|
||||
if (closeBGChatPanelButton != null)
|
||||
closeBGChatPanelButton.onClick.RemoveListener(CloseChatPanel);
|
||||
if (emojiButton != null)
|
||||
emojiButton.onClick.RemoveListener(ToggleEmojiPanel);
|
||||
if (closeEmojiPanelButton != null)
|
||||
@@ -135,25 +133,29 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
EventBus.Unsubscribe<OnEventClearChat>(OnChatMessageClear);
|
||||
EventBus.Unsubscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
|
||||
EventBus.Unsubscribe<ChatChannelFilterChangedEvent>(OnChannelFilterChanged);
|
||||
EventBus.Unsubscribe<OpenChatPanelRequestedEvent>(OnOpenChatPanelRequested);
|
||||
|
||||
if (_pool != null)
|
||||
{
|
||||
_pool.Clear();
|
||||
_pool.Dispose();
|
||||
}
|
||||
|
||||
ClearMiniChatViews();
|
||||
}
|
||||
|
||||
private void OnChannelFilterChanged(ChatChannelFilterChangedEvent e)
|
||||
{
|
||||
if (this == null) return;
|
||||
_currentFilterChannel = e.channel;
|
||||
RefreshMiniChat();
|
||||
if (chatPanelUIGO != null && chatPanelUIGO.activeSelf)
|
||||
RefreshVisible();
|
||||
}
|
||||
|
||||
void OnOpenChatPanelRequested(OpenChatPanelRequestedEvent _)
|
||||
{
|
||||
if (this == null) return;
|
||||
OpenChatPanel();
|
||||
}
|
||||
|
||||
private bool ShouldShowMessage(ChatMessageData data)
|
||||
{
|
||||
if (_currentFilterChannel == ChatChannel.GP_CHAT_LOCAL) return true;
|
||||
@@ -226,8 +228,6 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
if (_messages.Count > maxStoredMessages)
|
||||
_messages.RemoveAt(0);
|
||||
|
||||
RefreshMiniChat();
|
||||
|
||||
if (chatPanelUIGO == null || !chatPanelUIGO.activeSelf)
|
||||
return;
|
||||
|
||||
@@ -309,68 +309,6 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
ScrollToBottom();
|
||||
}
|
||||
|
||||
void RefreshMiniChat()
|
||||
{
|
||||
if (miniChatContent == null) return;
|
||||
|
||||
_miniFilterBuffer.Clear();
|
||||
foreach (var msg in _messages)
|
||||
{
|
||||
if (ShouldShowMessage(msg))
|
||||
_miniFilterBuffer.Add(msg);
|
||||
}
|
||||
|
||||
int take = Mathf.Min(Mathf.Max(1, miniChatPreviewLines), _miniFilterBuffer.Count);
|
||||
int startIdx = _miniFilterBuffer.Count - take;
|
||||
|
||||
while (_miniChatViews.Count < take)
|
||||
{
|
||||
var v = CreateMiniChatItem();
|
||||
if (v == null) return;
|
||||
_miniChatViews.Add(v);
|
||||
}
|
||||
|
||||
while (_miniChatViews.Count > take)
|
||||
{
|
||||
var last = _miniChatViews[_miniChatViews.Count - 1];
|
||||
_miniChatViews.RemoveAt(_miniChatViews.Count - 1);
|
||||
if (last != null)
|
||||
Destroy(last.gameObject);
|
||||
}
|
||||
|
||||
for (int i = 0; i < take; i++)
|
||||
{
|
||||
var data = _miniFilterBuffer[startIdx + i];
|
||||
var view = _miniChatViews[i];
|
||||
Sprite icon = _iconCache.ContainsKey(data.channel) ? _iconCache[data.channel] : null;
|
||||
view.gameObject.SetActive(true);
|
||||
view.transform.SetSiblingIndex(i);
|
||||
if (miniChatPassThroughClicks)
|
||||
DisableGraphicsRaycastUnder(view.transform);
|
||||
view.Bind(icon, data.message);
|
||||
}
|
||||
|
||||
Canvas.ForceUpdateCanvases();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mini chat rows use TMP/Image with raycastTarget on — they sit above the open button and steal clicks.
|
||||
/// </summary>
|
||||
static void DisableGraphicsRaycastUnder(Transform root)
|
||||
{
|
||||
if (root == null) return;
|
||||
foreach (var g in root.GetComponentsInChildren<Graphic>(true))
|
||||
g.raycastTarget = false;
|
||||
}
|
||||
|
||||
ChatMessageView CreateMiniChatItem()
|
||||
{
|
||||
var prefab = miniMessagePrefab != null ? miniMessagePrefab : messagePrefab;
|
||||
if (prefab == null || miniChatContent == null) return null;
|
||||
var item = Instantiate(prefab, miniChatContent, false);
|
||||
return item;
|
||||
}
|
||||
|
||||
public void ScrollToBottom()
|
||||
{
|
||||
Canvas.ForceUpdateCanvases();
|
||||
@@ -387,24 +325,13 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
|
||||
_visibleViews.Clear();
|
||||
_messages.Clear();
|
||||
ClearMiniChatViews();
|
||||
}
|
||||
|
||||
void ClearMiniChatViews()
|
||||
{
|
||||
foreach (var v in _miniChatViews)
|
||||
{
|
||||
if (v != null)
|
||||
Destroy(v.gameObject);
|
||||
}
|
||||
|
||||
_miniChatViews.Clear();
|
||||
}
|
||||
|
||||
public void OnHandlerChatButton()
|
||||
{
|
||||
if (chatPanelUIGO == null) return;
|
||||
bool open = !chatPanelUIGO.activeSelf;
|
||||
chatPanelUIGO.SetActive(open);
|
||||
SetChatPanelAndBgVisible(open);
|
||||
|
||||
if (open)
|
||||
RefreshVisible();
|
||||
@@ -415,7 +342,7 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
public void OpenChatPanel()
|
||||
{
|
||||
if (chatPanelUIGO == null) return;
|
||||
chatPanelUIGO.SetActive(true);
|
||||
SetChatPanelAndBgVisible(true);
|
||||
RefreshVisible();
|
||||
|
||||
_chatInput ??= GetComponent<ChatInputHandler>();
|
||||
@@ -425,8 +352,7 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
|
||||
public void CloseChatPanel()
|
||||
{
|
||||
if (chatPanelUIGO == null) return;
|
||||
chatPanelUIGO.SetActive(false);
|
||||
SetChatPanelAndBgVisible(false);
|
||||
SetEmojiPanelVisible(false);
|
||||
}
|
||||
|
||||
@@ -454,5 +380,10 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
}
|
||||
}
|
||||
|
||||
public struct OnEventClearChat{}
|
||||
public struct OnEventClearChat { }
|
||||
|
||||
/// <summary>
|
||||
/// Mini chat (hoặc HUD) publish để mở panel chat đầy đủ; <see cref="ChatSystemlUI"/> subscribe.
|
||||
/// </summary>
|
||||
public struct OpenChatPanelRequestedEvent { }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace BrewMonster.Scripts.ChatUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Một ô emoji trong bảng chọn emoji — One emoji cell in the emoji picker grid.
|
||||
/// Gán lên prefab Button có Image + (tuỳ chọn) TMP_Text cho tooltip.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Button))]
|
||||
public class EmojiButtonCell : MonoBehaviour
|
||||
{
|
||||
[SerializeField] Image _icon;
|
||||
[Tooltip("Tuỳ chọn: hiện hint text (tên emoji). Optional: show hint/tooltip text.")]
|
||||
|
||||
public event Action<int, int> OnClicked; // (emotionSet, emotionIndex)
|
||||
|
||||
private int _emotionSet;
|
||||
private int _emotionIndex;
|
||||
private Button _button;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_button = GetComponent<Button>();
|
||||
if (_icon == null)
|
||||
_icon = GetComponentInChildren<Image>(true);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (_button != null)
|
||||
_button.onClick.AddListener(HandleClick);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (_button != null)
|
||||
_button.onClick.RemoveListener(HandleClick);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gán dữ liệu cho ô — Bind emotion data to this cell.
|
||||
/// </summary>
|
||||
/// <param name="emotionSet">Chỉ số bộ (N trong Emotions{N}.txt).</param>
|
||||
/// <param name="emotionIndex">Chỉ số emotion trong bộ (dòng trong .txt).</param>
|
||||
/// <param name="icon">Sprite frame đầu tiên để hiển thị (có thể null). First frame sprite.</param>
|
||||
/// <param name="hint">Tên / tooltip. Hint / tooltip text.</param>
|
||||
public void Bind(int emotionSet, int emotionIndex, Sprite icon, string hint)
|
||||
{
|
||||
_emotionSet = emotionSet;
|
||||
_emotionIndex = emotionIndex;
|
||||
|
||||
if (_icon != null)
|
||||
{
|
||||
_icon.enabled = icon != null;
|
||||
_icon.sprite = icon;
|
||||
}
|
||||
|
||||
if (_button != null)
|
||||
_button.interactable = icon != null;
|
||||
}
|
||||
|
||||
private void HandleClick()
|
||||
{
|
||||
OnClicked?.Invoke(_emotionSet, _emotionIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad6cc9a7d46da8949bae44dfcf3fe03d
|
||||
@@ -0,0 +1,187 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BrewMonster.Scripts.Chat;
|
||||
using BrewMonster.Scripts.Chat.EmotionData;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace BrewMonster.Scripts.ChatUI
|
||||
{
|
||||
/// <summary>
|
||||
/// MonoBehaviour gắn trên EmojiPanel — quản lý lưới emoji để người chơi chọn.
|
||||
/// MonoBehaviour placed on EmojiPanel — manages the emoji grid for player selection.
|
||||
///
|
||||
/// Cách dùng trên Inspector:
|
||||
/// 1) Kéo component này lên EmojiPanel GameObject trong prefab.
|
||||
/// 2) Gán EmotionLibrarySpriteMap SO (đã build từ Emotion Atlas Converter).
|
||||
/// 3) Gán GridContent = child "Context" của EmojiPanel.
|
||||
/// 4) Tạo prefab EmojiButtonCell → gán vào CellPrefab.
|
||||
/// 5) Gán ChatInputHandler và ChatSystemlUI.
|
||||
/// </summary>
|
||||
public class EmojiPickerUI : MonoBehaviour
|
||||
{
|
||||
[Header("Emotion Data")]
|
||||
[Tooltip("SO chứa Library + TMP Sprite Asset. Tạo qua Perfect World → Chat → Emotion Atlas Converter.")]
|
||||
[SerializeField] EmotionLibrarySpriteMap _emotionSpriteMap;
|
||||
|
||||
[Header("Grid")]
|
||||
[Tooltip("RectTransform chứa các ô emoji (child 'Context' của EmojiPanel). GridLayoutGroup được thêm tự động nếu chưa có.")]
|
||||
[SerializeField] RectTransform _gridContent;
|
||||
|
||||
[Tooltip("Prefab một ô emoji (Button + Image + EmojiButtonCell). Cell prefab.")]
|
||||
[SerializeField] EmojiButtonCell _cellPrefab;
|
||||
|
||||
[Tooltip("Kích thước mỗi ô (pixel). Cell size in pixels.")]
|
||||
[SerializeField] Vector2 _cellSize = new Vector2(64f, 64f);
|
||||
|
||||
[Tooltip("Khoảng cách giữa các ô. Spacing between cells.")]
|
||||
[SerializeField] Vector2 _cellSpacing = new Vector2(4f, 4f);
|
||||
|
||||
[Tooltip("Số cột cố định. Fixed column count.")]
|
||||
[SerializeField] int _columnCount = 8;
|
||||
|
||||
[Header("Wiring")]
|
||||
[Tooltip("ChatInputHandler để nhét emoji code vào ô nhập liệu.")]
|
||||
[SerializeField] ChatInputHandler _chatInput;
|
||||
|
||||
[Tooltip("ChatSystemlUI để đóng EmojiPanel sau khi chọn.")]
|
||||
[SerializeField] ChatSystemlUI _chatSystemUI;
|
||||
|
||||
private readonly List<EmojiButtonCell> _cells = new();
|
||||
private bool _built;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
TryAutoWireIfNeeded();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
BuildGrid();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (!_built)
|
||||
BuildGrid();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Xây dựng lưới emoji từ EmotionLibrarySpriteMap.
|
||||
/// Build emoji grid from EmotionLibrarySpriteMap.
|
||||
/// </summary>
|
||||
public void BuildGrid()
|
||||
{
|
||||
ClearGrid();
|
||||
TryAutoWireIfNeeded();
|
||||
|
||||
if (_gridContent == null || _cellPrefab == null)
|
||||
{
|
||||
Debug.LogWarning("[Cuong] GridContent hoặc CellPrefab chưa được gán.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_emotionSpriteMap == null || _emotionSpriteMap.Library == null)
|
||||
{
|
||||
Debug.LogWarning("[Cuong] EmotionLibrarySpriteMap chưa được gán hoặc chưa có Library.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureGridLayout();
|
||||
|
||||
_gridContent.gameObject.SetActive(true);
|
||||
|
||||
// Keep UI stable: always render in set index order.
|
||||
foreach (var set in _emotionSpriteMap.Library.Sets.OrderBy(x => x?.EmotionSetIndex ?? int.MaxValue))
|
||||
{
|
||||
if (set == null) continue;
|
||||
if (set.Entries == null || set.Entries.Count == 0) continue;
|
||||
|
||||
for (int i = 0; i < set.Entries.Count; i++)
|
||||
{
|
||||
var entry = set.Entries[i];
|
||||
if (entry == null) continue;
|
||||
|
||||
Sprite icon = (entry.FrameSprites != null && entry.FrameSprites.Length > 0)
|
||||
? entry.FrameSprites[0]
|
||||
: null;
|
||||
|
||||
var cell = Instantiate(_cellPrefab, _gridContent, false);
|
||||
cell.Bind(set.EmotionSetIndex, i, icon, entry.Hint);
|
||||
cell.OnClicked += HandleEmojiClicked;
|
||||
_cells.Add(cell);
|
||||
}
|
||||
}
|
||||
|
||||
_built = true;
|
||||
}
|
||||
|
||||
private void ClearGrid()
|
||||
{
|
||||
foreach (var c in _cells)
|
||||
{
|
||||
if (c == null) continue;
|
||||
c.OnClicked -= HandleEmojiClicked;
|
||||
Destroy(c.gameObject);
|
||||
}
|
||||
_cells.Clear();
|
||||
_built = false;
|
||||
}
|
||||
|
||||
private void TryAutoWireIfNeeded()
|
||||
{
|
||||
_chatInput ??= GetComponentInParent<ChatInputHandler>();
|
||||
_chatSystemUI ??= GetComponentInParent<ChatSystemlUI>();
|
||||
}
|
||||
|
||||
private void EnsureGridLayout()
|
||||
{
|
||||
var layout = _gridContent.GetComponent<GridLayoutGroup>();
|
||||
if (layout == null)
|
||||
layout = _gridContent.gameObject.AddComponent<GridLayoutGroup>();
|
||||
|
||||
layout.cellSize = _cellSize;
|
||||
layout.spacing = _cellSpacing;
|
||||
layout.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
|
||||
layout.constraintCount = _columnCount;
|
||||
layout.startCorner = GridLayoutGroup.Corner.UpperLeft;
|
||||
layout.startAxis = GridLayoutGroup.Axis.Horizontal;
|
||||
layout.childAlignment = TextAnchor.UpperLeft;
|
||||
|
||||
var fitter = _gridContent.GetComponent<ContentSizeFitter>();
|
||||
if (fitter == null)
|
||||
fitter = _gridContent.gameObject.AddComponent<ContentSizeFitter>();
|
||||
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gọi khi người chơi click một ô emoji — Called when player clicks an emoji cell.
|
||||
/// Chèn mã emoji vào input field và đóng panel.
|
||||
/// Inserts the emotion code into the chat input and closes the panel.
|
||||
/// </summary>
|
||||
private void HandleEmojiClicked(int emotionSet, int emotionIndex)
|
||||
{
|
||||
TryAutoWireIfNeeded();
|
||||
if (_chatInput != null)
|
||||
{
|
||||
EmotionTMPTagBuilder.InsertEmotionTag(_chatInput.inputField, _emotionSpriteMap, emotionSet,
|
||||
emotionIndex);
|
||||
Debug.Log("[Cuong] HandleEmojiClicked.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[Cuong] Không tìm thấy ChatInputHandler để insert emoji.", this);
|
||||
}
|
||||
|
||||
/*if (_chatSystemUI != null)
|
||||
{
|
||||
_chatSystemUI.CloseEmojiPanel();
|
||||
}*/
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ClearGrid();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a53a5ea5bc85bc449775b64714b421f
|
||||
@@ -0,0 +1,201 @@
|
||||
using System.Collections.Generic;
|
||||
using CSNetwork;
|
||||
using CSNetwork.GPDataType;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace BrewMonster.Scripts.ChatUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Vùng xem trước chat nhỏ; đồng bộ qua EventBus với tin nhắn và bộ lọc kênh.
|
||||
/// Mở panel chat đầy đủ qua <see cref="OpenChatPanelRequestedEvent"/>.
|
||||
/// </summary>
|
||||
public class MiniChatUI : MonoBehaviour
|
||||
{
|
||||
[Header("MiniChat")]
|
||||
public Button onOpenChatPanelButton;
|
||||
[Tooltip("Parent cho các dòng tin xem trước (nên có VerticalLayoutGroup).")]
|
||||
public RectTransform miniChatContent;
|
||||
[Tooltip("Null = dùng messagePrefab (fallback).")]
|
||||
public ChatMessageView miniMessagePrefab;
|
||||
[Tooltip("Prefab dòng tin khi miniMessagePrefab null (giống messagePrefab panel chính).")]
|
||||
public ChatMessageView messagePrefab;
|
||||
[SerializeField] int miniChatPreviewLines = 5;
|
||||
[Tooltip("Tắt raycast trên dòng preview để click xuyên xuống onOpenChatPanelButton (TMP/Image mặc định chặn Button).")]
|
||||
[SerializeField] bool miniChatPassThroughClicks = true;
|
||||
[SerializeField] int maxStoredMessages = 2000;
|
||||
|
||||
[Header("Chat System Data")]
|
||||
public ChatSystemSO chatSystemSO;
|
||||
|
||||
readonly List<ChatMessageData> _messages = new();
|
||||
readonly List<ChatMessageData> _miniFilterBuffer = new();
|
||||
readonly List<ChatMessageView> _miniChatViews = new();
|
||||
Dictionary<byte, Sprite> _iconCache;
|
||||
ChatChannel _currentFilterChannel = ChatChannel.GP_CHAT_LOCAL;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_iconCache = new Dictionary<byte, Sprite>();
|
||||
if (chatSystemSO != null && chatSystemSO.channelIcons != null)
|
||||
{
|
||||
foreach (var mapping in chatSystemSO.channelIcons)
|
||||
_iconCache[(byte)mapping.channel] = mapping.icon;
|
||||
}
|
||||
|
||||
EventBus.Subscribe<OnEventClearChat>(OnChatMessageClear);
|
||||
EventBus.Subscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
|
||||
EventBus.Subscribe<ChatChannelFilterChangedEvent>(OnChannelFilterChanged);
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
RefreshMiniChat();
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (onOpenChatPanelButton != null)
|
||||
onOpenChatPanelButton.onClick.AddListener(OnOpenChatPanelButtonClicked);
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (onOpenChatPanelButton != null)
|
||||
onOpenChatPanelButton.onClick.RemoveListener(OnOpenChatPanelButtonClicked);
|
||||
|
||||
EventBus.Unsubscribe<OnEventClearChat>(OnChatMessageClear);
|
||||
EventBus.Unsubscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
|
||||
EventBus.Unsubscribe<ChatChannelFilterChangedEvent>(OnChannelFilterChanged);
|
||||
|
||||
ClearMiniChatViews();
|
||||
}
|
||||
|
||||
void OnOpenChatPanelButtonClicked()
|
||||
{
|
||||
EventBus.Publish(new OpenChatPanelRequestedEvent());
|
||||
}
|
||||
|
||||
void OnChannelFilterChanged(ChatChannelFilterChangedEvent e)
|
||||
{
|
||||
if (this == null) return;
|
||||
_currentFilterChannel = e.channel;
|
||||
RefreshMiniChat();
|
||||
}
|
||||
|
||||
bool ShouldShowMessage(ChatMessageData data)
|
||||
{
|
||||
if (_currentFilterChannel == ChatChannel.GP_CHAT_LOCAL) return true;
|
||||
if (data.channel == (byte)ChatChannel.GP_CHAT_MISC) return true;
|
||||
return data.channel == (byte)_currentFilterChannel;
|
||||
}
|
||||
|
||||
void OnChatMessageReceived(GameSession.ChatMessageEvent x)
|
||||
{
|
||||
ChatThreadDispatcher.Instance.Post(() =>
|
||||
{
|
||||
if (this == null) return;
|
||||
AddMessage(x.context, x.channel);
|
||||
});
|
||||
}
|
||||
|
||||
void OnChatMessageClear(OnEventClearChat obj)
|
||||
{
|
||||
ChatThreadDispatcher.Instance.Post(() =>
|
||||
{
|
||||
if (this == null) return;
|
||||
ClearChat();
|
||||
});
|
||||
}
|
||||
|
||||
void AddMessage(string msg, byte channel)
|
||||
{
|
||||
if (this == null) return;
|
||||
|
||||
var data = new ChatMessageData { message = msg, channel = channel };
|
||||
_messages.Add(data);
|
||||
if (_messages.Count > maxStoredMessages)
|
||||
_messages.RemoveAt(0);
|
||||
RefreshMiniChat();
|
||||
}
|
||||
|
||||
void RefreshMiniChat()
|
||||
{
|
||||
if (miniChatContent == null) return;
|
||||
|
||||
_miniFilterBuffer.Clear();
|
||||
foreach (var msg in _messages)
|
||||
{
|
||||
if (ShouldShowMessage(msg))
|
||||
_miniFilterBuffer.Add(msg);
|
||||
}
|
||||
|
||||
int take = Mathf.Min(Mathf.Max(1, miniChatPreviewLines), _miniFilterBuffer.Count);
|
||||
int startIdx = _miniFilterBuffer.Count - take;
|
||||
|
||||
while (_miniChatViews.Count < take)
|
||||
{
|
||||
var v = CreateMiniChatItem();
|
||||
if (v == null) return;
|
||||
_miniChatViews.Add(v);
|
||||
}
|
||||
|
||||
while (_miniChatViews.Count > take)
|
||||
{
|
||||
var last = _miniChatViews[_miniChatViews.Count - 1];
|
||||
_miniChatViews.RemoveAt(_miniChatViews.Count - 1);
|
||||
if (last != null)
|
||||
Destroy(last.gameObject);
|
||||
}
|
||||
|
||||
for (int i = 0; i < take; i++)
|
||||
{
|
||||
var data = _miniFilterBuffer[startIdx + i];
|
||||
var view = _miniChatViews[i];
|
||||
Sprite icon = _iconCache.ContainsKey(data.channel) ? _iconCache[data.channel] : null;
|
||||
view.gameObject.SetActive(true);
|
||||
view.transform.SetSiblingIndex(i);
|
||||
if (miniChatPassThroughClicks)
|
||||
DisableGraphicsRaycastUnder(view.transform);
|
||||
view.Bind(icon, data.message);
|
||||
}
|
||||
|
||||
Canvas.ForceUpdateCanvases();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mini chat rows use TMP/Image with raycastTarget on — they sit above the open button and steal clicks.
|
||||
/// </summary>
|
||||
static void DisableGraphicsRaycastUnder(Transform root)
|
||||
{
|
||||
if (root == null) return;
|
||||
foreach (var g in root.GetComponentsInChildren<Graphic>(true))
|
||||
g.raycastTarget = false;
|
||||
}
|
||||
|
||||
ChatMessageView CreateMiniChatItem()
|
||||
{
|
||||
var prefab = miniMessagePrefab != null ? miniMessagePrefab : messagePrefab;
|
||||
if (prefab == null || miniChatContent == null) return null;
|
||||
var item = Instantiate(prefab, miniChatContent, false);
|
||||
return item;
|
||||
}
|
||||
|
||||
void ClearChat()
|
||||
{
|
||||
_messages.Clear();
|
||||
ClearMiniChatViews();
|
||||
}
|
||||
|
||||
void ClearMiniChatViews()
|
||||
{
|
||||
foreach (var v in _miniChatViews)
|
||||
{
|
||||
if (v != null)
|
||||
Destroy(v.gameObject);
|
||||
}
|
||||
|
||||
_miniChatViews.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c3012f1ec3cb3d4a9dbf4bab742e81c
|
||||
@@ -2,8 +2,10 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BrewMonster.Scripts.Chat.EmotionData;
|
||||
using TMPro;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TextCore;
|
||||
|
||||
namespace BrewMonster.Scripts.Editor
|
||||
{
|
||||
@@ -331,6 +333,175 @@ namespace BrewMonster.Scripts.Editor
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// TMP Sprite Asset Generator
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Tạo TMP_SpriteAsset từ snapshot đã convert (atlas phải ở chế độ Sprite Multiple).
|
||||
/// Create a TMP_SpriteAsset from a converted snapshot (atlas must already be Sprite Multiple).
|
||||
/// </summary>
|
||||
/// <param name="snapshot">Snapshot bộ emotion đã convert — Converted emotion set snapshot.</param>
|
||||
/// <param name="outputAssetPath">Đường dẫn lưu .asset (VD: Assets/TextMesh Pro/Resources/Sprite Assets/Emotions 0.asset).</param>
|
||||
/// <param name="cellSizeInPoints">
|
||||
/// Kích thước ô tính theo pt TMP (chiều cao glyph). Nếu = 0 thì lấy cellHeight từ snapshot.
|
||||
/// Cell size in TMP points (glyph height). 0 = use snapshot.CellHeight.
|
||||
/// </param>
|
||||
/// <param name="result">TMP_SpriteAsset vừa tạo.</param>
|
||||
/// <param name="error">Thông báo lỗi nếu thất bại.</param>
|
||||
public static bool CreateTMPSpriteAsset(
|
||||
EmotionSetSnapshot snapshot,
|
||||
string outputAssetPath,
|
||||
float cellSizeInPoints,
|
||||
out TMP_SpriteAsset result,
|
||||
out string error)
|
||||
{
|
||||
result = null;
|
||||
error = null;
|
||||
|
||||
if (snapshot?.SourceAtlas == null)
|
||||
{
|
||||
error = "Snapshot hoặc SourceAtlas null. Hãy chạy Convert trước.";
|
||||
return false;
|
||||
}
|
||||
|
||||
string atlasPath = AssetDatabase.GetAssetPath(snapshot.SourceAtlas);
|
||||
if (string.IsNullOrEmpty(atlasPath))
|
||||
{
|
||||
error = "SourceAtlas không phải asset trong project.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var importer = AssetImporter.GetAtPath(atlasPath) as TextureImporter;
|
||||
if (importer == null || importer.spriteImportMode != SpriteImportMode.Multiple)
|
||||
{
|
||||
error = $"Atlas tại {atlasPath} chưa ở chế độ Sprite Multiple. Hãy chạy Convert trước.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var atlasTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(atlasPath);
|
||||
if (atlasTexture == null)
|
||||
{
|
||||
error = $"Không load được Texture2D: {atlasPath}";
|
||||
return false;
|
||||
}
|
||||
|
||||
var spritesByName = LoadSpritesByName(atlasPath);
|
||||
if (spritesByName.Count == 0)
|
||||
{
|
||||
error = "Không tìm thấy sprite nào trong atlas. Kiểm tra import settings.";
|
||||
return false;
|
||||
}
|
||||
|
||||
int texW = atlasTexture.width;
|
||||
int texH = atlasTexture.height;
|
||||
int cellW = snapshot.CellWidth;
|
||||
int cellH = snapshot.CellHeight;
|
||||
|
||||
if (cellW <= 0 || cellH <= 0)
|
||||
{
|
||||
error = "CellWidth/CellHeight trong snapshot phải > 0.";
|
||||
return false;
|
||||
}
|
||||
|
||||
int nNumX = texW / cellW;
|
||||
int nNumY = texH / cellH;
|
||||
int totalCells = nNumX * nNumY;
|
||||
|
||||
// Kích thước ô dùng cho TMP metrics (point scale) — Cell size for TMP glyph metrics
|
||||
float glyphH = cellSizeInPoints > 0f ? cellSizeInPoints : cellH;
|
||||
float glyphW = cellSizeInPoints > 0f ? cellSizeInPoints * ((float)cellW / cellH) : cellW;
|
||||
|
||||
// Dùng TMP 3.x API: TMP_SpriteGlyph + TMP_SpriteCharacter (spriteInfoList đã deprecated)
|
||||
// Use TMP 3.x API: TMP_SpriteGlyph + TMP_SpriteCharacter (spriteInfoList is deprecated)
|
||||
var spriteGlyphTable = new List<TMP_SpriteGlyph>(totalCells);
|
||||
var spriteCharTable = new List<TMP_SpriteCharacter>(totalCells);
|
||||
|
||||
for (int i = 0; i < totalCells; i++)
|
||||
{
|
||||
string cellName = CellSpriteName(i);
|
||||
if (!spritesByName.TryGetValue(cellName, out Sprite spr) || spr == null)
|
||||
continue;
|
||||
|
||||
Rect r = spr.textureRect; // pixel rect, origin bottom-left
|
||||
|
||||
var glyph = new TMP_SpriteGlyph
|
||||
{
|
||||
index = (uint)spriteGlyphTable.Count,
|
||||
sprite = spr,
|
||||
glyphRect = new GlyphRect((int)r.x, (int)r.y, (int)r.width, (int)r.height),
|
||||
metrics = new GlyphMetrics(glyphW, glyphH, 0f, glyphH, glyphW),
|
||||
scale = 1f,
|
||||
atlasIndex = 0,
|
||||
};
|
||||
spriteGlyphTable.Add(glyph);
|
||||
|
||||
var character = new TMP_SpriteCharacter(0, glyph)
|
||||
{
|
||||
name = cellName,
|
||||
scale = 1f,
|
||||
};
|
||||
spriteCharTable.Add(character);
|
||||
}
|
||||
|
||||
if (spriteCharTable.Count == 0)
|
||||
{
|
||||
error = "Không build được bảng character. Kiểm tra tên sprite dạng cell_XXXX.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tìm TMP Sprite shader — Find TMP Sprite shader
|
||||
Shader shader = Shader.Find("TextMeshPro/Sprite");
|
||||
if (shader == null)
|
||||
{
|
||||
error = "Không tìm thấy shader 'TextMeshPro/Sprite'. Đảm bảo TMP package đã import đầy đủ.";
|
||||
return false;
|
||||
}
|
||||
|
||||
string outputDir = Path.GetDirectoryName(outputAssetPath)?.Replace('\\', '/') ?? "Assets";
|
||||
EnsureFolder(outputDir);
|
||||
|
||||
// Material — tên khớp với TMP convention: "<AssetName> Material"
|
||||
string assetNameNoExt = Path.GetFileNameWithoutExtension(outputAssetPath);
|
||||
string matPath = $"{outputDir}/{assetNameNoExt} Material.mat";
|
||||
|
||||
// Xóa asset cũ nếu tồn tại — Delete existing assets if present
|
||||
if (AssetDatabase.LoadAssetAtPath<Material>(matPath) != null)
|
||||
AssetDatabase.DeleteAsset(matPath);
|
||||
if (AssetDatabase.LoadAssetAtPath<TMP_SpriteAsset>(outputAssetPath) != null)
|
||||
AssetDatabase.DeleteAsset(outputAssetPath);
|
||||
|
||||
var mat = new Material(shader) { name = $"{assetNameNoExt} Material" };
|
||||
mat.mainTexture = atlasTexture;
|
||||
AssetDatabase.CreateAsset(mat, matPath);
|
||||
|
||||
var spriteAsset = ScriptableObject.CreateInstance<TMP_SpriteAsset>();
|
||||
spriteAsset.name = assetNameNoExt;
|
||||
spriteAsset.spriteSheet = atlasTexture;
|
||||
spriteAsset.material = mat;
|
||||
//spriteAsset.spriteGlyphTable = spriteGlyphTable;
|
||||
//spriteAsset.spriteCharacterTable = spriteCharTable;
|
||||
|
||||
// Rebuild lookup tables để <sprite name="..."> hoạt động — Rebuild lookup tables for name-based lookup
|
||||
spriteAsset.UpdateLookupTables();
|
||||
|
||||
AssetDatabase.CreateAsset(spriteAsset, outputAssetPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
result = spriteAsset;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tính đường dẫn mặc định của TMP_SpriteAsset cho một bộ emotion.
|
||||
/// Returns the default TMP_SpriteAsset output path for an emotion set.
|
||||
/// </summary>
|
||||
public static string DefaultTMPSpriteAssetPath(string tmpSpriteFolder, int emotionSetIndex)
|
||||
{
|
||||
return $"{tmpSpriteFolder.TrimEnd('/')}/Emotions {emotionSetIndex}.asset";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using BrewMonster.Scripts.Chat.EmotionData;
|
||||
using TMPro;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -23,6 +24,15 @@ namespace BrewMonster.Scripts.Editor
|
||||
|
||||
private string _libraryAssetPath = "Assets/PerfectWorld/UI/Chat/GeneratedEmotions/EmotionLibrary.asset";
|
||||
|
||||
// ── TMP Sprite Asset ──────────────────────────────────────────────────
|
||||
private bool _genTmpOnConvert = true;
|
||||
private string _tmpSpriteFolder = "Assets/TextMesh Pro/Resources/Sprite Assets";
|
||||
private float _tmpCellSizePt = 0f; // 0 = lấy từ cellH
|
||||
private bool _showTmpSection = true;
|
||||
// Snapshot cuối cùng được convert (cho nút gen TMP standalone)
|
||||
// Last converted snapshot (for standalone TMP gen button)
|
||||
private EmotionSetSnapshot _lastSnapshot;
|
||||
|
||||
[MenuItem("Tools/Perfect World/ChatSystem/Emotion Atlas Converter…")]
|
||||
public static void Open()
|
||||
{
|
||||
@@ -120,6 +130,9 @@ namespace BrewMonster.Scripts.Editor
|
||||
RunConvertLibrary();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(12f);
|
||||
DrawTMPSection();
|
||||
|
||||
EditorGUILayout.Space(8f);
|
||||
EditorGUILayout.HelpBox(
|
||||
"Mỗi bộ: một texture **Sprite Multiple**, tên sub-sprite `cell_0000` … theo chỉ số ô (giống C++).\n" +
|
||||
@@ -127,6 +140,53 @@ namespace BrewMonster.Scripts.Editor
|
||||
MessageType.Info);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// TMP Sprite Asset section
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
private void DrawTMPSection()
|
||||
{
|
||||
_showTmpSection = EditorGUILayout.Foldout(_showTmpSection, "TMP Sprite Asset", true, EditorStyles.foldoutHeader);
|
||||
if (!_showTmpSection) return;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
_tmpSpriteFolder = EditorGUILayout.TextField(
|
||||
new GUIContent("Sprite Assets folder",
|
||||
"Thư mục lưu TMP_SpriteAsset (.asset). Mặc định: Assets/TextMesh Pro/Resources/Sprite Assets\n" +
|
||||
"Folder for TMP_SpriteAsset files. Default: Assets/TextMesh Pro/Resources/Sprite Assets"),
|
||||
_tmpSpriteFolder);
|
||||
|
||||
_tmpCellSizePt = EditorGUILayout.FloatField(
|
||||
new GUIContent("Cell size (pt)",
|
||||
"Chiều cao glyph theo đơn vị TMP point. 0 = dùng Cell height (pixel).\n" +
|
||||
"Glyph height in TMP points. 0 = use Cell height in pixels."),
|
||||
_tmpCellSizePt);
|
||||
|
||||
_genTmpOnConvert = EditorGUILayout.ToggleLeft(
|
||||
"Tự động gen TMP Sprite Asset sau mỗi lần Convert — Auto-generate TMP Sprite Asset after Convert",
|
||||
_genTmpOnConvert);
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
"Output: <Sprite Assets folder>/Emotions {N}.asset + Emotions {N} Material.mat\n" +
|
||||
"Tên sub-sprite khớp với atlas: cell_0000 … (dùng cho <sprite name=\"cell_XXXX\"> trong TMP).\n" +
|
||||
"Sub-sprite names match atlas: cell_0000 … (use <sprite name=\"cell_XXXX\"> in TMP).",
|
||||
MessageType.Info);
|
||||
|
||||
EditorGUILayout.Space(4f);
|
||||
|
||||
// Nút gen standalone từ snapshot cuối — Standalone gen button from last snapshot
|
||||
EditorGUI.BeginDisabledGroup(_lastSnapshot == null);
|
||||
if (GUILayout.Button("Gen TMP Sprite Asset từ lần Convert cuối — from last Convert"))
|
||||
RunGenTMPSingle(_lastSnapshot);
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
if (_lastSnapshot == null)
|
||||
EditorGUILayout.HelpBox("Chạy Convert single ít nhất một lần để kích hoạt nút này.\nRun Convert (single) once to enable this button.", MessageType.None);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
private int NextSuggestedSetIndex()
|
||||
{
|
||||
int max = -1;
|
||||
@@ -149,6 +209,8 @@ namespace BrewMonster.Scripts.Editor
|
||||
return;
|
||||
}
|
||||
|
||||
_lastSnapshot = snapshot;
|
||||
|
||||
var so = ScriptableObject.CreateInstance<EmotionSetDataSO>();
|
||||
EmotionAtlasConverterCore.ApplySnapshotToSetSO(so, snapshot);
|
||||
string setFolder = $"{_outputFolder}/Emotions{_emotionSetIndex}";
|
||||
@@ -156,10 +218,37 @@ namespace BrewMonster.Scripts.Editor
|
||||
AssetDatabase.CreateAsset(so, soPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
EditorUtility.DisplayDialog("Done", $"Đã tạo:\n{soPath}\nAtlas (Multiple) trong thư mục set (hoặc đã slice tại nguồn).", "OK");
|
||||
string msg = $"Đã tạo:\n{soPath}\nAtlas (Multiple) trong thư mục set.";
|
||||
|
||||
if (_genTmpOnConvert)
|
||||
{
|
||||
TMP_SpriteAsset tmpAsset = RunGenTMPSingle(snapshot);
|
||||
if (tmpAsset != null)
|
||||
msg += $"\nTMP Sprite Asset: {AssetDatabase.GetAssetPath(tmpAsset)}";
|
||||
}
|
||||
|
||||
EditorUtility.DisplayDialog("Done", msg, "OK");
|
||||
EditorGUIUtility.PingObject(so);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gen TMP_SpriteAsset cho một snapshot. Trả về asset nếu thành công, null nếu thất bại.
|
||||
/// Generate TMP_SpriteAsset for one snapshot. Returns asset on success, null on failure.
|
||||
/// </summary>
|
||||
private TMP_SpriteAsset RunGenTMPSingle(EmotionSetSnapshot snapshot)
|
||||
{
|
||||
if (snapshot == null) return null;
|
||||
string outPath = EmotionAtlasConverterCore.DefaultTMPSpriteAssetPath(_tmpSpriteFolder, snapshot.EmotionSetIndex);
|
||||
if (!EmotionAtlasConverterCore.CreateTMPSpriteAsset(snapshot, outPath, _tmpCellSizePt, out var tmpAsset, out string err))
|
||||
{
|
||||
EditorUtility.DisplayDialog("TMP Gen Error", $"Set {snapshot.EmotionSetIndex}: {err}", "OK");
|
||||
return null;
|
||||
}
|
||||
|
||||
EditorGUIUtility.PingObject(tmpAsset);
|
||||
return tmpAsset;
|
||||
}
|
||||
|
||||
private void RunConvertLibrary()
|
||||
{
|
||||
if (!EmotionAtlasConverterCore.TryValidateBatchIndices(_batchSlots, out string dupErr))
|
||||
@@ -220,9 +309,28 @@ namespace BrewMonster.Scripts.Editor
|
||||
AssetDatabase.CreateAsset(library, _libraryAssetPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
EditorUtility.DisplayDialog("Done",
|
||||
$"EmotionLibrary: {ok} bộ.\n{_libraryAssetPath}",
|
||||
"OK");
|
||||
int tmpOk = 0;
|
||||
if (_genTmpOnConvert)
|
||||
{
|
||||
for (int i = 0; i < library.Sets.Count; i++)
|
||||
{
|
||||
var snap = library.Sets[i];
|
||||
EditorUtility.DisplayProgressBar("TMP Sprite Assets", $"Set {snap.EmotionSetIndex}…", (float)i / Mathf.Max(1, library.Sets.Count));
|
||||
string outPath = EmotionAtlasConverterCore.DefaultTMPSpriteAssetPath(_tmpSpriteFolder, snap.EmotionSetIndex);
|
||||
if (EmotionAtlasConverterCore.CreateTMPSpriteAsset(snap, outPath, _tmpCellSizePt, out _, out string tmpErr))
|
||||
tmpOk++;
|
||||
else
|
||||
Debug.LogWarning($"[EmotionConverter] TMP gen Set {snap.EmotionSetIndex}: {tmpErr}");
|
||||
}
|
||||
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
string doneMsg = $"EmotionLibrary: {ok} bộ.\n{_libraryAssetPath}";
|
||||
if (_genTmpOnConvert)
|
||||
doneMsg += $"\nTMP Sprite Assets: {tmpOk}/{ok} bộ → {_tmpSpriteFolder}";
|
||||
|
||||
EditorUtility.DisplayDialog("Done", doneMsg, "OK");
|
||||
EditorGUIUtility.PingObject(library);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,9 +84,10 @@ namespace CSNetwork
|
||||
|
||||
var it = itemsSet.GetItemIterator();
|
||||
|
||||
for (int i = 0; i < nCount; i++)
|
||||
while (it.MoveNext())
|
||||
{
|
||||
EditBoxItemBase pItem = it.Current.Value;
|
||||
if (pItem == null) continue;
|
||||
|
||||
if (pItem.GetType() == EditboxItemType.enumEIEmotion)
|
||||
{
|
||||
@@ -97,8 +98,6 @@ namespace CSNetwork
|
||||
|
||||
pItem.SetInfo(MarshalEmotionInfo(nEmotionSet, nIndex));
|
||||
}
|
||||
|
||||
it.MoveNext();
|
||||
}
|
||||
|
||||
return MarshalEditBoxText(strOrgText, itemsSet);
|
||||
@@ -591,8 +590,6 @@ public class AUI_UNMARSH_UNDERLINE_INFO
|
||||
|
||||
public class EditBoxItemsSet
|
||||
{
|
||||
const char AUICOMMON_ITEM_CODE_START = '\u0001';
|
||||
const char AUICOMMON_ITEM_CODE_END = '\u0010';
|
||||
protected Dictionary<char, EditBoxItemBase> m_Items = new();
|
||||
protected int[] m_ItemsCount = new int[(int)AUICommon.EditboxItemType.enumEINum];
|
||||
protected char m_cNextItemChar;
|
||||
@@ -653,7 +650,7 @@ public class EditBoxItemsSet
|
||||
|
||||
public bool IsEditboxItemCode(char ch)
|
||||
{
|
||||
return ch >= AUICOMMON_ITEM_CODE_START && ch <= AUICOMMON_ITEM_CODE_END;
|
||||
return AUICommon.IsEditboxItemCode(ch);
|
||||
}
|
||||
|
||||
public int GetItemCountByType(AUICommon.EditboxItemType type)
|
||||
@@ -700,8 +697,8 @@ public class EditBoxItemsSet
|
||||
|
||||
public char EditboxGetNextChar(char cur)
|
||||
{
|
||||
if (cur >= AUICOMMON_ITEM_CODE_END)
|
||||
return AUICOMMON_ITEM_CODE_START;
|
||||
if (cur >= AUICommon.AUICOMMON_ITEM_CODE_END)
|
||||
return AUICommon.AUICOMMON_ITEM_CODE_START;
|
||||
else
|
||||
return (char)(cur + 1);
|
||||
}
|
||||
@@ -967,9 +964,24 @@ public class EditBoxItemBase
|
||||
m_uExtraDataSize = size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [Port] EditBoxItemBase::Serialize — AUICommon.cpp — wire payload sau mỗi ký tự placeholder PUA.
|
||||
/// [Port] EditBoxItemBase::Serialize — AUICommon.cpp — wire payload after each PUA placeholder character.
|
||||
/// </summary>
|
||||
public virtual string Serialize()
|
||||
{
|
||||
return "";
|
||||
if (m_type == AUICommon.EditboxItemType.enumEICoord)
|
||||
{
|
||||
uint ul = m_bSameColor ? m_dwColor : m_dwUnderLineColor;
|
||||
return $"<{(int)m_type}><{m_dwColor}><{(m_bUnderLine ? 1 : 0)}><{ul}><{m_strName}><{m_strInfo}>";
|
||||
}
|
||||
|
||||
if (m_type == AUICommon.EditboxItemType.enumEIImage)
|
||||
{
|
||||
return $"<{(int)m_type}><{m_dwColor}><{m_nImageIndex}><{m_nImageFrame}><{m_fImageScale.ToString(System.Globalization.CultureInfo.InvariantCulture)}><{m_strInfo}>";
|
||||
}
|
||||
|
||||
return $"<{(int)m_type}><{m_strInfo}>";
|
||||
}
|
||||
|
||||
public static EditBoxItemBase Unserialize(string text, ref int index)
|
||||
|
||||
@@ -1962,7 +1962,7 @@ namespace CSNetwork
|
||||
string szName = pHost.GetName();
|
||||
char[] szText = new char[80];
|
||||
AUICommon.AUI_ConvertChatString(ref szName, ref szText, false);
|
||||
Debug.Log($"[Cuong] {szText}");
|
||||
|
||||
string fmt = AUIDialog.FormatPrintf(pStrTab.GetWideString((int)FixedMsg.FIXMSG_CHAT));
|
||||
string str;
|
||||
try {
|
||||
@@ -2044,6 +2044,18 @@ namespace CSNetwork
|
||||
m_iCharID = iCharID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wire 正文(表情等)→ TMP — 供仅走 EventBus 的系统消息使用;玩家消息由 CECGameUIMan.AddChatMessage 处理。
|
||||
/// Wire body (emotions, etc.) → TMP — for system messages that only use EventBus; player messages use CECGameUIMan.AddChatMessage.
|
||||
/// </summary>
|
||||
private string ConvertWireBodyForChatPanel(string wireBody, int cEmotion)
|
||||
{
|
||||
if (string.IsNullOrEmpty(wireBody))
|
||||
return wireBody;
|
||||
var ui = EC_Game.GetGameRun()?.GetUIManager()?.GetInGameUIMan() as CECGameUIMan;
|
||||
return ui != null ? ui.ConvertWireChatBodyForDisplay(wireBody, cEmotion) : wireBody;
|
||||
}
|
||||
|
||||
private bool OnPrtcWorldChat(Protocol pProtocol, bool bCalledagain)
|
||||
{
|
||||
worldchat p = (worldchat)pProtocol;
|
||||
@@ -2059,6 +2071,7 @@ namespace CSNetwork
|
||||
string strMsg = Encoding.Unicode.GetString(p.Msg.ToArray());
|
||||
string strSrcName = Encoding.Unicode.GetString(p.Name.ToArray());
|
||||
Debug.Log($"[Cuong] {strMsg}");
|
||||
// Wire → TMP 在 AddChatMessage / ChatEmotionDisplayPipeline 内完成 — English: wire → TMP inside AddChatMessage.
|
||||
string fmt = AUIDialog.FormatPrintf(pStrTab.GetWideString((int)FixedMsg.FIXMSG_CHAT));
|
||||
string formatted;
|
||||
try {
|
||||
@@ -2070,11 +2083,16 @@ namespace CSNetwork
|
||||
pGameUI.AddChatMessage(formatted, (ChatChannel)p.Channel, p.Roleid, strSrcName,
|
||||
0, p.Emotion, null, strMsg);
|
||||
|
||||
// Bubble chat on head
|
||||
CECPlayer pSrcPlayer = EC_Game.GetGameRun().GetWorld().GetPlayerMan().GetPlayer(p.Roleid);
|
||||
if (pSrcPlayer != null)
|
||||
// Kênh không thuộc showsAboveHead trong AddChatMessage 时 vẫn cần bong bóng — English: extra bubble for channels AddChatMessage does not publish.
|
||||
if (!CECGameUIMan.ChannelShowsChatBubbleAboveHead((ChatChannel)p.Channel))
|
||||
{
|
||||
EventBus.PublishChannel(p.Roleid, new EventChatMessageOnTopPlayer(p.Roleid, strMsg));
|
||||
CECPlayer pSrcPlayer = EC_Game.GetGameRun().GetWorld().GetPlayerMan().GetPlayer(p.Roleid);
|
||||
if (pSrcPlayer != null)
|
||||
{
|
||||
string bubbleTmp = pGameUI.ConvertWireBodyForHeadBubble(
|
||||
strMsg, (ChatChannel)p.Channel, p.Roleid, p.Emotion);
|
||||
EventBus.PublishChannel(p.Roleid, new EventChatMessageOnTopPlayer(p.Roleid, bubbleTmp));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -2115,8 +2133,6 @@ namespace CSNetwork
|
||||
|
||||
private bool OnPrtcChatMessage(Protocol pProtocol, bool bCalledagain)
|
||||
{
|
||||
CECGameUIMan pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan();
|
||||
|
||||
chatmessage p = (chatmessage)pProtocol;
|
||||
//var channel = (ChatChannel)p.Channel;
|
||||
|
||||
@@ -2158,7 +2174,7 @@ namespace CSNetwork
|
||||
case 18: case 19: case 20: case 21: case 22: // Auction Message
|
||||
// pGameUI.AddSysAuctionMessage(...)
|
||||
Debug.Log("[Cuong] Auction " + strTemp);
|
||||
EventBus.Publish(new ChatMessageEvent(strTemp, p.Channel));
|
||||
EventBus.Publish(new ChatMessageEvent(ConvertWireBodyForChatPanel(strTemp, p.Emotion), p.Channel));
|
||||
break;
|
||||
case 24: // Task Message
|
||||
// OnTaskChatMessage(p.Data.RawBuffer, p.Data.Size);
|
||||
@@ -2182,7 +2198,7 @@ namespace CSNetwork
|
||||
}
|
||||
else
|
||||
{
|
||||
EventBus.Publish(new ChatMessageEvent(strTemp, p.Channel));
|
||||
EventBus.Publish(new ChatMessageEvent(ConvertWireBodyForChatPanel(strTemp, p.Emotion), p.Channel));
|
||||
}
|
||||
}else if (p.Channel == (byte)ChatChannel.GP_CHAT_INSTANCE && p.Srcroleid == 1)
|
||||
{
|
||||
@@ -2213,7 +2229,7 @@ namespace CSNetwork
|
||||
// Do not pass szText into FormatPrintf — it is a scratch buffer from AUI_ConvertChatString, not a format arg.
|
||||
string fmt = AUIDialog.FormatPrintf(pStrTab.GetWideString((int)FixedMsg.FIXMSG_CHAT));
|
||||
string str = string.Format(fmt, szName, szMsg);
|
||||
// [Port] Gọi AddChatMessage để hiển thị lên UI Chat Box.
|
||||
// [Port] Gọi AddChatMessage để hiển thị lên UI Chat Box — wire→TMP 在 AddChatMessage 内(ChatEmotionDisplayPipeline)。
|
||||
// AddChatMessage bên trong đã tự publish ChatMessageEvent (cho ChatPanelUI)
|
||||
// VÀ EventChatMessageOnTopPlayer (cho Head Bubble) nếu channel thuộc nhóm
|
||||
// showsAboveHead (LOCAL, TEAM, FARCRY, SUPERFARCRY, BATTLE, COUNTRY).
|
||||
@@ -2245,7 +2261,8 @@ namespace CSNetwork
|
||||
0,
|
||||
p.Emotion
|
||||
);
|
||||
EventBus.Publish(new ChatMessageEvent(message));
|
||||
// Không publish thêm ChatMessageEvent(message): message vẫn là wire trong thân,
|
||||
// AddChatMessage đã publish bản đã wire→TMP — English: no duplicate; AddChatMessage already publishes converted text.
|
||||
|
||||
CECUIHelper.RemoveNameFlagFromNPCChat(strTemp, out szMsg);
|
||||
|
||||
@@ -2269,6 +2286,7 @@ namespace CSNetwork
|
||||
CECGameUIMan pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan();
|
||||
CECStringTab pStrTab = EC_Game.GetFixedMsgs();
|
||||
|
||||
// Private chat wire body → TMP 在 AddChatMessage — English: wire → TMP inside AddChatMessage.
|
||||
if (p.Channel == 0 /* CHANNEL_NORMAL */ || p.Channel == 1 /* CHANNEL_NORMALRE */)
|
||||
{
|
||||
// Format: "[Name] whispers to [You]: [Message]"
|
||||
@@ -2290,11 +2308,14 @@ namespace CSNetwork
|
||||
p.Channel, p.Emotion, null, strMsg);
|
||||
}
|
||||
|
||||
// Set player's last said words for head bubble
|
||||
// 私聊头顶气泡:AddChatMessage 对 GP_CHAT_WHISPER 不发布 EventChatMessageOnTopPlayer,在此单独发 TMP 正文。
|
||||
// Whisper head bubble: AddChatMessage does not publish EventChatMessageOnTopPlayer for whisper — publish TMP body here.
|
||||
CECPlayer pSrcPlayer = EC_Game.GetGameRun().GetWorld().GetPlayerMan().GetPlayer(p.Srcroleid);
|
||||
if (pSrcPlayer != null)
|
||||
{
|
||||
EventBus.PublishChannel(p.Srcroleid, new EventChatMessageOnTopPlayer(p.Srcroleid, strMsg));
|
||||
string bubbleTmp = pGameUI.ConvertWireBodyForHeadBubble(
|
||||
strMsg, ChatChannel.GP_CHAT_WHISPER, p.Srcroleid, p.Emotion);
|
||||
EventBus.PublishChannel(p.Srcroleid, new EventChatMessageOnTopPlayer(p.Srcroleid, bubbleTmp));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,57 @@ namespace BrewMonster.UI
|
||||
_chatEmotionPipeline.SetSpriteMap(map);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 服务器下发的 wire 正文 → TMP(表情/坐标/物品等内联)— 供 GameSession 等不经过 AddChatMessage 的 UI 路径。
|
||||
/// Server wire body → TMP (inline emotion/coord/item) — for UI paths that do not go through AddChatMessage.
|
||||
/// </summary>
|
||||
public string ConvertWireChatBodyForDisplay(string wireBody, int cEmotion)
|
||||
{
|
||||
if (string.IsNullOrEmpty(wireBody))
|
||||
return wireBody;
|
||||
string f = _chatEmotionPipeline.ApplyChannelEmotionFilter(wireBody, cEmotion);
|
||||
return _chatEmotionPipeline.ConvertInlineItemsToTmp(f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 仅正文 wire(无 printf 包装)→ 头顶气泡 TMP — 与 AddChatMessage 内 FilterBadWords + 内联转 TMP 一致。
|
||||
/// Wire body only (no printf wrapper) → head-bubble TMP — same as AddChatMessage FilterBadWords + inline to TMP.
|
||||
/// </summary>
|
||||
public string ConvertWireBodyForHeadBubble(string wireBody, ChatChannel cChannel, int idPlayer, int cEmotion)
|
||||
{
|
||||
if (string.IsNullOrEmpty(wireBody))
|
||||
return wireBody;
|
||||
string s = _chatEmotionPipeline.ApplyChannelEmotionFilter(wireBody, cEmotion);
|
||||
if (string.IsNullOrEmpty(s))
|
||||
return s;
|
||||
bool isPlayerChannel = cChannel == ChatChannel.GP_CHAT_LOCAL
|
||||
|| cChannel == ChatChannel.GP_CHAT_FARCRY
|
||||
|| cChannel == ChatChannel.GP_CHAT_TEAM
|
||||
|| cChannel == ChatChannel.GP_CHAT_FACTION
|
||||
|| cChannel == ChatChannel.GP_CHAT_WHISPER
|
||||
|| cChannel == ChatChannel.GP_CHAT_TRADE
|
||||
|| cChannel == ChatChannel.GP_CHAT_SUPERFARCRY
|
||||
|| cChannel == ChatChannel.GP_CHAT_BATTLE
|
||||
|| cChannel == ChatChannel.GP_CHAT_COUNTRY;
|
||||
if (isPlayerChannel && idPlayer > 0)
|
||||
CECUIManager.Instance.FilterBadWords(ref s);
|
||||
return _chatEmotionPipeline.ConvertInlineItemsToTmp(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kênh có bong bóng trên đầu trong AddChatMessage — dùng để tránh double-publish với PROTOCOL_WORLDCHAT.
|
||||
/// Channels that get head bubble from AddChatMessage — avoids double-publish with PROTOCOL_WORLDCHAT.
|
||||
/// </summary>
|
||||
public static bool ChannelShowsChatBubbleAboveHead(ChatChannel cChannel)
|
||||
{
|
||||
return cChannel == ChatChannel.GP_CHAT_LOCAL
|
||||
|| cChannel == ChatChannel.GP_CHAT_FARCRY
|
||||
|| cChannel == ChatChannel.GP_CHAT_TEAM
|
||||
|| cChannel == ChatChannel.GP_CHAT_SUPERFARCRY
|
||||
|| cChannel == ChatChannel.GP_CHAT_BATTLE
|
||||
|| cChannel == ChatChannel.GP_CHAT_COUNTRY;
|
||||
}
|
||||
|
||||
// Layout/settings flags for GetUserLayout (saved to server)
|
||||
// 布局/设置标志,用于 GetUserLayout(保存到服务器)
|
||||
private bool m_bAutoReply;
|
||||
@@ -601,18 +652,15 @@ namespace BrewMonster.UI
|
||||
|
||||
// C++: AddChatMessage also handles head bubble via pPlayer->SetLastSaidWords
|
||||
// Unity equivalent: Publish EventChatMessageOnTopPlayer
|
||||
bool showsAboveHead = cChannel == ChatChannel.GP_CHAT_LOCAL
|
||||
|| cChannel == ChatChannel.GP_CHAT_FARCRY
|
||||
|| cChannel == ChatChannel.GP_CHAT_TEAM
|
||||
|| cChannel == ChatChannel.GP_CHAT_SUPERFARCRY
|
||||
|| cChannel == ChatChannel.GP_CHAT_BATTLE
|
||||
|| cChannel == ChatChannel.GP_CHAT_COUNTRY;
|
||||
bool showsAboveHead = ChannelShowsChatBubbleAboveHead(cChannel);
|
||||
|
||||
if (showsAboveHead && idPlayer > 0)
|
||||
{
|
||||
// C++: pPlayer->SetLastSaidWords(strTemp, ...) — strTemp là nội dung chat thuần,
|
||||
// KHÔNG bao gồm tên người chơi. Dùng pszMsgOrigion thay vì pszMsg.
|
||||
string bubbleText = !string.IsNullOrEmpty(pszMsgOrigion) ? pszMsgOrigion : pszMsg;
|
||||
// C++: pPlayer->SetLastSaidWords(strTemp, ...) — chỉ thân tin (không tên); bản wire pszMsgOrigion phải → TMP giống khung chat.
|
||||
// C++: SetLastSaidWords — body only (no name); wire pszMsgOrigion must become TMP like chat panel.
|
||||
string bubbleText = !string.IsNullOrEmpty(pszMsgOrigion)
|
||||
? ConvertWireBodyForHeadBubble(pszMsgOrigion, cChannel, idPlayer, cEmotion)
|
||||
: pszMsg;
|
||||
EventBus.PublishChannel(idPlayer, new EventChatMessageOnTopPlayer(idPlayer, bubbleText));
|
||||
}
|
||||
|
||||
|
||||
+9
@@ -18,6 +18,7 @@ MonoBehaviour:
|
||||
CellHeight: 32
|
||||
SourceAtlas: {fileID: 2800000, guid: 865978f275731044a9b84fe9dbfe4210, type: 3}
|
||||
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions0.txt
|
||||
TmpSpriteAsset: {fileID: 11400000, guid: ee97404ddf0b24345809ec3b36f78e02, type: 2}
|
||||
Entries:
|
||||
- StartPos: 0
|
||||
NumFrames: 4
|
||||
@@ -436,6 +437,7 @@ MonoBehaviour:
|
||||
CellHeight: 32
|
||||
SourceAtlas: {fileID: 2800000, guid: 9e83ff623b8332a48a31f02c32a417bf, type: 3}
|
||||
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions1.txt
|
||||
TmpSpriteAsset: {fileID: 11400000, guid: 6f37606e7f8ba924fb4bb97cd0e6c734, type: 2}
|
||||
Entries:
|
||||
- StartPos: 0
|
||||
NumFrames: 4
|
||||
@@ -867,6 +869,7 @@ MonoBehaviour:
|
||||
CellHeight: 32
|
||||
SourceAtlas: {fileID: 2800000, guid: 6414ed03b9b595c4893bf27641a26a92, type: 3}
|
||||
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions2.txt
|
||||
TmpSpriteAsset: {fileID: 11400000, guid: c0d482604d9e30848afc3d65f2576f77, type: 2}
|
||||
Entries:
|
||||
- StartPos: 0
|
||||
NumFrames: 6
|
||||
@@ -1560,6 +1563,7 @@ MonoBehaviour:
|
||||
CellHeight: 32
|
||||
SourceAtlas: {fileID: 2800000, guid: cdad8f497ffd51e4caeecc2115c9c5bf, type: 3}
|
||||
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions3.txt
|
||||
TmpSpriteAsset: {fileID: 11400000, guid: 3a5ecb4a05fb4de42a06037cf133bc69, type: 2}
|
||||
Entries:
|
||||
- StartPos: 0
|
||||
NumFrames: 14
|
||||
@@ -2158,6 +2162,7 @@ MonoBehaviour:
|
||||
CellHeight: 32
|
||||
SourceAtlas: {fileID: 2800000, guid: 3f3a694e7f4186645b1c0e2e4fd9d477, type: 3}
|
||||
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions4.txt
|
||||
TmpSpriteAsset: {fileID: 11400000, guid: ba5f1b4119a9bd84987d0c718b44f183, type: 2}
|
||||
Entries:
|
||||
- StartPos: 0
|
||||
NumFrames: 4
|
||||
@@ -2615,6 +2620,7 @@ MonoBehaviour:
|
||||
CellHeight: 32
|
||||
SourceAtlas: {fileID: 2800000, guid: f545545a96251f8429c50ca2d2b415e0, type: 3}
|
||||
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions5.txt
|
||||
TmpSpriteAsset: {fileID: 11400000, guid: c664bed7f27d3bf4d88ffaa5b81a30c5, type: 2}
|
||||
Entries:
|
||||
- StartPos: 0
|
||||
NumFrames: 4
|
||||
@@ -3093,6 +3099,7 @@ MonoBehaviour:
|
||||
CellHeight: 32
|
||||
SourceAtlas: {fileID: 2800000, guid: 7857a986ab79aee4a9e881d288a4b8c8, type: 3}
|
||||
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions6.txt
|
||||
TmpSpriteAsset: {fileID: 11400000, guid: 966ad31186c6a8642b429169350b685b, type: 2}
|
||||
Entries:
|
||||
- StartPos: 0
|
||||
NumFrames: 2
|
||||
@@ -3675,6 +3682,7 @@ MonoBehaviour:
|
||||
CellHeight: 32
|
||||
SourceAtlas: {fileID: 2800000, guid: 6e5f5a0f5610bd849a8ee5a9e995da18, type: 3}
|
||||
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions7.txt
|
||||
TmpSpriteAsset: {fileID: 11400000, guid: 146a3d3c57ca61845890281300f22db2, type: 2}
|
||||
Entries:
|
||||
- StartPos: 0
|
||||
NumFrames: 6
|
||||
@@ -4363,6 +4371,7 @@ MonoBehaviour:
|
||||
CellHeight: 32
|
||||
SourceAtlas: {fileID: 2800000, guid: 84e9c440264236f45adfb529204b91f9, type: 3}
|
||||
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions8.txt
|
||||
TmpSpriteAsset: {fileID: 11400000, guid: 516b8c277c9eb5f4dbf5af1a8bf42e89, type: 2}
|
||||
Entries:
|
||||
- StartPos: 0
|
||||
NumFrames: 9
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3011939e4e9c0ce4e83bc03a748fcf96
|
||||
guid: e4fbe1473e80a9c4794327d8bb11a4ab
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
@@ -11416,7 +11416,7 @@ TextureImporter:
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 3097da9ef9188074aafd00a6cf38eaf4
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
|
||||
@@ -87,6 +87,7 @@ GameObject:
|
||||
- component: {fileID: 7228077960814023056}
|
||||
- component: {fileID: 5305392080666511277}
|
||||
- component: {fileID: 7604324431011831295}
|
||||
- component: {fileID: 8883197050699466089}
|
||||
m_Layer: 5
|
||||
m_Name: Text (TMP)
|
||||
m_TagString: Untagged
|
||||
@@ -236,6 +237,26 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
m_HorizontalFit: 0
|
||||
m_VerticalFit: 2
|
||||
--- !u!114 &8883197050699466089
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6240941777052618231}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 291.7
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
--- !u!1 &6627717456258223658
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -0,0 +1,746 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &718172578440456188
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 8823934296412569056}
|
||||
m_Layer: 5
|
||||
m_Name: Sliding Area
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &8823934296412569056
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 718172578440456188}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 4092718722133982449}
|
||||
m_Father: {fileID: 3822843052799882238}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: -20, y: -20}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!1 &843061721837273778
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3676046446549609595}
|
||||
- component: {fileID: 482462868452457425}
|
||||
- component: {fileID: 5438734168994104898}
|
||||
- component: {fileID: 1062586482629190498}
|
||||
m_Layer: 5
|
||||
m_Name: prefab_MiniChatUI
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &3676046446549609595
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 843061721837273778}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 1026350322123039068}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &482462868452457425
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 843061721837273778}
|
||||
m_CullTransparentMesh: 0
|
||||
--- !u!114 &5438734168994104898
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 843061721837273778}
|
||||
m_Enabled: 0
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 0.392}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &1062586482629190498
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 843061721837273778}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 0c3012f1ec3cb3d4a9dbf4bab742e81c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
onOpenChatPanelButton: {fileID: 7633421558195200411}
|
||||
miniChatContent: {fileID: 6901861700741151626}
|
||||
miniMessagePrefab: {fileID: 1976417251556044024, guid: af3b510e838bc934d97c31d0c665ede6, type: 3}
|
||||
messagePrefab: {fileID: 1976417251556044024, guid: af3b510e838bc934d97c31d0c665ede6, type: 3}
|
||||
miniChatPreviewLines: 5
|
||||
miniChatPassThroughClicks: 1
|
||||
maxStoredMessages: 10
|
||||
chatSystemSO: {fileID: 11400000, guid: 43f54723aa074c74e83e5be28975bee5, type: 2}
|
||||
--- !u!1 &3073746784354305239
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3822843052799882238}
|
||||
- component: {fileID: 3308938358456462458}
|
||||
- component: {fileID: 4982766943607529008}
|
||||
- component: {fileID: 883384003667195735}
|
||||
m_Layer: 5
|
||||
m_Name: Scrollbar Vertical
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
--- !u!224 &3822843052799882238
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3073746784354305239}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 8823934296412569056}
|
||||
m_Father: {fileID: 4845475585563704660}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 1, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 10, y: -17}
|
||||
m_Pivot: {x: 1, y: 1}
|
||||
--- !u!222 &3308938358456462458
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3073746784354305239}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &4982766943607529008
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3073746784354305239}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &883384003667195735
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3073746784354305239}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 2a4db7a114972834c8e4117be1d82ba3, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 7407862892964548378}
|
||||
m_HandleRect: {fileID: 4092718722133982449}
|
||||
m_Direction: 2
|
||||
m_Value: 0
|
||||
m_Size: 1
|
||||
m_NumberOfSteps: 0
|
||||
m_OnValueChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!1 &3298050727520355041
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4092718722133982449}
|
||||
- component: {fileID: 1090130158241239070}
|
||||
- component: {fileID: 7407862892964548378}
|
||||
m_Layer: 5
|
||||
m_Name: Handle
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &4092718722133982449
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3298050727520355041}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 8823934296412569056}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 20, y: 20}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &1090130158241239070
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3298050727520355041}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &7407862892964548378
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3298050727520355041}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &3329353435299617291
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1026350322123039068}
|
||||
- component: {fileID: 5093099212117339368}
|
||||
- component: {fileID: 7552365743554843694}
|
||||
- component: {fileID: 7633421558195200411}
|
||||
m_Layer: 5
|
||||
m_Name: ButtonOpenChat
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1026350322123039068
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3329353435299617291}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 4845475585563704660}
|
||||
m_Father: {fileID: 3676046446549609595}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 0}
|
||||
m_AnchoredPosition: {x: -175.64499, y: 151.8}
|
||||
m_SizeDelta: {x: -1148.71, y: 227}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &5093099212117339368
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3329353435299617291}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &7552365743554843694
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3329353435299617291}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: dcaed5f7f4ac2564caa797987cad6e40, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &7633421558195200411
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3329353435299617291}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 0
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 7552365743554843694}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!1 &3415728744842130225
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6901861700741151626}
|
||||
- component: {fileID: 8221922843074448897}
|
||||
- component: {fileID: 7620348421717998225}
|
||||
- component: {fileID: 1308636600769309511}
|
||||
m_Layer: 5
|
||||
m_Name: Content
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &6901861700741151626
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3415728744842130225}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 4775615495870991432}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 10, y: 0}
|
||||
m_SizeDelta: {x: -30, y: 0}
|
||||
m_Pivot: {x: 0, y: 1}
|
||||
--- !u!114 &8221922843074448897
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3415728744842130225}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_HorizontalFit: 0
|
||||
m_VerticalFit: 2
|
||||
--- !u!114 &7620348421717998225
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3415728744842130225}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Padding:
|
||||
m_Left: 0
|
||||
m_Right: 0
|
||||
m_Top: 0
|
||||
m_Bottom: 0
|
||||
m_ChildAlignment: 0
|
||||
m_Spacing: 0
|
||||
m_ChildForceExpandWidth: 0
|
||||
m_ChildForceExpandHeight: 0
|
||||
m_ChildControlWidth: 0
|
||||
m_ChildControlHeight: 0
|
||||
m_ChildScaleWidth: 0
|
||||
m_ChildScaleHeight: 0
|
||||
m_ReverseArrangement: 0
|
||||
--- !u!114 &1308636600769309511
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3415728744842130225}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4cf21a97aa5c9445c9859afa14de01ad, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
_rectTransform: {fileID: 6901861700741151626}
|
||||
--- !u!1 &4130028666636203937
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4775615495870991432}
|
||||
- component: {fileID: 6058492214783487750}
|
||||
- component: {fileID: 6128544922052315148}
|
||||
- component: {fileID: 8978003769100856221}
|
||||
m_Layer: 5
|
||||
m_Name: Viewport
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &4775615495870991432
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4130028666636203937}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 6901861700741151626}
|
||||
m_Father: {fileID: 4845475585563704660}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &6058492214783487750
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4130028666636203937}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &6128544922052315148
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4130028666636203937}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 10917, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &8978003769100856221
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4130028666636203937}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_ShowMaskGraphic: 0
|
||||
--- !u!1 &6874262700332502098
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4845475585563704660}
|
||||
- component: {fileID: 6639134328005110591}
|
||||
- component: {fileID: 566607841426224557}
|
||||
- component: {fileID: 8391397933666603359}
|
||||
m_Layer: 5
|
||||
m_Name: Scroll View
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &4845475585563704660
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6874262700332502098}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 4775615495870991432}
|
||||
- {fileID: 3822843052799882238}
|
||||
m_Father: {fileID: 1026350322123039068}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &6639134328005110591
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6874262700332502098}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &566607841426224557
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6874262700332502098}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: efd7d18860367bf48b7d93df7b0d8b1f, type: 3}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &8391397933666603359
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6874262700332502098}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1aa08ab6e0800fa44ae55d278d1423e3, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Content: {fileID: 6901861700741151626}
|
||||
m_Horizontal: 0
|
||||
m_Vertical: 1
|
||||
m_MovementType: 2
|
||||
m_Elasticity: 0.1
|
||||
m_Inertia: 1
|
||||
m_DecelerationRate: 0.135
|
||||
m_ScrollSensitivity: 1
|
||||
m_Viewport: {fileID: 4775615495870991432}
|
||||
m_HorizontalScrollbar: {fileID: 0}
|
||||
m_VerticalScrollbar: {fileID: 0}
|
||||
m_HorizontalScrollbarVisibility: 2
|
||||
m_VerticalScrollbarVisibility: 1
|
||||
m_HorizontalScrollbarSpacing: -3
|
||||
m_VerticalScrollbarSpacing: -3
|
||||
m_OnValueChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05206a01b3910384cb9a17c74225d554
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,136 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &7588729112411274048
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6105556955705595552}
|
||||
- component: {fileID: 3541366497170239759}
|
||||
- component: {fileID: 9017642443478747476}
|
||||
- component: {fileID: -8647101344485271738}
|
||||
- component: {fileID: 4685469653980001717}
|
||||
m_Layer: 5
|
||||
m_Name: prefab_EmojiCell
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &6105556955705595552
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7588729112411274048}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 100, y: 100}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &3541366497170239759
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7588729112411274048}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &9017642443478747476
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7588729112411274048}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &-8647101344485271738
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7588729112411274048}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 0
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 9017642443478747476}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!114 &4685469653980001717
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7588729112411274048}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: ad6cc9a7d46da8949bae44dfcf3fe03d, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
_icon: {fileID: 9017642443478747476}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 616f563fe32927844be2a17dbb408331
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -5,6 +5,8 @@ using CSNetwork.GPDataType;
|
||||
using System.Collections.Generic;
|
||||
using BrewMonster;
|
||||
using BrewMonster.Network;
|
||||
using BrewMonster.Scripts.Chat;
|
||||
using BrewMonster.Scripts.Chat.EmotionData;
|
||||
using BrewMonster.Scripts.Managers;
|
||||
using BrewMonster.UI;
|
||||
using CSNetwork;
|
||||
@@ -29,6 +31,18 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
public TMP_InputField inputField;
|
||||
public ChatSystemSO chatSystem;
|
||||
|
||||
[Header("Emoji")]
|
||||
[Tooltip("SO ánh xạ emotion → TMP sprite tag. Gán EmotionLibrarySpriteMap đã build từ Emotion Atlas Converter.")]
|
||||
[SerializeField] EmotionLibrarySpriteMap _spriteMap;
|
||||
|
||||
/// <summary>
|
||||
/// Nội dung tin nhắn theo protocol wire (MarshalEditBoxText) — gửi server; không phải TMP.
|
||||
/// Message body in wire protocol (MarshalEditBoxText) — sent to server; not TMP.
|
||||
/// </summary>
|
||||
private string _chatWireBody = "";
|
||||
|
||||
private bool _syncingWireToDisplay;
|
||||
|
||||
[Serializable]
|
||||
public struct ChannelButtonMapping
|
||||
{
|
||||
@@ -73,7 +87,10 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
{
|
||||
EventBus.Unsubscribe<WhisperPlayerEvent>(OnWhisperPlayerEvent);
|
||||
if (inputField != null)
|
||||
{
|
||||
inputField.onSubmit.RemoveListener(OnSubmit);
|
||||
inputField.onValueChanged.RemoveListener(OnInputValueChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWhisperPlayerEvent(WhisperPlayerEvent e)
|
||||
@@ -87,6 +104,9 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
if (inputField != null && Application.platform != RuntimePlatform.Android)
|
||||
inputField.onSubmit.AddListener(OnSubmit);
|
||||
|
||||
if (inputField != null)
|
||||
inputField.onValueChanged.AddListener(OnInputValueChanged);
|
||||
|
||||
foreach (var mapping in channelButtons)
|
||||
{
|
||||
if (mapping.button != null)
|
||||
@@ -168,6 +188,8 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
inputField.MoveTextEnd(false);
|
||||
}
|
||||
|
||||
SyncChatWireBodyFromInput();
|
||||
|
||||
EventBus.Publish(new ChatChannelFilterChangedEvent(m_currentChannel));
|
||||
}
|
||||
|
||||
@@ -219,7 +241,10 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
|
||||
private void OnCommand_speak(string text)
|
||||
{
|
||||
string strText = text.Trim();
|
||||
SyncChatWireBodyFromInput();
|
||||
|
||||
string routingLine = (text ?? "").Trim();
|
||||
string strText = _chatWireBody?.Trim() ?? "";
|
||||
|
||||
if (strText.Length <= 0)
|
||||
{
|
||||
@@ -232,26 +257,27 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
int nSlot = -1;
|
||||
|
||||
FilterBadWords(ref strText);
|
||||
_chatWireBody = strText;
|
||||
|
||||
if (HandleDebugCommand(strText))
|
||||
if (HandleDebugCommand(routingLine))
|
||||
return;
|
||||
|
||||
float now = Time.time;
|
||||
|
||||
if (!CheckFarCryRequirement(strText, now))
|
||||
if (!CheckFarCryRequirement(routingLine, now))
|
||||
return;
|
||||
|
||||
if (!CheckSuperFarCryRequirement(strText, now))
|
||||
if (!CheckSuperFarCryRequirement(routingLine, now))
|
||||
return;
|
||||
|
||||
if (!CheckSpamProtection(strText, now))
|
||||
return;
|
||||
|
||||
ChatChannel resolvedChannel = ParseAndSendMessage(strText, nPack, nSlot);
|
||||
ChatChannel resolvedChannel = ParseAndSendMessage(routingLine, nPack, nSlot);
|
||||
|
||||
if (resolvedChannel == ChatChannel.GP_CHAT_FARCRY && strText.StartsWith("!@"))
|
||||
if (resolvedChannel == ChatChannel.GP_CHAT_FARCRY && routingLine.StartsWith("!@"))
|
||||
m_dwTickFarCry = now;
|
||||
if (resolvedChannel == ChatChannel.GP_CHAT_SUPERFARCRY && strText.StartsWith("!#"))
|
||||
if (resolvedChannel == ChatChannel.GP_CHAT_SUPERFARCRY && routingLine.StartsWith("!#"))
|
||||
m_dwTickFarCry2 = now;
|
||||
|
||||
SaveHistory(resolvedChannel, strText, nPack, nSlot, now);
|
||||
@@ -365,32 +391,27 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
private ChatChannel ParseAndSendMessage(string text, int nPack, int nSlot)
|
||||
{
|
||||
ChatChannel channel = m_currentChannel;
|
||||
string pszMsg = text;
|
||||
string pszMsg = _chatWireBody;
|
||||
|
||||
if (text.StartsWith("!!"))
|
||||
{
|
||||
channel = ChatChannel.GP_CHAT_TEAM;
|
||||
pszMsg = text.Substring(2);
|
||||
}
|
||||
else if (text.StartsWith("!~"))
|
||||
{
|
||||
channel = ChatChannel.GP_CHAT_FACTION;
|
||||
pszMsg = text.Substring(2);
|
||||
}
|
||||
else if (text.StartsWith("!@"))
|
||||
{
|
||||
channel = ChatChannel.GP_CHAT_FARCRY;
|
||||
pszMsg = text.Substring(2);
|
||||
}
|
||||
else if (text.StartsWith("!#"))
|
||||
{
|
||||
channel = ChatChannel.GP_CHAT_SUPERFARCRY;
|
||||
pszMsg = text.Substring(2);
|
||||
}
|
||||
else if (text.StartsWith("$"))
|
||||
{
|
||||
channel = ChatChannel.GP_CHAT_TRADE;
|
||||
pszMsg = text.Substring(1);
|
||||
}
|
||||
else if (text.StartsWith("/"))
|
||||
{
|
||||
@@ -433,11 +454,10 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
}
|
||||
|
||||
string player = cmd.Substring(0, spaceIndex);
|
||||
string msg = cmd.Substring(spaceIndex + 1);
|
||||
|
||||
m_whisperTarget = player;
|
||||
|
||||
SendPrivateChat(player, msg, nPack, nSlot);
|
||||
SendPrivateChat(player, _chatWireBody, nPack, nSlot);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
@@ -474,12 +494,12 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
string localMsg;
|
||||
try
|
||||
{
|
||||
localMsg = string.Format(fmt, target, msg);
|
||||
localMsg = string.Format(fmt, target, _chatWireBody);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback: dùng &target& để AddChatMessage tạo link — format không có & thì mình thêm
|
||||
localMsg = $"&{target}&: {msg}";
|
||||
localMsg = $"&{target}&: {_chatWireBody}";
|
||||
}
|
||||
|
||||
CECGameUIMan pGameUI = EC_Game.GetGameRun()?.GetUIManager()?.GetInGameUIMan();
|
||||
@@ -487,7 +507,7 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
{
|
||||
// idTarget là ID người NHẬN (hoặc -1 nếu không tìm thấy trong friend list)
|
||||
// AddChatMessage sẽ convert &target& thành TMP link tag có dạng idTarget|target
|
||||
pGameUI.AddChatMessage(localMsg, ChatChannel.GP_CHAT_WHISPER, idTarget, null, 0, 0, null, msg);
|
||||
pGameUI.AddChatMessage(localMsg, ChatChannel.GP_CHAT_WHISPER, idTarget, null, 0, 0, null, _chatWireBody);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,6 +628,7 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
|
||||
private void ClearTextInInputField()
|
||||
{
|
||||
_chatWireBody = "";
|
||||
if (m_currentChannel == ChatChannel.GP_CHAT_WHISPER && !string.IsNullOrEmpty(m_whisperTarget))
|
||||
{
|
||||
inputField.text = "/" + m_whisperTarget + " ";
|
||||
@@ -617,6 +638,87 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
inputField.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
private int GetEmotionSetForCurrentChannel()
|
||||
{
|
||||
return m_currentChannel == ChatChannel.GP_CHAT_SUPERFARCRY ? GameSession.SUPER_FAR_CRY_EMOTION_SET : 0;
|
||||
}
|
||||
|
||||
private string BuildVisualPrefixForCurrentChannel()
|
||||
{
|
||||
if (chatSystem == null)
|
||||
return "";
|
||||
if (m_currentChannel == ChatChannel.GP_CHAT_WHISPER && !string.IsNullOrEmpty(m_whisperTarget))
|
||||
return "/" + m_whisperTarget + " ";
|
||||
var cfg = chatSystem.channelIcons.Find(c => c.channel == m_currentChannel);
|
||||
return cfg.prefix ?? "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lấy phần thân (TMP) sau prefix kênh / whisper — English: TMP body after channel or whisper prefix.
|
||||
/// </summary>
|
||||
private string ExtractMessageBodyFromVisual(string full)
|
||||
{
|
||||
if (string.IsNullOrEmpty(full))
|
||||
return "";
|
||||
|
||||
if (full.StartsWith("/"))
|
||||
{
|
||||
int sp = full.IndexOf(' ');
|
||||
if (sp > 0 && sp + 1 < full.Length)
|
||||
return full.Substring(sp + 1);
|
||||
return "";
|
||||
}
|
||||
|
||||
string t = RemoveKnownPrefix(full);
|
||||
if (chatSystem != null)
|
||||
{
|
||||
var cfg = chatSystem.channelIcons.Find(c => c.channel == m_currentChannel);
|
||||
if (cfg.prefix != null && cfg.prefix.Length > 0 && t.StartsWith(cfg.prefix))
|
||||
t = t.Substring(cfg.prefix.Length);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
private void SyncChatWireBodyFromInput()
|
||||
{
|
||||
if (inputField == null)
|
||||
return;
|
||||
string tmpBody = ExtractMessageBodyFromVisual(inputField.text ?? "");
|
||||
if (_spriteMap == null)
|
||||
{
|
||||
_chatWireBody = tmpBody;
|
||||
return;
|
||||
}
|
||||
|
||||
_chatWireBody = ChatWireTmpCodec.TmpBodyToWire(tmpBody, _spriteMap);
|
||||
}
|
||||
|
||||
private void OnInputValueChanged(string _)
|
||||
{
|
||||
if (_syncingWireToDisplay)
|
||||
return;
|
||||
SyncChatWireBodyFromInput();
|
||||
}
|
||||
|
||||
private void RefreshInputDisplayFromWire()
|
||||
{
|
||||
if (inputField == null)
|
||||
return;
|
||||
_syncingWireToDisplay = true;
|
||||
try
|
||||
{
|
||||
string prefix = BuildVisualPrefixForCurrentChannel();
|
||||
string tmpBody = ChatWireTmpCodec.WireBodyToTmpForDisplay(_chatWireBody, _spriteMap, GetEmotionSetForCurrentChannel());
|
||||
inputField.text = prefix + tmpBody;
|
||||
inputField.MoveTextEnd(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncingWireToDisplay = false;
|
||||
}
|
||||
}
|
||||
private void ChangeFocus()
|
||||
{
|
||||
inputField.ActivateInputField();
|
||||
@@ -636,5 +738,33 @@ namespace BrewMonster.Scripts.ChatUI
|
||||
{
|
||||
return EC_Game.GetGameRun()?.GetHostPlayer()?.GetBasicProps().iLevel ?? 0;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// EMOJI INSERT
|
||||
// =====================================================
|
||||
|
||||
/// <summary>
|
||||
/// Thêm emotion vào buffer wire rồi refresh TMP — gửi server đúng protocol.
|
||||
/// Append emotion to wire buffer then refresh TMP — server protocol preserved.
|
||||
/// </summary>
|
||||
public void AppendEmotionWire(int emotionSet, int emotionIndex)
|
||||
{
|
||||
string segment = ChatWireTmpCodec.BuildMarshaledEmotionWire(emotionSet, emotionIndex);
|
||||
if (string.IsNullOrEmpty(segment))
|
||||
return;
|
||||
_chatWireBody += segment;
|
||||
RefreshInputDisplayFromWire();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Chèn TMP emotion tag vào inputField tại vị trí caret hiện tại.
|
||||
/// Inserts the TMP emotion tag into the inputField at the current caret position.
|
||||
/// Gọi từ EmojiPickerUI khi người chơi bấm chọn emoji.
|
||||
/// Called from EmojiPickerUI when the player selects an emoji.
|
||||
/// </summary>
|
||||
public void InsertEmoji(int emotionSet, int emotionIndex)
|
||||
{
|
||||
AppendEmotionWire(emotionSet, emotionIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ Material:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 2800000, guid: e1a057e1756274235a7c676d3d0a761a, type: 3}
|
||||
m_Texture: {fileID: 2800000, guid: dffef66376be4fa480fb02b19edbe903, type: 3}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
@@ -79,7 +79,7 @@ MonoBehaviour:
|
||||
m_StrikethroughThickness: 0
|
||||
m_TabWidth: 0
|
||||
m_Material: {fileID: 2103686}
|
||||
spriteSheet: {fileID: 2800000, guid: e1a057e1756274235a7c676d3d0a761a, type: 3}
|
||||
spriteSheet: {fileID: 2800000, guid: dffef66376be4fa480fb02b19edbe903, type: 3}
|
||||
m_SpriteCharacterTable:
|
||||
- m_ElementType: 2
|
||||
m_Unicode: 128522
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee97404ddf0b24345809ec3b36f78e02
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f37606e7f8ba924fb4bb97cd0e6c734
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0d482604d9e30848afc3d65f2576f77
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a5ecb4a05fb4de42a06037cf133bc69
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba5f1b4119a9bd84987d0c718b44f183
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c664bed7f27d3bf4d88ffaa5b81a30c5
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 966ad31186c6a8642b429169350b685b
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 146a3d3c57ca61845890281300f22db2
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 516b8c277c9eb5f4dbf5af1a8bf42e89
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -36,11 +36,20 @@ MonoBehaviour:
|
||||
m_fallbackFontAssets: []
|
||||
m_matchMaterialPreset: 1
|
||||
m_HideSubTextObjects: 0
|
||||
m_defaultSpriteAsset: {fileID: 11400000, guid: c41005c129ba4d66911b75229fd70b45, type: 2}
|
||||
m_defaultSpriteAsset: {fileID: 11400000, guid: ee97404ddf0b24345809ec3b36f78e02, type: 2}
|
||||
m_defaultSpriteAssetPath: Sprite Assets/
|
||||
m_enableEmojiSupport: 1
|
||||
m_MissingCharacterSpriteUnicode: 0
|
||||
m_EmojiFallbackTextAssets: []
|
||||
m_EmojiFallbackTextAssets:
|
||||
- {fileID: 11400000, guid: ee97404ddf0b24345809ec3b36f78e02, type: 2}
|
||||
- {fileID: 11400000, guid: 6f37606e7f8ba924fb4bb97cd0e6c734, type: 2}
|
||||
- {fileID: 11400000, guid: c0d482604d9e30848afc3d65f2576f77, type: 2}
|
||||
- {fileID: 11400000, guid: 3a5ecb4a05fb4de42a06037cf133bc69, type: 2}
|
||||
- {fileID: 11400000, guid: ba5f1b4119a9bd84987d0c718b44f183, type: 2}
|
||||
- {fileID: 11400000, guid: c664bed7f27d3bf4d88ffaa5b81a30c5, type: 2}
|
||||
- {fileID: 11400000, guid: 966ad31186c6a8642b429169350b685b, type: 2}
|
||||
- {fileID: 11400000, guid: 146a3d3c57ca61845890281300f22db2, type: 2}
|
||||
- {fileID: 11400000, guid: 516b8c277c9eb5f4dbf5af1a8bf42e89, type: 2}
|
||||
m_defaultColorGradientPresetsPath: Color Gradient Presets/
|
||||
m_defaultStyleSheet: {fileID: 11400000, guid: f952c082cb03451daed3ee968ac6c63e, type: 2}
|
||||
m_StyleSheetsResourcePath:
|
||||
|
||||
Reference in New Issue
Block a user