diff --git a/Assets/AddressableAssetsData/AssetGroups/music.asset b/Assets/AddressableAssetsData/AssetGroups/music.asset index 18d9df907f..de69b3fb66 100644 --- a/Assets/AddressableAssetsData/AssetGroups/music.asset +++ b/Assets/AddressableAssetsData/AssetGroups/music.asset @@ -16,12 +16,12 @@ MonoBehaviour: m_GUID: afbd6624ad1875d42b7e3e6259565fe2 m_SerializeEntries: - m_GUID: 4e04b7b226d1cc5478651828e338a72e - m_Address: music/ambiencestereo/daynormal2.wav + m_Address: ambiencestereo/daynormal2.wav m_ReadOnly: 0 m_SerializedLabels: [] FlaggedDuringContentUpdateRestriction: 0 - m_GUID: 5b2928e4e95f7e949a10194be3a7a609 - m_Address: "music/2014/\u65B0\u624B\u6751/\u5176\u4ED6\u5730\u533A.mp3" + m_Address: "2014/\u65B0\u624B\u6751/\u5176\u4ED6\u5730\u533A.mp3" m_ReadOnly: 0 m_SerializedLabels: [] FlaggedDuringContentUpdateRestriction: 0 diff --git a/Assets/PerfectWorld/Scene/LoginScene.unity b/Assets/PerfectWorld/Scene/LoginScene.unity index dd1cee2e1d..857b9fb373 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:1b73830dda2d83841e5ab247afef32f0802c6fd82fefd3b768af57ee7bc35f17 -size 118175 +oid sha256:631a23da0c12055ef55e35b21fac40b752599bdbb43d0d2c1c7fd90674d4b791 +size 118853 diff --git a/Assets/PerfectWorld/ScriptableObjects/Music.meta b/Assets/PerfectWorld/ScriptableObjects/Music.meta new file mode 100644 index 0000000000..5e01174e8c --- /dev/null +++ b/Assets/PerfectWorld/ScriptableObjects/Music.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e2311d4689c402744abcee34fcd3d14e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PerfectWorld/ScriptableObjects/Music/WorldMusicDatabase.asset b/Assets/PerfectWorld/ScriptableObjects/Music/WorldMusicDatabase.asset new file mode 100644 index 0000000000..c7d104d941 --- /dev/null +++ b/Assets/PerfectWorld/ScriptableObjects/Music/WorldMusicDatabase.asset @@ -0,0 +1,18 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f2e6193e2f88fb48961a9fecd3a405c, type: 3} + m_Name: WorldMusicDatabase + m_EditorClassIdentifier: + entries: + - worldTag: 161 + bgmPath: "2014/\u65B0\u624B\u6751/\u5176\u4ED6\u5730\u533A.mp3 " + ambiencePath: ambiencestereo/daynormal2.wav diff --git a/Assets/PerfectWorld/ScriptableObjects/Music/WorldMusicDatabase.asset.meta b/Assets/PerfectWorld/ScriptableObjects/Music/WorldMusicDatabase.asset.meta new file mode 100644 index 0000000000..76cc454bd5 --- /dev/null +++ b/Assets/PerfectWorld/ScriptableObjects/Music/WorldMusicDatabase.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7602c1f71697aae42a7751212c5144dc +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PerfectWorld/Scripts/Addressable/AddressableManager.cs b/Assets/PerfectWorld/Scripts/Addressable/AddressableManager.cs index b47d5bca61..261c2e2f3d 100644 --- a/Assets/PerfectWorld/Scripts/Addressable/AddressableManager.cs +++ b/Assets/PerfectWorld/Scripts/Addressable/AddressableManager.cs @@ -243,7 +243,7 @@ namespace BrewMonster.Scripts try { - var handle = Addressables.LoadAssetAsync(assetPath); + var handle = Addressables.LoadAssetAsync(assetPath.Trim()); await handle.Task; if (handle.OperationException != null) { diff --git a/Assets/PerfectWorld/Scripts/Sound/AudioManager.cs b/Assets/PerfectWorld/Scripts/Sound/AudioManager.cs index af0ec4413d..6dab128068 100644 --- a/Assets/PerfectWorld/Scripts/Sound/AudioManager.cs +++ b/Assets/PerfectWorld/Scripts/Sound/AudioManager.cs @@ -1,11 +1,19 @@ using System.Collections; +using BrewMonster; using UnityEngine; +using UnityEngine.Audio; public class AudioManager : MonoBehaviour { public static AudioManager Instance; + public AudioSource bgmSource; + [SerializeField] private AudioMixerGroup _bgmMixerGroup; + [SerializeField] private AudioMixerGroup _ambienceMixerGroup; + + private AudioSource _ambienceSource; + void Awake() { if (Instance == null) @@ -13,23 +21,51 @@ public class AudioManager : MonoBehaviour Instance = this; DontDestroyOnLoad(gameObject); } - else Destroy(gameObject); + else + { + Destroy(gameObject); + return; + } + + DiscoverAudioSources(); } + private void DiscoverAudioSources() + { + var sources = GetComponents(); + + if (sources.Length > 0) + { + bgmSource = sources[0]; + if (_bgmMixerGroup != null) + bgmSource.outputAudioMixerGroup = _bgmMixerGroup; + } + + if (sources.Length > 1) + { + _ambienceSource = sources[1]; + if (_ambienceMixerGroup != null) + _ambienceSource.outputAudioMixerGroup = _ambienceMixerGroup; + } + } + + // ── BGM ────────────────────────────────────────────────────────────────── + public void PlayBGM(AudioClip clip, float fadeTime = 1f) { StartCoroutine(FadeInBGM(clip, fadeTime)); } + public void StopBGM(float fadeTime = 1f) { - StartCoroutine(FadeOut(fadeTime)); + StartCoroutine(FadeOutBGM(fadeTime)); } - IEnumerator FadeInBGM(AudioClip clip, float fadeTime) { + BMLogger.LogError($"HoangDev: FadeInBGM fadeTime {fadeTime} clip: {clip.name}" ); if (bgmSource.isPlaying) - yield return StartCoroutine(FadeOut(fadeTime)); + yield return StartCoroutine(FadeOutBGM(fadeTime)); bgmSource.clip = clip; bgmSource.Play(); @@ -38,21 +74,77 @@ public class AudioManager : MonoBehaviour while (t < fadeTime) { t += Time.deltaTime; - bgmSource.volume = Mathf.Lerp(0, 1, t / fadeTime); + bgmSource.volume = Mathf.Lerp(0f, 1f, t / fadeTime); yield return null; } + bgmSource.volume = 1f; } - IEnumerator FadeOut(float fadeTime) + IEnumerator FadeOutBGM(float fadeTime) { float startVol = bgmSource.volume; float t = 0f; while (t < fadeTime) { t += Time.deltaTime; - bgmSource.volume = Mathf.Lerp(startVol, 0, t / fadeTime); + bgmSource.volume = Mathf.Lerp(startVol, 0f, t / fadeTime); yield return null; } + bgmSource.volume = 0f; bgmSource.Stop(); } -} \ No newline at end of file + + // ── World music (BGM + ambience together) ──────────────────────────────── + + public void PlayWorldMusic(AudioClip bgm, AudioClip ambience, float fadeTime = 1f) + { + if (bgm != null) + StartCoroutine(FadeInBGM(bgm, fadeTime)); + + if (ambience != null && _ambienceSource != null) + StartCoroutine(FadeInAmbience(ambience, fadeTime)); + } + + public void StopWorldMusic(float fadeTime = 1f) + { + StopBGM(fadeTime); + + if (_ambienceSource != null && _ambienceSource.isPlaying) + StartCoroutine(FadeOutAmbience(fadeTime)); + } + + // ── Ambience ───────────────────────────────────────────────────────────── + + IEnumerator FadeInAmbience(AudioClip clip, float fadeTime) + { + if (_ambienceSource.isPlaying) + yield return StartCoroutine(FadeOutAmbience(fadeTime)); + + _ambienceSource.clip = clip; + _ambienceSource.loop = true; + _ambienceSource.Play(); + + float t = 0f; + while (t < fadeTime) + { + t += Time.deltaTime; + _ambienceSource.volume = Mathf.Lerp(0f, 1f, t / fadeTime); + yield return null; + } + _ambienceSource.volume = 1f; + } + + IEnumerator FadeOutAmbience(float fadeTime) + { + float startVol = _ambienceSource.volume; + float t = 0f; + while (t < fadeTime) + { + t += Time.deltaTime; + _ambienceSource.volume = Mathf.Lerp(startVol, 0f, t / fadeTime); + yield return null; + } + _ambienceSource.volume = 0f; + _ambienceSource.Stop(); + } +} diff --git a/Assets/PerfectWorld/Scripts/Sound/TemporaryBackgroundMusic.cs b/Assets/PerfectWorld/Scripts/Sound/TemporaryBackgroundMusic.cs index 38b3c44f67..326ff7878d 100644 --- a/Assets/PerfectWorld/Scripts/Sound/TemporaryBackgroundMusic.cs +++ b/Assets/PerfectWorld/Scripts/Sound/TemporaryBackgroundMusic.cs @@ -50,8 +50,8 @@ namespace BrewMonster.Scripts private async UniTask PlayBackgroundMusic() { // use AddressableManager to load the background music and ambient sound - var backgroundMusicClip = await AddressableManager.Instance.LoadAudioClipAsync(backgroundMusicPath.ToLower()); - var ambientSoundClip = await AddressableManager.Instance.LoadAudioClipAsync(ambientSoundPath.ToLower()); + var backgroundMusicClip = await AddressableManager.Instance.LoadAudioClipAsync(backgroundMusicPath.Trim()); + var ambientSoundClip = await AddressableManager.Instance.LoadAudioClipAsync(ambientSoundPath.Trim()); // play the background music and ambient sound backgroundMusicSource.clip = backgroundMusicClip; diff --git a/Assets/PerfectWorld/Scripts/Sound/WorldMusicController.cs b/Assets/PerfectWorld/Scripts/Sound/WorldMusicController.cs new file mode 100644 index 0000000000..74a0228acf --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Sound/WorldMusicController.cs @@ -0,0 +1,64 @@ +using Cysharp.Threading.Tasks; +using UnityEngine; + +namespace BrewMonster.Scripts +{ + /// + /// Lives on the persistent "Music" GameObject alongside AudioManager. + /// Waits for the host player to be ready (mirroring TemporaryBackgroundMusic), + /// then loads BGM + ambience clips via Addressables and plays them through AudioManager. + /// + public class WorldMusicController : MonoBehaviour + { + public static WorldMusicController Instance; + + [SerializeField] private WorldMusicDatabaseSO _worldMusicDB; + + void Awake() + { + if (Instance == null) + { + Instance = this; + DontDestroyOnLoad(gameObject); + } + else + { + Destroy(this); + } + } + + /// + /// Called from LoginScreenUI when the player selects a character. + /// Kicks off the async setup without blocking the caller. + /// + public void InitForWorld(int worldTag) + { + BMLogger.LogError("HoangDev: InitForWorld worldTag:"+worldTag); + AudioManager.Instance.StopBGM(1f); + SetupAudioSources(worldTag).Forget(); + } + + private async UniTask SetupAudioSources(int worldTag) + { + var hostPlayer = CECGameRun.Instance?.GetHostPlayer(); + while (hostPlayer == null) + { + await UniTask.DelayFrame(1); + hostPlayer = CECGameRun.Instance?.GetHostPlayer(); + } + + var entry = _worldMusicDB?.Lookup(worldTag); + if (entry == null) + { + Debug.LogWarning($"[WorldMusicController] No music entry found for worldTag {worldTag}"); + return; + } + BMLogger.LogError($"HoangDev: SetupAudioSources entry.bgmPath.ToLower():{entry.bgmPath.ToLower()}"); + BMLogger.LogError($"HoangDev: SetupAudioSources entry.ambiencePath.ToLower()):{entry.ambiencePath.ToLower()}"); + var bgmClip = await AddressableManager.Instance.LoadAudioClipAsync(entry.bgmPath.ToLower()); + var ambienceClip = await AddressableManager.Instance.LoadAudioClipAsync(entry.ambiencePath.ToLower()); + + AudioManager.Instance.PlayWorldMusic(bgmClip, ambienceClip, 2f); + } + } +} diff --git a/Assets/PerfectWorld/Scripts/Sound/WorldMusicController.cs.meta b/Assets/PerfectWorld/Scripts/Sound/WorldMusicController.cs.meta new file mode 100644 index 0000000000..892f48a364 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Sound/WorldMusicController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f520c80aab01bbc45aa22010a90dfd66 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Sound/WorldMusicDatabaseSO.cs b/Assets/PerfectWorld/Scripts/Sound/WorldMusicDatabaseSO.cs new file mode 100644 index 0000000000..0ad1ca130d --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Sound/WorldMusicDatabaseSO.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace BrewMonster.Scripts +{ + [System.Serializable] + public class WorldMusicEntry + { + [Tooltip("Matches RoleInfo.worldtag from the server (e.g. 161 for a61, 169 for a69)")] + public int worldTag; + + [Tooltip("Addressable address for the BGM audio clip")] + public string bgmPath; + + [Tooltip("Addressable address for the ambience audio clip")] + public string ambiencePath; + } + + [CreateAssetMenu(fileName = "WorldMusicDatabase", menuName = "PerfectWorld/World Music Database")] + public class WorldMusicDatabaseSO : ScriptableObject + { + public List entries = new List(); + + public WorldMusicEntry Lookup(int worldTag) + => entries?.Find(e => e.worldTag == worldTag); + } +} diff --git a/Assets/PerfectWorld/Scripts/Sound/WorldMusicDatabaseSO.cs.meta b/Assets/PerfectWorld/Scripts/Sound/WorldMusicDatabaseSO.cs.meta new file mode 100644 index 0000000000..d0147d848c --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Sound/WorldMusicDatabaseSO.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5f2e6193e2f88fb48961a9fecd3a405c \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs b/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs index f7d60a9af3..b9f3def29b 100644 --- a/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs +++ b/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs @@ -1,439 +1,440 @@ -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(); - 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(); - 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