feedback report 3 done
This commit is contained in:
@@ -71,6 +71,10 @@ public class CupidConnector : MonoBehaviour
|
|||||||
NetworkManager.singleton.StartServer();
|
NetworkManager.singleton.StartServer();
|
||||||
#else
|
#else
|
||||||
//Client code
|
//Client code
|
||||||
|
// Practice starts its own localhost host+client on a free port.
|
||||||
|
if (GameTutorialManager.tutorialModeEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
if (Cupid.RoomPort < 0)
|
if (Cupid.RoomPort < 0)
|
||||||
{
|
{
|
||||||
Logger.Log("Invalid Room port");
|
Logger.Log("Invalid Room port");
|
||||||
|
|||||||
@@ -94,6 +94,13 @@ public class CupidLobby : MonoBehaviour
|
|||||||
{
|
{
|
||||||
RefreshMatchmakingPanel();
|
RefreshMatchmakingPanel();
|
||||||
|
|
||||||
|
if (Application.internetReachability == NetworkReachability.NotReachable)
|
||||||
|
{
|
||||||
|
Logger.Log("Matchmaker unreachable: no internet");
|
||||||
|
StopMatchmakingNoInternet();
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
using (UnityWebRequest req = UnityWebRequest.Get(Cupid.CupidURI + "/"))
|
using (UnityWebRequest req = UnityWebRequest.Get(Cupid.CupidURI + "/"))
|
||||||
{
|
{
|
||||||
req.downloadHandler = new DownloadHandlerBuffer();
|
req.downloadHandler = new DownloadHandlerBuffer();
|
||||||
@@ -101,6 +108,13 @@ public class CupidLobby : MonoBehaviour
|
|||||||
yield return req.SendWebRequest();
|
yield return req.SendWebRequest();
|
||||||
|
|
||||||
string text = req.downloadHandler != null ? req.downloadHandler.text : "";
|
string text = req.downloadHandler != null ? req.downloadHandler.text : "";
|
||||||
|
if (req.result == UnityWebRequest.Result.ConnectionError)
|
||||||
|
{
|
||||||
|
Logger.Log("Matchmaker unreachable: " + req.error + " body: " + text);
|
||||||
|
StopMatchmakingNoInternet();
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
if (req.result != UnityWebRequest.Result.Success)
|
if (req.result != UnityWebRequest.Result.Success)
|
||||||
{
|
{
|
||||||
Logger.Log("Matchmaker poll failed: " + req.error + " body: " + text);
|
Logger.Log("Matchmaker poll failed: " + req.error + " body: " + text);
|
||||||
@@ -200,6 +214,15 @@ public class CupidLobby : MonoBehaviour
|
|||||||
RefreshMatchmakingPanel();
|
RefreshMatchmakingPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StopMatchmakingNoInternet()
|
||||||
|
{
|
||||||
|
matchmaking = false;
|
||||||
|
RefreshMatchmakingPanel();
|
||||||
|
if (MainMenuManager.instance != null)
|
||||||
|
MainMenuManager.instance.ShowMatchmakingNoInternet();
|
||||||
|
StartCoroutine(CancelMatchmake());
|
||||||
|
}
|
||||||
|
|
||||||
void RefreshMatchmakingPanel()
|
void RefreshMatchmakingPanel()
|
||||||
{
|
{
|
||||||
MatchmakingUI.SetActive(matchmaking);
|
MatchmakingUI.SetActive(matchmaking);
|
||||||
|
|||||||
@@ -17608,6 +17608,7 @@ MonoBehaviour:
|
|||||||
waitingForOpponentPanel: {fileID: 455168947}
|
waitingForOpponentPanel: {fileID: 455168947}
|
||||||
timeoutForOpponentPanel: {fileID: 1527762872}
|
timeoutForOpponentPanel: {fileID: 1527762872}
|
||||||
btnLeaveGame: {fileID: 1615513061}
|
btnLeaveGame: {fileID: 1615513061}
|
||||||
|
btnLeavePrematureGame: {fileID: 1799677546}
|
||||||
btnEmote: {fileID: 95546039}
|
btnEmote: {fileID: 95546039}
|
||||||
emotesPanel: {fileID: 261412632}
|
emotesPanel: {fileID: 261412632}
|
||||||
textEmotesParent: {fileID: 1806896278}
|
textEmotesParent: {fileID: 1806896278}
|
||||||
@@ -23060,6 +23061,10 @@ PrefabInstance:
|
|||||||
propertyPath: m_Name
|
propertyPath: m_Name
|
||||||
value: StatCanvas
|
value: StatCanvas
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 7210299847814502381, guid: 400992651824baf47abb64c18227c556, type: 3}
|
||||||
|
propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.size
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
m_RemovedComponents: []
|
m_RemovedComponents: []
|
||||||
m_RemovedGameObjects: []
|
m_RemovedGameObjects: []
|
||||||
m_AddedGameObjects: []
|
m_AddedGameObjects: []
|
||||||
|
|||||||
+10864
-5
File diff suppressed because it is too large
Load Diff
@@ -46,6 +46,7 @@ public class GameCanvas : MonoBehaviour
|
|||||||
public GameObject waitingForOpponentPanel;
|
public GameObject waitingForOpponentPanel;
|
||||||
public GameObject timeoutForOpponentPanel;
|
public GameObject timeoutForOpponentPanel;
|
||||||
public Button btnLeaveGame;
|
public Button btnLeaveGame;
|
||||||
|
public Button btnLeavePrematureGame;
|
||||||
|
|
||||||
[Header("Emotes")]
|
[Header("Emotes")]
|
||||||
public Button btnEmote;
|
public Button btnEmote;
|
||||||
@@ -70,6 +71,12 @@ public class GameCanvas : MonoBehaviour
|
|||||||
if (btnLeaveGame != null)
|
if (btnLeaveGame != null)
|
||||||
btnLeaveGame.onClick.AddListener(OnLeaveGamePressed);
|
btnLeaveGame.onClick.AddListener(OnLeaveGamePressed);
|
||||||
|
|
||||||
|
if (btnLeavePrematureGame == null && timeoutForOpponentPanel != null)
|
||||||
|
btnLeavePrematureGame = timeoutForOpponentPanel.GetComponentInChildren<Button>(true);
|
||||||
|
|
||||||
|
if (btnLeavePrematureGame != null)
|
||||||
|
btnLeavePrematureGame.onClick.AddListener(ConfirmLeaveMatch);
|
||||||
|
|
||||||
if(btnEmote != null)
|
if(btnEmote != null)
|
||||||
btnEmote.onClick.AddListener(OnEmotePressed);
|
btnEmote.onClick.AddListener(OnEmotePressed);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine.Networking;
|
using UnityEngine.Networking;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
using Mirror;
|
||||||
public class GameOverCanvas : MonoBehaviour
|
public class GameOverCanvas : MonoBehaviour
|
||||||
{
|
{
|
||||||
public bool showOnLoad = false;
|
public bool showOnLoad = false;
|
||||||
@@ -61,6 +62,9 @@ public class GameOverCanvas : MonoBehaviour
|
|||||||
const int BetLimitUnlimited = 500;
|
const int BetLimitUnlimited = 500;
|
||||||
|
|
||||||
int rematchOpponentBetLimitCoins = BetLimitUnlimited;
|
int rematchOpponentBetLimitCoins = BetLimitUnlimited;
|
||||||
|
bool opponentLeftDuringRematch;
|
||||||
|
bool gameOverVisible;
|
||||||
|
bool rematchRequestLocalPending;
|
||||||
|
|
||||||
[Header("Rematch panel")]
|
[Header("Rematch panel")]
|
||||||
public CanvasGroup rematchPanel;
|
public CanvasGroup rematchPanel;
|
||||||
@@ -258,21 +262,78 @@ public class GameOverCanvas : MonoBehaviour
|
|||||||
return;
|
return;
|
||||||
if (txtBetLimitWarning != null)
|
if (txtBetLimitWarning != null)
|
||||||
txtBetLimitWarning.gameObject.SetActive(false);
|
txtBetLimitWarning.gameObject.SetActive(false);
|
||||||
NetPlayer.localPlayer.RequestRematch();
|
|
||||||
SetRematchPendingState(true);
|
SetRematchPendingState(true);
|
||||||
|
NetPlayer.localPlayer.RequestRematch();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnRematchRequested(int opponentMaxBetLimitCoins){
|
public void OnRematchRequested(int opponentMaxBetLimitCoins){
|
||||||
|
if (opponentLeftDuringRematch || AloneInRoom())
|
||||||
|
{
|
||||||
|
OnRematchCancelled(NetPlayer.OpponentLeftRematchMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
rematchOpponentBetLimitCoins = opponentMaxBetLimitCoins;
|
rematchOpponentBetLimitCoins = opponentMaxBetLimitCoins;
|
||||||
if(winningTeam == myTeam){
|
if(winningTeam == myTeam){
|
||||||
//Only the loser can initiate the rematch; winner sets the stake
|
//Only the loser can initiate the rematch; winner sets the stake
|
||||||
ShowRematchPanel();
|
ShowRematchPanel();
|
||||||
}else{
|
}else{
|
||||||
rematchWarningLoser.text = "Waiting for opponent to accept";
|
rematchWarningLoser.text = "Waiting for opponent to accept";
|
||||||
|
StartCoroutine(CoroutineWaitForOpponentToAcceptRematch());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opponent's player object was destroyed while this client is still in the match.
|
||||||
|
/// </summary>
|
||||||
|
public void NotifyOpponentDisconnected()
|
||||||
|
{
|
||||||
|
if (!gameOverVisible || opponentLeftDuringRematch || !NetworkClient.isConnected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (rematchRequestLocalPending || IsWaitingForOpponent() || IsRematchPanelOpen())
|
||||||
|
{
|
||||||
|
OnRematchCancelled(NetPlayer.OpponentLeftRematchMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
opponentLeftDuringRematch = true;
|
||||||
|
UpdateRematchAvailability();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator CoroutineWaitForOpponentToAcceptRematch(){
|
||||||
|
var wait = new WaitForSeconds(0.5f);
|
||||||
|
while(IsWaitingForOpponent()){
|
||||||
|
if (AloneInRoom())
|
||||||
|
{
|
||||||
|
OnRematchCancelled(NetPlayer.OpponentLeftRematchMessage);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return wait;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsWaitingForOpponent()
|
||||||
|
{
|
||||||
|
return rematchWarningLoser != null
|
||||||
|
&& rematchWarningLoser.text.ToLowerInvariant().Contains("waiting");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsRematchPanelOpen()
|
||||||
|
{
|
||||||
|
return rematchPanel != null && rematchPanel.alpha > 0.01f;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AloneInRoom()
|
||||||
|
{
|
||||||
|
if (!NetworkClient.isConnected)
|
||||||
|
return false;
|
||||||
|
return FindObjectsByType<NetPlayer>(FindObjectsSortMode.None).Length <= 1;
|
||||||
|
}
|
||||||
|
|
||||||
void SetRematchPendingState(bool val){
|
void SetRematchPendingState(bool val){
|
||||||
|
rematchRequestLocalPending = val;
|
||||||
btnRematch.interactable = false;
|
btnRematch.interactable = false;
|
||||||
btnLeave.interactable = !val;
|
btnLeave.interactable = !val;
|
||||||
if(val){
|
if(val){
|
||||||
@@ -386,6 +447,7 @@ public class GameOverCanvas : MonoBehaviour
|
|||||||
|
|
||||||
float rcPrize = 0;
|
float rcPrize = 0;
|
||||||
public void Show(Team winningTeam, Team myTeam, int redScore, int blueScore){
|
public void Show(Team winningTeam, Team myTeam, int redScore, int blueScore){
|
||||||
|
gameOverVisible = true;
|
||||||
Setup(winningTeam, myTeam, redScore, blueScore);
|
Setup(winningTeam, myTeam, redScore, blueScore);
|
||||||
|
|
||||||
canvasGroup.blocksRaycasts = true;
|
canvasGroup.blocksRaycasts = true;
|
||||||
@@ -525,18 +587,29 @@ public class GameOverCanvas : MonoBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void OnRematchCancelled(){
|
public void OnRematchCancelled(string message = NetPlayer.RematchDeclinedMessage){
|
||||||
|
if (message == NetPlayer.OpponentLeftRematchMessage && opponentLeftDuringRematch)
|
||||||
|
return;
|
||||||
|
|
||||||
|
rematchRequestLocalPending = false;
|
||||||
|
if (message == NetPlayer.OpponentLeftRematchMessage)
|
||||||
|
opponentLeftDuringRematch = true;
|
||||||
|
|
||||||
btnRematch.interactable=false;
|
btnRematch.interactable=false;
|
||||||
btnLeave.interactable=true;
|
btnLeave.interactable=true;
|
||||||
|
|
||||||
rematchWarningLoser.text = "Rematch declined.";
|
if (rematchWarningLoser != null)
|
||||||
|
rematchWarningLoser.text = string.IsNullOrEmpty(message) ? NetPlayer.RematchDeclinedMessage : message;
|
||||||
StartCoroutine(CoroutineHideRematchPanel());
|
StartCoroutine(CoroutineHideRematchPanel());
|
||||||
|
|
||||||
|
if (countdownCoroutine != null)
|
||||||
|
StopCoroutine(countdownCoroutine);
|
||||||
countdownCoroutine = StartCoroutine(CoroutineCountdown());
|
countdownCoroutine = StartCoroutine(CoroutineCountdown());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnRematchFailed(string errorMessage)
|
public void OnRematchFailed(string errorMessage)
|
||||||
{
|
{
|
||||||
|
rematchRequestLocalPending = false;
|
||||||
btnRematch.interactable = true;
|
btnRematch.interactable = true;
|
||||||
btnLeave.interactable = true;
|
btnLeave.interactable = true;
|
||||||
if (rematchWarningLoser != null)
|
if (rematchWarningLoser != null)
|
||||||
@@ -611,6 +684,14 @@ public class GameOverCanvas : MonoBehaviour
|
|||||||
|
|
||||||
void UpdateRematchAvailability()
|
void UpdateRematchAvailability()
|
||||||
{
|
{
|
||||||
|
if (opponentLeftDuringRematch)
|
||||||
|
{
|
||||||
|
btnRematch.interactable = false;
|
||||||
|
if (rematchWarningLoser != null)
|
||||||
|
rematchWarningLoser.text = NetPlayer.OpponentLeftRematchMessage;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (GameTutorialManager.tutorialModeEnabled)
|
if (GameTutorialManager.tutorialModeEnabled)
|
||||||
{
|
{
|
||||||
btnRematch.interactable = false;
|
btnRematch.interactable = false;
|
||||||
|
|||||||
@@ -84,6 +84,14 @@ public class GameTutorialManager : MonoBehaviour
|
|||||||
{
|
{
|
||||||
NetManager.StopNetworkingForSceneChange();
|
NetManager.StopNetworkingForSceneChange();
|
||||||
|
|
||||||
|
int port = GetAvailablePort.GetAvailableUdpPort();
|
||||||
|
Transport transport = NetworkManager.singleton != null
|
||||||
|
? NetworkManager.singleton.transport
|
||||||
|
: Transport.active;
|
||||||
|
if (transport is PortTransport portTransport)
|
||||||
|
portTransport.Port = (ushort)port;
|
||||||
|
Logger.Log("Practice host starting on port " + port);
|
||||||
|
|
||||||
NetworkManager.singleton.networkAddress = "localhost";
|
NetworkManager.singleton.networkAddress = "localhost";
|
||||||
NetworkManager.singleton.StartHost();
|
NetworkManager.singleton.StartHost();
|
||||||
|
|
||||||
|
|||||||
+407
-106
@@ -3,6 +3,7 @@ using System.Collections;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
#endif
|
#endif
|
||||||
@@ -16,12 +17,14 @@ public class LoginManager : MonoBehaviour
|
|||||||
{
|
{
|
||||||
public const string AuthBaseUrl = "https://kkapi.playpoolstudios.com";
|
public const string AuthBaseUrl = "https://kkapi.playpoolstudios.com";
|
||||||
const string PlayerPrefsAuthKey = "WalkInvest.AuthToken";
|
const string PlayerPrefsAuthKey = "WalkInvest.AuthToken";
|
||||||
|
const string PlayerPrefsDeviceIdKey = "WalkInvest.DeviceId";
|
||||||
|
const string AlreadyLoggedInCode = "already_logged_in";
|
||||||
|
|
||||||
const int MIN_USERNAME_LENGTH = 3;
|
const int MIN_USERNAME_LENGTH = 2;
|
||||||
const int MAX_USERNAME_LENGTH = 16;
|
const int MIN_REGISTER_PASSWORD_LENGTH = 6;
|
||||||
const int MIN_PASSWORD_LENGTH = 8;
|
|
||||||
const int MAX_PASSWORD_LENGTH = 20;
|
|
||||||
const int ShortAuthMessageMaxLength = 36;
|
const int ShortAuthMessageMaxLength = 36;
|
||||||
|
const string PlayerConnectionError = "Please check your internet connection and try again.";
|
||||||
|
const string PlayerGenericError = "Something went wrong. Please try again.";
|
||||||
|
|
||||||
[Header("Login")]
|
[Header("Login")]
|
||||||
[SerializeField] private TMP_InputField usernameInputLogin;
|
[SerializeField] private TMP_InputField usernameInputLogin;
|
||||||
@@ -55,6 +58,8 @@ public class LoginManager : MonoBehaviour
|
|||||||
|
|
||||||
bool _authInProgress;
|
bool _authInProgress;
|
||||||
Coroutine _keepaliveRoutine;
|
Coroutine _keepaliveRoutine;
|
||||||
|
bool _remoteLogoutInFlight;
|
||||||
|
bool _retireAfterRemoteLogout;
|
||||||
bool _appPaused;
|
bool _appPaused;
|
||||||
bool _appFocused = true;
|
bool _appFocused = true;
|
||||||
bool _handlingKeepaliveUnauthorized;
|
bool _handlingKeepaliveUnauthorized;
|
||||||
@@ -115,7 +120,10 @@ public class LoginManager : MonoBehaviour
|
|||||||
{
|
{
|
||||||
instance.StopKeepalive();
|
instance.StopKeepalive();
|
||||||
SceneManager.sceneLoaded -= instance.OnSceneLoaded;
|
SceneManager.sceneLoaded -= instance.OnSceneLoaded;
|
||||||
Destroy(instance.gameObject);
|
if (instance._remoteLogoutInFlight)
|
||||||
|
instance._retireAfterRemoteLogout = true;
|
||||||
|
else
|
||||||
|
Destroy(instance.gameObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
instance = this;
|
instance = this;
|
||||||
@@ -136,6 +144,10 @@ public class LoginManager : MonoBehaviour
|
|||||||
{
|
{
|
||||||
btnLogin.onClick.AddListener(OnLogin);
|
btnLogin.onClick.AddListener(OnLogin);
|
||||||
btnRegister.onClick.AddListener(OnRegister);
|
btnRegister.onClick.AddListener(OnRegister);
|
||||||
|
if (usernameInputLogin != null)
|
||||||
|
usernameInputLogin.characterLimit = 0;
|
||||||
|
if (usernameInputRegister != null)
|
||||||
|
usernameInputRegister.characterLimit = 0;
|
||||||
if (btnServerUpdate != null)
|
if (btnServerUpdate != null)
|
||||||
btnServerUpdate.onClick.AddListener(OnServerUpdateClick);
|
btnServerUpdate.onClick.AddListener(OnServerUpdateClick);
|
||||||
if (btnErrorClose != null)
|
if (btnErrorClose != null)
|
||||||
@@ -172,7 +184,7 @@ public class LoginManager : MonoBehaviour
|
|||||||
if (usernameInputLogin != null) usernameInputLogin.text = devUser;
|
if (usernameInputLogin != null) usernameInputLogin.text = devUser;
|
||||||
if (passwordInputLogin != null) passwordInputLogin.text = devPass;
|
if (passwordInputLogin != null) passwordInputLogin.text = devPass;
|
||||||
|
|
||||||
string loginJson = "{\"username\":\"" + EscapeJsonString(devUser) + "\",\"password\":\"" + EscapeJsonString(devPass) + "\"}";
|
string loginJson = BuildLoginBody(devUser, devPass);
|
||||||
// StartCoroutine(AuthPostCoroutine(AuthBaseUrl + "/auth/login", loginJson, "Signed in."));
|
// StartCoroutine(AuthPostCoroutine(AuthBaseUrl + "/auth/login", loginJson, "Signed in."));
|
||||||
SetBusy(false);
|
SetBusy(false);
|
||||||
#else
|
#else
|
||||||
@@ -244,21 +256,40 @@ public class LoginManager : MonoBehaviour
|
|||||||
PlayerPrefs.Save();
|
PlayerPrefs.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Clears session state and all PlayerPrefs (full logout).</summary>
|
/// <summary>Clears the server session, then local session state and PlayerPrefs.</summary>
|
||||||
public static void Logout()
|
public static void Logout()
|
||||||
{
|
{
|
||||||
|
if (MainMenuManager.instance != null)
|
||||||
|
MainMenuManager.instance.ClearWithdrawalAddress();
|
||||||
|
|
||||||
|
string token = AuthToken;
|
||||||
if (instance != null)
|
if (instance != null)
|
||||||
|
{
|
||||||
instance.StopKeepalive();
|
instance.StopKeepalive();
|
||||||
|
if (!string.IsNullOrEmpty(token))
|
||||||
|
{
|
||||||
|
instance._remoteLogoutInFlight = true;
|
||||||
|
instance.StartCoroutine(instance.RemoteLogoutCoroutine(token));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AuthToken = "";
|
AuthToken = "";
|
||||||
ClearLocalUserProfile();
|
ClearLocalUserProfile();
|
||||||
|
MainMenuManager.ClearCachedSettingsProfile();
|
||||||
ClearMatchYourTeam();
|
ClearMatchYourTeam();
|
||||||
OnUserDataUpdated = null;
|
OnUserDataUpdated = null;
|
||||||
Cupid.ClearSession();
|
Cupid.ClearSession();
|
||||||
CupidLobby.ClearUsername();
|
CupidLobby.ClearUsername();
|
||||||
GameManager.ClearMatchSession();
|
GameManager.ClearMatchSession();
|
||||||
|
if (LevelLoadManager.instance != null)
|
||||||
|
LevelLoadManager.instance.ResetSession();
|
||||||
NetPlayer.RedPlayer = null;
|
NetPlayer.RedPlayer = null;
|
||||||
NetPlayer.BluePlayer = null;
|
NetPlayer.BluePlayer = null;
|
||||||
|
|
||||||
|
string deviceId = PlayerPrefs.GetString(PlayerPrefsDeviceIdKey, "");
|
||||||
PlayerPrefs.DeleteAll();
|
PlayerPrefs.DeleteAll();
|
||||||
|
if (IsUsableDeviceId(deviceId))
|
||||||
|
PlayerPrefs.SetString(PlayerPrefsDeviceIdKey, deviceId);
|
||||||
PlayerPrefs.Save();
|
PlayerPrefs.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,8 +311,25 @@ public class LoginManager : MonoBehaviour
|
|||||||
void SetBusy(bool busy)
|
void SetBusy(bool busy)
|
||||||
{
|
{
|
||||||
_authInProgress = busy;
|
_authInProgress = busy;
|
||||||
if (btnLogin != null) btnLogin.interactable = !busy;
|
SetButtonInteractable(btnLogin, !busy);
|
||||||
if (btnRegister != null) btnRegister.interactable = !busy;
|
SetButtonInteractable(btnRegister, !busy);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetButtonInteractable(Button button, bool interactable)
|
||||||
|
{
|
||||||
|
if (button == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (button.interactable == interactable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
button.interactable = interactable;
|
||||||
|
if (!interactable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Disabling mid-click swallows pointer-up, and the button then ignores later clicks.
|
||||||
|
button.enabled = false;
|
||||||
|
button.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShowAuthMessage(string message)
|
void ShowAuthMessage(string message)
|
||||||
@@ -289,6 +337,8 @@ public class LoginManager : MonoBehaviour
|
|||||||
if (string.IsNullOrEmpty(message))
|
if (string.IsNullOrEmpty(message))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
message = ToPlayerError(message, PlayerGenericError);
|
||||||
|
|
||||||
if (MessageBoxDialog.instance == null)
|
if (MessageBoxDialog.instance == null)
|
||||||
{
|
{
|
||||||
_pendingAuthMessage = message;
|
_pendingAuthMessage = message;
|
||||||
@@ -321,40 +371,37 @@ public class LoginManager : MonoBehaviour
|
|||||||
void OnLogin()
|
void OnLogin()
|
||||||
{
|
{
|
||||||
if (_authInProgress) return;
|
if (_authInProgress) return;
|
||||||
if (_keepaliveRoutine != null && !string.IsNullOrEmpty(AuthToken)) return;
|
// A failed attempt can leave a token and keepalive without leaving this screen.
|
||||||
|
// Drop that session so this click performs a new connection check.
|
||||||
|
if (_keepaliveRoutine != null && !string.IsNullOrEmpty(AuthToken))
|
||||||
|
InvalidateStoredSession();
|
||||||
|
|
||||||
string username = usernameInputLogin.text;
|
string username = usernameInputLogin.text != null ? usernameInputLogin.text.Trim() : "";
|
||||||
string password = passwordInputLogin.text;
|
string password = passwordInputLogin.text ?? "";
|
||||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
|
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
|
||||||
{
|
{
|
||||||
ShowAuthMessage("Please fill in all fields");
|
ShowAuthMessage("Please fill in all fields");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (username.Length < MIN_USERNAME_LENGTH || username.Length > MAX_USERNAME_LENGTH)
|
if (username.Length < MIN_USERNAME_LENGTH)
|
||||||
{
|
{
|
||||||
ShowAuthMessage("Username must be between " + MIN_USERNAME_LENGTH + " and " + MAX_USERNAME_LENGTH + " characters");
|
ShowAuthMessage("Username must be at least " + MIN_USERNAME_LENGTH + " characters");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password.Length < MIN_PASSWORD_LENGTH || password.Length > MAX_PASSWORD_LENGTH)
|
StartCoroutine(AuthPostCoroutine(AuthBaseUrl + "/auth/login", BuildLoginBody(username, password), "Signed in."));
|
||||||
{
|
|
||||||
ShowAuthMessage("Password must be between " + MIN_PASSWORD_LENGTH + " and " + MAX_PASSWORD_LENGTH + " characters");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string json = "{\"username\":\"" + EscapeJsonString(username) + "\",\"password\":\"" + EscapeJsonString(password) + "\"}";
|
|
||||||
StartCoroutine(AuthPostCoroutine(AuthBaseUrl + "/auth/login", json, "Signed in."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnRegister()
|
void OnRegister()
|
||||||
{
|
{
|
||||||
if (_authInProgress) return;
|
if (_authInProgress) return;
|
||||||
if (_keepaliveRoutine != null && !string.IsNullOrEmpty(AuthToken)) return;
|
if (_keepaliveRoutine != null && !string.IsNullOrEmpty(AuthToken))
|
||||||
|
InvalidateStoredSession();
|
||||||
|
|
||||||
string username = usernameInputRegister.text;
|
string username = usernameInputRegister.text != null ? usernameInputRegister.text.Trim() : "";
|
||||||
string email = emailInputRegister != null ? emailInputRegister.text : "";
|
string email = emailInputRegister != null ? emailInputRegister.text : "";
|
||||||
string password = passwordInputRegister.text;
|
string password = passwordInputRegister.text ?? "";
|
||||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password))
|
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password))
|
||||||
{
|
{
|
||||||
ShowAuthMessage("Please fill in all fields");
|
ShowAuthMessage("Please fill in all fields");
|
||||||
@@ -368,20 +415,19 @@ public class LoginManager : MonoBehaviour
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (username.Length < MIN_USERNAME_LENGTH || username.Length > MAX_USERNAME_LENGTH)
|
if (username.Length < MIN_USERNAME_LENGTH)
|
||||||
{
|
{
|
||||||
ShowAuthMessage("Username must be between " + MIN_USERNAME_LENGTH + " and " + MAX_USERNAME_LENGTH + " characters");
|
ShowAuthMessage("Username must be at least " + MIN_USERNAME_LENGTH + " characters");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password.Length < MIN_PASSWORD_LENGTH || password.Length > MAX_PASSWORD_LENGTH)
|
if (password.Length < MIN_REGISTER_PASSWORD_LENGTH)
|
||||||
{
|
{
|
||||||
ShowAuthMessage("Password must be between " + MIN_PASSWORD_LENGTH + " and " + MAX_PASSWORD_LENGTH + " characters");
|
ShowAuthMessage("Password must be at least " + MIN_REGISTER_PASSWORD_LENGTH + " characters");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string json = "{\"username\":\"" + EscapeJsonString(username) + "\",\"email\":\"" + EscapeJsonString(email) + "\",\"password\":\"" + EscapeJsonString(password) + "\"}";
|
StartCoroutine(AuthPostCoroutine(AuthBaseUrl + "/auth/register", BuildRegisterBody(username, email, password), "Account created."));
|
||||||
StartCoroutine(AuthPostCoroutine(AuthBaseUrl + "/auth/register", json, "Account created."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool IsValidEmailFormat(string email)
|
static bool IsValidEmailFormat(string email)
|
||||||
@@ -416,69 +462,301 @@ public class LoginManager : MonoBehaviour
|
|||||||
return s.Replace("\\", "\\\\").Replace("\"", "\\\"");
|
return s.Replace("\\", "\\\\").Replace("\"", "\\\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static string BuildLoginBody(string username, string password)
|
||||||
|
{
|
||||||
|
return "{\"username\":\"" + EscapeJsonString(username)
|
||||||
|
+ "\",\"password\":\"" + EscapeJsonString(password)
|
||||||
|
+ "\",\"device_id\":\"" + EscapeJsonString(GetDeviceId()) + "\"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
static string BuildRegisterBody(string username, string email, string password)
|
||||||
|
{
|
||||||
|
return "{\"username\":\"" + EscapeJsonString(username)
|
||||||
|
+ "\",\"email\":\"" + EscapeJsonString(email)
|
||||||
|
+ "\",\"password\":\"" + EscapeJsonString(password)
|
||||||
|
+ "\",\"device_id\":\"" + EscapeJsonString(GetDeviceId()) + "\"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stable id sent on register and login. The server keeps it across logout, so a login
|
||||||
|
/// from this device can still match while logout is in flight.
|
||||||
|
/// </summary>
|
||||||
|
static string GetDeviceId()
|
||||||
|
{
|
||||||
|
string id = SystemInfo.deviceUniqueIdentifier;
|
||||||
|
if (IsUsableDeviceId(id))
|
||||||
|
return id;
|
||||||
|
|
||||||
|
id = PlayerPrefs.GetString(PlayerPrefsDeviceIdKey, "");
|
||||||
|
if (IsUsableDeviceId(id))
|
||||||
|
return id;
|
||||||
|
|
||||||
|
id = Guid.NewGuid().ToString("N");
|
||||||
|
PlayerPrefs.SetString(PlayerPrefsDeviceIdKey, id);
|
||||||
|
PlayerPrefs.Save();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsUsableDeviceId(string id)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrWhiteSpace(id) && id != SystemInfo.unsupportedIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>JsonUtility cannot read <c>"id":"42"</c> into <see cref="UserData.id"/>.</summary>
|
||||||
|
static string CoerceQuotedNumericIds(string json)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(json))
|
||||||
|
return json;
|
||||||
|
return Regex.Replace(json, "\"id\"\\s*:\\s*\"(\\d+)\"", "\"id\":$1");
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsTransportFailure(UnityWebRequest request)
|
||||||
|
{
|
||||||
|
return request.result != UnityWebRequest.Result.Success &&
|
||||||
|
request.result != UnityWebRequest.Result.ProtocolError;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string TransportError(UnityWebRequest request)
|
||||||
|
{
|
||||||
|
string technical = request == null || string.IsNullOrEmpty(request.error) ? "Network error" : request.error;
|
||||||
|
string url = request != null ? request.url : "";
|
||||||
|
Logger.Log("Network error: " + technical + " url=" + url);
|
||||||
|
return PlayerConnectionError;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string PlayerFacingHttpError(string body, long responseCode)
|
||||||
|
{
|
||||||
|
if (responseCode == 401)
|
||||||
|
return "Session expired. Please sign in again.";
|
||||||
|
|
||||||
|
string apiError = TryReadApiErrorField(body);
|
||||||
|
string candidate = !string.IsNullOrWhiteSpace(apiError) ? apiError : body;
|
||||||
|
if (string.IsNullOrWhiteSpace(candidate))
|
||||||
|
{
|
||||||
|
Logger.Log("HTTP " + responseCode + " with no error message");
|
||||||
|
return PlayerGenericError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ToPlayerError(candidate, PlayerGenericError);
|
||||||
|
}
|
||||||
|
|
||||||
|
static string TryReadApiErrorField(string body)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(body))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
string trimmed = body.TrimStart();
|
||||||
|
if (trimmed.Length == 0 || trimmed[0] != '{')
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AuthApiResponse resp = JsonUtility.FromJson<AuthApiResponse>(body);
|
||||||
|
return resp != null ? resp.error : null;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rewrites engine and transport text ("Cannot resolve destination host", HTTP dumps, raw JSON)
|
||||||
|
/// before it reaches the player. Readable server messages are left as-is.
|
||||||
|
/// </summary>
|
||||||
|
static string ToPlayerError(string message, string fallback)
|
||||||
|
{
|
||||||
|
if (IsConnectivityError(message))
|
||||||
|
{
|
||||||
|
Logger.Log("Replaced technical error: " + message);
|
||||||
|
return PlayerConnectionError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsPlayerReadable(message))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(message))
|
||||||
|
Logger.Log("Replaced technical error: " + message);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return message.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsConnectivityError(string message)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(message))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string m = message.ToLowerInvariant();
|
||||||
|
return m.Contains("resolve host")
|
||||||
|
|| m.Contains("resolve destination")
|
||||||
|
|| m.Contains("could not resolve")
|
||||||
|
|| m.Contains("couldn't resolve")
|
||||||
|
|| m.Contains("cannot resolve")
|
||||||
|
|| m.Contains("can't resolve")
|
||||||
|
|| m.Contains("unable to resolve")
|
||||||
|
|| m.Contains("unknown host")
|
||||||
|
|| m.Contains("name resolution")
|
||||||
|
|| m.Contains("name or service")
|
||||||
|
|| m.Contains("destination host")
|
||||||
|
|| m.Contains("cannot connect")
|
||||||
|
|| m.Contains("can't connect")
|
||||||
|
|| m.Contains("could not connect")
|
||||||
|
|| m.Contains("couldn't connect")
|
||||||
|
|| m.Contains("connection refused")
|
||||||
|
|| m.Contains("connection reset")
|
||||||
|
|| m.Contains("connection aborted")
|
||||||
|
|| m.Contains("connection timed")
|
||||||
|
|| m.Contains("network is unreachable")
|
||||||
|
|| m.Contains("network error")
|
||||||
|
|| m.Contains("no internet")
|
||||||
|
|| m.Contains("no route to host")
|
||||||
|
|| m.Contains("host unreachable")
|
||||||
|
|| m.Contains("failed to receive")
|
||||||
|
|| m.Contains("request timeout")
|
||||||
|
|| m.Contains("timed out")
|
||||||
|
|| m.Contains("timeout")
|
||||||
|
|| m.Contains("ssl")
|
||||||
|
|| m.Contains("certificate")
|
||||||
|
|| m.Contains("curl error")
|
||||||
|
|| m.Contains("unknown error")
|
||||||
|
|| m.Contains("insecure connection");
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsPlayerReadable(string message)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(message) || IsConnectivityError(message))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string trimmed = message.Trim();
|
||||||
|
if (trimmed.Length > 180)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
char c = trimmed[0];
|
||||||
|
if (c == '{' || c == '<' || c == '[')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string m = trimmed.ToLowerInvariant();
|
||||||
|
return m.IndexOf("http/1") < 0
|
||||||
|
&& m.IndexOf("request failed (") < 0
|
||||||
|
&& m.IndexOf("empty response") < 0
|
||||||
|
&& m.IndexOf("exception") < 0
|
||||||
|
&& m.IndexOf("stack trace") < 0
|
||||||
|
&& m.IndexOf("stacktrace") < 0
|
||||||
|
&& m.IndexOf("nullreference") < 0
|
||||||
|
&& m.IndexOf("data processing error") < 0
|
||||||
|
&& m.IndexOf("malformed url") < 0
|
||||||
|
&& m.IndexOf('\n') < 0
|
||||||
|
&& m.IndexOf('\r') < 0;
|
||||||
|
}
|
||||||
|
|
||||||
IEnumerator AuthPostCoroutine(string url, string jsonBody, string successMessage)
|
IEnumerator AuthPostCoroutine(string url, string jsonBody, string successMessage)
|
||||||
{
|
{
|
||||||
SetBusy(true);
|
SetBusy(true);
|
||||||
HideAuthMessage();
|
HideAuthMessage();
|
||||||
ClearLocalUserProfile();
|
ClearLocalUserProfile();
|
||||||
|
|
||||||
using (var request = new UnityWebRequest(url, UnityWebRequest.kHttpVerbPOST))
|
bool enteredMenu = false;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonBody);
|
using (var request = new UnityWebRequest(url, UnityWebRequest.kHttpVerbPOST))
|
||||||
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
|
|
||||||
request.downloadHandler = new DownloadHandlerBuffer();
|
|
||||||
request.SetRequestHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
yield return request.SendWebRequest();
|
|
||||||
|
|
||||||
string text = request.downloadHandler != null ? request.downloadHandler.text : "";
|
|
||||||
|
|
||||||
if (request.result != UnityWebRequest.Result.Success &&
|
|
||||||
request.result != UnityWebRequest.Result.ProtocolError)
|
|
||||||
{
|
{
|
||||||
ShowAuthMessage("Network Error, Please try again.");
|
byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonBody);
|
||||||
SetBusy(false);
|
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
|
||||||
yield break;
|
request.downloadHandler = new DownloadHandlerBuffer();
|
||||||
}
|
request.SetRequestHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(text))
|
yield return request.SendWebRequest();
|
||||||
{
|
|
||||||
ShowAuthMessage("Empty response from server");
|
|
||||||
SetBusy(false);
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthApiResponse resp = JsonUtility.FromJson<AuthApiResponse>(text);
|
if (IsTransportFailure(request))
|
||||||
|
|
||||||
if (resp.ok && !string.IsNullOrEmpty(resp.token))
|
|
||||||
{
|
|
||||||
AuthToken = resp.token;
|
|
||||||
PlayerPrefs.SetString(PlayerPrefsAuthKey, resp.token);
|
|
||||||
PlayerPrefs.Save();
|
|
||||||
StartKeepalive();
|
|
||||||
|
|
||||||
bool profileOk = false;
|
|
||||||
string profileErr = null;
|
|
||||||
yield return StartCoroutine(FetchUserDataCoroutine((success, err, _) =>
|
|
||||||
{
|
{
|
||||||
profileOk = success;
|
ShowAuthMessage(TransportError(request));
|
||||||
profileErr = err;
|
yield break;
|
||||||
}, true));
|
}
|
||||||
|
|
||||||
if (profileOk)
|
string text = CoerceQuotedNumericIds(ReadResponseBody(request));
|
||||||
ProceedToMainMenu();
|
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
ShowAuthMessage(PlayerGenericError);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthApiResponse resp = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
resp = JsonUtility.FromJson<AuthApiResponse>(text);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Log("Auth JSON error: " + e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp != null && resp.ok && !string.IsNullOrEmpty(resp.token))
|
||||||
|
{
|
||||||
|
AuthToken = resp.token;
|
||||||
|
PlayerPrefs.SetString(PlayerPrefsAuthKey, resp.token);
|
||||||
|
PlayerPrefs.Save();
|
||||||
|
|
||||||
|
bool profileOk = resp.user != null && !string.IsNullOrEmpty(resp.user.username);
|
||||||
|
string profileErr = null;
|
||||||
|
if (profileOk)
|
||||||
|
ApplyUserData(resp.user);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yield return StartCoroutine(FetchUserDataCoroutine((success, err, _) =>
|
||||||
|
{
|
||||||
|
profileOk = success;
|
||||||
|
profileErr = err;
|
||||||
|
}, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profileOk)
|
||||||
|
{
|
||||||
|
enteredMenu = true;
|
||||||
|
StartKeepalive();
|
||||||
|
ProceedToMainMenu();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InvalidateStoredSession();
|
||||||
|
ShowAuthMessage(string.IsNullOrEmpty(profileErr)
|
||||||
|
? PlayerConnectionError
|
||||||
|
: profileErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
ShowAuthMessage(string.IsNullOrEmpty(profileErr)
|
{
|
||||||
? "Signed in but could not load your profile. Check your connection and try again."
|
string serverError = resp != null ? resp.error : null;
|
||||||
: profileErr);
|
if (string.IsNullOrEmpty(serverError) && resp != null && resp.code == AlreadyLoggedInCode)
|
||||||
}
|
serverError = "Already logged in on another device";
|
||||||
else
|
ShowAuthMessage(string.IsNullOrEmpty(serverError)
|
||||||
{
|
? "Login failed. Please try again."
|
||||||
ShowAuthMessage("Login failed. Please try again.");
|
: serverError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (!enteredMenu)
|
||||||
|
SetBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SetBusy(false);
|
static string ReadResponseBody(UnityWebRequest request)
|
||||||
|
{
|
||||||
|
if (request == null || request.downloadHandler == null)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return request.downloadHandler.text ?? "";
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Log("Auth response unreadable: " + e.Message);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HideServerMessagePanels()
|
void HideServerMessagePanels()
|
||||||
@@ -673,16 +951,15 @@ public class LoginManager : MonoBehaviour
|
|||||||
|
|
||||||
string text = request.downloadHandler != null ? request.downloadHandler.text : "";
|
string text = request.downloadHandler != null ? request.downloadHandler.text : "";
|
||||||
|
|
||||||
if (request.result != UnityWebRequest.Result.Success &&
|
if (IsTransportFailure(request))
|
||||||
request.result != UnityWebRequest.Result.ProtocolError)
|
|
||||||
{
|
{
|
||||||
onComplete?.Invoke(false, string.IsNullOrEmpty(request.error) ? "Network error" : request.error, null);
|
onComplete?.Invoke(false, TransportError(request), null);
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.responseCode < 200 || request.responseCode >= 300)
|
if (request.responseCode < 200 || request.responseCode >= 300)
|
||||||
{
|
{
|
||||||
onComplete?.Invoke(false, string.IsNullOrEmpty(text) ? "Request failed (" + request.responseCode + ")" : text, null);
|
onComplete?.Invoke(false, PlayerFacingHttpError(text, request.responseCode), null);
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -731,22 +1008,21 @@ public class LoginManager : MonoBehaviour
|
|||||||
|
|
||||||
string text = request.downloadHandler != null ? request.downloadHandler.text : "";
|
string text = request.downloadHandler != null ? request.downloadHandler.text : "";
|
||||||
|
|
||||||
if (request.result != UnityWebRequest.Result.Success &&
|
if (IsTransportFailure(request))
|
||||||
request.result != UnityWebRequest.Result.ProtocolError)
|
|
||||||
{
|
{
|
||||||
onComplete?.Invoke(false, string.IsNullOrEmpty(request.error) ? "Network error" : request.error);
|
onComplete?.Invoke(false, TransportError(request));
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.responseCode < 200 || request.responseCode >= 300)
|
if (request.responseCode < 200 || request.responseCode >= 300)
|
||||||
{
|
{
|
||||||
onComplete?.Invoke(false, string.IsNullOrEmpty(text) ? "Request failed (" + request.responseCode + ")" : text);
|
onComplete?.Invoke(false, PlayerFacingHttpError(text, request.responseCode));
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(text))
|
if (string.IsNullOrEmpty(text))
|
||||||
{
|
{
|
||||||
onComplete?.Invoke(false, "Empty response from server");
|
onComplete?.Invoke(false, PlayerGenericError);
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -765,12 +1041,12 @@ public class LoginManager : MonoBehaviour
|
|||||||
if (refreshOk)
|
if (refreshOk)
|
||||||
onComplete?.Invoke(true, null);
|
onComplete?.Invoke(true, null);
|
||||||
else
|
else
|
||||||
onComplete?.Invoke(false, string.IsNullOrEmpty(refreshErr) ? "Purchase ok but profile refresh failed" : refreshErr);
|
onComplete?.Invoke(false, string.IsNullOrEmpty(refreshErr) ? PlayerGenericError : ToPlayerError(refreshErr, PlayerGenericError));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string err = resp != null && !string.IsNullOrEmpty(resp.error) ? resp.error : "Purchase failed";
|
string err = resp != null && !string.IsNullOrEmpty(resp.error) ? resp.error : null;
|
||||||
onComplete?.Invoke(false, err);
|
onComplete?.Invoke(false, ToPlayerError(err, "Purchase failed. Please try again."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -798,22 +1074,21 @@ public class LoginManager : MonoBehaviour
|
|||||||
|
|
||||||
string text = request.downloadHandler != null ? request.downloadHandler.text : "";
|
string text = request.downloadHandler != null ? request.downloadHandler.text : "";
|
||||||
|
|
||||||
if (request.result != UnityWebRequest.Result.Success &&
|
if (IsTransportFailure(request))
|
||||||
request.result != UnityWebRequest.Result.ProtocolError)
|
|
||||||
{
|
{
|
||||||
onComplete?.Invoke(false, string.IsNullOrEmpty(request.error) ? "Network error" : request.error, null);
|
onComplete?.Invoke(false, TransportError(request), null);
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.responseCode < 200 || request.responseCode >= 300)
|
if (request.responseCode < 200 || request.responseCode >= 300)
|
||||||
{
|
{
|
||||||
onComplete?.Invoke(false, string.IsNullOrEmpty(text) ? "Request failed (" + request.responseCode + ")" : text, null);
|
onComplete?.Invoke(false, PlayerFacingHttpError(text, request.responseCode), null);
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(text))
|
if (string.IsNullOrEmpty(text))
|
||||||
{
|
{
|
||||||
onComplete?.Invoke(false, "Empty response from server", null);
|
onComplete?.Invoke(false, PlayerGenericError, null);
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -822,8 +1097,8 @@ public class LoginManager : MonoBehaviour
|
|||||||
onComplete?.Invoke(true, null, resp.report);
|
onComplete?.Invoke(true, null, resp.report);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string err = resp != null && !string.IsNullOrEmpty(resp.error) ? resp.error : "Ping report failed";
|
string err = resp != null && !string.IsNullOrEmpty(resp.error) ? resp.error : null;
|
||||||
onComplete?.Invoke(false, err, null);
|
onComplete?.Invoke(false, ToPlayerError(err, PlayerGenericError), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -846,26 +1121,25 @@ public class LoginManager : MonoBehaviour
|
|||||||
|
|
||||||
string text = request.downloadHandler != null ? request.downloadHandler.text : "";
|
string text = request.downloadHandler != null ? request.downloadHandler.text : "";
|
||||||
|
|
||||||
if (request.result != UnityWebRequest.Result.Success &&
|
if (IsTransportFailure(request))
|
||||||
request.result != UnityWebRequest.Result.ProtocolError)
|
|
||||||
{
|
{
|
||||||
onComplete?.Invoke(false, string.IsNullOrEmpty(request.error) ? "Network error" : request.error, null);
|
onComplete?.Invoke(false, TransportError(request), null);
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.responseCode < 200 || request.responseCode >= 300)
|
if (request.responseCode < 200 || request.responseCode >= 300)
|
||||||
{
|
{
|
||||||
onComplete?.Invoke(false, string.IsNullOrEmpty(text) ? "Request failed (" + request.responseCode + ")" : text, null);
|
onComplete?.Invoke(false, PlayerFacingHttpError(text, request.responseCode), null);
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(text))
|
if (string.IsNullOrEmpty(text))
|
||||||
{
|
{
|
||||||
onComplete?.Invoke(false, "Empty response from server", null);
|
onComplete?.Invoke(false, PlayerGenericError, null);
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthUserApiResponse resp = JsonUtility.FromJson<AuthUserApiResponse>(text);
|
AuthUserApiResponse resp = JsonUtility.FromJson<AuthUserApiResponse>(CoerceQuotedNumericIds(text));
|
||||||
|
|
||||||
if (resp != null && resp.ok && resp.user != null)
|
if (resp != null && resp.ok && resp.user != null)
|
||||||
{
|
{
|
||||||
@@ -875,8 +1149,8 @@ public class LoginManager : MonoBehaviour
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string err = resp != null && !string.IsNullOrEmpty(resp.error) ? resp.error : "Request failed";
|
string err = resp != null && !string.IsNullOrEmpty(resp.error) ? resp.error : null;
|
||||||
onComplete?.Invoke(false, err, null);
|
onComplete?.Invoke(false, ToPlayerError(err, PlayerGenericError), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -948,6 +1222,33 @@ public class LoginManager : MonoBehaviour
|
|||||||
_keepaliveRoutine = null;
|
_keepaliveRoutine = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IEnumerator RemoteLogoutCoroutine(string token)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string url = AuthBaseUrl + "/auth/logout";
|
||||||
|
using (var request = new UnityWebRequest(url, UnityWebRequest.kHttpVerbPOST))
|
||||||
|
{
|
||||||
|
request.uploadHandler = new UploadHandlerRaw(Array.Empty<byte>());
|
||||||
|
request.downloadHandler = new DownloadHandlerBuffer();
|
||||||
|
request.SetRequestHeader("Authorization", "Bearer " + token);
|
||||||
|
|
||||||
|
yield return request.SendWebRequest();
|
||||||
|
|
||||||
|
if (IsTransportFailure(request))
|
||||||
|
Logger.Log("auth/logout network error: " + (string.IsNullOrEmpty(request.error) ? "Network error" : request.error));
|
||||||
|
else if (request.responseCode < 200 || request.responseCode >= 300)
|
||||||
|
Logger.Log("auth/logout failed: HTTP " + request.responseCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_remoteLogoutInFlight = false;
|
||||||
|
if (_retireAfterRemoteLogout)
|
||||||
|
Destroy(gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IEnumerator KeepalivePingCoroutine()
|
IEnumerator KeepalivePingCoroutine()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(AuthToken))
|
if (string.IsNullOrEmpty(AuthToken))
|
||||||
@@ -971,7 +1272,7 @@ public class LoginManager : MonoBehaviour
|
|||||||
{
|
{
|
||||||
AuthApiResponse resp = JsonUtility.FromJson<AuthApiResponse>(text);
|
AuthApiResponse resp = JsonUtility.FromJson<AuthApiResponse>(text);
|
||||||
if (resp != null && !string.IsNullOrEmpty(resp.error))
|
if (resp != null && !string.IsNullOrEmpty(resp.error))
|
||||||
err = resp.error;
|
err = ToPlayerError(resp.error, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleKeepaliveUnauthorized(err);
|
HandleKeepaliveUnauthorized(err);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using UnityEngine;
|
|||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
using DG.Tweening;
|
||||||
|
|
||||||
public class MainMenuManager : MonoBehaviour
|
public class MainMenuManager : MonoBehaviour
|
||||||
{
|
{
|
||||||
@@ -59,6 +60,11 @@ public class MainMenuManager : MonoBehaviour
|
|||||||
public ToggleButton toggleSfx;
|
public ToggleButton toggleSfx;
|
||||||
public ToggleButton toggleMusic;
|
public ToggleButton toggleMusic;
|
||||||
|
|
||||||
|
[Header("Info popups")]
|
||||||
|
public RectTransform gameModesInfoPopup;
|
||||||
|
public RectTransform withdrawalInfoPopup;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>Default play cost (coin units) if <c>/settings</c> is not loaded yet.</summary>
|
/// <summary>Default play cost (coin units) if <c>/settings</c> is not loaded yet.</summary>
|
||||||
const int FallbackEntryFeeCoins = 21;
|
const int FallbackEntryFeeCoins = 21;
|
||||||
|
|
||||||
@@ -66,12 +72,27 @@ public class MainMenuManager : MonoBehaviour
|
|||||||
|
|
||||||
public static MainMenuManager instance;
|
public static MainMenuManager instance;
|
||||||
|
|
||||||
|
/// <summary>Last settings profile successfully loaded this session. Survives scene reloads.</summary>
|
||||||
|
public static PlayerProfileApiResponse CachedSettingsProfile { get; private set; }
|
||||||
|
|
||||||
|
public static void ClearCachedSettingsProfile()
|
||||||
|
{
|
||||||
|
CachedSettingsProfile = null;
|
||||||
|
}
|
||||||
|
|
||||||
void Awake()
|
void Awake()
|
||||||
{
|
{
|
||||||
if(LoginManager.instance==null){
|
if(LoginManager.instance==null){
|
||||||
SceneManager.LoadScene(0);
|
SceneManager.LoadScene(0);
|
||||||
}
|
}
|
||||||
instance = this;
|
instance = this;
|
||||||
|
|
||||||
|
// Awake runs before the first frame, so a leftover address cannot show until this assignment.
|
||||||
|
if (inputCryptoAddress != null)
|
||||||
|
inputCryptoAddress.text = PlayerPrefs.GetString(CryptoAddressPrefsKey, string.Empty);
|
||||||
|
|
||||||
|
// Scene text is placeholder account data. Replace it before the first frame.
|
||||||
|
ApplyCachedSettingsProfileOrClearPlaceholders();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
@@ -81,6 +102,7 @@ public class MainMenuManager : MonoBehaviour
|
|||||||
usernameText.text = "";
|
usernameText.text = "";
|
||||||
ccText.text = "";
|
ccText.text = "";
|
||||||
rcText.text = "";
|
rcText.text = "";
|
||||||
|
ApplyCachedSettingsProfileOrClearPlaceholders();
|
||||||
LoginManager.instance.GetUserData((success, error, user) => {
|
LoginManager.instance.GetUserData((success, error, user) => {
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
@@ -91,8 +113,12 @@ public class MainMenuManager : MonoBehaviour
|
|||||||
{
|
{
|
||||||
if (btnPlay != null)
|
if (btnPlay != null)
|
||||||
btnPlay.interactable = false;
|
btnPlay.interactable = false;
|
||||||
if (rcPlayText != null)
|
if (!TryGetCachedSettingsProfile(out _))
|
||||||
rcPlayText.text = string.IsNullOrEmpty(error) ? "Could not load profile. Try again." : error;
|
ShowSettingsProfileUnavailable();
|
||||||
|
if (rcPlayText != null){
|
||||||
|
string errorMsg = string.IsNullOrEmpty(error) ? "Could not load profile. Try again." : error;
|
||||||
|
MessageBoxDialog.ShowBasic(errorMsg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -103,10 +129,7 @@ public class MainMenuManager : MonoBehaviour
|
|||||||
btnBuyUsd50.onClick.AddListener(() => OnBuy(RcPack.Usd50));
|
btnBuyUsd50.onClick.AddListener(() => OnBuy(RcPack.Usd50));
|
||||||
|
|
||||||
if (inputCryptoAddress != null)
|
if (inputCryptoAddress != null)
|
||||||
{
|
|
||||||
inputCryptoAddress.text = PlayerPrefs.GetString(CryptoAddressPrefsKey, string.Empty);
|
|
||||||
inputCryptoAddress.onValueChanged.AddListener(OnCryptoAddressChanged);
|
inputCryptoAddress.onValueChanged.AddListener(OnCryptoAddressChanged);
|
||||||
}
|
|
||||||
|
|
||||||
if (txtWithdrawStatus != null)
|
if (txtWithdrawStatus != null)
|
||||||
txtWithdrawStatus.gameObject.SetActive(false);
|
txtWithdrawStatus.gameObject.SetActive(false);
|
||||||
@@ -144,6 +167,23 @@ public class MainMenuManager : MonoBehaviour
|
|||||||
btnLogout.onClick.RemoveListener(OnLogout);
|
btnLogout.onClick.RemoveListener(OnLogout);
|
||||||
if (btnWithdrawal != null)
|
if (btnWithdrawal != null)
|
||||||
btnWithdrawal.onClick.RemoveListener(OnWithdrawalPressed);
|
btnWithdrawal.onClick.RemoveListener(OnWithdrawalPressed);
|
||||||
|
if (inputCryptoAddress != null)
|
||||||
|
inputCryptoAddress.onValueChanged.RemoveListener(OnCryptoAddressChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drops the saved wallet address before logout deletes prefs.
|
||||||
|
/// The field listener is removed first so scene unload cannot write the old address back.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearWithdrawalAddress()
|
||||||
|
{
|
||||||
|
if (inputCryptoAddress != null)
|
||||||
|
{
|
||||||
|
inputCryptoAddress.onValueChanged.RemoveListener(OnCryptoAddressChanged);
|
||||||
|
inputCryptoAddress.text = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerPrefs.DeleteKey(CryptoAddressPrefsKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnLogout()
|
void OnLogout()
|
||||||
@@ -269,6 +309,16 @@ public class MainMenuManager : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Shown when matchmaking is on screen and the matchmaker cannot be reached.</summary>
|
||||||
|
public void ShowMatchmakingNoInternet()
|
||||||
|
{
|
||||||
|
const string message = "No internet connection, Please try again later";
|
||||||
|
if (MessageBoxDialog.IsAvailable)
|
||||||
|
MessageBoxDialog.ShowBasic(message);
|
||||||
|
else
|
||||||
|
Debug.LogError(message);
|
||||||
|
}
|
||||||
|
|
||||||
void ShowWithdrawalMessage(string message, bool basic)
|
void ShowWithdrawalMessage(string message, bool basic)
|
||||||
{
|
{
|
||||||
if (MessageBoxDialog.IsAvailable)
|
if (MessageBoxDialog.IsAvailable)
|
||||||
@@ -456,40 +506,128 @@ public class MainMenuManager : MonoBehaviour
|
|||||||
RefreshSettingsProfile();
|
RefreshSettingsProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApplyCachedSettingsProfileOrClearPlaceholders()
|
||||||
|
{
|
||||||
|
if (TryGetCachedSettingsProfile(out PlayerProfileApiResponse cached))
|
||||||
|
ApplySettingsProfile(cached);
|
||||||
|
else
|
||||||
|
ClearSettingsProfilePlaceholders();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Cache is only shown when it belongs to the signed-in player.</summary>
|
||||||
|
static bool TryGetCachedSettingsProfile(out PlayerProfileApiResponse profile)
|
||||||
|
{
|
||||||
|
profile = CachedSettingsProfile;
|
||||||
|
if (profile == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
UserData user = LoginManager.CurrentUser;
|
||||||
|
if (user == null || profile.player_id != user.id)
|
||||||
|
{
|
||||||
|
profile = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void RefreshSettingsProfile()
|
void RefreshSettingsProfile()
|
||||||
{
|
{
|
||||||
|
bool hasCache = TryGetCachedSettingsProfile(out PlayerProfileApiResponse cached);
|
||||||
|
if (hasCache)
|
||||||
|
ApplySettingsProfile(cached);
|
||||||
|
|
||||||
if (profileInfoFetcher == null)
|
if (profileInfoFetcher == null)
|
||||||
|
{
|
||||||
|
if (!hasCache)
|
||||||
|
ShowSettingsProfileUnavailable();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
UserData user = LoginManager.CurrentUser;
|
UserData user = LoginManager.CurrentUser;
|
||||||
if (user == null)
|
if (user == null)
|
||||||
|
{
|
||||||
|
if (!hasCache)
|
||||||
|
ClearSettingsProfilePlaceholders();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
profileInfoFetcher.GetPlayerProfile(user.id, (success, error, profile) =>
|
profileInfoFetcher.GetPlayerProfile(user.id, (success, error, profile) =>
|
||||||
{
|
{
|
||||||
if (!success || profile == null)
|
if (!success || profile == null || profile.player_id != user.id)
|
||||||
{
|
{
|
||||||
Debug.LogWarning("Settings profile fetch failed: " + (error ?? "unknown"));
|
Debug.LogWarning("Settings profile fetch failed: " + (error ?? "unknown"));
|
||||||
|
if (TryGetCachedSettingsProfile(out PlayerProfileApiResponse last))
|
||||||
|
ApplySettingsProfile(last);
|
||||||
|
else
|
||||||
|
ShowSettingsProfileUnavailable();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CachedSettingsProfile = profile;
|
||||||
ApplySettingsProfile(profile);
|
ApplySettingsProfile(profile);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplySettingsProfile(PlayerProfileApiResponse profile)
|
void ClearSettingsProfilePlaceholders()
|
||||||
{
|
{
|
||||||
if (txtUsername != null)
|
SetSettingsProfileText(txtUsername, "-");
|
||||||
txtUsername.text = profile.username ?? "";
|
SetSettingsProfileText(txtEmail, "-");
|
||||||
if (txtEmail != null)
|
SetSettingsProfileText(txtId, "-");
|
||||||
txtEmail.text = profile.email ?? "";
|
SetSettingsProfileText(txtAge, "-");
|
||||||
if (txtId != null)
|
SetSettingsProfileText(txtMatches, "-");
|
||||||
txtId.text = profile.player_id.ToString();
|
SetSettingsProfileText(txtWinRatio, "-");
|
||||||
if (txtAge != null)
|
|
||||||
txtAge.text = profile.days_since_registration.ToString();
|
|
||||||
if (txtMatches != null)
|
|
||||||
txtMatches.text = profile.matches_played.ToString();
|
|
||||||
if (txtWinRatio != null)
|
|
||||||
txtWinRatio.text = (profile.win_ratio * 100f).ToString("0.#") + "%";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShowSettingsProfileUnavailable()
|
||||||
|
{
|
||||||
|
SetSettingsProfileText(txtUsername, "Unavailable");
|
||||||
|
SetSettingsProfileText(txtEmail, "Profile data cannot be loaded");
|
||||||
|
SetSettingsProfileText(txtId, "-");
|
||||||
|
SetSettingsProfileText(txtAge, "-");
|
||||||
|
SetSettingsProfileText(txtMatches, "-");
|
||||||
|
SetSettingsProfileText(txtWinRatio, "-");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetSettingsProfileText(TMP_Text label, string value)
|
||||||
|
{
|
||||||
|
if (label != null)
|
||||||
|
label.text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplySettingsProfile(PlayerProfileApiResponse profile)
|
||||||
|
{
|
||||||
|
SetSettingsProfileText(txtUsername, profile.username ?? "");
|
||||||
|
SetSettingsProfileText(txtEmail, profile.email ?? "");
|
||||||
|
SetSettingsProfileText(txtId, profile.player_id.ToString());
|
||||||
|
SetSettingsProfileText(txtAge, profile.days_since_registration.ToString());
|
||||||
|
SetSettingsProfileText(txtMatches, profile.matches_played.ToString());
|
||||||
|
SetSettingsProfileText(txtWinRatio, (profile.win_ratio * 100f).ToString("0.#") + "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void ShowGameModesInfoPopup(){
|
||||||
|
gameModesInfoPopup.localScale = Vector3.zero;
|
||||||
|
gameModesInfoPopup.gameObject.SetActive(true);
|
||||||
|
gameModesInfoPopup.DOScale(Vector3.one, 0.3f).SetEase(Ease.OutBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowWithdrawalInfoPopup(){
|
||||||
|
withdrawalInfoPopup.localScale = Vector3.zero;
|
||||||
|
withdrawalInfoPopup.gameObject.SetActive(true);
|
||||||
|
withdrawalInfoPopup.DOScale(Vector3.one, 0.3f).SetEase(Ease.OutBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HideGameModesInfoPopup(){
|
||||||
|
gameModesInfoPopup.DOScale(Vector3.zero, 0.3f).SetEase(Ease.InBack).OnComplete(() => {
|
||||||
|
gameModesInfoPopup.gameObject.SetActive(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HideWithdrawalInfoPopup(){
|
||||||
|
withdrawalInfoPopup.DOScale(Vector3.zero, 0.3f).SetEase(Ease.InBack).OnComplete(() => {
|
||||||
|
withdrawalInfoPopup.gameObject.SetActive(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,13 +89,16 @@ public class NetManager : NetworkManager
|
|||||||
|
|
||||||
public override void OnServerDisconnect(NetworkConnectionToClient conn)
|
public override void OnServerDisconnect(NetworkConnectionToClient conn)
|
||||||
{
|
{
|
||||||
|
NetPlayer disconnectingPlayer = null;
|
||||||
if (conn != null && conn.identity != null)
|
if (conn != null && conn.identity != null)
|
||||||
{
|
{
|
||||||
var netPlayer = conn.identity.GetComponent<NetPlayer>();
|
disconnectingPlayer = conn.identity.GetComponent<NetPlayer>();
|
||||||
if (netPlayer != null && GameManager.instance != null)
|
if (disconnectingPlayer != null && GameManager.instance != null)
|
||||||
GameManager.instance.OnDedicatedMatchPlayerDisconnected(netPlayer.myTeam);
|
GameManager.instance.OnDedicatedMatchPlayerDisconnected(disconnectingPlayer.myTeam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NetPlayer.CancelPendingRematchOnDisconnect(disconnectingPlayer);
|
||||||
|
|
||||||
if (conn != null)
|
if (conn != null)
|
||||||
Logger.Log($"Mirror server: client disconnected (connId={conn.connectionId})");
|
Logger.Log($"Mirror server: client disconnected (connId={conn.connectionId})");
|
||||||
base.OnServerDisconnect(conn);
|
base.OnServerDisconnect(conn);
|
||||||
|
|||||||
@@ -165,6 +165,8 @@ public class NetPlayer : NetworkBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OnDestroy(){
|
void OnDestroy(){
|
||||||
|
bool opponentLeft = localPlayer != null && localPlayer != this;
|
||||||
|
|
||||||
if (pingReportCoroutine != null)
|
if (pingReportCoroutine != null)
|
||||||
{
|
{
|
||||||
StopCoroutine(pingReportCoroutine);
|
StopCoroutine(pingReportCoroutine);
|
||||||
@@ -173,6 +175,13 @@ public class NetPlayer : NetworkBehaviour
|
|||||||
if(localPlayer == this){
|
if(localPlayer == this){
|
||||||
localPlayer = null;
|
localPlayer = null;
|
||||||
}
|
}
|
||||||
|
if (ReferenceEquals(RedPlayer, this))
|
||||||
|
RedPlayer = null;
|
||||||
|
if (ReferenceEquals(BluePlayer, this))
|
||||||
|
BluePlayer = null;
|
||||||
|
|
||||||
|
if (opponentLeft && GameOverCanvas.instance != null)
|
||||||
|
GameOverCanvas.instance.NotifyOpponentDisconnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetupInputPanel(){
|
void SetupInputPanel(){
|
||||||
@@ -426,12 +435,25 @@ public class NetPlayer : NetworkBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
public const string RematchDeclinedMessage = "Rematch declined.";
|
||||||
|
public const string OpponentLeftRematchMessage = "Opponent left.";
|
||||||
|
|
||||||
|
/// <summary>Server-only. True after a rematch is requested until it is accepted, declined, or fails.</summary>
|
||||||
|
static bool rematchRequestPending;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
/// Request a rematch with the opponent.
|
/// Request a rematch with the opponent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void RequestRematch(){
|
public void RequestRematch(){
|
||||||
int maxBetLimitCoins = GameOverCanvas.GetPersonalBetLimit();
|
int maxBetLimitCoins = GameOverCanvas.GetPersonalBetLimit();
|
||||||
if(isServer){
|
if(isServer){
|
||||||
|
if (CountConnectedPlayers() < 2)
|
||||||
|
{
|
||||||
|
rematchRequestPending = false;
|
||||||
|
OnRematchCancelledServer(OpponentLeftRematchMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rematchRequestPending = true;
|
||||||
GameOverCanvas.instance.OnRematchRequested(maxBetLimitCoins);
|
GameOverCanvas.instance.OnRematchRequested(maxBetLimitCoins);
|
||||||
RpcRequestRematch(maxBetLimitCoins);
|
RpcRequestRematch(maxBetLimitCoins);
|
||||||
}else{
|
}else{
|
||||||
@@ -442,6 +464,13 @@ public class NetPlayer : NetworkBehaviour
|
|||||||
[Command]
|
[Command]
|
||||||
void CmdRequestRematch(int maxBetLimitCoins){
|
void CmdRequestRematch(int maxBetLimitCoins){
|
||||||
Logger.Log("Rematch requested, max bet limit coins: " + maxBetLimitCoins);
|
Logger.Log("Rematch requested, max bet limit coins: " + maxBetLimitCoins);
|
||||||
|
if (CountConnectedPlayers() < 2)
|
||||||
|
{
|
||||||
|
rematchRequestPending = false;
|
||||||
|
OnRematchCancelledServer(OpponentLeftRematchMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rematchRequestPending = true;
|
||||||
|
|
||||||
if(isServerOnly){
|
if(isServerOnly){
|
||||||
//server
|
//server
|
||||||
@@ -473,8 +502,15 @@ public class NetPlayer : NetworkBehaviour
|
|||||||
void OnRematchAcceptedServer(int betRcCoins ){
|
void OnRematchAcceptedServer(int betRcCoins ){
|
||||||
if(!isServer){ return; }
|
if(!isServer){ return; }
|
||||||
|
|
||||||
|
rematchRequestPending = false;
|
||||||
|
|
||||||
if(betRcCoins <= 0){
|
if(betRcCoins <= 0){
|
||||||
OnRematchCancelledServer();
|
OnRematchCancelledServer(RematchDeclinedMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(RedPlayer == null || BluePlayer == null){
|
||||||
|
OnRematchCancelledServer(OpponentLeftRematchMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,22 +598,54 @@ public class NetPlayer : NetworkBehaviour
|
|||||||
MessageBoxDialog.Show("Rematch failed", string.IsNullOrEmpty(errorMessage) ? "Could not start a rematch." : errorMessage);
|
MessageBoxDialog.Show("Rematch failed", string.IsNullOrEmpty(errorMessage) ? "Could not start a rematch." : errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnRematchCancelledServer(){
|
static int CountConnectedPlayers()
|
||||||
|
{
|
||||||
|
if (!NetworkServer.active)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
foreach (NetworkConnectionToClient conn in NetworkServer.connections.Values)
|
||||||
|
{
|
||||||
|
if (conn != null && conn.identity != null && conn.identity.GetComponent<NetPlayer>() != null)
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server-only. If a rematch request is still pending, cancel it before the disconnecting player is destroyed.
|
||||||
|
/// </summary>
|
||||||
|
public static void CancelPendingRematchOnDisconnect(NetPlayer disconnectingPlayer)
|
||||||
|
{
|
||||||
|
if (!NetworkServer.active || !rematchRequestPending)
|
||||||
|
return;
|
||||||
|
|
||||||
|
rematchRequestPending = false;
|
||||||
|
|
||||||
|
NetPlayer messenger = disconnectingPlayer;
|
||||||
|
if (messenger == null)
|
||||||
|
messenger = RedPlayer != null ? RedPlayer : BluePlayer;
|
||||||
|
if (messenger != null)
|
||||||
|
messenger.OnRematchCancelledServer(OpponentLeftRematchMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnRematchCancelledServer(string message){
|
||||||
if(!isServer){ return; }
|
if(!isServer){ return; }
|
||||||
|
|
||||||
RpcCancelRematch();
|
RpcCancelRematch(message);
|
||||||
if(!isServerOnly){
|
if(!isServerOnly){
|
||||||
CancelRematch();
|
CancelRematch(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[ClientRpc]
|
[ClientRpc]
|
||||||
void RpcCancelRematch(){
|
void RpcCancelRematch(string message){
|
||||||
CancelRematch();
|
CancelRematch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CancelRematch(){
|
void CancelRematch(string message){
|
||||||
GameOverCanvas.instance.OnRematchCancelled();
|
if (GameOverCanvas.instance != null)
|
||||||
|
GameOverCanvas.instance.OnRematchCancelled(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerator CoReportPingPeriodically()
|
IEnumerator CoReportPingPeriodically()
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
public static class GetAvailablePort
|
||||||
|
{
|
||||||
|
|
||||||
|
public static int GetAvailableUdpPort()
|
||||||
|
{
|
||||||
|
using var socket = new Socket(
|
||||||
|
AddressFamily.InterNetwork,
|
||||||
|
SocketType.Dgram,
|
||||||
|
ProtocolType.Udp
|
||||||
|
);
|
||||||
|
|
||||||
|
socket.Bind(new IPEndPoint(IPAddress.Any, 0));
|
||||||
|
|
||||||
|
return ((IPEndPoint)socket.LocalEndPoint).Port;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 56db3ddf723732541b67b5c72b5901c5
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class OpenLink : MonoBehaviour
|
||||||
|
{
|
||||||
|
public string url = "https://www.google.com";
|
||||||
|
|
||||||
|
public void Open(){
|
||||||
|
Application.OpenURL(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 91a8a303678625f419fe59a1d4dc2549
|
||||||
@@ -109,6 +109,36 @@ public class LevelLoadManager : MonoBehaviour
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drops the last match from the loading screen. This object survives scene loads, so logout must call this
|
||||||
|
/// or the next login shows the previous matchup while the overlay is up.
|
||||||
|
/// </summary>
|
||||||
|
public void ResetSession()
|
||||||
|
{
|
||||||
|
StopAllCoroutines();
|
||||||
|
BlocksMainMenuSettingsBootstrap = false;
|
||||||
|
gameOver = false;
|
||||||
|
myPlayer = null;
|
||||||
|
opponentPlayer = null;
|
||||||
|
|
||||||
|
canvasGroup.DOKill();
|
||||||
|
contentImage.DOKill();
|
||||||
|
canvasGroup.alpha = 0f;
|
||||||
|
canvasGroup.blocksRaycasts = false;
|
||||||
|
canvasGroup.interactable = false;
|
||||||
|
contentImage.fillAmount = 0f;
|
||||||
|
|
||||||
|
matchmadeUI.SetActive(false);
|
||||||
|
p1_winner.SetActive(false);
|
||||||
|
p2_winner.SetActive(false);
|
||||||
|
txtMiddle.text = "VS";
|
||||||
|
p1_name.text = "";
|
||||||
|
p2_name.text = "";
|
||||||
|
p1_l10.text = "";
|
||||||
|
p2_l10.text = "";
|
||||||
|
txtRcPrize.text = "";
|
||||||
|
}
|
||||||
|
|
||||||
Team myTeam = Team.Red;
|
Team myTeam = Team.Red;
|
||||||
|
|
||||||
public void SetupRematch(int rc_prize, int port, int gameId){
|
public void SetupRematch(int rc_prize, int port, int gameId){
|
||||||
|
|||||||
@@ -6,5 +6,7 @@ public class AuthApiResponse
|
|||||||
{
|
{
|
||||||
public bool ok;
|
public bool ok;
|
||||||
public string error;
|
public string error;
|
||||||
|
public string code;
|
||||||
public string token;
|
public string token;
|
||||||
|
public UserData user;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,156 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0b3f83ae1037be445863f9c0fbf01920
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable:
|
||||||
|
- first:
|
||||||
|
213: -5225953659475634250
|
||||||
|
second: branching_arrow_0
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 13
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 0
|
||||||
|
sRGBTexture: 1
|
||||||
|
linearTexture: 0
|
||||||
|
fadeOut: 0
|
||||||
|
borderMipMap: 0
|
||||||
|
mipMapsPreserveCoverage: 0
|
||||||
|
alphaTestReferenceValue: 0.5
|
||||||
|
mipMapFadeDistanceStart: 1
|
||||||
|
mipMapFadeDistanceEnd: 3
|
||||||
|
bumpmap:
|
||||||
|
convertToNormalMap: 0
|
||||||
|
externalNormalMap: 0
|
||||||
|
heightScale: 0.25
|
||||||
|
normalMapFilter: 0
|
||||||
|
flipGreenChannel: 0
|
||||||
|
isReadable: 0
|
||||||
|
streamingMipmaps: 0
|
||||||
|
streamingMipmapsPriority: 0
|
||||||
|
vTOnly: 0
|
||||||
|
ignoreMipmapLimit: 0
|
||||||
|
grayScaleToAlpha: 0
|
||||||
|
generateCubemap: 6
|
||||||
|
cubemapConvolution: 0
|
||||||
|
seamlessCubemap: 0
|
||||||
|
textureFormat: 1
|
||||||
|
maxTextureSize: 2048
|
||||||
|
textureSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
filterMode: 1
|
||||||
|
aniso: 1
|
||||||
|
mipBias: 0
|
||||||
|
wrapU: 1
|
||||||
|
wrapV: 1
|
||||||
|
wrapW: 1
|
||||||
|
nPOTScale: 0
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 2
|
||||||
|
spriteExtrude: 1
|
||||||
|
spriteMeshType: 1
|
||||||
|
alignment: 0
|
||||||
|
spritePivot: {x: 0.5, y: 0.5}
|
||||||
|
spritePixelsToUnits: 100
|
||||||
|
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
spriteGenerateFallbackPhysicsShape: 1
|
||||||
|
alphaUsage: 1
|
||||||
|
alphaIsTransparency: 1
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
textureType: 8
|
||||||
|
textureShape: 1
|
||||||
|
singleChannelComponent: 0
|
||||||
|
flipbookRows: 1
|
||||||
|
flipbookColumns: 1
|
||||||
|
maxTextureSizeSet: 0
|
||||||
|
compressionQualitySet: 0
|
||||||
|
textureFormatSet: 0
|
||||||
|
ignorePngGamma: 0
|
||||||
|
applyGammaDecoding: 0
|
||||||
|
swizzle: 50462976
|
||||||
|
cookieLightType: 0
|
||||||
|
platformSettings:
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: DefaultTexturePlatform
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: Standalone
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: Android
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites:
|
||||||
|
- serializedVersion: 2
|
||||||
|
name: branching_arrow_0
|
||||||
|
rect:
|
||||||
|
serializedVersion: 2
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: 182
|
||||||
|
height: 176
|
||||||
|
alignment: 0
|
||||||
|
pivot: {x: 0, y: 0}
|
||||||
|
border: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
customData:
|
||||||
|
outline: []
|
||||||
|
physicsShape: []
|
||||||
|
tessellationDetail: -1
|
||||||
|
bones: []
|
||||||
|
spriteID: 6b75d42c5dea977b0800000000000000
|
||||||
|
internalID: -5225953659475634250
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
outline: []
|
||||||
|
customData:
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID:
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
spriteCustomMetadata:
|
||||||
|
entries: []
|
||||||
|
nameFileIdTable:
|
||||||
|
branching_arrow_0: -5225953659475634250
|
||||||
|
mipmapLimitGroupName:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
Logger initiated at 9/6/2026 9:17:41 PM
|
Logger initiated at 9/24/2026 7:04:31 AM
|
||||||
|
|
||||||
[9/6/2026 9:17:41 PM] Cupid settings updated
|
[9/24/2026 7:04:31 AM] Cupid settings updated
|
||||||
[9/6/2026 9:17:42 PM] Cupid settings updated
|
[9/24/2026 7:04:31 AM] Cupid settings updated
|
||||||
[9/6/2026 9:17:47 PM] Configured dedicated match reporting for match id 0 with secret 38516e39d3406225b7c3015a66e5d25c54e9b2024c0755212122bdcea3e3a328 and internal api base https://kkapi.playpoolstudios.com
|
|
||||||
[9/6/2026 9:17:53 PM] Invalid Room port
|
|
||||||
[9/6/2026 9:17:53 PM] Mirror server exception logging enabled
|
|
||||||
[9/6/2026 9:17:53 PM] Game started On localhost, tutorial mode enabled
|
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ PlayerSettings:
|
|||||||
loadStoreDebugModeEnabled: 0
|
loadStoreDebugModeEnabled: 0
|
||||||
visionOSBundleVersion: 1.0
|
visionOSBundleVersion: 1.0
|
||||||
tvOSBundleVersion: 1.0
|
tvOSBundleVersion: 1.0
|
||||||
bundleVersion: 1.37
|
bundleVersion: 1.41
|
||||||
preloadedAssets:
|
preloadedAssets:
|
||||||
- {fileID: -944628639613478452, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
|
- {fileID: -944628639613478452, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
|
||||||
metroInputSource: 0
|
metroInputSource: 0
|
||||||
|
|||||||
Reference in New Issue
Block a user