Merge pull request 'feature/update-logout-logic' (#203) from feature/update-logout-logic into develop

Reviewed-on: https://git.pthub.vn/Unity/perfect-world-unity/pulls/203
This commit is contained in:
haimh
2026-03-06 10:55:06 +00:00
39 changed files with 1355 additions and 94 deletions
@@ -109,7 +109,9 @@ public class CECPendingLogoutHalf : CECPendingAction
bool bSuccess = false;
if (IsInGame())
{
GetGameSession().SendPlayerLogout(PendingActionConstants._PLAYER_LOGOUT_HALF);
// TODO: currently, we logout logic != C++, need to implement properly
GetGameSession().c2s_SendCmdLogout(PendingActionConstants._PLAYER_LOGOUT_HALF);
// UnityGameSession.ReturnToSelectRole();
bSuccess = true;
}
return bSuccess;
@@ -128,7 +130,10 @@ public class CECPendingLogoutFull : CECPendingAction
bool bSuccess = false;
if (IsInGame())
{
GetGameSession().SendPlayerLogout(PendingActionConstants._PLAYER_LOGOUT_FULL);
// TODO: currently, we logout logic != C++, need to implement properly
// C++ code: GetGameSession()->SendPlayerLogout(PendingActionConstants::_PLAYER_LOGOUT_FULL);
GetGameSession().c2s_SendCmdLogout(PendingActionConstants._PLAYER_LOGOUT_FULL);
// UnityGameSession.LogoutAccount();
bSuccess = true;
}
return bSuccess;
@@ -147,8 +152,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;
@@ -0,0 +1,39 @@
using System.Collections.Generic;
using UnityEngine;
namespace BrewMonster.Scripts
{
public interface ITickable
{
bool Tick(uint dwDeltaTime);
}
public class TickInvoker : MonoSingleton<TickInvoker>
{
List<ITickable> tickables = new List<ITickable>();
protected override void Initialize()
{
DontDestroyOnLoad(gameObject);
}
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<tickables.Count; i++)
{
tickables[i].Tick( (uint)(Time.deltaTime * 1000)); // Convert to milliseconds
}
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 94ae88742bf5484ea9a61c2f850f9e18
timeCreated: 1772424967
@@ -0,0 +1,7 @@
namespace BrewMonster.Scripts
{
public class TestLogoutLogic : Singleton<TestLogoutLogic>
{
public bool WasClientSendLogoutMessage;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: df29527a68a5442e814e73146a4ad68a
timeCreated: 1772608601
@@ -841,7 +841,7 @@ public class CECNPCMan : IMsgHandler
}
}
// Set born stamp & born-in-sight (giữ nguyên semantics)
uint bornStamp = CECWorld.Instance.GetBornStamp();
uint bornStamp = CECGameRun.Instance.GetWorld().GetBornStamp();
if (!object.ReferenceEquals(pNPC, null))
{
@@ -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
{
@@ -245,7 +245,7 @@ namespace PerfectWorld.Scripts.Managers
Debug.LogError($"Failed to create matter: {info.mid}");
return false;
}
pMatter.SetBornStamp(CECWorld.Instance.GetBornStamp());
pMatter.SetBornStamp(CECGameRun.Instance.GetWorld().GetBornStamp());
m_MatterTab[info.mid] = pMatter;
@@ -1,4 +1,4 @@
using BrewMonster.Network;
using BrewMonster.Network;
using BrewMonster.Scripts;
using CSNetwork.GPDataType;
using System;
@@ -392,8 +392,9 @@ namespace BrewMonster
EC_CDR.OnGroundMove(ref cdr);
//BMLogger.LogError($"HoangDev: FlashMove seg={i} stepTime={cdr.t} center=({cdr.vCenter})");
if (CECWorld.Instance.GetAssureMove() != null)
CECWorld.Instance.GetAssureMove().NoAssureMove();
var world = CECGameRun.Instance?.GetWorld();
if (world?.GetAssureMove() != null)
world.GetAssureMove().NoAssureMove();
if ((cdr.vCenter - vStartPos).Magnitude() >= fDist * 0.98f)
{
+1 -1
View File
@@ -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);
@@ -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
{
@@ -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;
@@ -23,7 +24,6 @@ namespace CSNetwork
{
private static IPrefixedLogger
_logger = LoggerFactory.GetLogger(nameof(GameSession)); // Get class-specific logger
private NetworkManager _networkManager;
private string _host;
private int _port;
@@ -84,6 +84,8 @@ namespace CSNetwork
_networkManager.ProtocolReceived += OnProtocolReceived;
_networkManager.ErrorOccurred += OnErrorOccurred;
_networkManager.Disconnected += OnDisconnected;
TestLogoutLogic.Instance.WasClientSendLogoutMessage = false;
}
public void SetLogPath(string path)
@@ -512,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();
}
@@ -528,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;
@@ -549,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:
@@ -570,8 +573,10 @@ 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);
// HandlePlayerLogout((playerlogout)protocol);
OnPrtcPlayerLogout((playerlogout)protocol);
break;
case ProtocolType.PROTOCOL_AUTOTEAMSETGOAL_RE:
@@ -590,11 +595,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<ushort>();
@@ -1192,11 +1238,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)
@@ -1509,12 +1559,14 @@ namespace CSNetwork
/// - outType=1: back to select role
/// - outType=0: logout account
/// </summary>
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)
{
@@ -1639,6 +1691,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;
@@ -2083,5 +2151,6 @@ namespace CSNetwork
{
// TODO: C2SCommandFactory.CreateNPCSevCrossServerGetOutCmd() and SendProtocol
}
}
}
@@ -472,6 +472,12 @@ namespace CSNetwork
// after successful decodes/consumptions.
bytesConsumedFromOriginal = processingStream.Position; // Use final stream position
}
originalBlockLength = _decryptedOctets.Length;
_logger.Log(LogType.Info,
$" securityApplied = {securityApplied} bytesConsumedFromOriginal = {bytesConsumedFromOriginal }, originalBlockLength = {originalBlockLength}"
);
EndProcessing:
_receiveOctets.SetSize(0);
@@ -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
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6d43c7ce793f4c3698ff104d0d66f328
timeCreated: 1772523576
@@ -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;
@@ -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;
@@ -107,7 +108,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)
{
@@ -182,6 +183,8 @@ namespace BrewMonster.Network
{
// Tell LoginScene what to show next.
LogoutFlowState.NextLoginEntry = entryTarget;
_gameSession.Disconnected -= OnUnexpectedDisconnect;
EC_ManMessageMono.Instance.CECNPCMan.Release();
if (clearSavedCreds)
{
@@ -197,11 +200,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);
if(outType == 0) await WaitForDisconnectAsync(timeoutMs: 100);
}
}
catch (Exception ex)
@@ -211,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.
@@ -245,7 +250,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;
}
}
@@ -293,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)
@@ -68,6 +68,8 @@ namespace BrewMonster.Scripts.Task
uint m_ulNPCInfoTimeMark;
private Dictionary<uint, NPC_INFO> 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<bool> LoadTasksFromPack(string address, bool bLoadDescript, Action<float> 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.");
@@ -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;
}
@@ -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))
@@ -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<OBJECT_COORD> instCoord = new List<OBJECT_COORD>(), tempCoord = new List<OBJECT_COORD>();
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
@@ -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"))
// {
@@ -9,15 +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
);
UnityGameSession.ReturnToSelectRole();
}
void OnClickYes()
{
CECGameRun.Instance.GetPendingLogOut().AppendForSaveConfig(new CECPendingLogoutHalf());
}
}
}
@@ -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);
}
}
}
@@ -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))
{
+94 -1
View File
@@ -8,7 +8,7 @@ using UnityEngine;
namespace BrewMonster.Scripts.World
{
public class CECWorld : Singleton<CECWorld>
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();
}
}
}
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5f58ceadd359a44bc8ab2da938c71e9c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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=...)
```
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4caec71ab8d214b58b08e61470605ff6
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
-2
View File
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 5de219a5b9756ae4ebf01e2919b92cde
+2 -2
View File
@@ -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;
+1 -1
View File
@@ -871,7 +871,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()
+173 -4
View File
@@ -791,7 +791,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;
@@ -989,7 +989,7 @@ namespace BrewMonster
bool bFound = false;
vPos = default;
CECWorld world = CECWorld.Instance;
CECWorld world = CECGameRun.Instance.GetWorld();
if (world == null)
{
return false;
@@ -1064,7 +1064,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;
@@ -3805,7 +3805,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)
@@ -3819,5 +3985,8 @@ namespace BrewMonster
return trace.GetTraceReason() == Trace_reason.TRACE_SPELL;
}
}
}
+24 -4
View File
@@ -197,6 +197,8 @@ public class CECUIManager : MonoSingleton<CECUIManager>
}
return null;
}
public void UpdateSkillRelatedUI()
{
// ¸üм¼ÄÜÏà¹ØµÄ½çÃæÏÔʾ
@@ -273,7 +275,25 @@ public class CECUIManager : MonoSingleton<CECUIManager>
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<CECUIManager>
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
{
+290
View File
@@ -0,0 +1,290 @@
using BrewMonster;
using BrewMonster.Network;
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
{
UnityGameSession.ReturnToSelectRole();
// 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
{
UnityGameSession.LogoutAccount();
// 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;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b039014c898e4659adb7c90cc570b637
timeCreated: 1772439168
@@ -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,17 @@ public partial class CECGameRun
if (!m_InstTab.ContainsKey(161))
m_InstTab.Add(161, new CECInstance());
AddressableManager.Instance.OnDispose += Dispose;
m_pWorld = CECWorld.Instance;
StartGame(0, Vector3.zero);
if (m_pWorld == null)
m_pWorld = new CECWorld();
m_pWorld = CECWorld.Instance;
m_pendingLogout = new CECPendingActionArray( false);
TickInvoker.Instance.RegisterTickable(this);
}
private static void Dispose()
{
TickInvoker.Instance.UnregisterTickable(instance);
instance = null;
}
@@ -152,26 +152,103 @@ public partial class CECGameRun
}
#endif
}
private bool init;
public bool StartGame(int idInst, Vector3 vHostPos)
{
if (init)
// 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))
{
return false;
}
// 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())
{
return false;
// a_LogOutput(1, "CECGameRun::StartGame, Failed to create shortcuts");
return false;
}
if (!JumpToInstance(idInst, vHostPos))
{
BMLogger.LogError("CECGameRun::StartGame, Failed to create game world.");
return false;
}
init = true;
//
// // 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 +269,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 +300,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<CECHostPlayer>();
hostPlayer.InitCharacter(info);
m_pHostPlayer = ObjectSpawner.Instance.InstantiateObject(_playerPrefab, setThisAsParent: true).AddComponent<CECHostPlayer>();
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 +1008,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
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d68cfcd6293664808ba733553314250a