Add logic update friend UI, friend status

This commit is contained in:
HungDK
2026-03-18 11:39:26 +07:00
parent 9f31aa446a
commit 2adc3ede59
5 changed files with 326 additions and 19 deletions
@@ -1,5 +1,8 @@
using System.Collections.Generic;
using System.Text;
using BrewMonster.Network;
using CSNetwork;
using CSNetwork.Protocols.RPCData;
namespace BrewMonster
{
@@ -22,16 +25,18 @@ namespace BrewMonster
public bool IsGameOnline() => IsGameOnline(Status);
public bool IsGTOnline() => IsGTOnline(Status);
// C++ gnetdef.h: GAME_ONLINE=0x01, GT_INVISIBLE=0x80
public static bool IsGameOnline(byte s)
{
// TODO: implement đúng theo logic C++
return (s & 0x01) != 0;
}
// C++: except GAME_ONLINE and GT_INVISIBLE, any other status means GT online
public static bool IsGTOnline(byte s)
{
// TODO: implement đúng theo logic C++
return (s & 0x02) != 0;
const byte GAME_ONLINE = 0x01;
const byte GT_INVISIBLE = 0x80;
return (s & unchecked((byte)~GAME_ONLINE) & unchecked((byte)~GT_INVISIBLE)) != 0;
}
}
@@ -86,6 +91,68 @@ namespace BrewMonster
public bool CheckInit() => m_Groups.Count > 0;
/// <summary>Reset from server getfriends_re. C++ format: status list is pairs (friendIndex, statusByte).</summary>
public void ResetFromServer(List<GGroupInfo> groups, List<GFriendInfo> friends, List<byte> status)
{
m_Groups.Clear();
m_FriendTable.Clear();
m_OfflineMsgs.Clear();
m_FriendEx.Clear();
m_SendInfo.Clear();
if (groups != null)
{
for (int i = 0; i < groups.Count; i++)
{
var g = groups[i];
int gid = g?.gid ?? 0;
string gname = DecodeOctets(g?.name);
AddGroup(gid, string.IsNullOrEmpty(gname) ? $"Group{gid}" : gname);
}
}
int friendCount = friends?.Count ?? 0;
for (int i = 0; i < friendCount; i++)
{
var f = friends[i];
if (f == null) continue;
int gid = f.gid;
if (GetGroupByID(gid) == null)
AddGroup(gid, $"Group{gid}");
string name = DecodeOctets(f.name);
AddFriend(f.rid, f.cls, gid, 0, name);
}
// Apply status pairs: [friendIndex0, status0, friendIndex1, status1, ...]
if (status != null && (status.Count % 2) == 0 && friends != null)
{
for (int i = 0; i < status.Count; i += 2)
{
int friendIndex = status[i];
byte st = status[i + 1];
if (friendIndex < 0 || friendIndex >= friends.Count) continue;
var fi = friends[friendIndex];
if (fi == null) continue;
ChangeFriendStatus(fi.rid, st);
}
}
}
private static string DecodeOctets(Octets o)
{
if (o == null || o.Size <= 0) return string.Empty;
try
{
return Encoding.Unicode.GetString(o.ToArray(), 0, o.Size);
}
catch
{
return string.Empty;
}
}
public Friend AddFriend(int id, int profession, int groupId, byte status, string name)
{
var friend = new Friend
@@ -130,8 +197,21 @@ namespace BrewMonster
public void ChangeFriendStatus(int idFriend, byte status)
{
if (m_FriendTable.TryGetValue(idFriend, out var friend))
friend.Status = status;
if (!m_FriendTable.TryGetValue(idFriend, out var friend))
return;
friend.Status = status;
m_FriendTable[idFriend] = friend;
var group = GetGroupByID(friend.GroupId);
if (group == null) return;
for (int i = 0; i < group.Friends.Count; i++)
{
if (group.Friends[i].Id != idFriend) continue;
group.Friends[i] = friend;
break;
}
}
public void ChangeFriendGroup(int idFriend, int newGroupId)
@@ -216,7 +296,7 @@ namespace BrewMonster
public int GetGroupNum() => m_Groups.Count;
public GROUP? GetGroupByIndex(int index)
public GROUP GetGroupByIndex(int index)
{
if (index < 0 || index >= m_Groups.Count)
return null;
@@ -224,7 +304,7 @@ namespace BrewMonster
return m_Groups[index];
}
public GROUP? GetGroupByID(int id)
public GROUP GetGroupByID(int id)
{
return m_Groups.Find(g => g.GroupId == id);
}
@@ -235,7 +315,7 @@ namespace BrewMonster
public int GetOfflineMsgNum() => m_OfflineMsgs.Count;
public MESSAGE? GetOfflineMsg(int index)
public MESSAGE GetOfflineMsg(int index)
{
if (index < 0 || index >= m_OfflineMsgs.Count)
return null;
@@ -66,6 +66,15 @@ namespace CSNetwork
/// <summary>Raised when server sends PROTOCOL_ADDFRIEND_RE(203). Args: retcode (0=success), message.</summary>
public event Action<byte, string> AddFriendResultReceived;
/// <summary>Raised when server sends PROTOCOL_GETFRIENDS_RE(207).</summary>
public event Action<getfriends_re> FriendListReceived;
/// <summary>Raised when server sends PROTOCOL_FRIENDSTATUS(214). Args: roleid, status.</summary>
public event Action<int, byte> FriendStatusChanged;
/// <summary>Raised when server sends PROTOCOL_FRIENDEXTLIST.</summary>
public event Action<friendextlist> FriendExtListReceived;
/// <summary>Raised when the underlying network disconnects.</summary>
public event Action Disconnected;
@@ -612,6 +621,18 @@ namespace CSNetwork
OnAddFriendRe((addfriend_re)protocol);
break;
case ProtocolType.PROTOCOL_GETFRIENDS_RE:
OnGetFriendsRe((getfriends_re)protocol);
break;
case ProtocolType.PROTOCOL_FRIENDEXTLIST:
OnFriendExtList((friendextlist)protocol);
break;
case ProtocolType.PROTOCOL_FRIENDSTATUS:
OnFriendStatus((friendstatus)protocol);
break;
default:
_logger.Log(LogType.Warning, $"Received unhandled protocol type: {protocol.GetPType()}");
break;
@@ -635,6 +656,47 @@ namespace CSNetwork
PostToUnityContext(() => FriendRequestReceived?.Invoke(p.Xid, p.Srcroleid, askerName));
}
private void OnGetFriendsRe(getfriends_re p)
{
PostToUnityContext(() =>
{
try
{
var host = EC_Game.GetGameRun()?.GetHostPlayer();
host?.GetFriendMan()?.ResetFromServer(p.Groups, p.Friends, p.Status);
}
catch (Exception ex)
{
_logger.Log(LogType.Error, $"OnGetFriendsRe failed: {ex.Message}");
_logger.LogException(ex);
}
FriendListReceived?.Invoke(p);
});
}
private void OnFriendExtList(friendextlist p)
{
PostToUnityContext(() => FriendExtListReceived?.Invoke(p));
}
private void OnFriendStatus(friendstatus p)
{
PostToUnityContext(() =>
{
try
{
var host = EC_Game.GetGameRun()?.GetHostPlayer();
host?.GetFriendMan()?.ChangeFriendStatus(p.Roleid, p.Status);
}
catch (Exception ex)
{
_logger.Log(LogType.Error, $"OnFriendStatus failed: {ex.Message}");
_logger.LogException(ex);
}
FriendStatusChanged?.Invoke(p.Roleid, p.Status);
});
}
private void OnAddFriendRe(addfriend_re p)
{
string friendName = "";
@@ -2468,6 +2530,17 @@ namespace CSNetwork
SendProtocol(p);
}
/// <summary>Send PROTOCOL_DELFRIEND(212). Delete a friend by role id.</summary>
public void Friend_Del(int friendId)
{
var p = new delfriend();
p.Roleid = m_iCharID;
p.Friendid = friendId;
p.Localsid = (int)_localsid;
SendProtocol(p);
Friend_GetList();
}
public void c2s_SendCmdTeamKickMember(int idMember)
{
var g = new gamedatasend();
@@ -13,6 +13,8 @@ namespace BrewMonster
{
public partial class CECHostPlayer
{
private readonly CECFriendMan _friendMan = new CECFriendMan();
public CECFriendMan GetFriendMan() => _friendMan;
// 服务器控制的额外操作限制
public enum PLAYER_LIMIT
@@ -369,7 +371,7 @@ namespace BrewMonster
CECNPC pNPC = EC_Game.GetGameRun().GetWorld().GetNPCMan().GetNPC(m_pPetCorral.GetActivePetNPCID());
if (pNPC && pNPC.GetMasterID() == GetCharacterID())
{
//pNPC.BubbleText(CECNPC::BUBBLE_HPWARN, 0);
//pNPC.BubbleText(CECNPC::BUBBLE_HPWARN, 0);
}
}
}
@@ -1,6 +1,8 @@
using BrewMonster.Network;
using BrewMonster.Scripts.Pet;
using BrewMonster.UI;
using CSNetwork;
using CSNetwork.Protocols;
using CSNetwork.GPDataType;
using ModelRenderer.Scripts.GameData;
using System;
@@ -14,6 +16,13 @@ namespace BrewMonster
{
public class CDlgFriendList : AUIDialog
{
private enum FriendTab
{
Friend = 0,
Blacklist = 1,
Archrival = 2
}
[Header("Buttons")]
[SerializeField] private Button m_helpBtn;
[SerializeField] private Button m_friendBtn;
@@ -41,6 +50,14 @@ namespace BrewMonster
[SerializeField] private GameObject m_contextMenu;
[SerializeField] private GameObject m_inputName;
private CSNetwork.GameSession _session;
private FriendTab _currentTab = FriendTab.Friend;
private int _selectedFriendId;
private Image _selectedRowImage;
private Color _selectedRowOriginalColor;
private TMP_InputField _addNameInput;
private static readonly Color SelectedRowColor = new Color32(255, 255, 255, 60);
public void OnInitDialog()
{
if (!IsShow())
@@ -48,6 +65,18 @@ namespace BrewMonster
Show(true);
}
_session = UnityGameSession.Instance?.GameSession;
if (_session != null)
{
_session.FriendListReceived -= OnFriendListReceived;
_session.FriendListReceived += OnFriendListReceived;
_session.FriendStatusChanged -= OnFriendStatusChanged;
_session.FriendStatusChanged += OnFriendStatusChanged;
_session.Friend_GetList();
}
RefreshListFromHost();
if (m_helpBtn != null)
{
m_helpBtn.onClick.RemoveAllListeners();
@@ -62,17 +91,18 @@ namespace BrewMonster
m_friendBtn.onClick.RemoveAllListeners();
m_friendBtn.onClick.AddListener(() =>
{
// TODO: Show friend list
_currentTab = FriendTab.Friend;
RefreshListFromHost();
});
}
if (m_blacklistBtn != null)
{
m_blacklistBtn.onClick.RemoveAllListeners();
m_blacklistBtn.onClick.AddListener(() =>
{
// TODO: Show blacklist
_currentTab = FriendTab.Blacklist;
RefreshListFromHost();
});
}
@@ -81,7 +111,8 @@ namespace BrewMonster
m_archrivalBtn.onClick.RemoveAllListeners();
m_archrivalBtn.onClick.AddListener(() =>
{
// TODO: Show archrival list
_currentTab = FriendTab.Archrival;
RefreshListFromHost();
});
}
@@ -90,7 +121,9 @@ namespace BrewMonster
m_addBtn.onClick.RemoveAllListeners();
m_addBtn.onClick.AddListener(() =>
{
m_inputName.SetActive(true);
EnsureAddNameInput();
if (m_inputName != null)
m_inputName.SetActive(true);
});
}
@@ -99,7 +132,9 @@ namespace BrewMonster
m_deleteBtn.onClick.RemoveAllListeners();
m_deleteBtn.onClick.AddListener(() =>
{
// TODO: Delete selected friend
if (_session == null) return;
if (_selectedFriendId <= 0) return;
_session.Friend_Del(_selectedFriendId);
});
}
@@ -117,7 +152,15 @@ namespace BrewMonster
m_confirmBtn.onClick.RemoveAllListeners();
m_confirmBtn.onClick.AddListener(() =>
{
// TODO: Confirm adding friend with name from input field
if (_session == null) return;
EnsureAddNameInput();
var n = _addNameInput != null ? _addNameInput.text : string.Empty;
n = (n ?? string.Empty).Trim();
if (n.Length <= 0) return;
_session.Friend_Add(0, n);
if (m_inputName != null)
m_inputName.SetActive(false);
});
}
@@ -184,5 +227,113 @@ namespace BrewMonster
});
}
}
public override void OnDisable()
{
base.OnDisable();
if (_session != null)
{
_session.FriendListReceived -= OnFriendListReceived;
_session.FriendStatusChanged -= OnFriendStatusChanged;
}
}
private void OnFriendListReceived(getfriends_re _)
{
RefreshListFromHost();
}
private void OnFriendStatusChanged(int _, byte __)
{
RefreshListFromHost();
}
private void RefreshListFromHost()
{
if (m_friendContainer == null || m_friendDetailPrefabs == null)
return;
ClearSelection();
for (int i = m_friendContainer.childCount - 1; i >= 0; i--)
{
var child = m_friendContainer.GetChild(i);
if (child != null)
Destroy(child.gameObject);
}
if (_currentTab != FriendTab.Friend)
return;
var host = GetHostPlayer();
var man = host?.GetFriendMan();
if (man == null || !man.CheckInit())
return;
int groupNum = man.GetGroupNum();
for (int gi = 0; gi < groupNum; gi++)
{
var g = man.GetGroupByIndex(gi);
if (g == null) continue;
foreach (var f in g.Friends)
{
var go = Instantiate(m_friendDetailPrefabs, m_friendContainer);
WireRowSelection(go, f.Id);
var nameTxt = go.transform.Find("text_name")?.GetComponent<TextMeshProUGUI>();
var stateTxt = go.transform.Find("text_state")?.GetComponent<TextMeshProUGUI>();
if (nameTxt != null) nameTxt.text = string.IsNullOrEmpty(f.Name) ? f.Id.ToString() : f.Name;
if (stateTxt != null)
{
bool online = f.IsGameOnline();
stateTxt.text = online ? "Online" : "Offline";
}
}
}
}
private void WireRowSelection(GameObject rowGo, int friendId)
{
if (rowGo == null) return;
var btn = rowGo.GetComponent<Button>();
var img = rowGo.GetComponent<Image>();
if (btn == null) return;
btn.onClick.RemoveAllListeners();
btn.onClick.AddListener(() => SelectRow(friendId, img));
}
private void SelectRow(int friendId, Image rowImage)
{
if (_selectedRowImage != null)
_selectedRowImage.color = _selectedRowOriginalColor;
_selectedFriendId = friendId;
_selectedRowImage = rowImage;
if (_selectedRowImage != null)
{
_selectedRowOriginalColor = _selectedRowImage.color;
_selectedRowImage.color = SelectedRowColor;
}
}
private void ClearSelection()
{
if (_selectedRowImage != null)
_selectedRowImage.color = _selectedRowOriginalColor;
_selectedFriendId = 0;
_selectedRowImage = null;
_selectedRowOriginalColor = default;
}
private void EnsureAddNameInput()
{
if (_addNameInput != null) return;
if (m_inputName == null) return;
_addNameInput = m_inputName.GetComponentInChildren<TMP_InputField>(true);
}
}
}
+4 -3
View File
@@ -4,10 +4,11 @@ namespace BrewMonster
{
public partial class CECHostPlayer
{
private CECFriendMan _pFriendMan;
/*private CECFriendMan _pFriendMan;
public CECFriendMan GetFriendMan() { return _pFriendMan; }
public void SetFriendMan(CECFriendMan pFriendMan) { _pFriendMan = pFriendMan; }
*/
public bool IsOmitBlocking(int roleid)
{
CECFactionMan pFacMan = EC_Game.GetFactionMan();
@@ -31,4 +32,4 @@ namespace BrewMonster
return false;
}
}
}
}