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;
[Header("Audio")]
[Tooltip("Set 3 clips to pick a random login theme.")]
[SerializeField] private AudioClip[] _loginBGMs;
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()
{
var chosenBgm = PickLoginBGM();
if (chosenBgm != null && AudioManager.Instance != null)
{
AudioManager.Instance.PlayBGM(chosenBgm, 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());
}
private AudioClip PickLoginBGM()
{
if (_loginBGMs != null && _loginBGMs.Length > 0)
{
// UnityEngine.Random is deterministic per session and good enough here.
int idx = UnityEngine.Random.Range(0, _loginBGMs.Length);
return _loginBGMs[idx];
}
return null;
}
// 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);
}
// NOTE: Previously had an ApplyLoginEntry helper that referenced LogoutFlowState.
// It was unused and caused build/lint issues on some platforms, so it's been removed.
///
/// 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();
AudioManager.Instance.StopBGM(1f);
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