diff --git a/Assets/PerfectWorld/Scripts/Managers/CECNPCMan.cs b/Assets/PerfectWorld/Scripts/Managers/CECNPCMan.cs index da872c1e90..948d9bb4d0 100644 --- a/Assets/PerfectWorld/Scripts/Managers/CECNPCMan.cs +++ b/Assets/PerfectWorld/Scripts/Managers/CECNPCMan.cs @@ -9,7 +9,6 @@ using System.Buffers.Binary; using System.Collections.Generic; using System.Runtime.InteropServices; using BrewMonster.Network; -using Unity.VisualScripting; using UnityEngine; public class CECNPCMan : IMsgHandler diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkSpell.cs b/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkSpell.cs index 26b153b95d..7e3171f17a 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkSpell.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkSpell.cs @@ -1,5 +1,4 @@ using CSNetwork.GPDataType; -using Unity.VisualScripting; using UnityEngine; using static BrewMonster.Scripts.CECHPWorkSpell.Spell_magic_state; namespace BrewMonster.Scripts diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkTrace.cs b/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkTrace.cs index fdd5b8c8f9..7ca4f5100d 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkTrace.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkTrace.cs @@ -6,7 +6,6 @@ using CSNetwork.GPDataType; using System; using System.Runtime.ConstrainedExecution; using PerfectWorld.Scripts; -using Unity.VisualScripting; using UnityEngine; /////////////////////////////////////////////////////////////////////////// diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs b/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs index 93f3700dce..5e020ab927 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Unity.VisualScripting; using UnityEngine; namespace BrewMonster.Scripts.Managers @@ -129,6 +128,42 @@ namespace BrewMonster.Scripts.Managers m_aItems[iSlot2] = tmp; } + /// + /// Place or stack item in a specific slot (server-specified slot). Matches C++ expectation that client uses same slot as server. + /// + public bool PutItemInSlot(int iSlot, int tid, int iExpireDate, int iAmount, out int piLastSlot, out int piLastAmount) + { + piLastSlot = -1; + piLastAmount = 0; + if (iSlot < 0 || iSlot >= m_aItems.Length || iAmount <= 0) + return false; + + var slotItem = m_aItems[iSlot]; + if (slotItem == null) + { + var newItem = EC_IvtrItem.CreateItem(tid, iExpireDate, iAmount); + if (newItem == null) + return false; + newItem.Slot = iSlot; + newItem.SetCount(iAmount); + m_aItems[iSlot] = newItem; + piLastSlot = iSlot; + piLastAmount = iAmount; + return true; + } + if (slotItem.GetTemplateID() != tid) + return false; + int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(tid)); + int canAdd = Math.Max(0, pileLimit - Math.Max(0, slotItem.GetCount())); + if (canAdd <= 0) + return false; + int add = Math.Min(canAdd, iAmount); + slotItem.AddAmount(add); + piLastSlot = iSlot; + piLastAmount = slotItem.GetCount(); + return true; + } + public bool MergeItem(int tid, int iExpireDate, int iAmount, out int piLastSlot, out int piLastAmount) { piLastSlot = -1; @@ -170,6 +205,9 @@ namespace BrewMonster.Scripts.Managers return false; } var newItem = EC_IvtrItem.CreateItem(tid, iExpireDate, iAmount); + if (newItem == null) + return false; + newItem.Slot = firstEmpty; newItem.SetCount(iAmount); m_aItems[firstEmpty] = newItem; diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrEquip.cs b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrEquip.cs index c90d41ef89..1ee7462d39 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrEquip.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrEquip.cs @@ -14,9 +14,6 @@ using System.Text.RegularExpressions; using System.Reflection; using BrewMonster.Scripts.Managers; using BrewMonster.Scripts; -using UnityEngine.AddressableAssets; -using CSNetwork.Protocols; -using Unity.VisualScripting; namespace PerfectWorld.Scripts.Managers { diff --git a/Assets/PerfectWorld/Scripts/ModelFiles/CECTaoistRank.cs b/Assets/PerfectWorld/Scripts/ModelFiles/CECTaoistRank.cs index 0ecaca5d44..ae799e4970 100644 --- a/Assets/PerfectWorld/Scripts/ModelFiles/CECTaoistRank.cs +++ b/Assets/PerfectWorld/Scripts/ModelFiles/CECTaoistRank.cs @@ -1,4 +1,3 @@ -using Unity.VisualScripting; using UnityEngine; namespace BrewMonster diff --git a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs index 2fc72a6358..e7111f217c 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs @@ -1,26 +1,17 @@ -using Animancer; -using BrewMonster; using BrewMonster.Managers; using BrewMonster.Network; using BrewMonster.PerfectWorld.Scripts.Vfx; using BrewMonster.Scripts; -using BrewMonster.Scripts; using BrewMonster.Scripts.Managers; using BrewMonster.Scripts.Skills; using CSNetwork.GPDataType; using ModelRenderer.Scripts.GameData; -using PerfectWorld.Scripts.Managers; using System; using System.Collections.Generic; using System.Threading.Tasks; -using System.Xml.Linq; using TMPro; -using Unity.VisualScripting; using UnityEngine; using UnityEngine.SceneManagement; -using UnityEngine.UIElements; -using static BrewMonster.CECPlayer; -using BrewMonster.Network; using System.Runtime.InteropServices; using PerfectWorld.Scripts.Managers.BrewMonster.Managers; using CSNetwork; diff --git a/Assets/PerfectWorld/Scripts/NPC/CECMonster.cs b/Assets/PerfectWorld/Scripts/NPC/CECMonster.cs index fb1bbc6b59..af051e4245 100644 --- a/Assets/PerfectWorld/Scripts/NPC/CECMonster.cs +++ b/Assets/PerfectWorld/Scripts/NPC/CECMonster.cs @@ -4,9 +4,6 @@ using ModelRenderer.Scripts.GameData; using System; using System.Runtime.InteropServices; using System.Text; -using Unity.VisualScripting; -using UnityEngine; -using static CECNPC; public class CECMonster : CECNPC { diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs index a2f2c05aea..2fcbaa4fc5 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs @@ -1315,6 +1315,26 @@ namespace CSNetwork.GPDataType public byte bySlot; } + /// One item in cmd_purchase_item (buy from NPC/booth). Wire: item_id, expire_date, count, inv_index, booth_slot = 15 bytes. + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct cmd_purchase_item_ITEM + { + public int item_id; + public int expire_date; + public uint count; + public ushort inv_index; + public byte booth_slot; + } + + /// Fixed header of cmd_purchase_item. Rest of packet is item_count x cmd_purchase_item_ITEM. + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct cmd_purchase_item_header + { + public uint cost; + public uint yinpiao; + public byte flag; + public ushort item_count; + } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct cmd_get_own_money @@ -1394,7 +1414,7 @@ namespace CSNetwork.GPDataType public byte index; }; [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct cmd_pickup_item + public struct cmd_pickup_item { public int tid; public int expire_date; diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs index dc0a3ac586..2cd0e8486d 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs @@ -1,2043 +1,2067 @@ -using BrewMonster; -using BrewMonster.Common; -using BrewMonster.Managers; -using BrewMonster.Network; -using BrewMonster.Scripts.Skills; -using BrewMonster.UI; -using CSNetwork.C2SCommand; -using CSNetwork.GPDataType; -using CSNetwork.Protocols; -using CSNetwork.Protocols.RPCData; -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using UnityEngine; -using CommandID = CSNetwork.GPDataType.CommandID; - -namespace CSNetwork -{ - public class GameSession : IDisposable - { - private static IPrefixedLogger - _logger = LoggerFactory.GetLogger(nameof(GameSession)); // Get class-specific logger - - private NetworkManager _networkManager; - private string _host; - private int _port; - private string _username; - private string _password; - private int _currentUserId = -1; // To store the UserID after successful login - private uint _localsid = 0; // To store the LocalSID from onlineannounce - private int m_iCharID; - private int m_idLastSelTarget = 0; // ID of selected item last time - - CECStringTab m_ErrorMsgs; - - // State management for async operations and callbacks - private Action _loginCallback; - private Action> _roleListCallback; - private List _accumulatedRoles; - private Action _selectRoleCallback; - private Action _createRoleCallback; - private RoleInfo _selectedRole; - public bool IsConnected => _networkManager?.IsConnected ?? false; - public static SynchronizationContext Context; - private CECC2SCmdCache m_CmdCache; // C2S command cache - - /// Raised when server sends PROTOCOL_PLAYERLOGOUT(69). - public event Action PlayerLogoutReceived; - - /// Raised when the underlying network disconnects. - public event Action Disconnected; - - private static void PostToUnityContext(Action action) - { - if (action == null) return; - var ctx = Context; - if (ctx != null) - { - ctx.Post(_ => action(), null); - } - else - { - action(); - } - } -#if UNITY_EDITOR - public bool isDebug; - public bool IsDebug - { - get => isDebug; - set => isDebug = value; - } -#endif - public CECC2SCmdCache CmdCache { get => m_CmdCache; } - - - public GameSession() - { - _networkManager = new NetworkManager(); - m_CmdCache = new CECC2SCmdCache(); - _networkManager.ProtocolReceived += OnProtocolReceived; - _networkManager.ErrorOccurred += OnErrorOccurred; - _networkManager.Disconnected += OnDisconnected; - } - - public void SetLogPath(string path) - { - LoggerFactory.SetFileLoggerImplementation(new FileLogger()); - _logger = LoggerFactory.GetCustomLogger(path, nameof(GameSession) + GetHashCode(), LoggerType.File); - _networkManager.SetLogPath(path); - } - - /// - /// Connects to the game server asynchronously. - /// - /// Server hostname or IP address. - /// Server port. - /// Task representing the asynchronous connect operation. Check IsConnected property or handle Disconnected event for status. - public async Task ConnectAsync(string host, int port) - { - if (IsConnected) - { - _logger.Log(LogType.Warning, "ConnectAsync called but already connected."); - return; - } - - _host = host; - _port = port; - _logger.Log(LogType.Info, $"Attempting to connect to {_host}:{_port}..."); - try - { - await _networkManager.ConnectAsync(_host, _port); - if (IsConnected) - { - _logger.Log(LogType.Info, "Connection established."); - } - else - { - _logger.Log(LogType.Warning, - "Connection failed after ConnectAsync completed (check NetworkManager logs/events)."); - } - } - catch (Exception ex) - { - _logger.Log(LogType.Error, $"Connection exception: {ex.Message}"); - _logger.LogException(ex); - OnDisconnected(); - } - } - - - public void Disconnect() - { - _networkManager.Disconnect(); - } - - /// - /// Initiates the login process asynchronously. - /// - /// Account username. - /// Account password. - /// Action invoked with true on successful login (OnlineAnnounce received), false otherwise. - public void LoginAsync(string username, string password, Action callback) - { - if (!IsConnected) - { - _logger.Log(LogType.Warning, "LoginAsync called but not connected."); - callback?.Invoke(false); - return; - } - - if (_loginCallback != null) - { - _logger.Log(LogType.Warning, "LoginAsync called while another login is already in progress."); - callback?.Invoke(false); - return; - } - - _username = username; - _password = password; - _loginCallback = callback; - _currentUserId = -1; // Reset user ID - - _logger.Log(LogType.Info, $"Initiating login for user '{_username}'..."); - } - - /// - /// Initiates fetching the role list asynchronously. Requires successful login. - /// - /// Action invoked with the complete list of roles, or null/empty list on failure. - public void GetRoleListAsync(Action> callback) - { - if (!IsConnected) - { - _logger.Log(LogType.Warning, "GetRoleListAsync called but not connected."); - callback?.Invoke(null); - return; - } - - if (_currentUserId == -1) - { - _logger.Log(LogType.Warning, "GetRoleListAsync called but not logged in."); - callback?.Invoke(null); - return; - } - - if (_roleListCallback != null) - { - _logger.Log(LogType.Warning, - "GetRoleListAsync called while another role list retrieval is already in progress."); - callback?.Invoke(null); - return; - } - - _roleListCallback = callback; - _accumulatedRoles = new List(); - _logger.Log(LogType.Info, "Requesting role list..."); - RequestRoleListInternal(); - } - - public RoleInfo GetRoleInfo() - { - return _selectedRole; - } - - public void SelectRoleAsync(RoleInfo role, Action callback) - { - _selectedRole = role; - _selectRoleCallback = callback; - SetCharacterID(role.roleid); - SendProtocol(new selectrole() - { - Roleid = role.roleid, - Flag = 0 - }); - } - - public void CreateRoleAsync(RoleInfo roleInfo, Octets referId, Action callback) - { - if (!IsConnected) - { - callback?.Invoke(null); - return; - } - - if (_currentUserId == -1) - { - callback?.Invoke(null); - return; - } - - if (_createRoleCallback != null) - { - callback?.Invoke(null); - return; - } - - _createRoleCallback = callback; - - createrole createRoleProtocol = new createrole() - { - Userid = _currentUserId, - Localsid = _localsid, - Roleinfo = roleInfo, - Referid = referId ?? new Octets() - }; - - Debug.Log($"[GameSession] Creating role - UserID: {_currentUserId}, Localsid: {_localsid}, Profession: {roleInfo.occupation}, Gender: {roleInfo.gender}"); - Debug.Log($"[GameSession] RoleInfo details - Name size: {roleInfo.name?.Size ?? 0}, Equipment count: {roleInfo.equipment?.Count ?? 0}, Custom data size: {roleInfo.custom_data?.Size ?? 0}, Race: {roleInfo.race}"); - - // Log first few bytes of custom_data for debugging - if (roleInfo.custom_data != null && roleInfo.custom_data.Size > 0) - { - byte[] customDataPreview = new byte[Math.Min(16, (int)roleInfo.custom_data.Size)]; - Array.Copy(roleInfo.custom_data.ByteArray, 0, customDataPreview, 0, customDataPreview.Length); - string hexPreview = BitConverter.ToString(customDataPreview).Replace("-", " "); - Debug.Log($"[GameSession] Custom_data preview (first 16 bytes): {hexPreview}"); - } - - Debug.Log($"[GameSession] Sending createrole protocol (Type: {createRoleProtocol.Type})"); - SendProtocol(createRoleProtocol); - } - - /// - /// Helper method to create a new RoleInfo for character creation. - /// Matches C++ NewCharacterImpl behavior. - /// - public static RoleInfo CreateNewRoleInfo(string name, int profession, int gender) - { - RoleInfo roleInfo = new RoleInfo(); - - // Set basic info - roleInfo.occupation = (byte)profession; - roleInfo.gender = (byte)gender; - roleInfo.level = 1; - roleInfo.level2 = 0; - roleInfo.status = 0; // _ROLE_STATUS_NORMAL - roleInfo.delete_time = 0; - roleInfo.create_time = 0; // Server will set this - roleInfo.lastlogin_time = 0; - roleInfo.posx = 0.0f; - roleInfo.posy = 0.0f; - roleInfo.posz = 0.0f; - roleInfo.worldtag = 0; // Server will set this - roleInfo.referrer_role = 0; - roleInfo.cash_add = 0; - - // Set name - C++ uses Unicode encoding (ACHAR = wchar_t) - if (!string.IsNullOrEmpty(name)) - { - byte[] nameBytes = Encoding.Unicode.GetBytes(name); - roleInfo.name = new Octets(nameBytes); - } - else - { - roleInfo.name = new Octets(); - } - - // Initialize equipment list with 29 empty items (IVTRSIZE_EQUIPPACK = 29) - roleInfo.equipment = new List(); - for (int i = 0; i < 29; i++) - { - // Important: GRoleInventory.data must be non-null or Marshal() will fail and the packet won't send. - roleInfo.equipment.Add(new GRoleInventory - { - id = 0, - pos = i, - count = 0, - max_count = 0, - data = new Octets(), - proctype = 0, - expire_date = 0, - guid1 = 0, - guid2 = 0, - mask = 0 - }); - } - - // Initialize custom data exactly as C++ does: memset to 0, then set specific values - // This matches C++ LoadDefaultCustomizeData behavior - roleInfo.custom_data = CreateDefaultCustomizeData(profession, gender); - - // Initialize other empty custom data fields - roleInfo.custom_status = new Octets(); - roleInfo.charactermode = new Octets(); - roleInfo.reincarnation_data = new Octets(); - roleInfo.realm_data = new Octets(); - - // Race is typically determined by profession, but we'll leave it at 0 for now - // Server may set it based on profession - roleInfo.race = 0; - - return roleInfo; - } - - /// - /// Creates default customize data exactly matching C++ LoadDefaultCustomizeData behavior. - /// Structure layout - trying multiple sizes due to potential padding: - /// - DWORD dwVersion (offset 0-3, 4 bytes) = 0x10007001 - /// - FACE_CUSTOMIZEDATA faceData (offset 4-87, 84 bytes) = all zeros - /// - unsigned short bodyID (offset 88-89, 2 bytes) = 0 - /// - A3DCOLOR colorBody (offset 90-93, 4 bytes) = 0xffffffff - /// - 6 unsigned char scales (offset 94-99, 6 bytes) = all 128 - /// Note: C++ sizeof might include padding, trying 100, 104, 108 bytes - /// - private static Octets CreateDefaultCustomizeData(int profession, int gender) - { - // C++ sizeof(PLAYER_CUSTOMIZEDATA) - Based on server response, it expects 176 bytes - // The C++ code sends: sizeof(CECPlayer::PLAYER_CUSTOMIZEDATA) which appears to be 176 bytes - // Server response shows: custom_data size=176, so server expects/receives 176 bytes - // Structure layout (176 bytes total): - // - DWORD dwVersion (4 bytes, offset 0) = 0x10007001 - // - FACE_CUSTOMIZEDATA faceData (84 bytes, offset 4) = all zeros for now - // - unsigned short bodyID (2 bytes, offset 88) = 0 - // - A3DCOLOR colorBody (4 bytes, offset 90) = 0xffffffff - // - 6 unsigned char scales (6 bytes, offset 94) = all 128 - // - Padding/additional fields (76 bytes, offset 100-175) = all zeros - const int CUSTOMIZE_DATA_SIZE = 176; // Match server expectation (176 bytes from response) - const uint CUSTOMIZE_DATA_VERSION = 0x10007001; // CUSTOMIZE_DATA_VERSION from C++ - byte[] customDataBytes = new byte[CUSTOMIZE_DATA_SIZE]; - - // Step 1: memset to 0 (already done by new byte[]) - - // Step 2: Set dwVersion at offset 0-3 (little-endian) - byte[] versionBytes = BitConverter.GetBytes(CUSTOMIZE_DATA_VERSION); - Array.Copy(versionBytes, 0, customDataBytes, 0, 4); - - // Step 3: FACE_CUSTOMIZEDATA at offset 4-87 is already zero (84 bytes) - // (In C++ this would be loaded from INI, but for now we use zeros) - - // Step 4: bodyID at offset 88-89 is already zero (2 bytes) - // Note: There might be 2 bytes padding here if struct is 4-byte aligned - - // Step 5: Set colorBody to 0xffffffff - // Try offset 90 first (no padding), if that doesn't work try 92 (with padding) - byte[] colorBodyBytes = BitConverter.GetBytes(0xffffffffu); - Array.Copy(colorBodyBytes, 0, customDataBytes, 90, 4); // Try 90 first - - // Step 6: Set all 6 scale fields to 128 - // Try offset 94 first (no padding), if that doesn't work try 96 (with padding) - for (int i = 94; i < 100; i++) - { - customDataBytes[i] = 128; - } - - return new Octets(customDataBytes); - } - - public void EnterWorldAsync(RoleInfo role, Action callback) - { - SendProtocol(new enterworld() - { - Roleid = _selectedRole.roleid, - Provider_link_id = 0, - }, callback); - } - - public void RequestDropIvtrItem(byte index, int amount) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.CreateDropIvtrItem(index, amount); - SendProtocol(gamedatasendRequest); - } - - public void RequestDropEquipItem(byte index) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.CreateDropEquipItem(index); - SendProtocol(gamedatasendRequest); - } - - public void RequestPickupItem(int idItem, int tid) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.CreatePickupItem(idItem, tid); - SendProtocol(gamedatasendRequest); - } - - public void c2s_SendCmdGetIvtrDetailData(byte byPackage, Action callback) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.c2s_SendCmdGetIvtrDetailData(byPackage); - SendProtocol(gamedatasendRequest, callback); - } - - public void c2s_SendCmdQueryCashInfo() - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdQueryCashInfo(); - SendProtocol(gamedatasendRequest); - } - - public void c2s_SendCmdOpenFashionTrash(string password) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdOpenFashionTrash(password); - SendProtocol(gamedatasendRequest); - } - - public void c2s_SendCmdEquipItem(byte iIvtrIdx, byte iEquipIdx, Action callback) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.c2s_SendCmdEquipItem(iIvtrIdx, iEquipIdx); - SendProtocol(gamedatasendRequest, callback); - } - - public void c2s_SendCmdReviveVillage(int param = 0) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdReviveVillage(param); - SendProtocol(gamedatasendRequest); - } - public void c2s_SendCmdReviveItem(int param = 0) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdReviveVillage(param); - SendProtocol(gamedatasendRequest); - } - public void RequestReviveByPlayer(int param = 0) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdReviveVillage(param); - SendProtocol(gamedatasendRequest); - } - - - public void c2s_SendCmdMallShopping(uint count, CMD_MallShopping.goods[] goodsArray) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.CreateGetMallShopping(count, goodsArray); - SendProtocol(gamedatasendRequest); - } - public void c2s_SendCmdGatherMaterial(int idMatter, int iToolPack, int idToolIndex, int idTool, int idTask) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.c2s_SendCmdGatherMaterial(idMatter, iToolPack, idToolIndex, idTool, idTask); - SendProtocol(gamedatasendRequest); - } - - public void c2s_SendCmdUseItemWithTarget(byte byPackage, byte bySlot, int tid, byte byPVPMask) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateUseItemWithTarget(byPackage, bySlot, tid, byPVPMask); - SendProtocol(gamedatasend); - } - - public void RequestOwnItemInfoAsync( - byte byPackage, - byte bySlot, - int type, - int expire_date, - int state, - uint count, - ushort crc, - ushort content_length, - byte[] content, - Action callback) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.CreateOwnItemInfo(byPackage, bySlot, type, expire_date, state, - count, crc, content_length, content); - SendProtocol(gamedatasendRequest, callback); - } - - // --- Protocol Sending --- - public void SendProtocol(Protocol protocol, Action complete = null) - { - if (IsConnected) - { - _logger.Log(LogType.Debug, - $"Sending protocol: {protocol.GetType().Name} (Detail: {protocol.ToString})"); - BMLogger.Log($"[GameSession] Sending protocol: {protocol.GetType().Name} (Type: {protocol.GetPType()}) + {protocol.ToString}"); - _networkManager.Send(protocol); - complete?.Invoke(); - } - else - { - _logger.Log(LogType.Warning, $"Cannot send protocol ({protocol.GetType().Name}), not connected."); - BMLogger.LogError($"[GameSession] Cannot send protocol ({protocol.GetType().Name}), not connected."); - } - } - - // --- Event Handlers (from NetworkManager) --- - - private void OnProtocolReceived(Protocol protocol) - { - _logger.Log(LogType.Debug, $"Received protocol: {protocol.GetType().Name} (Type: {protocol.Type})"); - if (protocol is null) - return; - - - // Route protocol to appropriate handler - switch (protocol.GetPType()) - { - case ProtocolType.PROTOCOL_CHALLENGE: - HandleChallenge((challenge)protocol); - break; - case ProtocolType.PROTOCOL_KEYEXCHANGE: - HandleKeyExchange((KeyExchange)protocol); - break; - case ProtocolType.PROTOCOL_ONLINEANNOUNCE: - HandleOnlineAnnounce((onlineannounce)protocol); - break; - case ProtocolType.PROTOCOL_ROLELIST_RE: - HandleRoleListResponse((RoleListResponse)protocol); - break; - // Add cases for other protocols GameSession might need to handle - case ProtocolType.PROTOCOL_SELECTROLE_RE: - HandleSelectRoleResponse((SelectRole_Re)protocol); - //_networkManager.IgnoreBytes = 2; - break; - case ProtocolType.PROTOCOL_CREATEROLE_RE: - HandleCreateRoleResponse((createrole_re)protocol); - break; - case ProtocolType.PROTOCOL_ERRORINFO: - HandleErrorInfo((errorinfo)protocol); - break; - case ProtocolType.PROTOCOL_S2CGAMEDATASEND: - case ProtocolType.PROTOCOL_GAMEDATASEND: - HandleServerDataSend((gamedatasend)protocol); - break; - case ProtocolType.PROTOCOL_CHATMESSAGE: - _logger.Log(LogType.Warning, $"HoangDev :ProtocolType.PROTOCOL_CHATMESSAGE {protocol.GetPType()}"); - OnPrtcChatMessage(protocol, false); - break; - case ProtocolType.PROTOCOL_PLAYERBASEINFO_RE: - OnPrtcPlayerBaseInfoRe(protocol); - break; - case ProtocolType.PROTOCOL_GETUICONFIG_RE: OnPrtcGetConfigRe(protocol); break; - case ProtocolType.PROTOCOL_PLAYERLOGOUT: - HandlePlayerLogout((playerlogout)protocol); - break; - - case ProtocolType.PROTOCOL_AUTOTEAMSETGOAL_RE: - { - // CECAutoTeam pAutoTeam = CECGameRun.Instance.GetHostPlayer().GetAutoTeam(); - // if( pAutoTeam !=null) - // pAutoTeam.OnPrtcAutoTeamSetGoalRe((AutoTeamSetGoal_Re)protocol); - } - break; - - default: - _logger.Log(LogType.Warning, $"Received unhandled protocol type: {protocol.GetPType()}"); - break; - } - } - - private void HandlePlayerLogout(playerlogout protocol) - { - // Original client receives this before EVENT_DISCONNECT. - // We just publish it to allow higher-level flow (UnityGameSession/UI) to react. - PostToUnityContext(() => PlayerLogoutReceived?.Invoke(protocol)); - } - - private void HandleServerDataSend(gamedatasend protocol) - { - int lenghtHeader = Marshal.SizeOf(); - var pDataBuf = new byte[protocol.Data.ByteArray.Length - lenghtHeader]; - var byteArrHeader = new byte[lenghtHeader]; - long dwDataSize = protocol.Data.Size; - - if (dwDataSize < Marshal.SizeOf()) - { - _logger.Error($"### GameDataSend: size invalid {dwDataSize}"); - return; - } - - dwDataSize -= Marshal.SizeOf(); // subtract the header size (ushort) - for (int i = 0; i < protocol.Data.ByteArray.Length; i++) - { - if (i < lenghtHeader) - { - byteArrHeader[i] = protocol.Data.ByteArray[i]; - } - else - { - pDataBuf[i - lenghtHeader] = protocol.Data.ByteArray[i]; - } - } - - var pCmdHeader = BitConverter.ToUInt16(byteArrHeader); - //sss -#if UNITY_EDITOR - if (isDebug) - { - BMLogger.LogError($"### GameDataSend: CMDID {pCmdHeader}"); - } -#endif - int iHostID = _selectedRole.roleid; - switch (pCmdHeader) - { - case CommandID.PLAYER_INFO_2: - case CommandID.PLAYER_INFO_3: - case CommandID.PLAYER_INFO_4: - case CommandID.PLAYER_INFO_2_LIST: - case CommandID.PLAYER_INFO_3_LIST: - case CommandID.PLAYER_INFO_23_LIST: - - break; - - case CommandID.PLAYER_INFO_1: - case CommandID.PLAYER_ENTER_WORLD: - case CommandID.PLAYER_ENTER_SLICE: - case CommandID.PLAYER_INFO_1_LIST: - case CommandID.PLAYER_INFO_00: - case CommandID.SELF_INFO_1: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERINFO, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, - pCmdHeader, iHostID); - break; - case CommandID.OBJECT_MOVE: - int lenghtDataType = Marshal.SizeOf(); - byte[] arrByteData = GetBytes(pDataBuf, lenghtDataType, 0); - int idObjMove = BitConverter.ToInt32(arrByteData); - if (ISPLAYERID(idObjMove)) - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERMOVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, - pDataBuf, pCmdHeader, iHostID); - } - else if (ISNPCID(idObjMove)) - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCMOVE, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, - pCmdHeader); - } - - break; - case CommandID.OBJECT_STOP_MOVE: - { - int id1 = GPDataTypeHelper.FromBytes(pDataBuf); - - if (ISPLAYERID(id1)) - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERSTOPMOVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, - pDataBuf, pCmdHeader); - } - else if (ISNPCID(id1)) - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCSTOPMOVE, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, - pCmdHeader); - } - - break; - } - case CommandID.OBJECT_LEAVE_SLICE: - { - int id = GPDataTypeHelper.FromBytes(pDataBuf); - if (ISPLAYERID(id)) - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERRUNOUT, (int)MANAGER_INDEX.MAN_PLAYER, -1, - pDataBuf, pCmdHeader); - } - else if (ISNPCID(id)) - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCRUNOUT, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, - pCmdHeader); - } - - break; - } - case CommandID.OWN_IVTR_DATA: - case CommandID.OWN_IVTR_DETAIL_DATA: - case CommandID.GET_OWN_MONEY: - case CommandID.CHANGE_IVTR_SIZE: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_IVTRINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader, iHostID); - break; - case CommandID.EXG_IVTR_ITEM: - case CommandID.MOVE_IVTR_ITEM: - case CommandID.PLAYER_DROP_ITEM: - case CommandID.EXG_EQUIP_ITEM: - case CommandID.EQUIP_ITEM: - case CommandID.MOVE_EQUIP_ITEM: - case CommandID.UNFREEZE_IVTR_SLOT: - case CommandID.PLAYER_EQUIP_TRASHBOX_ITEM: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ITEMOPERATION, (int)MANAGER_INDEX.MAN_PLAYER, 0, - pDataBuf, pCmdHeader); - break; - case CommandID.PLAYER_CASH: - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_IVTRINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader, iHostID); - break; - } - case CommandID.MATTER_INFO_LIST: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTERINFO, (int)MANAGER_INDEX.MAN_MATTER, 0, pDataBuf, - pCmdHeader); - break; - case CommandID.MATTER_ENTER_WORLD: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTERENTWORLD, (int)MANAGER_INDEX.MAN_MATTER, 0, - pDataBuf, pCmdHeader); - break; - case CommandID.PICKUP_ITEM: - case CommandID.HOST_OBTAIN_ITEM: - case CommandID.PRODUCE_ONCE: - case CommandID.TASK_DELIVER_ITEM: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PICKUPITEM, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader); - break; - case CommandID.MATTER_PICKUP: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PICKUPMATTER, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - case CommandID.PICKUP_MONEY: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PICKUPMONEY, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader); - break; - case CommandID.HOST_CORRECT_POS: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CORRECTPOS, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader, iHostID); - break; - case CommandID.EMPTY_ITEM_SLOT: - case CommandID.OWN_ITEM_INFO: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_OWNITEMINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader, iHostID); - break; - case CommandID.PLAYER_DIED: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDIED, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - case CommandID.HOST_DIED: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_DIED, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.PLAYER_REVIVE: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERREVIVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - case CommandID.NOTIFY_HOSTPOS: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_GOTO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.NPC_ENTER_SLICE: - case CommandID.NPC_INFO_LIST: - case CommandID.NPC_INFO_00: - case CommandID.NPC_ENTER_WORLD: - case CommandID.NPC_VISIBLE_TID_NOTIFY: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCINFO, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, - pCmdHeader, dwDataSize); - break; - case CommandID.TASK_DATA: - case CommandID.TASK_VAR_DATA: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_TASKDATA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader, dwDataSize); - break; - case CommandID.BE_HURT: - case CommandID.HURT_RESULT: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_HURTRESULT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader); - break; - case CommandID.OBJECT_ATTACK_RESULT: - //int id = GPDataTypeHelper.FromBytes(pDataBuf); - cmd_object_atk_result pCmdAtk = GPDataTypeHelper.FromBytes(pDataBuf); - //BMLogger.LogError($"OBJECT_ATTACK_RESULT: npc ? " + ISNPCID(id)); - - if (ISPLAYERID(pCmdAtk.attacker_id)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERATKRESULT, MANAGER_INDEX.MAN_PLAYER, -1, - pDataBuf, pCmdHeader); - else if (ISNPCID(pCmdAtk.attacker_id)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCATKRESULT, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); - break; - case CommandID.HOST_ATTACKRESULT: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ATKRESULT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader); - break; - case CommandID.HOST_ATTACKED: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ATTACKED, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader); - break; - - case CommandID.TEAM_JOIN_TEAM: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_JOINTEAM, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.TEAM_LEAVE_PARTY: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_LEAVETEAM, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.TEAM_NEW_MEMBER: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_NEWTEAMMEM, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.TEAM_MEMBER_DATA: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_TEAMMEMBERDATA, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.TEAM_MEMBER_LEAVE: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_LEAVETEAM, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - - case CommandID.ERROR_MESSAGE: - { - int errRaw = BitConverter.ToInt32(pDataBuf, 0); - // Note: _logger may be configured as a file logger via SetLogPath(), so also log to console for visibility. - _logger.Info($"### GameDataSend: ERROR_MESSAGE: {errRaw}"); -#if UNITY_EDITOR - BMLogger.LogError($"### GameDataSend: ERROR_MESSAGE: {errRaw}"); -#endif - cmd_error_msg pCmd = GPDataTypeHelper.FromBytes(pDataBuf); -#if UNITY_EDITOR - BMLogger.LogError($"### GameDataSend: ERROR_MESSAGE parsed iMessage={pCmd.iMessage}"); -#endif - Debug.LogError($"### GameDataSend: ERROR_MESSAGE: {errRaw}"); - if (pCmd.iMessage != 0) - { - // string szMsg = m_ErrorMsgs.GetWideString(pCmd.iMessage); - // if (string.IsNullOrEmpty(szMsg)) - // BMLogger.LogError("SERVER - unknown error !"); - // else - // { - // BMLogger.LogError("SERVER - error: "+szMsg); - // } - // else if (pCmd.iMessage != 2) - // g_pGame.GetGameRun().AddChatMessage(szMsg, GP_CHAT_MISC); - } - - if (pCmd.iMessage == 2) - { - // Attack target is too far - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_TARGETISFAR, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - } - else if (pCmd.iMessage == 20) - { - // Failed to cast skill - //pGameRun.PostMessage(MSG_PM_CASTSKILL, MAN_PLAYER, 0, (DWORD)pDataBuf, pCmdHeader.cmd); - } - else if (pCmd.iMessage == 133 || pCmd.iMessage == 134) - { - // deal failed - //pGameRun.PostMessage(MSG_HST_BUY_SELL_FAIL, MAN_PLAYER, 0, (DWORD)pDataBuf, pCmdHeader.cmd); - } - else if (pCmd.iMessage == 158) - { - // µ±Ç°»ãÂʲ»¶Ô£¬ÖØÐÂÈ¡»ãÂÊ - //c2s_CmdGetCashMoneyRate(); - } - else if (pCmd.iMessage == 108 /*&& pGameRun.GetHostPlayer().IsInKingService()*/) - { - /* CECGameUIMan* pGameUI = pGameRun.GetUIManager().GetInGameUIMan(); - if (pGameUI) - pGameUI.EndNPCService();*/ - } - else if - (pCmd.iMessage == 108 /*&& pGameRun.GetHostPlayer().GetOfflineShopCtrl().GetNPCSevFlag() != COfflineShopCtrl::NPCSEV_NULL*/ - ) - { - /* CECGameUIMan* pGameUI = pGameRun.GetUIManager().GetInGameUIMan(); - if (pGameUI) - pGameUI.EndNPCService();*/ - } - else if (pCmd.iMessage == 175) - { - //c2s_CmdQueryParallelWorld(); - } - else if (pCmd.iMessage == 6) - { - //AP_ActionEvent(AP_EVENT_CANNOTPICKUP); - } - - break; - } - case CommandID.SELECT_TARGET: - case CommandID.UNSELECT: - - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SELTARGET, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader); - break; - case CommandID.NPC_DIED: - case CommandID.NPC_DIED2: - - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCDIED, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); - break; - case CommandID.OBJECT_DISAPPEAR: - { - int lenghtDataType1 = Marshal.SizeOf(); - byte[] arrByteData1 = GetBytes(pDataBuf, lenghtDataType1, 0); - int objectId = BitConverter.ToInt32(arrByteData1); - if (ISPLAYERID(objectId)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDISAPPEAR, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - else if (ISNPCID(objectId)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCDISAPPEAR, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); - else if (ISMATTERID(objectId)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTERDISAPPEAR, MANAGER_INDEX.MAN_MATTER, 0, pDataBuf, pCmdHeader); - - break; - } - case CommandID.SELF_INFO_00: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_INFO00, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader); - break; - case CommandID.NPC_GREETING: - { - // If this greeting is from the skill-learn NPC, record it (C++ skill dialog relies on this). - try - { - cmd_npc_greeting greet = GPDataTypeHelper.FromBytes(pDataBuf); - CECHostSkillModel.Instance.OnNpcGreeting(greet.idObject); - } - catch (Exception ex) - { - _logger.Log(LogType.Warning, $"Failed to parse NPC_GREETING payload: {ex.Message}"); - } - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_NPCGREETING, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - } - case CommandID.ACTIVATE_WAYPOINT: - case CommandID.WAYPOINT_LIST: - - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_WAYPOINT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.SERVER_TIME: - { - cmd_server_time pcmd_server_time = GPDataTypeHelper.FromBytes(pDataBuf); - EC_ManMessage.PostMessage(EC_MsgDef.MSG_SERVERTIME, -1, 0, pcmd_server_time.time, pcmd_server_time.timebias); - break; - } - case CommandID.SCENE_SERVICE_NPC_LIST: - { - CECHostSkillModel.Instance.RecvNPCServiceList(protocol.Data); - break; - } - case CommandID.SKILL_DATA: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SKILLDATA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.OBJECT_CAST_SKILL: - case CommandID.OBJECT_CAST_INSTANT_SKILL: - case CommandID.OBJECT_CAST_POS_SKILL: - { - cmd_object_cast_skill pCmd2 = GPDataTypeHelper.FromBytes(pDataBuf,true); - if (ISPLAYERID(pCmd2.caster)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_CASTSKILL, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - else if (ISNPCID(pCmd2.caster)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCCASTSKILL, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); - - break; - } - case CommandID.LEVEL_UP: - { - cmd_level_up pCmdLevelUp = GPDataTypeHelper.FromBytes(pDataBuf); ; - if (ISPLAYERID(pCmdLevelUp.id)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERLEVELUP, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - else if (ISNPCID(pCmdLevelUp.id)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCLEVELUP, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); - break; - } - case CommandID.HOST_START_ATTACK: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_STARTATTACK, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); - break; - case CommandID.HOST_STOPATTACK: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_STOPATTACK, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); - break; - case CommandID.HOST_SKILL_ATTACK_RESULT: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SKILLRESULT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.CHANGE_FACE_START: - case CommandID.CHANGE_FACE_END: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CHANGEFACE, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); - break; - case CommandID.ENCHANT_RESULT: - cmd_enchant_result pCmd3 = GPDataTypeHelper.FromBytes(pDataBuf); - if (ISPLAYERID(pCmd3.caster)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_ENCHANTRESULT, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - else if (ISNPCID(pCmd3.caster)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_ENCHANTRESULT, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); - break; - case CommandID.HOST_STOP_SKILL: - case CommandID.SELF_SKILL_INTERRUPTED: - case CommandID.SKILL_PERFORM: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_CASTSKILL, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.SET_COOLDOWN: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SETCOOLTIME, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.HOST_USE_ITEM: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_USEITEM, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.COMBO_SKILL_PREPARE: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_COMBO_SKILL_PREPARE, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); - break; - case CommandID.PLAYER_EXT_PROP_BASE: - case CommandID.PLAYER_EXT_PROP_MOVE: - case CommandID.PLAYER_EXT_PROP_ATK: - case CommandID.PLAYER_EXT_PROP_DEF: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYEREXTPROP, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - case CommandID.OWN_EXT_PROP: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_OWNEXTPROP, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.OBJECT_DO_EMOTE: - case CommandID.OBJECT_EMOTE_RESTORE: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDOEMOTE, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - case CommandID.OUT_OF_SIGHT_LIST: - { - cmd_out_of_sight_list pCmd5 = default; - pCmd5.uCount = GPDataTypeHelper.FromBytes(pDataBuf); - int offset2 = sizeof(uint); - pCmd5.idList = new int[pCmd5.uCount]; - for (int i = 0; i < pCmd5.uCount; i++) - { - pCmd5.idList[i] = GPDataTypeHelper.FromBytes(pDataBuf, offset2); - offset2 += 4;//sizeof int; - } - - for (uint n = 0; n < pCmd5.uCount; n++) - { - if (ISPLAYERID(pCmd5.idList[n])) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYEROUTOFVIEW, MANAGER_INDEX.MAN_PLAYER, -1, pCmd5.idList[n], pCmdHeader); - else if (ISNPCID(pCmd5.idList[n])) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCOUTOFVIEW, MANAGER_INDEX.MAN_NPC, 0, pCmd5.idList[n], pCmdHeader); - else if (ISMATTERID(pCmd5.idList[n])) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTEROUTOFVIEW, MANAGER_INDEX.MAN_MATTER, 0, pCmd5.idList[n], pCmdHeader); - } - - break; - } - case CommandID.PLAYER_GATHER_START: - case CommandID.PLAYER_GATHER_STOP: - case CommandID.MINE_GATHERED: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERGATHER, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - case CommandID.COOLTIME_DATA: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_COOLTIMEDATA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.OBJECT_TAKEOFF: - { - cmd_object_takeoff pCmdTakeOff = GPDataTypeHelper.FromBytes((byte[])pDataBuf); - if (ISPLAYERID(pCmdTakeOff.object_id)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERFLY, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - } - case CommandID.OBJECT_LANDING: - { - cmd_object_landing pCmdLanding = GPDataTypeHelper.FromBytes((byte[])pDataBuf); - if (ISPLAYERID(pCmdLanding.object_id)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERFLY, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - } - case CommandID.HOST_RUSH_FLY: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERFLY, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.FLYSWORD_TIME: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_FLYSWORDTIME, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.PRODUCE_START: - case CommandID.PRODUCE_END: - case CommandID.PRODUCE_NULL: - // Post MSG_HST_PRODUCEITEM message with command ID as parameter (matches C++ behavior) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PRODUCEITEM, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - - - case CommandID.LEARN_SKILL: - BMLogger.LogError("### GameDataSend: LEARN_SKILL"); - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_LEARNSKILL, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.GAIN_PET: - case CommandID.FREE_PET: - case CommandID.SUMMON_PET: - case CommandID.RECALL_PET: - case CommandID.PLAYER_START_PET_OP: - case CommandID.PLAYER_STOP_PET_OP: - case CommandID.PET_RECEIVE_EXP: - case CommandID.PET_LEVELUP: - case CommandID.PET_ROOM: - case CommandID.PET_ROOM_CAPACITY: - case CommandID.PET_HONOR_POINT: - case CommandID.PET_HUNGER_GAUGE: - case CommandID.PET_DEAD: - case CommandID.PET_REVIVE: - case CommandID.PET_HP_NOTIFY: - case CommandID.PET_AI_STATE: - case CommandID.PET_SET_COOLDOWN: - case CommandID.SUMMON_PLANT_PET: - case CommandID.PLANT_PET_DISAPPEAR: - case CommandID.PLANT_PET_HP_NOTIFY: - case CommandID.PET_PROPERTY: - case CommandID.PET_REBUILD_INHERIT_START: - case CommandID.PET_REBUILD_INHERIT_INFO: - case CommandID.PET_REBUILD_INHERIT_END: - case CommandID.PET_EVOLUTION_DONE: - case CommandID.PET_REBUILD_NATURE_START: - case CommandID.PET_REBUILD_NATURE_INFO: - case CommandID.PET_REBUILD_NATURE_END: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PETOPT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.SET_PLAYER_LIMIT: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SETPLAYERLIMIT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.PLAYER_MOUNTING: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERMOUNT, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - case CommandID.DUEL_RECV_REQUEST: - case CommandID.DUEL_REJECT_REQUEST: - case CommandID.DUEL_PREPARE: - case CommandID.DUEL_CANCEL: - case CommandID.HOST_DUEL_START: - case CommandID.DUEL_STOP: - case CommandID.DUEL_RESULT: - // Origin: host duel events use MSG_PM_DUELOPT (391); handler distinguishes by pCmdHeader (214-220) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_DUELOPT, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.PLAYER_DUEL_START: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDUELOPT, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - case CommandID.EMBED_ITEM: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_EMBEDITEM, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.CLEAR_TESSERA: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CLEARTESSERA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.PLAYER_LEAVE_WORLD: - { - cmd_player_leave_world pCmd = GPDataTypeHelper.FromBytes(pDataBuf); - if (ISPLAYERID(pCmd.id)) - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYEREXIT, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - } - break; - } - default: -#if UNITY_EDITOR - if (isDebug) - { - BMLogger.LogError($"### GameDataSend: Unhandled CMDID {pCmdHeader} (payloadBytes={pDataBuf?.Length ?? 0})"); - } -#endif - break; - } - } - - - private void HandleSelectRoleResponse(SelectRole_Re protocol) - { - _logger.Info($"Select role response {protocol.result}"); - var callback = _selectRoleCallback; - PostToUnityContext(() => callback?.Invoke(_selectedRole)); - } - - private void HandleCreateRoleResponse(createrole_re protocol) - { - Debug.Log($"[GameSession] HandleCreateRoleResponse - result: {protocol.result}, roleid: {protocol.roleid}"); - - if (protocol.result != (int)ErrCode.ERR_SUCCESS) - { - string errorMsg = $"Create role failed with result code: {protocol.result} (ERR_SUCCESS = {(int)ErrCode.ERR_SUCCESS})"; - _logger.Log(LogType.Error, errorMsg); - Debug.LogError($"[GameSession] {errorMsg}"); - var callback = _createRoleCallback; - _createRoleCallback = null; - PostToUnityContext(() => callback?.Invoke(null)); - return; - } - - Debug.Log($"[GameSession] Create role successful! RoleID: {protocol.roleid}"); - var successCallback = _createRoleCallback; - _createRoleCallback = null; - PostToUnityContext(() => successCallback?.Invoke(protocol.roleinfo)); - } - - private void HandleErrorInfo(errorinfo protocol) - { - Debug.LogError($"[GameSession] Server error - Errcode: {protocol.Errcode}"); - - // If we're waiting for create role response and get an error, fail the callback - if (_createRoleCallback != null) - { - Debug.LogError($"[GameSession] Create role failed due to server error: {protocol.Errcode}"); - var callback = _createRoleCallback; - _createRoleCallback = null; - PostToUnityContext(() => callback?.Invoke(null)); - } - } - - private void OnErrorOccurred(string errorMessage) - { - _logger.Log(LogType.Error, $"Network Error: {errorMessage}"); - FailLoginInProgress(errorMessage); - FailRoleListInProgress(errorMessage); - } - - private void OnDisconnected() - { - _logger.Log(LogType.Info, "Disconnected from server."); - _currentUserId = -1; - FailLoginInProgress("Disconnected"); - FailRoleListInProgress("Disconnected"); - // Clear command cache - m_CmdCache.RemoveAllCmds(); - PostToUnityContext(() => Disconnected?.Invoke()); - } - - private void HandleChallenge(challenge challenge) - { - if (_loginCallback == null || string.IsNullOrEmpty(_username)) - { - _logger.Log(LogType.Warning, "Received Challenge but not expecting it or username not set."); - return; - } - - _logger.Log(LogType.Info, "Handling Challenge..."); - - response response = new response(); - byte[] usernameBytes = Encoding.ASCII.GetBytes(_username); - byte[] passwordBytes = Encoding.ASCII.GetBytes(_password); - response.identity.Replace(usernameBytes); - response.Setup(new Octets(usernameBytes), new Octets(passwordBytes), challenge.nonce); - - uint clientId = 0xffffffff; - byte[] clientIdBytes = BitConverter.GetBytes(clientId); - response.cli_fingerprint.Replace(clientIdBytes); - response.use_token = 0; - - _networkManager.SetNonce(response.response_data); - SendProtocol(response); - _logger.Log(LogType.Info, "Sent Response."); - } - - private void HandleKeyExchange(KeyExchange keyExchange) - { - if (_loginCallback == null || string.IsNullOrEmpty(_username)) - { - _logger.Log(LogType.Warning, "Received KeyExchange but not expecting it."); - return; - } - - _logger.Log(LogType.Info, "Handling KeyExchange..."); - keyExchange.Setup(_networkManager, _username); - keyExchange.Blkickuser = 1; - SendProtocol(keyExchange); - _logger.Log(LogType.Info, "Sent KeyExchange acknowledgment/response."); - } - - private void HandleOnlineAnnounce(onlineannounce announce) - { - if (_loginCallback == null) - { - _logger.Log(LogType.Warning, "Received OnlineAnnounce but not expecting it."); - return; - } - - _logger.Log(LogType.Info, $"Login successful! UserID: {announce.Userid}, LocalSID: {announce.Localsid}"); - _currentUserId = announce.Userid; - _localsid = announce.Localsid; - - var callback = _loginCallback; - _loginCallback = null; - PostToUnityContext(() => callback?.Invoke(true)); - } - - private void RequestRoleListInternal(int lastHandle = -1) - { - rolelist rolelistRequest = new rolelist(); - rolelistRequest.Userid = _currentUserId; - rolelistRequest.Localsid = 0; - rolelistRequest.Handle = lastHandle; - - SendProtocol(rolelistRequest); - - - //gamedatasend gamedatasendRequest = new gamedatasend(); - //gamedatasendRequest.Data = C2SCommandFactory.CreatePlayerMove(); - - //SendProtocol(gamedatasendRequest); - } - - private void HandleRoleListResponse(RoleListResponse response) - { - if (_roleListCallback == null || _accumulatedRoles == null) - { - _logger.Log(LogType.Warning, "Received RoleListResponse but not expecting it."); - return; - } - - _logger.Log(LogType.Debug, - $"Received RoleListResponse. Handle: {response.handle}, Result: {response.result}, Count: {response.rolelist.Count}"); - - if (response.result == 0) - { - _accumulatedRoles.AddRange(response.rolelist); - - foreach (var role in response.rolelist) - { - try - { - string roleName = Encoding.UTF8.GetString(role.name.ByteArray, 0, role.name.Length); - _logger.Log(LogType.Info, $" - Role ID: {role.roleid}, Name: {roleName}, Level: {role.level}"); - } - catch (Exception ex) - { - _logger.Log(LogType.Error, $" - Error decoding role name: {ex.Message}"); - _logger.LogException(ex); - } - } - - if (response.handle != -1) - { - _logger.Log(LogType.Debug, $"Requesting next batch of roles (handle: {response.handle})..."); - RequestRoleListInternal(response.handle); - } - else - { - _logger.Log(LogType.Info, $"Finished fetching roles. Total count: {_accumulatedRoles.Count}"); - var callback = _roleListCallback; - var result = _accumulatedRoles; - _roleListCallback = null; - _accumulatedRoles = null; - PostToUnityContext(() => callback?.Invoke(result)); - } - } - else - { - _logger.Log(LogType.Error, $"Role list retrieval failed. Result code: {response.result}"); - FailRoleListInProgress($"Role list retrieval failed (Result: {response.result})"); - } - } - - // --- Helper methods for failure handling --- - private void FailLoginInProgress(string reason) - { - if (_loginCallback != null) - { - _logger.Log(LogType.Error, $"Login failed: {reason}"); - var callback = _loginCallback; - _loginCallback = null; - PostToUnityContext(() => callback?.Invoke(false)); - } - } - - private void FailRoleListInProgress(string reason) - { - if (_roleListCallback != null) - { - _logger.Log(LogType.Error, $"Role list retrieval failed: {reason}"); - var callback = _roleListCallback; - _roleListCallback = null; - _accumulatedRoles = null; - PostToUnityContext(() => callback?.Invoke(null)); - } - } - - // --- IDisposable Implementation --- - private bool disposedValue = false; - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - if (_networkManager != null) - { - _logger.Log(LogType.Info, "[DUCK] Disposing GameSession and disconnecting..."); - _networkManager.ProtocolReceived -= OnProtocolReceived; - _networkManager.ErrorOccurred -= OnErrorOccurred; - _networkManager.Disconnected -= OnDisconnected; - _networkManager.Disconnect(); - _networkManager.Dispose(); - _networkManager = null; - } - - _loginCallback = null; - _roleListCallback = null; - _accumulatedRoles = null; - } - - disposedValue = true; - } - } - - public void Dispose() - { - Dispose(true); - // GC.SuppressFinalize(this); - } - - public bool ISPLAYERID(int id) - { - return id != 0 && (id & 0x80000000) == 0; - } - public bool ISNPCID(int id) => ((id & 0x80000000) != 0) && ((id & 0x40000000) == 0); - public bool ISMATTERID(int id) => ((id) & 0xC0000000) == 0xC0000000; - private byte[] GetBytes(byte[] bytes, int length, int index) - { - byte[] arrByteData = new byte[length]; - for (int i = 0; i < length; i++) - { - arrByteData[i] = bytes[i + index]; - } - - return arrByteData; - } - - public void c2s_CmdPlayerMove(in Vector3 vCurPos, in Vector3 vDest, - int iTime, float fSpeed, int iMoveMode, ushort wStamp) - { - gamedatasend gamedatasend = new gamedatasend(); - - gamedatasend.Data = - C2SCommandFactory.CreatePlayerMove(vCurPos, vDest, (ushort)iTime, fSpeed, (byte)iMoveMode, wStamp); - SendProtocol(gamedatasend); - } - public void c2s_SendCmdCastSkill(int idSkill, byte byPVPMask, int iNumTarget, int[] aTargets) - { - gamedatasend gamedatasend = new gamedatasend(); - - gamedatasend.Data = - C2SCommandFactory.CreatePlayerCastSkill(idSkill, byPVPMask, iNumTarget, aTargets); - - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdCastInstantSkill(int idSkill, byte byPVPMask, int iNumTarget, int[] aTargets) - { - gamedatasend gamedatasend = new gamedatasend(); - - gamedatasend.Data = - C2SCommandFactory.CreatePlayerCastInstantSkill(idSkill, byPVPMask, iNumTarget, aTargets); - - SendProtocol(gamedatasend); - } - - public void c2s_CmdCastPosSkill(int idSkill, Vector3 vDest, byte byPVPMask, int iNumTarget, int aTargets) - { - gamedatasend gamedatasend = new gamedatasend(); - - gamedatasend.Data = - C2SCommandFactory.CreatePlayerCastPosSkill(idSkill, vDest, byPVPMask, iNumTarget, aTargets); - - SendProtocol(gamedatasend); - } - public void c2s_SendCmdNotifyForceAttack(int iForceAttack, byte refuseBless) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNotifyForceAttack(iForceAttack, refuseBless); - SendProtocol(gamedatasend); - } - public void c2s_SendCmdContinueAction() - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.CONTINUE_ACTION); - SendProtocol(gamedatasend); - } - - /// - /// Client logout request. Mirrors original client behavior: - /// - outType=1: back to select role - /// - outType=0: logout account - /// - public void SendPlayerLogout(int outType, Action complete = null) - { - var g = new gamedatasend(); - g.Data = C2SCommandFactory.CreatePlayerLogoutCmd(outType); - SendProtocol(g, complete); - } - public void c2s_SendCmdStopMove(in Vector3 vDest, float fSpeed, int iMoveMode, - byte byDir, ushort wStamp, int iTime) - { - gamedatasend gamedatasend = new gamedatasend(); - - gamedatasend.Data = - C2SCommandFactory.CreatePlayerStop(vDest, fSpeed, (byte)iMoveMode, byDir, wStamp, (ushort)iTime); - SendProtocol(gamedatasend); - } - - public void c2s_CmdSendEnterPKPrecinctint() - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.ENTER_PK_PROTECTED); - SendProtocol(gamedatasend); - } - public void SendChatData(byte cChannel, in string szMsg, int iPack, int iSlot) - { - publicchat publicChat = new publicchat(); - publicChat.Channel = cChannel; - publicChat.Roleid = m_iCharID; - - byte[] unicodeBytes = Encoding.Unicode.GetBytes(szMsg); - publicChat.Msg.Replace(unicodeBytes); - _logger.Log(LogType.Warning, $"HoangDev : publicChat {publicChat}"); - SendProtocol(publicChat); - } - public void LoadConfigData() - { - getuiconfig p = new getuiconfig(); - p.Roleid = m_iCharID; - SendProtocol(p); - } - - /// - /// Save config data to server (sends setuiconfig with compressed config blob). - /// 保存配置数据到服务器(发送 setuiconfig 及压缩后的配置数据)。 - /// - public void SaveConfigData(byte[] pBuf, int len) - { - BMLogger.Log($"[MH] Session.SaveConfigData | len={len}"); - - if (pBuf == null || len <= 0) return; - var p = new setuiconfig(); - p.Roleid = m_iCharID; - p.Localsid = (int)_localsid; - byte[] slice = new byte[len]; - Buffer.BlockCopy(pBuf, 0, slice, 0, len); - p.Ui_config = new Octets(slice); - - // return; - SendProtocol(p); - } - - private void SetCharacterID(int iCharID) - { - m_iCharID = iCharID; - } - - private void OnPrtcChatMessage(Protocol pProtocol, bool bCalledagain) - { - chatmessage p = (chatmessage)pProtocol; - - string strTemp = System.Text.Encoding.Unicode.GetString(p.Msg.ToArray(), 0, p.Msg.Length); - - _logger.Log(LogType.Warning, $"HoangDev : OnPrtcChatMessage :{strTemp}"); - EventBus.Publish(new ChatMessageEvent(strTemp)); - } - - public struct ChatMessageEvent - { - public string context; - - public ChatMessageEvent(string context) - { - this.context = context; - } - } - public void OnPrtcGetConfigRe(Protocol pProtocol) - { - getuiconfig_re p = (getuiconfig_re)pProtocol; - if (p.Result != (int)ErrCode.ERR_SUCCESS) - BMLogger.LogError("CECGameSession::OnPrtcGetConfigRe, link return error code of " + p.Result); - else - { - if (!CECGameRun.Instance.LoadConfigsFromServer(p.UiConfig.RawBuffer, p.UiConfig.Size)) - { - // if load failed then use current setting directly - //TODO : fix later - EC_Game.GetConfigs().ApplyUserSetting(); - } - - // Now, Get config data request is sent after all host initial data ready. - // so when we receive this reply, we can do some last work before game - // really starts. Maybe it's not the best place to do these work, but - // now we do it here. - // Enalbe game UI - CECGameUIMan pGameUI = (CECGameUIMan)EC_Game.GetGameRun().GetUIManager().GetInGameUIMan(); - if (pGameUI != null) - { - pGameUI.EnableUI(true); - - // Get referral name for adding friend or other display - //TODO: a Hung lam phan select role info di - /* RoleInfo info = EC_Game.GetGameRun().GetSelectedRoleInfo(); - if (info.referrer_role > 0) - GetPlayerBriefInfo(1, info.referrer_role, 2);*/ - } - - CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer(); - pHost.OnAllInitDataReady(); - - /* if (pHost.IsGM()) - { - CDlgCountryMap pDlgCountryMap = (CDlgCountryMap)pGameUI.GetDialog("Win_CountryMap"); - pDlgCountryMap.GetConfig(); - } - - g_pGame.GetConfigs().ApplyOptimizeSetting(); - - if (g_pGame.GetConfigs().IsMiniClient()) - CECMCDownload::GetInstance().SendGetDownloadOK();*/ - } - } - private void OnPrtcPlayerBaseInfoRe(Protocol pProtocol) - { - playerbaseinfo_re p = (playerbaseinfo_re)pProtocol; - BMLogger.Log($"OnPrtcPlayerBaseInfoRe: {p.Roleid} {p.Player.cls} {p.Player.gender}"); - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERBASEINFO, MANAGER_INDEX.MAN_PLAYER, -1, p); - } - - public void c2s_CmdNPCSevAcceptTask(int idTask, int idStorage, int idRefreshItem) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateCmdNPCSevAcceptTask(idTask, idStorage, idRefreshItem); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdGetAllData(bool byPack, bool byEquip, bool byTask) - { - gamedatasend gamedatasend = new gamedatasend(); - - gamedatasend.Data = C2SCommandFactory.CreateGetAllDataCommand(byPack, byEquip, byTask); - _logger.Log(LogType.Warning, $"[Dat]- SendCmdGetAllData {byPack},{byEquip},{byTask}"); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevHello(int nid) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevHelloDataCommand(nid); - SendProtocol(gamedatasend); - } - - public void c2s_CmdNormalAttack(byte byPVPMask) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNormalAttackDataCmd(byPVPMask); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdCancelAction() - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(CSNetwork.C2SCommand.CommandID.CANCEL_ACTION); - SendProtocol(gamedatasend); - } - - public void c2s_CmdUnselect() - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(CSNetwork.C2SCommand.CommandID.UNSELECT); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdSelectTarget(int idTarget) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateSelectTarget(idTarget); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevWaypoint() - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevWaypointCmd(NPC_service_type.GP_NPCSEV_WAYPOINT, 0); - SendProtocol(gamedatasend); - } - public void c2s_SendCmdNPCSevMakeItem(int idSkill, int idItem, uint dwCount) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevMakeItemCmd(idSkill, idItem, dwCount); - SendProtocol(gamedatasend); - } - public void GetRoleBaseInfo(int iNumRole, List aRoleIDs) - { - int iNumLimit = 128; - playerbaseinfo p = null; - int iCount = 0; - - while (iCount < iNumRole) - { - p = new(); - p.Roleid = _selectedRole.roleid; - - int iNumSend = iNumLimit; - if (iCount + iNumLimit > iNumRole) - iNumSend = iNumRole - iCount; - - if (iNumSend > 0) - { - p.playerList = new(); - for (int i = 0; i < iNumSend; i++) - p.playerList.Add(aRoleIDs[iCount + i]); - - SendProtocol(p); - } - - - iCount += iNumSend; - } - } - - public void c2s_SendCmdGetOtherEquip(int iNumID, List aIDs) - { - // int iNumLimit = 250; - // int iCount = 0; - - // while (iCount < iNumID) - // { - // int iNumSend = iNumLimit; - // if (iCount + iNumLimit > iNumID) - // iNumSend = iNumID - iCount; - - // if (iNumSend > 0) - // { - // } - // } - } - - public void c2s_SendCmdNPCSevAcceptTask(int idTask, int idStorage, int idRefreshItem) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateCmdNPCSevAcceptTask( - idTask, - idStorage, - idRefreshItem); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevReturnTask(int idTask, int iChoice) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevReturnTaskCmd( - idTask, - iChoice); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevTaskMatter(int idTask) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevTaskMatterCmd(idTask); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevLearnSkill(int idSkill) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevLearnSkillCmd(idSkill); - BMLogger.LogError("HoangDev : c2s_SendCmdNPCSevLearnSkill gamedatasend.Data : " + gamedatasend.Data.Size); - BMLogger.LogError("HoangDev : c2s_SendCmdNPCSevLearnSkill idSkill : " + idSkill); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevBuy(int itemNum, C2SCommand.npc_trade_item[] items) - { - if (itemNum <= 0 || items == null || items.Length < itemNum) - return; - - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevBuyCmd(itemNum, items); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevSell(int itemNum, C2SCommand.npc_sell_item[] items) - { - if (itemNum <= 0 || items == null || items.Length < itemNum) - return; - - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevSellCmd(itemNum, items); - SendProtocol(gamedatasend); - } - - public void GetRoleCustomizeData(int iNumRole, List aRoleIDs) - { - if (iNumRole <= 0 || aRoleIDs == null || aRoleIDs.Count == 0) return; - - int iNumLimit = 240; - int iCount = 0; - - while (iCount < iNumRole) - { - getcustomdata p = new(); - p.Roleid = _selectedRole.roleid; - - int iNumSend = iNumLimit; - if (iCount + iNumLimit > iNumRole) - iNumSend = iNumRole - iCount; - - for (int i = 0; i < iNumSend; i++) - p.playerlist.Add(aRoleIDs[iCount + i]); - - SendProtocol(p); - - iCount += iNumSend; - } - } - public void c2s_SendCmdEmoteAction(uint wPose) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateEmoteActionCmd((int)wPose); - SendProtocol(gamedatasend); - } - public void c2s_SendCmdTaskNotify(byte[] pData, uint dwDataSize) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateTaskNotifyCmd( pData, dwDataSize); - BMLogger.Log($"[MH Task] c2s_SendCmdTaskNotify Command ID : {pData[0]} Size: {dwDataSize}"); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdStandUp() - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.STAND_UP); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdAutoTeamSetGoal(int type, int goal_id, int op) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateAutoTeamSetGoalCommand(type,goal_id, op); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdTeamInvite(int idPlayer) - { - var g = new gamedatasend(); - g.Data = C2SCommandFactory.CreateTeamInviteCommand(idPlayer); - SendProtocol(g); - } - - public void c2s_SendCmdDuelRequest(int idTarget) - { - var g = new gamedatasend(); - g.Data = C2SCommandFactory.CreateDuelRequestCommand(idTarget); - SendProtocol(g); - } - - public void c2s_SendCmdDuelReply(bool accept, int idInviter) - { - var g = new gamedatasend(); - g.Data = C2SCommandFactory.CreateDuelReplyCommand(accept, idInviter); - SendProtocol(g); - } - - public void c2s_SendCmdTeamKickMember(int idMember) - { - var g = new gamedatasend(); - g.Data = C2SCommandFactory.CreateTeamKickMemberCommand(idMember); - SendProtocol(g); - } - - public void c2s_SendCmdTeamLeaveParty() - { - var g = new gamedatasend(); - g.Data = C2SCommandFactory.CreateTeamLeavePartyCommand(); - SendProtocol(g); - } - - public void c2s_SendCmdTeamDismissParty() - { - var g = new gamedatasend(); - g.Data = C2SCommandFactory.CreateTeamDismissPartyCommand(); - SendProtocol(g); - } - - public void c2s_SendCmdTeamSetPickupFlag(short pickupFlag) - { - var g = new gamedatasend(); - g.Data = C2SCommandFactory.CreateTeamSetPickupFlagCommand(pickupFlag); - SendProtocol(g); - } - - public void c2s_SendCmdTeamChangeLeader(int idNewLeader) - { - var g = new gamedatasend(); - g.Data = C2SCommandFactory.CreateTeamChangeLeaderCommand(idNewLeader); - SendProtocol(g); - } - - public void c2s_SendCmdTeamMemberPos(int count, int[] memberIds) - { - if (memberIds == null || count <= 0 || count > memberIds.Length) return; - var g = new gamedatasend(); - g.Data = C2SCommandFactory.CreateTeamMemberPosCommand(count, memberIds); - SendProtocol(g); - } - - public void c2s_CmdGoto(float x, float y, float z) - { - c2s_SendCmdGoto(x, y, z); - } - - // Send C2S::GOTO command data - void c2s_SendCmdGoto(float x, float y, float z) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateGoToCommed( x, y, z); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdUseItem(byte byPackage, byte bySlot, int tid, byte byCount) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateUseItemCmd(byPackage, bySlot, tid, byCount); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdGetExtProps() - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.GET_EXT_PROP); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdGivePresent(int roleid, int mail_id, int goods_id, int goods_index, int goods_slot) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateGivePresentCmd(roleid, mail_id, goods_id, goods_index, goods_slot); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdEnterSanctuary(int id) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateEnterSanctuaryCmd(id); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdEnterInstance(int iTransIdx, int idInst) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateEnterInstanceCmd(iTransIdx, idInst); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdActiveRushFly(bool bActive) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateActiveRushFlyCmd(bActive); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdPetCtrl(int idTarget, int cmd, byte[] pParamBuf, int iParamLen) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreatePetCtrlCmd(idTarget, cmd, pParamBuf, iParamLen); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdQueryFactionPVPInfo(int faction_id) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateQueryFactionPVPInfo(faction_id); - SendProtocol(gamedatasend); - } - - public void SendCmdPetCtrl(int idTarget, int cmd, object pParamBuf, int iParamLen) - { - m_CmdCache.SendCmdPetCtrl(idTarget, cmd, (byte[])pParamBuf, iParamLen); - } - - public void c2s_SendCmdPetSummon(int iPetIdx) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreatePetSummon(iPetIdx); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevEmbed(ushort wStoneIdx, ushort wEquipIdx, int tidStone, int tidEquip) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevEmbedCmd(wStoneIdx, wEquipIdx, tidStone, tidEquip); - SendProtocol(gamedatasend); - } - public void c2s_SendCmdGetItemInfo(byte byPackage, byte bySlot) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateGetItemInfoCmd(byPackage, bySlot); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdSetStatusPts(int vitality, int energy, int strength, int agility) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateSetStatusPtCmd(vitality, energy, strength, agility); - SendProtocol(gamedatasend); - } - public void c2s_CmdNPCSevClearEmbeddedChip(int iEquipIdx, int tidEquip) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevClearEmbeddedChipCmd(iEquipIdx, tidEquip); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdPetRecall() - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(CSNetwork.C2SCommand.CommandID.RECALL_PET); - SendProtocol(gamedatasend); - } - public void c2s_CmdDebug(ushort icmd, int param1 = int.MinValue) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateDebugCmd(icmd, param1); - SendProtocol(gamedatasend); - } - public void c2s_SendCmdNPCSevHatchPet(int iIvtrIdx, int idEgg) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevHatchPetCmd(iIvtrIdx, idEgg); - SendProtocol(gamedatasend); - } - public void c2s_SendCmdNPCSevRestorePet(int iPetIdx) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevRestorePetCmd(iPetIdx); - SendProtocol(gamedatasend); - } - - // Cross-server get in (C++: c2s_CmdNPCSevCrossServerGetIn) — TODO: implement C2S packet when needed - public void c2s_CmdNPCSevCrossServerGetIn() - { - // TODO: C2SCommandFactory.CreateNPCSevCrossServerGetInCmd() and SendProtocol - } - - // Cross-server get out (C++: c2s_CmdNPCSevCrossServerGetOut) — TODO: implement C2S packet when needed - public void c2s_CmdNPCSevCrossServerGetOut() - { - // TODO: C2SCommandFactory.CreateNPCSevCrossServerGetOutCmd() and SendProtocol - } - } +using BrewMonster; +using BrewMonster.Common; +using BrewMonster.Managers; +using BrewMonster.Network; +using BrewMonster.Scripts.Skills; +using BrewMonster.UI; +using CSNetwork.C2SCommand; +using CSNetwork.GPDataType; +using CSNetwork.Protocols; +using CSNetwork.Protocols.RPCData; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; +using CommandID = CSNetwork.GPDataType.CommandID; + +namespace CSNetwork +{ + public class GameSession : IDisposable + { + private static IPrefixedLogger + _logger = LoggerFactory.GetLogger(nameof(GameSession)); // Get class-specific logger + + private NetworkManager _networkManager; + private string _host; + private int _port; + private string _username; + private string _password; + private int _currentUserId = -1; // To store the UserID after successful login + private uint _localsid = 0; // To store the LocalSID from onlineannounce + private int m_iCharID; + private int m_idLastSelTarget = 0; // ID of selected item last time + + CECStringTab m_ErrorMsgs; + + // State management for async operations and callbacks + private Action _loginCallback; + private Action> _roleListCallback; + private List _accumulatedRoles; + private Action _selectRoleCallback; + private Action _createRoleCallback; + private RoleInfo _selectedRole; + public bool IsConnected => _networkManager?.IsConnected ?? false; + public static SynchronizationContext Context; + private CECC2SCmdCache m_CmdCache; // C2S command cache + + /// Raised when server sends PROTOCOL_PLAYERLOGOUT(69). + public event Action PlayerLogoutReceived; + + /// Raised when the underlying network disconnects. + public event Action Disconnected; + + private static void PostToUnityContext(Action action) + { + if (action == null) return; + var ctx = Context; + if (ctx != null) + { + ctx.Post(_ => action(), null); + } + else + { + action(); + } + } +#if UNITY_EDITOR + public bool isDebug; + public bool IsDebug + { + get => isDebug; + set => isDebug = value; + } +#endif + public CECC2SCmdCache CmdCache { get => m_CmdCache; } + + + public GameSession() + { + _networkManager = new NetworkManager(); + m_CmdCache = new CECC2SCmdCache(); + _networkManager.ProtocolReceived += OnProtocolReceived; + _networkManager.ErrorOccurred += OnErrorOccurred; + _networkManager.Disconnected += OnDisconnected; + } + + public void SetLogPath(string path) + { + LoggerFactory.SetFileLoggerImplementation(new FileLogger()); + _logger = LoggerFactory.GetCustomLogger(path, nameof(GameSession) + GetHashCode(), LoggerType.File); + _networkManager.SetLogPath(path); + } + + /// + /// Connects to the game server asynchronously. + /// + /// Server hostname or IP address. + /// Server port. + /// Task representing the asynchronous connect operation. Check IsConnected property or handle Disconnected event for status. + public async Task ConnectAsync(string host, int port) + { + if (IsConnected) + { + _logger.Log(LogType.Warning, "ConnectAsync called but already connected."); + return; + } + + _host = host; + _port = port; + _logger.Log(LogType.Info, $"Attempting to connect to {_host}:{_port}..."); + try + { + await _networkManager.ConnectAsync(_host, _port); + if (IsConnected) + { + _logger.Log(LogType.Info, "Connection established."); + } + else + { + _logger.Log(LogType.Warning, + "Connection failed after ConnectAsync completed (check NetworkManager logs/events)."); + } + } + catch (Exception ex) + { + _logger.Log(LogType.Error, $"Connection exception: {ex.Message}"); + _logger.LogException(ex); + OnDisconnected(); + } + } + + + public void Disconnect() + { + _networkManager.Disconnect(); + } + + /// + /// Initiates the login process asynchronously. + /// + /// Account username. + /// Account password. + /// Action invoked with true on successful login (OnlineAnnounce received), false otherwise. + public void LoginAsync(string username, string password, Action callback) + { + if (!IsConnected) + { + _logger.Log(LogType.Warning, "LoginAsync called but not connected."); + callback?.Invoke(false); + return; + } + + if (_loginCallback != null) + { + _logger.Log(LogType.Warning, "LoginAsync called while another login is already in progress."); + callback?.Invoke(false); + return; + } + + _username = username; + _password = password; + _loginCallback = callback; + _currentUserId = -1; // Reset user ID + + _logger.Log(LogType.Info, $"Initiating login for user '{_username}'..."); + } + + /// + /// Initiates fetching the role list asynchronously. Requires successful login. + /// + /// Action invoked with the complete list of roles, or null/empty list on failure. + public void GetRoleListAsync(Action> callback) + { + if (!IsConnected) + { + _logger.Log(LogType.Warning, "GetRoleListAsync called but not connected."); + callback?.Invoke(null); + return; + } + + if (_currentUserId == -1) + { + _logger.Log(LogType.Warning, "GetRoleListAsync called but not logged in."); + callback?.Invoke(null); + return; + } + + if (_roleListCallback != null) + { + _logger.Log(LogType.Warning, + "GetRoleListAsync called while another role list retrieval is already in progress."); + callback?.Invoke(null); + return; + } + + _roleListCallback = callback; + _accumulatedRoles = new List(); + _logger.Log(LogType.Info, "Requesting role list..."); + RequestRoleListInternal(); + } + + public RoleInfo GetRoleInfo() + { + return _selectedRole; + } + + public void SelectRoleAsync(RoleInfo role, Action callback) + { + _selectedRole = role; + _selectRoleCallback = callback; + SetCharacterID(role.roleid); + SendProtocol(new selectrole() + { + Roleid = role.roleid, + Flag = 0 + }); + } + + public void CreateRoleAsync(RoleInfo roleInfo, Octets referId, Action callback) + { + if (!IsConnected) + { + callback?.Invoke(null); + return; + } + + if (_currentUserId == -1) + { + callback?.Invoke(null); + return; + } + + if (_createRoleCallback != null) + { + callback?.Invoke(null); + return; + } + + _createRoleCallback = callback; + + createrole createRoleProtocol = new createrole() + { + Userid = _currentUserId, + Localsid = _localsid, + Roleinfo = roleInfo, + Referid = referId ?? new Octets() + }; + + Debug.Log($"[GameSession] Creating role - UserID: {_currentUserId}, Localsid: {_localsid}, Profession: {roleInfo.occupation}, Gender: {roleInfo.gender}"); + Debug.Log($"[GameSession] RoleInfo details - Name size: {roleInfo.name?.Size ?? 0}, Equipment count: {roleInfo.equipment?.Count ?? 0}, Custom data size: {roleInfo.custom_data?.Size ?? 0}, Race: {roleInfo.race}"); + + // Log first few bytes of custom_data for debugging + if (roleInfo.custom_data != null && roleInfo.custom_data.Size > 0) + { + byte[] customDataPreview = new byte[Math.Min(16, (int)roleInfo.custom_data.Size)]; + Array.Copy(roleInfo.custom_data.ByteArray, 0, customDataPreview, 0, customDataPreview.Length); + string hexPreview = BitConverter.ToString(customDataPreview).Replace("-", " "); + Debug.Log($"[GameSession] Custom_data preview (first 16 bytes): {hexPreview}"); + } + + Debug.Log($"[GameSession] Sending createrole protocol (Type: {createRoleProtocol.Type})"); + SendProtocol(createRoleProtocol); + } + + /// + /// Helper method to create a new RoleInfo for character creation. + /// Matches C++ NewCharacterImpl behavior. + /// + public static RoleInfo CreateNewRoleInfo(string name, int profession, int gender) + { + RoleInfo roleInfo = new RoleInfo(); + + // Set basic info + roleInfo.occupation = (byte)profession; + roleInfo.gender = (byte)gender; + roleInfo.level = 1; + roleInfo.level2 = 0; + roleInfo.status = 0; // _ROLE_STATUS_NORMAL + roleInfo.delete_time = 0; + roleInfo.create_time = 0; // Server will set this + roleInfo.lastlogin_time = 0; + roleInfo.posx = 0.0f; + roleInfo.posy = 0.0f; + roleInfo.posz = 0.0f; + roleInfo.worldtag = 0; // Server will set this + roleInfo.referrer_role = 0; + roleInfo.cash_add = 0; + + // Set name - C++ uses Unicode encoding (ACHAR = wchar_t) + if (!string.IsNullOrEmpty(name)) + { + byte[] nameBytes = Encoding.Unicode.GetBytes(name); + roleInfo.name = new Octets(nameBytes); + } + else + { + roleInfo.name = new Octets(); + } + + // Initialize equipment list with 29 empty items (IVTRSIZE_EQUIPPACK = 29) + roleInfo.equipment = new List(); + for (int i = 0; i < 29; i++) + { + // Important: GRoleInventory.data must be non-null or Marshal() will fail and the packet won't send. + roleInfo.equipment.Add(new GRoleInventory + { + id = 0, + pos = i, + count = 0, + max_count = 0, + data = new Octets(), + proctype = 0, + expire_date = 0, + guid1 = 0, + guid2 = 0, + mask = 0 + }); + } + + // Initialize custom data exactly as C++ does: memset to 0, then set specific values + // This matches C++ LoadDefaultCustomizeData behavior + roleInfo.custom_data = CreateDefaultCustomizeData(profession, gender); + + // Initialize other empty custom data fields + roleInfo.custom_status = new Octets(); + roleInfo.charactermode = new Octets(); + roleInfo.reincarnation_data = new Octets(); + roleInfo.realm_data = new Octets(); + + // Race is typically determined by profession, but we'll leave it at 0 for now + // Server may set it based on profession + roleInfo.race = 0; + + return roleInfo; + } + + /// + /// Creates default customize data exactly matching C++ LoadDefaultCustomizeData behavior. + /// Structure layout - trying multiple sizes due to potential padding: + /// - DWORD dwVersion (offset 0-3, 4 bytes) = 0x10007001 + /// - FACE_CUSTOMIZEDATA faceData (offset 4-87, 84 bytes) = all zeros + /// - unsigned short bodyID (offset 88-89, 2 bytes) = 0 + /// - A3DCOLOR colorBody (offset 90-93, 4 bytes) = 0xffffffff + /// - 6 unsigned char scales (offset 94-99, 6 bytes) = all 128 + /// Note: C++ sizeof might include padding, trying 100, 104, 108 bytes + /// + private static Octets CreateDefaultCustomizeData(int profession, int gender) + { + // C++ sizeof(PLAYER_CUSTOMIZEDATA) - Based on server response, it expects 176 bytes + // The C++ code sends: sizeof(CECPlayer::PLAYER_CUSTOMIZEDATA) which appears to be 176 bytes + // Server response shows: custom_data size=176, so server expects/receives 176 bytes + // Structure layout (176 bytes total): + // - DWORD dwVersion (4 bytes, offset 0) = 0x10007001 + // - FACE_CUSTOMIZEDATA faceData (84 bytes, offset 4) = all zeros for now + // - unsigned short bodyID (2 bytes, offset 88) = 0 + // - A3DCOLOR colorBody (4 bytes, offset 90) = 0xffffffff + // - 6 unsigned char scales (6 bytes, offset 94) = all 128 + // - Padding/additional fields (76 bytes, offset 100-175) = all zeros + const int CUSTOMIZE_DATA_SIZE = 176; // Match server expectation (176 bytes from response) + const uint CUSTOMIZE_DATA_VERSION = 0x10007001; // CUSTOMIZE_DATA_VERSION from C++ + byte[] customDataBytes = new byte[CUSTOMIZE_DATA_SIZE]; + + // Step 1: memset to 0 (already done by new byte[]) + + // Step 2: Set dwVersion at offset 0-3 (little-endian) + byte[] versionBytes = BitConverter.GetBytes(CUSTOMIZE_DATA_VERSION); + Array.Copy(versionBytes, 0, customDataBytes, 0, 4); + + // Step 3: FACE_CUSTOMIZEDATA at offset 4-87 is already zero (84 bytes) + // (In C++ this would be loaded from INI, but for now we use zeros) + + // Step 4: bodyID at offset 88-89 is already zero (2 bytes) + // Note: There might be 2 bytes padding here if struct is 4-byte aligned + + // Step 5: Set colorBody to 0xffffffff + // Try offset 90 first (no padding), if that doesn't work try 92 (with padding) + byte[] colorBodyBytes = BitConverter.GetBytes(0xffffffffu); + Array.Copy(colorBodyBytes, 0, customDataBytes, 90, 4); // Try 90 first + + // Step 6: Set all 6 scale fields to 128 + // Try offset 94 first (no padding), if that doesn't work try 96 (with padding) + for (int i = 94; i < 100; i++) + { + customDataBytes[i] = 128; + } + + return new Octets(customDataBytes); + } + + public void EnterWorldAsync(RoleInfo role, Action callback) + { + SendProtocol(new enterworld() + { + Roleid = _selectedRole.roleid, + Provider_link_id = 0, + }, callback); + } + + public void RequestDropIvtrItem(byte index, int amount) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.CreateDropIvtrItem(index, amount); + SendProtocol(gamedatasendRequest); + } + + public void RequestDropEquipItem(byte index) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.CreateDropEquipItem(index); + SendProtocol(gamedatasendRequest); + } + + public void RequestPickupItem(int idItem, int tid) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.CreatePickupItem(idItem, tid); + SendProtocol(gamedatasendRequest); + } + + public void c2s_SendCmdGetIvtrDetailData(byte byPackage, Action callback) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.c2s_SendCmdGetIvtrDetailData(byPackage); + SendProtocol(gamedatasendRequest, callback); + } + + public void c2s_SendCmdQueryCashInfo() + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdQueryCashInfo(); + SendProtocol(gamedatasendRequest); + } + + public void c2s_SendCmdOpenFashionTrash(string password) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdOpenFashionTrash(password); + SendProtocol(gamedatasendRequest); + } + + public void c2s_SendCmdEquipItem(byte iIvtrIdx, byte iEquipIdx, Action callback) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.c2s_SendCmdEquipItem(iIvtrIdx, iEquipIdx); + SendProtocol(gamedatasendRequest, callback); + } + + public void c2s_SendCmdReviveVillage(int param = 0) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdReviveVillage(param); + SendProtocol(gamedatasendRequest); + } + public void c2s_SendCmdReviveItem(int param = 0) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdReviveVillage(param); + SendProtocol(gamedatasendRequest); + } + public void RequestReviveByPlayer(int param = 0) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdReviveVillage(param); + SendProtocol(gamedatasendRequest); + } + + + public void c2s_SendCmdMallShopping(uint count, CMD_MallShopping.goods[] goodsArray) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.CreateGetMallShopping(count, goodsArray); + SendProtocol(gamedatasendRequest); + } + public void c2s_SendCmdGatherMaterial(int idMatter, int iToolPack, int idToolIndex, int idTool, int idTask) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.c2s_SendCmdGatherMaterial(idMatter, iToolPack, idToolIndex, idTool, idTask); + SendProtocol(gamedatasendRequest); + } + + public void c2s_SendCmdUseItemWithTarget(byte byPackage, byte bySlot, int tid, byte byPVPMask) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateUseItemWithTarget(byPackage, bySlot, tid, byPVPMask); + SendProtocol(gamedatasend); + } + + public void RequestOwnItemInfoAsync( + byte byPackage, + byte bySlot, + int type, + int expire_date, + int state, + uint count, + ushort crc, + ushort content_length, + byte[] content, + Action callback) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.CreateOwnItemInfo(byPackage, bySlot, type, expire_date, state, + count, crc, content_length, content); + SendProtocol(gamedatasendRequest, callback); + } + + // --- Protocol Sending --- + public void SendProtocol(Protocol protocol, Action complete = null) + { + if (IsConnected) + { + _logger.Log(LogType.Debug, + $"Sending protocol: {protocol.GetType().Name} (Detail: {protocol.ToString})"); + BMLogger.Log($"[GameSession] Sending protocol: {protocol.GetType().Name} (Type: {protocol.GetPType()}) + {protocol.ToString}"); + _networkManager.Send(protocol); + complete?.Invoke(); + } + else + { + _logger.Log(LogType.Warning, $"Cannot send protocol ({protocol.GetType().Name}), not connected."); + BMLogger.LogError($"[GameSession] Cannot send protocol ({protocol.GetType().Name}), not connected."); + } + } + + // --- Event Handlers (from NetworkManager) --- + + private void OnProtocolReceived(Protocol protocol) + { + _logger.Log(LogType.Debug, $"Received protocol: {protocol.GetType().Name} (Type: {protocol.Type})"); + if (protocol is null) + return; + + + // Route protocol to appropriate handler + switch (protocol.GetPType()) + { + case ProtocolType.PROTOCOL_CHALLENGE: + HandleChallenge((challenge)protocol); + break; + case ProtocolType.PROTOCOL_KEYEXCHANGE: + HandleKeyExchange((KeyExchange)protocol); + break; + case ProtocolType.PROTOCOL_ONLINEANNOUNCE: + HandleOnlineAnnounce((onlineannounce)protocol); + break; + case ProtocolType.PROTOCOL_ROLELIST_RE: + HandleRoleListResponse((RoleListResponse)protocol); + break; + // Add cases for other protocols GameSession might need to handle + case ProtocolType.PROTOCOL_SELECTROLE_RE: + HandleSelectRoleResponse((SelectRole_Re)protocol); + //_networkManager.IgnoreBytes = 2; + break; + case ProtocolType.PROTOCOL_CREATEROLE_RE: + HandleCreateRoleResponse((createrole_re)protocol); + break; + case ProtocolType.PROTOCOL_ERRORINFO: + HandleErrorInfo((errorinfo)protocol); + break; + case ProtocolType.PROTOCOL_S2CGAMEDATASEND: + case ProtocolType.PROTOCOL_GAMEDATASEND: + HandleServerDataSend((gamedatasend)protocol); + break; + case ProtocolType.PROTOCOL_CHATMESSAGE: + _logger.Log(LogType.Warning, $"HoangDev :ProtocolType.PROTOCOL_CHATMESSAGE {protocol.GetPType()}"); + OnPrtcChatMessage(protocol, false); + break; + case ProtocolType.PROTOCOL_PLAYERBASEINFO_RE: + OnPrtcPlayerBaseInfoRe(protocol); + break; + case ProtocolType.PROTOCOL_GETUICONFIG_RE: OnPrtcGetConfigRe(protocol); break; + case ProtocolType.PROTOCOL_PLAYERLOGOUT: + HandlePlayerLogout((playerlogout)protocol); + break; + + case ProtocolType.PROTOCOL_AUTOTEAMSETGOAL_RE: + { + // CECAutoTeam pAutoTeam = CECGameRun.Instance.GetHostPlayer().GetAutoTeam(); + // if( pAutoTeam !=null) + // pAutoTeam.OnPrtcAutoTeamSetGoalRe((AutoTeamSetGoal_Re)protocol); + } + break; + + default: + _logger.Log(LogType.Warning, $"Received unhandled protocol type: {protocol.GetPType()}"); + break; + } + } + + private void HandlePlayerLogout(playerlogout protocol) + { + // Original client receives this before EVENT_DISCONNECT. + // We just publish it to allow higher-level flow (UnityGameSession/UI) to react. + PostToUnityContext(() => PlayerLogoutReceived?.Invoke(protocol)); + } + + private void HandleServerDataSend(gamedatasend protocol) + { + int lenghtHeader = Marshal.SizeOf(); + var pDataBuf = new byte[protocol.Data.ByteArray.Length - lenghtHeader]; + var byteArrHeader = new byte[lenghtHeader]; + long dwDataSize = protocol.Data.Size; + + if (dwDataSize < Marshal.SizeOf()) + { + _logger.Error($"### GameDataSend: size invalid {dwDataSize}"); + return; + } + + dwDataSize -= Marshal.SizeOf(); // subtract the header size (ushort) + for (int i = 0; i < protocol.Data.ByteArray.Length; i++) + { + if (i < lenghtHeader) + { + byteArrHeader[i] = protocol.Data.ByteArray[i]; + } + else + { + pDataBuf[i - lenghtHeader] = protocol.Data.ByteArray[i]; + } + } + + var pCmdHeader = BitConverter.ToUInt16(byteArrHeader); + //sss + // Note: isDebug is UNITY_EDITOR-only. Use runtime gate so we can see logs in Development Build too. + bool logInv = Application.isEditor || Debug.isDebugBuild; + if (logInv) + { + // Only log inventory-related commands to avoid spam. + if (pCmdHeader == CommandID.OWN_IVTR_DATA || + pCmdHeader == CommandID.OWN_IVTR_DETAIL_DATA || + pCmdHeader == CommandID.OWN_ITEM_INFO || + pCmdHeader == CommandID.EMPTY_ITEM_SLOT || + pCmdHeader == CommandID.PICKUP_ITEM || + pCmdHeader == CommandID.HOST_OBTAIN_ITEM || + pCmdHeader == CommandID.PRODUCE_ONCE || + pCmdHeader == CommandID.TASK_DELIVER_ITEM || + pCmdHeader == CommandID.PURCHASE_ITEM || + pCmdHeader == CommandID.CHANGE_IVTR_SIZE) + { + Debug.Log($"[INVNET] S2C cmd={pCmdHeader} payloadBytes={pDataBuf?.Length ?? 0}"); + } + } + +#if UNITY_EDITOR + if (isDebug) + { + BMLogger.LogError($"### GameDataSend: CMDID {pCmdHeader}"); + } +#endif + int iHostID = _selectedRole.roleid; + switch (pCmdHeader) + { + case CommandID.PLAYER_INFO_2: + case CommandID.PLAYER_INFO_3: + case CommandID.PLAYER_INFO_4: + case CommandID.PLAYER_INFO_2_LIST: + case CommandID.PLAYER_INFO_3_LIST: + case CommandID.PLAYER_INFO_23_LIST: + + break; + + case CommandID.PLAYER_INFO_1: + case CommandID.PLAYER_ENTER_WORLD: + case CommandID.PLAYER_ENTER_SLICE: + case CommandID.PLAYER_INFO_1_LIST: + case CommandID.PLAYER_INFO_00: + case CommandID.SELF_INFO_1: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERINFO, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, + pCmdHeader, iHostID); + break; + case CommandID.OBJECT_MOVE: + int lenghtDataType = Marshal.SizeOf(); + byte[] arrByteData = GetBytes(pDataBuf, lenghtDataType, 0); + int idObjMove = BitConverter.ToInt32(arrByteData); + if (ISPLAYERID(idObjMove)) + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERMOVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, + pDataBuf, pCmdHeader, iHostID); + } + else if (ISNPCID(idObjMove)) + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCMOVE, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, + pCmdHeader); + } + + break; + case CommandID.OBJECT_STOP_MOVE: + { + int id1 = GPDataTypeHelper.FromBytes(pDataBuf); + + if (ISPLAYERID(id1)) + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERSTOPMOVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, + pDataBuf, pCmdHeader); + } + else if (ISNPCID(id1)) + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCSTOPMOVE, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, + pCmdHeader); + } + + break; + } + case CommandID.OBJECT_LEAVE_SLICE: + { + int id = GPDataTypeHelper.FromBytes(pDataBuf); + if (ISPLAYERID(id)) + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERRUNOUT, (int)MANAGER_INDEX.MAN_PLAYER, -1, + pDataBuf, pCmdHeader); + } + else if (ISNPCID(id)) + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCRUNOUT, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, + pCmdHeader); + } + + break; + } + case CommandID.OWN_IVTR_DATA: + case CommandID.OWN_IVTR_DETAIL_DATA: + case CommandID.GET_OWN_MONEY: + case CommandID.CHANGE_IVTR_SIZE: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_IVTRINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader, iHostID); + break; + case CommandID.EXG_IVTR_ITEM: + case CommandID.MOVE_IVTR_ITEM: + case CommandID.PLAYER_DROP_ITEM: + case CommandID.EXG_EQUIP_ITEM: + case CommandID.EQUIP_ITEM: + case CommandID.MOVE_EQUIP_ITEM: + case CommandID.UNFREEZE_IVTR_SLOT: + case CommandID.PLAYER_EQUIP_TRASHBOX_ITEM: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ITEMOPERATION, (int)MANAGER_INDEX.MAN_PLAYER, 0, + pDataBuf, pCmdHeader); + break; + case CommandID.PLAYER_CASH: + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_IVTRINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader, iHostID); + break; + } + case CommandID.MATTER_INFO_LIST: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTERINFO, (int)MANAGER_INDEX.MAN_MATTER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.MATTER_ENTER_WORLD: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTERENTWORLD, (int)MANAGER_INDEX.MAN_MATTER, 0, + pDataBuf, pCmdHeader); + break; + case CommandID.PICKUP_ITEM: + case CommandID.HOST_OBTAIN_ITEM: + case CommandID.PRODUCE_ONCE: + case CommandID.TASK_DELIVER_ITEM: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PICKUPITEM, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.PURCHASE_ITEM: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PURCHASEITEMS, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.MATTER_PICKUP: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PICKUPMATTER, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + case CommandID.PICKUP_MONEY: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PICKUPMONEY, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.HOST_CORRECT_POS: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CORRECTPOS, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader, iHostID); + break; + case CommandID.EMPTY_ITEM_SLOT: + case CommandID.OWN_ITEM_INFO: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_OWNITEMINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader, iHostID); + break; + case CommandID.PLAYER_DIED: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDIED, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + case CommandID.HOST_DIED: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_DIED, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.PLAYER_REVIVE: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERREVIVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + case CommandID.NOTIFY_HOSTPOS: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_GOTO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.NPC_ENTER_SLICE: + case CommandID.NPC_INFO_LIST: + case CommandID.NPC_INFO_00: + case CommandID.NPC_ENTER_WORLD: + case CommandID.NPC_VISIBLE_TID_NOTIFY: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCINFO, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, + pCmdHeader, dwDataSize); + break; + case CommandID.TASK_DATA: + case CommandID.TASK_VAR_DATA: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_TASKDATA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader, dwDataSize); + break; + case CommandID.BE_HURT: + case CommandID.HURT_RESULT: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_HURTRESULT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.OBJECT_ATTACK_RESULT: + //int id = GPDataTypeHelper.FromBytes(pDataBuf); + cmd_object_atk_result pCmdAtk = GPDataTypeHelper.FromBytes(pDataBuf); + //BMLogger.LogError($"OBJECT_ATTACK_RESULT: npc ? " + ISNPCID(id)); + + if (ISPLAYERID(pCmdAtk.attacker_id)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERATKRESULT, MANAGER_INDEX.MAN_PLAYER, -1, + pDataBuf, pCmdHeader); + else if (ISNPCID(pCmdAtk.attacker_id)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCATKRESULT, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); + break; + case CommandID.HOST_ATTACKRESULT: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ATKRESULT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.HOST_ATTACKED: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ATTACKED, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + + case CommandID.TEAM_JOIN_TEAM: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_JOINTEAM, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.TEAM_LEAVE_PARTY: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_LEAVETEAM, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.TEAM_NEW_MEMBER: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_NEWTEAMMEM, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.TEAM_MEMBER_DATA: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_TEAMMEMBERDATA, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.TEAM_MEMBER_LEAVE: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_LEAVETEAM, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + + case CommandID.ERROR_MESSAGE: + { + int errRaw = BitConverter.ToInt32(pDataBuf, 0); + // Note: _logger may be configured as a file logger via SetLogPath(), so also log to console for visibility. + _logger.Info($"### GameDataSend: ERROR_MESSAGE: {errRaw}"); +#if UNITY_EDITOR + BMLogger.LogError($"### GameDataSend: ERROR_MESSAGE: {errRaw}"); +#endif + cmd_error_msg pCmd = GPDataTypeHelper.FromBytes(pDataBuf); +#if UNITY_EDITOR + BMLogger.LogError($"### GameDataSend: ERROR_MESSAGE parsed iMessage={pCmd.iMessage}"); +#endif + Debug.LogError($"### GameDataSend: ERROR_MESSAGE: {errRaw}"); + if (pCmd.iMessage != 0) + { + // string szMsg = m_ErrorMsgs.GetWideString(pCmd.iMessage); + // if (string.IsNullOrEmpty(szMsg)) + // BMLogger.LogError("SERVER - unknown error !"); + // else + // { + // BMLogger.LogError("SERVER - error: "+szMsg); + // } + // else if (pCmd.iMessage != 2) + // g_pGame.GetGameRun().AddChatMessage(szMsg, GP_CHAT_MISC); + } + + if (pCmd.iMessage == 2) + { + // Attack target is too far + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_TARGETISFAR, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + } + else if (pCmd.iMessage == 20) + { + // Failed to cast skill + //pGameRun.PostMessage(MSG_PM_CASTSKILL, MAN_PLAYER, 0, (DWORD)pDataBuf, pCmdHeader.cmd); + } + else if (pCmd.iMessage == 133 || pCmd.iMessage == 134) + { + // deal failed + //pGameRun.PostMessage(MSG_HST_BUY_SELL_FAIL, MAN_PLAYER, 0, (DWORD)pDataBuf, pCmdHeader.cmd); + } + else if (pCmd.iMessage == 158) + { + // µ±Ç°»ãÂʲ»¶Ô£¬ÖØÐÂÈ¡»ãÂÊ + //c2s_CmdGetCashMoneyRate(); + } + else if (pCmd.iMessage == 108 /*&& pGameRun.GetHostPlayer().IsInKingService()*/) + { + /* CECGameUIMan* pGameUI = pGameRun.GetUIManager().GetInGameUIMan(); + if (pGameUI) + pGameUI.EndNPCService();*/ + } + else if + (pCmd.iMessage == 108 /*&& pGameRun.GetHostPlayer().GetOfflineShopCtrl().GetNPCSevFlag() != COfflineShopCtrl::NPCSEV_NULL*/ + ) + { + /* CECGameUIMan* pGameUI = pGameRun.GetUIManager().GetInGameUIMan(); + if (pGameUI) + pGameUI.EndNPCService();*/ + } + else if (pCmd.iMessage == 175) + { + //c2s_CmdQueryParallelWorld(); + } + else if (pCmd.iMessage == 6) + { + //AP_ActionEvent(AP_EVENT_CANNOTPICKUP); + } + + break; + } + case CommandID.SELECT_TARGET: + case CommandID.UNSELECT: + + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SELTARGET, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.NPC_DIED: + case CommandID.NPC_DIED2: + + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCDIED, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); + break; + case CommandID.OBJECT_DISAPPEAR: + { + int lenghtDataType1 = Marshal.SizeOf(); + byte[] arrByteData1 = GetBytes(pDataBuf, lenghtDataType1, 0); + int objectId = BitConverter.ToInt32(arrByteData1); + if (ISPLAYERID(objectId)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDISAPPEAR, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + else if (ISNPCID(objectId)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCDISAPPEAR, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); + else if (ISMATTERID(objectId)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTERDISAPPEAR, MANAGER_INDEX.MAN_MATTER, 0, pDataBuf, pCmdHeader); + + break; + } + case CommandID.SELF_INFO_00: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_INFO00, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.NPC_GREETING: + { + // If this greeting is from the skill-learn NPC, record it (C++ skill dialog relies on this). + try + { + cmd_npc_greeting greet = GPDataTypeHelper.FromBytes(pDataBuf); + CECHostSkillModel.Instance.OnNpcGreeting(greet.idObject); + } + catch (Exception ex) + { + _logger.Log(LogType.Warning, $"Failed to parse NPC_GREETING payload: {ex.Message}"); + } + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_NPCGREETING, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + } + case CommandID.ACTIVATE_WAYPOINT: + case CommandID.WAYPOINT_LIST: + + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_WAYPOINT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.SERVER_TIME: + { + cmd_server_time pcmd_server_time = GPDataTypeHelper.FromBytes(pDataBuf); + EC_ManMessage.PostMessage(EC_MsgDef.MSG_SERVERTIME, -1, 0, pcmd_server_time.time, pcmd_server_time.timebias); + break; + } + case CommandID.SCENE_SERVICE_NPC_LIST: + { + CECHostSkillModel.Instance.RecvNPCServiceList(protocol.Data); + break; + } + case CommandID.SKILL_DATA: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SKILLDATA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.OBJECT_CAST_SKILL: + case CommandID.OBJECT_CAST_INSTANT_SKILL: + case CommandID.OBJECT_CAST_POS_SKILL: + { + cmd_object_cast_skill pCmd2 = GPDataTypeHelper.FromBytes(pDataBuf,true); + if (ISPLAYERID(pCmd2.caster)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_CASTSKILL, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + else if (ISNPCID(pCmd2.caster)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCCASTSKILL, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); + + break; + } + case CommandID.LEVEL_UP: + { + cmd_level_up pCmdLevelUp = GPDataTypeHelper.FromBytes(pDataBuf); ; + if (ISPLAYERID(pCmdLevelUp.id)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERLEVELUP, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + else if (ISNPCID(pCmdLevelUp.id)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCLEVELUP, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); + break; + } + case CommandID.HOST_START_ATTACK: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_STARTATTACK, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); + break; + case CommandID.HOST_STOPATTACK: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_STOPATTACK, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); + break; + case CommandID.HOST_SKILL_ATTACK_RESULT: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SKILLRESULT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.CHANGE_FACE_START: + case CommandID.CHANGE_FACE_END: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CHANGEFACE, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); + break; + case CommandID.ENCHANT_RESULT: + cmd_enchant_result pCmd3 = GPDataTypeHelper.FromBytes(pDataBuf); + if (ISPLAYERID(pCmd3.caster)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_ENCHANTRESULT, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + else if (ISNPCID(pCmd3.caster)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_ENCHANTRESULT, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); + break; + case CommandID.HOST_STOP_SKILL: + case CommandID.SELF_SKILL_INTERRUPTED: + case CommandID.SKILL_PERFORM: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_CASTSKILL, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.SET_COOLDOWN: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SETCOOLTIME, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.HOST_USE_ITEM: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_USEITEM, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.COMBO_SKILL_PREPARE: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_COMBO_SKILL_PREPARE, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); + break; + case CommandID.PLAYER_EXT_PROP_BASE: + case CommandID.PLAYER_EXT_PROP_MOVE: + case CommandID.PLAYER_EXT_PROP_ATK: + case CommandID.PLAYER_EXT_PROP_DEF: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYEREXTPROP, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + case CommandID.OWN_EXT_PROP: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_OWNEXTPROP, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.OBJECT_DO_EMOTE: + case CommandID.OBJECT_EMOTE_RESTORE: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDOEMOTE, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + case CommandID.OUT_OF_SIGHT_LIST: + { + cmd_out_of_sight_list pCmd5 = default; + pCmd5.uCount = GPDataTypeHelper.FromBytes(pDataBuf); + int offset2 = sizeof(uint); + pCmd5.idList = new int[pCmd5.uCount]; + for (int i = 0; i < pCmd5.uCount; i++) + { + pCmd5.idList[i] = GPDataTypeHelper.FromBytes(pDataBuf, offset2); + offset2 += 4;//sizeof int; + } + + for (uint n = 0; n < pCmd5.uCount; n++) + { + if (ISPLAYERID(pCmd5.idList[n])) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYEROUTOFVIEW, MANAGER_INDEX.MAN_PLAYER, -1, pCmd5.idList[n], pCmdHeader); + else if (ISNPCID(pCmd5.idList[n])) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCOUTOFVIEW, MANAGER_INDEX.MAN_NPC, 0, pCmd5.idList[n], pCmdHeader); + else if (ISMATTERID(pCmd5.idList[n])) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTEROUTOFVIEW, MANAGER_INDEX.MAN_MATTER, 0, pCmd5.idList[n], pCmdHeader); + } + + break; + } + case CommandID.PLAYER_GATHER_START: + case CommandID.PLAYER_GATHER_STOP: + case CommandID.MINE_GATHERED: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERGATHER, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + case CommandID.COOLTIME_DATA: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_COOLTIMEDATA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.OBJECT_TAKEOFF: + { + cmd_object_takeoff pCmdTakeOff = GPDataTypeHelper.FromBytes((byte[])pDataBuf); + if (ISPLAYERID(pCmdTakeOff.object_id)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERFLY, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + } + case CommandID.OBJECT_LANDING: + { + cmd_object_landing pCmdLanding = GPDataTypeHelper.FromBytes((byte[])pDataBuf); + if (ISPLAYERID(pCmdLanding.object_id)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERFLY, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + } + case CommandID.HOST_RUSH_FLY: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERFLY, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.FLYSWORD_TIME: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_FLYSWORDTIME, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.PRODUCE_START: + case CommandID.PRODUCE_END: + case CommandID.PRODUCE_NULL: + // Post MSG_HST_PRODUCEITEM message with command ID as parameter (matches C++ behavior) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PRODUCEITEM, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + + + case CommandID.LEARN_SKILL: + BMLogger.LogError("### GameDataSend: LEARN_SKILL"); + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_LEARNSKILL, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.GAIN_PET: + case CommandID.FREE_PET: + case CommandID.SUMMON_PET: + case CommandID.RECALL_PET: + case CommandID.PLAYER_START_PET_OP: + case CommandID.PLAYER_STOP_PET_OP: + case CommandID.PET_RECEIVE_EXP: + case CommandID.PET_LEVELUP: + case CommandID.PET_ROOM: + case CommandID.PET_ROOM_CAPACITY: + case CommandID.PET_HONOR_POINT: + case CommandID.PET_HUNGER_GAUGE: + case CommandID.PET_DEAD: + case CommandID.PET_REVIVE: + case CommandID.PET_HP_NOTIFY: + case CommandID.PET_AI_STATE: + case CommandID.PET_SET_COOLDOWN: + case CommandID.SUMMON_PLANT_PET: + case CommandID.PLANT_PET_DISAPPEAR: + case CommandID.PLANT_PET_HP_NOTIFY: + case CommandID.PET_PROPERTY: + case CommandID.PET_REBUILD_INHERIT_START: + case CommandID.PET_REBUILD_INHERIT_INFO: + case CommandID.PET_REBUILD_INHERIT_END: + case CommandID.PET_EVOLUTION_DONE: + case CommandID.PET_REBUILD_NATURE_START: + case CommandID.PET_REBUILD_NATURE_INFO: + case CommandID.PET_REBUILD_NATURE_END: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PETOPT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.SET_PLAYER_LIMIT: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SETPLAYERLIMIT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.PLAYER_MOUNTING: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERMOUNT, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + case CommandID.DUEL_RECV_REQUEST: + case CommandID.DUEL_REJECT_REQUEST: + case CommandID.DUEL_PREPARE: + case CommandID.DUEL_CANCEL: + case CommandID.HOST_DUEL_START: + case CommandID.DUEL_STOP: + case CommandID.DUEL_RESULT: + // Origin: host duel events use MSG_PM_DUELOPT (391); handler distinguishes by pCmdHeader (214-220) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_DUELOPT, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.PLAYER_DUEL_START: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDUELOPT, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + case CommandID.EMBED_ITEM: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_EMBEDITEM, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.CLEAR_TESSERA: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CLEARTESSERA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.PLAYER_LEAVE_WORLD: + { + cmd_player_leave_world pCmd = GPDataTypeHelper.FromBytes(pDataBuf); + if (ISPLAYERID(pCmd.id)) + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYEREXIT, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + } + break; + } + default: +#if UNITY_EDITOR + if (isDebug) + { + BMLogger.LogError($"### GameDataSend: Unhandled CMDID {pCmdHeader} (payloadBytes={pDataBuf?.Length ?? 0})"); + } +#endif + break; + } + } + + + private void HandleSelectRoleResponse(SelectRole_Re protocol) + { + _logger.Info($"Select role response {protocol.result}"); + var callback = _selectRoleCallback; + PostToUnityContext(() => callback?.Invoke(_selectedRole)); + } + + private void HandleCreateRoleResponse(createrole_re protocol) + { + Debug.Log($"[GameSession] HandleCreateRoleResponse - result: {protocol.result}, roleid: {protocol.roleid}"); + + if (protocol.result != (int)ErrCode.ERR_SUCCESS) + { + string errorMsg = $"Create role failed with result code: {protocol.result} (ERR_SUCCESS = {(int)ErrCode.ERR_SUCCESS})"; + _logger.Log(LogType.Error, errorMsg); + Debug.LogError($"[GameSession] {errorMsg}"); + var callback = _createRoleCallback; + _createRoleCallback = null; + PostToUnityContext(() => callback?.Invoke(null)); + return; + } + + Debug.Log($"[GameSession] Create role successful! RoleID: {protocol.roleid}"); + var successCallback = _createRoleCallback; + _createRoleCallback = null; + PostToUnityContext(() => successCallback?.Invoke(protocol.roleinfo)); + } + + private void HandleErrorInfo(errorinfo protocol) + { + Debug.LogError($"[GameSession] Server error - Errcode: {protocol.Errcode}"); + + // If we're waiting for create role response and get an error, fail the callback + if (_createRoleCallback != null) + { + Debug.LogError($"[GameSession] Create role failed due to server error: {protocol.Errcode}"); + var callback = _createRoleCallback; + _createRoleCallback = null; + PostToUnityContext(() => callback?.Invoke(null)); + } + } + + private void OnErrorOccurred(string errorMessage) + { + _logger.Log(LogType.Error, $"Network Error: {errorMessage}"); + FailLoginInProgress(errorMessage); + FailRoleListInProgress(errorMessage); + } + + private void OnDisconnected() + { + _logger.Log(LogType.Info, "Disconnected from server."); + _currentUserId = -1; + FailLoginInProgress("Disconnected"); + FailRoleListInProgress("Disconnected"); + // Clear command cache + m_CmdCache.RemoveAllCmds(); + PostToUnityContext(() => Disconnected?.Invoke()); + } + + private void HandleChallenge(challenge challenge) + { + if (_loginCallback == null || string.IsNullOrEmpty(_username)) + { + _logger.Log(LogType.Warning, "Received Challenge but not expecting it or username not set."); + return; + } + + _logger.Log(LogType.Info, "Handling Challenge..."); + + response response = new response(); + byte[] usernameBytes = Encoding.ASCII.GetBytes(_username); + byte[] passwordBytes = Encoding.ASCII.GetBytes(_password); + response.identity.Replace(usernameBytes); + response.Setup(new Octets(usernameBytes), new Octets(passwordBytes), challenge.nonce); + + uint clientId = 0xffffffff; + byte[] clientIdBytes = BitConverter.GetBytes(clientId); + response.cli_fingerprint.Replace(clientIdBytes); + response.use_token = 0; + + _networkManager.SetNonce(response.response_data); + SendProtocol(response); + _logger.Log(LogType.Info, "Sent Response."); + } + + private void HandleKeyExchange(KeyExchange keyExchange) + { + if (_loginCallback == null || string.IsNullOrEmpty(_username)) + { + _logger.Log(LogType.Warning, "Received KeyExchange but not expecting it."); + return; + } + + _logger.Log(LogType.Info, "Handling KeyExchange..."); + keyExchange.Setup(_networkManager, _username); + keyExchange.Blkickuser = 1; + SendProtocol(keyExchange); + _logger.Log(LogType.Info, "Sent KeyExchange acknowledgment/response."); + } + + private void HandleOnlineAnnounce(onlineannounce announce) + { + if (_loginCallback == null) + { + _logger.Log(LogType.Warning, "Received OnlineAnnounce but not expecting it."); + return; + } + + _logger.Log(LogType.Info, $"Login successful! UserID: {announce.Userid}, LocalSID: {announce.Localsid}"); + _currentUserId = announce.Userid; + _localsid = announce.Localsid; + + var callback = _loginCallback; + _loginCallback = null; + PostToUnityContext(() => callback?.Invoke(true)); + } + + private void RequestRoleListInternal(int lastHandle = -1) + { + rolelist rolelistRequest = new rolelist(); + rolelistRequest.Userid = _currentUserId; + rolelistRequest.Localsid = 0; + rolelistRequest.Handle = lastHandle; + + SendProtocol(rolelistRequest); + + + //gamedatasend gamedatasendRequest = new gamedatasend(); + //gamedatasendRequest.Data = C2SCommandFactory.CreatePlayerMove(); + + //SendProtocol(gamedatasendRequest); + } + + private void HandleRoleListResponse(RoleListResponse response) + { + if (_roleListCallback == null || _accumulatedRoles == null) + { + _logger.Log(LogType.Warning, "Received RoleListResponse but not expecting it."); + return; + } + + _logger.Log(LogType.Debug, + $"Received RoleListResponse. Handle: {response.handle}, Result: {response.result}, Count: {response.rolelist.Count}"); + + if (response.result == 0) + { + _accumulatedRoles.AddRange(response.rolelist); + + foreach (var role in response.rolelist) + { + try + { + string roleName = Encoding.UTF8.GetString(role.name.ByteArray, 0, role.name.Length); + _logger.Log(LogType.Info, $" - Role ID: {role.roleid}, Name: {roleName}, Level: {role.level}"); + } + catch (Exception ex) + { + _logger.Log(LogType.Error, $" - Error decoding role name: {ex.Message}"); + _logger.LogException(ex); + } + } + + if (response.handle != -1) + { + _logger.Log(LogType.Debug, $"Requesting next batch of roles (handle: {response.handle})..."); + RequestRoleListInternal(response.handle); + } + else + { + _logger.Log(LogType.Info, $"Finished fetching roles. Total count: {_accumulatedRoles.Count}"); + var callback = _roleListCallback; + var result = _accumulatedRoles; + _roleListCallback = null; + _accumulatedRoles = null; + PostToUnityContext(() => callback?.Invoke(result)); + } + } + else + { + _logger.Log(LogType.Error, $"Role list retrieval failed. Result code: {response.result}"); + FailRoleListInProgress($"Role list retrieval failed (Result: {response.result})"); + } + } + + // --- Helper methods for failure handling --- + private void FailLoginInProgress(string reason) + { + if (_loginCallback != null) + { + _logger.Log(LogType.Error, $"Login failed: {reason}"); + var callback = _loginCallback; + _loginCallback = null; + PostToUnityContext(() => callback?.Invoke(false)); + } + } + + private void FailRoleListInProgress(string reason) + { + if (_roleListCallback != null) + { + _logger.Log(LogType.Error, $"Role list retrieval failed: {reason}"); + var callback = _roleListCallback; + _roleListCallback = null; + _accumulatedRoles = null; + PostToUnityContext(() => callback?.Invoke(null)); + } + } + + // --- IDisposable Implementation --- + private bool disposedValue = false; + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + if (_networkManager != null) + { + _logger.Log(LogType.Info, "[DUCK] Disposing GameSession and disconnecting..."); + _networkManager.ProtocolReceived -= OnProtocolReceived; + _networkManager.ErrorOccurred -= OnErrorOccurred; + _networkManager.Disconnected -= OnDisconnected; + _networkManager.Disconnect(); + _networkManager.Dispose(); + _networkManager = null; + } + + _loginCallback = null; + _roleListCallback = null; + _accumulatedRoles = null; + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + // GC.SuppressFinalize(this); + } + + public bool ISPLAYERID(int id) + { + return id != 0 && (id & 0x80000000) == 0; + } + public bool ISNPCID(int id) => ((id & 0x80000000) != 0) && ((id & 0x40000000) == 0); + public bool ISMATTERID(int id) => ((id) & 0xC0000000) == 0xC0000000; + private byte[] GetBytes(byte[] bytes, int length, int index) + { + byte[] arrByteData = new byte[length]; + for (int i = 0; i < length; i++) + { + arrByteData[i] = bytes[i + index]; + } + + return arrByteData; + } + + public void c2s_CmdPlayerMove(in Vector3 vCurPos, in Vector3 vDest, + int iTime, float fSpeed, int iMoveMode, ushort wStamp) + { + gamedatasend gamedatasend = new gamedatasend(); + + gamedatasend.Data = + C2SCommandFactory.CreatePlayerMove(vCurPos, vDest, (ushort)iTime, fSpeed, (byte)iMoveMode, wStamp); + SendProtocol(gamedatasend); + } + public void c2s_SendCmdCastSkill(int idSkill, byte byPVPMask, int iNumTarget, int[] aTargets) + { + gamedatasend gamedatasend = new gamedatasend(); + + gamedatasend.Data = + C2SCommandFactory.CreatePlayerCastSkill(idSkill, byPVPMask, iNumTarget, aTargets); + + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdCastInstantSkill(int idSkill, byte byPVPMask, int iNumTarget, int[] aTargets) + { + gamedatasend gamedatasend = new gamedatasend(); + + gamedatasend.Data = + C2SCommandFactory.CreatePlayerCastInstantSkill(idSkill, byPVPMask, iNumTarget, aTargets); + + SendProtocol(gamedatasend); + } + + public void c2s_CmdCastPosSkill(int idSkill, Vector3 vDest, byte byPVPMask, int iNumTarget, int aTargets) + { + gamedatasend gamedatasend = new gamedatasend(); + + gamedatasend.Data = + C2SCommandFactory.CreatePlayerCastPosSkill(idSkill, vDest, byPVPMask, iNumTarget, aTargets); + + SendProtocol(gamedatasend); + } + public void c2s_SendCmdNotifyForceAttack(int iForceAttack, byte refuseBless) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNotifyForceAttack(iForceAttack, refuseBless); + SendProtocol(gamedatasend); + } + public void c2s_SendCmdContinueAction() + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.CONTINUE_ACTION); + SendProtocol(gamedatasend); + } + + /// + /// Client logout request. Mirrors original client behavior: + /// - outType=1: back to select role + /// - outType=0: logout account + /// + public void SendPlayerLogout(int outType, Action complete = null) + { + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreatePlayerLogoutCmd(outType); + SendProtocol(g, complete); + } + public void c2s_SendCmdStopMove(in Vector3 vDest, float fSpeed, int iMoveMode, + byte byDir, ushort wStamp, int iTime) + { + gamedatasend gamedatasend = new gamedatasend(); + + gamedatasend.Data = + C2SCommandFactory.CreatePlayerStop(vDest, fSpeed, (byte)iMoveMode, byDir, wStamp, (ushort)iTime); + SendProtocol(gamedatasend); + } + + public void c2s_CmdSendEnterPKPrecinctint() + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.ENTER_PK_PROTECTED); + SendProtocol(gamedatasend); + } + public void SendChatData(byte cChannel, in string szMsg, int iPack, int iSlot) + { + publicchat publicChat = new publicchat(); + publicChat.Channel = cChannel; + publicChat.Roleid = m_iCharID; + + byte[] unicodeBytes = Encoding.Unicode.GetBytes(szMsg); + publicChat.Msg.Replace(unicodeBytes); + _logger.Log(LogType.Warning, $"HoangDev : publicChat {publicChat}"); + SendProtocol(publicChat); + } + public void LoadConfigData() + { + getuiconfig p = new getuiconfig(); + p.Roleid = m_iCharID; + SendProtocol(p); + } + + /// + /// Save config data to server (sends setuiconfig with compressed config blob). + /// 保存配置数据到服务器(发送 setuiconfig 及压缩后的配置数据)。 + /// + public void SaveConfigData(byte[] pBuf, int len) + { + BMLogger.Log($"[MH] Session.SaveConfigData | len={len}"); + + if (pBuf == null || len <= 0) return; + var p = new setuiconfig(); + p.Roleid = m_iCharID; + p.Localsid = (int)_localsid; + byte[] slice = new byte[len]; + Buffer.BlockCopy(pBuf, 0, slice, 0, len); + p.Ui_config = new Octets(slice); + + // return; + SendProtocol(p); + } + + private void SetCharacterID(int iCharID) + { + m_iCharID = iCharID; + } + + private void OnPrtcChatMessage(Protocol pProtocol, bool bCalledagain) + { + chatmessage p = (chatmessage)pProtocol; + + string strTemp = System.Text.Encoding.Unicode.GetString(p.Msg.ToArray(), 0, p.Msg.Length); + + _logger.Log(LogType.Warning, $"HoangDev : OnPrtcChatMessage :{strTemp}"); + EventBus.Publish(new ChatMessageEvent(strTemp)); + } + + public struct ChatMessageEvent + { + public string context; + + public ChatMessageEvent(string context) + { + this.context = context; + } + } + public void OnPrtcGetConfigRe(Protocol pProtocol) + { + getuiconfig_re p = (getuiconfig_re)pProtocol; + if (p.Result != (int)ErrCode.ERR_SUCCESS) + BMLogger.LogError("CECGameSession::OnPrtcGetConfigRe, link return error code of " + p.Result); + else + { + if (!CECGameRun.Instance.LoadConfigsFromServer(p.UiConfig.RawBuffer, p.UiConfig.Size)) + { + // if load failed then use current setting directly + //TODO : fix later + EC_Game.GetConfigs().ApplyUserSetting(); + } + + // Now, Get config data request is sent after all host initial data ready. + // so when we receive this reply, we can do some last work before game + // really starts. Maybe it's not the best place to do these work, but + // now we do it here. + // Enalbe game UI + CECGameUIMan pGameUI = (CECGameUIMan)EC_Game.GetGameRun().GetUIManager().GetInGameUIMan(); + if (pGameUI != null) + { + pGameUI.EnableUI(true); + + // Get referral name for adding friend or other display + //TODO: a Hung lam phan select role info di + /* RoleInfo info = EC_Game.GetGameRun().GetSelectedRoleInfo(); + if (info.referrer_role > 0) + GetPlayerBriefInfo(1, info.referrer_role, 2);*/ + } + + CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer(); + pHost.OnAllInitDataReady(); + + /* if (pHost.IsGM()) + { + CDlgCountryMap pDlgCountryMap = (CDlgCountryMap)pGameUI.GetDialog("Win_CountryMap"); + pDlgCountryMap.GetConfig(); + } + + g_pGame.GetConfigs().ApplyOptimizeSetting(); + + if (g_pGame.GetConfigs().IsMiniClient()) + CECMCDownload::GetInstance().SendGetDownloadOK();*/ + } + } + private void OnPrtcPlayerBaseInfoRe(Protocol pProtocol) + { + playerbaseinfo_re p = (playerbaseinfo_re)pProtocol; + BMLogger.Log($"OnPrtcPlayerBaseInfoRe: {p.Roleid} {p.Player.cls} {p.Player.gender}"); + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERBASEINFO, MANAGER_INDEX.MAN_PLAYER, -1, p); + } + + public void c2s_CmdNPCSevAcceptTask(int idTask, int idStorage, int idRefreshItem) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateCmdNPCSevAcceptTask(idTask, idStorage, idRefreshItem); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdGetAllData(bool byPack, bool byEquip, bool byTask) + { + gamedatasend gamedatasend = new gamedatasend(); + + gamedatasend.Data = C2SCommandFactory.CreateGetAllDataCommand(byPack, byEquip, byTask); + _logger.Log(LogType.Warning, $"[Dat]- SendCmdGetAllData {byPack},{byEquip},{byTask}"); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevHello(int nid) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevHelloDataCommand(nid); + SendProtocol(gamedatasend); + } + + public void c2s_CmdNormalAttack(byte byPVPMask) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNormalAttackDataCmd(byPVPMask); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdCancelAction() + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(CSNetwork.C2SCommand.CommandID.CANCEL_ACTION); + SendProtocol(gamedatasend); + } + + public void c2s_CmdUnselect() + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(CSNetwork.C2SCommand.CommandID.UNSELECT); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdSelectTarget(int idTarget) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateSelectTarget(idTarget); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevWaypoint() + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevWaypointCmd(NPC_service_type.GP_NPCSEV_WAYPOINT, 0); + SendProtocol(gamedatasend); + } + public void c2s_SendCmdNPCSevMakeItem(int idSkill, int idItem, uint dwCount) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevMakeItemCmd(idSkill, idItem, dwCount); + SendProtocol(gamedatasend); + } + public void GetRoleBaseInfo(int iNumRole, List aRoleIDs) + { + int iNumLimit = 128; + playerbaseinfo p = null; + int iCount = 0; + + while (iCount < iNumRole) + { + p = new(); + p.Roleid = _selectedRole.roleid; + + int iNumSend = iNumLimit; + if (iCount + iNumLimit > iNumRole) + iNumSend = iNumRole - iCount; + + if (iNumSend > 0) + { + p.playerList = new(); + for (int i = 0; i < iNumSend; i++) + p.playerList.Add(aRoleIDs[iCount + i]); + + SendProtocol(p); + } + + + iCount += iNumSend; + } + } + + public void c2s_SendCmdGetOtherEquip(int iNumID, List aIDs) + { + // int iNumLimit = 250; + // int iCount = 0; + + // while (iCount < iNumID) + // { + // int iNumSend = iNumLimit; + // if (iCount + iNumLimit > iNumID) + // iNumSend = iNumID - iCount; + + // if (iNumSend > 0) + // { + // } + // } + } + + public void c2s_SendCmdNPCSevAcceptTask(int idTask, int idStorage, int idRefreshItem) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateCmdNPCSevAcceptTask( + idTask, + idStorage, + idRefreshItem); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevReturnTask(int idTask, int iChoice) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevReturnTaskCmd( + idTask, + iChoice); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevTaskMatter(int idTask) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevTaskMatterCmd(idTask); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevLearnSkill(int idSkill) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevLearnSkillCmd(idSkill); + BMLogger.LogError("HoangDev : c2s_SendCmdNPCSevLearnSkill gamedatasend.Data : " + gamedatasend.Data.Size); + BMLogger.LogError("HoangDev : c2s_SendCmdNPCSevLearnSkill idSkill : " + idSkill); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevBuy(int itemNum, C2SCommand.npc_trade_item[] items) + { + if (itemNum <= 0 || items == null || items.Length < itemNum) + return; + + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevBuyCmd(itemNum, items); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevSell(int itemNum, C2SCommand.npc_sell_item[] items) + { + if (itemNum <= 0 || items == null || items.Length < itemNum) + return; + + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevSellCmd(itemNum, items); + SendProtocol(gamedatasend); + } + + public void GetRoleCustomizeData(int iNumRole, List aRoleIDs) + { + if (iNumRole <= 0 || aRoleIDs == null || aRoleIDs.Count == 0) return; + + int iNumLimit = 240; + int iCount = 0; + + while (iCount < iNumRole) + { + getcustomdata p = new(); + p.Roleid = _selectedRole.roleid; + + int iNumSend = iNumLimit; + if (iCount + iNumLimit > iNumRole) + iNumSend = iNumRole - iCount; + + for (int i = 0; i < iNumSend; i++) + p.playerlist.Add(aRoleIDs[iCount + i]); + + SendProtocol(p); + + iCount += iNumSend; + } + } + public void c2s_SendCmdEmoteAction(uint wPose) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateEmoteActionCmd((int)wPose); + SendProtocol(gamedatasend); + } + public void c2s_SendCmdTaskNotify(byte[] pData, uint dwDataSize) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateTaskNotifyCmd( pData, dwDataSize); + BMLogger.Log($"[MH Task] c2s_SendCmdTaskNotify Command ID : {pData[0]} Size: {dwDataSize}"); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdStandUp() + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.STAND_UP); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdAutoTeamSetGoal(int type, int goal_id, int op) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateAutoTeamSetGoalCommand(type,goal_id, op); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdTeamInvite(int idPlayer) + { + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreateTeamInviteCommand(idPlayer); + SendProtocol(g); + } + + public void c2s_SendCmdDuelRequest(int idTarget) + { + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreateDuelRequestCommand(idTarget); + SendProtocol(g); + } + + public void c2s_SendCmdDuelReply(bool accept, int idInviter) + { + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreateDuelReplyCommand(accept, idInviter); + SendProtocol(g); + } + + public void c2s_SendCmdTeamKickMember(int idMember) + { + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreateTeamKickMemberCommand(idMember); + SendProtocol(g); + } + + public void c2s_SendCmdTeamLeaveParty() + { + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreateTeamLeavePartyCommand(); + SendProtocol(g); + } + + public void c2s_SendCmdTeamDismissParty() + { + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreateTeamDismissPartyCommand(); + SendProtocol(g); + } + + public void c2s_SendCmdTeamSetPickupFlag(short pickupFlag) + { + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreateTeamSetPickupFlagCommand(pickupFlag); + SendProtocol(g); + } + + public void c2s_SendCmdTeamChangeLeader(int idNewLeader) + { + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreateTeamChangeLeaderCommand(idNewLeader); + SendProtocol(g); + } + + public void c2s_SendCmdTeamMemberPos(int count, int[] memberIds) + { + if (memberIds == null || count <= 0 || count > memberIds.Length) return; + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreateTeamMemberPosCommand(count, memberIds); + SendProtocol(g); + } + + public void c2s_CmdGoto(float x, float y, float z) + { + c2s_SendCmdGoto(x, y, z); + } + + // Send C2S::GOTO command data + void c2s_SendCmdGoto(float x, float y, float z) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateGoToCommed( x, y, z); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdUseItem(byte byPackage, byte bySlot, int tid, byte byCount) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateUseItemCmd(byPackage, bySlot, tid, byCount); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdGetExtProps() + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.GET_EXT_PROP); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdGivePresent(int roleid, int mail_id, int goods_id, int goods_index, int goods_slot) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateGivePresentCmd(roleid, mail_id, goods_id, goods_index, goods_slot); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdEnterSanctuary(int id) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateEnterSanctuaryCmd(id); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdEnterInstance(int iTransIdx, int idInst) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateEnterInstanceCmd(iTransIdx, idInst); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdActiveRushFly(bool bActive) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateActiveRushFlyCmd(bActive); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdPetCtrl(int idTarget, int cmd, byte[] pParamBuf, int iParamLen) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreatePetCtrlCmd(idTarget, cmd, pParamBuf, iParamLen); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdQueryFactionPVPInfo(int faction_id) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateQueryFactionPVPInfo(faction_id); + SendProtocol(gamedatasend); + } + + public void SendCmdPetCtrl(int idTarget, int cmd, object pParamBuf, int iParamLen) + { + m_CmdCache.SendCmdPetCtrl(idTarget, cmd, (byte[])pParamBuf, iParamLen); + } + + public void c2s_SendCmdPetSummon(int iPetIdx) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreatePetSummon(iPetIdx); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevEmbed(ushort wStoneIdx, ushort wEquipIdx, int tidStone, int tidEquip) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevEmbedCmd(wStoneIdx, wEquipIdx, tidStone, tidEquip); + SendProtocol(gamedatasend); + } + public void c2s_SendCmdGetItemInfo(byte byPackage, byte bySlot) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateGetItemInfoCmd(byPackage, bySlot); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdSetStatusPts(int vitality, int energy, int strength, int agility) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateSetStatusPtCmd(vitality, energy, strength, agility); + SendProtocol(gamedatasend); + } + public void c2s_CmdNPCSevClearEmbeddedChip(int iEquipIdx, int tidEquip) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevClearEmbeddedChipCmd(iEquipIdx, tidEquip); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdPetRecall() + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(CSNetwork.C2SCommand.CommandID.RECALL_PET); + SendProtocol(gamedatasend); + } + public void c2s_CmdDebug(ushort icmd, int param1 = int.MinValue) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateDebugCmd(icmd, param1); + SendProtocol(gamedatasend); + } + public void c2s_SendCmdNPCSevHatchPet(int iIvtrIdx, int idEgg) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevHatchPetCmd(iIvtrIdx, idEgg); + SendProtocol(gamedatasend); + } + public void c2s_SendCmdNPCSevRestorePet(int iPetIdx) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevRestorePetCmd(iPetIdx); + SendProtocol(gamedatasend); + } + + // Cross-server get in (C++: c2s_CmdNPCSevCrossServerGetIn) — TODO: implement C2S packet when needed + public void c2s_CmdNPCSevCrossServerGetIn() + { + // TODO: C2SCommandFactory.CreateNPCSevCrossServerGetInCmd() and SendProtocol + } + + // Cross-server get out (C++: c2s_CmdNPCSevCrossServerGetOut) — TODO: implement C2S packet when needed + public void c2s_CmdNPCSevCrossServerGetOut() + { + // TODO: C2SCommandFactory.CreateNPCSevCrossServerGetOutCmd() and SendProtocol + } + } } \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Objet/Shortcut/CECShortcutSet.cs b/Assets/PerfectWorld/Scripts/Objet/Shortcut/CECShortcutSet.cs index a213c693be..521fdd7c8d 100644 --- a/Assets/PerfectWorld/Scripts/Objet/Shortcut/CECShortcutSet.cs +++ b/Assets/PerfectWorld/Scripts/Objet/Shortcut/CECShortcutSet.cs @@ -22,7 +22,6 @@ using CSNetwork.GPDataType; using CSNetwork.S2CCommand; using System; using System.Collections.Generic; -using Unity.VisualScripting; using UnityEngine; using static BrewMonster.EC_Resource; using static BrewMonster.IconResourceType; diff --git a/Assets/PerfectWorld/Scripts/Skills/CECSCSkill.cs b/Assets/PerfectWorld/Scripts/Skills/CECSCSkill.cs index 9714ea4f15..39d8402f47 100644 --- a/Assets/PerfectWorld/Scripts/Skills/CECSCSkill.cs +++ b/Assets/PerfectWorld/Scripts/Skills/CECSCSkill.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using Unity.VisualScripting; using static BrewMonster.SkillArrayWrapper; namespace BrewMonster diff --git a/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs b/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs index 2f24ba1bc2..803301005d 100644 --- a/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs +++ b/Assets/PerfectWorld/Scripts/Skills/ElementSkill.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; -using Unity.VisualScripting; namespace BrewMonster.Scripts.Skills { diff --git a/Assets/PerfectWorld/Scripts/Task/TaskTemplContainerSO.cs b/Assets/PerfectWorld/Scripts/Task/TaskTemplContainerSO.cs index ca680cf261..de231005e8 100644 --- a/Assets/PerfectWorld/Scripts/Task/TaskTemplContainerSO.cs +++ b/Assets/PerfectWorld/Scripts/Task/TaskTemplContainerSO.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using Unity.VisualScripting; using UnityEngine; using UnityEngine.Serialization; diff --git a/Assets/PerfectWorld/Scripts/Task/UI/DlgTask.cs b/Assets/PerfectWorld/Scripts/Task/UI/DlgTask.cs index f0d1f7deac..15e287798d 100644 --- a/Assets/PerfectWorld/Scripts/Task/UI/DlgTask.cs +++ b/Assets/PerfectWorld/Scripts/Task/UI/DlgTask.cs @@ -17,7 +17,6 @@ using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using TMPro; -using Unity.VisualScripting; namespace BrewMonster.Scripts.Task.UI { diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/AUIImagePicture.cs b/Assets/PerfectWorld/Scripts/UI/GamePlay/AUIImagePicture.cs index 7933bb173b..786d055af6 100644 --- a/Assets/PerfectWorld/Scripts/UI/GamePlay/AUIImagePicture.cs +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/AUIImagePicture.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using Unity.VisualScripting; using UnityEngine; using UnityEngine.UI; diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs b/Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs index 684d10e5d0..b1b532ee89 100644 --- a/Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs @@ -10,9 +10,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using Unity.VisualScripting; using UnityEngine; -using static UnityEngine.Rendering.DebugUI; namespace BrewMonster.UI { diff --git a/Assets/PerfectWorld/Scripts/UI/NPCShopDetailPanel.cs b/Assets/PerfectWorld/Scripts/UI/NPCShopDetailPanel.cs index 80e2d2c712..0955fa0986 100644 --- a/Assets/PerfectWorld/Scripts/UI/NPCShopDetailPanel.cs +++ b/Assets/PerfectWorld/Scripts/UI/NPCShopDetailPanel.cs @@ -24,6 +24,7 @@ public class NPCShopDetailPanel : MonoBehaviour private GShopItem currentItem; private NPCShopUIManager shopManager; + private int shopItemIndex; void Start() { @@ -39,10 +40,11 @@ public class NPCShopDetailPanel : MonoBehaviour buyButton.onClick.AddListener(OnBuyButtonClicked); } - public void SetupDetailPanel(GShopItem item, NPCShopUIManager manager) + public void SetupDetailPanel(GShopItem item, NPCShopUIManager manager, int index) { currentItem = item; shopManager = manager; + shopItemIndex = index; UpdateDisplay(); } @@ -189,17 +191,19 @@ public class NPCShopDetailPanel : MonoBehaviour } } - // Create npc_trade_item array for buying from NPC - // The tid is the item template ID, index is shop item index (0 for now), count is quantity to buy + // Server requires SEVNPC_HELLO with NPC id before buy, and the correct shop slot index + if (shopManager != null && shopManager.CurrentNPCID != 0) + UnityGameSession.c2s_CmdNPCSevHello((int)shopManager.CurrentNPCID); + + // Create npc_trade_item: tid = template ID, index = shop slot (server validates this), count = quantity npc_trade_item[] items = new npc_trade_item[1]; items[0] = new npc_trade_item { tid = (int)currentItem.id, - index = 0, // Shop item index - may need to be determined from shop item position - count = 1 // Quantity to buy + index = (uint)shopItemIndex, + count = 1 }; - // Send the buy command UnityGameSession.c2s_CmdNPCSevBuy(1, items); Debug.Log($"[NPCShopDetailPanel] Sent buy command for item {currentItem.id}, price {price}"); diff --git a/Assets/PerfectWorld/Scripts/UI/NPCShopItemPanel.cs b/Assets/PerfectWorld/Scripts/UI/NPCShopItemPanel.cs index 5ccd57d557..e112a3e4ea 100644 --- a/Assets/PerfectWorld/Scripts/UI/NPCShopItemPanel.cs +++ b/Assets/PerfectWorld/Scripts/UI/NPCShopItemPanel.cs @@ -19,6 +19,7 @@ public class NPCShopItemPanel : MonoBehaviour private GShopItem itemData; private Coroutine iconLoadCoroutine; private NPCShopUIManager shopManager; + private int shopItemIndex; void Start() { @@ -54,10 +55,11 @@ public class NPCShopItemPanel : MonoBehaviour } } - public void SetupItem(GShopItem item, NPCShopUIManager manager) + public void SetupItem(GShopItem item, NPCShopUIManager manager, int index) { itemData = item; shopManager = manager; + shopItemIndex = index; UpdateDisplay(); } @@ -65,7 +67,7 @@ public class NPCShopItemPanel : MonoBehaviour { if (shopManager != null && itemData.id != 0) { - shopManager.ShowItemDetail(itemData); + shopManager.ShowItemDetail(itemData, shopItemIndex); } } diff --git a/Assets/PerfectWorld/Scripts/UI/NPCShopUIManager.cs b/Assets/PerfectWorld/Scripts/UI/NPCShopUIManager.cs index 4c61a35673..d12ff02f9b 100644 --- a/Assets/PerfectWorld/Scripts/UI/NPCShopUIManager.cs +++ b/Assets/PerfectWorld/Scripts/UI/NPCShopUIManager.cs @@ -35,6 +35,9 @@ public class NPCShopUIManager : MonoBehaviour private int currentTabIndex = 0; private uint currentNPCID = 0; private NPC_SELL_SERVICE? cachedSellService = null; + + /// Current NPC id for this shop session. Send SEVNPC_HELLO with this before buy. + public uint CurrentNPCID => currentNPCID; private NPCShopDetailPanel detailPanelScript; void Start() @@ -278,8 +281,9 @@ public class NPCShopUIManager : MonoBehaviour if (elementDataMan == null) return; - foreach (var good in page.goods) + for (int i = 0; i < page.goods.Length; i++) { + var good = page.goods[i]; if (good.id == 0) continue; @@ -293,8 +297,8 @@ public class NPCShopUIManager : MonoBehaviour // Create GShopItem GShopItem shopItem = CreateShopItemFromGood(good, itemData, itemDataType); - // Create panel - CreateItemPanel(shopItem); + // Create panel with shop slot index (server expects this in npc_trade_item.index) + CreateItemPanel(shopItem, i); } } } @@ -372,7 +376,7 @@ public class NPCShopUIManager : MonoBehaviour return shopItem; } - void CreateItemPanel(GShopItem item) + void CreateItemPanel(GShopItem item, int shopItemIndex) { if (itemPanelPrefab == null || itemContainer == null) return; @@ -386,7 +390,7 @@ public class NPCShopUIManager : MonoBehaviour if (itemPanelScript != null) { - itemPanelScript.SetupItem(item, this); + itemPanelScript.SetupItem(item, this, shopItemIndex); } else { @@ -406,7 +410,7 @@ public class NPCShopUIManager : MonoBehaviour currentItemPanels.Clear(); } - public void ShowItemDetail(GShopItem item) + public void ShowItemDetail(GShopItem item, int shopItemIndex) { if (item.id == 0) return; @@ -428,7 +432,7 @@ public class NPCShopUIManager : MonoBehaviour if (detailPanelScript != null) { npcShopDetailPanel.SetActive(true); - detailPanelScript.SetupDetailPanel(item, this); + detailPanelScript.SetupDetailPanel(item, this, shopItemIndex); } else { diff --git a/Assets/PerfectWorld/Scripts/UI/ShopDetailPanel.cs b/Assets/PerfectWorld/Scripts/UI/ShopDetailPanel.cs index f351d1ba91..2ae252e81c 100644 --- a/Assets/PerfectWorld/Scripts/UI/ShopDetailPanel.cs +++ b/Assets/PerfectWorld/Scripts/UI/ShopDetailPanel.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using System; using System.Collections.Generic; using TMPro; -using Unity.VisualScripting; using UnityEngine; using UnityEngine.UI; diff --git a/Assets/Scripts/CECHostPlayer.Inventory.cs b/Assets/Scripts/CECHostPlayer.Inventory.cs index 64e371fc10..c6105f2b43 100644 --- a/Assets/Scripts/CECHostPlayer.Inventory.cs +++ b/Assets/Scripts/CECHostPlayer.Inventory.cs @@ -1,4 +1,4 @@ -using BrewMonster.Managers; +using BrewMonster.Managers; using BrewMonster.Network; using BrewMonster.Scripts; using BrewMonster.Scripts.Managers; @@ -22,12 +22,76 @@ namespace BrewMonster var data = Msg.dwParam1 as byte[]; int cmd = Convert.ToInt32(Msg.dwParam2); int hostId = Convert.ToInt32(Msg.dwParam3); - switch (cmd) { case CommandID.OWN_IVTR_DATA: { LogInventoryPacket("OWN_IVTR_DATA", data, hostId); + + // C++: pPack->ResetItems(*pCmd) where cmd_own_ivtr_info.content is a compact stream: + // for each slot [0..ivtr_size): tid (int); if tid>=0 then expire_date (int) and amount (int). + if (data != null && data.Length >= 6) + { + byte byPackage = data[0]; + byte ivtrSize = data[1]; + uint contentLength = BitConverter.ToUInt32(data, 2); + int index = 6; + int remaining = data.Length - index; + int contentBytes = remaining; + if (contentLength < (uint)remaining) + contentBytes = (int)contentLength; + + var inv = GetInventory(byPackage); + if (inv != null) + { + inv.Resize(ivtrSize); + inv.RemoveAllItems(); + + int end = index + Math.Max(0, contentBytes); + for (int slot = 0; slot < ivtrSize; slot++) + { + if (index + 4 > end) + break; + int tid = BitConverter.ToInt32(data, index); + index += 4; + if (tid < 0) + { + inv.SetItem(slot, null); + continue; + } + if (index + 8 > end) + break; + int expireDate = BitConverter.ToInt32(data, index); + index += 4; + int amount = BitConverter.ToInt32(data, index); + index += 4; + + if (amount > 0) + { + var item = EC_IvtrItem.CreateItem(tid, expireDate, amount); + if (item != null) + { + item.Package = byPackage; + item.Slot = slot; + item.SetCount(amount); + inv.SetItem(slot, item); + } + } + else + { + inv.SetItem(slot, null); + } + } + } + + if (byPackage == InventoryConst.IVTRTYPE_EQUIPPACK) + { + UpdateEquipSkins(); + } + + var ui = GameObject.FindFirstObjectByType(); + ui?.RefreshAll(); + } break; } case CommandID.OWN_IVTR_DETAIL_DATA: @@ -71,6 +135,19 @@ namespace BrewMonster break; } + case CommandID.CHANGE_IVTR_SIZE: + { + // C++: resize pack (normal inventory) + if (data != null && data.Length >= 4) + { + int newSize = BitConverter.ToInt32(data, 0); + if (m_pPack != null) + m_pPack.Resize(newSize); + var ui = GameObject.FindFirstObjectByType(); + ui?.RefreshAll(); + } + break; + } case CommandID.GET_OWN_MONEY: { if (data != null) @@ -168,14 +245,13 @@ namespace BrewMonster { case CommandID.OWN_ITEM_INFO: { - //Debug.Log("[Inventory] OWN_ITEM_INFO received"); - //var data = Msg.dwParam1 as byte[]; - //int hostId = Convert.ToInt32(Msg.dwParam3); - //LogInventoryPacket("OWN_ITEM_INFO", data, hostId); + if (Application.isEditor || Debug.isDebugBuild) + Debug.Log($"[INVNET] HST_OWNITEMINFO cmd=OWN_ITEM_INFO bytes={(Msg.dwParam1 as byte[])?.Length ?? 0}"); - //Handmade + // Match C++ cmd_own_item_info layout and behavior: update an existing item in place. + // If the slot is missing (can happen if client missed OWN_IVTR_DATA), create it to match server state. var data = Msg.dwParam1 as byte[]; - if (data == null || data.Length == 0) + if (data == null || data.Length < 22) return; byte byPackage = data[0]; @@ -184,7 +260,6 @@ namespace BrewMonster int expire_date = BitConverter.ToInt32(data, 6); int state = BitConverter.ToInt32(data, 10); uint count = BitConverter.ToUInt32(data, 14); - ushort crc = BitConverter.ToUInt16(data, 18); ushort content_length = BitConverter.ToUInt16(data, 20); byte[] content = null; @@ -192,38 +267,45 @@ namespace BrewMonster { content = new byte[content_length]; Buffer.BlockCopy(data, 22, content, 0, content_length); - - string hexDebug = BitConverter.ToString(content); - //Debug.Log($"[OWN_ITEM_INFO] Full Content Hex ({content_length} bytes): {hexDebug}"); } - //Debug.Log($"[OWN_ITEM_INFO] Parsed: package={byPackage}, slot={bySlot}, tid={type}, count={count}, content_len={content_length}"); - EC_Inventory pInventory = GetInventory(byPackage); - EC_IvtrItem newItem = EC_IvtrItem.CreateItem(type, expire_date, (int)count); + if (pInventory == null) + return; - if (newItem != null) + if (bySlot >= pInventory.GetSize()) + pInventory.Resize(bySlot + 1); + + var pItem = pInventory.GetItem(bySlot, false); + if (pItem == null) { - newItem.SetProcType(state); - - newItem.GetDetailDataFromLocal(); - if (content != null && content.Length > 0) - { - newItem.SetItemInfo(content, content_length); - } - - pInventory.SetItem(bySlot, newItem); - - //Debug.Log($"[OWN_ITEM_INFO] Fixed Update: Pack {byPackage} Slot {bySlot} - Type {type}"); + pItem = EC_IvtrItem.CreateItem(type, expire_date, (int)count); + if (pItem == null) + return; + pItem.Package = byPackage; + pItem.Slot = bySlot; + pInventory.SetItem(bySlot, pItem); } + pItem.SetExpireDate(expire_date); + pItem.SetProcType(state); + pItem.SetAmount((int)count); + if (content != null && content.Length > 0) + pItem.SetItemInfo(content, content.Length); + else + pItem.SetItemInfo(null, 0); + +#if UNITY_EDITOR + Debug.Log($"[Inventory] OWN_ITEM_INFO pkg={byPackage} slot={bySlot} tid={type} count={count} contentLen={content_length}"); +#endif + if (byPackage == InventoryConst.IVTRTYPE_EQUIPPACK) { UpdateEquipSkins(); } else if (byPackage == InventoryConst.IVTRTYPE_PACK) { - if (newItem.IsEquipment()) + if (pItem != null && pItem.IsEquipment()) { // TODO } @@ -342,7 +424,7 @@ namespace BrewMonster } // Trigger UI refresh if an EC_InventoryUI is present in scene - var ui = GameObject.FindObjectOfType(); + var ui = GameObject.FindFirstObjectByType(); if (ui != null) { ui.RefreshAll(); @@ -354,208 +436,215 @@ namespace BrewMonster } } + /// + /// Message MSG_HST_PICKUPITEM handler. Matches C++ flow: switch only fills idItem/iExpireDate/iAmount/iCmdLastSlot/iCmdSlotAmount/iPack/iMsg; + /// then single common path: MergeItem (or PutItemInSlot to match server slot), GetItemInfo request for equipment, notifications, RefreshAll. + /// public void OnMsgHstPickupItem(in ECMSG Msg) { var data = Msg.dwParam1 as byte[]; int cmd = Convert.ToInt32(Msg.dwParam2); + if (data == null) + return; + + if (Application.isEditor || Debug.isDebugBuild) + Debug.Log($"[INVNET] HST_PICKUPITEM cmd={cmd} bytes={data.Length}"); bool bDoOther = false; - int idItem, iExpireDate = 0, iAmount, iCmdLastSlot, iCmdSlotAmount, iPack, iMsg = -1; + int idItem = 0, iExpireDate = 0, iAmount = 0, iCmdLastSlot = 0, iCmdSlotAmount = 0, iPack = 0, iMsg = -1; switch (cmd) { - case CommandID.HOST_OBTAIN_ITEM: - { - // Parse cmd_host_obtain_item struct data - int type = BitConverter.ToInt32(data, 0); - int expire_date = BitConverter.ToInt32(data, 4); - uint amount = BitConverter.ToUInt32(data, 8); - uint slot_amount = BitConverter.ToUInt32(data, 12); - byte where = data[16]; // Package index - byte index = data[17]; // Slot index in that package - var newItem = EC_IvtrItem.CreateItem(type, expire_date, (int)amount); - - // Add item to inventory - var ivt = GetInventory(where); - if (newItem.Content != null && newItem.Content.Length > 0) - { - newItem.SetItemInfo(newItem.Content, newItem.Content.Length); - } - ivt.SetItem(index, newItem); - - Debug.Log( - $"[HOST_OBTAIN_ITEM] Successfully added item {type} to package {where}, slot {index} with count {amount}"); - - // Trigger UI refresh if an EC_InventoryUI is present in scene - var ui = GameObject.FindFirstObjectByType(); - if (ui != null) - { - ui.RefreshAll(); - } - - UpdateEquipSkins(); - } - break; case CommandID.PICKUP_ITEM: { - int tid = BitConverter.ToInt32(data, 0); - int expire_date = BitConverter.ToInt32(data, 4); - iAmount = (int)BitConverter.ToUInt32(data, 8); - uint iSlotAmount = BitConverter.ToUInt32(data, 12); - byte byPackage = data[16]; - byte bySlot = data[17]; - - //Debug.Log($"[Inventory] PICKUP_ITEM: tid={tid}, expire_date={expire_date}, iAmount={iAmount}, iSlotAmount={iSlotAmount}, byPackage={byPackage}, bySlot={bySlot}"); - - // Notify pickupItem script about successful pickup - pickupItem pickupScript = pickupItem.Instance; - if (pickupScript != null) - { - //Debug.Log($"[Inventory] PICKUP_ITEM: tid={tid}, expire_date={expire_date}, iAmount={iAmount}, iSlotAmount={iSlotAmount}, byPackage={byPackage}, bySlot={bySlot}"); - - // Notify pickupItem script about successful pickup - pickupScript = UnityEngine.Object.FindFirstObjectByType(); - if (pickupScript != null) - { - pickupScript.OnPickupSuccess(tid); - } - - // Create new inventory item data - var newItem = EC_IvtrItem.CreateItem(tid, expire_date, (int)iAmount); - - // Add item to inventory - var ivt = GetInventory(byPackage); - ivt.SetItem(bySlot, newItem); - - //Debug.Log($"[Inventory] Successfully added item {tid} to package {byPackage}, slot {bySlot} with count {iAmount}"); - - // Trigger UI refresh if an EC_InventoryUI is present in scene - var ui = GameObject.FindFirstObjectByType(); - if (ui != null) - { - ui.RefreshAll(); - } - } - else - { - Debug.LogWarning("[Inventory] PICKUP_ITEM: Invalid data length"); - } - + var pCmdPickup = GPDataTypeHelper.FromBytes(data); + idItem = pCmdPickup.tid; + iExpireDate = pCmdPickup.expire_date; + iAmount = (int)pCmdPickup.iAmount; + iCmdLastSlot = pCmdPickup.bySlot; + iCmdSlotAmount = (int)pCmdPickup.iSlotAmount; + iPack = pCmdPickup.byPackage; + iMsg = (int)FixedMsg.FIXMSG_PICKUPITEM; + break; + } + case CommandID.HOST_OBTAIN_ITEM: + { + var pCmdObtain = GPDataTypeHelper.FromBytes(data); + idItem = pCmdObtain.type; + iExpireDate = pCmdObtain.expire_date; + iAmount = (int)pCmdObtain.amount; + iCmdLastSlot = pCmdObtain.index; + iCmdSlotAmount = (int)pCmdObtain.slot_amount; + iPack = pCmdObtain.where; + iMsg = (int)FixedMsg.FIXMSG_GAINITEM; + break; + } + case CommandID.PRODUCE_ONCE: + { + var pCmdProduce = GPDataTypeHelper.FromBytes(data); + idItem = pCmdProduce.type; + iExpireDate = 0; + iAmount = (int)pCmdProduce.amount; + iCmdLastSlot = pCmdProduce.index; + iCmdSlotAmount = (int)pCmdProduce.slot_amount; + iPack = pCmdProduce.where; + iMsg = (int)FixedMsg.FIXMSG_PRODUCEITEM; + var dlgProduce = UnityEngine.Object.FindFirstObjectByType(); + dlgProduce?.OnProduceOnce(pCmdProduce); break; } case CommandID.TASK_DELIVER_ITEM: - cmd_task_deliver_item pCmd = GPDataTypeHelper.FromBytes(data); - // ASSERT(pCmd); - - idItem = pCmd.type; - iExpireDate = pCmd.expire_date; - iAmount = (int)pCmd.amount; - iCmdLastSlot = pCmd.index; - iCmdSlotAmount = (int)pCmd.slot_amount; - iPack = pCmd.where; + { + var pCmdTask = GPDataTypeHelper.FromBytes(data); + idItem = pCmdTask.type; + iExpireDate = pCmdTask.expire_date; + iAmount = (int)pCmdTask.amount; + iCmdLastSlot = pCmdTask.index; + iCmdSlotAmount = (int)pCmdTask.slot_amount; + iPack = pCmdTask.where; iMsg = (int)FixedMsg.FIXMSG_GAINITEM; bDoOther = true; - - - // Create new inventory item data - var taskNewItem = EC_IvtrItem.CreateItem(idItem, iExpireDate, (int)iAmount); - - // Add item to inventory - var task_ivt = GetInventory((byte)iPack); - if (!task_ivt.MergeItem(idItem, iExpireDate, iAmount, out var iLastSlot, out var iSlotNum) || - iLastSlot != iCmdLastSlot || iSlotNum != iCmdSlotAmount) - { - return; - } - - task_ivt.SetItem(iCmdLastSlot, taskNewItem); - - //Debug.Log($"[Inventory] Successfully added item {tid} to package {byPackage}, slot {bySlot} with count {iAmount}"); - - // Trigger UI refresh if an EC_InventoryUI is present in scene - var task_ui = GameObject.FindFirstObjectByType(); - if (task_ui != null) - { - task_ui.RefreshAll(); - } - break; - case CommandID.PRODUCE_ONCE: + } + default: + return; + } + +#if UNITY_EDITOR + Debug.Log($"[Inventory] PICKUP_FLOW cmd={cmd} pack={iPack} slot={iCmdLastSlot} tid={idItem} amt={iAmount} slotAmt={iCmdSlotAmount}"); +#endif + EC_Inventory pInventory = GetPack(iPack); + if (pInventory == null) + return; + + if (iCmdLastSlot >= pInventory.GetSize()) + pInventory.Resize(iCmdLastSlot + 1); + + bool placed = pInventory.PutItemInSlot(iCmdLastSlot, idItem, iExpireDate, iAmount, out int iLastSlot, out int iSlotNum); + if (!placed) + { + placed = pInventory.MergeItem(idItem, iExpireDate, iAmount, out iLastSlot, out iSlotNum); + if (!placed || iLastSlot != iCmdLastSlot || iSlotNum != iCmdSlotAmount) { - // Parse cmd_produce_once struct data - cmd_produce_once produceCmd = GPDataTypeHelper.FromBytes(data); +#if UNITY_EDITOR + Debug.LogWarning($"[Inventory] PICKUP_FLOW desync: placed={placed} lastSlot={iLastSlot} slotNum={iSlotNum} expectedSlot={iCmdLastSlot} expectedSlotAmt={iCmdSlotAmount}"); +#endif + return; + } + } - int produceItemId = produceCmd.type; - int produceExpireDate = 0; - uint produceAmount = produceCmd.amount; - byte producePack = produceCmd.where; - byte produceSlot = produceCmd.index; + if (cmd == CommandID.HOST_OBTAIN_ITEM && iPack == Inventory_type.IVTRTYPE_PACK) + { + // C++: CECShoppingManager::Instance().OnObtainItem(iPack, idItem, iAmount); + } - Debug.Log( - $"[PRODUCE_ONCE] Received: itemId={produceItemId}, amount={produceAmount}, pack={producePack}, slot={produceSlot}"); + EC_IvtrItem pItem = pInventory.GetItem(iCmdLastSlot, false); + if (pItem != null) + { + pItem.Package = (byte)iPack; + pItem.Slot = iCmdLastSlot; + int cid = pItem.GetClassID(); + if (pItem.IsEquipment() || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_TASKNMMATTER || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_TASKDICE || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_TASKITEM || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_GOBLIN_EXPPILL || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_WEDDINGBOOKCARD || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_WEDDINGINVITECARD || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_SKILLTOME || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_GOBLIN || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_PETEGG) + { + UnityGameSession.c2s_CmdGetItemInfo((byte)iPack, (byte)iCmdLastSlot); + } + if (iMsg >= 0) + { + CECGameRun pGameRun = EC_Game.GetGameRun(); + pGameRun?.AddFixedMessage(iMsg, iAmount, pItem.GetName()); + } + } - // Get inventory - var produce_ivt = GetInventory(producePack); - if (produce_ivt == null) + if (bDoOther) + { + // C++: m_pTaskInterface->DoAutoTeamForTask(idItem); + } + + if (cmd == CommandID.PICKUP_ITEM) + { + var pickupScript = UnityEngine.Object.FindFirstObjectByType(); + pickupScript?.OnPickupSuccess(idItem); + } + + var ui = GameObject.FindFirstObjectByType(); + ui?.RefreshAll(); + UpdateEquipSkins(); + } + + /// Buy from NPC/booth: server sends PURCHASE_ITEM (cmd_purchase_item). C++ OnMsgHstPurchaseItems. + public void OnMsgHstPurchaseItems(ECMSG Msg) + { + var data = Msg.dwParam1 as byte[]; + if (data == null || data.Length < 11) + return; + + var header = GPDataTypeHelper.FromBytes(data); + int index = 11; + const int itemSize = 15; // item_id(4) + expire_date(4) + count(4) + inv_index(2) + booth_slot(1) + EC_Inventory pPack = GetPack(Inventory_type.IVTRTYPE_PACK); + if (pPack == null) + return; + + var slotsNeedingDetail = new System.Collections.Generic.List(); + + for (int i = 0; i < header.item_count && index + itemSize <= data.Length; i++) + { + int item_id = BitConverter.ToInt32(data, index); index += 4; + int expire_date = BitConverter.ToInt32(data, index); index += 4; + int count = (int)BitConverter.ToUInt32(data, index); index += 4; + ushort inv_index = BitConverter.ToUInt16(data, index); index += 2; + index += 1; // booth_slot + + if (inv_index >= pPack.GetSize()) + pPack.Resize(inv_index + 1); + + bool placed = pPack.PutItemInSlot(inv_index, item_id, expire_date, count, out int lastSlot, out int slotNum); + if (!placed) + { + placed = pPack.MergeItem(item_id, expire_date, count, out lastSlot, out slotNum); + if (!placed || lastSlot != inv_index) + continue; + } + + var pItem = pPack.GetItem(inv_index, false); + if (pItem != null) + { + pItem.Package = (byte)Inventory_type.IVTRTYPE_PACK; + pItem.Slot = inv_index; + int cid = pItem.GetClassID(); + if (pItem.IsEquipment() || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_TASKNMMATTER || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_TASKDICE || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_TASKITEM || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_GOBLIN_EXPPILL || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_WEDDINGBOOKCARD || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_WEDDINGINVITECARD || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_SKILLTOME || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_GOBLIN || + cid == (int)EC_IvtrItem.InventoryClassId.ICID_PETEGG) { - Debug.LogWarning($"[PRODUCE_ONCE] Invalid inventory package {producePack}"); - return; - } - - // Check if the slot already has an item - var existingItem = produce_ivt.GetItem(produceSlot, false); - - if (existingItem != null) - { - if (existingItem.m_tid == produceItemId) - { - existingItem.m_iCount = (int)produceAmount; - Debug.Log( - $"[PRODUCE_ONCE] Updated existing item count at slot {produceSlot} to {produceAmount}"); - } - else - { - Debug.LogWarning( - $"[PRODUCE_ONCE] Slot {produceSlot} already has different item (tid={existingItem.m_tid}), not overwriting with {produceItemId}"); - return; - } - } - else - { - var produceNewItem = new EC_IvtrItem - { - Package = producePack, - Slot = produceSlot, - m_tid = produceItemId, - m_expire_date = produceExpireDate, - State = 0, - m_iCount = (int)produceAmount, - Crc = 0, - Content = null - }; - - produce_ivt.SetItem(produceSlot, produceNewItem); - Debug.Log($"[PRODUCE_ONCE] Created new item at slot {produceSlot} with count {produceAmount}"); - } - - // Trigger UI refresh - var produce_ui = GameObject.FindFirstObjectByType(); - if (produce_ui != null) - { - produce_ui.RefreshAll(); - } - - UpdateEquipSkins(); - - // Notify DlgProduce - var dlgProduce = GameObject.FindFirstObjectByType(); - if (dlgProduce != null) - { - dlgProduce.OnProduceOnce(produceCmd); + slotsNeedingDetail.Add((byte)inv_index); } } - break; } + + AddMoneyAmount(-(int)header.cost); + + foreach (byte slot in slotsNeedingDetail) + UnityGameSession.c2s_CmdGetItemInfo((byte)Inventory_type.IVTRTYPE_PACK, slot); + + var ui = GameObject.FindFirstObjectByType(); + ui?.RefreshAll(); + UpdateEquipSkins(); } private void OnMsgHstUseItem(ECMSG Msg) diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index 5dfb12b25d..63405d1aee 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -43,9 +43,9 @@ namespace BrewMonster private CECHPWorkMan m_pWorkMan; // Host work manager private uint m_dwLIES; // Logic-influence extend states private FACTION_FORTRESS_ENTER m_fortressEnter; // ½øÈë»ùµØÐÅÏ¢ - private PVPINFO m_pvp; // pvp information - private bool m_bInSanctuary = false; // true, player is in sanctuary - private int m_idFaction = 0; // ID of player's faction + //private PVPINFO m_pvp; // pvp information + //private bool m_bInSanctuary = false; // true, player is in sanctuary + //private int m_idFaction = 0; // ID of player's faction public bool m_bPrepareFight = false; // true, prepare to fight private int m_iJumpCount = 0; private bool m_bJumpInWater = false; @@ -565,6 +565,9 @@ namespace BrewMonster case EC_MsgDef.MSG_HST_PICKUPITEM: OnMsgHstPickupItem(Msg); break; + case EC_MsgDef.MSG_HST_PURCHASEITEMS: + OnMsgHstPurchaseItems(Msg); + break; case EC_MsgDef.MSG_HST_PRODUCEITEM: OnMsgHstProduceItem(Msg); break; @@ -1467,10 +1470,10 @@ namespace BrewMonster } // Get faction ID - public int GetFactionID() - { - return m_idFaction; - } + //public int GetFactionID() + //{ + // return m_idFaction; + //} public void SetPrayDistancePlus(float prayDistancePlus) { m_fPrayDistancePlus = prayDistancePlus;