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 BrewMonster.PerfectWorld.Scripts.UI; using BrewMonster.Scripts; using BrewMonster.Scripts.Chat; using BrewMonster.Scripts.Managers; using BrewMonster.Scripts.UI; using BrewMonster.Scripts; using BrewMonster.Scripts.Managers; using UnityEngine; using CECPlayer = BrewMonster.CECPlayer; 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 server sends RPC_ADDFRIENDRQST(204) — another player wants to add you as friend. /// Args: xid (RPC transaction id), srcroleid, askerName. /// public event Action FriendRequestReceived; /// Raised when server sends PROTOCOL_ADDFRIEND_RE(203). Args: retcode (0=success), message. public event Action AddFriendResultReceived; /// Raised when server sends PROTOCOL_GETFRIENDS_RE(207). public event Action FriendListReceived; public event Action FriendDeletedReceived; /// Raised when server sends PROTOCOL_FRIENDSTATUS(214). Args: roleid, status. public event Action FriendStatusChanged; /// Raised when server sends PROTOCOL_FRIENDEXTLIST. public event Action FriendExtListReceived; /// 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; TestLogoutLogic.Instance.WasClientSendLogoutMessage = false; } 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: OnPrtcSelectRoleRe((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: OnPrtcChatMessage(protocol, false); break; case ProtocolType.PROTOCOL_PRIVATECHAT: OnPrtcPrivateChat(protocol); break; case ProtocolType.PROTOCOL_WORLDCHAT: OnPrtcWorldChat(protocol, false); break; case ProtocolType.PROTOCOL_PLAYERBASEINFO_RE: OnPrtcPlayerBaseInfoRe(protocol); break; case ProtocolType.PROTOCOL_GETUICONFIG_RE: OnPrtcGetConfigRe(protocol); break; case ProtocolType.PROTOCOL_SETUICONFIG_RE: OnPrtcSetConfigRe(protocol); break; case ProtocolType.PROTOCOL_PLAYERLOGOUT: // HandlePlayerLogout((playerlogout)protocol); OnPrtcPlayerLogout((playerlogout)protocol); break; case ProtocolType.PROTOCOL_AUTOTEAMSETGOAL_RE: { // CECAutoTeam pAutoTeam = CECGameRun.Instance.GetHostPlayer().GetAutoTeam(); // if( pAutoTeam !=null) // pAutoTeam.OnPrtcAutoTeamSetGoalRe((AutoTeamSetGoal_Re)protocol); } break; case ProtocolType.RPC_ADDFRIENDRQST: OnAddFriendRqst((addfriendrqst)protocol); break; case ProtocolType.PROTOCOL_ADDFRIEND_RE: OnAddFriendRe((addfriend_re)protocol); break; case ProtocolType.PROTOCOL_GETFRIENDS_RE: OnGetFriendsRe((getfriends_re)protocol); break; case ProtocolType.PROTOCOL_FRIENDEXTLIST: OnFriendExtList((friendextlist)protocol); break; case ProtocolType.PROTOCOL_FRIENDSTATUS: OnFriendStatus((friendstatus)protocol); break; case ProtocolType.PROTOCOL_DELFRIEND_RE: OnDelFriendRe((delfriend_re)protocol); break; default: _logger.Log(LogType.Warning, $"Received unhandled protocol type: {protocol.GetPType()}"); break; } } private void OnDelFriendRe(delfriend_re p) { PostToUnityContext(() => { FriendDeletedReceived?.Invoke(p); if (p != null && p.retcode == 0) Friend_GetList(); }); } private void HandlePlayerLogout(playerlogout protocol) { // old code of HUNGDK // 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 OnAddFriendRqst(addfriendrqst p) { string askerName = ""; if (p.Srcname != null && p.Srcname.Size > 0) askerName = System.Text.Encoding.Unicode.GetString(p.Srcname.ToArray(), 0, p.Srcname.Size); // Forward xid + srcroleid + name to Unity layer so it can answer via Friend_AddResponse(xid, agree) PostToUnityContext(() => FriendRequestReceived?.Invoke(p.Xid, p.Srcroleid, askerName)); } private void OnGetFriendsRe(getfriends_re p) { PostToUnityContext(() => { try { var host = EC_Game.GetGameRun()?.GetHostPlayer(); host?.GetFriendMan()?.ResetFromServer(p.Groups, p.Friends, p.Status); } catch (Exception ex) { _logger.Log(LogType.Error, $"OnGetFriendsRe failed: {ex.Message}"); _logger.LogException(ex); } FriendListReceived?.Invoke(p); }); } private void OnFriendExtList(friendextlist p) { PostToUnityContext(() => FriendExtListReceived?.Invoke(p)); } private void OnFriendStatus(friendstatus p) { PostToUnityContext(() => { try { var host = EC_Game.GetGameRun()?.GetHostPlayer(); host?.GetFriendMan()?.ChangeFriendStatus(p.Roleid, p.Status); } catch (Exception ex) { _logger.Log(LogType.Error, $"OnFriendStatus failed: {ex.Message}"); _logger.LogException(ex); } FriendStatusChanged?.Invoke(p.Roleid, p.Status); }); } private void OnAddFriendRe(addfriend_re p) { string friendName = ""; if (p.info?.name != null && p.info.name.Size > 0) friendName = System.Text.Encoding.Unicode.GetString(p.info.name.ToArray(), 0, p.info.name.Size); string msg = p.retcode == 0 ? $"Thêm bạn thành công: {friendName}" : $"Không thể thêm bạn do: {GetAddFriendRetcodeMessage(p.retcode)}"; if (p.retcode == 0) { Friend_GetList(); } PostToUnityContext(() => AddFriendResultReceived?.Invoke(p.retcode, msg)); } /// FS_ERR server retcodes for addfriend_re. private static string GetAddFriendRetcodeMessage(byte retcode) { switch (retcode) { //case 0: return "Success"; //case 1: return "Player is offline"; //case 2: return "Request was refused"; //case 3: return "Timeout"; //case 4: return "No remaining space"; //case 5: return "Not found"; //case 6: return "Invalid parameter"; //case 7: return "Duplicate entry"; //case 8: return "Friend list has not been retrieved yet"; case 0: return "thành công"; case 1: return "người chơi đang offline"; case 2: return "yêu cầu bị từ chối"; case 3: return "hết thời gian yêu cầu"; case 4: return "không còn chỗ trống"; case 5: return "không tìm thấy"; case 6: return "tham số không hợp lệ"; case 7: return "mục nhập trùng lặp"; case 8: return "danh sách bạn bè chưa được tải về"; default: return $"retcode={retcode}"; } } // void CECGameSession::OnPrtcPlayerLogout(GNET::Protocol* pProtocol) // { // using namespace GNET; // PlayerLogout* p = (PlayerLogout*)pProtocol; void OnPrtcPlayerLogout(playerlogout protocol) { // m_CmdCache.RemoveAllCmds(); m_CmdCache.RemoveAllCmds(); // int iFlag; // switch (p->result) // { // case _PLAYER_LOGOUT_FULL: iFlag = 0; break; // case _PLAYER_LOGOUT_HALF: iFlag = 1; break; // default: iFlag = 2; break; // } int iFlag; switch (protocol.Result) { case PendingActionConstants._PLAYER_LOGOUT_FULL: iFlag = 0; break; case PendingActionConstants._PLAYER_LOGOUT_HALF: iFlag = 1; break; default: iFlag = 2; break; } // g_pGame->GetGameRun()->SetLogoutFlag(iFlag); EC_Game.GetGameRun().SetLogoutFlag(iFlag); // if (!IsConnected() && g_pGame->GetGameRun()->GetLogoutFlag() == 1) // { // a_LogOutput(1, "CECGameSession::OnPrtcPlayerLogout, LogoutFlag=1 replaced by 2."); // g_pGame->GetGameRun()->SetLogoutFlag(2); // } if (!IsConnected && EC_Game.GetGameRun().GetLogoutFlag() == 1) { // _logger.Log(LogType.Log, "CECGameSession::OnPrtcPlayerLogout, LogoutFlag=1 replaced by 2."); EC_Game.GetGameRun().SetLogoutFlag(2); } } /// Format parsed PLAYER_EQUIP_DETAIL with real game data: slot names and item names from element data. private static string FormatPlayerEquipDetailReadable(PlayerEquipDetailData parsed) { var sb = new StringBuilder(); sb.Append($"RoleId={parsed.RoleId}"); if (parsed.Slots.Count == 0) { sb.Append(", no slots"); return sb.ToString(); } sb.Append(" | "); for (int i = 0; i < parsed.Slots.Count; i++) { var (slotIndex, tid) = parsed.Slots[i]; string slotName = GetEquipSlotName(slotIndex); string itemName = tid <= 0 ? "(empty)" : (EC_IvtrItemUtils.Instance.ResolveItemName(tid) ?? $"(tid{tid})"); if (string.IsNullOrEmpty(itemName)) itemName = $"(tid{tid})"; if (i > 0) sb.Append(", "); sb.Append($"{slotName}: {itemName}"); } return sb.ToString(); } private static string GetEquipSlotName(byte slotIndex) { if (Enum.IsDefined(typeof(IndexOfIteminEquipmentInventory), slotIndex)) { var name = Enum.GetName(typeof(IndexOfIteminEquipmentInventory), slotIndex); if (!string.IsNullOrEmpty(name)) return name; } return $"Slot{slotIndex}"; } private void HandleServerDataSend(gamedatasend protocol) { 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.EQUIP_DATA: case CommandID.EQUIP_DATA_CHANGED: // BMLogger.Log($"### EQUIP_DATA: CMDID {pCmdHeader}"); // MSG_PM_PLAYEREQUIPDATA, MAN_PLAYER, -1, (DWORD)pDataBuf, pCmdHeader->cmd, dwDataSize); EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYEREQUIPDATA, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); break; case CommandID.PLAYER_EQUIP_DETAIL: // View other player profile/equip detail — parse and log with real game data (item names, slot names) if (pDataBuf != null && pDataBuf.Length > 0) { try { var parsed = GPDataTypeHelper.ParsePlayerEquipDetail(pDataBuf); string readable = FormatPlayerEquipDetailReadable(parsed); Debug.Log($"[PLAYER_EQUIP_DETAIL] {readable}"); if (parsed.Slots.Count == 0 && pDataBuf.Length > 6) Debug.Log($"[PLAYER_EQUIP_DETAIL] Raw(hex): {BitConverter.ToString(pDataBuf)}"); } catch (Exception ex) { Debug.LogWarning($"[PLAYER_EQUIP_DETAIL] Parse failed: {ex.Message}. Raw length={pDataBuf.Length}, hex: {BitConverter.ToString(pDataBuf, 0, Math.Min(64, pDataBuf.Length))}..."); } } else Debug.Log("[PLAYER_EQUIP_DETAIL] Server sent empty payload."); break; case CommandID.PLAYER_CASH: { EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_IVTRINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, 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.ATTACK_ONCE: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ATTACKONCE, 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_LEADER_INVITE: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_TEAMINVITE, (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.HOST_SKILL_ATTACKED: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SKILLATTACKED, 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: BMLogger.LogError("EC_MsgDef.MSG_PM_PLAYEREXTPROP"); 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; } case CommandID.PLAYER_CHGSHAPE: EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERCHGSHAPE, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); break; case CommandID.SET_MOVE_STAMP: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SETMOVESTAMP, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); break; case CommandID.UPDATE_EXT_STATE: { cmd_update_ext_state pCmd = GPDataTypeHelper.FromBytes((byte[])pDataBuf); if (ISPLAYERID(pCmd.id)) EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYEREXTSTATE, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); else if (ISNPCID(pCmd.id)) EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCEXTSTATE, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); break; } case CommandID.ICON_STATE_NOTIFY: { // this is a bit hacky, but it works. because the cmd_icon_state_notify is not blittable (contains List<>); read leading id only for routing. // cmd_icon_state_notify is not blittable (contains List<>); read leading id only for routing. if (pDataBuf == null || pDataBuf.Length < sizeof(int)) break; int iconStateEntityId = BitConverter.ToInt32(pDataBuf, 0); if (ISPLAYERID(iconStateEntityId)) EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYEREXTSTATE, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); else if (ISNPCID(iconStateEntityId)) EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCEXTSTATE, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); break; } case CommandID.ENTER_SANCTUARY: case CommandID.LEAVE_SANCTUARY: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SANCTUARY, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); break; case CommandID.OBJECT_SKILL_ATTACK_RESULT: { cmd_object_skill_attack_result pCmd = GPDataTypeHelper.FromBytes ((byte[])pDataBuf); if (ISPLAYERID(pCmd.attacker_id)) EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERSKILLRESULT, MANAGER_INDEX.MAN_PLAYER, -1,pDataBuf, pCmdHeader); else if (ISNPCID(pCmd.attacker_id)) EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCSKILLRESULT, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); break; } default: #if UNITY_EDITOR if (isDebug) { BMLogger.LogError($"### GameDataSend: Unhandled CMDID {pCmdHeader} (payloadBytes={pDataBuf?.Length ?? 0})"); } #endif break; } } private void OnPrtcSelectRoleRe(SelectRole_Re protocol) { _logger.Info($"Select role response {protocol.result}"); var callback = _selectRoleCallback; PostToUnityContext(() => callback?.Invoke(_selectedRole)); // in C++: we call CECGameRun() via pLoginUIMan->LaunchLoading(); // now: quick hack to start game immediately after role selection - can refactor later if needed EC_Game.GetGameRun().StartGame(0, Vector3.zero); } 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 c2s_SendCmdLogout(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 const int SUPER_FAR_CRY_EMOTION_SET = 6; /// /// Convert string become byte (Encoding.Unicode trong .NET = UTF-16 LE) /// /// Data type: enum, Chanel chat /// Text send to server /// /// public void SendChatData(byte cChannel, in string szMsg, int iPack, int iSlot) { if (string.IsNullOrEmpty(szMsg)) return; publicchat p = new publicchat(); p.Channel = cChannel; p.Roleid = m_iCharID; if (iPack == EC_Inventory.IVTRTYPE_CLIENT_GENERALCARD_PACK) { CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer(); EC_Inventory clientPack = pHost != null ? pHost.GetPack(EC_Inventory.IVTRTYPE_CLIENT_GENERALCARD_PACK) : null; if (pHost != null && clientPack != null) { EC_IvtrItem pCard = clientPack.GetItem(iSlot); if (pCard != null) { short cmd = (short)CHAT_S2C.EChatS2CCommand.CHAT_GENERALCARD_COLLECTION; int cardId = pCard.GetTemplateID(); byte[] bytes = new byte[6]; BitConverter.GetBytes(cmd).CopyTo(bytes, 0); BitConverter.GetBytes(cardId).CopyTo(bytes, 2); p.Data.Replace(bytes); } } } else if (iPack >= 0 && iSlot >= 0) { byte[] bytes = new byte[5]; short cmd = (short)CHAT_S2C.EChatS2CCommand.CHAT_EQUIP_ITEM; short index = (short)iSlot; byte where = (byte)iPack; BitConverter.GetBytes(cmd).CopyTo(bytes, 0); bytes[2] = where; BitConverter.GetBytes(index).CopyTo(bytes, 3); p.Data.Replace(bytes); } byte[] unicodeBytes = Encoding.Unicode.GetBytes(szMsg); p.Msg.Replace(unicodeBytes); SendProtocol(p); // [Port] C++: DlgChat::OnCommand_speak dòng 736: // Server KHÔNG echo tin nhắn quay lại cho chính người gửi. // => Phải gọi AddChatMessage ngay tại local để hiển thị lên UI Chat Panel. { CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer(); int nEmotionSet = 0; // Head Bubble: Chỉ hiện bong bóng đầu cho các kênh "quanh người" (giống C++ SendChatData) if (cChannel is (byte)ChatChannel.GP_CHAT_LOCAL or (byte)ChatChannel.GP_CHAT_FARCRY or (byte)ChatChannel.GP_CHAT_SUPERFARCRY or (byte)ChatChannel.GP_CHAT_BATTLE or (byte)ChatChannel.GP_CHAT_COUNTRY) { string strMsg = szMsg; if (cChannel == (byte)ChatChannel.GP_CHAT_SUPERFARCRY) { if (strMsg.Length > 8) strMsg = strMsg.Substring(0, strMsg.Length - 8); nEmotionSet = SUPER_FAR_CRY_EMOTION_SET; } } // UI Chat Panel: Hiển thị cho TẤT CẢ kênh (Local, Team, Faction, Trade...) CECStringTab pStrTab = EC_Game.GetFixedMsgs(); string szName = pHost.GetName(); char[] szText = new char[80]; AUICommon.AUI_ConvertChatString(ref szName, ref szText, false); string fmt = AUICommon.ConvertPrintfToCSharpFormat(pStrTab.GetWideString((int)FixedMsg.FIXMSG_CHAT)); string str; try { str = string.Format(fmt, szName, szMsg); } catch { str = $"{szName}: {szMsg}"; } EC_Game.GetGameRun().AddChatMessage(str, cChannel, pHost.GetCharacterID(), null, 0, nEmotionSet, null, szMsg); } } public void SendPrivateChatData(string szDstName, string szMsg, byte byFlag = 0, int idPlayer = 0, int iPack = -1, int iSlot = -1) { if (string.IsNullOrEmpty(szMsg)) return; CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer(); // In Unity version, pHost doesn't have AddRelatedPlayer. // C++ uses it to cache player names for chat, but here it's handled differently. var p = new privatechat(); p.Srcroleid = m_iCharID; p.Dstroleid = idPlayer; p.Channel = byFlag; Octets data = new Octets(); if (iPack >= 0 && iSlot >= 0 && pHost != null) { EC_Inventory pPack = pHost.GetPack(iPack); if (pPack != null) { EC_IvtrItem pItem = pPack.GetItem(iSlot); if (pItem != null) { // TODO: Implement item serialization if needed } } } p.Data = data; p.Src_name = new Octets(Encoding.Unicode.GetBytes(pHost != null ? pHost.GetName() : "")); p.Dst_name = new Octets(Encoding.Unicode.GetBytes(szDstName)); p.Msg = new Octets(Encoding.Unicode.GetBytes(szMsg)); SendProtocol(p); } 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 bool OnPrtcWorldChat(Protocol pProtocol, bool bCalledagain) { worldchat p = (worldchat)pProtocol; CECGameUIMan pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan(); CECStringTab pStrTab = EC_Game.GetFixedMsgs(); string strMsg = Encoding.Unicode.GetString(p.Msg.ToArray()); string strSrcName = Encoding.Unicode.GetString(p.Name.ToArray()); string fmt = AUICommon.ConvertPrintfToCSharpFormat(pStrTab.GetWideString((int)FixedMsg.FIXMSG_CHAT)); string nameWithTag = $"&{strSrcName}&"; string formatted; try { formatted = string.Format(fmt, nameWithTag, strMsg); } catch { formatted = $"{strSrcName}: {strMsg}"; } pGameUI.AddChatMessage(formatted, (ChatChannel)p.Channel, p.Roleid, strSrcName, 0, p.Emotion, null, strMsg); // Bubble chat on head CECPlayer pSrcPlayer = EC_Game.GetGameRun().GetWorld().GetPlayerMan().GetPlayer(p.Roleid); if (pSrcPlayer != null) { EventBus.PublishChannel(p.Roleid, new EventChatMessageOnTopPlayer(p.Roleid, strMsg)); } return true; } /*private void OnTaskChatMessage(byte[] pBuf, int sz) { ... logic ẩn ... }*/ private bool OnBattleChatMessage(chatmessage p, List pPendingFactions) { // Tương đương OnBattleChatMessage trong C++ // Hiện tại đơn giản hóa việc hiển thị, thực tế cần parse nội dung Battle cụ thể Debug.Log($"[Battle Chat] RoleID: {p.Srcroleid}"); return true; } private bool OnFortressChatMessage(chatmessage p, List pPendingFactions) { // Tương đương OnFortressChatMessage trong C++ Debug.Log($"[Fortress Chat] RoleID: {p.Srcroleid}"); return true; } private bool OnKingChatMessage(chatmessage p, bool bGetPlayerName = true) { // Tương đương OnKingChatMessage trong C++ Debug.Log($"[King Chat] RoleID: {p.Srcroleid}"); return true; } private void OnFactionPVPChatMessage(chatmessage p) { // Tương đương OnFactionPVPChatMessage trong C++ Debug.Log($"[FactionPVP Chat] RoleID: {p.Srcroleid}"); } private bool OnPrtcChatMessage(Protocol pProtocol, bool bCalledagain) { CECGameUIMan pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan(); chatmessage p = (chatmessage)pProtocol; //var channel = (ChatChannel)p.Channel; Debug.Log("[Cuong] reciver chat channel: " + p.Channel); if (Chat_GameSession.ShouldBlockByLevel(p)) { Debug.Log("[Cuong] 1"); return true; } EC_IvtrItem pItem = CHAT_S2C.CreateChatItem(p.Data); string szMsg = null; string strTemp = Encoding.Unicode.GetString(p.Msg.ToArray(), 0, p.Msg.Length); string strMsgOrigion = strTemp; // [Port] CECGameUIMan::FilterInvalidTags — Lọc các tag đặc biệt không hợp lệ strTemp = AUICommon.FilterInvalidTags(strTemp, pItem == null); if (!Chat_GameSession.PolicyResolver(pProtocol, p, ref strTemp, out szMsg)) { Debug.Log("[Cuong] 2"); return false; } if (p.Channel is (byte)ChatChannel.GP_CHAT_BROADCAST or (byte)ChatChannel.GP_CHAT_SYSTEM || p.Srcroleid == 0) { if (p.Channel == (byte)ChatChannel.GP_CHAT_SYSTEM && p.Srcroleid > 0) { switch (p.Srcroleid) { case 1: case 2: case 3: case 4: case 6: case 7: // Battle Message if (!bCalledagain) { List pending = new List(); if (!OnBattleChatMessage(p, pending)) { // Handle pending faction info return false; } } break; case 18: case 19: case 20: case 21: case 22: // Auction Message // pGameUI.AddSysAuctionMessage(...) Debug.Log("[Auction] " + strTemp); EventBus.Publish(new ChatMessageEvent(strTemp, p.Channel)); break; case 24: // Task Message // OnTaskChatMessage(p.Data.RawBuffer, p.Data.Size); break; case 29: case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: case 38: case 39: case 40: case 41: case 42: case 43: case 45: // Fortress Message OnFortressChatMessage(p, null); break; case 46: case 47: case 48: case 49: // Country Battle // OnCountryChatMessage(p); break; case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: case 58: case 59: // King Chat OnKingChatMessage(p); break; case 60: case 61: case 62: case 63: case 64: // Faction PVP OnFactionPVPChatMessage(p); break; } } else { Debug.Log("[Cuong] 5"); EventBus.Publish(new ChatMessageEvent(strTemp, p.Channel)); } }else if (p.Channel == (byte)ChatChannel.GP_CHAT_INSTANCE && p.Srcroleid == 1) { Debug.Log("[Cuong] 6"); // Chat_GameSession.AUICTranslate trans; // EC_Game.GetGameRun().GetUIManager().GetInGameUIMan().AddHeartBeatHint(trans.Translate(szMsg )); } else { Debug.Log("[Cuong] Other"); CECStringTab pStrTab = EC_Game.GetFixedMsgs(); if (ISPLAYERID(p.Srcroleid)) { string szName = EC_Game.GetGameRun().GetPlayerName(p.Srcroleid, false); if (string.IsNullOrEmpty(szName)) { Debug.Log("[Cuong] Other 0"); if (!bCalledagain) { Chat_GameSession.AddElemForPendingProtocols(pProtocol); Chat_GameSession.AddChatPlayerID(p.Srcroleid); } return false; } else { Debug.Log("[Cuong] Other 1"); char[] szText = new char[80]; AUICommon.AUI_ConvertChatString(ref szName,ref szText, false); string fmt = AUICommon.ConvertPrintfToCSharpFormat(pStrTab.GetWideString((int)FixedMsg.FIXMSG_CHAT)); string str = string.Format( fmt, szName, szMsg ); // [Port] Gọi AddChatMessage để hiển thị lên UI Chat Box. // AddChatMessage bên trong đã tự publish ChatMessageEvent (cho ChatPanelUI) // VÀ EventChatMessageOnTopPlayer (cho Head Bubble) nếu channel thuộc nhóm // showsAboveHead (LOCAL, TEAM, FARCRY, SUPERFARCRY, BATTLE, COUNTRY). // => Không cần publish thêm ở đây nữa. EC_Game.GetGameRun().AddChatMessage(str, p.Channel, p.Srcroleid, null, 0, p.Emotion, null, strMsgOrigion); } } else if(ISNPCID(p.Srcroleid)) { Debug.Log("[Cuong] ISNPCID " + strTemp); CECNPC pNPC = EC_Game.GetGameRun().GetWorld().GetNPCMan().GetNPC(p.Srcroleid); if (pNPC != null) { string str; string template = AUICommon.ConvertPrintfToCSharpFormat(pStrTab.GetWideString((int)FixedMsg.FIXMSG_CHAT2)); string message = string.Format( template, pNPC.GetName(), szMsg ); EC_Game.GetGameRun().AddChatMessage( message, p.Channel, p.Srcroleid, null, 0, p.Emotion ); EventBus.Publish(new ChatMessageEvent(message)); CECUIHelper.RemoveNameFlagFromNPCChat(strTemp, out szMsg); pNPC.SetLastSaidWords(szMsg); } } } return true; } private void OnPrtcPrivateChat(Protocol pProtocol) { privatechat p = (privatechat)pProtocol; // TODO: Blacklist check (PlayerIsBlack) // if (PlayerIsBlack(p.Srcroleid)) return; string strMsg = Encoding.Unicode.GetString(p.Msg.ToArray()); string strSrcName = Encoding.Unicode.GetString(p.Src_name.ToArray()); CECGameUIMan pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan(); CECStringTab pStrTab = EC_Game.GetFixedMsgs(); if (p.Channel == 0 /* CHANNEL_NORMAL */ || p.Channel == 1 /* CHANNEL_NORMALRE */) { // Format: "[Name] whispers to [You]: [Message]" string fmt = AUICommon.ConvertPrintfToCSharpFormat(pStrTab.GetWideString((int)FixedMsg.FIXMSG_PRIVATECHAT1)); // Wrap sender name in & so the AddChatMessage regex can color it. string nameWithTag = $"&{strSrcName}&"; string formatted; try { formatted = string.Format(fmt, nameWithTag, strMsg); } catch { formatted = $"{strSrcName} whispers to you: {strMsg}"; } pGameUI.AddChatMessage(formatted, ChatChannel.GP_CHAT_WHISPER, p.Srcroleid, strSrcName, p.Channel, p.Emotion, null, strMsg); } else { pGameUI.AddChatMessage(strMsg, ChatChannel.GP_CHAT_WHISPER, p.Srcroleid, strSrcName, p.Channel, p.Emotion, null, strMsg); } // Set player's last said words for head bubble CECPlayer pSrcPlayer = EC_Game.GetGameRun().GetWorld().GetPlayerMan().GetPlayer(p.Srcroleid); if (pSrcPlayer != null) { EventBus.PublishChannel(p.Srcroleid, new EventChatMessageOnTopPlayer(p.Srcroleid, strMsg)); } } public struct ChatMessageEvent { public string context; public byte channel; public ChatMessageEvent(string context, byte channel = 0) { this.context = context; this.channel = channel; } } 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 OnPrtcSetConfigRe(Protocol pProtocol) { SetUIConfig_Re p = (SetUIConfig_Re)pProtocol; if (p.result != (int)ErrCode.ERR_SUCCESS) BMLogger.LogError($"CECGameSession::OnPrtcSetConfigRe, link return error code of {p.result}"); if (CECGameRun.Instance != null) { TestLogoutLogic.Instance.WasClientSendLogoutMessage = true; CECGameRun.Instance.GetPendingLogOut().TriggerAll(); CECGameRun.Instance.GetPendingLogOut().Clear(); } } 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) { // using namespace C2S; const int iNumLimit = 250; int iCount = 0; while (iCount < iNumID) { int iNumSend = iNumLimit; if (iCount + iNumLimit > iNumID) iNumSend = iNumID - iCount; /*/ int iSize = sizeof (cmd_header) + sizeof (WORD) + iNumSend * sizeof (int); BYTE* pBuf = (BYTE*)a_malloctemp(iSize); if (!pBuf) return; ((cmd_header*)pBuf)->cmd = C2S::GET_OTHER_EQUIP; cmd_get_other_equip* pCmd = (cmd_get_other_equip*)(pBuf + sizeof (cmd_header)); pCmd->size = (WORD)iNumSend; for (int i=0; i < iNumSend; i++) pCmd->idlist[i] = aIDs[iCount+i]; g_pGame->GetGameSession()->SendGameData(pBuf, iSize); a_freetemp(pBuf); //*/ var idlist = new int[iNumSend]; for (int i=0; i < iNumSend; i++) idlist[i] = aIDs[iCount+i]; gamedatasend gamedatasend = new gamedatasend(); gamedatasend.Data = C2SCommandFactory.CreateGetOtherEquipCmd(iNumID, idlist); SendProtocol(gamedatasend); iCount += iNumSend; } } /// Request other player profile/equip detail (C2S GET_OTHER_EQUIP_DETAIL). Server responds with PLAYER_EQUIP_DETAIL. public void c2s_SendCmdGetOtherEquipDetail(int roleId) { gamedatasend gamedatasend = new gamedatasend(); gamedatasend.Data = C2SCommandFactory.CreateGetOtherEquipDetailCmd(roleId); SendProtocol(gamedatasend); } public void c2s_SendCmdNPCSevAcceptTask(int idTask, int idStorage, int idRefreshItem) { gamedatasend gamedatasend = new gamedatasend(); 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_SendCmdTeamAgreeInvite(int idLeader, int team_seq) { var g = new gamedatasend(); g.Data = C2SCommandFactory.CreateTeamAgreeInviteCommand(idLeader, team_seq); SendProtocol(g); } public void c2s_SendCmdTeamRejectInvite(int idLeader) { var g = new gamedatasend(); g.Data = C2SCommandFactory.CreateTeamRejectInviteCommand(idLeader); 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); } /// Send PROTOCOL_ADDFRIEND(202). Port of CECGameSession::friend_Add(idPlayer, szName). public void Friend_Add(int idPlayer, string name) { var p = new addfriend(); p.Srcroleid = m_iCharID; p.Dstroleid = idPlayer; if (!string.IsNullOrEmpty(name)) p.Dstname = new Octets(System.Text.Encoding.Unicode.GetBytes(name)); else p.Dstname = new Octets(); p.Srclsid = (int)_localsid; SendProtocol(p); } /// /// Send RPC_ADDFRIENDRQST(204) response. Port of CECGameSession::friend_AddResponse(dwHandle, bAgree). /// Answer to received friend request: xid from request, agree = true (0) or false (69). /// Matches GNET Rpc XID: low bit = is_request; response must clear that bit. /// public void Friend_AddResponse(uint xid, bool agree) { const byte ERR_TRADE_AGREE = 0; const byte ERR_TRADE_REFUSE = 69; uint responseXid = xid & 0x7FFFFFFFu; // clear request bit, keep count var p = new addfriendrqstres(responseXid, agree ? ERR_TRADE_AGREE : ERR_TRADE_REFUSE); SendProtocol(p); } /// Send PROTOCOL_GETFRIENDS(206). Port of CECGameSession::friend_GetList(). public void Friend_GetList() { var p = new getfriends(); p.Roleid = m_iCharID; p.Localsid = (int)_localsid; SendProtocol(p); } /// Send PROTOCOL_DELFRIEND(212). Delete a friend by role id. public void Friend_Del(int friendId) { var p = new delfriend(); p.Roleid = m_iCharID; p.Friendid = friendId; p.Localsid = (int)_localsid; SendProtocol(p); } 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 } } }