From 750e7b4ff8f969164f58b037ccf09dc7ae551de2 Mon Sep 17 00:00:00 2001 From: HungDK <> Date: Mon, 9 Feb 2026 18:07:37 +0700 Subject: [PATCH] Handle logic of Duel --- .../Scripts/Managers/EC_ManPlayer.cs | 1645 +++++++++-------- .../Scripts/Players/EC_ElsePlayer.cs | 1 + Assets/Scripts/CECGameRun.cs | 7 + Assets/Scripts/CECHostPlayer.cs | 177 +- Assets/Scripts/CECUIManager.cs | 24 +- 5 files changed, 1020 insertions(+), 834 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs b/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs index a4f2074c08..9211f1736a 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs @@ -1,810 +1,837 @@ -using BrewMonster; -using BrewMonster.Managers; -using BrewMonster.Network; -using BrewMonster.Scripts; -using CSNetwork; -using CSNetwork.GPDataType; -using CSNetwork.Protocols; -using CSNetwork.Protocols.RPCData; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Reflection; -using System.Runtime.InteropServices; -using TMPro; -using UnityEngine; -using UnityEngine.SceneManagement; - -namespace PerfectWorld.Scripts.Managers -{ - namespace BrewMonster.Managers - { - [Serializable] - public class EC_ManPlayer : IMsgHandler - { - Dictionary m_UkPlayerTab = new Dictionary(); - Dictionary m_PlayerTab = new Dictionary(); - private readonly object m_csPlayerTab = new object(); - CECHostPlayer m_pHostPlayer; - public int HandlerId => (int)MANAGER_INDEX.MAN_PLAYER; - public bool ProcessMessage(ECMSG Msg) - { - if (Msg.iSubID == 0) - { - if (CECGameRun.Instance == null) return true; - if (CECGameRun.Instance.GetHostPlayer() == null) return true; - CECGameRun.Instance.GetHostPlayer().ProcessMessage(Msg); - } - else if (Msg.iSubID < 0) - { - switch ((int)Msg.dwMsg) - { - case int value when value == EC_MsgDef.MSG_PM_PLAYERINFO: - { - OnMsgPlayerInfo(Msg); - break; - } - case int value when value == EC_MsgDef.MSG_PM_PLAYERMOVE: - { - OnMsgPlayerMove(Msg); - break; - } - case int value when value == EC_MsgDef.MSG_PM_PLAYERLEVELUP: - OnMsgPlayerLevelUp(Msg); - break; - case int value when value == EC_MsgDef.MSG_PM_PLAYERSTOPMOVE: - { - OnMsgPlayerStopMove(Msg); - break; - } - case int value when value == EC_MsgDef.MSG_PM_PLAYERBASEINFO: - case int value2 when value2 == EC_MsgDef.MSG_PM_FACTION_PVP_MASK_MODIFY: - case int value3 when value3 == EC_MsgDef.MSG_PM_PLAYERATKRESULT: - case int value4 when value4 == EC_MsgDef.MSG_PM_CASTSKILL: - case int value5 when value5 == EC_MsgDef.MSG_PM_ENCHANTRESULT: - case int value6 when value6 == EC_MsgDef.MSG_PM_PLAYERDOEMOTE: - case int value7 when value7 == EC_MsgDef.MSG_PM_PLAYERGATHER: - case int value8 when value8 == EC_MsgDef.MSG_PM_PLAYERFLY: - case int value9 when value9 == EC_MsgDef.MSG_PM_PLAYERMOUNT: - TransmitMessage(Msg); - break; - case int value when value == EC_MsgDef.MSG_PM_PLAYERDIED: - OnMsgPlayerDied(Msg); - break; - case int value when value == EC_MsgDef.MSG_PM_PLAYERREVIVE: - OnMsgPlayerRevive(Msg); - break; - case int value when value == EC_MsgDef.MSG_PM_PLAYERRUNOUT: - OnMsgPlayerRunOut(Msg); - break; - case EC_MsgDef.MSG_PM_PICKUPMATTER: - OnMsgPlayerPickupMatter(Msg); - break; - case EC_MsgDef.MSG_PM_PLAYEREXTPROP: - OnMsgPlayerExtProp(Msg); - break; - } - } - else - { - - } - return true; - } - - private void OnMsgPlayerDied(ECMSG Msg) - { - cmd_player_died pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - EC_ElsePlayer pPlayer = SeekOutElsePlayer(pCmd.idPlayer); - //if (pPlayer && !pPlayer.IsAboutToDie()) - // pPlayer.Killed(pCmd.idKiller); - - EventBus.PublishChannel(pPlayer.GetCharacterID(), new ClearComActFlagAllRankNodesEvent(true)); - pPlayer.PlayAction((int)PLAYER_ACTION_TYPE.ACT_GROUNDDIE); - } - - private void OnMsgPlayerRevive(ECMSG Msg) - { - cmd_player_revive pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - CECHostPlayer pHost = GetHostPlayer(); - if (pHost && pHost.GetCharacterID() == pCmd.idPlayer) - { - pHost.HandleRevive(pCmd.sReviveType, pCmd.pos); - } - else - { - EC_ElsePlayer pPlayer = SeekOutElsePlayer(pCmd.idPlayer); - if (pPlayer) - pPlayer.HandleRevive(pCmd.sReviveType, pCmd.pos); - } - } - - public void OnMsgPlayerInfo(ECMSG Msg) - { - int iHostID = Convert.ToInt32(Msg.dwParam3); - int lenghtByte = Marshal.SizeOf(); - byte[] byteArray = new byte[lenghtByte]; - byte[] data = (byte[])Msg.dwParam1; - for (int i = 0; i < lenghtByte; i++) - { - byteArray[i] = data[i]; - } - int cid = BitConverter.ToInt32(byteArray); - int commandID = Convert.ToInt32(Msg.dwParam2); - switch (commandID) - { - case CommandID.PLAYER_INFO_1: - case CommandID.PLAYER_ENTER_WORLD: - case CommandID.PLAYER_ENTER_SLICE: - { - if (cid != iHostID) - { - info_player_1 info_Player_1 = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - ElsePlayerEnter(info_Player_1, commandID); - } - break; - } - case CommandID.SELF_INFO_1: - cmd_self_info_1 info = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - HostPlayerInfo1(info); - break; - case CommandID.PLAYER_INFO_2: break; - case CommandID.PLAYER_INFO_3: break; - case CommandID.PLAYER_INFO_4: break; - - case CommandID.PLAYER_INFO_1_LIST: - { - byte[] bytes = (byte[])Msg.dwParam1; - cmd_player_info_1_list pCmd = GPDataTypeHelper.FromBytes(bytes); - if (pCmd.count == 0) break; - - List a1 = new(); - List a2 = new(); - List a3 = new(); - List a4 = new(); - - - int lenght = Marshal.SizeOf(); - int preSize = 0; - byte[] pDataBuf = null; - preSize += Marshal.SizeOf(); - for (int i = 0; i < pCmd.count; i++) - { - pDataBuf = new byte[lenght]; - for (int j = 0; j < pDataBuf.Length; j++) - { - pDataBuf[j] = bytes[preSize + j]; - } - info_player_1 Info = GPDataTypeHelper.FromBytes(pDataBuf); - if (Info.cid != iHostID) - { - EC_ElsePlayer pPlayer = ElsePlayerEnter(Info, commandID); - - if (pPlayer != null) - { - if (!pPlayer.m_bCustomReady) - { - if (!pPlayer.m_bBaseInfoReady) - a2.Add(Info.cid); - else - a3.Add(Info.cid); - } - - // Is equipment data ready - if (!pPlayer.m_bEquipReady) - a1.Add(Info.cid); - - // TODO: Implement get faction - /*/ - // Get faction info - if (pPlayer.GetFactionID() && !g_pGame.GetFactionMan().GetFaction(pPlayer.GetFactionID())) - { - int i(0); - for (i = 0; i < a4.GetSize(); i++) - if (a4[i] == pPlayer.GetFactionID()) - break; - - if (i == a4.GetSize()) - a4.Add(pPlayer.GetFactionID()); - } - //*/ - } - } - - // Calculate player info data size and skip it - int iSize = Marshal.SizeOf(); - if ((Info.state & PlayerNPCState.GP_STATE_ADV_MODE) != 0) - iSize += Marshal.SizeOf() * 2; - - if ((Info.state & PlayerNPCState.GP_STATE_SHAPE) != 0) - iSize += Marshal.SizeOf(); - - if ((Info.state & PlayerNPCState.GP_STATE_EMOTE) != 0) - iSize += Marshal.SizeOf(); - - if ((Info.state & PlayerNPCState.GP_STATE_EXTEND_PROPERTY) != 0) - iSize += Marshal.SizeOf() * NumberDWORDsPlayerNPC.OBJECT_EXT_STATE_COUNT; - - if ((Info.state & PlayerNPCState.GP_STATE_FACTION) != 0) - iSize += (Marshal.SizeOf() + Marshal.SizeOf()); - - if ((Info.state & PlayerNPCState.GP_STATE_BOOTH) != 0) - iSize += Marshal.SizeOf(); - - //// Parse effect data - if ((Info.state & PlayerNPCState.GP_STATE_EFFECT) != 0) - { - iSize += Marshal.SizeOf(); - byte byNum = bytes[preSize + iSize]; - - if (byNum > 0) - iSize += byNum * Marshal.SizeOf(); - } - - if ((Info.state & PlayerNPCState.GP_STATE_PARIAH) != 0) - iSize += Marshal.SizeOf(); - - if ((Info.state & PlayerNPCState.GP_STATE_IN_MOUNT) != 0) - iSize += Marshal.SizeOf() + Marshal.SizeOf(); - - // Parse bind data - if ((Info.state & PlayerNPCState.GP_STATE_IN_BIND) != 0) - iSize += Marshal.SizeOf() + Marshal.SizeOf(); - - // Parse spouse data - if ((Info.state & PlayerNPCState.GP_STATE_SPOUSE) != 0) - iSize += Marshal.SizeOf(); - - if ((Info.state & PlayerNPCState.GP_STATE_EQUIPDISABLED) != 0) - iSize += Marshal.SizeOf(); - - if ((Info.state & PlayerNPCState.GP_STATE_PLAYERFORCE) != 0) - iSize += Marshal.SizeOf(); - - if ((Info.state & PlayerNPCState.GP_STATE_MULTIOBJ_EFFECT) != 0) - { - int effectNum = Marshal.ReadInt32(bytes, iSize);//*(int*)(pDataBuf + iSize); - iSize += Marshal.SizeOf(); - - if (effectNum > 0) - iSize += effectNum * (Marshal.SizeOf() + Marshal.SizeOf()); - } - - if ((Info.state & PlayerNPCState.GP_STATE_COUNTRY) != 0) - iSize += Marshal.SizeOf(); - if ((Info.state2 & PlayerNPCState2.GP_STATE2_TITLE) != 0) - iSize += Marshal.SizeOf(); - if ((Info.state2 & PlayerNPCState2.GP_STATE2_REINCARNATION) != 0) - iSize += Marshal.SizeOf(); - if ((Info.state2 & PlayerNPCState2.GP_STATE2_REALM) != 0) - iSize += Marshal.SizeOf(); - if ((Info.state2 & PlayerNPCState2.GP_STATE2_FACTION_PVP_MASK) != 0) - iSize += Marshal.SizeOf(); - - // Goblin refine data - if ((Info.state & PlayerNPCState.GP_STATE_GOBLINREFINE) != 0) - iSize += Marshal.SizeOf(); - - //pDataBuf += iSize; - - preSize += iSize; - } - - // Get both base info and custom data - if (a2.Count > 0) - UnityGameSession.GetRoleBaseInfo(a2.Count, a2); - - // Only get custom data - if (a3.Count > 0) - UnityGameSession.GetRoleCustomizeData(a3.Count, a3); - break; - } - } - } - - public void OnMsgPlayerMove(ECMSG Msg) - { - int iHostID = Convert.ToInt32(Msg.dwParam3); - cmd_object_move pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - if (pCmd.use_time == 0) - return; - if (pCmd.id != iHostID) - { - EC_ElsePlayer pPlayer = SeekOutElsePlayer(pCmd.id); - if (pPlayer) - { - pPlayer.MoveTo(pCmd); - } - } - } - - private bool OnMsgPlayerLevelUp(ECMSG Msg) - { - cmd_level_up pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - CECHostPlayer pHost = GetHostPlayer(); - if (pCmd.id == pHost.GetCharacterID()) - { - pHost.LevelUp(); - } - else - { - EC_ElsePlayer pPlayer = SeekOutElsePlayer(pCmd.id); - if (pPlayer) - pPlayer.LevelUp(); - } - - return true; - } - - public bool HostPlayerInfo1(cmd_self_info_1 info) - { - //bool isDoneWorldRender = false; - //bool isDoneNPCRender = false; - //Action actLoadChar = () => - //{ - // if (!isDoneNPCRender || !isDoneWorldRender) - // { - // return; - // } - // GameController.Instance.InitCharacter(info); - //}; - //string nameScene = "NPCRender"; - //UnityGameSession.Instance.LoadScene(nameScene, LoadSceneMode.Single, (value) => - //{ - // isDoneNPCRender = value; - // actLoadChar?.Invoke(); - //}); - //nameScene = "WorldRender"; - //UnityGameSession.Instance.LoadScene(nameScene, LoadSceneMode.Additive, (value) => - //{ - // isDoneWorldRender = value; - // actLoadChar?.Invoke(); - //}); - //UnityGameSession.Instance.LoadScene("HoangTest", LoadSceneMode.Single, (value) => - //{ - // isDoneWorldRender = value; - // actLoadChar?.Invoke(); - //}); - CECGameRun.Instance.InitCharacter(info); - return true; - } - - public EC_ElsePlayer ElsePlayerEnter(info_player_1 info, int iCmd) - { - // If this player's id is in unknown table, remove it because this player - // won't be unknown anymore - if (m_UkPlayerTab.TryGetValue(info.cid, out int value)) - { - if (value != 0) // Pair.second != 0 - { - m_UkPlayerTab.Remove(info.cid); - } - } - - int iAppearFlag = (iCmd == (int)CommandID.PLAYER_ENTER_WORLD) ? - (int)PlayerAppearFlag.APPEAR_ENTERWORLD : (int)PlayerAppearFlag.APPEAR_RUNINTOVIEW; - - // Has player been in active player table ? - EC_ElsePlayer pPlayer = GetElsePlayer(info.cid); - if (pPlayer != null) - { - // Check if the GameObject is still valid (not destroyed) - if (pPlayer.gameObject != null) - { - // This player has existed in player table, call special initial function - pPlayer.Init(info, iAppearFlag); - return pPlayer; - } - else - { - // GameObject was destroyed but entry still exists in table - clean it up - lock (m_csPlayerTab) - { - m_PlayerTab.Remove(info.cid); - } - } - } - - // Create a new player - if (!(pPlayer = CreateElsePlayer(info, iAppearFlag))) - { - return null; - } - lock (m_csPlayerTab) - { - if (m_PlayerTab.ContainsKey(info.cid)) - { - m_PlayerTab[info.cid] = pPlayer; - } - else - { - m_PlayerTab.Add(info.cid, pPlayer); - } - } - return pPlayer; - } - - private EC_ElsePlayer CreateElsePlayer(info_player_1 info, int iAppearFlag) - { - GameObject ob = CECGameRun.Instance.InitCharacter(info); - EC_ElsePlayer elsePlayer = ob.AddComponent(); - - elsePlayer.Init(info, iAppearFlag); - return elsePlayer; - } - - public EC_ElsePlayer GetElsePlayer(int cid, long dwBornStamp = 0) - { - lock (m_csPlayerTab) - { - if (!m_PlayerTab.TryGetValue(cid, out var player)) - return null; - if (dwBornStamp != 0) - { - //TO DO: fix after GetBornStamp() is create in code - //if (player.GetBornStamp() != dwBornStamp) - return null; - } - - return player; - } - } - - private EC_ElsePlayer SeekOutElsePlayer(int cid) - { - if (!m_PlayerTab.TryGetValue(cid, out var player)) - { - // Couldn't find this else player, put it into unknown player table - m_UkPlayerTab[cid] = cid; - return null; - } - - return player; - } - - - public bool OnMsgPlayerStopMove(ECMSG Msg) - { - cmd_object_stop_move pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - EC_ElsePlayer pPlayer = SeekOutElsePlayer(pCmd.id); - if (pPlayer) - pPlayer.StopMoveTo(pCmd); - return true; - } - - public bool OnMsgPlayerRunOut(ECMSG Msg) - { - int id = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - CECHostPlayer pHost = GetHostPlayer(); - if (pHost != null && id == pHost.GetCharacterID()) - return true; // Don't process if it's the host player - - ElsePlayerLeave(id, false); - return true; - } - - public void OnMsgPlayerPickupMatter(ECMSG Msg) - { - cmd_matter_pickup pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - // CECHostPlayer pHost = GetHostPlayer(); - - - EC_ManMessageMono.Instance.EC_ManMatter.RemoveMatter(pCmd.matter_id); - - } - - private void ElsePlayerLeave(int cid, bool bExitGame) - { - EC_ElsePlayer pPlayer = SeekOutElsePlayer(cid); - if (pPlayer == null) - return; - - // Remove from active player table - lock (m_csPlayerTab) - { - m_PlayerTab.Remove(cid); - } - - // If this player is selected by host, cancel the selection - CECHostPlayer pHost = GetHostPlayer(); - if (pHost != null && pHost.GetSelectedTarget() == cid) - pHost.SelectTarget(0); - - UnityGameSession.Instance.GetC2SCmdCache().RemovePlayerBaseInfo(cid); - // Release player resource - if (pPlayer != null) - { - if (bExitGame) - { - // Player exited game - handle differently if needed - } - // Destroy the player GameObject - if (pPlayer.gameObject != null) - UnityEngine.Object.Destroy(pPlayer.gameObject); - } - } - - 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 Initialize() - { - - } - public bool TransmitMessage(ECMSG Msg) - { - int cid = 0; - var host = CECGameRun.Instance.GetHostPlayer(); - - switch (Msg.dwMsg) - { - /*case long value when value == EC_MsgDef.MSG_PM_PLAYEREQUIPDATA: - if (msg.dwParam2 == S2C.EQUIP_DATA) - cid = ((cmd_equip_data)msg.dwParam1).idPlayer; - else // EQUIP_DATA_CHANGED - cid = ((cmd_equip_data_changed)msg.dwParam1).idPlayer; - break;*/ - - case long value when value == EC_MsgDef.MSG_PM_PLAYERBASEINFO: - cid = (int)((playerbaseinfo_re)Msg.dwParam1).Player.id; - // Xoá khỏi cache - UnityGameSession.Instance.GetC2SCmdCache().RemovePlayerBaseInfo(cid); - break; - - /*case long value when value == EC_MsgDef.MSG_PM_PLAYERCUSTOM: - cid = ((GetCustomData_Re)msg.dwParam1).cus_roleid; - break; - - case long value when value == EC_MsgDef.MSG_PM_PLAYERFLY: - if (msg.dwParam2 == S2C.OBJECT_TAKEOFF) - cid = ((cmd_object_takeoff)msg.dwParam1).object_id; - else - cid = ((cmd_object_landing)msg.dwParam1).object_id; - break;*/ - case long value when value == EC_MsgDef.MSG_PM_PLAYERATKRESULT: - cmd_object_atk_result pCmdAtk = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - cid = pCmdAtk.attacker_id; - break; - case long value when value == EC_MsgDef.MSG_PM_CASTSKILL: - switch (Convert.ToInt32(Msg.dwParam2)) - { - case int value2 when value2 == CommandID.OBJECT_CAST_SKILL: cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).caster; break; - case int value2 when value2 == CommandID.OBJECT_CAST_INSTANT_SKILL: cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).caster; break; - case int value2 when value2 == CommandID.OBJECT_CAST_POS_SKILL: cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).caster; break; - case int value2 when value2 == CommandID.SKILL_INTERRUPTED: cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).caster; break; - case int value2 when value2 == CommandID.PLAYER_CAST_RUNE_SKILL: cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).caster; break; - case int value2 when value2 == CommandID.PLAYER_CAST_RUNE_INSTANT_SKILL: cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).caster; break; - } - break; - case long value when value == EC_MsgDef.MSG_PM_PLAYERDOEMOTE: - - if (Convert.ToInt32(Msg.dwParam2) == CommandID.OBJECT_DO_EMOTE) - { - cmd_object_do_emote pCmdDoEmote = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - cid = pCmdDoEmote.id; - } - else // Msg.dwParam2 == OBJECT_EMOTE_RESTORE - { - // cid = ((cmd_object_emote_restore*)Msg.dwParam1)->id; - } - - break; - case long value when value == EC_MsgDef.MSG_PM_ENCHANTRESULT: - - cid = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1).caster; - break; - case long value when value == EC_MsgDef.MSG_PM_PLAYERGATHER: - - if (Convert.ToInt32(Msg.dwParam2) == CommandID.PLAYER_GATHER_START) - { - cmd_player_gather_start cmdPlayerGatherStart = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - cid = cmdPlayerGatherStart.pid; - } - else if (Convert.ToInt32(Msg.dwParam2) == CommandID.PLAYER_GATHER_STOP) - { - cmd_player_gather_stop cmdPlayerGatherStop = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - cid = cmdPlayerGatherStop.pid; - } - else - { - cmd_mine_gathered cmdMineGathered = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - cid = cmdMineGathered.player_id; - } - - break; - case long value when value == EC_MsgDef.MSG_PM_PLAYERFLY: - - if (Convert.ToInt32(Msg.dwParam2) == CommandID.OBJECT_TAKEOFF) - cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).object_id; - else // OBJECT_LANDING - cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).object_id; - - break; - case long value when value == EC_MsgDef.MSG_PM_PLAYERMOUNT: - cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).id; - break; - // ⚠️ Các case khác cũng tương tự, chỉ việc lấy ra đúng trường id / caster / user ... - // Do quá dài nên bạn có thể copy dần từng case từ C++ sang. - - default: - System.Diagnostics.Debug.Assert(false, "Unknown message"); - return false; - } - - if (cid == 0) - { - System.Diagnostics.Debug.Assert(false, "cid = 0"); - return false; - } - - if (host != null && cid == host.GetCharacterID()) - { - host.ProcessMessage(Msg); - } - else - { - var elsePlayer = SeekOutElsePlayer(cid); - if (elsePlayer != null) - elsePlayer.ProcessMessage(Msg); - } - - return true; - } - - // Get a player (may be host or else player) by id - public CECPlayer GetPlayer(int cid, uint dwBornStamp = 0) - { - CECHostPlayer pHost = GetHostPlayer(); - if (pHost && pHost.GetCharacterID() == cid) - return pHost; - else - return GetElsePlayer(cid, dwBornStamp); - } - - public CECHostPlayer GetHostPlayer() - { - return CECGameRun.Instance.GetHostPlayer(); - } - - /// Returns character IDs of all other players in the world (excluding host). Used e.g. by DlgArrangeTeam for nearby list. - public List GetOtherPlayerCharacterIds() - { - var list = new List(); - CECHostPlayer pHost = GetHostPlayer(); - int idHost = pHost != null ? pHost.GetCharacterID() : 0; - lock (m_csPlayerTab) - { - foreach (int cid in m_PlayerTab.Keys) - { - if (cid != idHost) - list.Add(cid); - } - } - return list; - } - - public bool OnMsgPlayerExtProp(ECMSG Msg) - { - object pData; - int idPlayer, iIndex; - - switch (Msg.dwParam2) - { - case CommandID.PLAYER_EXT_PROP_BASE: - { - cmd_pep_base pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - idPlayer = pCmd.idPlayer; - pData = pCmd.ep_base; - iIndex = (int)ExtendPropertyClass.EXTPROPIDX_BASE; - break; - } - case CommandID.PLAYER_EXT_PROP_MOVE: - { - cmd_pep_move pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - idPlayer = pCmd.idPlayer; - pData = pCmd.ep_move; - iIndex = (int)ExtendPropertyClass.EXTPROPIDX_MOVE; - break; - } - case CommandID.PLAYER_EXT_PROP_ATK: - { - cmd_pep_attack pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - idPlayer = pCmd.idPlayer; - pData = pCmd.ep_attack; - iIndex = (int)ExtendPropertyClass.EXTPROPIDX_ATTACK; - break; - } - - case CommandID.PLAYER_EXT_PROP_DEF: - { - cmd_pep_def pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); - idPlayer = pCmd.idPlayer; - pData = pCmd.ep_def; - iIndex = (int)ExtendPropertyClass.EXTPROPIDX_DEF; - break; - } - default: - return false; - } - - if (!GPDataTypeHelper.ISPLAYERID(idPlayer)) - { - //ASSERT(ISPLAYERID(idPlayer)); - return false; - } - - //CECGameSession* pSession = g_pGame.GetGameSession(); - - if (idPlayer == m_pHostPlayer.GetCharacterID()) - { - GetHostPlayer().SetPartExtendProps(iIndex, pData); - } - else - { - EC_ElsePlayer pPlayer = SeekOutElsePlayer(idPlayer); - if (pPlayer) - pPlayer.SetPartExtendProps(iIndex, pData); - } - - return true; - } - - // Get player candidates whom can be auto-selected by 'TAB' key - public void TabSelectCandidates(int idCurSel, List aCands) - { - CECHostPlayer pHost = GetHostPlayer(); - if (pHost == null) - return; - - // Note: IsSkeletonReady() check is commented out in Unity codebase - // if (!pHost.IsSkeletonReady()) - // { - // // Only when IsSkeletonReady() is true, GetDistToHost() is valid - // return; - // } - - // Trace all Else Players - lock (m_csPlayerTab) - { - foreach (var kvp in m_PlayerTab) - { - EC_ElsePlayer pPlayer = kvp.Value; - if (pPlayer == null) - continue; - - if (!pPlayer.IsSelectable() || - pPlayer.IsDead() || - pPlayer.GetCharacterID() == idCurSel || - pHost.AttackableJudge(pPlayer.GetCharacterID(), false) != 1) - continue; - - float fDist = pPlayer.GetDistToHost(); - if (fDist > EC_RoleTypes.EC_TABSEL_DIST || !pHost.CanSafelySelectWith(fDist)) - continue; // Target is too far - - aCands.Add(pPlayer); - } - } - } - } - } - public struct EC_PLAYERLOADRESULT - { - public uint dwValidMask; - public CECModel pPlayerModel; - public int iShape; - public CECModel pDummyModel; - public CECModel pPetModel; - /* CECFace pFaceModel; - A3DShader pBodyShaders[3];*/ - public CECModel pFlyNviagteModel; - //CECPlayer::EquipsLoadResult EquipResult; - }; +using BrewMonster; +using BrewMonster.Managers; +using BrewMonster.Network; +using BrewMonster.Scripts; +using CSNetwork; +using CSNetwork.GPDataType; +using CSNetwork.Protocols; +using CSNetwork.Protocols.RPCData; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using TMPro; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace PerfectWorld.Scripts.Managers +{ + namespace BrewMonster.Managers + { + [Serializable] + public class EC_ManPlayer : IMsgHandler + { + Dictionary m_UkPlayerTab = new Dictionary(); + Dictionary m_PlayerTab = new Dictionary(); + private readonly object m_csPlayerTab = new object(); + CECHostPlayer m_pHostPlayer; + public int HandlerId => (int)MANAGER_INDEX.MAN_PLAYER; + public bool ProcessMessage(ECMSG Msg) + { + if (Msg.iSubID == 0) + { + if (CECGameRun.Instance == null) return true; + if (CECGameRun.Instance.GetHostPlayer() == null) return true; + // Duel invite: show accept/reject popup (origin uses MSG_PM_DUELOPT with command DUEL_RECV_REQUEST = 214) + // dwParam2 is ushort (pCmdHeader from GameSession); use Convert to avoid InvalidCastException when unboxing + if ((int)Msg.dwMsg == EC_MsgDef.MSG_PM_DUELOPT && Convert.ToInt32(Msg.dwParam2) == CommandID.DUEL_RECV_REQUEST && Msg.dwParam1 is byte[] pDataBuf && pDataBuf.Length >= 4) + { + int inviterId = BitConverter.ToInt32(pDataBuf, 0); + var gameUI = EC_Game.GetGameRun()?.GetUIManager()?.GetInGameUIMan(); + if (gameUI != null) + { + var dlg = gameUI.GetDialog("DlgDuelInvite"); + var showMethod = dlg?.GetType().GetMethod("ShowForDuelInvite", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance, null, new[] { typeof(int) }, null); + showMethod?.Invoke(dlg, new object[] { inviterId }); + } + } + CECGameRun.Instance.GetHostPlayer().ProcessMessage(Msg); + } + else if (Msg.iSubID < 0) + { + switch ((int)Msg.dwMsg) + { + case int value when value == EC_MsgDef.MSG_PM_PLAYERINFO: + { + OnMsgPlayerInfo(Msg); + break; + } + case int value when value == EC_MsgDef.MSG_PM_PLAYERMOVE: + { + OnMsgPlayerMove(Msg); + break; + } + case int value when value == EC_MsgDef.MSG_PM_PLAYERLEVELUP: + OnMsgPlayerLevelUp(Msg); + break; + case int value when value == EC_MsgDef.MSG_PM_PLAYERSTOPMOVE: + { + OnMsgPlayerStopMove(Msg); + break; + } + case int value when value == EC_MsgDef.MSG_PM_PLAYERBASEINFO: + case int value2 when value2 == EC_MsgDef.MSG_PM_FACTION_PVP_MASK_MODIFY: + case int value3 when value3 == EC_MsgDef.MSG_PM_PLAYERATKRESULT: + case int value4 when value4 == EC_MsgDef.MSG_PM_CASTSKILL: + case int value5 when value5 == EC_MsgDef.MSG_PM_ENCHANTRESULT: + case int value6 when value6 == EC_MsgDef.MSG_PM_PLAYERDOEMOTE: + case int value7 when value7 == EC_MsgDef.MSG_PM_PLAYERGATHER: + case int value8 when value8 == EC_MsgDef.MSG_PM_PLAYERFLY: + case int value9 when value9 == EC_MsgDef.MSG_PM_PLAYERMOUNT: + TransmitMessage(Msg); + break; + case int value when value == EC_MsgDef.MSG_PM_PLAYERDIED: + OnMsgPlayerDied(Msg); + break; + case int value when value == EC_MsgDef.MSG_PM_PLAYERREVIVE: + OnMsgPlayerRevive(Msg); + break; + case int value when value == EC_MsgDef.MSG_PM_PLAYERRUNOUT: + OnMsgPlayerRunOut(Msg); + break; + case EC_MsgDef.MSG_PM_PICKUPMATTER: + OnMsgPlayerPickupMatter(Msg); + break; + case EC_MsgDef.MSG_PM_PLAYEREXTPROP: + OnMsgPlayerExtProp(Msg); + break; + case int value when value == EC_MsgDef.MSG_PM_PLAYERDUELOPT: + // PLAYER_DUEL_START (229): server may send to both duelists; update host state if host is one of them + // dwParam2 is ushort (pCmdHeader from GameSession); use Convert to avoid InvalidCastException when unboxing + if (Convert.ToInt32(Msg.dwParam2) == CommandID.PLAYER_DUEL_START && Msg.dwParam1 is byte[] duelData && duelData.Length >= 8) + { + var pHost = GetHostPlayer(); + if (pHost != null) + { + var method = pHost.GetType().GetMethod("OnMsgPlayerDuelStart", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(byte[]) }, null); + method?.Invoke(pHost, new object[] { duelData }); + } + } + TransmitMessage(Msg); + break; + } + } + else + { + + } + return true; + } + + private void OnMsgPlayerDied(ECMSG Msg) + { + cmd_player_died pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + EC_ElsePlayer pPlayer = SeekOutElsePlayer(pCmd.idPlayer); + //if (pPlayer && !pPlayer.IsAboutToDie()) + // pPlayer.Killed(pCmd.idKiller); + + EventBus.PublishChannel(pPlayer.GetCharacterID(), new ClearComActFlagAllRankNodesEvent(true)); + pPlayer.PlayAction((int)PLAYER_ACTION_TYPE.ACT_GROUNDDIE); + } + + private void OnMsgPlayerRevive(ECMSG Msg) + { + cmd_player_revive pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + CECHostPlayer pHost = GetHostPlayer(); + if (pHost && pHost.GetCharacterID() == pCmd.idPlayer) + { + pHost.HandleRevive(pCmd.sReviveType, pCmd.pos); + } + else + { + EC_ElsePlayer pPlayer = SeekOutElsePlayer(pCmd.idPlayer); + if (pPlayer) + pPlayer.HandleRevive(pCmd.sReviveType, pCmd.pos); + } + } + + public void OnMsgPlayerInfo(ECMSG Msg) + { + int iHostID = Convert.ToInt32(Msg.dwParam3); + int lenghtByte = Marshal.SizeOf(); + byte[] byteArray = new byte[lenghtByte]; + byte[] data = (byte[])Msg.dwParam1; + for (int i = 0; i < lenghtByte; i++) + { + byteArray[i] = data[i]; + } + int cid = BitConverter.ToInt32(byteArray); + int commandID = Convert.ToInt32(Msg.dwParam2); + switch (commandID) + { + case CommandID.PLAYER_INFO_1: + case CommandID.PLAYER_ENTER_WORLD: + case CommandID.PLAYER_ENTER_SLICE: + { + if (cid != iHostID) + { + info_player_1 info_Player_1 = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + ElsePlayerEnter(info_Player_1, commandID); + } + break; + } + case CommandID.SELF_INFO_1: + cmd_self_info_1 info = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + HostPlayerInfo1(info); + break; + case CommandID.PLAYER_INFO_2: break; + case CommandID.PLAYER_INFO_3: break; + case CommandID.PLAYER_INFO_4: break; + + case CommandID.PLAYER_INFO_1_LIST: + { + byte[] bytes = (byte[])Msg.dwParam1; + cmd_player_info_1_list pCmd = GPDataTypeHelper.FromBytes(bytes); + if (pCmd.count == 0) break; + + List a1 = new(); + List a2 = new(); + List a3 = new(); + List a4 = new(); + + + int lenght = Marshal.SizeOf(); + int preSize = 0; + byte[] pDataBuf = null; + preSize += Marshal.SizeOf(); + for (int i = 0; i < pCmd.count; i++) + { + pDataBuf = new byte[lenght]; + for (int j = 0; j < pDataBuf.Length; j++) + { + pDataBuf[j] = bytes[preSize + j]; + } + info_player_1 Info = GPDataTypeHelper.FromBytes(pDataBuf); + if (Info.cid != iHostID) + { + EC_ElsePlayer pPlayer = ElsePlayerEnter(Info, commandID); + + if (pPlayer != null) + { + if (!pPlayer.m_bCustomReady) + { + if (!pPlayer.m_bBaseInfoReady) + a2.Add(Info.cid); + else + a3.Add(Info.cid); + } + + // Is equipment data ready + if (!pPlayer.m_bEquipReady) + a1.Add(Info.cid); + + // TODO: Implement get faction + /*/ + // Get faction info + if (pPlayer.GetFactionID() && !g_pGame.GetFactionMan().GetFaction(pPlayer.GetFactionID())) + { + int i(0); + for (i = 0; i < a4.GetSize(); i++) + if (a4[i] == pPlayer.GetFactionID()) + break; + + if (i == a4.GetSize()) + a4.Add(pPlayer.GetFactionID()); + } + //*/ + } + } + + // Calculate player info data size and skip it + int iSize = Marshal.SizeOf(); + if ((Info.state & PlayerNPCState.GP_STATE_ADV_MODE) != 0) + iSize += Marshal.SizeOf() * 2; + + if ((Info.state & PlayerNPCState.GP_STATE_SHAPE) != 0) + iSize += Marshal.SizeOf(); + + if ((Info.state & PlayerNPCState.GP_STATE_EMOTE) != 0) + iSize += Marshal.SizeOf(); + + if ((Info.state & PlayerNPCState.GP_STATE_EXTEND_PROPERTY) != 0) + iSize += Marshal.SizeOf() * NumberDWORDsPlayerNPC.OBJECT_EXT_STATE_COUNT; + + if ((Info.state & PlayerNPCState.GP_STATE_FACTION) != 0) + iSize += (Marshal.SizeOf() + Marshal.SizeOf()); + + if ((Info.state & PlayerNPCState.GP_STATE_BOOTH) != 0) + iSize += Marshal.SizeOf(); + + //// Parse effect data + if ((Info.state & PlayerNPCState.GP_STATE_EFFECT) != 0) + { + iSize += Marshal.SizeOf(); + byte byNum = bytes[preSize + iSize]; + + if (byNum > 0) + iSize += byNum * Marshal.SizeOf(); + } + + if ((Info.state & PlayerNPCState.GP_STATE_PARIAH) != 0) + iSize += Marshal.SizeOf(); + + if ((Info.state & PlayerNPCState.GP_STATE_IN_MOUNT) != 0) + iSize += Marshal.SizeOf() + Marshal.SizeOf(); + + // Parse bind data + if ((Info.state & PlayerNPCState.GP_STATE_IN_BIND) != 0) + iSize += Marshal.SizeOf() + Marshal.SizeOf(); + + // Parse spouse data + if ((Info.state & PlayerNPCState.GP_STATE_SPOUSE) != 0) + iSize += Marshal.SizeOf(); + + if ((Info.state & PlayerNPCState.GP_STATE_EQUIPDISABLED) != 0) + iSize += Marshal.SizeOf(); + + if ((Info.state & PlayerNPCState.GP_STATE_PLAYERFORCE) != 0) + iSize += Marshal.SizeOf(); + + if ((Info.state & PlayerNPCState.GP_STATE_MULTIOBJ_EFFECT) != 0) + { + int effectNum = Marshal.ReadInt32(bytes, iSize);//*(int*)(pDataBuf + iSize); + iSize += Marshal.SizeOf(); + + if (effectNum > 0) + iSize += effectNum * (Marshal.SizeOf() + Marshal.SizeOf()); + } + + if ((Info.state & PlayerNPCState.GP_STATE_COUNTRY) != 0) + iSize += Marshal.SizeOf(); + if ((Info.state2 & PlayerNPCState2.GP_STATE2_TITLE) != 0) + iSize += Marshal.SizeOf(); + if ((Info.state2 & PlayerNPCState2.GP_STATE2_REINCARNATION) != 0) + iSize += Marshal.SizeOf(); + if ((Info.state2 & PlayerNPCState2.GP_STATE2_REALM) != 0) + iSize += Marshal.SizeOf(); + if ((Info.state2 & PlayerNPCState2.GP_STATE2_FACTION_PVP_MASK) != 0) + iSize += Marshal.SizeOf(); + + // Goblin refine data + if ((Info.state & PlayerNPCState.GP_STATE_GOBLINREFINE) != 0) + iSize += Marshal.SizeOf(); + + //pDataBuf += iSize; + + preSize += iSize; + } + + // Get both base info and custom data + if (a2.Count > 0) + UnityGameSession.GetRoleBaseInfo(a2.Count, a2); + + // Only get custom data + if (a3.Count > 0) + UnityGameSession.GetRoleCustomizeData(a3.Count, a3); + break; + } + } + } + + public void OnMsgPlayerMove(ECMSG Msg) + { + int iHostID = Convert.ToInt32(Msg.dwParam3); + cmd_object_move pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + if (pCmd.use_time == 0) + return; + if (pCmd.id != iHostID) + { + EC_ElsePlayer pPlayer = SeekOutElsePlayer(pCmd.id); + if (pPlayer) + { + pPlayer.MoveTo(pCmd); + } + } + } + + private bool OnMsgPlayerLevelUp(ECMSG Msg) + { + cmd_level_up pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + CECHostPlayer pHost = GetHostPlayer(); + if (pCmd.id == pHost.GetCharacterID()) + { + pHost.LevelUp(); + } + else + { + EC_ElsePlayer pPlayer = SeekOutElsePlayer(pCmd.id); + if (pPlayer) + pPlayer.LevelUp(); + } + + return true; + } + + public bool HostPlayerInfo1(cmd_self_info_1 info) + { + //bool isDoneWorldRender = false; + //bool isDoneNPCRender = false; + //Action actLoadChar = () => + //{ + // if (!isDoneNPCRender || !isDoneWorldRender) + // { + // return; + // } + // GameController.Instance.InitCharacter(info); + //}; + //string nameScene = "NPCRender"; + //UnityGameSession.Instance.LoadScene(nameScene, LoadSceneMode.Single, (value) => + //{ + // isDoneNPCRender = value; + // actLoadChar?.Invoke(); + //}); + //nameScene = "WorldRender"; + //UnityGameSession.Instance.LoadScene(nameScene, LoadSceneMode.Additive, (value) => + //{ + // isDoneWorldRender = value; + // actLoadChar?.Invoke(); + //}); + //UnityGameSession.Instance.LoadScene("HoangTest", LoadSceneMode.Single, (value) => + //{ + // isDoneWorldRender = value; + // actLoadChar?.Invoke(); + //}); + CECGameRun.Instance.InitCharacter(info); + return true; + } + + public EC_ElsePlayer ElsePlayerEnter(info_player_1 info, int iCmd) + { + // If this player's id is in unknown table, remove it because this player + // won't be unknown anymore + if (m_UkPlayerTab.TryGetValue(info.cid, out int value)) + { + if (value != 0) // Pair.second != 0 + { + m_UkPlayerTab.Remove(info.cid); + } + } + + int iAppearFlag = (iCmd == (int)CommandID.PLAYER_ENTER_WORLD) ? + (int)PlayerAppearFlag.APPEAR_ENTERWORLD : (int)PlayerAppearFlag.APPEAR_RUNINTOVIEW; + + // Has player been in active player table ? + EC_ElsePlayer pPlayer = GetElsePlayer(info.cid); + if (pPlayer != null) + { + // Check if the GameObject is still valid (not destroyed) + if (pPlayer.gameObject != null) + { + // This player has existed in player table, call special initial function + pPlayer.Init(info, iAppearFlag); + return pPlayer; + } + else + { + // GameObject was destroyed but entry still exists in table - clean it up + lock (m_csPlayerTab) + { + m_PlayerTab.Remove(info.cid); + } + } + } + + // Create a new player + if (!(pPlayer = CreateElsePlayer(info, iAppearFlag))) + { + return null; + } + lock (m_csPlayerTab) + { + if (m_PlayerTab.ContainsKey(info.cid)) + { + m_PlayerTab[info.cid] = pPlayer; + } + else + { + m_PlayerTab.Add(info.cid, pPlayer); + } + } + return pPlayer; + } + + private EC_ElsePlayer CreateElsePlayer(info_player_1 info, int iAppearFlag) + { + GameObject ob = CECGameRun.Instance.InitCharacter(info); + EC_ElsePlayer elsePlayer = ob.AddComponent(); + + elsePlayer.Init(info, iAppearFlag); + return elsePlayer; + } + + public EC_ElsePlayer GetElsePlayer(int cid, long dwBornStamp = 0) + { + lock (m_csPlayerTab) + { + if (!m_PlayerTab.TryGetValue(cid, out var player)) + return null; + if (dwBornStamp != 0) + { + //TO DO: fix after GetBornStamp() is create in code + //if (player.GetBornStamp() != dwBornStamp) + return null; + } + + return player; + } + } + + private EC_ElsePlayer SeekOutElsePlayer(int cid) + { + if (!m_PlayerTab.TryGetValue(cid, out var player)) + { + // Couldn't find this else player, put it into unknown player table + m_UkPlayerTab[cid] = cid; + return null; + } + + return player; + } + + + public bool OnMsgPlayerStopMove(ECMSG Msg) + { + cmd_object_stop_move pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + EC_ElsePlayer pPlayer = SeekOutElsePlayer(pCmd.id); + if (pPlayer) + pPlayer.StopMoveTo(pCmd); + return true; + } + + public bool OnMsgPlayerRunOut(ECMSG Msg) + { + int id = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + CECHostPlayer pHost = GetHostPlayer(); + if (pHost != null && id == pHost.GetCharacterID()) + return true; // Don't process if it's the host player + + ElsePlayerLeave(id, false); + return true; + } + + public void OnMsgPlayerPickupMatter(ECMSG Msg) + { + cmd_matter_pickup pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + // CECHostPlayer pHost = GetHostPlayer(); + + + EC_ManMessageMono.Instance.EC_ManMatter.RemoveMatter(pCmd.matter_id); + + } + + private void ElsePlayerLeave(int cid, bool bExitGame) + { + EC_ElsePlayer pPlayer = SeekOutElsePlayer(cid); + if (pPlayer == null) + return; + + // Remove from active player table + lock (m_csPlayerTab) + { + m_PlayerTab.Remove(cid); + } + + // If this player is selected by host, cancel the selection + CECHostPlayer pHost = GetHostPlayer(); + if (pHost != null && pHost.GetSelectedTarget() == cid) + pHost.SelectTarget(0); + + UnityGameSession.Instance.GetC2SCmdCache().RemovePlayerBaseInfo(cid); + // Release player resource + if (pPlayer != null) + { + if (bExitGame) + { + // Player exited game - handle differently if needed + } + // Destroy the player GameObject + if (pPlayer.gameObject != null) + UnityEngine.Object.Destroy(pPlayer.gameObject); + } + } + + 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 Initialize() + { + + } + public bool TransmitMessage(ECMSG Msg) + { + int cid = 0; + var host = CECGameRun.Instance.GetHostPlayer(); + + switch (Msg.dwMsg) + { + /*case long value when value == EC_MsgDef.MSG_PM_PLAYEREQUIPDATA: + if (msg.dwParam2 == S2C.EQUIP_DATA) + cid = ((cmd_equip_data)msg.dwParam1).idPlayer; + else // EQUIP_DATA_CHANGED + cid = ((cmd_equip_data_changed)msg.dwParam1).idPlayer; + break;*/ + + case long value when value == EC_MsgDef.MSG_PM_PLAYERBASEINFO: + cid = (int)((playerbaseinfo_re)Msg.dwParam1).Player.id; + // Xoá khỏi cache + UnityGameSession.Instance.GetC2SCmdCache().RemovePlayerBaseInfo(cid); + break; + + /*case long value when value == EC_MsgDef.MSG_PM_PLAYERCUSTOM: + cid = ((GetCustomData_Re)msg.dwParam1).cus_roleid; + break; + + case long value when value == EC_MsgDef.MSG_PM_PLAYERFLY: + if (msg.dwParam2 == S2C.OBJECT_TAKEOFF) + cid = ((cmd_object_takeoff)msg.dwParam1).object_id; + else + cid = ((cmd_object_landing)msg.dwParam1).object_id; + break;*/ + case long value when value == EC_MsgDef.MSG_PM_PLAYERATKRESULT: + cmd_object_atk_result pCmdAtk = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + cid = pCmdAtk.attacker_id; + break; + case long value when value == EC_MsgDef.MSG_PM_CASTSKILL: + switch (Convert.ToInt32(Msg.dwParam2)) + { + case int value2 when value2 == CommandID.OBJECT_CAST_SKILL: cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).caster; break; + case int value2 when value2 == CommandID.OBJECT_CAST_INSTANT_SKILL: cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).caster; break; + case int value2 when value2 == CommandID.OBJECT_CAST_POS_SKILL: cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).caster; break; + case int value2 when value2 == CommandID.SKILL_INTERRUPTED: cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).caster; break; + case int value2 when value2 == CommandID.PLAYER_CAST_RUNE_SKILL: cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).caster; break; + case int value2 when value2 == CommandID.PLAYER_CAST_RUNE_INSTANT_SKILL: cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).caster; break; + } + break; + case long value when value == EC_MsgDef.MSG_PM_PLAYERDOEMOTE: + + if (Convert.ToInt32(Msg.dwParam2) == CommandID.OBJECT_DO_EMOTE) + { + cmd_object_do_emote pCmdDoEmote = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + cid = pCmdDoEmote.id; + } + else // Msg.dwParam2 == OBJECT_EMOTE_RESTORE + { + // cid = ((cmd_object_emote_restore*)Msg.dwParam1)->id; + } + + break; + case long value when value == EC_MsgDef.MSG_PM_ENCHANTRESULT: + + cid = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1).caster; + break; + case long value when value == EC_MsgDef.MSG_PM_PLAYERGATHER: + + if (Convert.ToInt32(Msg.dwParam2) == CommandID.PLAYER_GATHER_START) + { + cmd_player_gather_start cmdPlayerGatherStart = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + cid = cmdPlayerGatherStart.pid; + } + else if (Convert.ToInt32(Msg.dwParam2) == CommandID.PLAYER_GATHER_STOP) + { + cmd_player_gather_stop cmdPlayerGatherStop = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + cid = cmdPlayerGatherStop.pid; + } + else + { + cmd_mine_gathered cmdMineGathered = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + cid = cmdMineGathered.player_id; + } + + break; + case long value when value == EC_MsgDef.MSG_PM_PLAYERFLY: + + if (Convert.ToInt32(Msg.dwParam2) == CommandID.OBJECT_TAKEOFF) + cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).object_id; + else // OBJECT_LANDING + cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).object_id; + + break; + case long value when value == EC_MsgDef.MSG_PM_PLAYERMOUNT: + cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).id; + break; + // ⚠️ Các case khác cũng tương tự, chỉ việc lấy ra đúng trường id / caster / user ... + // Do quá dài nên bạn có thể copy dần từng case từ C++ sang. + + default: + System.Diagnostics.Debug.Assert(false, "Unknown message"); + return false; + } + + if (cid == 0) + { + System.Diagnostics.Debug.Assert(false, "cid = 0"); + return false; + } + + if (host != null && cid == host.GetCharacterID()) + { + host.ProcessMessage(Msg); + } + else + { + var elsePlayer = SeekOutElsePlayer(cid); + if (elsePlayer != null) + elsePlayer.ProcessMessage(Msg); + } + + return true; + } + + // Get a player (may be host or else player) by id + public CECPlayer GetPlayer(int cid, uint dwBornStamp = 0) + { + CECHostPlayer pHost = GetHostPlayer(); + if (pHost && pHost.GetCharacterID() == cid) + return pHost; + else + return GetElsePlayer(cid, dwBornStamp); + } + + public CECHostPlayer GetHostPlayer() + { + return CECGameRun.Instance.GetHostPlayer(); + } + + /// Returns character IDs of all other players in the world (excluding host). Used e.g. by DlgArrangeTeam for nearby list. + public List GetOtherPlayerCharacterIds() + { + var list = new List(); + CECHostPlayer pHost = GetHostPlayer(); + int idHost = pHost != null ? pHost.GetCharacterID() : 0; + lock (m_csPlayerTab) + { + foreach (int cid in m_PlayerTab.Keys) + { + if (cid != idHost) + list.Add(cid); + } + } + return list; + } + + public bool OnMsgPlayerExtProp(ECMSG Msg) + { + object pData; + int idPlayer, iIndex; + + switch (Msg.dwParam2) + { + case CommandID.PLAYER_EXT_PROP_BASE: + { + cmd_pep_base pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + idPlayer = pCmd.idPlayer; + pData = pCmd.ep_base; + iIndex = (int)ExtendPropertyClass.EXTPROPIDX_BASE; + break; + } + case CommandID.PLAYER_EXT_PROP_MOVE: + { + cmd_pep_move pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + idPlayer = pCmd.idPlayer; + pData = pCmd.ep_move; + iIndex = (int)ExtendPropertyClass.EXTPROPIDX_MOVE; + break; + } + case CommandID.PLAYER_EXT_PROP_ATK: + { + cmd_pep_attack pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + idPlayer = pCmd.idPlayer; + pData = pCmd.ep_attack; + iIndex = (int)ExtendPropertyClass.EXTPROPIDX_ATTACK; + break; + } + + case CommandID.PLAYER_EXT_PROP_DEF: + { + cmd_pep_def pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); + idPlayer = pCmd.idPlayer; + pData = pCmd.ep_def; + iIndex = (int)ExtendPropertyClass.EXTPROPIDX_DEF; + break; + } + default: + return false; + } + + if (!GPDataTypeHelper.ISPLAYERID(idPlayer)) + { + //ASSERT(ISPLAYERID(idPlayer)); + return false; + } + + //CECGameSession* pSession = g_pGame.GetGameSession(); + + if (idPlayer == m_pHostPlayer.GetCharacterID()) + { + GetHostPlayer().SetPartExtendProps(iIndex, pData); + } + else + { + EC_ElsePlayer pPlayer = SeekOutElsePlayer(idPlayer); + if (pPlayer) + pPlayer.SetPartExtendProps(iIndex, pData); + } + + return true; + } + + // Get player candidates whom can be auto-selected by 'TAB' key + public void TabSelectCandidates(int idCurSel, List aCands) + { + CECHostPlayer pHost = GetHostPlayer(); + if (pHost == null) + return; + + // Note: IsSkeletonReady() check is commented out in Unity codebase + // if (!pHost.IsSkeletonReady()) + // { + // // Only when IsSkeletonReady() is true, GetDistToHost() is valid + // return; + // } + + // Trace all Else Players + lock (m_csPlayerTab) + { + foreach (var kvp in m_PlayerTab) + { + EC_ElsePlayer pPlayer = kvp.Value; + if (pPlayer == null) + continue; + + if (!pPlayer.IsSelectable() || + pPlayer.IsDead() || + pPlayer.GetCharacterID() == idCurSel || + pHost.AttackableJudge(pPlayer.GetCharacterID(), false) != 1) + continue; + + float fDist = pPlayer.GetDistToHost(); + if (fDist > EC_RoleTypes.EC_TABSEL_DIST || !pHost.CanSafelySelectWith(fDist)) + continue; // Target is too far + + aCands.Add(pPlayer); + } + } + } + } + } + public struct EC_PLAYERLOADRESULT + { + public uint dwValidMask; + public CECModel pPlayerModel; + public int iShape; + public CECModel pDummyModel; + public CECModel pPetModel; + /* CECFace pFaceModel; + A3DShader pBodyShaders[3];*/ + public CECModel pFlyNviagteModel; + //CECPlayer::EquipsLoadResult EquipResult; + }; } \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Players/EC_ElsePlayer.cs b/Assets/PerfectWorld/Scripts/Players/EC_ElsePlayer.cs index 65595d0b06..5ae94088b4 100644 --- a/Assets/PerfectWorld/Scripts/Players/EC_ElsePlayer.cs +++ b/Assets/PerfectWorld/Scripts/Players/EC_ElsePlayer.cs @@ -51,6 +51,7 @@ namespace BrewMonster public void Init(info_player_1 Info, int iAppearFlag) { SetUpPlayer(); + m_iCID = (int)CECObject.Class_ID.OCID_ELSEPLAYER; m_dwResFlags = (uint)PlayerResourcesReadyFlag.RESFG_ALL; m_pEPWorkMan = new CECEPWorkMan(this); diff --git a/Assets/Scripts/CECGameRun.cs b/Assets/Scripts/CECGameRun.cs index 889b974591..c54f1b1d93 100644 --- a/Assets/Scripts/CECGameRun.cs +++ b/Assets/Scripts/CECGameRun.cs @@ -205,6 +205,13 @@ public partial class CECGameRun CECPlayer.InitStaticRes(); hostPlayer = ObjectSpawner.Instance.InstantiateObject(_playerPrefab, setThisAsParent: true).AddComponent(); hostPlayer.InitCharacter(info); + + if (hostPlayer != null) + { + var t = Type.GetType("BrewMonster.UI.SelectedTargetHUDController, Assembly-CSharp"); + if (t != null && hostPlayer.GetComponent(t) == null) + hostPlayer.gameObject.AddComponent(t); + } } public CECMonster GetMonster() { diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index f0a4a1d6de..481d4509b4 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -622,6 +622,7 @@ namespace BrewMonster case int value when value == EC_MsgDef.MSG_HST_NEWTEAMMEM: OnMsgHstNewTeamMem(Msg); break; case int value when value == EC_MsgDef.MSG_HST_TEAMMEMBERDATA: OnMsgHstTeamMemberData(Msg); break; case int value when value == EC_MsgDef.MSG_HST_CONTINUECOMBOSKILL: OnMsgContinueComboSkill(Msg); break; + case int value when value == EC_MsgDef.MSG_PM_DUELOPT: OnMsgHstDuelOpt(Msg); break; } /*if (bActionStartSkill) @@ -714,6 +715,76 @@ namespace BrewMonster catch { } } + /// Update host duel state from S2C duel commands (MSG_PM_DUELOPT). + private void OnMsgHstDuelOpt(ECMSG Msg) + { + int cmdId = Convert.ToInt32(Msg.dwParam2); + byte[] data = Msg.dwParam1 as byte[]; + int idOpp = 0; + if (data != null && data.Length >= 4) + idOpp = BitConverter.ToInt32(data, 0); + switch (cmdId) + { + case CommandID.DUEL_PREPARE: + m_pvp.iDuelState = Duel_state.DUEL_ST_PREPARE; + m_pvp.idDuelOpp = idOpp; + m_pvp.iDuelTimeCnt = data != null && data.Length >= 8 ? BitConverter.ToInt32(data, 4) : 3000; + break; + case CommandID.HOST_DUEL_START: + m_pvp.iDuelState = Duel_state.DUEL_ST_INDUEL; + m_pvp.idDuelOpp = idOpp; + m_pvp.iDuelTimeCnt = 0; + // Origin: server must be notified of force-attack (PVP) so it accepts attacks on the duel opponent + NotifyServerForceAttack(true); + break; + case CommandID.DUEL_STOP: + case CommandID.DUEL_RESULT: + m_pvp.iDuelState = Duel_state.DUEL_ST_STOPPING; + m_pvp.iDuelTimeCnt = data != null && data.Length >= 8 ? BitConverter.ToInt32(data, 4) : 3000; + if (data != null && data.Length >= 12) + m_pvp.iDuelRlt = BitConverter.ToInt32(data, 8); + NotifyServerForceAttack(false); + break; + case CommandID.DUEL_CANCEL: + case CommandID.DUEL_REJECT_REQUEST: + m_pvp.iDuelState = Duel_state.DUEL_ST_NONE; + m_pvp.idDuelOpp = 0; + m_pvp.iDuelTimeCnt = 0; + NotifyServerForceAttack(false); + break; + } + } + + /// Called when MSG_PM_PLAYERDUELOPT (229) is received; server may send duel start to both participants. If host is one of the two ids, set duel state. + public void OnMsgPlayerDuelStart(byte[] data) + { + if (data == null || data.Length < 8) return; + int id1 = BitConverter.ToInt32(data, 0); + int id2 = BitConverter.ToInt32(data, 4); + int cid = m_PlayerInfo.cid; + if (cid == id1) + { + m_pvp.iDuelState = Duel_state.DUEL_ST_INDUEL; + m_pvp.idDuelOpp = id2; + m_pvp.iDuelTimeCnt = 0; + NotifyServerForceAttack(true); + } + else if (cid == id2) + { + m_pvp.iDuelState = Duel_state.DUEL_ST_INDUEL; + m_pvp.idDuelOpp = id1; + m_pvp.iDuelTimeCnt = 0; + NotifyServerForceAttack(true); + } + } + + /// Origin: notify server of force-attack (PVP) state so it accepts/rejects attacks on players. Call when duel starts (true) or ends (false). + private void NotifyServerForceAttack(bool bForceAttack) + { + byte refuseBless = EC_Utility.glb_BuildRefuseBLSMask(); + UnityGameSession.c2s_SendCmdNotifyForceAttack(glb_BuildPVPMask(bForceAttack), refuseBless); + } + private void OnMsgContinueComboSkill(ECMSG Msg) { bool bMeleeing = ((int)Msg.dwParam1 == 1); @@ -2604,10 +2675,50 @@ 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); + + // Parse and apply single-item update (e.g. after embed) so UI reflects new item info without relog + if (data != null && data.Length >= 22) + { + byte byPackage = data[0]; + byte bySlot = data[1]; + int tid = BitConverter.ToInt32(data, 2); + int expireDate = BitConverter.ToInt32(data, 6); + int state = BitConverter.ToInt32(data, 10); + uint count = BitConverter.ToUInt32(data, 14); + ushort crc = BitConverter.ToUInt16(data, 18); + ushort contentLength = BitConverter.ToUInt16(data, 20); + byte[] content = null; + if (contentLength > 0 && data.Length >= 22 + contentLength) + { + content = new byte[contentLength]; + Buffer.BlockCopy(data, 22, content, 0, contentLength); + } + + try + { + var item = EC_IvtrItem.CreateItem(tid, expireDate, (int)count); + item.Package = byPackage; + item.Slot = bySlot; + item.State = state; + item.Crc = crc; + item.Content = content; + + var inv = GetInventory(byPackage); + if (inv != null) + { + inv.SetItem(bySlot, item); + if (byPackage == InventoryConst.IVTRTYPE_EQUIPPACK) + UpdateEquipSkins(); + } + } + catch (Exception ex) + { + Debug.LogWarning($"[Inventory] OWN_ITEM_INFO apply failed: {ex.Message}"); + } + } break; } } @@ -2979,6 +3090,7 @@ namespace BrewMonster public override void SetUpPlayer() { base.SetUpPlayer(); + m_iCID = (int)CECObject.Class_ID.OCID_HOSTPLAYER; m_IncantCnt = new CECCounter(); m_IncantCnt.SetPeriod(1000); @@ -3234,8 +3346,6 @@ namespace BrewMonster //if (!EC_Game.GetGameRun().GetWorld().GetObject(idTarget, 1)) // return false; - bool bStartNewWork = false; - bool bUseAutoPF = false; //CECPlayerWrapper* pWrapper = CECAutoPolicy::GetInstance().GetPlayerWrapper(); //if (CECAutoPolicy::GetInstance().IsAutoPolicyEnabled() && pWrapper.GetAttackError() >= 2) @@ -3253,12 +3363,10 @@ namespace BrewMonster return false; // Host is attacking the target pWorkTrace = (CECHPWorkTrace)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT); - bStartNewWork = true; } else if (m_pWorkMan.CanStartWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT)) { pWorkTrace = (CECHPWorkTrace)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT); - bStartNewWork = true; } if (pWorkTrace != null) @@ -3268,14 +3376,20 @@ namespace BrewMonster bUseAutoPF); pWorkTrace.SetMoveCloseFlag(bMoreClose); - if (bStartNewWork) - m_pWorkMan.StartWork_p1(pWorkTrace); + // Always start/re-start so trace runs (reuse path only updated target; work may be queued not current) + m_pWorkMan.StartWork_p1(pWorkTrace); return true; } return false; } + /// Use host's m_pvp (we update it from S2C duel packets). Base IsInDuel() reads CECPlayer.m_pvp which is never set. + public new bool IsInDuel() { return m_pvp.iDuelState == Duel_state.DUEL_ST_INDUEL; } + + /// Duel opponent character id when in duel; 0 otherwise. Used by HPWork so trace/melee send correct PVP mask. + public int GetDuelOpponentId() { return m_pvp.idDuelOpp; } + public int AttackableJudge(int idTarget, bool bForceAttack) { if (CannotAttack()) @@ -3368,19 +3482,22 @@ namespace BrewMonster } } } - // TO DO: fix later - //else if (GPDataTypeHelper.ISPLAYERID(idTarget)) - //{ - // // Check duel at first - // if (m_pvp.iDuelState == Duel_state.DUEL_ST_INDUEL && m_pvp.idDuelOpp == idTarget) - // return 1; - // else if (m_pvp.iDuelState == Duel_state.DUEL_ST_STOPPING && m_pvp.idDuelOpp == idTarget) - // return 0; + else if (GPDataTypeHelper.ISPLAYERID(idTarget)) + { + // Duel: can attack only duel opponent while in duel (origin: attack but not kill) + if (m_pvp.iDuelState == Duel_state.DUEL_ST_INDUEL && m_pvp.idDuelOpp == idTarget) + return 1; + if (m_pvp.iDuelState == Duel_state.DUEL_ST_STOPPING && m_pvp.idDuelOpp == idTarget) + return 0; - // // In sanctuary we cannot attack other players - // if (m_bInSanctuary) - // return 0; + // In sanctuary we cannot attack other players + if (m_bInSanctuary) + return 0; + // Other player: no attack when not in duel (full PVP/free PVP/battle camp can be ported later) + iRet = 0; + } + // TO DO: full PVP branch ported from origin (commented below) // //ASSERT(pObject.GetClassID() == CECObject::OCID_ELSEPLAYER); // EC_ElsePlayer pPlayer = (EC_ElsePlayer)pObject; // ROLEBASICPROP bp = pPlayer.GetBasicProps(); @@ -4025,6 +4142,10 @@ namespace BrewMonster if (!pSkill.ReadyToCast()) return false; + // Duel: so trace and CastSkill send correct PVP mask when touching duel opp + if (IsInDuel() && idCastTarget == m_pvp.idDuelOpp) + bForceAttack = true; + if (CECCastSkillWhenMove.Instance.IsSkillSupported(pSkill.GetSkillID(), this) && m_pWorkMan.IsMovingToPosition() && m_pWorkMan.CanCastSkillImmediately(pSkill.GetSkillID())) @@ -4301,6 +4422,10 @@ namespace BrewMonster } } + // Duel: server accepts attack/skill on player only with PVP/force mask + if (IsInDuel() && idTarget == m_pvp.idDuelOpp) + bForceAttack = true; + //TODO: Check cast condition - method not yet implemented int iRet = CheckSkillCastCondition(m_pPrepSkill); if (iRet != 0) @@ -4687,7 +4812,6 @@ namespace BrewMonster public bool SelectTarget(int idTarget) { - //BMLogger.LogError("HoangDev: HostPlayer SelectTarget"); bool bRet = false; bool canDo = CanDo(ActionCanDo.CANDO_CHANGESELECT); bool canselect = CanSelectTarget(idTarget); @@ -4696,16 +4820,17 @@ namespace BrewMonster bRet = true; if (idTarget == 0) { - //BMLogger.LogError("HoangDev: HostPlayer Unsetlect npc"); UnityGameSession.c2s_CmdUnselect(); + m_idSelTarget = 0; + m_idUCSelTarget = 0; } else { - //BMLogger.LogError("HoangDev: HostPlayer setlect npc"); UnityGameSession.c2s_CmdSelectTarget(idTarget); + m_idSelTarget = idTarget; + m_idUCSelTarget = idTarget; } } - return bRet; } @@ -6546,7 +6671,10 @@ namespace BrewMonster MaxHealth = maxHealth; IDNPC = idnpc; } - }; + } + + /// Fired when selected target HUD should be hidden (deselect or switch). + public struct TargetHUDClearEvent { } public struct GNDINFO { @@ -7696,6 +7824,9 @@ namespace BrewMonster return false; } } + // Duel: server accepts skill on opponent only with PVP/force mask + if (IsInDuel() && m_idSelTarget == m_pvp.idDuelOpp) + bForceAttack = true; int idCastTarget = m_idSelTarget; int iTargetType = pSkill.GetTargetType(); diff --git a/Assets/Scripts/CECUIManager.cs b/Assets/Scripts/CECUIManager.cs index 1ad2df6744..4dfa5204e4 100644 --- a/Assets/Scripts/CECUIManager.cs +++ b/Assets/Scripts/CECUIManager.cs @@ -21,6 +21,7 @@ public class CECUIManager : MonoSingleton [SerializeField] private int currentTargetNPCID; CECGameUIMan gameUI; AUIManager aUIManager; + AUIDialog _dlgPlayerOptions; [SerializeField] private DialogScriptTableObject dialogResouce; [SerializeField] private Canvas canvasDlg; @@ -37,6 +38,7 @@ public class CECUIManager : MonoSingleton base.Awake(); EventBus.Subscribe(ShowUINPC); + EventBus.Subscribe(OnTargetHUDClear); EventBus.Subscribe(TryHideUINPC); EventBus.Subscribe(OnMessageBox); @@ -61,6 +63,7 @@ public class CECUIManager : MonoSingleton private void OnDestroy() { EventBus.Unsubscribe(ShowUINPC); + EventBus.Unsubscribe(OnTargetHUDClear); EventBus.Unsubscribe(TryHideUINPC); EventBus.Unsubscribe(OnMessageBox); } @@ -68,10 +71,17 @@ public class CECUIManager : MonoSingleton private void ShowUINPC(CECHostPlayer.NPCINFO obj) { npsUI.gameObject.SetActive(true); - npsUI.SetText($"{obj.CurrentHealth}/{obj.MaxHealth}", obj.Name, ""); - npsUI.SetHealthImage((float)obj.CurrentHealth / (float)obj.MaxHealth); + string hpText = obj.MaxHealth > 0 ? $"{obj.CurrentHealth}/{obj.MaxHealth}" : "-/-"; + npsUI.SetText(hpText, obj.Name ?? "", ""); + float fill = obj.MaxHealth > 0 ? (float)obj.CurrentHealth / (float)obj.MaxHealth : 1f; + npsUI.SetHealthImage(fill); currentTargetNPCID = obj.IDNPC; } + + private void OnTargetHUDClear(CECHostPlayer.TargetHUDClearEvent _) + { + npsUI.gameObject.SetActive(false); + } public CDlgQuickBar GetCDlgQuickBar() { return m_pDlgQuickBar1; @@ -394,6 +404,16 @@ public class CECUIManager : MonoSingleton return gameUI; } + /// Shows the player options menu for the given character. Requires a prefab with id "DlgPlayerOptions" in DialogScriptTableObject. + public void ShowPlayerOptionsDialog(int characterId) + { + if (characterId == 0 || canvasDlg == null) return; + var gui = GetInGameUIMan(); + if (_dlgPlayerOptions == null) + _dlgPlayerOptions = gui.GetDialog("DlgPlayerOptions"); + _dlgPlayerOptions?.ShowForPlayer(characterId); + } + /// /// Get the current target NPC ID (same as stored from NPCINFO event) ///