Files
BlockSpace-v1.5.0b/Assets/Scripts/Assembly-CSharp/BlockSpace/Worlds/WorldCommandPoller.cs
T
2026-06-01 17:19:55 +02:00

649 lines
21 KiB
C#

using System;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;
using BlockSpace.Account;
using BlockSpace.Voxels;
using ExitGames.Client.Photon;
using Photon.Pun;
using Photon.Realtime;
using Photon.VR;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
namespace BlockSpace.Worlds
{
[DisallowMultipleComponent]
public sealed class WorldCommandPoller : MonoBehaviourPunCallbacks
{
[Serializable]
private sealed class PollRequest
{
public string playerId;
}
[Serializable]
private sealed class PollResponse
{
public bool ok;
public Command command;
public string error;
}
[Serializable]
private sealed class Command
{
public string type;
public string worldName;
public string roomName;
}
[Serializable]
private sealed class PresencePingRequest
{
public string playerId;
public string roomName;
}
[SerializeField]
private bool enable = true;
[SerializeField]
private string backendBaseUrl = "https://blockspacebackend-production.up.railway.app";
[SerializeField]
private float pollIntervalSeconds = 2f;
[SerializeField]
private float onlinePingIntervalSeconds = 5f;
[Header("Loading Screen")]
[SerializeField]
private GameObject loadingScreenObject;
[SerializeField]
private Slider loadingProgressSlider;
[SerializeField]
private WorldLoadingScreenController loadingScreen;
private const string DefaultLobbyWorldName = "Lobby";
private const string DefaultLobbyWorldId = "ZT5HjbaGAT5adlkvAirJ0A";
private const int PublicWorldJoinRetryCount = 5;
private const string RoomWorldVersionKey = "worldVersion";
private Coroutine pollCoroutine;
private Coroutine pingCoroutine;
private bool joinInProgress;
private bool joinResultReady;
private bool joinSucceeded;
private bool leftRoomReady;
private bool lobbyReady;
private bool startupLobbyJoinRequested;
private new void OnEnable()
{
StartLoopsIfNeeded();
}
private void Start()
{
Debug.Log("[WorldCommandPoller] Start: beginning lobby startup sequence.");
StartCoroutine(JoinLobbyWhenBackendReady());
}
private IEnumerator JoinLobbyWhenBackendReady()
{
BeginLoadingScreen(0.02f);
float connectStart = Time.realtimeSinceStartup;
while (PhotonNetwork.NetworkClientState != ClientState.ConnectedToMasterServer && Time.realtimeSinceStartup - connectStart < 15f)
{
yield return null;
}
Debug.Log($"[WorldCommandPoller] JoinLobbyWhenBackendReady: Photon state={PhotonNetwork.NetworkClientState} after wait.");
SetLoadingProgress(0.08f);
if (startupLobbyJoinRequested)
{
yield break;
}
string playerId = BlockSpaceAccountPrefs.GetUserFolderId();
WorldBackendClient client = new WorldBackendClient(backendBaseUrl);
float backendStart = Time.realtimeSinceStartup;
while (Time.realtimeSinceStartup - backendStart < 20f)
{
WorldBackendClient.ResolveWorldResponse resolve = null;
yield return client.ResolveWorld("Lobby", playerId, delegate(WorldBackendClient.ResolveWorldResponse r)
{
resolve = r;
});
if (resolve != null && resolve.ok)
{
Debug.Log("[WorldCommandPoller] JoinLobbyWhenBackendReady: backend resolve succeeded for Lobby.");
SetLoadingProgress(0.14f);
break;
}
if (resolve != null)
{
Debug.LogWarning($"[WorldCommandPoller] JoinLobbyWhenBackendReady: backend responded but resolve failed. Continuing to lobby join. ok={resolve.ok}, error={resolve.error}");
SetLoadingProgress(0.14f);
break;
}
Debug.LogWarning("[WorldCommandPoller] JoinLobbyWhenBackendReady: backend resolve returned null.");
yield return new WaitForSecondsRealtime(1f);
}
startupLobbyJoinRequested = true;
Debug.Log("[WorldCommandPoller] JoinLobbyWhenBackendReady: proceeding to join default Lobby world.");
SetLoadingProgress(0.2f);
yield return JoinWorldInstance("Lobby");
yield return IncrementVisitForCurrentWorldIfMatches("Lobby");
}
private IEnumerator IncrementVisitForCurrentWorldIfMatches(string baseWorldName)
{
if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null || !string.Equals(GetBaseWorldName(PhotonNetwork.CurrentRoom.Name), baseWorldName, StringComparison.OrdinalIgnoreCase))
{
yield break;
}
string userFolderId = BlockSpaceAccountPrefs.GetUserFolderId();
if (!string.IsNullOrEmpty(userFolderId))
{
WorldBackendClient client = new WorldBackendClient(backendBaseUrl);
WorldBackendClient.ResolveWorldResponse resolve = null;
yield return client.ResolveWorld(baseWorldName, userFolderId, delegate(WorldBackendClient.ResolveWorldResponse r)
{
resolve = r;
});
if (resolve != null && resolve.ok && resolve.exists && !string.IsNullOrEmpty(resolve.worldId))
{
yield return client.IncrementVisit(resolve.worldId, null);
}
}
}
private static string RandomPrivateCode()
{
char c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[UnityEngine.Random.Range(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".Length)];
char c2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[UnityEngine.Random.Range(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".Length)];
char c3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[UnityEngine.Random.Range(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".Length)];
return new string(new char[3] { c, c2, c3 });
}
private IEnumerator JoinPrivateWorldInstance(string worldName)
{
for (int attempt = 0; attempt < 10; attempt++)
{
string text = RandomPrivateCode();
string roomName = worldName + "|" + text;
yield return JoinExactRoom(roomName);
if (PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null && string.Equals(PhotonNetwork.CurrentRoom.Name, roomName, StringComparison.OrdinalIgnoreCase))
{
break;
}
}
}
private new void OnDisable()
{
StopAllCoroutines();
pollCoroutine = null;
pingCoroutine = null;
SetLoadingScreen(visible: false, 0f);
}
private void StartLoopsIfNeeded()
{
if (enable)
{
if (pollCoroutine == null)
{
pollCoroutine = StartCoroutine(PollLoop());
}
if (pingCoroutine == null)
{
pingCoroutine = StartCoroutine(PresencePingLoop());
}
}
}
private void SetLoadingScreen(bool visible, float progress)
{
WorldLoadingScreenController worldLoadingScreenController = ResolveLoadingScreen();
if (!(worldLoadingScreenController == null))
{
worldLoadingScreenController.SetLoading(visible);
worldLoadingScreenController.SetProgress(progress);
}
}
private void BeginLoadingScreen(float progress)
{
WorldLoadingScreenController worldLoadingScreenController = ResolveLoadingScreen();
if (!(worldLoadingScreenController == null))
{
worldLoadingScreenController.BeginLoading(progress);
}
}
private void SetLoadingProgress(float progress)
{
WorldLoadingScreenController worldLoadingScreenController = ResolveLoadingScreen();
if (!(worldLoadingScreenController == null))
{
worldLoadingScreenController.SetCheckpoint(progress);
}
}
private WorldLoadingScreenController ResolveLoadingScreen()
{
if (loadingScreen == null)
{
loadingScreen = ((WorldLoadingScreenController.Instance != null) ? WorldLoadingScreenController.Instance : UnityEngine.Object.FindFirstObjectByType<WorldLoadingScreenController>());
}
if (loadingScreen == null && loadingScreenObject != null)
{
loadingScreen = base.gameObject.AddComponent<WorldLoadingScreenController>();
}
if (loadingScreen != null)
{
loadingScreen.Configure(loadingScreenObject, loadingProgressSlider);
}
return loadingScreen;
}
private IEnumerator PresencePingLoop()
{
while (enable)
{
yield return new WaitForSecondsRealtime(Mathf.Clamp(onlinePingIntervalSeconds, 1f, 60f));
if (!BlockSpaceAccountPrefs.IsLinked)
{
continue;
}
string linkedPlayerId = BlockSpaceAccountPrefs.GetLinkedPlayerId();
if (!string.IsNullOrEmpty(linkedPlayerId))
{
string url = (backendBaseUrl ?? string.Empty).Trim().TrimEnd('/') + "/api/presence/ping";
string roomName = ((PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null) ? PhotonNetwork.CurrentRoom.Name : "");
string s = JsonUtility.ToJson(new PresencePingRequest
{
playerId = linkedPlayerId,
roomName = roomName
});
using UnityWebRequest request = new UnityWebRequest(url, "POST");
byte[] bytes = Encoding.UTF8.GetBytes(s);
request.uploadHandler = new UploadHandlerRaw(bytes);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
yield return request.SendWebRequest();
}
}
}
private IEnumerator PollLoop()
{
while (enable)
{
yield return new WaitForSecondsRealtime(Mathf.Clamp(pollIntervalSeconds, 0.5f, 30f));
if (!BlockSpaceAccountPrefs.IsLinked || joinInProgress)
{
continue;
}
string linkedPlayerId = BlockSpaceAccountPrefs.GetLinkedPlayerId();
if (string.IsNullOrEmpty(linkedPlayerId))
{
continue;
}
string text = (backendBaseUrl ?? string.Empty).Trim().TrimEnd('/');
if (string.IsNullOrEmpty(text))
{
continue;
}
string url = text + "/api/commands/poll";
string s = JsonUtility.ToJson(new PollRequest
{
playerId = linkedPlayerId
});
PollResponse response = null;
using (UnityWebRequest request = new UnityWebRequest(url, "POST"))
{
byte[] bytes = Encoding.UTF8.GetBytes(s);
request.uploadHandler = new UploadHandlerRaw(bytes);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
try
{
response = JsonUtility.FromJson<PollResponse>(request.downloadHandler.text);
}
catch
{
}
}
}
if (response == null || !response.ok || response.command == null)
{
continue;
}
if (!string.Equals(response.command.type, "join_world", StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(response.command.type, "world_reload", StringComparison.OrdinalIgnoreCase))
{
string text2 = (response.command.worldName ?? string.Empty).Trim();
if (!string.IsNullOrEmpty(text2))
{
yield return JoinWorldInstance(text2);
}
}
else if (string.Equals(response.command.type, "world_force_save", StringComparison.OrdinalIgnoreCase))
{
string text3 = (response.command.worldName ?? string.Empty).Trim();
if (!string.IsNullOrEmpty(text3))
{
yield return ForceSaveWorld(text3);
}
}
else if (string.Equals(response.command.type, "join_room", StringComparison.OrdinalIgnoreCase))
{
string text4 = (response.command.roomName ?? string.Empty).Trim();
if (!string.IsNullOrEmpty(text4))
{
yield return JoinExactRoom(text4);
}
}
else if (string.Equals(response.command.type, "join_world_private", StringComparison.OrdinalIgnoreCase))
{
string text5 = (response.command.worldName ?? string.Empty).Trim();
if (!string.IsNullOrEmpty(text5))
{
yield return JoinPrivateWorldInstance(text5);
}
}
}
else
{
string text6 = (response.command.worldName ?? string.Empty).Trim();
if (!string.IsNullOrEmpty(text6))
{
yield return JoinWorldInstance(text6);
}
}
}
}
private static string GetBaseWorldName(string roomName)
{
string text = (roomName ?? string.Empty).Trim();
if (string.IsNullOrEmpty(text))
{
return text;
}
int num = text.LastIndexOf('|');
if (num <= 0)
{
return text;
}
string text2 = text.Substring(num + 1);
if (text2.Length == 3 && Regex.IsMatch(text2, "^[A-Za-z0-9]{3}$"))
{
return text.Substring(0, num);
}
return text;
}
private IEnumerator ForceSaveWorld(string baseWorldName)
{
if (PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null && string.Equals(GetBaseWorldName(PhotonNetwork.CurrentRoom.Name), baseWorldName, StringComparison.OrdinalIgnoreCase))
{
VoxelGroupPhotonSync voxelGroupPhotonSync = UnityEngine.Object.FindFirstObjectByType<VoxelGroupPhotonSync>();
if (!(voxelGroupPhotonSync == null))
{
yield return voxelGroupPhotonSync.ForceSaveNow();
}
}
}
private IEnumerator JoinWorldInstance(string worldName)
{
Debug.Log("[WorldCommandPoller] JoinWorldInstance: starting join for world '" + worldName + "'.");
joinInProgress = true;
BeginLoadingScreen(0.2f);
if (PhotonNetwork.InRoom)
{
SetLoadingProgress(0.24f);
Debug.Log("[WorldCommandPoller] JoinWorldInstance: leaving current room '" + PhotonNetwork.CurrentRoom?.Name + "'.");
leftRoomReady = false;
PhotonNetwork.LeaveRoom();
float start = Time.realtimeSinceStartup;
while (!leftRoomReady && Time.realtimeSinceStartup - start < 10f)
{
yield return null;
}
SetLoadingProgress(0.3f);
Debug.Log($"[WorldCommandPoller] JoinWorldInstance: left room ready={leftRoomReady}, inRoom={PhotonNetwork.InRoom}.");
}
SetLoadingProgress(0.34f);
float masterStart = Time.realtimeSinceStartup;
while (PhotonNetwork.NetworkClientState != ClientState.ConnectedToMasterServer && Time.realtimeSinceStartup - masterStart < 10f)
{
yield return null;
}
SetLoadingProgress(0.4f);
if (PhotonNetwork.NetworkClientState != ClientState.ConnectedToMasterServer)
{
joinInProgress = false;
SetLoadingScreen(visible: false, 0f);
yield break;
}
WorldBackendClient client = new WorldBackendClient(backendBaseUrl);
int maxPlayers = ((PhotonVRManager.Manager != null) ? PhotonVRManager.Manager.DefaultRoomLimit : 16);
for (int attempt = 0; attempt < 5; attempt++)
{
SetLoadingProgress(0.46f);
WorldBackendClient.LobbyTargetResponse target = null;
yield return client.ResolveLobbyTarget(worldName, maxPlayers, delegate(WorldBackendClient.LobbyTargetResponse r)
{
target = r;
});
if (target == null || !target.ok || string.IsNullOrEmpty(target.roomName))
{
Debug.LogWarning(string.Format("[WorldCommandPoller] JoinWorldInstance: lobby target failed for '{0}' [attempt {1}/{2}]. error={3}", worldName, attempt + 1, 5, (target != null) ? target.error : "<null>"));
yield return new WaitForSecondsRealtime(1f);
continue;
}
joinResultReady = false;
joinSucceeded = false;
SetLoadingProgress(0.54f);
ExitGames.Client.Photon.Hashtable hashtable = new ExitGames.Client.Photon.Hashtable();
if (!string.IsNullOrEmpty(target.worldVersion))
{
hashtable["worldVersion"] = target.worldVersion;
}
RoomOptions roomOptions = new RoomOptions();
roomOptions.IsVisible = true;
roomOptions.IsOpen = true;
roomOptions.MaxPlayers = (byte)Mathf.Clamp(maxPlayers, 1, 255);
roomOptions.CustomRoomProperties = hashtable;
roomOptions.CustomRoomPropertiesForLobby = new string[1] { "worldVersion" };
RoomOptions roomOptions2 = roomOptions;
Debug.Log($"[WorldCommandPoller] JoinWorldInstance: backend selected {target.action}('{target.roomName}') worldVersion='{target.worldVersion}' roomsSeen={target.roomsSeen} [attempt {attempt + 1}/{5}].");
if (string.Equals(target.action, "join", StringComparison.OrdinalIgnoreCase))
{
SetLoadingProgress(0.6f);
PhotonNetwork.JoinRoom(target.roomName);
}
else
{
SetLoadingProgress(0.6f);
PhotonNetwork.JoinOrCreateRoom(target.roomName, roomOptions2, null);
}
float start = Time.realtimeSinceStartup;
while (!joinResultReady && Time.realtimeSinceStartup - start < 12f)
{
yield return null;
}
if (!joinResultReady && PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null && string.Equals(PhotonNetwork.CurrentRoom.Name, target.roomName, StringComparison.OrdinalIgnoreCase))
{
joinSucceeded = true;
joinResultReady = true;
Debug.Log("[WorldCommandPoller] JoinWorldInstance: detected successful join by current room '" + target.roomName + "' without callback.");
}
Debug.Log($"[WorldCommandPoller] JoinWorldInstance: room '{target.roomName}' join result ready={joinResultReady}, succeeded={joinSucceeded}, inRoom={PhotonNetwork.InRoom}, currentRoom={PhotonNetwork.CurrentRoom?.Name}.");
if (joinSucceeded && PhotonNetwork.InRoom)
{
Debug.Log("[WorldCommandPoller] JoinWorldInstance: successfully joined room '" + target.roomName + "'.");
joinInProgress = false;
SetLoadingProgress(0.66f);
yield break;
}
if (PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null && !string.Equals(PhotonNetwork.CurrentRoom.Name, target.roomName, StringComparison.OrdinalIgnoreCase))
{
Debug.LogWarning("[WorldCommandPoller] JoinWorldInstance: still in different room '" + PhotonNetwork.CurrentRoom.Name + "' after attempting '" + target.roomName + "', leaving before next attempt.");
leftRoomReady = false;
PhotonNetwork.LeaveRoom();
float leaveStart = Time.realtimeSinceStartup;
while (!leftRoomReady && Time.realtimeSinceStartup - leaveStart < 10f)
{
yield return null;
}
SetLoadingProgress(0.3f);
}
yield return new WaitForSecondsRealtime(0.5f);
}
joinInProgress = false;
SetLoadingScreen(visible: false, 0f);
}
private IEnumerator JoinExactRoom(string roomName)
{
Debug.Log("[WorldCommandPoller] JoinExactRoom: starting join for exact room '" + roomName + "'.");
joinInProgress = true;
BeginLoadingScreen(0.2f);
if (PhotonNetwork.InRoom)
{
SetLoadingProgress(0.24f);
Debug.Log("[WorldCommandPoller] JoinExactRoom: leaving current room '" + PhotonNetwork.CurrentRoom?.Name + "'.");
leftRoomReady = false;
PhotonNetwork.LeaveRoom();
float start = Time.realtimeSinceStartup;
while (!leftRoomReady && Time.realtimeSinceStartup - start < 10f)
{
yield return null;
}
SetLoadingProgress(0.3f);
Debug.Log($"[WorldCommandPoller] JoinExactRoom: left room ready={leftRoomReady}, inRoom={PhotonNetwork.InRoom}.");
}
SetLoadingProgress(0.34f);
float masterStart = Time.realtimeSinceStartup;
while (PhotonNetwork.NetworkClientState != ClientState.ConnectedToMasterServer && Time.realtimeSinceStartup - masterStart < 10f)
{
yield return null;
}
SetLoadingProgress(0.4f);
if (PhotonNetwork.NetworkClientState != ClientState.ConnectedToMasterServer)
{
joinInProgress = false;
SetLoadingScreen(visible: false, 0f);
yield break;
}
if (!PhotonNetwork.InLobby)
{
lobbyReady = false;
PhotonNetwork.JoinLobby();
float start = Time.realtimeSinceStartup;
while (!lobbyReady && Time.realtimeSinceStartup - start < 10f)
{
yield return null;
}
SetLoadingProgress(0.5f);
if (!PhotonNetwork.InLobby)
{
joinInProgress = false;
SetLoadingScreen(visible: false, 0f);
yield break;
}
}
joinResultReady = false;
joinSucceeded = false;
RoomOptions roomOptions = new RoomOptions
{
IsVisible = true,
IsOpen = true,
MaxPlayers = ((PhotonVRManager.Manager != null) ? ((byte)PhotonVRManager.Manager.DefaultRoomLimit) : 16)
};
Debug.Log("[WorldCommandPoller] JoinExactRoom: requesting JoinOrCreateRoom('" + roomName + "').");
SetLoadingProgress(0.6f);
PhotonNetwork.JoinOrCreateRoom(roomName, roomOptions, null);
float waitStart = Time.realtimeSinceStartup;
while (!joinResultReady && Time.realtimeSinceStartup - waitStart < 10f)
{
yield return null;
}
Debug.Log($"[WorldCommandPoller] JoinExactRoom: room '{roomName}' join result ready={joinResultReady}, succeeded={joinSucceeded}, inRoom={PhotonNetwork.InRoom}.");
joinInProgress = false;
if (joinSucceeded && PhotonNetwork.InRoom)
{
SetLoadingProgress(0.66f);
}
else
{
SetLoadingScreen(visible: false, 0f);
}
}
public override void OnLeftRoom()
{
leftRoomReady = true;
base.OnLeftRoom();
}
public override void OnJoinedRoom()
{
joinSucceeded = true;
joinResultReady = true;
Debug.Log("[WorldCommandPoller] OnJoinedRoom: joined room '" + PhotonNetwork.CurrentRoom?.Name + "' successfully.");
base.OnJoinedRoom();
}
public override void OnJoinedLobby()
{
lobbyReady = true;
Debug.Log("[WorldCommandPoller] OnJoinedLobby: Photon lobby joined successfully.");
base.OnJoinedLobby();
}
public override void OnJoinRoomFailed(short returnCode, string message)
{
joinSucceeded = false;
joinResultReady = true;
Debug.LogWarning($"[WorldCommandPoller] OnJoinRoomFailed: code={returnCode}, message={message}, state={PhotonNetwork.NetworkClientState}.");
base.OnJoinRoomFailed(returnCode, message);
}
public override void OnCreateRoomFailed(short returnCode, string message)
{
joinSucceeded = false;
joinResultReady = true;
Debug.LogWarning($"[WorldCommandPoller] OnCreateRoomFailed: code={returnCode}, message={message}, state={PhotonNetwork.NetworkClientState}.");
base.OnCreateRoomFailed(returnCode, message);
}
}
}