Merge pull request 'Add CreateGetOtherEquipDetailCmd and update player option UI prefab' (#208) from feature/character_info into develop

Reviewed-on: https://git.pthub.vn/Unity/perfect-world-unity/pulls/208
This commit is contained in:
hungdk
2026-03-09 07:56:11 +00:00
7 changed files with 152 additions and 3 deletions
@@ -116,7 +116,7 @@ MonoBehaviour:
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 0
m_Interactable: 1
m_TargetGraphic: {fileID: 369226366953714584}
m_OnClick:
m_PersistentCalls:
@@ -457,6 +457,19 @@ namespace CSNetwork.C2SCommand
public int[] idList; // Variable length array
}
// Get other player equip/profile detail (view other player profile) — same shape as CMD_GetOtherEquip
public struct CMD_GetOtherEquipDetail
{
public ushort size;
public int[] idList; // Variable length array (role IDs)
}
// Single-role variant: body is only roleId (4 bytes). Some servers expect this instead of size+idList.
public struct CMD_GetOtherEquipDetailSingle
{
public int roleId;
}
// Team set pickup command
public struct CMD_TeamSetPickup
{
@@ -377,6 +377,22 @@ namespace CSNetwork.C2SCommand
return SerializeCommand(CommandID.SELECT_TARGET, cmd);
}
/// <summary>Create C2S GET_OTHER_EQUIP_DETAIL command (view other player profile/equip). Sends cmd + roleId only (4-byte body).</summary>
public static Octets CreateGetOtherEquipDetailCmd(int roleId)
{
var cmd = new CMD_GetOtherEquipDetailSingle { roleId = roleId };
return SerializeCommand(CommandID.GET_OTHER_EQUIP_DETAIL, cmd);
}
/// <summary>Create C2S GET_OTHER_EQUIP_DETAIL command for multiple role IDs (size + idList format).</summary>
public static Octets CreateGetOtherEquipDetailCmd(int[] roleIds)
{
if (roleIds == null || roleIds.Length == 0)
throw new ArgumentException("roleIds cannot be null or empty.", nameof(roleIds));
var cmd = new CMD_GetOtherEquipDetail { size = (ushort)roleIds.Length, idList = roleIds };
return SerializeCommand(CommandID.GET_OTHER_EQUIP_DETAIL, cmd);
}
public static Octets CreateDropIvtrItem(byte index, int amount)
{
var cmd = new CMD_DropIvtrItem
@@ -1363,6 +1363,34 @@ namespace CSNetwork.GPDataType
public uint count_equip;
}
/// <summary>S2C PLAYER_EQUIP_DETAIL: one equipment slot entry (slot index + item template id).</summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct s2c_player_equip_detail_slot
{
public byte slot_index;
public int tid;
}
/// <summary>Parsed S2C PLAYER_EQUIP_DETAIL payload for readable use.</summary>
public class PlayerEquipDetailData
{
public int RoleId { get; set; }
public List<(byte SlotIndex, int Tid)> Slots { get; } = new List<(byte, int)>();
public override string ToString()
{
var sb = new StringBuilder();
sb.Append($"[PlayerEquipDetail] RoleId={RoleId}, Slots={Slots.Count}: ");
for (int i = 0; i < Slots.Count; i++)
{
var (slot, tid) = Slots[i];
if (i > 0) sb.Append(", ");
sb.Append($"[{slot}]=tid{tid}");
}
return sb.ToString();
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct cmd_move_equip_item
{
@@ -1701,6 +1729,30 @@ namespace CSNetwork.GPDataType
return (id & 0xC0000000) == 0xC0000000;
}
/// <summary>Parse S2C PLAYER_EQUIP_DETAIL payload into readable data. Layout: roleId (4), count (2), then count × (slot byte, tid int).</summary>
public static PlayerEquipDetailData ParsePlayerEquipDetail(byte[] data)
{
var result = new PlayerEquipDetailData();
if (data == null || data.Length < 6)
return result;
int pos = 0;
result.RoleId = BitConverter.ToInt32(data, pos);
pos += 4;
ushort count = BitConverter.ToUInt16(data, pos);
pos += 2;
int slotSize = Marshal.SizeOf<s2c_player_equip_detail_slot>();
for (int i = 0; i < count && pos + slotSize <= data.Length; i++)
{
var slot = FromBytes<s2c_player_equip_detail_slot>(data, pos);
result.Slots.Add((slot.slot_index, slot.tid));
pos += slotSize;
}
return result;
}
public static A3DVECTOR3 g_vOrigin = new A3DVECTOR3(0.0f);
public static A3DVECTOR3 g_vAxisX = new A3DVECTOR3(1.0f, 0.0f, 0.0f);
public static A3DVECTOR3 g_vAxisY = new A3DVECTOR3(0.0f, 1.0f, 0.0f);
@@ -15,6 +15,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BrewMonster.Scripts;
using BrewMonster.Scripts.Managers;
using UnityEngine;
using CommandID = CSNetwork.GPDataType.CommandID;
@@ -639,7 +640,39 @@ namespace CSNetwork
EC_Game.GetGameRun().SetLogoutFlag(2);
}
}
/// <summary>Format parsed PLAYER_EQUIP_DETAIL with real game data: slot names and item names from element data.</summary>
private static string FormatPlayerEquipDetailReadable(PlayerEquipDetailData parsed)
{
var sb = new StringBuilder();
sb.Append($"RoleId={parsed.RoleId}");
if (parsed.Slots.Count == 0)
{
sb.Append(", no slots");
return sb.ToString();
}
sb.Append(" | ");
for (int i = 0; i < parsed.Slots.Count; i++)
{
var (slotIndex, tid) = parsed.Slots[i];
string slotName = GetEquipSlotName(slotIndex);
string itemName = tid <= 0 ? "(empty)" : (EC_IvtrItemUtils.Instance.ResolveItemName(tid) ?? $"(tid{tid})");
if (string.IsNullOrEmpty(itemName)) itemName = $"(tid{tid})";
if (i > 0) sb.Append(", ");
sb.Append($"{slotName}: {itemName}");
}
return sb.ToString();
}
private static string GetEquipSlotName(byte slotIndex)
{
if (Enum.IsDefined(typeof(IndexOfIteminEquipmentInventory), slotIndex))
{
var name = Enum.GetName(typeof(IndexOfIteminEquipmentInventory), slotIndex);
if (!string.IsNullOrEmpty(name)) return name;
}
return $"Slot{slotIndex}";
}
private void HandleServerDataSend(gamedatasend protocol)
{
@@ -783,6 +816,26 @@ namespace CSNetwork
EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ITEMOPERATION, (int)MANAGER_INDEX.MAN_PLAYER, 0,
pDataBuf, pCmdHeader);
break;
case CommandID.PLAYER_EQUIP_DETAIL:
// View other player profile/equip detail — parse and log with real game data (item names, slot names)
if (pDataBuf != null && pDataBuf.Length > 0)
{
try
{
var parsed = GPDataTypeHelper.ParsePlayerEquipDetail(pDataBuf);
string readable = FormatPlayerEquipDetailReadable(parsed);
Debug.Log($"[PLAYER_EQUIP_DETAIL] {readable}");
if (parsed.Slots.Count == 0 && pDataBuf.Length > 6)
Debug.Log($"[PLAYER_EQUIP_DETAIL] Raw(hex): {BitConverter.ToString(pDataBuf)}");
}
catch (Exception ex)
{
Debug.LogWarning($"[PLAYER_EQUIP_DETAIL] Parse failed: {ex.Message}. Raw length={pDataBuf.Length}, hex: {BitConverter.ToString(pDataBuf, 0, Math.Min(64, pDataBuf.Length))}...");
}
}
else
Debug.Log("[PLAYER_EQUIP_DETAIL] Server sent empty payload.");
break;
case CommandID.PLAYER_CASH:
{
EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_IVTRINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf,
@@ -1824,6 +1877,14 @@ namespace CSNetwork
// }
}
/// <summary>Request other player profile/equip detail (C2S GET_OTHER_EQUIP_DETAIL). Server responds with PLAYER_EQUIP_DETAIL.</summary>
public void c2s_SendCmdGetOtherEquipDetail(int roleId)
{
gamedatasend gamedatasend = new gamedatasend();
gamedatasend.Data = C2SCommandFactory.CreateGetOtherEquipDetailCmd(roleId);
SendProtocol(gamedatasend);
}
public void c2s_SendCmdNPCSevAcceptTask(int idTask, int idStorage, int idRefreshItem)
{
gamedatasend gamedatasend = new gamedatasend();
@@ -732,6 +732,12 @@ namespace BrewMonster.Network
Instance._gameSession.CmdCache.SendCmdExtProps();
}
/// <summary>Request other player profile/equip detail (C2S GET_OTHER_EQUIP_DETAIL). Server responds with PLAYER_EQUIP_DETAIL; response is currently logged via Debug.Log.</summary>
public static void c2s_SendCmdGetOtherEquipDetail(int roleId)
{
Instance._gameSession.c2s_SendCmdGetOtherEquipDetail(roleId);
}
/// <summary>Send C2S::SET_STATUS_POINT (attribute point allocation).</summary>
public static void c2s_CmdSetStatusPts(int vitality, int energy, int strength, int agility)
{
@@ -60,7 +60,8 @@ namespace BrewMonster.UI
{
Debug.Log("OnViewInfo: " + characterId);
var list = new List<int> { characterId };
UnityGameSession.GetRoleBaseInfo(1, list);
//UnityGameSession.GetRoleBaseInfo(1, list);
UnityGameSession.c2s_SendCmdGetOtherEquipDetail(characterId);
}
void OnTeamInvite(int characterId)