From 9c10751894745131f402d7c5a0452d63730b5d14 Mon Sep 17 00:00:00 2001 From: MinhHai Date: Tue, 3 Mar 2026 14:15:00 +0700 Subject: [PATCH 1/4] WIP: remove singleton of CECWorld, rename CECGameRun's file --- .../Scripts/Common/EC_PendingAction.cs | 12 +- .../Scripts/Common/TickInvoker.cs | 36 +++ .../Scripts/Common/TickInvoker.cs.meta | 3 + .../Scripts/Managers/CECNPCMan.cs | 2 +- .../Scripts/Managers/EC_ManMatter.cs | 2 +- .../PerfectWorld/Scripts/Move/CECHostMove.cs | 7 +- Assets/PerfectWorld/Scripts/Move/EC_CDR.cs | 2 +- .../Scripts/Network/CSNetwork/GameSession.cs | 47 ++- .../Scripts/Network/UnityGameSession.cs | 2 +- .../Scripts/Task/CECTaskInterface.cs | 2 +- .../Scripts/Task/UI/DlgNameLink.cs | 2 +- .../Scripts/Task/UI/DlgTaskTrace.cs | 8 +- .../Scripts/UI/Login/BtnBackToSelectRole.cs | 1 + Assets/PerfectWorld/Scripts/World/CECWorld.cs | 95 +++++- Assets/Scripts/CECGameRun.cs.meta | 2 - Assets/Scripts/CECHostPlayer.Combat.cs | 4 +- Assets/Scripts/CECHostPlayer.Skill.cs | 2 +- Assets/Scripts/CECHostPlayer.cs | 177 +++++++++- Assets/Scripts/EC_GameRun.GameState.cs | 285 ++++++++++++++++ Assets/Scripts/EC_GameRun.GameState.cs.meta | 3 + ...{CECGameRun_Task.cs => EC_GameRun.Task.cs} | 0 ...n_Task.cs.meta => EC_GameRun.Task.cs.meta} | 0 .../Scripts/{CECGameRun.cs => EC_GameRun.cs} | 306 ++++++++++++++++-- Assets/Scripts/EC_GameRun.cs.meta | 2 + 24 files changed, 953 insertions(+), 49 deletions(-) create mode 100644 Assets/PerfectWorld/Scripts/Common/TickInvoker.cs create mode 100644 Assets/PerfectWorld/Scripts/Common/TickInvoker.cs.meta delete mode 100644 Assets/Scripts/CECGameRun.cs.meta create mode 100644 Assets/Scripts/EC_GameRun.GameState.cs create mode 100644 Assets/Scripts/EC_GameRun.GameState.cs.meta rename Assets/Scripts/{CECGameRun_Task.cs => EC_GameRun.Task.cs} (100%) rename Assets/Scripts/{CECGameRun_Task.cs.meta => EC_GameRun.Task.cs.meta} (100%) rename Assets/Scripts/{CECGameRun.cs => EC_GameRun.cs} (77%) create mode 100644 Assets/Scripts/EC_GameRun.cs.meta diff --git a/Assets/PerfectWorld/Scripts/Common/EC_PendingAction.cs b/Assets/PerfectWorld/Scripts/Common/EC_PendingAction.cs index aedf88b999..f728b65f0e 100644 --- a/Assets/PerfectWorld/Scripts/Common/EC_PendingAction.cs +++ b/Assets/PerfectWorld/Scripts/Common/EC_PendingAction.cs @@ -109,7 +109,8 @@ public class CECPendingLogoutHalf : CECPendingAction bool bSuccess = false; if (IsInGame()) { - GetGameSession().SendPlayerLogout(PendingActionConstants._PLAYER_LOGOUT_HALF); + // GetGameSession().SendPlayerLogout(PendingActionConstants._PLAYER_LOGOUT_HALF); + UnityGameSession.ReturnToSelectRole(); bSuccess = true; } return bSuccess; @@ -128,7 +129,9 @@ public class CECPendingLogoutFull : CECPendingAction bool bSuccess = false; if (IsInGame()) { - GetGameSession().SendPlayerLogout(PendingActionConstants._PLAYER_LOGOUT_FULL); + // C++ code: GetGameSession()->SendPlayerLogout(PendingActionConstants::_PLAYER_LOGOUT_FULL); + // GetGameSession().SendPlayerLogout(PendingActionConstants._PLAYER_LOGOUT_FULL); + UnityGameSession.LogoutAccount(); bSuccess = true; } return bSuccess; @@ -147,8 +150,9 @@ public class CECPendingSellingRole : CECPendingAction bool bSuccess = false; if (IsInGame()) { - GetGameSession().SendPlayerLogout(PendingActionConstants._PLAYER_LOGOUT_HALF); - GetGameRun().SetSellingRoleID(GetHostPlayer().GetCharacterID()); + // GetGameSession().SendPlayerLogout(PendingActionConstants._PLAYER_LOGOUT_HALF); + // GetGameRun().SetSellingRoleID(GetHostPlayer().GetCharacterID()); + UnityGameSession.ReturnToSelectRole(); bSuccess = true; } return bSuccess; diff --git a/Assets/PerfectWorld/Scripts/Common/TickInvoker.cs b/Assets/PerfectWorld/Scripts/Common/TickInvoker.cs new file mode 100644 index 0000000000..4782b86b52 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Common/TickInvoker.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace BrewMonster.Scripts +{ + public interface ITickable + { + bool Tick(uint dwDeltaTime); + } + + + public class TickInvoker : MonoSingleton + { + List tickables = new List(); + + public void RegisterTickable(ITickable tickable) + { + if (!tickables.Contains(tickable)) + tickables.Add(tickable); + } + + public void UnregisterTickable(ITickable tickable) + { + tickables.Remove(tickable); + } + + void Update() + { + for (int i=0; i= fDist * 0.98f) { diff --git a/Assets/PerfectWorld/Scripts/Move/EC_CDR.cs b/Assets/PerfectWorld/Scripts/Move/EC_CDR.cs index eba939a961..be91ce5e95 100644 --- a/Assets/PerfectWorld/Scripts/Move/EC_CDR.cs +++ b/Assets/PerfectWorld/Scripts/Move/EC_CDR.cs @@ -896,7 +896,7 @@ namespace BrewMonster public static bool CollideWithTerrain(A3DVECTOR3 vStart, A3DVECTOR3 vDelta, ref float fFraction, ref A3DVECTOR3 vHitNormal, ref bool bStart) { - CECWorld pWorld = CECWorld.Instance; //g_pGame.GetGameRun().GetWorld(); + CECWorld pWorld = CECGameRun.Instance.GetWorld(); //g_pGame.GetGameRun().GetWorld(); A3DTerrain2 pTerrain = pWorld.GetTerrain(); bStart = false; float h1 = pTerrain.GetPosHeight(vStart, ref vHitNormal); diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs index 2cd0e8486d..c4ad077ffc 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs @@ -23,7 +23,6 @@ namespace CSNetwork { private static IPrefixedLogger _logger = LoggerFactory.GetLogger(nameof(GameSession)); // Get class-specific logger - private NetworkManager _networkManager; private string _host; private int _port; @@ -571,7 +570,8 @@ namespace CSNetwork break; case ProtocolType.PROTOCOL_GETUICONFIG_RE: OnPrtcGetConfigRe(protocol); break; case ProtocolType.PROTOCOL_PLAYERLOGOUT: - HandlePlayerLogout((playerlogout)protocol); + // HandlePlayerLogout((playerlogout)protocol); + OnPrtcPlayerLogout((playerlogout)protocol); break; case ProtocolType.PROTOCOL_AUTOTEAMSETGOAL_RE: @@ -590,11 +590,52 @@ namespace CSNetwork 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)); + // PostToUnityContext(() => PlayerLogoutReceived?.Invoke(protocol)); } + // 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); + } + } + + private void HandleServerDataSend(gamedatasend protocol) { int lenghtHeader = Marshal.SizeOf(); diff --git a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs index 2ea519aff2..6a26569be9 100644 --- a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs @@ -107,7 +107,7 @@ namespace BrewMonster.Network public static void LogoutAccount() { if (Instance == null) return; - _ = Instance.LogoutAndReturnAsync(outType: 0, entryTarget: LogoutFlowState.LoginEntryTarget.LoginUI, clearSavedCreds: true); + _ = Instance.LogoutAndReturnAsync(outType: 0, entryTarget: LogoutFlowState.LoginEntryTarget.LoginUI, clearSavedCreds: false); } public static void c2s_CmdCastSkill(int idSkill, byte byPVPMask, int iNumTarget, int[] aTargets) { diff --git a/Assets/PerfectWorld/Scripts/Task/CECTaskInterface.cs b/Assets/PerfectWorld/Scripts/Task/CECTaskInterface.cs index 81383cce4d..d96c1fd9aa 100644 --- a/Assets/PerfectWorld/Scripts/Task/CECTaskInterface.cs +++ b/Assets/PerfectWorld/Scripts/Task/CECTaskInterface.cs @@ -877,7 +877,7 @@ namespace BrewMonster.Scripts.Task pos[1] = vPos.y; pos[2] = vPos.z; } - var world = World.CECWorld.Instance; + var world = CECGameRun.Instance.GetWorld(); return world != null ? world.GetInstanceID() : 0; } diff --git a/Assets/PerfectWorld/Scripts/Task/UI/DlgNameLink.cs b/Assets/PerfectWorld/Scripts/Task/UI/DlgNameLink.cs index ef60fa8ee7..05034090cd 100644 --- a/Assets/PerfectWorld/Scripts/Task/UI/DlgNameLink.cs +++ b/Assets/PerfectWorld/Scripts/Task/UI/DlgNameLink.cs @@ -135,7 +135,7 @@ namespace BrewMonster.UI m_TargetPos = EC_Game.GetGameRun().GetHostPlayer().GetObjectCoordinates( idTarget, out m_Targets, ref bInTable); //todo: add map feature here. - if(!bInTable /*&& MAJOR_MAP== CECWorld.Instance.GetInstanceID())*/) + if(!bInTable /*&& MAJOR_MAP== CECGameRun.Instance.GetWorld().GetInstanceID())*/) { ATaskTemplMan pMan = EC_Game.GetTaskTemplateMan(); if(pMan.TryGetTaskNPCInfo((uint)idTarget, out NPC_INFO pInfo)) diff --git a/Assets/PerfectWorld/Scripts/Task/UI/DlgTaskTrace.cs b/Assets/PerfectWorld/Scripts/Task/UI/DlgTaskTrace.cs index b28b970217..d39953991f 100644 --- a/Assets/PerfectWorld/Scripts/Task/UI/DlgTaskTrace.cs +++ b/Assets/PerfectWorld/Scripts/Task/UI/DlgTaskTrace.cs @@ -152,10 +152,10 @@ namespace BrewMonster.Scripts.Task.UI { idWorld = gameRun.GetWorld().GetInstanceID(); } - else if (CECWorld.Instance != null) + else if (CECGameRun.Instance?.GetWorld() != null) { - idWorld = CECWorld.Instance.GetInstanceID(); - } + idWorld = CECGameRun.Instance.GetWorld().GetInstanceID(); + } if (IsShow()) { // �ѽ�������Ϣ��ʵʱ���� @@ -240,7 +240,7 @@ namespace BrewMonster.Scripts.Task.UI public void AppendCommand(int worldid, Task_Region[] pRegions, int size) { List instCoord = new List(), tempCoord = new List(); - int cur = CECWorld.Instance.GetInstanceID(); + int cur = CECGameRun.Instance?.GetWorld()?.GetInstanceID() ?? 161; CECInstance pInstance = EC_Game.GetGameRun().GetInstance(cur); string strCurMap = pInstance.GetPath(); // find the entrance of instance diff --git a/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs b/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs index 8468a0a5e7..94d1221059 100644 --- a/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs +++ b/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs @@ -14,6 +14,7 @@ namespace BrewMonster.UI return; UnityGameSession.ReturnToSelectRole(); + } } } diff --git a/Assets/PerfectWorld/Scripts/World/CECWorld.cs b/Assets/PerfectWorld/Scripts/World/CECWorld.cs index da885a8a56..194f64e27b 100644 --- a/Assets/PerfectWorld/Scripts/World/CECWorld.cs +++ b/Assets/PerfectWorld/Scripts/World/CECWorld.cs @@ -8,7 +8,7 @@ using UnityEngine; namespace BrewMonster.Scripts.World { - public class CECWorld : Singleton + public class CECWorld { protected A3DTerrain2 m_pA3DTerrain; CECOrnamentMan m_pOnmtMan; @@ -93,5 +93,98 @@ namespace BrewMonster.Scripts.World { return EC_ManMessageMono.Instance.GetECManPlayer; } + + + // Release object + public void Release() + { + // TODO: Release world resources in the correct order, currently just a placeholder to avoid compile errors. The actual release logic should closely follow the original C++ code to ensure proper cleanup and resource management. + // Release auto home + // ReleaseAutoHome(); // Not open comment, this same in C++ + // CECIntelligentRoute::Instance().Release(); // TODO + + // Release CDS object + // if (m_pCDS) + // { + // g_pGame->GetA3DEngine()->SetA3DCDS(NULL); + // delete m_pCDS; + // m_pCDS = NULL; + // } + + // Release nature objects + // ReleaseNatureObjects(); //TODO + + // Release scene before managers + // ReleaseScene(); + + // Release managers + // ReleaseManagers(); + + // force to release all loaded resource + // ThreadRemoveAllLoaded(); + + // if (m_pPrecinctSet) + // { + // delete m_pPrecinctSet; + // m_pPrecinctSet = NULL; + // } + // + // if (m_pRegionSet) + // { + // delete m_pRegionSet; + // m_pRegionSet = NULL; + // } + // + // if (m_pSceneLights) + // { + // delete m_pSceneLights; + // m_pSceneLights = NULL; + // } + // + // if (m_pAssureMove) + // { + // m_pAssureMove->ReleaseMap(); + // delete m_pAssureMove; + // m_pAssureMove = NULL; + // } + // + // m_dwBornStamp = 1; + } + + // Release current scene + void ReleaseScene() + { + // g_pGame->GetA3DEngine()->SetSky(NULL); + // + // A3DRELEASE(m_pScene); + // A3DRELEASE(m_pGrassLand); + // A3DRELEASE(m_pForest); + // A3DRELEASE(m_pA3DSky); + // + // // 1. force to exit loader thread + // ExitLoaderThread(); + // + // // 2. release manager + // for (int i=0; i < NUM_MANAGER; i++) + // { + // if (m_aManagers[i]) + // m_aManagers[i]->OnLeaveGameWorld(); + // } + // + // // 3. force to release all loaded resource + // ThreadRemoveAllLoaded(); + // + // // Release terrain after loading thread has been ended + // A3DRELEASE(m_pA3DTrnCuller); + // A3DRELEASE(m_pA3DTerrainWater); + // A3DRELEASE(m_pA3DTerrain); + // A3DRELEASE(m_pTerrainOutline); + // A3DRELEASE(m_pCloudManager); + // + // m_bWorldLoaded = false; + // + // // ɾ³ýËæ»úµØÍ¼ÐÅÏ¢ + // CECRandomMapProcess::DeleteAllRandomMapDataForSingleUser(); + } } } diff --git a/Assets/Scripts/CECGameRun.cs.meta b/Assets/Scripts/CECGameRun.cs.meta deleted file mode 100644 index 96acc12e8f..0000000000 --- a/Assets/Scripts/CECGameRun.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 5de219a5b9756ae4ebf01e2919b92cde \ No newline at end of file diff --git a/Assets/Scripts/CECHostPlayer.Combat.cs b/Assets/Scripts/CECHostPlayer.Combat.cs index 438abe7b3b..b94a466970 100644 --- a/Assets/Scripts/CECHostPlayer.Combat.cs +++ b/Assets/Scripts/CECHostPlayer.Combat.cs @@ -447,7 +447,7 @@ namespace BrewMonster // Ensure we are not under ground // 确保我们不在地下 A3DVECTOR3 vNormal = new A3DVECTOR3(); - float vTerrainHeight = CECWorld.Instance.GetTerrainHeight(vPos, ref vNormal); + float vTerrainHeight = CECGameRun.Instance.GetWorld().GetTerrainHeight(vPos, ref vNormal); if (vPos.y < vTerrainHeight) vPos.y = vTerrainHeight; @@ -507,7 +507,7 @@ namespace BrewMonster // Ensure we are not under ground // 确保我们不在地下 A3DVECTOR3 vNormal = new A3DVECTOR3(); - float vTerrainHeight = CECWorld.Instance.GetTerrainHeight(vPos, ref vNormal); + float vTerrainHeight = CECGameRun.Instance.GetWorld().GetTerrainHeight(vPos, ref vNormal); if (vPos.y < vTerrainHeight) vPos.y = vTerrainHeight; diff --git a/Assets/Scripts/CECHostPlayer.Skill.cs b/Assets/Scripts/CECHostPlayer.Skill.cs index 5274471353..75ff39b621 100644 --- a/Assets/Scripts/CECHostPlayer.Skill.cs +++ b/Assets/Scripts/CECHostPlayer.Skill.cs @@ -853,7 +853,7 @@ namespace BrewMonster break; // Get target object - CECObject pObject = CECWorld.Instance.GetObject(idTarget, 0); + CECObject pObject = CECGameRun.Instance.GetWorld().GetObject(idTarget, 0); if (pObject == null) break; A3DVECTOR3 vHostPos = EC_Utility.ToA3DVECTOR3(transform.position); // GetPos() diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index 63405d1aee..b021df3f98 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -747,7 +747,7 @@ namespace BrewMonster while (true) { A3DVECTOR3 refake = new A3DVECTOR3(); - float terrianHeight = CECWorld.Instance.GetTerrainHeight(vTargetPos, ref refake); + float terrianHeight = CECGameRun.Instance.GetWorld().GetTerrainHeight(vTargetPos, ref refake); if (terrianHeight > vTargetPos.y + 1E-4f) break; @@ -945,7 +945,7 @@ namespace BrewMonster bool bFound = false; vPos = default; - CECWorld world = CECWorld.Instance; + CECWorld world = CECGameRun.Instance.GetWorld(); if (world == null) { return false; @@ -1020,7 +1020,7 @@ namespace BrewMonster // ×¢Ò⣺vPos ±»µ÷Õûºó£¬ÓпÉÄÜ´¦ÓÚ͹°üÖУ»Ðè¼ì²é·µ»Ø¸ß¶Èµ÷ÕûÖµ£¬ÒÔ±ÜÃâµ÷Õû¹ý´ó (Beware of large adjustments placing us back into brushes) A3DVECTOR3 vTemp = new A3DVECTOR3(vPos); - CECWorld world = CECWorld.Instance; + CECWorld world = CECGameRun.Instance.GetWorld(); if (world == null) { return vTemp.y; @@ -3734,7 +3734,173 @@ namespace BrewMonster //private bool ISNPCID(int id) => ((id & 0x80000000) != 0) && ((id & 0x40000000) == 0); //private bool ISPLAYERID(int id) => id != 0 && (id & 0x80000000) == 0; //private bool ISMATTERID(int id) => ((id) & 0xC0000000) == 0xC0000000; - } + + // Release object + public void Release() + { + // TODO: Release all objects created by player, such as inventory, skills, etc. + // CECInstanceReenter::Instance().Clear(); + // CECShoppingItemsMover::Instance().Clear(); + // CECFashionShopManager::Instance().Clear(); + // CECShoppingManager::Instance().Clear(); + // CECUseUniversalTokenCommandManager::Instance().Clear(); + // CECUniversalTokenHTTPOSNavigatorTicketHandler::Instance().Clear(); + // RandMallShoppingManager::Instance().Release(); + // CECFactionPVPModel::Instance().Clear(); + // CECHostSkillModel::Instance().Release(); + // CECComboSkillState::Instance().Release(); + // CECPlayerLevelRankRealmChangeCheck::Instance().Release(); + // CECHostFashionEquipFromStorageSystem::Instance().Clear(); + // + // m_pSaveLifeTrigger = NULL; + // CECQuickBuyPopManager::Instance().ClearPolicies(); + // + // // Ïú»ÙPlayerWrapper + // CECAutoPolicy::GetInstance().OnLeaveWorld(); + // + // // Save favorite auction list first + // SaveFavorAucItems(); + // + // // Release duel images + // ReleaseDuelImages(); + // + // // Release sounds + // g_pGame->GetGameRun()->ReleaseSoundTable(); + // m_pCurMoveSnd = NULL; + // + // // Release friend manger + // if (m_pFriendMan) + // { + // delete m_pFriendMan; + // m_pFriendMan = NULL; + // } + // + // // Release pet corral + // if (m_pPetCorral) + // { + // delete m_pPetCorral; + // m_pPetCorral = NULL; + // } + // + // if (m_pPetWords) + // { + // delete m_pPetWords; + // m_pPetWords = NULL; + // } + // + // if (m_pForceMgr) + // { + // delete m_pForceMgr; + // m_pForceMgr = NULL; + // } + // + // if (m_pOnlineAwardCtrl) + // { + // delete m_pOnlineAwardCtrl; + // m_pOnlineAwardCtrl = NULL; + // } + // + // if (m_pOffShopCtrl) + // { + // delete m_pOffShopCtrl; + // m_pOffShopCtrl = NULL; + // } + // + // if (m_pAutoTeam) + // { + // delete m_pAutoTeam; + // m_pAutoTeam = NULL; + // } + // + // if (m_pChariot) + // { + // delete m_pChariot; + // m_pChariot = NULL; + // } + // + // int i; + // + // // Release all shortcuts + // for (i=0; i < NUM_HOSTSCSETS1; i++) + // A3DRELEASE(m_aSCSets1[i]); + // + // for (i=0; i < NUM_HOSTSCSETS2; i++) + // A3DRELEASE(m_aSCSets2[i]); + // + // for (i=0; i < NUM_SYSMODSETS; i++) + // A3DRELEASE(m_aSCSetSysMod[i]); + // + // // Release all inventories + // A3DRELEASE(m_pPack); + // A3DRELEASE(m_pEquipPack); + // A3DRELEASE(m_pTrashBoxPack); + // A3DRELEASE(m_pTrashBoxPack2); + // A3DRELEASE(m_pTrashBoxPack3); + // A3DRELEASE(m_pAccountBoxPack); + // A3DRELEASE(m_pGeneralCardPack); + // A3DRELEASE(m_pTaskPack); + // A3DRELEASE(m_pDealPack); + // A3DRELEASE(m_pEPDealPack); + // A3DRELEASE(m_pTaskInterface); + // A3DRELEASE(m_pSpritePortrait); + // A3DRELEASE(m_pBuyPack); + // A3DRELEASE(m_pSellPack); + // A3DRELEASE(m_pBoothSPack); + // A3DRELEASE(m_pBoothBPack); + // A3DRELEASE(m_pEPBoothSPack); + // A3DRELEASE(m_pEPBoothBPack); + // A3DRELEASE(m_pEPEquipPack); + // A3DRELEASE(m_pClientGenCardPack); + // + // for (i=0; i < NUM_NPCIVTR; i++) + // { + // A3DRELEASE(m_aNPCPacks[i]); + // } + // + // // Release all skills + // ReleaseSkills(); + // + // // Clear current combo skill + // ClearComboSkill(); + // + // if (m_pWorkMan) + // { + // delete m_pWorkMan; + // m_pWorkMan = NULL; + // } + // + // m_CameraCtrl.Release(); + // + // m_aTeamInvs.RemoveAll(); + // + // g_pGame->GetGFXCaster()->ReleaseGFXEx(m_pMoveTargetGFX); + // g_pGame->GetGFXCaster()->ReleaseGFXEx(m_pSelectedGFX); + // g_pGame->GetGFXCaster()->ReleaseGFXEx(m_pHoverGFX); + // g_pGame->GetGFXCaster()->ReleaseGFXEx(m_pFloatDust); + // + // m_pMoveTargetGFX = NULL; + // m_pSelectedGFX = NULL; + // m_pHoverGFX = NULL; + // m_pFloatDust = NULL; + // + // // Clear tab select table + // m_aTabSels.RemoveAll(false); + // + // m_aForceInfo.RemoveAll(); + // + // if (m_pActionSwitcher) + // { + // delete m_pActionSwitcher; + // m_pActionSwitcher = NULL; + // } + // + // CECQShopConfig::Instance().ClearBuyedItem(); + // + // A3DRELEASE(m_pNavigatePlayer); + // + // CECPlayer::Release(); + } + } public sealed class CECHPTraceSpellMatcher : CECHPWorkMatcher { public override bool Match(CECHPWork pWork, int priority, bool isDelayWork) @@ -3748,5 +3914,8 @@ namespace BrewMonster return trace.GetTraceReason() == Trace_reason.TRACE_SPELL; } } + + + } diff --git a/Assets/Scripts/EC_GameRun.GameState.cs b/Assets/Scripts/EC_GameRun.GameState.cs new file mode 100644 index 0000000000..8c502038d0 --- /dev/null +++ b/Assets/Scripts/EC_GameRun.GameState.cs @@ -0,0 +1,285 @@ + + using BrewMonster; + using UnityEngine; + + public partial class CECGameRun + { + int m_iGameState; // Game state + // Logout flag (C++: m_iLogoutFlag) + private int m_iLogoutFlag = -1; + + // Logout + public void Logout() + { + // ASSERT(m_iGameState == GS_GAME); + if (m_iGameState != (int)GameState.GS_GAME) + { + BMLogger.LogError($"Logout called but game state is not GS_GAME, current state: {m_iGameState}"); + return; + } + + // TODO: Check if we need to call OnLogout for UI and cross server here + // overlay::GTOverlay::Instance().Logout(); + // CECCrossServer::Instance().OnLogout(); + + bool bExitApp = false; + + // if (CECUIConfig::Instance().GetLoginUI().bAvoidLoginUI && m_iLogoutFlag != 1){ + if( 1 == 2 && m_iLogoutFlag != 1){ // TODO: check if we need to avoid login UI based on config and logout flag here + bExitApp = true; + }else if (m_iLogoutFlag == 0) // Exit application directly + { + bExitApp = true; + } + else if (m_iLogoutFlag == 1) // Logout game and re-select role + { + // TODO: Check if we need to send switch game for mini client here + // Origin C++ + // StartLogin(); + StartLogin(); + + // + // // ÏÂÔØÆ÷ÏìÓ¦Í˳öÓÎϷ״̬ + // if( g_pGame->GetConfigs()->IsMiniClient() ) + // CECMCDownload::GetInstance().SendSwitchGame(false); + // + // // Goto select role interface directly + // CECLoginUIMan* pLoginUIMan = m_pUIManager->GetLoginUIMan(); + // if (pLoginUIMan) + // { + // if(GetSellingRoleID() == 0) + // { + // pLoginUIMan->LaunchCharacter(); + // } + // + // g_pGame->GetGameSession()->ReLogin(true); + // pLoginUIMan->SetRoleListReady(false); + // if (!CECReconnect::Instance().IsReconnecting()){ + // CECReconnect::Instance().SetRoleID(0); + // } + // } + // else + // { + // ASSERT(pLoginUIMan); + // bExitApp = true; + // } + } + else if (m_iLogoutFlag == 2) // Logout game and goto login state + { + // TODO: Check if we need to send switch game for mini client here + // Origin C++ + // StartLogin(); + + // if (CECLoginUIMan* pLoginUIMan = m_pUIManager->GetLoginUIMan()){ + // g_pGame->GetGameRun()->SetSellingRoleID(0); + // g_pGame->GetGameSession()->ReLogin(false); + // if (CECCrossServer::Instance().IsWaitLogin()){ + // pLoginUIMan->LaunchCharacter(); + // pLoginUIMan->ChangeSceneByRole(); + // pLoginUIMan->ReclickLoginButton(); + // }else if (CECReconnect::Instance().IsReconnecting()){ + // pLoginUIMan->ChangeCameraByScene(CECLoginUIMan::LOGIN_SCENE_SELCHAR); + // pLoginUIMan->ReclickLoginButton(); + // } + } + else + { + // ASSERT(NULL); + bExitApp = true; + } + + + // if (m_pRandomMapProc) + // A3DRELEASE(m_pRandomMapProc); + + // if (bExitApp) + // { + // // Exit game application + // EndGameState(false); + // ::PostMessage(g_pGame->GetGameInit().hWnd, WM_QUIT, 0, 0); + // } + } + + // End current game state + void EndGameState(bool bReset = true/* true */) + { + if (m_iGameState == (int)GameState.GS_NONE) + return; + + int iCurState = m_iGameState; + m_iGameState = (int)GameState.GS_NONE; + + // TODO: Check if we need to call OnEndLoginState or OnEndGameState based on current state + if (iCurState == (int)GameState.GS_LOGIN) + OnEndLoginState(); + else if (iCurState == (int)GameState.GS_GAME) + OnEndGameState(); + + // Stop background sound and music + // CELBackMusic* pBackMusic = g_pGame->GetBackMusic(); + // if (pBackMusic) + // { + // pBackMusic->StopMusic(true, true); + // pBackMusic->StopBackSFX(); + // } + + // if (bReset) + // g_pGame.Reset(); + + } + + + // Start login interface + bool StartLogin() + { + // End current game state + EndGameState(); + + m_iGameState = (int)GameState.GS_LOGIN; + + // if( !CreateLoginWorld() ) + // { + // a_LogOutput(1, "CECGameRun::StartLogin, Failed to create login world."); + // return false; + // } + // + // // Change UI manager + // if (!m_pUIManager->ChangeCurUIManager(CECUIManager::UIMAN_LOGIN)) + // { + // a_LogOutput(1, "CECGameRun::StartLogin, Failed to change UI manager."); + // return false; + // } + // + // m_pUIManager->GetLoginUIMan()->LaunchPreface(); + // + // if (!m_pLogo){ + // m_pLogo = new A2DSprite; + // if (!m_pLogo->Init(g_pGame->GetA3DDevice(), "logo.dds", 0)){ + // A3DRELEASE(m_pLogo); + // }else{ + // m_pLogo->SetLinearFilter(true); + // } + // } + // if (af_IsFileExist("surfaces\\kr.dds")) + // { + // if (!m_pClassification){ + // m_pClassification = new A2DSprite; + // if (!m_pClassification->Init(g_pGame->GetA3DDevice(), "kr.dds", 0)){ + // A3DRELEASE(m_pClassification); + // }else{ + // m_pClassification->SetLinearFilter(true); + // } + // } + // } + // // Change cursor to default icon + // g_pGame->ChangeCursor(RES_CUR_NORMAL); + // // Discard current frame + // g_pGame->DiscardFrame(); + + return true; + } + + // End login state + void OnEndLoginState() + { + // Release UI module + // m_pUIManager.ChangeCurUIManager(-1); + + // Release World + // ReleaseLoginWorld(); + + // A3DRELEASE(m_pLogo); + // A3DRELEASE(m_pClassification); + } + + // End game state + void OnEndGameState() + { + ReleasePendingActions(); + + // Release UI module + // m_pUIManager.ChangeCurUIManager(-1); + + // Release shortcuts + ReleaseShortcuts(); + + // Release team manager + // A3DRELEASE(m_pTeamMan); + if (m_pTeamMan != null) + { + m_pTeamMan.Release(); + m_pTeamMan = null; + } + + // Release host player before world released + ReleaseHostPlayer(); + + // Release world + ReleaseWorld(); + + // Release message manager + // A3DRELEASE(m_pMessageMan); + + // g_pGame.ReleaseInGameRes(); + + // Return the default memory state + // m_pMemSimplify.OnEndGameState(); + // CECOptimize::Instance().OnEndGameState(); + } + + // Release shortcuts + void ReleaseShortcuts() + { + // A3DRELEASE(m_pNormalSCS); + // A3DRELEASE(m_pTeamSCS); + // A3DRELEASE(m_pTradeSCS); + // A3DRELEASE(m_pPoseSCS); + // A3DRELEASE(m_pFactionSCS); + + m_pNormalSCS = null; + m_pTeamSCS = null; + m_pTradeSCS = null; + m_pPoseSCS = null; + m_pFactionSCS = null; + } + + // Release host player + void ReleaseHostPlayer() + { + // C++ version: + // Release host player + // if (m_pHostPlayer) + // { + // m_pHostPlayer->Release(); + // delete m_pHostPlayer; + // m_pHostPlayer = NULL; + // } + + // Release host player + if (m_pHostPlayer) + { + m_pHostPlayer.Release(); + GameObject.Destroy(m_pHostPlayer.gameObject); + m_pHostPlayer = null; + } + } + + // Release world + void ReleaseWorld() + { + // m_pInputCtrl->ClearKBFilterStack(); + // m_pInputCtrl->ClearMouFilterStack(); + // + // g_pGame->GetViewport()->SwitchCamera(false); + + if (m_pWorld != null) + { + // if (m_pHostPlayer) + // m_pHostPlayer.SetPlayerMan(NULL); + + this.m_pWorld.Release(); + // delete m_pWorld; + m_pWorld = null; + } + } + } diff --git a/Assets/Scripts/EC_GameRun.GameState.cs.meta b/Assets/Scripts/EC_GameRun.GameState.cs.meta new file mode 100644 index 0000000000..492ed9131e --- /dev/null +++ b/Assets/Scripts/EC_GameRun.GameState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b039014c898e4659adb7c90cc570b637 +timeCreated: 1772439168 \ No newline at end of file diff --git a/Assets/Scripts/CECGameRun_Task.cs b/Assets/Scripts/EC_GameRun.Task.cs similarity index 100% rename from Assets/Scripts/CECGameRun_Task.cs rename to Assets/Scripts/EC_GameRun.Task.cs diff --git a/Assets/Scripts/CECGameRun_Task.cs.meta b/Assets/Scripts/EC_GameRun.Task.cs.meta similarity index 100% rename from Assets/Scripts/CECGameRun_Task.cs.meta rename to Assets/Scripts/EC_GameRun.Task.cs.meta diff --git a/Assets/Scripts/CECGameRun.cs b/Assets/Scripts/EC_GameRun.cs similarity index 77% rename from Assets/Scripts/CECGameRun.cs rename to Assets/Scripts/EC_GameRun.cs index 54f0468a02..1c0c5235d1 100644 --- a/Assets/Scripts/CECGameRun.cs +++ b/Assets/Scripts/EC_GameRun.cs @@ -13,10 +13,12 @@ using System.Collections.Generic; using System.Data; using System.Linq; using System.Threading.Tasks; +using PerfectWorld.Scripts.Shop; using Unity.Cinemachine; +using Unity.VisualScripting; using UnityEngine; -public partial class CECGameRun +public partial class CECGameRun : ITickable { private static CECGameRun instance; @@ -39,9 +41,8 @@ public partial class CECGameRun // private GameRunConfig _gameRunConfig; //[SerializeField] private Transform ground; - CECHostPlayer hostPlayer; + CECHostPlayer m_pHostPlayer; private CECWorld m_pWorld; - int m_iGameState; // Game state protected CECUIManager m_pUIManager; // UI manager @@ -50,13 +51,13 @@ public partial class CECGameRun #endregion - public CECWorld GetWorld() + public CECWorld GetWorld() { - if(m_pWorld == null) + if (m_pWorld == null) { - m_pWorld = CECWorld.Instance; + m_pWorld = new CECWorld(); } - return m_pWorld; + return m_pWorld; } public CECTeamMan GetTeamMan() { return m_pTeamMan; } @@ -77,9 +78,7 @@ public partial class CECGameRun // Cache for SaveConfigsToServer: last sent config data to skip duplicate sends private byte[] m_pCfgDataBuf; private int m_iCfgDataSize; - - // Logout flag (C++: m_iLogoutFlag) - private int m_iLogoutFlag = -1; + // Selling role ID for role trade (C++: SetSellingRoleID/GetSellingRoleID) private int m_iSellingRoleID; @@ -116,16 +115,19 @@ public partial class CECGameRun if (!m_InstTab.ContainsKey(161)) m_InstTab.Add(161, new CECInstance()); AddressableManager.Instance.OnDispose += Dispose; - m_pWorld = CECWorld.Instance; + if (m_pWorld == null) + m_pWorld = new CECWorld(); StartGame(0, Vector3.zero); - m_pWorld = CECWorld.Instance; m_pendingLogout = new CECPendingActionArray( false); + + TickInvoker.Instance.RegisterTickable(this); } private static void Dispose() { + TickInvoker.Instance.UnregisterTickable(instance); instance = null; } @@ -172,6 +174,102 @@ public partial class CECGameRun } init = true; + + // TODO: Implement the rest of the StartGame logic based on the original C++ code, including: + // End current game state + EndGameState(); + // + // memset(&m_WallowInfo, 0, sizeof(m_WallowInfo)); + m_iGameState = (int)GameState.GS_GAME; + // + // if (!g_pGame->LoadInGameRes()) + // { + // a_LogOutput(1, "CECGameRun::StartGame, Failed to call LoadInGameRes()."); + // return false; + // } + // + // // Create message manager + // if (!(m_pMessageMan = new CECMessageMan(this))) + // { + // glb_ErrorOutput(ECERR_NOTENOUGHMEMORY, "CECGameRun::StartGame", __LINE__); + // return false; + // } + // + // // Create default game world + // if (!JumpToInstance(idInst, vHostPos)) + // { + // a_LogOutput(1, "CECGameRun::StartGame, Failed to create game world."); + // return false; + // } + // + // // ÉèÖÿç·þ³É¹¦±êʶ£¬ÒÔÀûÓÚ CECGameUIMan ¸ù¾Ý¿ç·þ״̬×öÏàÓ¦³õʼ»¯ + // if (CECCrossServer::Instance().IsWaitLogin()){ + // CECCrossServer::Instance().OnLoginSuccess(); + // } + // if (CECReconnect::Instance().IsReconnecting()){ + // CECReconnect::Instance().OnReconnectSuccess(); + // } + // + // // Create host player + // if (!CreateHostPlayer()) + // { + // a_LogOutput(1, "CECGameRun::StartGame, Failed to create host player."); + // return false; + // } + // + // // Create team manager + // if (!(m_pTeamMan = new CECTeamMan)) + // { + // glb_ErrorOutput(ECERR_NOTENOUGHMEMORY, "CECGameRun::StartGame", __LINE__); + // return false; + // } + // + // // Reset faction manager + // g_pGame->GetFactionMan()->Release(false); + // + // // Create shortcuts + // if (!CreateShortcuts()) + // { + // a_LogOutput(1, "CECGameRun::StartGame, Failed to create shortcuts"); + // return false; + // } + // + // // Change UI manager + // if (!m_pUIManager->ChangeCurUIManager(CECUIManager::UIMAN_INGAME)) + // { + // a_LogOutput(1, "CECGameRun::StartGame, Failed to change UI manager."); + // return false; + // } + // m_pInputFilter->LoadHotKey(); + // + // CECGameUIMan* pGameUIMan = m_pUIManager->GetInGameUIMan(); + // if (pGameUIMan) + // pGameUIMan->ChangeWorldInstance(idInst); + // + // l_SaveCfgCnt.Reset(); + // + // // Change cursor to default icon + // g_pGame->ChangeCursor(RES_CUR_NORMAL); + // // Discard current frame + // g_pGame->DiscardFrame(); + // + // // Clear frame controller + // memset(&l_fc, 0, sizeof (l_fc)); + // + // // ³õʼ»¯ÍøÂçÑÓ³Ù²éѯ + // l_bFirstQuery = true; + // m_iInGameDelay = 0; + // l_DelayQueryCounter.Reset(); + // + // l_QueryServerTime.Reset(); + // + // // clear the selling id + // m_SellingRoleID = 0; + // + // // ÏÂÔØÆ÷ÏìÓ¦½øÈëÓÎϷ״̬ + // if( g_pGame->GetConfigs()->IsMiniClient() ) + // CECMCDownload::GetInstance().SendSwitchGame(true); + // return true; } @@ -192,9 +290,14 @@ public partial class CECGameRun } } + public CECGameRun() + { + m_iGameState = (int)GameState.GS_NONE; + } + public CECHostPlayer GetHostPlayer() { - return hostPlayer; + return m_pHostPlayer; } public void InitCharacter(cmd_self_info_1 info) { @@ -218,19 +321,19 @@ public partial class CECGameRun m_InstTab.Add(idInst, new CECInstance()); // Update global world instance id used by task checks - CECWorld.Instance?.SetInstanceID(idInst); + CECGameRun.Instance?.GetWorld()?.SetInstanceID(idInst); } } CECPlayer.InitStaticRes(); - hostPlayer = ObjectSpawner.Instance.InstantiateObject(_playerPrefab, setThisAsParent: true).AddComponent(); - hostPlayer.InitCharacter(info); + m_pHostPlayer = ObjectSpawner.Instance.InstantiateObject(_playerPrefab, setThisAsParent: true).AddComponent(); + m_pHostPlayer.InitCharacter(info); - if (hostPlayer != null) + if (m_pHostPlayer != null) { var t = Type.GetType("BrewMonster.UI.SelectedTargetHUDController, Assembly-CSharp"); - if (t != null && hostPlayer.GetComponent(t) == null) - hostPlayer.gameObject.AddComponent(t); + if (t != null && m_pHostPlayer.GetComponent(t) == null) + m_pHostPlayer.gameObject.AddComponent(t); } } public CECMonster GetMonster() @@ -926,7 +1029,172 @@ public partial class CECGameRun } public CECPendingActionArray GetPendingLogOut(){ return m_pendingLogout; } + + // Game tick routine + public bool Tick(uint dwDeltaTime) + { + // if (GetWallowInfo().anti_wallow_active && + // g_pGame->GetGameSession()->IsConnected() && + // GetGameState() == GS_GAME) + // { + // if (CECUIConfig::Instance().GetGameUI().nWallowHintType != CECUIConfig::GameUI::WHT_KOREA) + // { + // // ·À³ÁÃÔµ½3Сʱ£¬ÈôAU²»ÌßÈË£¨ÏûÏ¢¶ªÊ§£©£¬Ôò×Ô¶¯ÏÂÏß + // int stime = g_pGame->GetServerGMTTime(); + // int nTime = stime - GetWallowInfo().play_time; + // if (nTime >= 3 * 3600) + // { + // // ÒѾ­³¬¹ý3Сʱ + // g_pGame->GetGameSession()->SetBreakLinkFlag(CECGameSession::LBR_ANTI_WALLOW); + // } + // } + // } + // + // DWORD dwTickTime = a_GetTime(); + // + // CECGameSession* pSession = g_pGame->GetGameSession(); + // pSession->ProcessNewProtocols(); + // DWORD dwRealTime = g_pGame->GetRealTickTime(); + if (m_iLogoutFlag >= 0) + { + Logout(); + m_iLogoutFlag = -1; + } + + // CECReconnect::Instance().Tick(); + // + // if (m_pUIManager) + // m_bUIHasCursor = m_pUIManager->UIControlCursor(); + // else + // m_bUIHasCursor = false; + // + // // Deal input first + // if (m_pInputCtrl) + // m_pInputCtrl->Tick(); + // + // // Tick world + // if (!TickGameWorld(dwDeltaTime, g_pGame->GetViewport())) + // return false; + + // Tick UI + // if (m_pUIManager) + // m_pUIManager.Tick(); + + // // Tick GFX caster + // g_pGame->GetGFXCaster()->Tick(dwDeltaTime); + // + // // Tick GFX Manager + // g_pGame->GetA3DGFXExMan()->Tick(dwDeltaTime); + // + // // A3DEngine::TickAnimation trigger animation of many objects. + // // For example: A3DSky objects, GFX objects etc. + // static DWORD dwAnimTime = 0; + // dwAnimTime += dwDeltaTime; + // while (dwAnimTime >= TIME_TICKANIMATION) + // { + // dwAnimTime -= TIME_TICKANIMATION; + // g_pGame->GetA3DEngine()->TickAnimation(); + // } + // + // // Update ear position so that all 3D sounds' positions are correct + // static DWORD dwEarTime = 0; + // if ((dwEarTime += dwDeltaTime) >= TIME_UPDATEEAR) + // { + // dwEarTime -= TIME_UPDATEEAR; + // + // CECHostPlayer* pHostPlayer = NULL; + // if (m_pWorld) + // pHostPlayer = m_pWorld->GetHostPlayer(); + // + // A3DCamera * pCamera = g_pGame->GetViewport()->GetA3DCamera(); + // + // if (GetGameState() == CECGameRun::GS_GAME && pHostPlayer && pHostPlayer->HostIsReady()) + // { + // AM3DSoundDevice * pAM3DSoundDevice = g_pGame->GetA3DEngine()->GetAMSoundEngine()->GetAM3DSoundDevice(); + // A3DVECTOR3 vecDir = pCamera->GetDirH(); + // A3DVECTOR3 vecUp = A3DVECTOR3(0.0f, 1.0f, 0.0f); + // + // // Now we should adjust the 3d sound device's pos and orientation; + // if (pAM3DSoundDevice) + // { + // pAM3DSoundDevice->SetPosition(pHostPlayer->GetPos() + A3DVECTOR3(0.0f, 0.8f, 0.0f)); + // pAM3DSoundDevice->SetOrientation(vecDir, vecUp); + // pAM3DSoundDevice->UpdateChanges(); + // } + // } + // else + // g_pGame->GetViewport()->GetA3DCamera()->UpdateEar(); + // } + // + // // Tick Run-Time debug information + // g_pGame->GetRTDebug()->Tick(dwDeltaTime); + // + // // Save UI configs when time reached + // if (m_iGameState == GS_GAME && l_SaveCfgCnt.IncCounter(dwRealTime)) + // { + // l_SaveCfgCnt.Reset(); + // SaveConfigsToServer(); + // } + // + // l_StatCnt.IncCounter(dwDeltaTime); + // + // pSession->ClearOldProtocols(); + // + // DWORD dwCurrentTick = a_GetTime(); + // dwTickTime = (dwCurrentTick > dwTickTime) ? (dwCurrentTick - dwTickTime) : 0; + // l_Statistic.iTickTime = (int)dwTickTime; + // + // if (GetGameState() == CECGameRun::GS_GAME && l_fc.iAvgRdTime) + // { + // // Accumulate tick time + // l_fc.iTickCnt++; + // l_fc.iTickTime += (int)dwTickTime; + // } + // + // if (GetGameState() == GS_GAME && GetHostPlayer() && GetHostPlayer()->HostIsReady()) + // { + // if (l_bFirstQuery || l_DelayQueryCounter.IncCounter(dwDeltaTime)) + // { + // // µÚÒ»´Î²éѯ£¬»òÕß²éѯ¼ÆÊýÆ÷µ½µã + // + // // ·¢ËÍÍøÂçÓµ¼·²éѯЭÒé + // l_iDelayTimeStamp = timeGetTime(); + // g_pGame->GetGameSession()->c2s_CmdQueryNetworkDelay(l_iDelayTimeStamp); + // l_DelayQueryCounter.Reset(); + // l_bFirstQuery = false; + // } + // + // if (l_QueryServerTime.IncCounter(dwDeltaTime)) + // { + // g_pGame->GetGameSession()->c2s_CmdSendGetServerTime(); + // l_QueryServerTime.Reset(); + // } + // } + // + // m_pendingLogout.Update(dwDeltaTime); + // + // // ÓÅ»¯ÄÚ´æµÄÕ¼Óà + // m_pMemSimplify->Tick(dwDeltaTime); + // + // // ¸üÐÂÏÂÔØ×´Ì¬ + // CECMCDownload::GetInstance().Tick(dwDeltaTime); + // + // // ¸üÐÂ×Ô¶¯²ßÂÔ + // CECAutoPolicy::GetInstance().Tick(dwDeltaTime); + // + // if(m_pRandomMapProc) + // m_pRandomMapProc->Tick(dwDeltaTime); + // + // #ifdef _PROFILE_MEMORY + // g_TickMemoryHistory(); + // #endif + + return true; + } + + + } public enum GameState diff --git a/Assets/Scripts/EC_GameRun.cs.meta b/Assets/Scripts/EC_GameRun.cs.meta new file mode 100644 index 0000000000..6ff658e8ba --- /dev/null +++ b/Assets/Scripts/EC_GameRun.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d68cfcd6293664808ba733553314250a \ No newline at end of file From c637c164f997d8312555865533cda940a6f23bf0 Mon Sep 17 00:00:00 2001 From: MinhHai Date: Wed, 4 Mar 2026 16:04:28 +0700 Subject: [PATCH 2/4] WIP: wait SERVER response PROTOCOL_PLAYERLOGOUT then client execute logout logic --- .../Scripts/Common/EC_PendingAction.cs | 10 ++- .../Scripts/Debug/TestLogoutLogic.cs | 7 ++ .../Scripts/Debug/TestLogoutLogic.cs.meta | 3 + .../CSNetwork/C2SCommand/C2SCommand.cs | 1 + .../Scripts/Network/CSNetwork/GameSession.cs | 27 ++++++- .../Network/CSNetwork/NetworkManager.cs | 9 +++ .../CSNetwork/Protocols/SetUIConfig_Re.cs | 73 +++++++++++++++++++ .../Protocols/SetUIConfig_Re.cs.meta | 3 + .../Scripts/Network/UnityGameSession.cs | 9 ++- .../Scripts/UI/Login/BtnBackToSelectRole.cs | 4 +- Assets/Scripts/EC_GameRun.GameState.cs | 9 ++- 11 files changed, 142 insertions(+), 13 deletions(-) create mode 100644 Assets/PerfectWorld/Scripts/Debug/TestLogoutLogic.cs create mode 100644 Assets/PerfectWorld/Scripts/Debug/TestLogoutLogic.cs.meta create mode 100644 Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/SetUIConfig_Re.cs create mode 100644 Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/SetUIConfig_Re.cs.meta diff --git a/Assets/PerfectWorld/Scripts/Common/EC_PendingAction.cs b/Assets/PerfectWorld/Scripts/Common/EC_PendingAction.cs index f728b65f0e..fd1291ab28 100644 --- a/Assets/PerfectWorld/Scripts/Common/EC_PendingAction.cs +++ b/Assets/PerfectWorld/Scripts/Common/EC_PendingAction.cs @@ -109,8 +109,9 @@ public class CECPendingLogoutHalf : CECPendingAction bool bSuccess = false; if (IsInGame()) { - // GetGameSession().SendPlayerLogout(PendingActionConstants._PLAYER_LOGOUT_HALF); - UnityGameSession.ReturnToSelectRole(); + // TODO: currently, we logout logic != C++, need to implement properly + GetGameSession().c2s_SendCmdLogout(PendingActionConstants._PLAYER_LOGOUT_HALF); + // UnityGameSession.ReturnToSelectRole(); bSuccess = true; } return bSuccess; @@ -129,9 +130,10 @@ public class CECPendingLogoutFull : CECPendingAction bool bSuccess = false; if (IsInGame()) { + // TODO: currently, we logout logic != C++, need to implement properly // C++ code: GetGameSession()->SendPlayerLogout(PendingActionConstants::_PLAYER_LOGOUT_FULL); - // GetGameSession().SendPlayerLogout(PendingActionConstants._PLAYER_LOGOUT_FULL); - UnityGameSession.LogoutAccount(); + GetGameSession().c2s_SendCmdLogout(PendingActionConstants._PLAYER_LOGOUT_FULL); + // UnityGameSession.LogoutAccount(); bSuccess = true; } return bSuccess; diff --git a/Assets/PerfectWorld/Scripts/Debug/TestLogoutLogic.cs b/Assets/PerfectWorld/Scripts/Debug/TestLogoutLogic.cs new file mode 100644 index 0000000000..5b50128391 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Debug/TestLogoutLogic.cs @@ -0,0 +1,7 @@ +namespace BrewMonster.Scripts +{ + public class TestLogoutLogic : Singleton + { + public bool WasClientSendLogoutMessage; + } +} \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Debug/TestLogoutLogic.cs.meta b/Assets/PerfectWorld/Scripts/Debug/TestLogoutLogic.cs.meta new file mode 100644 index 0000000000..9b12ffbf43 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Debug/TestLogoutLogic.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: df29527a68a5442e814e73146a4ad68a +timeCreated: 1772608601 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs index 397c4817c6..f018981f06 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs @@ -285,6 +285,7 @@ namespace CSNetwork.C2SCommand public ushort useTime; // Time to use } + [StructLayout(LayoutKind.Sequential, Pack = 1)] // Player logout command public struct CMD_PlayerLogout { diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs index 7206bb4b77..19849e96b7 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs @@ -14,6 +14,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; +using BrewMonster.Scripts; using UnityEngine; using CommandID = CSNetwork.GPDataType.CommandID; @@ -83,6 +84,8 @@ namespace CSNetwork _networkManager.ProtocolReceived += OnProtocolReceived; _networkManager.ErrorOccurred += OnErrorOccurred; _networkManager.Disconnected += OnDisconnected; + + TestLogoutLogic.Instance.WasClientSendLogoutMessage = false; } public void SetLogPath(string path) @@ -511,7 +514,7 @@ namespace CSNetwork { _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}"); + BMLogger.Log($"[GameSession] Sending protocol: {protocol.GetType().Name} (Type: {protocol.GetPType()}) + {protocol.ToString} "); _networkManager.Send(protocol); complete?.Invoke(); } @@ -527,6 +530,7 @@ namespace CSNetwork private void OnProtocolReceived(Protocol protocol) { _logger.Log(LogType.Debug, $"Received protocol: {protocol.GetType().Name} (Type: {protocol.Type})"); + BMLogger.Log($"Received protocol: {protocol.GetType().Name} (Type: {protocol.Type})"); if (protocol is null) return; @@ -569,6 +573,7 @@ namespace CSNetwork 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); @@ -1547,12 +1552,14 @@ namespace CSNetwork /// - outType=1: back to select role /// - outType=0: logout account /// - public void SendPlayerLogout(int outType, Action complete = null) + 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) { @@ -1677,6 +1684,22 @@ namespace CSNetwork 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; diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs index 86ce0a9476..28383e9d49 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs @@ -6,6 +6,8 @@ using System.Threading; using System.Threading.Tasks; using System; using System.IO; +using BrewMonster; +using BrewMonster.Scripts; namespace CSNetwork { @@ -335,6 +337,13 @@ namespace CSNetwork break; } + if ( TestLogoutLogic.Instance != null && TestLogoutLogic.Instance.WasClientSendLogoutMessage ) + { + // BMLogger.Log(" MH: Receive Loop Exiting Early Due To Logout Message Sent"); + } + + BMLogger.Log($" MH: Received {bytesRead} bytes. Buffer Length before processing: {currentBufferLength}. Token: {token.GetHashCode()}"); + currentBufferLength += bytesRead; _receiveOctets.SetSize(bytesRead); diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/SetUIConfig_Re.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/SetUIConfig_Re.cs new file mode 100644 index 0000000000..f469d965a0 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/SetUIConfig_Re.cs @@ -0,0 +1,73 @@ +using System; + +namespace CSNetwork.Protocols +{ + // C++: class SetUIConfig_Re : public Protocol + public class SetUIConfig_Re : Protocol + { + // C++: int result; + public int result; + // C++: int roleid; + public int roleid; + // C++: unsigned int localsid; + public uint localsid; + + // C++: enum { PROTOCOL_TYPE = PROTOCOL_SETUICONFIG_RE }; + // C++: SetUIConfig_Re() { type = PROTOCOL_SETUICONFIG_RE; } + public SetUIConfig_Re() : base(ProtocolType.PROTOCOL_SETUICONFIG_RE) + { + } + + // C++: SetUIConfig_Re(int l_result,int l_roleid,unsigned int l_localsid) + // : result(l_result),roleid(l_roleid),localsid(l_localsid) { type = PROTOCOL_SETUICONFIG_RE; } + public SetUIConfig_Re(int l_result, int l_roleid, uint l_localsid) + : base(ProtocolType.PROTOCOL_SETUICONFIG_RE) + { + result = l_result; + roleid = l_roleid; + localsid = l_localsid; + } + + // C++: GNET::Protocol *Clone() const { return new SetUIConfig_Re(*this); } + public override Protocol Clone() => new SetUIConfig_Re + { + result = result, + roleid = roleid, + localsid = localsid + }; + + // C++: OctetsStream& marshal(OctetsStream & os) const { os << result; os << roleid; os << localsid; return os; } + public override void Marshal(OctetsStream os) + { + os.Write(result); + os.Write(roleid); + os.Write(localsid); + } + + // C++: const OctetsStream& unmarshal(const OctetsStream &os) { os >> result; os >> roleid; os >> localsid; return os; } + public override void Unmarshal(OctetsStream os) + { + result = os.ReadInt32(); + roleid = os.ReadInt32(); + localsid = os.ReadUInt32(); + } + + // C++: int PriorPolicy( ) const { return 1; } + public override int PriorPolicy() => 1; + + // C++: bool SizePolicy(size_t size) const { return size <= 64; } + public override bool SizePolicy(int size) => size <= 64; + + // C++: void Process(Manager *manager, Manager::Session::ID sid) { // TODO; #ifdef _TESTCODE ... #endif } + public override void Process(Manager mgr, uint sid) + { + // TODO +#if _TESTCODE + if (result == (int)BrewMonster.ErrCode.ERR_SUCCESS) + Protocol._logger.Debug("Set uiconfig successfully."); + else + Protocol._logger.Debug($"Set custom data failed. retcode={result}"); +#endif + } + } +} diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/SetUIConfig_Re.cs.meta b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/SetUIConfig_Re.cs.meta new file mode 100644 index 0000000000..bb5001462a --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/SetUIConfig_Re.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6d43c7ce793f4c3698ff104d0d66f328 +timeCreated: 1772523576 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs index d8afd79375..cacba8dc01 100644 --- a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs @@ -182,6 +182,7 @@ namespace BrewMonster.Network { // Tell LoginScene what to show next. LogoutFlowState.NextLoginEntry = entryTarget; + _gameSession.Disconnected -= OnUnexpectedDisconnect; if (clearSavedCreds) { @@ -197,11 +198,12 @@ namespace BrewMonster.Network // Mark this as an intentional disconnect to prevent showing error message _isIntentionalDisconnect = true; + // We call after receive LOGOUT(0) or LOGOUT(1) from server, but the server may choose to disconnect immediately or not. // Send LOGOUT(outType) like the original client. - _gameSession.SendPlayerLogout(outType); + // _gameSession.SendPlayerLogout(outType); // Wait briefly for server-driven disconnect. - await WaitForDisconnectAsync(timeoutMs: 4000); + await WaitForDisconnectAsync(timeoutMs: 100); } } catch (Exception ex) @@ -245,7 +247,8 @@ namespace BrewMonster.Network if (ui == null) continue; if (!ui.gameObject.scene.IsValid() || ui.gameObject.scene.name != LoginSceneName) continue; // Avoid hard dependency on method existence (merges may edit LoginScreenUI). - ui.SendMessage("ApplyLoginEntry", entryTarget, SendMessageOptions.DontRequireReceiver); + // ui.SendMessage("ApplyLoginEntry", entryTarget, SendMessageOptions.DontRequireReceiver); + ui.ApplyLoginEntry( entryTarget ); return; } } diff --git a/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs b/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs index 6371951b31..988d46d214 100644 --- a/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs +++ b/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs @@ -16,8 +16,8 @@ namespace BrewMonster.UI messageBoxType: MessageBoxType.YesButton ); - UnityGameSession.ReturnToSelectRole(); - + CECGameRun.Instance.GetPendingLogOut().AppendForSaveConfig(new CECPendingLogoutHalf()); + // UnityGameSession.ReturnToSelectRole(); } } } diff --git a/Assets/Scripts/EC_GameRun.GameState.cs b/Assets/Scripts/EC_GameRun.GameState.cs index 8c502038d0..10a124e690 100644 --- a/Assets/Scripts/EC_GameRun.GameState.cs +++ b/Assets/Scripts/EC_GameRun.GameState.cs @@ -1,5 +1,6 @@ using BrewMonster; + using BrewMonster.Network; using UnityEngine; public partial class CECGameRun @@ -33,6 +34,8 @@ } else if (m_iLogoutFlag == 1) // Logout game and re-select role { + UnityGameSession.ReturnToSelectRole(); + // TODO: Check if we need to send switch game for mini client here // Origin C++ // StartLogin(); @@ -66,6 +69,8 @@ } else if (m_iLogoutFlag == 2) // Logout game and goto login state { + UnityGameSession.LogoutAccount(); + // TODO: Check if we need to send switch game for mini client here // Origin C++ // StartLogin(); @@ -212,10 +217,10 @@ } // Release host player before world released - ReleaseHostPlayer(); + // ReleaseHostPlayer(); // Release world - ReleaseWorld(); + // ReleaseWorld(); // Release message manager // A3DRELEASE(m_pMessageMan); From f66036e708cc9a072da91f70086f41138bb592bd Mon Sep 17 00:00:00 2001 From: MinhHai Date: Thu, 5 Mar 2026 11:45:18 +0700 Subject: [PATCH 3/4] WIP: insert _receivedData into _decryptedOctets when not security --- .../Network/CSNetwork/NetworkManager.cs | 28 ++++- Assets/PerfectWorld/Scripts/_Doc.meta | 8 ++ .../_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md | 106 ++++++++++++++++++ .../PROTOCOL_PLAYERLOGOUT-bug-fix.md.meta | 7 ++ 4 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 Assets/PerfectWorld/Scripts/_Doc.meta create mode 100644 Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md create mode 100644 Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md.meta diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs index 28383e9d49..40ed5c2d9c 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs @@ -386,17 +386,19 @@ namespace CSNetwork int originalBlockLength = _decryptedOctets.Length; // Length before security int bytesConsumedFromOriginal = 0; // How many bytes of _receiveOctets are processed + Octets currentData = new Octets( + _receiveOctets.RawBuffer, + 0, + _receiveOctets.Size + ); + // 1. Apply Input Security (if active) if (securityApplied) { try { // Create a temporary Octets with the current raw buffer content - Octets currentData = new Octets( - _receiveOctets.RawBuffer, - 0, - _receiveOctets.Size - ); + // Update returns a NEW Octets object with processed data dataToProcess = currentIsec!.Update(currentData); _decryptedOctets.Insert(_decryptedOctets.Size, dataToProcess.ByteArray); @@ -416,8 +418,18 @@ namespace CSNetwork else { // No security, process directly from the receive buffer - dataToProcess = _receiveOctets; + // dataToProcess = _receiveOctets; + + // _decryptedOctets.Insert(_decryptedOctets.Size, currentData.ToArray()); + // dataToProcess = _decryptedOctets; + + // No security: append raw bytes to plaintext buffer so partial packets survive across reads. + byte[] slice = new byte[_receiveOctets.Size]; + Buffer.BlockCopy(_receiveOctets.RawBuffer, 0, slice, 0, _receiveOctets.Size); + _decryptedOctets.Insert(_decryptedOctets.Size, slice); } + + dataToProcess = _decryptedOctets; // 2. Process Protocols from 'dataToProcess' OctetsStream processingStream = new OctetsStream(dataToProcess); @@ -481,10 +493,14 @@ namespace CSNetwork // after successful decodes/consumptions. bytesConsumedFromOriginal = processingStream.Position; // Use final stream position } + + EndProcessing: _receiveOctets.SetSize(0); // 4. Compact the *original* _receiveOctets buffer + bytesConsumedFromOriginal = processingStream.Position; + originalBlockLength = _decryptedOctets.Length; CompactDecryptedBuffer(bytesConsumedFromOriginal, originalBlockLength); } diff --git a/Assets/PerfectWorld/Scripts/_Doc.meta b/Assets/PerfectWorld/Scripts/_Doc.meta new file mode 100644 index 0000000000..a0e4a188a1 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/_Doc.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5f58ceadd359a44bc8ab2da938c71e9c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md b/Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md new file mode 100644 index 0000000000..42258fbb5d --- /dev/null +++ b/Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md @@ -0,0 +1,106 @@ +# Bug: Client không nhận PROTOCOL_PLAYERLOGOUT sau khi logout + +## Tóm tắt + +- **Triệu chứng:** Server gửi `PROTOCOL_PLAYERLOGOUT` (69) sau khi client gửi lệnh logout, nhưng client **không nhận được** gói này (handler không chạy). +- **Nguyên nhân:** Logic xử lý buffer nhận trong `NetworkManager.ProcessBuffer()` sai: compact buffer decrypted theo từng “khối” thay vì theo số byte đã decode, khiến gói logout (thường là gói cuối trước khi server đóng) bị mất hoặc không bao giờ được decode. +- **Sửa:** Dùng một buffer plaintext tích lũy, decode hết các gói có thể rồi **chỉ consume đúng số byte đã decode** từ đầu buffer (`ConsumePlaintext`), nên gói 69 luôn được nhận. + +--- + +## 1. Timeline khi logout + +``` +Thời gian → + +Client: [Gửi logout] -------- đợi -------- [Socket đóng / Disconnect] + │ │ +Server: [Nhận logout] → [Gửi PROTOCOL_PLAYERLOGOUT] → [Đóng connection] + │ + └── Gói 69 (playerlogout) nằm trên đường truyền +``` + +Gói 69 **có tới** client (trong buffer TCP), nhưng **code xử lý buffer cũ làm mất** nó trước khi decode. + +--- + +## 2. Code cũ: buffer bị “cắt” sai + +Buffer giải mã `_decryptedOctets` coi như **một hàng ô** (mỗi ô = 1 byte): + +``` +Lần đọc 1 từ socket: +[ A ][ B ][ C ][ D ] ← decrypt xong, đẩy vào _decryptedOctets + └── decode được 1 gói (A,B) → "đã xử lý" + +Code cũ: compact dựa trên "originalBlockLength" = 4 (chỉ của khối này!). + +Lần đọc 2 (server gửi thêm rồi đóng): +[ C ][ D ][ E ][ F ][ G ] ← E,F,G = PROTOCOL_PLAYERLOGOUT (gói 69) + ↑ + C,D có thể bị coi nhầm / compact nhầm, hoặc E,F,G không được coi là "một gói đủ" +``` + +Kết quả: **Gói logout (E,F,G) không bao giờ được decode**, hoặc bị xóa khi compact. + +--- + +## 3. Code cũ vs Code mới (ý tưởng) + +- **Code cũ:** Mỗi lần đọc socket = **một khối riêng**. Compact theo “khối vừa decrypt” → số byte consume không khớp với toàn bộ plaintext → dữ liệu (gói 69) bị **cắt mất** hoặc **không đủ** để decode. +- **Code mới:** Luôn **nối** byte đã decrypt vào **một buffer dài**, decode từ đầu cho đến khi không đủ 1 gói, rồi **chỉ xóa đúng số byte đã decode** từ đầu. Phần còn lại giữ cho lần sau. + +--- + +## 4. Code mới: “một hàng dài”, consume từ trái sang + +``` +_decryptedOctets (sau nhiều lần append): + +[ A ][ B ][ C ][ D ][ E ][ F ][ G ][ H ] ... + └─ gói 1 ─┘ └── gói 2 ──┘ └─ chưa đủ 1 gói ─┘ + │ │ + decode decode + consume 2 consume 4 + +Sau consume: +[ E ][ F ][ G ][ H ] ... ← chỉ xóa 2+4 byte từ trái, giữ lại E,F,G,H... +``` + +Lần đọc tiếp (server gửi thêm rồi đóng): + +``` +[ E ][ F ][ G ][ H ][ I ][ J ] ← I,J = phần còn lại của gói 69 + └──────── PROTOCOL_PLAYERLOGOUT ────────┘ + decode được → fire event → consume 6 byte +``` + +Gói 69 luôn nằm trong **một hàng dài**, không bị cắt theo “khối” → client **nhận được** PROTOCOL_PLAYERLOGOUT. + +--- + +## 5. Bảng so sánh + +| | Code cũ | Code mới | +|---|--------|----------| +| **Buffer plaintext** | Coi từng “khối” nhỏ, compact theo khối | Một buffer dài, append liên tục | +| **Consume** | Theo “original block length” (dễ sai) | Đúng số byte đã decode (`stream.Position`) | +| **Gói nằm giữa 2 lần đọc** | Dễ bị mất / không đủ để decode | Giữ lại, lần sau decode tiếp | +| **Kết quả với gói logout** | Gói 69 thường bị mất | Gói 69 được decode và fire event | + +--- + +## 6. File thay đổi + +- **`NetworkManager.cs`**: `ProcessReceivedData`, `ProcessBuffer()`, thêm `ConsumePlaintext()`, bỏ `CompactDecryptedBuffer()` cũ. +- **`GameSession.cs`**: Thêm log trong `HandlePlayerLogout()`: `[GameSession] Received PROTOCOL_PLAYERLOGOUT (Result=..., Roleid=...)`. + +--- + +## 7. Cách kiểm tra + +Logout trong game và xác nhận log xuất hiện **trước** khi disconnect: + +```text +[GameSession] Received PROTOCOL_PLAYERLOGOUT (Result=..., Roleid=...) +``` diff --git a/Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md.meta b/Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md.meta new file mode 100644 index 0000000000..7ba870fbf9 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4caec71ab8d214b58b08e61470605ff6 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 56e385ab8808b509f858a051d1224567c7c0dd9b Mon Sep 17 00:00:00 2001 From: MinhHai Date: Fri, 6 Mar 2026 17:33:14 +0700 Subject: [PATCH 4/4] DONE: logout full and logout half login --- .../Scripts/Common/TickInvoker.cs | 7 ++- .../Scripts/Managers/CECNPCMan.cs | 45 +++++++++++++++++++ .../Scripts/Network/CSNetwork/GameSession.cs | 9 +++- .../Network/CSNetwork/NetworkManager.cs | 39 +++++----------- .../CSNetwork/Protocols/playerlogout.cs | 8 ++-- .../Scripts/Network/UnityGameSession.cs | 13 +++++- .../Scripts/Task/ATaskTemplMan.cs | 19 +++++--- .../Scripts/Task/UI/EmoteWindow.cs | 8 ++-- .../Scripts/UI/Login/BtnBackToSelectRole.cs | 44 +++++++++++++++--- .../Scripts/UI/Login/BtnLogoutAccount.cs | 2 + .../Scripts/UI/Login/LoginScreenUI.cs | 6 ++- Assets/Scripts/CECUIManager.cs | 28 ++++++++++-- Assets/Scripts/EC_GameRun.cs | 45 +++++-------------- 13 files changed, 182 insertions(+), 91 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/Common/TickInvoker.cs b/Assets/PerfectWorld/Scripts/Common/TickInvoker.cs index 4782b86b52..19ddeaf1b1 100644 --- a/Assets/PerfectWorld/Scripts/Common/TickInvoker.cs +++ b/Assets/PerfectWorld/Scripts/Common/TickInvoker.cs @@ -7,11 +7,14 @@ namespace BrewMonster.Scripts { bool Tick(uint dwDeltaTime); } - - public class TickInvoker : MonoSingleton { List tickables = new List(); + + protected override void Initialize() + { + DontDestroyOnLoad(gameObject); + } public void RegisterTickable(ITickable tickable) { diff --git a/Assets/PerfectWorld/Scripts/Managers/CECNPCMan.cs b/Assets/PerfectWorld/Scripts/Managers/CECNPCMan.cs index bc8b555692..20665baf85 100644 --- a/Assets/PerfectWorld/Scripts/Managers/CECNPCMan.cs +++ b/Assets/PerfectWorld/Scripts/Managers/CECNPCMan.cs @@ -908,6 +908,51 @@ public class CECNPCMan : IMsgHandler aCands.Add(pNPC); } } + + // Release manager + public void Release() + { + OnLeaveGameWorld(); + } + + // On leaving game world + bool OnLeaveGameWorld() + { + // Release all NPCs in active table + // NPCTable::iterator it = m_NPCTab.begin(); + // for (; it != m_NPCTab.end(); ++it) + foreach (var pNPC in m_NPCTab.Values) + { + ReleaseNPC(pNPC); + } + + m_NPCTab.Clear(); + + // Release all NPCs in disappear table + int i; + foreach (var pNPC in m_aDisappearNPCs) + { + ReleaseNPC(pNPC); + } + + m_aDisappearNPCs.Clear(); + + // Release all loaded models + // ACSWrapper csa(&m_csLoad); + // + // for (i=0; i < m_aLoadedModels.GetSize(); i++) + // { + // NPCMODEL* pInfo = m_aLoadedModels[i]; + // CECNPC::ReleaseNPCModel(pInfo->Ret); + // delete pInfo; + // } + // + // m_aLoadedModels.RemoveAll(); + // m_aMMNPCs.RemoveAll(false); + // m_aTabSels.RemoveAll(false); + + return true; + } } public struct NPCDiedEvent { diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs index 19849e96b7..40ad9c8ff5 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs @@ -552,7 +552,7 @@ namespace CSNetwork break; // Add cases for other protocols GameSession might need to handle case ProtocolType.PROTOCOL_SELECTROLE_RE: - HandleSelectRoleResponse((SelectRole_Re)protocol); + OnPrtcSelectRoleRe((SelectRole_Re)protocol); //_networkManager.IgnoreBytes = 2; break; case ProtocolType.PROTOCOL_CREATEROLE_RE: @@ -1235,11 +1235,15 @@ namespace CSNetwork } - private void HandleSelectRoleResponse(SelectRole_Re protocol) + 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) @@ -2144,5 +2148,6 @@ namespace CSNetwork { // TODO: C2SCommandFactory.CreateNPCSevCrossServerGetOutCmd() and SendProtocol } + } } \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs index 40ed5c2d9c..4211b348da 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs @@ -6,8 +6,6 @@ using System.Threading; using System.Threading.Tasks; using System; using System.IO; -using BrewMonster; -using BrewMonster.Scripts; namespace CSNetwork { @@ -337,13 +335,6 @@ namespace CSNetwork break; } - if ( TestLogoutLogic.Instance != null && TestLogoutLogic.Instance.WasClientSendLogoutMessage ) - { - // BMLogger.Log(" MH: Receive Loop Exiting Early Due To Logout Message Sent"); - } - - BMLogger.Log($" MH: Received {bytesRead} bytes. Buffer Length before processing: {currentBufferLength}. Token: {token.GetHashCode()}"); - currentBufferLength += bytesRead; _receiveOctets.SetSize(bytesRead); @@ -386,19 +377,17 @@ namespace CSNetwork int originalBlockLength = _decryptedOctets.Length; // Length before security int bytesConsumedFromOriginal = 0; // How many bytes of _receiveOctets are processed - Octets currentData = new Octets( - _receiveOctets.RawBuffer, - 0, - _receiveOctets.Size - ); - // 1. Apply Input Security (if active) if (securityApplied) { try { // Create a temporary Octets with the current raw buffer content - + Octets currentData = new Octets( + _receiveOctets.RawBuffer, + 0, + _receiveOctets.Size + ); // Update returns a NEW Octets object with processed data dataToProcess = currentIsec!.Update(currentData); _decryptedOctets.Insert(_decryptedOctets.Size, dataToProcess.ByteArray); @@ -418,18 +407,8 @@ namespace CSNetwork else { // No security, process directly from the receive buffer - // dataToProcess = _receiveOctets; - - // _decryptedOctets.Insert(_decryptedOctets.Size, currentData.ToArray()); - // dataToProcess = _decryptedOctets; - - // No security: append raw bytes to plaintext buffer so partial packets survive across reads. - byte[] slice = new byte[_receiveOctets.Size]; - Buffer.BlockCopy(_receiveOctets.RawBuffer, 0, slice, 0, _receiveOctets.Size); - _decryptedOctets.Insert(_decryptedOctets.Size, slice); + dataToProcess = _receiveOctets; } - - dataToProcess = _decryptedOctets; // 2. Process Protocols from 'dataToProcess' OctetsStream processingStream = new OctetsStream(dataToProcess); @@ -494,13 +473,15 @@ namespace CSNetwork bytesConsumedFromOriginal = processingStream.Position; // Use final stream position } + originalBlockLength = _decryptedOctets.Length; + _logger.Log(LogType.Info, + $" securityApplied = {securityApplied} bytesConsumedFromOriginal = {bytesConsumedFromOriginal }, originalBlockLength = {originalBlockLength}" + ); EndProcessing: _receiveOctets.SetSize(0); // 4. Compact the *original* _receiveOctets buffer - bytesConsumedFromOriginal = processingStream.Position; - originalBlockLength = _decryptedOctets.Length; CompactDecryptedBuffer(bytesConsumedFromOriginal, originalBlockLength); } diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/playerlogout.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/playerlogout.cs index bff70ce596..f758109a09 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/playerlogout.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/playerlogout.cs @@ -9,7 +9,7 @@ namespace CSNetwork.Protocols public int Roleid { get; set; } public int Provider_link_id { get; set; } public int Localsid { get; set; } - public int L_localsid { get; set; } + // public int L_localsid { get; set; } public playerlogout() : base(ProtocolType.PROTOCOL_PLAYERLOGOUT) { @@ -22,7 +22,7 @@ namespace CSNetwork.Protocols Roleid = Roleid, Provider_link_id = Provider_link_id, Localsid = Localsid, - L_localsid = L_localsid + // L_localsid = L_localsid }; public override void Marshal(OctetsStream os) @@ -31,7 +31,7 @@ namespace CSNetwork.Protocols os.Write(Roleid); os.Write(Provider_link_id); os.Write(Localsid); - os.Write(L_localsid); + // os.Write(L_localsid); } public override void Unmarshal(OctetsStream os) @@ -40,7 +40,7 @@ namespace CSNetwork.Protocols Roleid = os.ReadInt32(); Provider_link_id = os.ReadInt32(); Localsid = os.ReadInt32(); - L_localsid = os.ReadInt32(); + // L_localsid = os.ReadInt32(); } public override int PriorPolicy() => 101; diff --git a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs index cacba8dc01..08c117bae0 100644 --- a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs @@ -13,6 +13,7 @@ using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; +using BrewMonster.Managers; using BrewMonster.Scripts.Task; using BrewMonster.UI; using UnityEngine; @@ -183,6 +184,7 @@ namespace BrewMonster.Network // Tell LoginScene what to show next. LogoutFlowState.NextLoginEntry = entryTarget; _gameSession.Disconnected -= OnUnexpectedDisconnect; + EC_ManMessageMono.Instance.CECNPCMan.Release(); if (clearSavedCreds) { @@ -203,7 +205,7 @@ namespace BrewMonster.Network // _gameSession.SendPlayerLogout(outType); // Wait briefly for server-driven disconnect. - await WaitForDisconnectAsync(timeoutMs: 100); + if(outType == 0) await WaitForDisconnectAsync(timeoutMs: 100); } } catch (Exception ex) @@ -213,10 +215,11 @@ namespace BrewMonster.Network finally { // Fallback: if server didn't close, close locally. - if (_gameSession != null && _gameSession.IsConnected) + if (_gameSession != null && _gameSession.IsConnected && outType == 0) { _gameSession.Disconnect(); } + } // Return to LoginScene. @@ -296,6 +299,12 @@ namespace BrewMonster.Network { // get current active scene var currentScene = SceneManager.GetActiveScene(); + if ( currentScene.IsValid() && currentScene.name == LoginSceneName) + { + // LoginScene is already active, nothing to do. + return; + } + // Load LoginScene additively if needed (do not unload keepSceneName, e.g., a61). var loginScene = SceneManager.GetSceneByName(LoginSceneName); if (!loginScene.IsValid() || !loginScene.isLoaded) diff --git a/Assets/PerfectWorld/Scripts/Task/ATaskTemplMan.cs b/Assets/PerfectWorld/Scripts/Task/ATaskTemplMan.cs index 2012a6e28d..0aa18c8d5e 100644 --- a/Assets/PerfectWorld/Scripts/Task/ATaskTemplMan.cs +++ b/Assets/PerfectWorld/Scripts/Task/ATaskTemplMan.cs @@ -68,6 +68,8 @@ namespace BrewMonster.Scripts.Task uint m_ulNPCInfoTimeMark; private Dictionary m_NPCInfoMap = new(); + private bool _wasLoaded = false; + // Lookup NPC/task object coordinates info by template id (loaded from task_npc pack) // 通过模板ID查找NPC/任务对象坐标信息(从task_npc包加载) public bool TryGetTaskNPCInfo(uint id, out NPC_INFO info) @@ -95,12 +97,16 @@ namespace BrewMonster.Scripts.Task public async UniTask LoadTasksFromPack(string address, bool bLoadDescript, Action onProgress, CancellationToken token) { - bool wasLoaded = await LoadTaskTemplFromSO(); - if (wasLoaded) + // If loaded Task Template from last play time, skip loading again + if (_wasLoaded) { - BMLogger.Log($" [ATaskTemplMan] Loaded task templates from ScriptableObject."); - onProgress?.Invoke(1f); - return true; + goto END_PROGRESS; + } + + _wasLoaded = await LoadTaskTemplFromSO(); + if (_wasLoaded) + { + goto END_PROGRESS; } var handle = await AddressableManager.Instance.LoadTextAssetAsync(address); @@ -162,6 +168,9 @@ namespace BrewMonster.Scripts.Task await UniTask.Yield(); } + END_PROGRESS: + + _wasLoaded = true; onProgress?.Invoke(1f); Debug.Log($" Finished loading {m_TaskTemplMap.Count} task templates."); diff --git a/Assets/PerfectWorld/Scripts/Task/UI/EmoteWindow.cs b/Assets/PerfectWorld/Scripts/Task/UI/EmoteWindow.cs index d50eb53ae5..7f7b72d363 100644 --- a/Assets/PerfectWorld/Scripts/Task/UI/EmoteWindow.cs +++ b/Assets/PerfectWorld/Scripts/Task/UI/EmoteWindow.cs @@ -87,10 +87,10 @@ namespace BrewMonster.UI // if (bInAutoMode) return; //todo: dummy call StartGame // EC_Game.GetGameRun().StartGame(0, Vector3.zero); - if (EC_Game.GetGameRun().GetPoseCmdShortcuts() == null) - { - EC_Game.GetGameRun().StartGame(0, Vector3.zero); - } + // if (EC_Game.GetGameRun().GetPoseCmdShortcuts() == null) + // { + // EC_Game.GetGameRun().StartGame(0, Vector3.zero); + // } CECShortcut pSC = EC_Game.GetGameRun().GetPoseCmdShortcuts().GetShortcut(slot); // if (CDlgAutoHelp::IsAutoHelp() && strstr(pDlgSrc->GetName(), "Win_Quickbar")) // { diff --git a/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs b/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs index 988d46d214..0fc5bb5ada 100644 --- a/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs +++ b/Assets/PerfectWorld/Scripts/UI/Login/BtnBackToSelectRole.cs @@ -9,16 +9,50 @@ namespace BrewMonster.UI public class BtnBackToSelectRole : MonoBehaviour { public void OnClick() + { + // CECUIManager.Instance.ShowMessageBox( + // title: "Thoát", + // message: "Đang rời khỏi Thế Giới Hoàn Mỹ", + // messageBoxType: MessageBoxType.YesButton + // ); + + // CECGameRun.Instance.GetPendingLogOut().AppendForSaveConfig(new CECPendingLogoutHalf()); + // UnityGameSession.ReturnToSelectRole(); + + OnCommandRepick(); + } + + // void CDlgSystem3::OnCommandRepick(const char *szCommand) + // { + // a_LogOutput(1, "CDlgSystem3::OnCommandRepick "); + // + // if( !GetGameUIMan()->m_pDlgExit->IsShow() && + // !GetGameUIMan()->GetDialog("Game_Quit") ) + // { + // AUIDialog *pMsgBox = NULL; + // GetGameUIMan()->MessageBox("Game_Quit", + // GetGameUIMan()->GetStringFromTable(CECCrossServer::Instance().IsOnSpecialServer() ? 10131 : 202), + // MB_YESNO, A3DCOLORRGBA(255, 255, 255, 160), &pMsgBox); + // pMsgBox->SetIsModal(false); + // } + // } + + + void OnCommandRepick() { CECUIManager.Instance.ShowMessageBox( title: "Thoát", - message: "Đang rời khỏi Thế Giới Hoàn Mỹ", - messageBoxType: MessageBoxType.YesButton + message: CECUIManager.Instance.GetInGameUIMan().GetStringFromTable(202), + messageBoxType: MessageBoxType.BothYesNoButton, + onClickedYes: OnClickYes ); - - CECGameRun.Instance.GetPendingLogOut().AppendForSaveConfig(new CECPendingLogoutHalf()); - // UnityGameSession.ReturnToSelectRole(); } + + void OnClickYes() + { + CECGameRun.Instance.GetPendingLogOut().AppendForSaveConfig(new CECPendingLogoutHalf()); + } + } } diff --git a/Assets/PerfectWorld/Scripts/UI/Login/BtnLogoutAccount.cs b/Assets/PerfectWorld/Scripts/UI/Login/BtnLogoutAccount.cs index 53dcf51254..b0e31e6762 100644 --- a/Assets/PerfectWorld/Scripts/UI/Login/BtnLogoutAccount.cs +++ b/Assets/PerfectWorld/Scripts/UI/Login/BtnLogoutAccount.cs @@ -11,6 +11,8 @@ namespace BrewMonster.UI public void OnClick() { UnityGameSession.LogoutAccount(); + // CECGameRun.Instance.GetPendingLogOut().AppendForSaveConfig(new CECPendingLogoutFull()); + // UnityGameSession.Instance.GameSession.c2s_SendCmdLogout( PendingActionConstants._PLAYER_LOGOUT_FULL); } } } diff --git a/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs b/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs index 725a791c01..337f3fd63c 100644 --- a/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs +++ b/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs @@ -82,7 +82,7 @@ namespace BrewMonster.UI if (_selectCharacterScreen != null) _selectCharacterScreen.gameObject.SetActive(false); - ApplyLoginEntry(LogoutFlowState.ConsumeNextLoginEntry()); + // ApplyLoginEntry(LogoutFlowState.ConsumeNextLoginEntry()); } // Update is called once per frame @@ -181,6 +181,10 @@ namespace BrewMonster.UI if (entry == BrewMonster.Network.LogoutFlowState.LoginEntryTarget.SelectRole) { + // If we're returning to select role, skip straight to select role without showing login UI again, since we never fully left the game session. + OnLoginComplete(true); + return; + // Auto-login to reach Select Role like the original client, without showing Tech3C auth UI again. if (!string.IsNullOrEmpty(_usernameInputField.text) && !string.IsNullOrEmpty(_passwordInputField.text)) { diff --git a/Assets/Scripts/CECUIManager.cs b/Assets/Scripts/CECUIManager.cs index c58d6f68b9..451dfe22c9 100644 --- a/Assets/Scripts/CECUIManager.cs +++ b/Assets/Scripts/CECUIManager.cs @@ -197,6 +197,8 @@ public class CECUIManager : MonoSingleton } return null; } + + public void UpdateSkillRelatedUI() { // ¸üм¼ÄÜÏà¹ØµÄ½çÃæÏÔʾ @@ -273,7 +275,25 @@ public class CECUIManager : MonoSingleton if (string.Equals(pDlg.GetName(), "Game_Quit", StringComparison.OrdinalIgnoreCase)) { + // TODO + // Cancel hotkey customize because hot key state is determinted by CECHostInputFilter + // which is shared between repick role + // + // if (m_pDlgSettingQuickKey->IsShow()) + // m_pDlgSettingQuickKey->Show(false); + if( pSession.GameSession.IsConnected ) + { + // TODO + // if (CECCrossServer::Instance().IsOnSpecialServer()) + // g_pGame->GetGameRun()->GetPendingLogOut().AppendForSaveConfig(new CECPendingLogoutCrossServer()); + // else + // g_pGame->GetGameRun()->GetPendingLogOut().AppendForSaveConfig(new CECPendingLogoutHalf()); + + EC_Game.GetGameRun().GetPendingLogOut().AppendForSaveConfig(new CECPendingLogoutHalf()); + } + else + EC_Game.GetGameRun().SetLogoutFlag(2); } else if ((string.Equals(pDlg.GetName(), "Game_TeachSkill", StringComparison.OrdinalIgnoreCase) && DialogBoxCommandIDs.IDOK == iRetVal) || (string.Equals(pDlg.GetName(), "Game_LearnSkill", StringComparison.OrdinalIgnoreCase) && DialogBoxCommandIDs.IDOK == iRetVal)) @@ -509,10 +529,10 @@ public class CECUIManager : MonoSingleton public void OnClickedWaveHand() { - if (EC_Game.GetGameRun().GetPoseCmdShortcuts() == null) - { - EC_Game.GetGameRun().StartGame(0, Vector3.zero); - } + // if (EC_Game.GetGameRun().GetPoseCmdShortcuts() == null) + // { + // EC_Game.GetGameRun().StartGame(0, Vector3.zero); + // } CECShortcut pSC = EC_Game.GetGameRun().GetPoseCmdShortcuts().GetShortcut(slot); if (pSC != null) // && pObjSrc->GetDataPtr("ptr_CECShortcut") == pSC { diff --git a/Assets/Scripts/EC_GameRun.cs b/Assets/Scripts/EC_GameRun.cs index 1c0c5235d1..d178052914 100644 --- a/Assets/Scripts/EC_GameRun.cs +++ b/Assets/Scripts/EC_GameRun.cs @@ -117,8 +117,6 @@ public partial class CECGameRun : ITickable AddressableManager.Instance.OnDispose += Dispose; if (m_pWorld == null) m_pWorld = new CECWorld(); - StartGame(0, Vector3.zero); - m_pendingLogout = new CECPendingActionArray( false); @@ -154,27 +152,8 @@ public partial class CECGameRun : ITickable } #endif } - - private bool init; public bool StartGame(int idInst, Vector3 vHostPos) { - if (init) - { - return false; - } - // Create shortcuts - if (!CreateShortcuts()) - { - return false; - } - if (!JumpToInstance(idInst, vHostPos)) - { - BMLogger.LogError("CECGameRun::StartGame, Failed to create game world."); - return false; - } - - init = true; - // TODO: Implement the rest of the StartGame logic based on the original C++ code, including: // End current game state EndGameState(); @@ -195,12 +174,12 @@ public partial class CECGameRun : ITickable // return false; // } // - // // Create default game world - // if (!JumpToInstance(idInst, vHostPos)) - // { - // a_LogOutput(1, "CECGameRun::StartGame, Failed to create game world."); - // return false; - // } + // Create default game world + if (!JumpToInstance(idInst, vHostPos)) + { + // a_LogOutput(1, "CECGameRun::StartGame, Failed to create game world."); + return false; + } // // // ÉèÖÿç·þ³É¹¦±êʶ£¬ÒÔÀûÓÚ CECGameUIMan ¸ù¾Ý¿ç·þ״̬×öÏàÓ¦³õʼ»¯ // if (CECCrossServer::Instance().IsWaitLogin()){ @@ -227,12 +206,12 @@ public partial class CECGameRun : ITickable // // Reset faction manager // g_pGame->GetFactionMan()->Release(false); // - // // Create shortcuts - // if (!CreateShortcuts()) - // { - // a_LogOutput(1, "CECGameRun::StartGame, Failed to create shortcuts"); - // return false; - // } + // Create shortcuts + if (!CreateShortcuts()) + { + // a_LogOutput(1, "CECGameRun::StartGame, Failed to create shortcuts"); + return false; + } // // // Change UI manager // if (!m_pUIManager->ChangeCurUIManager(CECUIManager::UIMAN_INGAME))