diff --git a/Assets/AddressableAssetsData/AssetGroups/sfx.asset b/Assets/AddressableAssetsData/AssetGroups/sfx.asset
index bdb254f9e6..ce60e1e060 100644
--- a/Assets/AddressableAssetsData/AssetGroups/sfx.asset
+++ b/Assets/AddressableAssetsData/AssetGroups/sfx.asset
@@ -9330,6 +9330,11 @@ MonoBehaviour:
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
+ - m_GUID: b052c37c53745f84ab3c7bb0e3c410b0
+ m_Address: Assets/ModelRenderer/Art/sfx/music/music/theme_c.mp3
+ m_ReadOnly: 0
+ m_SerializedLabels: []
+ FlaggedDuringContentUpdateRestriction: 0
- m_GUID: b069390864138b445b51557b94886527
m_Address: "skill/02mage/\u5947\u95E8\u62A4\u7532_a"
m_ReadOnly: 0
@@ -12625,6 +12630,11 @@ MonoBehaviour:
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
+ - m_GUID: e6da5caa83084c446b019f91ef4c3349
+ m_Address: Assets/ModelRenderer/Art/sfx/music/music/theme_a.mp3
+ m_ReadOnly: 0
+ m_SerializedLabels: []
+ FlaggedDuringContentUpdateRestriction: 0
- m_GUID: e6df1ea9e172c414da99d57d3f22b935
m_Address: "monster/\u6218\u6597\u884C\u52A8/\u82B1\u8349\u7C7B\u8FD0\u52A82"
m_ReadOnly: 0
@@ -12905,6 +12915,11 @@ MonoBehaviour:
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
+ - m_GUID: eb201c0efb391fa4797948562ef08604
+ m_Address: Assets/ModelRenderer/Art/sfx/music/music/theme_b.mp3
+ m_ReadOnly: 0
+ m_SerializedLabels: []
+ FlaggedDuringContentUpdateRestriction: 0
- m_GUID: eb223a6970aaa9c45903924622374999
m_Address: "creature/monster/\u795D\u878D/\u6B7B\u4EA1_\u6C34\u4E2D"
m_ReadOnly: 0
diff --git a/Assets/ModelRenderer/Art/sfx/music/music/theme_a.mp3 b/Assets/ModelRenderer/Art/sfx/music/music/theme_a.mp3
new file mode 100644
index 0000000000..39f597f287
Binary files /dev/null and b/Assets/ModelRenderer/Art/sfx/music/music/theme_a.mp3 differ
diff --git a/Assets/ModelRenderer/Art/sfx/music/music/theme_a.mp3.meta b/Assets/ModelRenderer/Art/sfx/music/music/theme_a.mp3.meta
new file mode 100644
index 0000000000..bf81c9c8ef
--- /dev/null
+++ b/Assets/ModelRenderer/Art/sfx/music/music/theme_a.mp3.meta
@@ -0,0 +1,23 @@
+fileFormatVersion: 2
+guid: e6da5caa83084c446b019f91ef4c3349
+AudioImporter:
+ externalObjects: {}
+ serializedVersion: 8
+ defaultSettings:
+ serializedVersion: 2
+ loadType: 0
+ sampleRateSetting: 0
+ sampleRateOverride: 44100
+ compressionFormat: 1
+ quality: 1
+ conversionMode: 0
+ preloadAudioData: 0
+ platformSettingOverrides: {}
+ forceToMono: 0
+ normalize: 1
+ loadInBackground: 0
+ ambisonic: 0
+ 3D: 1
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/ModelRenderer/Art/sfx/music/music/theme_b.mp3 b/Assets/ModelRenderer/Art/sfx/music/music/theme_b.mp3
new file mode 100644
index 0000000000..dd077de344
Binary files /dev/null and b/Assets/ModelRenderer/Art/sfx/music/music/theme_b.mp3 differ
diff --git a/Assets/ModelRenderer/Art/sfx/music/music/theme_b.mp3.meta b/Assets/ModelRenderer/Art/sfx/music/music/theme_b.mp3.meta
new file mode 100644
index 0000000000..993f29ccf2
--- /dev/null
+++ b/Assets/ModelRenderer/Art/sfx/music/music/theme_b.mp3.meta
@@ -0,0 +1,23 @@
+fileFormatVersion: 2
+guid: eb201c0efb391fa4797948562ef08604
+AudioImporter:
+ externalObjects: {}
+ serializedVersion: 8
+ defaultSettings:
+ serializedVersion: 2
+ loadType: 0
+ sampleRateSetting: 0
+ sampleRateOverride: 44100
+ compressionFormat: 1
+ quality: 1
+ conversionMode: 0
+ preloadAudioData: 0
+ platformSettingOverrides: {}
+ forceToMono: 0
+ normalize: 1
+ loadInBackground: 0
+ ambisonic: 0
+ 3D: 1
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/ModelRenderer/Art/sfx/music/music/theme_c.mp3 b/Assets/ModelRenderer/Art/sfx/music/music/theme_c.mp3
new file mode 100644
index 0000000000..0be944baca
Binary files /dev/null and b/Assets/ModelRenderer/Art/sfx/music/music/theme_c.mp3 differ
diff --git a/Assets/ModelRenderer/Art/sfx/music/music/theme_c.mp3.meta b/Assets/ModelRenderer/Art/sfx/music/music/theme_c.mp3.meta
new file mode 100644
index 0000000000..5aa3af1cbe
--- /dev/null
+++ b/Assets/ModelRenderer/Art/sfx/music/music/theme_c.mp3.meta
@@ -0,0 +1,23 @@
+fileFormatVersion: 2
+guid: b052c37c53745f84ab3c7bb0e3c410b0
+AudioImporter:
+ externalObjects: {}
+ serializedVersion: 8
+ defaultSettings:
+ serializedVersion: 2
+ loadType: 0
+ sampleRateSetting: 0
+ sampleRateOverride: 44100
+ compressionFormat: 1
+ quality: 1
+ conversionMode: 0
+ preloadAudioData: 0
+ platformSettingOverrides: {}
+ forceToMono: 0
+ normalize: 1
+ loadInBackground: 0
+ ambisonic: 0
+ 3D: 1
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/PerfectWorld/Scene/LoginScene.unity b/Assets/PerfectWorld/Scene/LoginScene.unity
index 857b9fb373..4de3721598 100644
--- a/Assets/PerfectWorld/Scene/LoginScene.unity
+++ b/Assets/PerfectWorld/Scene/LoginScene.unity
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:631a23da0c12055ef55e35b21fac40b752599bdbb43d0d2c1c7fd90674d4b791
-size 118853
+oid sha256:bc028fc3c6b7fe9d9851f84da3d784923ccfb38af77605dd57c1e7e12e4c5817
+size 113080
diff --git a/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs b/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs
index b9f3def29b..e19fe6fe1a 100644
--- a/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs
+++ b/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs
@@ -1,440 +1,426 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using BrewMonster.Network;
-using BrewMonster.Scripts;
-using CSNetwork.Protocols;
-using CSNetwork.Protocols.RPCData;
-using TMPro;
-using UnityEngine;
-using UnityEngine.SceneManagement;
-using UnityEngine.UI;
-
-namespace BrewMonster.UI
-{
- ///
- /// Login Flow:
- /// 1. Enter username and password
- /// 2. Click login button
- /// 3. Login success, get the list of characters
- /// 4. Open the select character screen
- ///
- public class LoginScreenUI : MonoBehaviour
- {
- [SerializeField] private TMP_InputField _usernameInputField;
- [SerializeField] private TMP_InputField _passwordInputField;
- [SerializeField] private Button _loginButton;
- [SerializeField] private SelecScreenCharacter _selectCharacterScreen;
-
- private List _roleInfos;
- private List _currentRoles;
- private RoleInfo _pendingCreatedRole;
- private bool _loginInProgress;
- bool isDoneWorldRender = false;
- bool isDoneNPCRender = false;
- private SynchronizationContext context;
- public AudioClip loginBGM;
-
- void Awake()
- {
- // Ensure wrapper created early (Tech3C SDK).
- _ = Tech3CSDKWrapper.Instance;
-
- #if UNITY_EDITOR
- _usernameInputField.gameObject.SetActive(true);
- _passwordInputField.gameObject.SetActive(true);
- #else
- _usernameInputField.gameObject.SetActive(false);
- _passwordInputField.gameObject.SetActive(false);
- #endif
- }
-
- void OnEnable()
- {
- Tech3CSDKWrapper.Instance.SetLoginCallback(OnLoginCallback);
- Tech3CSDKWrapper.Instance.SetLogoutCallback(OnLogoutCallback);
- }
-
- private void OnDisable()
- {
- Tech3CSDKWrapper.Instance.RemoveLoginCallback();
- Tech3CSDKWrapper.Instance.RemoveLogoutCallback();
- }
-
- void Start()
- {
- AudioManager.Instance.PlayBGM(loginBGM, 1.5f);
- _loginButton.onClick.AddListener(OnLoginButtonClicked);
- context = SynchronizationContext.Current;
-#if UNITY_EDITOR
- // only load the username and password from the player prefs if in editor
- _usernameInputField.text = PlayerPrefs.GetString("username", "");
- _passwordInputField.text = PlayerPrefs.GetString("password", "");
-#endif
-
- // Default: login UI first, select-role hidden until login succeeds.
- if (_selectCharacterScreen != null)
- _selectCharacterScreen.gameObject.SetActive(false);
-
- // ApplyLoginEntry(LogoutFlowState.ConsumeNextLoginEntry());
- }
-
- // Update is called once per frame
- void Update()
- {
- if (_roleInfos != null)
- {
- _selectCharacterScreen.InitScreen(_roleInfos, OnClickSelectCharacter, OnCreateCharacterComplete);
- _roleInfos = null;
- }
-
-#if UNITY_EDITOR
- if (Input.GetKeyUp(KeyCode.LeftAlt))
- {
- _usernameInputField.text = "test016";
- _passwordInputField.text = "123456";
- OnLoginButtonClicked();
- }
-
- if (Input.GetKeyUp(KeyCode.Tab))
- {
- _usernameInputField.text = "test017";
- _passwordInputField.text = "123456";
- OnLoginButtonClicked();
- }
-#endif
- }
-
-
- public async void OnLoginButtonClicked()
- {
- if (_loginInProgress)
- {
- BMLogger.LogWarning("[LoginScreenUI] Login already in progress (ignored click).");
- return;
- }
-
- _loginInProgress = true;
- if (_loginButton != null) _loginButton.interactable = false;
-
- // If username or password is empty, use Tech3C SDK login UI.
- if (string.IsNullOrEmpty(_usernameInputField.text) || string.IsNullOrEmpty(_passwordInputField.text))
- {
- // Use Tech3C SDK login UI.
- bool started = Tech3CSDKWrapper.Instance.Login();
- if (!started)
- {
- // Fallback: manual username/password login (useful in dev if SDK not configured).
- BMLogger.LogWarning("[LoginScreenUI] Tech3CSDKWrapper.Login() failed, fallback to manual login.");
- await BeginGameLoginAsync(_usernameInputField.text, _passwordInputField.text);
- }
- }
- else
- {
- // otherwise use manual username/password login.
- BMLogger.LogError("[LoginScreenUI] Username/password empty.");
- await BeginGameLoginAsync(_usernameInputField.text, _passwordInputField.text);
- }
-
- }
-
- private async Task BeginGameLoginAsync(string username, string password)
- {
- if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
- {
- BMLogger.LogError("[LoginScreenUI] Username/password empty.");
- _loginInProgress = false;
- if (_loginButton != null) _loginButton.interactable = true;
- return;
- }
-
- BMLogger.Log("OnLoginButtonClicked");
- UnityGameSession.SetConnectionInfo("103.51.120.195", 29000);
- PlayerPrefs.SetString("username", username);
- PlayerPrefs.SetString("password", password);
- PlayerPrefs.Save();
-
- BMLogger.Log($"[LoginScreenUI] Connecting+login start user='{username}'");
- await UnityGameSession.Login(username, password, OnLoginComplete);
- }
-
- ///
- /// Apply how LoginScene should look after a logout flow.
- /// Call this when LoginScene is already loaded (additive) and you need to switch UI without reloading the scene.
- ///
- public void ApplyLoginEntry(BrewMonster.Network.LogoutFlowState.LoginEntryTarget entry)
- {
- _loginInProgress = false;
- if (_loginButton != null) _loginButton.interactable = true;
-
- // Always refresh fields from PlayerPrefs (LogoutAccount clears them).
- if (_usernameInputField != null) _usernameInputField.text = PlayerPrefs.GetString("username", "");
- if (_passwordInputField != null) _passwordInputField.text = PlayerPrefs.GetString("password", "");
-
- if (_selectCharacterScreen != null)
- _selectCharacterScreen.gameObject.SetActive(false);
-
- 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))
- {
- BMLogger.Log("[LoginScreenUI] Auto-login triggered (return-to-select-role).");
- _loginInProgress = true;
- if (_loginButton != null) _loginButton.interactable = false;
- _ = BeginGameLoginAsync(_usernameInputField.text, _passwordInputField.text);
- }
- }
- }
-
- ///
- /// Callback when the login is complete.
- /// Then get the list of characters
- ///
- private void OnLoginComplete(bool result)
- {
- BMLogger.Log($"[LoginScreenUI] OnLoginComplete result={result}");
- if (!result)
- {
- BMLogger.LogError("Login failed");
- if (_selectCharacterScreen != null)
- _selectCharacterScreen.gameObject.SetActive(false);
- _loginInProgress = false;
- if (_loginButton != null) _loginButton.interactable = true;
- return;
- }
-
- if (_selectCharacterScreen != null)
- _selectCharacterScreen.gameObject.SetActive(true);
- UnityGameSession.GetRoleListAsync(OnGetRoleListComplete);
- }
-
- ///
- /// Callback when the list of characters is retrieved.
- /// Then move to the select character screen
- ///
- private void OnGetRoleListComplete(List roleInfos)
- {
- if (roleInfos == null)
- {
- BMLogger.LogError("OnGetRoleListComplete: roleInfos is null");
- // Keep whatever is currently shown; don't overwrite UI state with null.
- _loginInProgress = false;
- if (_loginButton != null) _loginButton.interactable = true;
- return;
- }
-
- // Merge pending created role in case backend list hasn't updated yet.
- if (_pendingCreatedRole != null)
- {
- bool exists = false;
- for (int i = 0; i < roleInfos.Count; i++)
- {
- if (roleInfos[i].roleid == _pendingCreatedRole.roleid)
- {
- exists = true;
- break;
- }
- }
-
- if (!exists)
- {
- // Copy list so we don't mutate a list owned elsewhere.
- var merged = new List(roleInfos.Count + 1);
- merged.AddRange(roleInfos);
- merged.Add(_pendingCreatedRole);
- roleInfos = merged;
- }
- else
- {
- // Backend now includes the role; clear pending.
- _pendingCreatedRole = null;
- }
- }
-
- BMLogger.Log($"OnGetRoleListComplete: roles={roleInfos.Count}");
- _roleInfos = roleInfos;
- _currentRoles = roleInfos;
-
- // Login flow finished; keep login button disabled (origin-like) once you're at select role.
- _loginInProgress = false;
- }
-
- private void OnClickSelectCharacter(RoleInfo roleInfo)
- {
- LoadingSceneController.Instance.ShowLoadingScene(true);
- UnityGameSession.SelectRoleAsync(roleInfo, OnSelectRoleComplete);
- }
-
- ///
- /// Callback when a new character is created.
- /// Refreshes the role list and keeps the character selection screen visible.
- ///
- private void OnCreateCharacterComplete(RoleInfo createdRole)
- {
- BMLogger.Log("Character created, refreshing role list...");
- if (_selectCharacterScreen != null)
- {
- _selectCharacterScreen.gameObject.SetActive(true);
- }
-
- // Ensure the newly created role is visible immediately even if the server role list
- // hasn't updated yet.
- if (createdRole != null)
- {
- _pendingCreatedRole = createdRole;
- if (_currentRoles == null)
- {
- _currentRoles = new List();
- }
-
- bool exists = false;
- for (int i = 0; i < _currentRoles.Count; i++)
- {
- if (_currentRoles[i].roleid == createdRole.roleid)
- {
- exists = true;
- break;
- }
- }
-
- if (!exists)
- {
- _currentRoles.Add(createdRole);
- }
-
- _roleInfos = _currentRoles;
- }
- else
- {
- BMLogger.LogError("OnCreateCharacterComplete: createdRole is null (create-role callback returned null)");
- }
-
- // NOTE:
- // Immediately requesting the role list after create has been observed to disconnect
- // in some server builds. We rely on the createdRole callback to update UI instantly.
- // A server sync can be done later (e.g., next time you open this screen / re-login).
- }
-
- private void OnSelectRoleComplete(RoleInfo roleInfo)
- {
- context.Post(_ =>
- {
- isDoneWorldRender = false;
- isDoneNPCRender = false;
- Action actLoadChar = () =>
- {
- if (!isDoneNPCRender || !isDoneWorldRender)
- {
- return;
- }
- };
- SceneLoader.SceneLoadProcess = SceneLoadProcess.Loading;
- SceneLoader.LoadingProgress = 0;
-#if TESTFAST
- string nameScene = "LoginScene";
- SceneManager.UnloadSceneAsync(nameScene);
- isDoneNPCRender = true;
- isDoneWorldRender = true;
- actLoadChar?.Invoke();
- WorldMusicController.Instance.InitForWorld(roleInfo.worldtag);
- UnityGameSession.EnterWorldAsync(roleInfo, OnEnterWorldComplete);
-#else
- string nameScene = UnityGameSession.Instance.GetWorldInstanceName();
- UnityGameSession.Instance.LoadScene(nameScene, LoadSceneMode.Single,
- (progress) =>
- {
- LoadingSceneController.Instance.SetProgress(progress);
- },
- (value) =>
- {
- isDoneWorldRender = value;
- isDoneNPCRender = true;
- isDoneWorldRender = true;
- actLoadChar?.Invoke();
- WorldMusicController.Instance.InitForWorld(roleInfo.worldtag);
- UnityGameSession.EnterWorldAsync(roleInfo, OnEnterWorldComplete);
- });
-#endif
- }, null);
- }
-
- private async void OnEnterWorldComplete()
- {
- await Task.Delay(2000);
- // Request all known packages: 0=Inventory,1=Equipment,2=Task
- UnityGameSession.RequestAllInventoriesAsync(() => { /*BMLogger.Log("Sent Inventory Detail Requests (all packs)");*/ }, 0, 1, 2);
- await Task.Delay(1000);
- UnityGameSession.RequestCheckSecurityPassWd("");
- // C++ friend_GetList(); required before Add Friend
- await Task.Delay(1000);
- UnityGameSession.Friend_GetList();
- }
-
- //private void OnInventoryReceived(List inventoryData)
- //{
- // _inventoryUI.DisplayInventory(inventoryData);
- //}
-
-#if UNITY_EDITOR
- private void OnValidate()
- {
- if (_usernameInputField == null)
- {
- // find childrend with name "username"
- _usernameInputField = transform.Find("username").GetComponent();
- }
-
- if (_passwordInputField == null)
- {
- // find childrend with name "password"
- _passwordInputField = transform.Find("password").GetComponent();
- }
-
- if (_loginButton == null)
- {
- // find childrend with name "LoginBtn"
- _loginButton = transform.Find("LoginBtn").GetComponent