EVERYTHING

This commit is contained in:
niko
2026-06-01 17:19:55 +02:00
parent d063b81359
commit b62bac090b
1081 changed files with 1716544 additions and 0 deletions
@@ -0,0 +1,201 @@
using System.Collections.Generic;
using BlockSpace.Voxels;
using Photon.Pun;
using Photon.VR;
using UnityEngine;
using UnityEngine.XR;
namespace BlockSpace.Worlds
{
[DisallowMultipleComponent]
public sealed class CreatorFlightController : MonoBehaviour
{
[SerializeField]
private float flySpeed = 3.5f;
[SerializeField]
private float deadZone = 0.15f;
private InputDevice leftHand;
private bool flightProvidersDisabled;
private readonly Dictionary<Behaviour, bool> cachedProviderStates = new Dictionary<Behaviour, bool>();
private CharacterController phasedController;
private bool controllerCollisionCached;
private bool cachedControllerEnabled;
private bool cachedControllerDetectCollisions;
private void OnEnable()
{
leftHand = InputDevices.GetDeviceAtXRNode(XRNode.LeftHand);
}
private void OnDisable()
{
RestoreProviders();
}
private void Update()
{
if (!PhotonNetwork.InRoom)
{
RestoreProviders();
return;
}
VoxelGroupManager instance = VoxelGroupManager.Instance;
if (instance == null || instance.EditingLocked || !instance.CreatorMode)
{
RestoreProviders();
RestoreCharacterControllerCollisions();
return;
}
DisableNonFlightProviders();
if (!leftHand.isValid)
{
leftHand = InputDevices.GetDeviceAtXRNode(XRNode.LeftHand);
}
Vector2 value = Vector2.zero;
if (leftHand.isValid)
{
leftHand.TryGetFeatureValue(CommonUsages.primary2DAxis, out value);
}
if (Mathf.Abs(value.x) < deadZone)
{
value.x = 0f;
}
if (Mathf.Abs(value.y) < deadZone)
{
value.y = 0f;
}
Transform transform = ((PhotonVRManager.Manager != null) ? PhotonVRManager.Manager.Head : null);
Transform localVrTeleportRoot = PlayerTeleportUtil.GetLocalVrTeleportRoot();
if (!(transform == null) && !(localVrTeleportRoot == null))
{
Vector3 obj = ((transform.forward.sqrMagnitude > 0.0001f) ? transform.forward.normalized : Vector3.forward);
Vector3 vector = ((transform.right.sqrMagnitude > 0.0001f) ? transform.right.normalized : Vector3.right);
Vector3 vector2 = obj * value.y + vector * value.x;
if (vector2.sqrMagnitude > 1f)
{
vector2.Normalize();
}
CharacterController component = localVrTeleportRoot.GetComponent<CharacterController>();
if (component != null)
{
ApplyCharacterControllerPhase(component);
}
Vector3 vector3 = vector2 * flySpeed;
if (!(vector3.sqrMagnitude <= 0.0001f))
{
localVrTeleportRoot.position += vector3 * Time.deltaTime;
}
}
}
private void DisableNonFlightProviders()
{
if (flightProvidersDisabled)
{
return;
}
PhotonVRManager manager = PhotonVRManager.Manager;
if (!(manager == null))
{
cachedProviderStates.Clear();
cachedProviderStates.Clear();
DisableBehavioursUnder(manager.gameObject);
if (manager.LocalPlayer != null)
{
DisableBehavioursUnder(manager.LocalPlayer.gameObject);
}
if (manager.transform.root != null)
{
DisableBehavioursUnder(manager.transform.root.gameObject);
}
flightProvidersDisabled = true;
}
}
private void DisableBehavioursUnder(GameObject root)
{
if (root == null)
{
return;
}
Behaviour[] componentsInChildren = root.GetComponentsInChildren<Behaviour>(includeInactive: true);
foreach (Behaviour behaviour in componentsInChildren)
{
if (!(behaviour == null) && !(behaviour == this))
{
string text = behaviour.GetType().Name;
if (!string.IsNullOrEmpty(text) && !text.Contains("TurnProvider") && !text.Contains("SmoothTurn") && !text.Contains("SnapTurn") && (text.Contains("GravityProvider") || text.Contains("MoveProvider") || text.Contains("LocomotionProvider") || text.Contains("TeleportationProvider") || text.Contains("ClimbProvider") || text.Contains("ContinuousMove") || text.Contains("DynamicMove")))
{
cachedProviderStates[behaviour] = behaviour.enabled;
behaviour.enabled = false;
}
}
}
}
private void RestoreProviders()
{
if (!flightProvidersDisabled)
{
RestoreCharacterControllerCollisions();
return;
}
foreach (KeyValuePair<Behaviour, bool> cachedProviderState in cachedProviderStates)
{
if (cachedProviderState.Key != null)
{
cachedProviderState.Key.enabled = cachedProviderState.Value;
}
}
cachedProviderStates.Clear();
flightProvidersDisabled = false;
RestoreCharacterControllerCollisions();
}
private void ApplyCharacterControllerPhase(CharacterController controller)
{
if (!(controller == null))
{
if (phasedController != controller)
{
RestoreCharacterControllerCollisions();
phasedController = controller;
cachedControllerEnabled = controller.enabled;
cachedControllerDetectCollisions = controller.detectCollisions;
controllerCollisionCached = true;
}
controller.enabled = false;
controller.detectCollisions = false;
}
}
private void RestoreCharacterControllerCollisions()
{
if (controllerCollisionCached && phasedController != null)
{
phasedController.enabled = cachedControllerEnabled;
phasedController.detectCollisions = cachedControllerDetectCollisions;
EnsureControllerUsableAfterEdit(phasedController);
}
phasedController = null;
controllerCollisionCached = false;
}
private static void EnsureControllerUsableAfterEdit(CharacterController controller)
{
if (!(controller == null))
{
controller.enabled = true;
controller.detectCollisions = true;
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 5ce56b9c525bcb892a477ee43d16186d
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,28 @@
using Photon.Pun;
using UnityEngine;
namespace BlockSpace.Worlds
{
[DisallowMultipleComponent]
public sealed class FallResetTeleport : MonoBehaviourPunCallbacks
{
[SerializeField]
private float yKillPlane = -2000f;
[SerializeField]
private Vector3 respawnPosition = new Vector3(0f, 5f, 0f);
private void Update()
{
if (PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null)
{
Transform localVrTeleportRoot = PlayerTeleportUtil.GetLocalVrTeleportRoot();
Vector3 obj = ((localVrTeleportRoot != null) ? localVrTeleportRoot.position : base.transform.position);
if (!(obj.y >= yKillPlane))
{
PlayerTeleportUtil.TryTeleportLocalPlayer(respawnPosition);
}
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 2504ffd6bd641f2cc6b5297611d2e601
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,96 @@
using System;
using System.Text.RegularExpressions;
using Photon.Pun;
using UnityEngine;
namespace BlockSpace.Worlds
{
[DisallowMultipleComponent]
public sealed class LobbyOnlyEnabler : MonoBehaviourPunCallbacks
{
[Tooltip("Objects that should only be enabled while in the Lobby world (Lobby or Lobby|###).")]
[SerializeField]
private GameObject[] targets;
[Tooltip("If true, toggles this object's children too (but keeps this GameObject active so the script still runs).")]
[SerializeField]
private bool includeSelf;
[Tooltip("World base name considered the lobby.")]
[SerializeField]
private string lobbyWorldName = "Lobby";
[Tooltip("How often to check the lobby status in seconds.")]
[SerializeField]
private float checkInterval = 0.5f;
private void Awake()
{
InvokeRepeating("Apply", 0f, checkInterval);
Apply();
}
private void Apply()
{
bool active = IsInLobbyWorld();
if (includeSelf)
{
for (int i = 0; i < base.transform.childCount; i++)
{
Transform child = base.transform.GetChild(i);
if (child != null)
{
child.gameObject.SetActive(active);
}
}
}
if (targets == null)
{
return;
}
for (int j = 0; j < targets.Length; j++)
{
GameObject gameObject = targets[j];
if (!(gameObject == null) && !(gameObject == base.gameObject))
{
gameObject.SetActive(active);
}
}
}
private bool IsInLobbyWorld()
{
if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null)
{
return false;
}
string baseWorldName = GetBaseWorldName(PhotonNetwork.CurrentRoom.Name ?? string.Empty);
string text = (lobbyWorldName ?? string.Empty).Trim();
if (string.IsNullOrEmpty(text))
{
text = "Lobby";
}
return string.Equals(baseWorldName, text, StringComparison.OrdinalIgnoreCase);
}
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;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 475e38d91f56973ad2a1e19118f4433c
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,93 @@
using Photon.Pun;
using Photon.VR;
using Photon.VR.Player;
using UnityEngine;
namespace BlockSpace.Worlds
{
public static class PlayerTeleportUtil
{
public static Transform GetLocalVrTeleportRoot()
{
if (PhotonVRManager.Manager == null || PhotonVRManager.Manager.Head == null)
{
return null;
}
Transform head = PhotonVRManager.Manager.Head;
if (head.parent == null)
{
return head;
}
Transform parent = head.parent;
while (parent != null)
{
if (parent.GetComponent<CharacterController>() != null)
{
return parent;
}
if (parent.parent == null)
{
return parent;
}
parent = parent.parent;
}
return head;
}
public static bool TryTeleportLocalPlayer(Vector3 position)
{
Transform localVrTeleportRoot = GetLocalVrTeleportRoot();
if (localVrTeleportRoot != null)
{
TeleportAndZeroVelocity(localVrTeleportRoot.gameObject, position);
return true;
}
PhotonVRPlayer photonVRPlayer = ((PhotonVRManager.Manager != null) ? PhotonVRManager.Manager.LocalPlayer : null);
if (photonVRPlayer != null)
{
TeleportAndZeroVelocity(photonVRPlayer.gameObject, position);
return true;
}
int num = ((PhotonNetwork.LocalPlayer != null) ? PhotonNetwork.LocalPlayer.ActorNumber : 0);
PhotonVRPlayer[] array = Object.FindObjectsByType<PhotonVRPlayer>(FindObjectsInactive.Include);
foreach (PhotonVRPlayer photonVRPlayer2 in array)
{
if (!(photonVRPlayer2 == null))
{
PhotonView component = photonVRPlayer2.GetComponent<PhotonView>();
if (!(component == null) && (num <= 0 || component.OwnerActorNr == num) && (num > 0 || component.IsMine))
{
TeleportAndZeroVelocity(photonVRPlayer2.gameObject, position);
return true;
}
}
}
return false;
}
private static void TeleportAndZeroVelocity(GameObject root, Vector3 position)
{
if (!(root == null))
{
CharacterController component = root.GetComponent<CharacterController>();
if (component != null)
{
bool enabled = component.enabled;
component.enabled = false;
root.transform.position = position;
component.enabled = enabled;
}
else
{
root.transform.position = position;
}
Rigidbody component2 = root.GetComponent<Rigidbody>();
if (component2 != null)
{
component2.linearVelocity = Vector3.zero;
component2.angularVelocity = Vector3.zero;
}
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 638c6756de7d84b6b5b83f87f8d8baa1
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,390 @@
using System;
using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
namespace BlockSpace.Worlds
{
public sealed class WorldBackendClient
{
[Serializable]
public sealed class ResolveWorldResponse
{
public bool ok;
public bool exists;
public string worldId;
public string name;
public string visibility;
public bool isCreator;
public string updatedAt;
public string error;
}
[Serializable]
public sealed class LobbyTargetResponse
{
public bool ok;
public string worldName;
public string worldId;
public string worldVersion;
public string roomName;
public string action;
public bool exists;
public int roomsSeen;
public string error;
}
[Serializable]
public sealed class VisitResponse
{
public bool ok;
public int visits;
public string error;
}
[Serializable]
public sealed class BroadcastReloadResponse
{
public bool ok;
public int targets;
public string error;
}
[Serializable]
public sealed class UploadGroupsResponse
{
public bool ok;
public bool saved;
public int bytes;
public string updatedAt;
public string error;
}
[Serializable]
private sealed class BroadcastReloadRequest
{
public string playerId;
public string worldName;
}
private readonly string baseUrl;
public WorldBackendClient(string baseUrl)
{
this.baseUrl = (baseUrl ?? string.Empty).Trim().TrimEnd('/');
}
public IEnumerator ResolveLobbyTarget(string worldName, int maxPlayers, Action<LobbyTargetResponse> onDone)
{
if (string.IsNullOrEmpty(baseUrl))
{
onDone?.Invoke(new LobbyTargetResponse
{
ok = false,
error = "No base URL set."
});
yield break;
}
string text = (worldName ?? string.Empty).Trim();
if (string.IsNullOrEmpty(text))
{
onDone?.Invoke(new LobbyTargetResponse
{
ok = false,
error = "World name is required."
});
yield break;
}
string uri = baseUrl + "/api/worlds/lobby-target?name=" + UnityWebRequest.EscapeURL(text) + "&maxPlayers=" + Mathf.Clamp(maxPlayers, 1, 255);
using UnityWebRequest request = UnityWebRequest.Get(uri);
request.downloadHandler = new DownloadHandlerBuffer();
yield return request.SendWebRequest();
LobbyTargetResponse lobbyTargetResponse = null;
if (request.result == UnityWebRequest.Result.Success)
{
try
{
lobbyTargetResponse = JsonUtility.FromJson<LobbyTargetResponse>(request.downloadHandler.text);
}
catch
{
}
}
if (lobbyTargetResponse == null)
{
lobbyTargetResponse = new LobbyTargetResponse
{
ok = false,
error = request.error
};
}
onDone?.Invoke(lobbyTargetResponse);
}
public IEnumerator ResolveWorld(string roomName, string playerId, Action<ResolveWorldResponse> onDone)
{
if (string.IsNullOrEmpty(baseUrl))
{
onDone?.Invoke(new ResolveWorldResponse
{
ok = false,
exists = false,
error = "No base URL set."
});
yield break;
}
string text = (roomName ?? string.Empty).Trim();
if (string.IsNullOrEmpty(text))
{
onDone?.Invoke(new ResolveWorldResponse
{
ok = false,
exists = false,
error = "Room name is required."
});
yield break;
}
string uri = baseUrl + "/api/worlds/resolve?name=" + UnityWebRequest.EscapeURL(text) + "&playerId=" + UnityWebRequest.EscapeURL(playerId ?? string.Empty);
using UnityWebRequest request = UnityWebRequest.Get(uri);
request.downloadHandler = new DownloadHandlerBuffer();
yield return request.SendWebRequest();
ResolveWorldResponse resolveWorldResponse = null;
if (request.result == UnityWebRequest.Result.Success)
{
try
{
resolveWorldResponse = JsonUtility.FromJson<ResolveWorldResponse>(request.downloadHandler.text);
}
catch
{
}
}
if (resolveWorldResponse == null)
{
resolveWorldResponse = new ResolveWorldResponse
{
ok = false,
exists = false,
error = request.error
};
}
onDone?.Invoke(resolveWorldResponse);
}
public IEnumerator DownloadGroups(string worldId, Action<byte[]> onDone)
{
if (string.IsNullOrEmpty(baseUrl))
{
onDone?.Invoke(Array.Empty<byte>());
yield break;
}
string text = (worldId ?? string.Empty).Trim();
if (string.IsNullOrEmpty(text))
{
onDone?.Invoke(Array.Empty<byte>());
yield break;
}
string uri = baseUrl + "/api/worlds/" + UnityWebRequest.EscapeURL(text) + "/groups";
using UnityWebRequest request = UnityWebRequest.Get(uri);
request.downloadHandler = new DownloadHandlerBuffer();
yield return request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success)
{
onDone?.Invoke(Array.Empty<byte>());
yield break;
}
onDone?.Invoke(request.downloadHandler.data ?? Array.Empty<byte>());
}
public IEnumerator UploadGroups(string worldId, byte[] gzipBytes, string playerId, Action<UploadGroupsResponse> onDone)
{
if (string.IsNullOrEmpty(baseUrl))
{
onDone?.Invoke(new UploadGroupsResponse
{
ok = false,
saved = false,
bytes = 0,
error = "No base URL set."
});
yield break;
}
string text = (worldId ?? string.Empty).Trim();
if (string.IsNullOrEmpty(text))
{
onDone?.Invoke(new UploadGroupsResponse
{
ok = false,
saved = false,
bytes = 0,
error = "worldId is required."
});
yield break;
}
string text2 = (playerId ?? string.Empty).Trim();
if (string.IsNullOrEmpty(text2))
{
onDone?.Invoke(new UploadGroupsResponse
{
ok = false,
saved = false,
bytes = 0,
error = "playerId is required."
});
yield break;
}
string url = baseUrl + "/api/worlds/" + UnityWebRequest.EscapeURL(text) + "/groups?playerId=" + UnityWebRequest.EscapeURL(text2);
byte[] data = gzipBytes ?? Array.Empty<byte>();
using UnityWebRequest request = new UnityWebRequest(url, "PUT");
request.uploadHandler = new UploadHandlerRaw(data);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/octet-stream");
yield return request.SendWebRequest();
UploadGroupsResponse uploadGroupsResponse = null;
if (request.result == UnityWebRequest.Result.Success)
{
try
{
uploadGroupsResponse = JsonUtility.FromJson<UploadGroupsResponse>(request.downloadHandler.text);
}
catch
{
}
}
if (uploadGroupsResponse == null)
{
uploadGroupsResponse = new UploadGroupsResponse
{
ok = false,
saved = false,
bytes = 0,
error = request.error
};
}
onDone?.Invoke(uploadGroupsResponse);
}
public IEnumerator IncrementVisit(string worldId, Action<VisitResponse> onDone)
{
if (string.IsNullOrEmpty(baseUrl))
{
onDone?.Invoke(new VisitResponse
{
ok = false,
visits = 0,
error = "No base URL set."
});
yield break;
}
string text = (worldId ?? string.Empty).Trim();
if (string.IsNullOrEmpty(text))
{
onDone?.Invoke(new VisitResponse
{
ok = false,
visits = 0,
error = "worldId is required."
});
yield break;
}
string url = baseUrl + "/api/worlds/" + UnityWebRequest.EscapeURL(text) + "/visit";
using UnityWebRequest request = new UnityWebRequest(url, "POST");
request.uploadHandler = new UploadHandlerRaw(Array.Empty<byte>());
request.downloadHandler = new DownloadHandlerBuffer();
yield return request.SendWebRequest();
VisitResponse visitResponse = null;
if (request.result == UnityWebRequest.Result.Success)
{
try
{
visitResponse = JsonUtility.FromJson<VisitResponse>(request.downloadHandler.text);
}
catch
{
}
}
if (visitResponse == null)
{
visitResponse = new VisitResponse
{
ok = false,
visits = 0,
error = request.error
};
}
onDone?.Invoke(visitResponse);
}
public IEnumerator BroadcastReload(string playerId, string worldName, Action<BroadcastReloadResponse> onDone)
{
if (string.IsNullOrEmpty(baseUrl))
{
onDone?.Invoke(new BroadcastReloadResponse
{
ok = false,
targets = 0,
error = "No base URL set."
});
yield break;
}
string url = baseUrl + "/api/worlds/broadcast-reload";
string s = JsonUtility.ToJson(new BroadcastReloadRequest
{
playerId = (playerId ?? ""),
worldName = (worldName ?? "")
});
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();
BroadcastReloadResponse broadcastReloadResponse = null;
if (request.result == UnityWebRequest.Result.Success)
{
try
{
broadcastReloadResponse = JsonUtility.FromJson<BroadcastReloadResponse>(request.downloadHandler.text);
}
catch
{
}
}
if (broadcastReloadResponse == null)
{
broadcastReloadResponse = new BroadcastReloadResponse
{
ok = false,
targets = 0,
error = request.error
};
}
onDone?.Invoke(broadcastReloadResponse);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 1d028e93fd236b14d8bc5301b005b3ab
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,648 @@
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);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: f6188b95afcfc6352979a6717a50de0b
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,672 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;
using BlockSpace.Voxels;
using UnityEngine;
namespace BlockSpace.Worlds
{
public static class WorldGroupsCodec
{
[Serializable]
public struct WorldGroupRecord
{
public Vector3Int position;
public byte packedRotation;
public Vector3Int size;
public byte[] voxels;
}
[Serializable]
public struct NodePortValueRecord
{
public string portName;
public BSValue value;
}
[Serializable]
public struct WorldNodeRecord
{
public int nodeId;
public string definitionName;
public Vector3 position;
public Quaternion rotation;
public NodePortValueRecord[] inputValues;
public NodePortValueRecord[] outputValues;
}
[Serializable]
public struct WorldWireRecord
{
public int outputNodeId;
public string outputPortName;
public int inputNodeId;
public string inputPortName;
}
[Serializable]
public struct WorldSaveData
{
public List<WorldGroupRecord> groupRecords;
public List<WorldNodeRecord> nodeRecords;
public List<WorldWireRecord> wireRecords;
}
private static readonly byte[] Magic = Encoding.ASCII.GetBytes("BSW1");
private static readonly byte[] NodesSectionMagic = Encoding.ASCII.GetBytes("BSN1");
private static readonly byte[] WiresSectionMagic = Encoding.ASCII.GetBytes("BSR1");
private const string GroupRuntimeWirePrefix = "__group_runtime__:";
private const string GroupSavedWirePrefix = "__group_saved__:";
public static WorldWireRecord CreateGroupRuntimeWireRecord(int groupId, int inputNodeId, string inputPortName)
{
return new WorldWireRecord
{
outputNodeId = 0,
outputPortName = "__group_runtime__:" + Mathf.Max(0, groupId),
inputNodeId = inputNodeId,
inputPortName = inputPortName
};
}
public static WorldWireRecord CreateGroupSavedWireRecord(int savedGroupIndex, int inputNodeId, string inputPortName)
{
return new WorldWireRecord
{
outputNodeId = 0,
outputPortName = "__group_saved__:" + Mathf.Max(0, savedGroupIndex),
inputNodeId = inputNodeId,
inputPortName = inputPortName
};
}
public static bool TryGetGroupRuntimeWireSourceId(WorldWireRecord record, out int groupId)
{
return TryParseGroupWireValue(record, "__group_runtime__:", out groupId);
}
public static bool TryGetGroupSavedWireIndex(WorldWireRecord record, out int savedGroupIndex)
{
return TryParseGroupWireValue(record, "__group_saved__:", out savedGroupIndex);
}
public static byte[] EncodeGzip(IEnumerable<VoxelGroup> groups)
{
return EncodeGzip(groups, null, null);
}
public static byte[] EncodeGzip(IEnumerable<VoxelGroup> groups, IEnumerable<WorldNodeRecord> nodes, IEnumerable<WorldWireRecord> wires)
{
List<WorldGroupRecord> list = BuildGroupRecords(groups);
List<WorldNodeRecord> list2 = ((nodes != null) ? new List<WorldNodeRecord>(nodes) : new List<WorldNodeRecord>());
List<WorldWireRecord> list3 = ((wires != null) ? new List<WorldWireRecord>(wires) : new List<WorldWireRecord>());
using MemoryStream memoryStream = new MemoryStream();
memoryStream.Write(Magic, 0, Magic.Length);
WriteVarUInt(memoryStream, (uint)list.Count);
for (int i = 0; i < list.Count; i++)
{
WriteGroupRecord(memoryStream, list[i]);
}
if (list2.Count > 0)
{
memoryStream.Write(NodesSectionMagic, 0, NodesSectionMagic.Length);
WriteVarUInt(memoryStream, (uint)list2.Count);
for (int j = 0; j < list2.Count; j++)
{
WriteNodeRecord(memoryStream, list2[j]);
}
}
if (list3.Count > 0)
{
memoryStream.Write(WiresSectionMagic, 0, WiresSectionMagic.Length);
WriteVarUInt(memoryStream, (uint)list3.Count);
for (int k = 0; k < list3.Count; k++)
{
WriteWireRecord(memoryStream, list3[k]);
}
}
memoryStream.Position = 0L;
using MemoryStream memoryStream2 = new MemoryStream();
using (GZipStream destination = new GZipStream(memoryStream2, System.IO.Compression.CompressionLevel.Optimal, leaveOpen: true))
{
memoryStream.CopyTo(destination);
}
return memoryStream2.ToArray();
}
public static bool TryDecodeGzip(byte[] gzipBytes, out List<WorldGroupRecord> records)
{
WorldSaveData saveData;
bool result = TryDecodeGzip(gzipBytes, out saveData);
records = saveData.groupRecords ?? new List<WorldGroupRecord>();
return result;
}
private static bool TryParseGroupWireValue(WorldWireRecord record, string prefix, out int value)
{
value = 0;
if (record.outputNodeId > 0 || string.IsNullOrEmpty(record.outputPortName) || !record.outputPortName.StartsWith(prefix, StringComparison.Ordinal))
{
return false;
}
if (int.TryParse(record.outputPortName.Substring(prefix.Length), out value))
{
return value >= 0;
}
return false;
}
public static bool TryDecodeGzip(byte[] gzipBytes, out WorldSaveData saveData)
{
saveData = new WorldSaveData
{
groupRecords = new List<WorldGroupRecord>(),
nodeRecords = new List<WorldNodeRecord>(),
wireRecords = new List<WorldWireRecord>()
};
if (gzipBytes == null || gzipBytes.Length == 0)
{
return true;
}
byte[] array;
try
{
array = DecompressGzip(gzipBytes);
}
catch
{
return false;
}
if (array.Length < Magic.Length)
{
return true;
}
using MemoryStream memoryStream = new MemoryStream(array);
byte[] array2 = new byte[Magic.Length];
if (memoryStream.Read(array2, 0, array2.Length) != array2.Length)
{
return false;
}
for (int i = 0; i < Magic.Length; i++)
{
if (array2[i] != Magic[i])
{
return true;
}
}
uint num;
try
{
num = ReadVarUInt(memoryStream);
}
catch
{
return false;
}
for (uint num2 = 0u; num2 < num; num2++)
{
if (!TryReadGroupRecord(memoryStream, out var record))
{
return false;
}
saveData.groupRecords.Add(record);
}
string sectionName;
while (memoryStream.Position < memoryStream.Length && TryReadSectionHeader(memoryStream, out sectionName))
{
if (sectionName == "nodes")
{
if (!TryReadNodeSection(memoryStream, saveData.nodeRecords))
{
return false;
}
continue;
}
if (!(sectionName == "wires"))
{
break;
}
if (!TryReadWireSection(memoryStream, saveData.wireRecords))
{
return false;
}
}
return true;
}
private static List<WorldGroupRecord> BuildGroupRecords(IEnumerable<VoxelGroup> groups)
{
List<WorldGroupRecord> list = new List<WorldGroupRecord>();
if (groups == null)
{
return list;
}
foreach (VoxelGroup group in groups)
{
if (!(group == null) && group.size.x > 0 && group.size.y > 0 && group.size.z > 0 && group.voxels != null)
{
list.Add(new WorldGroupRecord
{
position = group.position,
packedRotation = group.packedRotation,
size = group.size,
voxels = (byte[])group.voxels.Clone()
});
}
}
return list;
}
private static void WriteGroupRecord(Stream stream, WorldGroupRecord record)
{
WriteVarInt(stream, record.position.x);
WriteVarInt(stream, record.position.y);
WriteVarInt(stream, record.position.z);
stream.WriteByte(record.packedRotation);
WriteVarUInt(stream, (uint)Mathf.Max(0, record.size.x));
WriteVarUInt(stream, (uint)Mathf.Max(0, record.size.y));
WriteVarUInt(stream, (uint)Mathf.Max(0, record.size.z));
WriteVoxelsRle(stream, record.voxels);
}
private static bool TryReadGroupRecord(Stream stream, out WorldGroupRecord record)
{
record = default(WorldGroupRecord);
try
{
int x = ReadVarInt(stream);
int y = ReadVarInt(stream);
int z = ReadVarInt(stream);
int num = stream.ReadByte();
if (num < 0)
{
return false;
}
uint num2 = ReadVarUInt(stream);
uint num3 = ReadVarUInt(stream);
uint num4 = ReadVarUInt(stream);
int num5 = (int)(num2 * num3 * num4);
if (num5 < 0)
{
return false;
}
byte[] voxels = ReadVoxelsRle(stream, num5);
record = new WorldGroupRecord
{
position = new Vector3Int(x, y, z),
packedRotation = (byte)num,
size = new Vector3Int((int)num2, (int)num3, (int)num4),
voxels = voxels
};
return true;
}
catch
{
return false;
}
}
private static bool TryReadSectionHeader(Stream stream, out string sectionName)
{
sectionName = string.Empty;
if (stream.Position + 4 > stream.Length)
{
return false;
}
byte[] array = new byte[4];
if (stream.Read(array, 0, array.Length) != array.Length)
{
return false;
}
if (SectionMatches(array, NodesSectionMagic))
{
sectionName = "nodes";
return true;
}
if (SectionMatches(array, WiresSectionMagic))
{
sectionName = "wires";
return true;
}
return false;
}
private static bool TryReadNodeSection(Stream stream, List<WorldNodeRecord> records)
{
try
{
uint num = ReadVarUInt(stream);
for (uint num2 = 0u; num2 < num; num2++)
{
records.Add(ReadNodeRecord(stream));
}
return true;
}
catch
{
return false;
}
}
private static bool TryReadWireSection(Stream stream, List<WorldWireRecord> records)
{
try
{
uint num = ReadVarUInt(stream);
for (uint num2 = 0u; num2 < num; num2++)
{
records.Add(ReadWireRecord(stream));
}
return true;
}
catch
{
return false;
}
}
private static void WriteNodeRecord(Stream stream, WorldNodeRecord record)
{
WriteVarInt(stream, record.nodeId);
WriteString(stream, record.definitionName);
WriteFloat(stream, record.position.x);
WriteFloat(stream, record.position.y);
WriteFloat(stream, record.position.z);
WriteFloat(stream, record.rotation.x);
WriteFloat(stream, record.rotation.y);
WriteFloat(stream, record.rotation.z);
WriteFloat(stream, record.rotation.w);
WritePortValueRecords(stream, record.inputValues);
WritePortValueRecords(stream, record.outputValues);
}
private static WorldNodeRecord ReadNodeRecord(Stream stream)
{
return new WorldNodeRecord
{
nodeId = ReadVarInt(stream),
definitionName = ReadString(stream),
position = new Vector3(ReadFloat(stream), ReadFloat(stream), ReadFloat(stream)),
rotation = new Quaternion(ReadFloat(stream), ReadFloat(stream), ReadFloat(stream), ReadFloat(stream)),
inputValues = ReadPortValueRecords(stream),
outputValues = ReadPortValueRecords(stream)
};
}
private static void WriteWireRecord(Stream stream, WorldWireRecord record)
{
WriteVarInt(stream, record.outputNodeId);
WriteString(stream, record.outputPortName);
WriteVarInt(stream, record.inputNodeId);
WriteString(stream, record.inputPortName);
}
private static WorldWireRecord ReadWireRecord(Stream stream)
{
return new WorldWireRecord
{
outputNodeId = ReadVarInt(stream),
outputPortName = ReadString(stream),
inputNodeId = ReadVarInt(stream),
inputPortName = ReadString(stream)
};
}
private static void WritePortValueRecords(Stream stream, NodePortValueRecord[] records)
{
if (records == null)
{
WriteVarUInt(stream, 0u);
return;
}
WriteVarUInt(stream, (uint)records.Length);
for (int i = 0; i < records.Length; i++)
{
WriteString(stream, records[i].portName);
WriteBSValue(stream, records[i].value);
}
}
private static NodePortValueRecord[] ReadPortValueRecords(Stream stream)
{
uint num = ReadVarUInt(stream);
NodePortValueRecord[] array = new NodePortValueRecord[num];
for (int i = 0; i < num; i++)
{
array[i] = new NodePortValueRecord
{
portName = ReadString(stream),
value = ReadBSValue(stream)
};
}
return array;
}
private static void WriteBSValue(Stream stream, BSValue value)
{
value = value.Normalize();
stream.WriteByte((byte)value.Kind);
switch (value.Kind)
{
case BSValueKind.Number:
WriteDouble(stream, value.Number);
break;
case BSValueKind.Text:
WriteString(stream, value.Text);
break;
case BSValueKind.Bool:
stream.WriteByte((byte)(value.Bool ? 1u : 0u));
break;
case BSValueKind.Player:
WriteVarInt(stream, value.PlayerId);
break;
case BSValueKind.Group:
WriteVarInt(stream, value.GroupId);
break;
}
}
private static BSValue ReadBSValue(Stream stream)
{
return (BSValueKind)stream.ReadByte() switch
{
BSValueKind.Number => BSValue.FromNumber(ReadDouble(stream)),
BSValueKind.Text => BSValue.FromText(ReadString(stream)),
BSValueKind.Bool => BSValue.FromBool(stream.ReadByte() != 0),
BSValueKind.Player => BSValue.FromPlayer(ReadVarInt(stream)),
BSValueKind.Group => BSValue.FromGroup(ReadVarInt(stream)),
_ => BSValue.None,
};
}
private static bool SectionMatches(byte[] a, byte[] b)
{
if (a == null || b == null || a.Length != b.Length)
{
return false;
}
for (int i = 0; i < a.Length; i++)
{
if (a[i] != b[i])
{
return false;
}
}
return true;
}
private static void WriteVoxelsRle(Stream stream, byte[] voxels)
{
if (voxels == null || voxels.Length == 0)
{
return;
}
int j;
for (int i = 0; i < voxels.Length; i += j)
{
byte b = voxels[i];
j = 1;
for (int num = voxels.Length - i; j < num && voxels[i + j] == b; j++)
{
}
stream.WriteByte(b);
WriteVarUInt(stream, (uint)j);
}
}
private static byte[] ReadVoxelsRle(Stream stream, int expectedLength)
{
if (expectedLength <= 0)
{
return Array.Empty<byte>();
}
byte[] array = new byte[expectedLength];
int num3;
for (int i = 0; i < expectedLength; i += num3)
{
int num = stream.ReadByte();
if (num < 0)
{
throw new EndOfStreamException();
}
uint num2 = ReadVarUInt(stream);
if (num2 == 0)
{
throw new InvalidDataException("RLE run length was 0.");
}
int val = expectedLength - i;
num3 = (int)Math.Min(num2, (uint)val);
for (int j = 0; j < num3; j++)
{
array[i + j] = (byte)num;
}
}
return array;
}
private static byte[] DecompressGzip(byte[] gzipBytes)
{
using MemoryStream stream = new MemoryStream(gzipBytes);
using GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress);
using MemoryStream memoryStream = new MemoryStream();
gZipStream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
private static void WriteString(Stream stream, string value)
{
string s = value ?? string.Empty;
byte[] bytes = Encoding.UTF8.GetBytes(s);
WriteVarUInt(stream, (uint)bytes.Length);
stream.Write(bytes, 0, bytes.Length);
}
private static string ReadString(Stream stream)
{
uint num = ReadVarUInt(stream);
if (num == 0)
{
return string.Empty;
}
byte[] array = new byte[num];
if (stream.Read(array, 0, array.Length) != array.Length)
{
throw new EndOfStreamException();
}
return Encoding.UTF8.GetString(array);
}
private static void WriteFloat(Stream stream, float value)
{
byte[] bytes = BitConverter.GetBytes(value);
stream.Write(bytes, 0, bytes.Length);
}
private static float ReadFloat(Stream stream)
{
byte[] array = new byte[4];
if (stream.Read(array, 0, array.Length) != array.Length)
{
throw new EndOfStreamException();
}
return BitConverter.ToSingle(array, 0);
}
private static void WriteDouble(Stream stream, double value)
{
byte[] bytes = BitConverter.GetBytes(value);
stream.Write(bytes, 0, bytes.Length);
}
private static double ReadDouble(Stream stream)
{
byte[] array = new byte[8];
if (stream.Read(array, 0, array.Length) != array.Length)
{
throw new EndOfStreamException();
}
return BitConverter.ToDouble(array, 0);
}
private static void WriteVarInt(Stream stream, int value)
{
uint value2 = (uint)((value << 1) ^ (value >> 31));
WriteVarUInt(stream, value2);
}
private static int ReadVarInt(Stream stream)
{
uint num = ReadVarUInt(stream);
return (int)((num >> 1) ^ (0 - (num & 1)));
}
private static void WriteVarUInt(Stream stream, uint value)
{
while (value >= 128)
{
stream.WriteByte((byte)(value | 0x80));
value >>= 7;
}
stream.WriteByte((byte)value);
}
private static uint ReadVarUInt(Stream stream)
{
uint num = 0u;
for (int i = 0; i < 35; i += 7)
{
int num2 = stream.ReadByte();
if (num2 < 0)
{
throw new EndOfStreamException();
}
num |= (uint)((num2 & 0x7F) << i);
if ((num2 & 0x80) == 0)
{
return num;
}
}
throw new InvalidDataException("VarUInt was too long.");
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 06756f42563711c56a7dd62fd0f60a5c
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,355 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace BlockSpace.Worlds
{
[DisallowMultipleComponent]
public sealed class WorldLoadingScreenController : MonoBehaviour
{
private struct CameraState
{
public CameraClearFlags clearFlags;
public Color backgroundColor;
}
private struct RigidbodyState
{
public Vector3 linearVelocity;
public Vector3 angularVelocity;
}
[SerializeField]
private GameObject loadingScreenObject;
[SerializeField]
private Slider progressSlider;
[SerializeField]
private bool hideSceneWhileLoading = true;
[SerializeField]
private int requiredTeleportsToComplete = 2;
private readonly Dictionary<Renderer, bool> rendererStates = new Dictionary<Renderer, bool>();
private readonly Dictionary<Canvas, bool> canvasStates = new Dictionary<Canvas, bool>();
private readonly Dictionary<Graphic, bool> graphicStates = new Dictionary<Graphic, bool>();
private readonly Dictionary<Camera, CameraState> cameraStates = new Dictionary<Camera, CameraState>();
private readonly Dictionary<Behaviour, bool> movementBehaviourStates = new Dictionary<Behaviour, bool>();
private readonly Dictionary<Rigidbody, RigidbodyState> rigidbodyStates = new Dictionary<Rigidbody, RigidbodyState>();
private bool loading;
private int completedTeleports;
public static WorldLoadingScreenController Instance { get; private set; }
public bool IsLoading => loading;
public static event Action LoadingFinished;
public void Configure(GameObject screenObject, Slider slider)
{
if (screenObject != null)
{
loadingScreenObject = screenObject;
}
if (slider != null)
{
progressSlider = slider;
}
if (!loading)
{
SetScreenVisible(visible: false);
}
}
private void Awake()
{
if (Instance != null && Instance != this)
{
Debug.LogWarning("Multiple WorldLoadingScreenController instances found. Using the latest active instance.", this);
}
Instance = this;
SetScreenVisible(visible: false);
SetProgress(0f);
}
private void OnDisable()
{
SetLoading(isLoading: false);
}
private void OnDestroy()
{
if (Instance == this)
{
Instance = null;
}
}
public void BeginLoading(float initialProgress = 0f)
{
completedTeleports = 0;
SetLoading(isLoading: true);
SetProgress(initialProgress);
}
public void SetLoading(bool isLoading)
{
if (loading == isLoading)
{
SetScreenVisible(isLoading);
return;
}
loading = isLoading;
SetScreenVisible(isLoading);
if (hideSceneWhileLoading)
{
if (isLoading)
{
HideScene();
}
else
{
RestoreScene();
}
}
if (isLoading)
{
DisableMovement();
}
else
{
RestoreMovement();
}
if (!isLoading)
{
completedTeleports = 0;
SetProgress(0f);
WorldLoadingScreenController.LoadingFinished?.Invoke();
}
}
public void SetCheckpoint(float progress)
{
if (loading && (!(progressSlider != null) || !(progress < progressSlider.value)))
{
SetProgress(progress);
}
}
public void NotifyLocalPlayerTeleported(bool forceComplete = false)
{
if (loading)
{
completedTeleports++;
if (!forceComplete && completedTeleports < Mathf.Max(1, requiredTeleportsToComplete))
{
SetCheckpoint(0.97f);
}
else
{
CompleteLoading();
}
}
}
public void CompleteLoading()
{
if (loading)
{
SetProgress(1f);
SetLoading(isLoading: false);
}
}
public void SetProgress(float progress)
{
if (loading && hideSceneWhileLoading)
{
HideScene();
}
if (!(progressSlider == null))
{
progressSlider.minValue = 0f;
progressSlider.maxValue = 1f;
progressSlider.value = Mathf.Clamp01(progress);
}
}
private void SetScreenVisible(bool visible)
{
if (loadingScreenObject != null)
{
loadingScreenObject.SetActive(visible);
}
}
private void HideScene()
{
Transform loadingRoot = ((loadingScreenObject != null) ? loadingScreenObject.transform : base.transform);
Renderer[] array = UnityEngine.Object.FindObjectsByType<Renderer>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (Renderer renderer in array)
{
if (!(renderer == null) && !rendererStates.ContainsKey(renderer) && !IsInsideLoadingScreen(renderer.transform, loadingRoot))
{
rendererStates[renderer] = renderer.enabled;
renderer.enabled = false;
}
}
Canvas[] array2 = UnityEngine.Object.FindObjectsByType<Canvas>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (Canvas canvas in array2)
{
if (!(canvas == null) && !canvasStates.ContainsKey(canvas) && !IsInsideLoadingScreen(canvas.transform, loadingRoot))
{
canvasStates[canvas] = canvas.enabled;
canvas.enabled = false;
}
}
Graphic[] array3 = UnityEngine.Object.FindObjectsByType<Graphic>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (Graphic graphic in array3)
{
if (!(graphic == null) && !graphicStates.ContainsKey(graphic) && !IsInsideLoadingScreen(graphic.transform, loadingRoot))
{
graphicStates[graphic] = graphic.enabled;
graphic.enabled = false;
}
}
Camera[] array4 = UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (Camera camera in array4)
{
if (!(camera == null) && !cameraStates.ContainsKey(camera))
{
cameraStates[camera] = new CameraState
{
clearFlags = camera.clearFlags,
backgroundColor = camera.backgroundColor
};
camera.clearFlags = CameraClearFlags.Color;
camera.backgroundColor = Color.black;
}
}
}
private void RestoreScene()
{
foreach (KeyValuePair<Renderer, bool> rendererState in rendererStates)
{
if (rendererState.Key != null)
{
rendererState.Key.enabled = rendererState.Value;
}
}
foreach (KeyValuePair<Canvas, bool> canvasState in canvasStates)
{
if (canvasState.Key != null)
{
canvasState.Key.enabled = canvasState.Value;
}
}
foreach (KeyValuePair<Graphic, bool> graphicState in graphicStates)
{
if (graphicState.Key != null)
{
graphicState.Key.enabled = graphicState.Value;
}
}
foreach (KeyValuePair<Camera, CameraState> cameraState in cameraStates)
{
if (cameraState.Key != null)
{
cameraState.Key.clearFlags = cameraState.Value.clearFlags;
cameraState.Key.backgroundColor = cameraState.Value.backgroundColor;
}
}
rendererStates.Clear();
canvasStates.Clear();
graphicStates.Clear();
cameraStates.Clear();
}
private void DisableMovement()
{
Behaviour[] array = UnityEngine.Object.FindObjectsByType<Behaviour>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (Behaviour behaviour in array)
{
if (!(behaviour == null) && !movementBehaviourStates.ContainsKey(behaviour) && IsMovementBehaviour(behaviour))
{
movementBehaviourStates[behaviour] = behaviour.enabled;
behaviour.enabled = false;
}
}
Rigidbody[] array2 = UnityEngine.Object.FindObjectsByType<Rigidbody>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (Rigidbody rigidbody in array2)
{
if (!(rigidbody == null) && !rigidbodyStates.ContainsKey(rigidbody))
{
rigidbodyStates[rigidbody] = new RigidbodyState
{
linearVelocity = rigidbody.linearVelocity,
angularVelocity = rigidbody.angularVelocity
};
rigidbody.linearVelocity = Vector3.zero;
rigidbody.angularVelocity = Vector3.zero;
}
}
}
private void RestoreMovement()
{
foreach (KeyValuePair<Behaviour, bool> movementBehaviourState in movementBehaviourStates)
{
if (movementBehaviourState.Key != null)
{
movementBehaviourState.Key.enabled = movementBehaviourState.Value;
}
}
foreach (KeyValuePair<Rigidbody, RigidbodyState> rigidbodyState in rigidbodyStates)
{
if (rigidbodyState.Key != null)
{
rigidbodyState.Key.linearVelocity = rigidbodyState.Value.linearVelocity;
rigidbodyState.Key.angularVelocity = rigidbodyState.Value.angularVelocity;
}
}
movementBehaviourStates.Clear();
rigidbodyStates.Clear();
}
private bool IsMovementBehaviour(Behaviour behaviour)
{
if (behaviour == this)
{
return false;
}
string text = behaviour.GetType().Name;
if (!((behaviour.GetType().FullName ?? string.Empty) == "GorillaLocomotion.Player") && !(text == "DesktopRigController") && !(text == "CreatorFlightController") && !text.Contains("LocomotionProvider") && !text.Contains("MoveProvider") && !text.Contains("TurnProvider") && !text.Contains("JumpProvider") && !text.Contains("TeleportationProvider") && !text.Contains("ClimbProvider") && !text.Contains("ContinuousMove") && !text.Contains("DynamicMove") && !text.Contains("SnapTurn"))
{
return text.Contains("SmoothTurn");
}
return true;
}
private bool IsInsideLoadingScreen(Transform target, Transform loadingRoot)
{
if (target != null && loadingRoot != null)
{
if (!(target == loadingRoot) && !target.IsChildOf(loadingRoot))
{
return loadingRoot.IsChildOf(target);
}
return true;
}
return false;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: bcf37729cb5d99fabff7ea5e8d1fbccc
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant: