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
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0a6e171f63505db4c89bf67db000f0a3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,11 @@
using UnityEngine;
[DisallowMultipleComponent]
public sealed class ANDBehavior : GuardedValueNodeBehavior
{
protected override void RecomputeCore()
{
bool value = GetInputValue("A").AsBool() && GetInputValue("B").AsBool();
SetOutputValueIfChanged("Result", BSValue.FromBool(value));
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: df4dba05174e90141983008cc81c6d34
@@ -0,0 +1,12 @@
using System;
using UnityEngine;
[DisallowMultipleComponent]
public sealed class AbsoluteValueBehavior : GuardedValueNodeBehavior
{
protected override void RecomputeCore()
{
double inputNumber = GetInputNumber("Value");
SetOutputValueIfChanged("Output", BSValue.FromNumber(Math.Abs(inputNumber)));
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fc01437f88c3d134f92886441768a907
@@ -0,0 +1,10 @@
using UnityEngine;
[DisallowMultipleComponent]
public sealed class AddBehavior : GuardedValueNodeBehavior
{
protected override void RecomputeCore()
{
SetOutputValueIfChanged("Output", BSValue.FromNumber(GetInputNumber("A") + GetInputNumber("B")));
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5a4c3bf640aca28449fa0335d4d42196
+308
View File
@@ -0,0 +1,308 @@
using System;
using BlockSpace.Voxels;
using UnityEngine;
[Serializable]
public struct BSValue
{
private const double WholeNumberEpsilon = 0.0001;
public BSValueKind Kind;
public double Number;
public string Text;
public bool Bool;
public int PlayerId;
public int GroupId;
public bool IsInitialized
{
get
{
if (Kind == BSValueKind.None && !(Math.Abs(Number) > 0.0001) && string.IsNullOrEmpty(Text) && PlayerId == 0)
{
return GroupId != 0;
}
return true;
}
}
public static BSValue None => new BSValue
{
Kind = BSValueKind.None
};
public static BSValue FromBool(bool value)
{
return new BSValue
{
Kind = BSValueKind.Bool,
Bool = value
};
}
public static BSValue FromNumber(double value)
{
return new BSValue
{
Kind = BSValueKind.Number,
Number = NormalizeNumber(value)
};
}
public static BSValue FromText(string value)
{
if (string.IsNullOrWhiteSpace(value) || string.Equals(value, "None", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "Null", StringComparison.OrdinalIgnoreCase))
{
return None;
}
if (string.Equals(value, "True", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "False", StringComparison.OrdinalIgnoreCase))
{
return FromBool(string.Equals(value, "True", StringComparison.OrdinalIgnoreCase));
}
if (double.TryParse(value, out var result))
{
return FromNumber(result);
}
return new BSValue
{
Kind = BSValueKind.Text,
Text = value
};
}
public static BSValue FromPlayer(int playerId)
{
return new BSValue
{
Kind = BSValueKind.Player,
PlayerId = playerId
};
}
public static BSValue FromGroup(int groupId)
{
return new BSValue
{
Kind = BSValueKind.Group,
GroupId = groupId
};
}
public BSValue Normalize()
{
switch (Kind)
{
case BSValueKind.Number:
Number = NormalizeNumber(Number);
break;
case BSValueKind.Text:
if (Text == null)
{
Text = string.Empty;
}
break;
}
return this;
}
public bool IsWholeNumber()
{
return Math.Abs(Number - Math.Round(Number)) <= 0.0001;
}
public double AsNumber()
{
switch (Kind)
{
case BSValueKind.Number:
return NormalizeNumber(Number);
case BSValueKind.Bool:
if (!Bool)
{
return 0.0;
}
return 1.0;
case BSValueKind.Text:
{
if (double.TryParse(Text, out var result))
{
return NormalizeNumber(result);
}
return EncodeTextAsNumber(Text);
}
case BSValueKind.Player:
return PlayerId;
case BSValueKind.Group:
return GroupId;
default:
return 0.0;
}
}
public bool AsBool()
{
return Kind switch
{
BSValueKind.Bool => Bool,
BSValueKind.None => false,
BSValueKind.Number => Math.Abs(AsNumber()) > 0.0001,
BSValueKind.Text => !string.IsNullOrEmpty(AsText()),
BSValueKind.Player => AsPlayerId() > 0,
BSValueKind.Group => AsGroupId() > 0,
_ => false,
};
}
public string AsText()
{
switch (Kind)
{
case BSValueKind.Number:
if (!IsWholeNumber())
{
return NormalizeNumber(Number).ToString("0.#######");
}
return Math.Round(Number).ToString();
case BSValueKind.Bool:
if (!Bool)
{
return "False";
}
return "True";
case BSValueKind.Text:
return Text ?? string.Empty;
case BSValueKind.Player:
return PlayerId.ToString();
case BSValueKind.Group:
return GroupId.ToString();
default:
return string.Empty;
}
}
public int AsPlayerId()
{
switch (Kind)
{
case BSValueKind.Player:
return PlayerId;
case BSValueKind.Number:
return Mathf.RoundToInt((float)NormalizeNumber(Number));
case BSValueKind.Group:
return GroupId;
case BSValueKind.Text:
{
if (int.TryParse(Text, out var result))
{
return result;
}
return Mathf.RoundToInt((float)EncodeTextAsNumber(Text));
}
default:
return 0;
}
}
public int AsGroupId()
{
switch (Kind)
{
case BSValueKind.Group:
return GroupId;
case BSValueKind.Number:
return Mathf.RoundToInt((float)NormalizeNumber(Number));
case BSValueKind.Player:
return PlayerId;
case BSValueKind.Text:
{
if (int.TryParse(Text, out var result))
{
return result;
}
return Mathf.RoundToInt((float)EncodeTextAsNumber(Text));
}
default:
return 0;
}
}
public bool TryResolveGroup(out VoxelGroup group)
{
return TryResolveGroup(VoxelGroupManager.Instance, out group);
}
public bool TryResolveGroup(VoxelGroupManager manager, out VoxelGroup group)
{
group = null;
if (manager == null)
{
return false;
}
int num = AsGroupId();
if (num > 0 && num <= 65535 && manager.TryGetGroup((ushort)num, out group))
{
return group != null;
}
return false;
}
public BSValue ConvertTo(BSValueKind targetKind)
{
return targetKind switch
{
BSValueKind.Bool => FromBool(AsBool()),
BSValueKind.Number => FromNumber(AsNumber()),
BSValueKind.Text => new BSValue
{
Kind = BSValueKind.Text,
Text = AsText()
},
BSValueKind.Player => FromPlayer(AsPlayerId()),
BSValueKind.Group => FromGroup(AsGroupId()),
_ => None,
};
}
public override string ToString()
{
return Kind switch
{
BSValueKind.Number => AsText(),
BSValueKind.Text => AsText(),
BSValueKind.Bool => AsText(),
BSValueKind.Player => $"Player({AsPlayerId()})",
BSValueKind.Group => $"Group({AsGroupId()})",
_ => "None",
};
}
private static double NormalizeNumber(double value)
{
double num = Math.Round(value);
if (!(Math.Abs(value - num) <= 0.0001))
{
return value;
}
return num;
}
private static double EncodeTextAsNumber(string value)
{
if (string.IsNullOrEmpty(value))
{
return 0.0;
}
uint num = 2166136261u;
for (int i = 0; i < value.Length; i++)
{
num ^= value[i];
num *= 16777619;
}
return num;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e6d9f229dd3e1e54782342cfe277824b
@@ -0,0 +1,9 @@
public enum BSValueKind
{
None = 0,
Number = 1,
Text = 2,
Bool = 3,
Player = 4,
Group = 5
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a85b9f5b812ad5640a170d915a7761cd
@@ -0,0 +1,23 @@
using System;
internal static class BSValueUtility
{
public static bool Equals(BSValue a, BSValue b)
{
a = a.Normalize();
b = b.Normalize();
if (a.Kind != b.Kind)
{
return false;
}
return a.Kind switch
{
BSValueKind.Number => Math.Abs(a.Number - b.Number) <= 0.0001,
BSValueKind.Text => string.Equals(a.Text ?? string.Empty, b.Text ?? string.Empty, StringComparison.Ordinal),
BSValueKind.Bool => a.Bool == b.Bool,
BSValueKind.Player => a.PlayerId == b.PlayerId,
BSValueKind.Group => a.GroupId == b.GroupId,
_ => true,
};
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f544467adeed1704f93ad611eefdf066
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9b1406eba2dc73f4aaff1b106f36984f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eabd788ad2929934fb35bab35ab31799
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,67 @@
using System;
using UnityEngine;
namespace BlockSpace.Account
{
public static class BlockSpaceAccountPrefs
{
public const string LinkedKey = "BlockSpace.Account.Linked";
public const string LinkedPlayerIdKey = "BlockSpace.Account.PlayerId";
public const string LinkedDiscordIdKey = "BlockSpace.Account.DiscordId";
public const string LinkedDiscordDisplayNameKey = "BlockSpace.Account.DiscordDisplayName";
public const string LocalUserIdKey = "BlockSpace.LocalUserId";
public static bool IsLinked => PlayerPrefs.GetInt("BlockSpace.Account.Linked", 0) == 1;
public static string GetLinkedPlayerId()
{
return PlayerPrefs.GetString("BlockSpace.Account.PlayerId", string.Empty);
}
public static string GetDiscordId()
{
return PlayerPrefs.GetString("BlockSpace.Account.DiscordId", string.Empty);
}
public static string GetDiscordDisplayName()
{
return PlayerPrefs.GetString("BlockSpace.Account.DiscordDisplayName", string.Empty);
}
public static void SetLinkedAccount(string playerId, string discordId, string discordDisplayName)
{
PlayerPrefs.SetInt("BlockSpace.Account.Linked", 1);
PlayerPrefs.SetString("BlockSpace.Account.PlayerId", playerId ?? string.Empty);
PlayerPrefs.SetString("BlockSpace.Account.DiscordId", discordId ?? string.Empty);
PlayerPrefs.SetString("BlockSpace.Account.DiscordDisplayName", discordDisplayName ?? string.Empty);
PlayerPrefs.Save();
}
public static string GetOrCreateLocalUserId()
{
string text = PlayerPrefs.GetString("BlockSpace.LocalUserId", string.Empty);
if (!string.IsNullOrEmpty(text))
{
return text;
}
string text2 = Guid.NewGuid().ToString("N");
PlayerPrefs.SetString("BlockSpace.LocalUserId", text2);
PlayerPrefs.Save();
return text2;
}
public static string GetUserFolderId()
{
string linkedPlayerId = GetLinkedPlayerId();
if (string.IsNullOrEmpty(linkedPlayerId))
{
return GetOrCreateLocalUserId();
}
return linkedPlayerId;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 215cf74244dadac5aef29414bd4d4247
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ac8c0922005754b488b5f42427e35f4f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,470 @@
using System.Collections;
using System.Text;
using BlockSpace.Voxels;
using ExitGames.Client.Photon;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using UnityEngine;
namespace BlockSpace.Gamemodes
{
[DisallowMultipleComponent]
[RequireComponent(typeof(PhotonView))]
[RequireComponent(typeof(GamemodeZone))]
public sealed class BuildBattleController : MonoBehaviourPunCallbacks
{
private enum Phase : byte
{
Build = 0,
Play = 1
}
private const double BuildDurationSeconds = 60.0;
private const double PlayDurationSeconds = 30.0;
[SerializeField]
private string[] themes = new string[5] { "Castle", "Rocket", "Robot", "Dragon", "Treehouse" };
[SerializeField]
private TMP_Text themeText;
[SerializeField]
private TMP_Text timerText;
[SerializeField]
private VoxelGroupManager groupManager;
[SerializeField]
private bool enableLogs = true;
private GamemodeZone zone;
private string currentTheme;
private bool localWasInZone;
private bool cachedCreatorMode;
private bool cachedEditingLocked;
private Coroutine masterLoop;
private bool bootstrapAttempted;
private bool hasPhaseCache;
private Phase cachedPhase;
private double cachedStartTime;
private double nextMissingPropsLogTime;
private string PhaseKey => $"BB_{zone.zoneID}_phase";
private string StartKey => $"BB_{zone.zoneID}_start";
private void Awake()
{
zone = GetComponent<GamemodeZone>();
if (groupManager == null)
{
groupManager = ((VoxelGroupManager.Instance != null) ? VoxelGroupManager.Instance : Object.FindObjectOfType<VoxelGroupManager>());
}
}
private void Start()
{
if (themeText != null)
{
themeText.text = "Waiting for next round...";
}
Log($"Start (inRoom={PhotonNetwork.InRoom}, isMaster={PhotonNetwork.IsMasterClient}, viewID={((base.photonView != null) ? base.photonView.ViewID : (-1))})");
TryStartMasterLoop();
}
private new void OnEnable()
{
Log($"OnEnable (inRoom={PhotonNetwork.InRoom}, isMaster={PhotonNetwork.IsMasterClient})");
TryStartMasterLoop();
}
private void Update()
{
UpdateTimerText();
UpdateMasterPhaseAdvance();
ApplyLocalToolRules();
}
private IEnumerator MasterLoop()
{
while (true)
{
yield return WaitForSecondsNetwork(60.0);
StartPlayPhase();
yield return WaitForSecondsNetwork(30.0);
StartBuildPhase();
}
}
public override void OnJoinedRoom()
{
if (themeText != null)
{
themeText.text = "Waiting for next round...";
}
Log($"OnJoinedRoom (isMaster={PhotonNetwork.IsMasterClient}, viewID={((base.photonView != null) ? base.photonView.ViewID : (-1))})");
TryStartMasterLoop();
}
public override void OnMasterClientSwitched(Player newMasterClient)
{
Log($"OnMasterClientSwitched (newMaster={newMasterClient?.ActorNumber}, amMaster={PhotonNetwork.IsMasterClient})");
TryStartMasterLoop();
}
private void TryStartMasterLoop()
{
if (!PhotonNetwork.InRoom)
{
Log("TryStartMasterLoop: not in room yet");
}
else if (!PhotonNetwork.IsMasterClient)
{
Log("TryStartMasterLoop: not master");
if (masterLoop != null)
{
StopCoroutine(masterLoop);
masterLoop = null;
Log("Stopped master loop.");
}
}
else
{
EnsurePhotonViewReady();
if (masterLoop == null)
{
Log("Starting master loop; kicking BUILD immediately.");
StartBuildPhase();
masterLoop = StartCoroutine(MasterLoop());
}
}
}
private void EnsurePhotonViewReady()
{
if (!(base.photonView == null) && base.photonView.ViewID == 0 && PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient)
{
bool flag = PhotonNetwork.AllocateViewID(base.photonView);
Log($"AllocateViewID -> {flag} (viewID={base.photonView.ViewID})");
}
}
private void StartBuildPhase()
{
if (PhotonNetwork.IsMasterClient && !IsFreshPhase(Phase.Build))
{
EnsurePhotonViewReady();
Log($"BUILD start (zoneID={zone.zoneID})");
int num = ((groupManager != null) ? groupManager.RemoveGroupsForZone(zone) : 0);
Log($"BUILD cleared {num} group(s) in zone {zone.zoneID}");
string text = PickRandomTheme();
RPC_SetTheme(text);
Log("BUILD theme picked: " + text);
base.photonView.RPC("RPC_SetTheme", RpcTarget.AllViaServer, text);
SetPhaseProperties(Phase.Build, PhotonNetwork.Time);
}
}
private void StartPlayPhase()
{
if (PhotonNetwork.IsMasterClient && !IsFreshPhase(Phase.Play))
{
EnsurePhotonViewReady();
Log($"PLAY start (zoneID={zone.zoneID})");
SetPhaseProperties(Phase.Play, PhotonNetwork.Time);
}
}
private bool IsFreshPhase(Phase phase)
{
if (hasPhaseCache && cachedPhase == phase)
{
return PhotonNetwork.Time - cachedStartTime < 1.0;
}
return false;
}
private string PickRandomTheme()
{
if (themes == null || themes.Length == 0)
{
return "Something";
}
int num = Random.Range(0, themes.Length);
if (!string.IsNullOrEmpty(themes[num]))
{
return themes[num];
}
return "Something";
}
private void SetPhaseProperties(Phase phase, double startTimestamp)
{
if (PhotonNetwork.InRoom)
{
if (PhotonNetwork.CurrentRoom == null)
{
Log("SetPhaseProperties: CurrentRoom is null");
return;
}
ExitGames.Client.Photon.Hashtable propertiesToSet = new ExitGames.Client.Photon.Hashtable
{
{
PhaseKey,
(int)phase
},
{ StartKey, startTimestamp }
};
PhotonNetwork.CurrentRoom.SetCustomProperties(propertiesToSet);
Log(string.Format("SetPhaseProperties: {0} start={1:F3}", (phase == Phase.Build) ? "BUILD" : "PLAY", startTimestamp));
LogRoomProps("After SetCustomProperties");
cachedPhase = phase;
cachedStartTime = startTimestamp;
hasPhaseCache = true;
base.photonView.RPC("RPC_SyncPhase", RpcTarget.AllViaServer, (int)phase, startTimestamp);
}
}
private void UpdateTimerText()
{
if (timerText == null || !PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null)
{
return;
}
if (!TryGetSyncedPhase(out var phase, out var startTime))
{
if (PhotonNetwork.Time >= nextMissingPropsLogTime)
{
nextMissingPropsLogTime = PhotonNetwork.Time + 2.0;
Log("UpdateTimerText: missing phase/start properties");
LogRoomProps("Timer missing props");
}
if (!bootstrapAttempted && PhotonNetwork.IsMasterClient)
{
bootstrapAttempted = true;
Log("UpdateTimerText: bootstrapping BUILD phase (master detected, no phase cache present).");
StartBuildPhase();
}
if (!TryGetSyncedPhase(out phase, out startTime))
{
return;
}
}
bootstrapAttempted = false;
double num = ((phase == Phase.Build) ? 60.0 : 30.0);
double num2 = PhotonNetwork.Time - startTime;
int num3 = Mathf.Max(0, Mathf.CeilToInt((float)(num - num2)));
timerText.text = ((phase == Phase.Build) ? "BUILD: " : "PLAY: ") + num3;
}
private void UpdateMasterPhaseAdvance()
{
if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || !TryGetSyncedPhase(out var phase, out var startTime))
{
return;
}
double num = ((phase == Phase.Build) ? 60.0 : 30.0);
double num2 = PhotonNetwork.Time - startTime;
if (!(num2 < num))
{
Log(string.Format("Master advancing phase from {0} after {1:F2}s", (phase == Phase.Build) ? "BUILD" : "PLAY", num2));
if (phase == Phase.Build)
{
StartPlayPhase();
}
else
{
StartBuildPhase();
}
}
}
private bool TryGetSyncedPhase(out Phase phase, out double startTime)
{
if (hasPhaseCache)
{
phase = cachedPhase;
startTime = cachedStartTime;
return true;
}
if (TryGetPhaseAndStart(out phase, out startTime))
{
cachedPhase = phase;
cachedStartTime = startTime;
hasPhaseCache = true;
return true;
}
return false;
}
private bool TryGetPhaseAndStart(out Phase phase, out double startTime)
{
phase = Phase.Build;
startTime = 0.0;
if (PhotonNetwork.CurrentRoom.CustomProperties == null)
{
return false;
}
PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue(PhaseKey, out var value);
PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue(StartKey, out var value2);
if (value == null || value2 == null)
{
return false;
}
if (value is int value3)
{
phase = (Phase)Mathf.Clamp(value3, 0, 1);
}
else if (value is byte value4)
{
phase = (Phase)Mathf.Clamp(value4, 0, 1);
}
if (value2 is double num)
{
startTime = num;
}
else if (value2 is float num2)
{
startTime = num2;
}
else if (value2 is int num3)
{
startTime = num3;
}
return true;
}
public override void OnRoomPropertiesUpdate(ExitGames.Client.Photon.Hashtable propertiesThatChanged)
{
if (propertiesThatChanged != null && (propertiesThatChanged.ContainsKey(PhaseKey) || propertiesThatChanged.ContainsKey(StartKey)))
{
LogRoomProps("OnRoomPropertiesUpdate (phase/start changed)");
}
}
private void ApplyLocalToolRules()
{
if (zone == null || groupManager == null)
{
return;
}
bool flag = GamemodeZoneManager.Instance != null && GamemodeZoneManager.Instance.CurrentZone == zone;
if (flag != localWasInZone)
{
if (flag)
{
cachedCreatorMode = groupManager.CreatorMode;
cachedEditingLocked = groupManager.EditingLocked;
}
else
{
groupManager.SetEditingLocked(cachedEditingLocked);
groupManager.SetCreatorMode(cachedCreatorMode);
}
localWasInZone = flag;
}
if (flag && PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null && TryGetSyncedPhase(out var phase, out var _))
{
if (phase == Phase.Build)
{
groupManager.SetEditingLocked(locked: false);
groupManager.SetCreatorMode(enabled: true);
}
else
{
groupManager.SetEditingLocked(locked: true);
groupManager.SetCreatorMode(enabled: false);
}
}
}
private IEnumerator WaitForSecondsNetwork(double seconds)
{
double start = PhotonNetwork.Time;
while (PhotonNetwork.Time - start < seconds)
{
yield return null;
}
}
[PunRPC]
private void RPC_SetTheme(string theme)
{
currentTheme = theme;
if (themeText != null)
{
themeText.text = "Theme: " + theme;
}
Log("RPC_SetTheme received: " + theme);
}
[PunRPC]
private void RPC_SyncPhase(int phase, double startTimestamp)
{
cachedPhase = (Phase)Mathf.Clamp(phase, 0, 1);
cachedStartTime = startTimestamp;
hasPhaseCache = true;
Log(string.Format("RPC_SyncPhase received: {0} start={1:F3}", (cachedPhase == Phase.Build) ? "BUILD" : "PLAY", startTimestamp));
}
private void Log(string message)
{
if (enableLogs)
{
Debug.Log($"[BuildBattle zone={zone.zoneID}] {message}", this);
}
}
private void LogRoomProps(string prefix)
{
if (enableLogs && PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null)
{
object value = null;
object value2 = null;
if (PhotonNetwork.CurrentRoom.CustomProperties != null)
{
PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue(PhaseKey, out value);
PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue(StartKey, out value2);
}
string text = ((PhotonNetwork.CurrentRoom.CustomProperties != null) ? FormatHashtable(PhotonNetwork.CurrentRoom.CustomProperties) : "null");
Debug.Log(string.Format("[BuildBattle zone={0}] {1} keys=({2}={3}, {4}={5}) all={6}", zone.zoneID, prefix, PhaseKey, value ?? "null", StartKey, value2 ?? "null", text), this);
}
}
private static string FormatHashtable(IDictionary table)
{
if (table == null)
{
return "null";
}
StringBuilder stringBuilder = new StringBuilder(256);
stringBuilder.Append("{");
bool flag = true;
foreach (DictionaryEntry item in table)
{
if (!flag)
{
stringBuilder.Append(", ");
}
flag = false;
stringBuilder.Append((item.Key != null) ? item.Key.ToString() : "null");
stringBuilder.Append("=");
stringBuilder.Append((item.Value != null) ? item.Value.ToString() : "null");
}
stringBuilder.Append("}");
return stringBuilder.ToString();
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 06715e3dff39b88da77c8f77f24a4212
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
namespace BlockSpace.Gamemodes
{
public enum GamemodeType : byte
{
BuildBattle = 0,
Tag = 1
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0a05560d801134644a774c189adb0943
@@ -0,0 +1,132 @@
using System.Collections.Generic;
using BlockSpace.Voxels;
using Photon.VR.Player;
using UnityEngine;
namespace BlockSpace.Gamemodes
{
[DisallowMultipleComponent]
[RequireComponent(typeof(Collider))]
public sealed class GamemodeZone : MonoBehaviour
{
[SerializeField]
public int zoneID;
[SerializeField]
public GamemodeType type;
private readonly HashSet<int> playersInside = new HashSet<int>();
private Collider zoneCollider;
public IReadOnlyCollection<int> PlayersInside => playersInside;
private void Awake()
{
zoneCollider = GetComponent<Collider>();
if (zoneCollider != null)
{
zoneCollider.isTrigger = true;
}
if (GamemodeZoneManager.Instance == null)
{
GameObject obj = new GameObject("GamemodeZoneManager");
obj.AddComponent<GamemodeZoneManager>();
Object.DontDestroyOnLoad(obj);
}
}
private void OnEnable()
{
EnsureZoneManager();
GamemodeZoneManager.Instance?.RegisterZone(this);
}
private void OnDisable()
{
GamemodeZoneManager.Instance?.UnregisterZone(this);
}
private void Reset()
{
Collider component = GetComponent<Collider>();
if (component != null)
{
component.isTrigger = true;
}
}
public bool ContainsGroup(VoxelGroup group)
{
if (group == null || group.size.x <= 0 || group.size.y <= 0 || group.size.z <= 0)
{
return false;
}
if (zoneCollider == null)
{
zoneCollider = GetComponent<Collider>();
}
if (zoneCollider != null)
{
return zoneCollider.bounds.Intersects(group.GetWorldBounds());
}
return false;
}
public bool ContainsWorldPosition(Vector3 worldPosition)
{
if (zoneCollider == null)
{
zoneCollider = GetComponent<Collider>();
}
if (zoneCollider != null)
{
return zoneCollider.bounds.Contains(worldPosition);
}
return false;
}
internal void RefreshPlayersInside(PhotonVRPlayer[] players)
{
playersInside.Clear();
if (players == null)
{
return;
}
foreach (PhotonVRPlayer photonVRPlayer in players)
{
if (!(photonVRPlayer == null) && !(photonVRPlayer.photonView == null) && photonVRPlayer.photonView.Owner != null && ContainsWorldPosition(GetPlayerZonePosition(photonVRPlayer)))
{
playersInside.Add(photonVRPlayer.photonView.Owner.ActorNumber);
}
}
}
internal static Vector3 GetPlayerZonePosition(PhotonVRPlayer player)
{
if (player == null)
{
return Vector3.zero;
}
if (player.Head != null)
{
return player.Head.position;
}
if (!(player.Body != null))
{
return player.transform.position;
}
return player.Body.position;
}
private static void EnsureZoneManager()
{
if (!(GamemodeZoneManager.Instance != null))
{
GameObject obj = new GameObject("GamemodeZoneManager");
obj.AddComponent<GamemodeZoneManager>();
Object.DontDestroyOnLoad(obj);
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: b3fcd6fe0b347da5e9598a2a20a86acc
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,93 @@
using System.Collections.Generic;
using Photon.VR.Player;
using UnityEngine;
namespace BlockSpace.Gamemodes
{
[DisallowMultipleComponent]
public sealed class GamemodeZoneManager : MonoBehaviour
{
private readonly List<GamemodeZone> zones = new List<GamemodeZone>();
public static GamemodeZoneManager Instance { get; private set; }
public GamemodeZone CurrentZone { get; private set; }
private void Awake()
{
if (Instance != null && Instance != this)
{
Object.Destroy(base.gameObject);
}
else
{
Instance = this;
}
}
private void OnDestroy()
{
if (Instance == this)
{
Instance = null;
}
}
private void Update()
{
RefreshZonesFromPlayerPositions();
}
internal void RegisterZone(GamemodeZone zone)
{
if (!(zone == null) && !zones.Contains(zone))
{
zones.Add(zone);
}
}
internal void UnregisterZone(GamemodeZone zone)
{
if (!(zone == null))
{
zones.Remove(zone);
if (CurrentZone == zone)
{
CurrentZone = null;
}
}
}
private void RefreshZonesFromPlayerPositions()
{
if (zones.Count == 0)
{
CurrentZone = null;
return;
}
PhotonVRPlayer[] array = Object.FindObjectsByType<PhotonVRPlayer>(FindObjectsInactive.Include);
PhotonVRPlayer photonVRPlayer = null;
foreach (PhotonVRPlayer photonVRPlayer2 in array)
{
if (!(photonVRPlayer2 == null) && photonVRPlayer2.photonView != null && photonVRPlayer2.photonView.IsMine)
{
photonVRPlayer = photonVRPlayer2;
}
}
CurrentZone = null;
Vector3 worldPosition = ((photonVRPlayer != null) ? GamemodeZone.GetPlayerZonePosition(photonVRPlayer) : Vector3.zero);
for (int j = 0; j < zones.Count; j++)
{
GamemodeZone gamemodeZone = zones[j];
if (!(gamemodeZone == null))
{
gamemodeZone.RefreshPlayersInside(array);
if (photonVRPlayer != null && CurrentZone == null && gamemodeZone.ContainsWorldPosition(worldPosition))
{
CurrentZone = gamemodeZone;
}
}
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: e68242b621309c19393fc31f439cd809
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 64367aab4ac836945be7783cc3c4f44b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,57 @@
using System.Collections.Generic;
using BlockSpace.Worlds;
using UnityEngine;
namespace BlockSpace.Nodes
{
public sealed class NodeWorldDefaultSpawner : MonoBehaviour
{
[SerializeField]
private NodeGraphManager nodeGraphManager;
[SerializeField]
private float startX;
[SerializeField]
private float y = 5f;
[SerializeField]
private float z = 10f;
[SerializeField]
private float xSpacing = 0.45f;
private void Reset()
{
nodeGraphManager = GetComponent<NodeGraphManager>();
}
public void EnsureDefaultNodesSpawnedIfEmpty(List<WorldGroupsCodec.WorldNodeRecord> nodeRecords, bool broadcast)
{
if (nodeGraphManager == null)
{
nodeGraphManager = GetComponent<NodeGraphManager>();
}
if (nodeGraphManager == null || (nodeRecords != null && nodeRecords.Count > 0) || nodeGraphManager.GetAllNodesSnapshot().Count > 0)
{
return;
}
List<NodeDefinition> allDefinitionsSnapshot = nodeGraphManager.GetAllDefinitionsSnapshot();
if (allDefinitionsSnapshot == null || allDefinitionsSnapshot.Count == 0)
{
return;
}
allDefinitionsSnapshot.Sort((NodeDefinition a, NodeDefinition b) => string.CompareOrdinal((a != null) ? a.name : string.Empty, (b != null) ? b.name : string.Empty));
float num = startX;
for (int num2 = 0; num2 < allDefinitionsSnapshot.Count; num2++)
{
NodeDefinition nodeDefinition = allDefinitionsSnapshot[num2];
if (!(nodeDefinition == null))
{
nodeGraphManager.SpawnNodeSystem(nodeDefinition, new Vector3(num, y, z), Quaternion.identity, broadcast);
num += xSpacing;
}
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 11f32f2e483126a228fb4dd0930feab5
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 129afda92058a4b4891653d975604202
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,438 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
namespace BlockSpace.Voxels
{
[DisallowMultipleComponent]
public sealed class AvatarCustomizerController : MonoBehaviour
{
private const float AvatarVoxelSizeScale = 0.5f;
private const float ButtonPressCooldown = 0.2f;
private const float ButtonShrinkDuration = 0.08f;
private const float ButtonSettleDuration = 0.12f;
private const float ButtonPressedScale = 0.84f;
private const float ButtonOvershootScale = 1.04f;
private const string ButtonThockResourcePath = "Thock";
private static readonly string[] SuccessClipAssetPaths = new string[3] { "Assets/success.wav", "Assets/success.mp3", "Assets/success.ogg" };
private const string UiRootPath = "UI";
private const string EditHeadPath = "UI/Edit Head";
private const string EditLeftPath = "UI/Edit Left";
private const string EditRightPath = "UI/Edit Right";
private const string StopEditingPath = "UI/Stop Editing";
private const string ApplyAndSavePath = "UI/Apply and Save";
private const string HeadGroupPath = "Origins/Head/Head Group";
private const string LeftGroupPath = "Origins/Left/Left Group";
private const string RightGroupPath = "Origins/Right/Right Group";
[SerializeField]
private VoxelGroupManager groupManager;
[SerializeField]
private VRVoxelBrush voxelBrush;
[SerializeField]
private Collider editHeadButton;
[SerializeField]
private Collider editLeftButton;
[SerializeField]
private Collider editRightButton;
[SerializeField]
private Collider stopEditingButton;
[SerializeField]
private Collider applyAndSaveButton;
[SerializeField]
private Transform headGroupTransform;
[SerializeField]
private Transform leftGroupTransform;
[SerializeField]
private Transform rightGroupTransform;
[SerializeField]
private AudioClip buttonThockClip;
[SerializeField]
private AudioClip applySuccessClip;
private VoxelGroup headEditorGroup;
private VoxelGroup leftEditorGroup;
private VoxelGroup rightEditorGroup;
private VoxelGroup activeEditorGroup;
private readonly Dictionary<Transform, Vector3> buttonBaseScales = new Dictionary<Transform, Vector3>();
private readonly Dictionary<Transform, Coroutine> buttonAnimationCoroutines = new Dictionary<Transform, Coroutine>();
private bool editorGroupsInitialized;
private bool loadedPersistedAvatar;
private bool editHeadInsidePrevious;
private bool editLeftInsidePrevious;
private bool editRightInsidePrevious;
private bool stopEditingInsidePrevious;
private bool applyAndSaveInsidePrevious;
private float nextButtonPressTime;
public void Configure(VoxelGroupManager manager, VRVoxelBrush brush)
{
if (manager != null)
{
groupManager = manager;
}
if (brush != null)
{
voxelBrush = brush;
}
ResolveSceneReferences();
EnsureButtonAudioAssigned();
EnsureApplySuccessAudioAssigned();
TryInitializeEditorGroups();
}
private void Awake()
{
ResolveSceneReferences();
EnsureButtonAudioAssigned();
EnsureApplySuccessAudioAssigned();
}
private void OnDisable()
{
StopAllCoroutines();
StopEditing();
ResetAllButtonScales();
}
private void Update()
{
ResolveSceneReferences();
if (TryInitializeEditorGroups())
{
HandleButton(editHeadButton, ref editHeadInsidePrevious, delegate
{
StartEditing(headEditorGroup);
});
HandleButton(editLeftButton, ref editLeftInsidePrevious, delegate
{
StartEditing(leftEditorGroup);
});
HandleButton(editRightButton, ref editRightInsidePrevious, delegate
{
StartEditing(rightEditorGroup);
});
HandleButton(stopEditingButton, ref stopEditingInsidePrevious, StopEditing);
HandleButton(applyAndSaveButton, ref applyAndSaveInsidePrevious, ApplyAndSave);
}
}
private void ResolveSceneReferences()
{
if (groupManager == null)
{
groupManager = ((VoxelGroupManager.Instance != null) ? VoxelGroupManager.Instance : UnityEngine.Object.FindObjectOfType<VoxelGroupManager>());
}
if (voxelBrush == null)
{
voxelBrush = ((VRVoxelBrush.Instance != null) ? VRVoxelBrush.Instance : UnityEngine.Object.FindObjectOfType<VRVoxelBrush>());
}
if (editHeadButton == null)
{
editHeadButton = FindCollider("UI/Edit Head");
}
if (editLeftButton == null)
{
editLeftButton = FindCollider("UI/Edit Left");
}
if (editRightButton == null)
{
editRightButton = FindCollider("UI/Edit Right");
}
if (stopEditingButton == null)
{
stopEditingButton = FindCollider("UI/Stop Editing");
}
if (applyAndSaveButton == null)
{
applyAndSaveButton = FindCollider("UI/Apply and Save");
}
if (headGroupTransform == null)
{
headGroupTransform = base.transform.Find("Origins/Head/Head Group");
}
if (leftGroupTransform == null)
{
leftGroupTransform = base.transform.Find("Origins/Left/Left Group");
}
if (rightGroupTransform == null)
{
rightGroupTransform = base.transform.Find("Origins/Right/Right Group");
}
}
private bool TryInitializeEditorGroups()
{
if (editorGroupsInitialized)
{
return true;
}
if (groupManager == null || headGroupTransform == null || leftGroupTransform == null || rightGroupTransform == null)
{
return false;
}
headEditorGroup = PrepareEditorGroup(headGroupTransform.gameObject, headEditorGroup, 61000);
leftEditorGroup = PrepareEditorGroup(leftGroupTransform.gameObject, leftEditorGroup, 61001);
rightEditorGroup = PrepareEditorGroup(rightGroupTransform.gameObject, rightEditorGroup, 61002);
if (headEditorGroup == null || leftEditorGroup == null || rightEditorGroup == null)
{
return false;
}
editorGroupsInitialized = true;
if (!loadedPersistedAvatar)
{
AvatarVoxelStorage.ApplyPayload(AvatarVoxelStorage.LoadLocalOrDefault(), headEditorGroup, leftEditorGroup, rightEditorGroup, groupManager, useLocalGridTransform: true);
loadedPersistedAvatar = true;
}
return true;
}
private VoxelGroup PrepareEditorGroup(GameObject groupObject, VoxelGroup existingGroup, ushort groupId)
{
if (groupObject == null || groupManager == null)
{
return null;
}
VoxelGroup voxelGroup = ((existingGroup != null) ? existingGroup : groupObject.GetComponent<VoxelGroup>());
if (voxelGroup == null)
{
voxelGroup = groupObject.AddComponent<VoxelGroup>();
}
AvatarVoxelStorage.ApplyGroupData(voxelGroup, AvatarVoxelStorage.CaptureGroup(voxelGroup), groupManager, useLocalGridTransform: true);
voxelGroup.SetVoxelSizeScale(0.5f);
voxelGroup.id = groupId;
return voxelGroup;
}
private void StartEditing(VoxelGroup group)
{
if (!(group == null))
{
activeEditorGroup = group;
VRHaptics.Pulse(XRNode.RightHand, 0.18f, 0.035f);
VRGroupSelector.Instance?.ClearSelectionForToolUse();
VRGroupSelector.Instance?.SetInteractionSuppressed(suppressed: true);
voxelBrush?.SetAvatarEditorTarget(group);
}
}
private void StopEditing()
{
activeEditorGroup = null;
voxelBrush?.ClearAvatarEditorTarget();
VRGroupSelector.Instance?.SetInteractionSuppressed(suppressed: false);
}
private void ApplyAndSave()
{
if (TryInitializeEditorGroups())
{
voxelBrush?.FinalizeAvatarEditorSessions();
AvatarVoxelPayload payload = AvatarVoxelStorage.CapturePayload(headEditorGroup, leftEditorGroup, rightEditorGroup);
AvatarVoxelStorage.SaveLocal(payload);
PhotonVoxelAvatar localOwnedInstance = PhotonVoxelAvatar.LocalOwnedInstance;
if (localOwnedInstance != null)
{
localOwnedInstance.ApplyAndBroadcastAvatar(payload);
}
VRHaptics.Pulse(XRNode.RightHand, 0.32f, 0.055f);
PlayApplySuccessFeedback();
}
}
public bool TryGetEditorGroups(out VoxelGroup headBody, out VoxelGroup left, out VoxelGroup right)
{
headBody = null;
left = null;
right = null;
if (!TryInitializeEditorGroups())
{
return false;
}
headBody = headEditorGroup;
left = leftEditorGroup;
right = rightEditorGroup;
if (headBody != null && left != null)
{
return right != null;
}
return false;
}
private void HandleButton(Collider button, ref bool insidePrevious, Action action)
{
bool flag = IsHandInside(button, (voxelBrush != null) ? voxelBrush.LeftHandAnchor : null) || IsHandInside(button, (voxelBrush != null) ? voxelBrush.RightHandAnchor : null);
if (flag && !insidePrevious)
{
PlayButtonEnterAnimation(button);
if (Time.unscaledTime >= nextButtonPressTime)
{
nextButtonPressTime = Time.unscaledTime + 0.2f;
PlayButtonClickFeedback(button);
action?.Invoke();
}
}
insidePrevious = flag;
}
private void PlayButtonEnterAnimation(Collider button)
{
if (!(button == null))
{
Transform transform = button.transform;
if (!buttonBaseScales.TryGetValue(transform, out var value))
{
value = transform.localScale;
buttonBaseScales.Add(transform, value);
}
if (buttonAnimationCoroutines.TryGetValue(transform, out var value2) && value2 != null)
{
StopCoroutine(value2);
}
transform.localScale = value;
buttonAnimationCoroutines[transform] = StartCoroutine(AnimateButtonScale(transform, value));
}
}
private void PlayButtonClickFeedback(Collider button)
{
if (!(buttonThockClip == null) && !(button == null))
{
AudioSource.PlayClipAtPoint(buttonThockClip, button.bounds.center, 1f);
}
}
private IEnumerator AnimateButtonScale(Transform buttonTransform, Vector3 baseScale)
{
Vector3 pressedScale = baseScale * 0.84f;
Vector3 overshootScale = baseScale * 1.04f;
float elapsed = 0f;
while (elapsed < 0.08f)
{
if (buttonTransform == null)
{
yield break;
}
elapsed += Time.unscaledDeltaTime;
float t = Mathf.Clamp01(elapsed / 0.08f);
buttonTransform.localScale = Vector3.LerpUnclamped(baseScale, pressedScale, t);
yield return null;
}
elapsed = 0f;
while (elapsed < 0.12f)
{
if (buttonTransform == null)
{
yield break;
}
elapsed += Time.unscaledDeltaTime;
float num = Mathf.Clamp01(elapsed / 0.12f);
float num2 = num * num * (3f - 2f * num);
Vector3 localScale = ((num2 < 0.5f) ? Vector3.LerpUnclamped(pressedScale, overshootScale, num2 / 0.5f) : Vector3.LerpUnclamped(overshootScale, baseScale, (num2 - 0.5f) / 0.5f));
buttonTransform.localScale = localScale;
yield return null;
}
if (buttonTransform != null)
{
buttonTransform.localScale = baseScale;
buttonAnimationCoroutines[buttonTransform] = null;
}
}
private void ResetAllButtonScales()
{
foreach (KeyValuePair<Transform, Vector3> buttonBaseScale in buttonBaseScales)
{
if (buttonBaseScale.Key != null)
{
buttonBaseScale.Key.localScale = buttonBaseScale.Value;
}
}
buttonAnimationCoroutines.Clear();
}
private void EnsureButtonAudioAssigned()
{
if (buttonThockClip == null)
{
buttonThockClip = Resources.Load<AudioClip>("Thock");
}
}
private void EnsureApplySuccessAudioAssigned()
{
_ = applySuccessClip != null;
}
private void PlayApplySuccessFeedback()
{
if (!(applySuccessClip == null))
{
Vector3 position = ((applyAndSaveButton != null) ? applyAndSaveButton.bounds.center : base.transform.position);
AudioSource.PlayClipAtPoint(applySuccessClip, position, 1f);
}
}
private Collider FindCollider(string relativePath)
{
Transform transform = base.transform.Find(relativePath);
if (!(transform != null))
{
return null;
}
return transform.GetComponent<Collider>();
}
private static bool IsHandInside(Collider button, Transform handAnchor)
{
if (button == null || handAnchor == null)
{
return false;
}
Vector3 position = handAnchor.position;
return (button.ClosestPoint(position) - position).sqrMagnitude <= 1E-06f;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 24ee2e6f1f1e8e4f675ae821bc566fa2
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,24 @@
using System;
namespace BlockSpace.Voxels
{
[Serializable]
public sealed class AvatarVoxelGroupData
{
public int positionX;
public int positionY;
public int positionZ;
public int packedRotation;
public int sizeX;
public int sizeY;
public int sizeZ;
public string voxelsBase64 = string.Empty;
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: fc0b84e5bb56b23da53ea384e74f038d
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
using System;
namespace BlockSpace.Voxels
{
[Serializable]
public sealed class AvatarVoxelPayload
{
public int version = 1;
public AvatarVoxelGroupData headBody = new AvatarVoxelGroupData();
public AvatarVoxelGroupData left = new AvatarVoxelGroupData();
public AvatarVoxelGroupData right = new AvatarVoxelGroupData();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1f1c1f8958028664fb466bcaed9e0418
@@ -0,0 +1,263 @@
using System;
using System.IO;
using BlockSpace.Account;
using UnityEngine;
namespace BlockSpace.Voxels
{
public static class AvatarVoxelStorage
{
public const string PlayerPrefsKey = "BlockSpace.Avatar.Payload";
private const string AvatarPayloadFileName = "avatar_payload.json";
private static string GetAvatarPayloadPath()
{
string userFolderId = BlockSpaceAccountPrefs.GetUserFolderId();
return Path.Combine(Application.persistentDataPath, "BlockSpace", "Users", userFolderId, "avatar_payload.json");
}
private static string GetAvatarPayloadPathForUser(string userId)
{
return Path.Combine(Application.persistentDataPath, "BlockSpace", "Users", userId, "avatar_payload.json");
}
private static void TryMigrateLocalAvatarToLinked()
{
if (!BlockSpaceAccountPrefs.IsLinked)
{
return;
}
string linkedPlayerId = BlockSpaceAccountPrefs.GetLinkedPlayerId();
if (string.IsNullOrEmpty(linkedPlayerId))
{
return;
}
string avatarPayloadPathForUser = GetAvatarPayloadPathForUser(linkedPlayerId);
if (File.Exists(avatarPayloadPathForUser))
{
return;
}
string orCreateLocalUserId = BlockSpaceAccountPrefs.GetOrCreateLocalUserId();
if (string.IsNullOrEmpty(orCreateLocalUserId))
{
return;
}
string avatarPayloadPathForUser2 = GetAvatarPayloadPathForUser(orCreateLocalUserId);
if (!File.Exists(avatarPayloadPathForUser2))
{
return;
}
try
{
string directoryName = Path.GetDirectoryName(avatarPayloadPathForUser);
if (!string.IsNullOrEmpty(directoryName))
{
Directory.CreateDirectory(directoryName);
}
File.Copy(avatarPayloadPathForUser2, avatarPayloadPathForUser, overwrite: true);
}
catch
{
}
}
public static AvatarVoxelPayload CreateEmptyPayload()
{
return new AvatarVoxelPayload();
}
public static AvatarVoxelPayload CapturePayload(VoxelGroup headBodyGroup, VoxelGroup leftGroup, VoxelGroup rightGroup)
{
return new AvatarVoxelPayload
{
headBody = CaptureGroup(headBodyGroup),
left = CaptureGroup(leftGroup),
right = CaptureGroup(rightGroup)
};
}
public static AvatarVoxelGroupData CaptureGroup(VoxelGroup group)
{
if (group == null)
{
return new AvatarVoxelGroupData();
}
return new AvatarVoxelGroupData
{
positionX = group.position.x,
positionY = group.position.y,
positionZ = group.position.z,
packedRotation = group.packedRotation,
sizeX = group.size.x,
sizeY = group.size.y,
sizeZ = group.size.z,
voxelsBase64 = ((group.voxels != null && group.voxels.Length != 0) ? Convert.ToBase64String(group.voxels) : string.Empty)
};
}
public static void ApplyPayload(AvatarVoxelPayload payload, VoxelGroup headBodyGroup, VoxelGroup leftGroup, VoxelGroup rightGroup, VoxelGroupManager manager, bool useLocalGridTransform)
{
AvatarVoxelPayload avatarVoxelPayload = payload ?? CreateEmptyPayload();
ApplyGroupData(headBodyGroup, avatarVoxelPayload.headBody, manager, useLocalGridTransform);
ApplyGroupData(leftGroup, avatarVoxelPayload.left, manager, useLocalGridTransform);
ApplyGroupData(rightGroup, avatarVoxelPayload.right, manager, useLocalGridTransform);
}
public static void ApplyGroupData(VoxelGroup group, AvatarVoxelGroupData data, VoxelGroupManager manager, bool useLocalGridTransform)
{
if (!(group == null) && !(manager == null))
{
AvatarVoxelGroupData avatarVoxelGroupData = data ?? new AvatarVoxelGroupData();
Vector3Int vector3Int = new Vector3Int(Mathf.Max(0, avatarVoxelGroupData.sizeX), Mathf.Max(0, avatarVoxelGroupData.sizeY), Mathf.Max(0, avatarVoxelGroupData.sizeZ));
byte[] array = DecodeVoxelBytes(avatarVoxelGroupData, vector3Int);
int num = ((vector3Int.x > 0 && vector3Int.y > 0 && vector3Int.z > 0) ? (vector3Int.x * vector3Int.y * vector3Int.z) : 0);
if (num > 0 && array.Length != num)
{
vector3Int = Vector3Int.zero;
array = new byte[0];
}
group.SetUseLocalGridTransform(useLocalGridTransform);
group.SetColliderGeneration(enabled: false);
group.SetOwner(manager);
group.OverwriteData(new Vector3Int(avatarVoxelGroupData.positionX, avatarVoxelGroupData.positionY, avatarVoxelGroupData.positionZ), (byte)Mathf.Clamp(avatarVoxelGroupData.packedRotation, 0, 255), vector3Int, array, queueRebuild: false);
group.SetGreedyRenderMeshing(enabled: true);
group.RebuildMesh();
group.RebuildCollider();
DisableCollider(group);
}
}
public static void SaveLocal(AvatarVoxelPayload payload)
{
try
{
string avatarPayloadPath = GetAvatarPayloadPath();
string directoryName = Path.GetDirectoryName(avatarPayloadPath);
if (!string.IsNullOrEmpty(directoryName))
{
Directory.CreateDirectory(directoryName);
}
File.WriteAllText(avatarPayloadPath, Serialize(payload));
if (PlayerPrefs.HasKey("BlockSpace.Avatar.Payload"))
{
PlayerPrefs.DeleteKey("BlockSpace.Avatar.Payload");
PlayerPrefs.Save();
}
}
catch (Exception ex)
{
Debug.LogWarning("Failed to save avatar payload to disk, falling back to PlayerPrefs: " + ex.Message);
PlayerPrefs.SetString("BlockSpace.Avatar.Payload", Serialize(payload));
PlayerPrefs.Save();
}
}
public static AvatarVoxelPayload LoadLocalOrDefault()
{
if (!TryLoadLocal(out var payload))
{
return CreateEmptyPayload();
}
return payload;
}
public static bool TryLoadLocal(out AvatarVoxelPayload payload)
{
payload = CreateEmptyPayload();
try
{
TryMigrateLocalAvatarToLinked();
string avatarPayloadPath = GetAvatarPayloadPath();
if (File.Exists(avatarPayloadPath))
{
return TryDeserialize(File.ReadAllText(avatarPayloadPath), out payload);
}
}
catch (Exception ex)
{
Debug.LogWarning("Failed to load avatar payload from disk: " + ex.Message);
}
if (!PlayerPrefs.HasKey("BlockSpace.Avatar.Payload"))
{
return false;
}
bool num = TryDeserialize(PlayerPrefs.GetString("BlockSpace.Avatar.Payload", string.Empty), out payload);
if (num)
{
SaveLocal(payload);
}
return num;
}
public static string Serialize(AvatarVoxelPayload payload)
{
return JsonUtility.ToJson(payload ?? CreateEmptyPayload());
}
public static bool TryDeserialize(string json, out AvatarVoxelPayload payload)
{
payload = CreateEmptyPayload();
if (string.IsNullOrWhiteSpace(json))
{
return false;
}
try
{
AvatarVoxelPayload avatarVoxelPayload = JsonUtility.FromJson<AvatarVoxelPayload>(json);
if (avatarVoxelPayload == null)
{
return false;
}
if (avatarVoxelPayload.headBody == null)
{
avatarVoxelPayload.headBody = new AvatarVoxelGroupData();
}
if (avatarVoxelPayload.left == null)
{
avatarVoxelPayload.left = new AvatarVoxelGroupData();
}
if (avatarVoxelPayload.right == null)
{
avatarVoxelPayload.right = new AvatarVoxelGroupData();
}
payload = avatarVoxelPayload;
return true;
}
catch (Exception ex)
{
Debug.LogWarning("Failed to deserialize avatar payload: " + ex.Message);
return false;
}
}
private static byte[] DecodeVoxelBytes(AvatarVoxelGroupData data, Vector3Int size)
{
int num = ((size.x > 0 && size.y > 0 && size.z > 0) ? (size.x * size.y * size.z) : 0);
if (num <= 0 || string.IsNullOrEmpty(data.voxelsBase64))
{
return new byte[0];
}
try
{
byte[] array = Convert.FromBase64String(data.voxelsBase64);
if (array.Length == num)
{
return array;
}
}
catch (FormatException)
{
}
return new byte[0];
}
private static void DisableCollider(VoxelGroup group)
{
if (!(group.meshCollider == null))
{
group.meshCollider.enabled = false;
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: dbabfe7dc2519a942ab9ed561762252f
@@ -0,0 +1,197 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace BlockSpace.Voxels
{
[DisallowMultipleComponent]
public sealed class BrowserButton : MonoBehaviour
{
private const float ButtonPressCooldown = 0.2f;
private const float ButtonShrinkDuration = 0.08f;
private const float ButtonSettleDuration = 0.12f;
private const float ButtonPressedScale = 0.84f;
private const float ButtonOvershootScale = 1.04f;
private const string ButtonThockResourcePath = "Thock";
[Header("Button")]
[SerializeField]
private Collider buttonCollider;
[SerializeField]
private VRVoxelBrush voxelBrush;
[Header("Browser Login")]
[SerializeField]
private string loginUrl = "https://example.com/login";
[Header("Audio")]
[SerializeField]
private AudioClip buttonThockClip;
private readonly Dictionary<Transform, Vector3> buttonBaseScales = new Dictionary<Transform, Vector3>();
private readonly Dictionary<Transform, Coroutine> buttonAnimationCoroutines = new Dictionary<Transform, Coroutine>();
private bool insidePrevious;
private float nextButtonPressTime;
private void Awake()
{
ResolveReferences();
EnsureButtonAudioAssigned();
}
private void OnDisable()
{
StopAllCoroutines();
ResetAllButtonScales();
}
private void Update()
{
ResolveReferences();
HandleButton();
}
private void ResolveReferences()
{
if (buttonCollider == null)
{
buttonCollider = GetComponent<Collider>();
}
if (voxelBrush == null)
{
voxelBrush = ((VRVoxelBrush.Instance != null) ? VRVoxelBrush.Instance : Object.FindObjectOfType<VRVoxelBrush>());
}
}
private void HandleButton()
{
bool flag = IsHandInside(buttonCollider, (voxelBrush != null) ? voxelBrush.LeftHandAnchor : null) || IsHandInside(buttonCollider, (voxelBrush != null) ? voxelBrush.RightHandAnchor : null);
if (flag && !insidePrevious)
{
PlayButtonEnterAnimation(buttonCollider);
if (Time.unscaledTime >= nextButtonPressTime)
{
nextButtonPressTime = Time.unscaledTime + 0.2f;
PlayButtonClickFeedback(buttonCollider);
OpenLoginUrl();
}
}
insidePrevious = flag;
}
private void OpenLoginUrl()
{
if (string.IsNullOrWhiteSpace(loginUrl))
{
Debug.LogWarning("[BrowserLoginButton] Login URL is empty.");
}
else
{
Application.OpenURL(loginUrl);
}
}
private void PlayButtonEnterAnimation(Collider button)
{
if (!(button == null))
{
Transform transform = button.transform;
if (!buttonBaseScales.TryGetValue(transform, out var value))
{
value = transform.localScale;
buttonBaseScales.Add(transform, value);
}
if (buttonAnimationCoroutines.TryGetValue(transform, out var value2) && value2 != null)
{
StopCoroutine(value2);
}
transform.localScale = value;
buttonAnimationCoroutines[transform] = StartCoroutine(AnimateButtonScale(transform, value));
}
}
private void PlayButtonClickFeedback(Collider button)
{
if (!(buttonThockClip == null) && !(button == null))
{
AudioSource.PlayClipAtPoint(buttonThockClip, button.bounds.center, 1f);
}
}
private IEnumerator AnimateButtonScale(Transform buttonTransform, Vector3 baseScale)
{
Vector3 pressedScale = baseScale * 0.84f;
Vector3 overshootScale = baseScale * 1.04f;
float elapsed = 0f;
while (elapsed < 0.08f)
{
if (buttonTransform == null)
{
yield break;
}
elapsed += Time.unscaledDeltaTime;
float t = Mathf.Clamp01(elapsed / 0.08f);
buttonTransform.localScale = Vector3.LerpUnclamped(baseScale, pressedScale, t);
yield return null;
}
elapsed = 0f;
while (elapsed < 0.12f)
{
if (buttonTransform == null)
{
yield break;
}
elapsed += Time.unscaledDeltaTime;
float num = Mathf.Clamp01(elapsed / 0.12f);
float num2 = num * num * (3f - 2f * num);
Vector3 localScale = ((num2 < 0.5f) ? Vector3.LerpUnclamped(pressedScale, overshootScale, num2 / 0.5f) : Vector3.LerpUnclamped(overshootScale, baseScale, (num2 - 0.5f) / 0.5f));
buttonTransform.localScale = localScale;
yield return null;
}
if (buttonTransform != null)
{
buttonTransform.localScale = baseScale;
buttonAnimationCoroutines[buttonTransform] = null;
}
}
private void ResetAllButtonScales()
{
foreach (KeyValuePair<Transform, Vector3> buttonBaseScale in buttonBaseScales)
{
if (buttonBaseScale.Key != null)
{
buttonBaseScale.Key.localScale = buttonBaseScale.Value;
}
}
buttonAnimationCoroutines.Clear();
}
private void EnsureButtonAudioAssigned()
{
if (buttonThockClip == null)
{
buttonThockClip = Resources.Load<AudioClip>("Thock");
}
}
private static bool IsHandInside(Collider button, Transform handAnchor)
{
if (button == null || handAnchor == null)
{
return false;
}
Vector3 position = handAnchor.position;
return (button.ClosestPoint(position) - position).sqrMagnitude <= 1E-06f;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: a44f466b65bbf4665b1fe07e32bc79bb
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,536 @@
using System.Collections.Generic;
using Photon.VR;
using UnityEngine;
using UnityEngine.InputSystem.XR;
using UnityEngine.XR;
using UnityEngine.XR.Interaction.Toolkit.Locomotion;
using UnityEngine.XR.Interaction.Toolkit.Locomotion.Jump;
using UnityEngine.XR.Interaction.Toolkit.Locomotion.Movement;
using UnityEngine.XR.Interaction.Toolkit.Locomotion.Turning;
namespace BlockSpace.Voxels
{
[DisallowMultipleComponent]
public sealed class DesktopRigController : MonoBehaviour
{
[SerializeField]
private Transform rigRoot;
[SerializeField]
private Transform headTransform;
[SerializeField]
private Transform leftHandTransform;
[SerializeField]
private Transform rightHandTransform;
[SerializeField]
private CharacterController characterController;
[SerializeField]
private float mouseSensitivity = 2.25f;
[SerializeField]
private float defaultMoveSpeed = 5f;
[SerializeField]
private float defaultJumpHeight = 1.1f;
[SerializeField]
private float handHorizontalOffset = 0.22f;
[SerializeField]
private float handVerticalOffset = -0.18f;
[SerializeField]
private float handForwardOffset = 0.35f;
[SerializeField]
private float maxLookPitch = 85f;
[SerializeField]
private float nonCreatorMoveSmoothing = 12f;
[SerializeField]
private float creatorMoveSmoothing = 40f;
private readonly List<LocomotionProvider> vrLocomotionProviders = new List<LocomotionProvider>();
private readonly Dictionary<LocomotionProvider, bool> vrLocomotionStates = new Dictionary<LocomotionProvider, bool>();
private readonly List<TrackedPoseDriver> trackedPoseDrivers = new List<TrackedPoseDriver>();
private readonly Dictionary<TrackedPoseDriver, bool> trackedPoseDriverStates = new Dictionary<TrackedPoseDriver, bool>();
private readonly Dictionary<Collider, bool> phaseColliderStates = new Dictionary<Collider, bool>();
private ContinuousMoveProvider moveProvider;
private JumpProvider jumpProvider;
private ContinuousTurnProvider turnProvider;
private float verticalVelocity;
private float pitch;
private bool pitchInitialized;
private bool pcModeActive;
private Vector3 smoothedHorizontalVelocity;
private bool characterControllerCollisionCached;
private bool cachedCharacterControllerEnabled;
private bool cachedCharacterControllerDetectCollisions;
public void Configure(Transform trackingOrigin, Transform head, Transform leftHand, Transform rightHand)
{
if (trackingOrigin != null)
{
rigRoot = trackingOrigin;
}
if (head != null)
{
headTransform = head;
}
if (leftHand != null)
{
leftHandTransform = leftHand;
}
if (rightHand != null)
{
rightHandTransform = rightHand;
}
ResolveReferences();
}
private void Awake()
{
ResolveReferences();
RefreshLocomotionProviders();
RefreshTrackedPoseDrivers();
}
private void OnEnable()
{
ResolveReferences();
}
private void OnDisable()
{
SetPcModeActive(active: false);
RestoreCharacterControllerCollisions();
UnlockCursor();
}
private void Update()
{
ResolveReferences();
bool flag = DetectXRInputActive();
SetPcModeActive(!flag);
if (!flag)
{
EnsureCursorLocked();
HandleLook();
HandleMovement();
UpdateProxyHands();
}
}
private void ResolveReferences()
{
if (rigRoot == null)
{
rigRoot = base.transform;
}
if (headTransform == null)
{
headTransform = ((Camera.main != null) ? Camera.main.transform : null);
if (headTransform == null && PhotonVRManager.Manager != null)
{
headTransform = PhotonVRManager.Manager.Head;
}
}
if (leftHandTransform == null && PhotonVRManager.Manager != null)
{
leftHandTransform = PhotonVRManager.Manager.LeftHand;
}
if (rightHandTransform == null && PhotonVRManager.Manager != null)
{
rightHandTransform = PhotonVRManager.Manager.RightHand;
}
if (characterController == null && rigRoot != null)
{
characterController = rigRoot.GetComponent<CharacterController>();
if (characterController == null)
{
characterController = rigRoot.GetComponentInParent<CharacterController>();
}
if (characterController == null)
{
characterController = rigRoot.GetComponentInChildren<CharacterController>(includeInactive: true);
}
}
if (characterController != null)
{
rigRoot = characterController.transform;
}
if (!pitchInitialized && headTransform != null)
{
pitch = NormalizeAngle(headTransform.localEulerAngles.x);
pitchInitialized = true;
}
if (moveProvider == null)
{
ContinuousMoveProvider[] array = Object.FindObjectsByType<ContinuousMoveProvider>(FindObjectsInactive.Include, FindObjectsSortMode.None);
if (array.Length != 0)
{
moveProvider = array[0];
}
}
if (jumpProvider == null)
{
JumpProvider[] array2 = Object.FindObjectsByType<JumpProvider>(FindObjectsInactive.Include, FindObjectsSortMode.None);
if (array2.Length != 0)
{
jumpProvider = array2[0];
}
}
if (turnProvider == null)
{
ContinuousTurnProvider[] array3 = Object.FindObjectsByType<ContinuousTurnProvider>(FindObjectsInactive.Include, FindObjectsSortMode.None);
if (array3.Length != 0)
{
turnProvider = array3[0];
}
}
}
private void RefreshLocomotionProviders()
{
vrLocomotionProviders.Clear();
LocomotionProvider[] array = Object.FindObjectsByType<LocomotionProvider>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (LocomotionProvider locomotionProvider in array)
{
if (!(locomotionProvider == null) && !vrLocomotionProviders.Contains(locomotionProvider))
{
vrLocomotionProviders.Add(locomotionProvider);
}
}
}
private void RefreshTrackedPoseDrivers()
{
trackedPoseDrivers.Clear();
TrackedPoseDriver[] array = Object.FindObjectsByType<TrackedPoseDriver>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (TrackedPoseDriver trackedPoseDriver in array)
{
if (!(trackedPoseDriver == null) && !trackedPoseDrivers.Contains(trackedPoseDriver))
{
trackedPoseDrivers.Add(trackedPoseDriver);
}
}
}
private void SetPcModeActive(bool active)
{
if (pcModeActive == active)
{
return;
}
pcModeActive = active;
if (pcModeActive)
{
RefreshLocomotionProviders();
RefreshTrackedPoseDrivers();
for (int i = 0; i < vrLocomotionProviders.Count; i++)
{
LocomotionProvider locomotionProvider = vrLocomotionProviders[i];
if (!(locomotionProvider == null))
{
if (!vrLocomotionStates.ContainsKey(locomotionProvider))
{
vrLocomotionStates.Add(locomotionProvider, locomotionProvider.enabled);
}
locomotionProvider.enabled = false;
}
}
for (int j = 0; j < trackedPoseDrivers.Count; j++)
{
TrackedPoseDriver trackedPoseDriver = trackedPoseDrivers[j];
if (!(trackedPoseDriver == null))
{
if (!trackedPoseDriverStates.ContainsKey(trackedPoseDriver))
{
trackedPoseDriverStates.Add(trackedPoseDriver, trackedPoseDriver.enabled);
}
trackedPoseDriver.enabled = false;
}
}
return;
}
foreach (KeyValuePair<LocomotionProvider, bool> vrLocomotionState in vrLocomotionStates)
{
if (vrLocomotionState.Key != null)
{
vrLocomotionState.Key.enabled = vrLocomotionState.Value;
}
}
vrLocomotionStates.Clear();
foreach (KeyValuePair<TrackedPoseDriver, bool> trackedPoseDriverState in trackedPoseDriverStates)
{
if (trackedPoseDriverState.Key != null)
{
trackedPoseDriverState.Key.enabled = trackedPoseDriverState.Value;
}
}
trackedPoseDriverStates.Clear();
verticalVelocity = 0f;
}
private void HandleLook()
{
if (!(rigRoot == null) && !(headTransform == null))
{
float angle = Input.GetAxisRaw("Mouse X") * GetMouseLookSpeed();
float num = Input.GetAxisRaw("Mouse Y") * GetMouseLookSpeed();
rigRoot.Rotate(Vector3.up, angle, Space.World);
pitch = Mathf.Clamp(pitch - num, 0f - maxLookPitch, maxLookPitch);
headTransform.localRotation = Quaternion.Euler(pitch, 0f, 0f);
}
}
private void HandleMovement()
{
if (rigRoot == null || headTransform == null)
{
return;
}
bool flag = VoxelGroupManager.Instance != null && !VoxelGroupManager.Instance.EditingLocked && VoxelGroupManager.Instance.CreatorMode;
ApplyCharacterControllerPhase(flag);
Vector2 zero = Vector2.zero;
if (Input.GetKey(KeyCode.W))
{
zero.y += 1f;
}
if (Input.GetKey(KeyCode.S))
{
zero.y -= 1f;
}
if (Input.GetKey(KeyCode.D))
{
zero.x += 1f;
}
if (Input.GetKey(KeyCode.A))
{
zero.x -= 1f;
}
if (zero.sqrMagnitude > 1f)
{
zero.Normalize();
}
Vector3 forward = headTransform.forward;
Vector3 right = headTransform.right;
if (!flag)
{
forward.y = 0f;
right.y = 0f;
}
forward = ((forward.sqrMagnitude > 0.0001f) ? forward.normalized : Vector3.forward);
right = ((right.sqrMagnitude > 0.0001f) ? right.normalized : Vector3.right);
Vector3 b = (forward * zero.y + right * zero.x) * GetMoveSpeed();
float b2 = ((VoxelGroupManager.Instance != null && !VoxelGroupManager.Instance.CreatorMode) ? nonCreatorMoveSmoothing : creatorMoveSmoothing);
float t = 1f - Mathf.Exp((0f - Mathf.Max(0.01f, b2)) * Time.deltaTime);
smoothedHorizontalVelocity = Vector3.Lerp(smoothedHorizontalVelocity, b, t);
if (characterController != null)
{
if (flag)
{
verticalVelocity = 0f;
rigRoot.position += smoothedHorizontalVelocity * Time.deltaTime;
return;
}
if (characterController.isGrounded && verticalVelocity < 0f)
{
verticalVelocity = -2f;
}
if (characterController.isGrounded && Input.GetKeyDown(KeyCode.Space))
{
verticalVelocity = Mathf.Sqrt(GetJumpHeight() * -2f * Physics.gravity.y);
}
verticalVelocity += Physics.gravity.y * Time.deltaTime;
Vector3 vector = smoothedHorizontalVelocity + Vector3.up * verticalVelocity;
characterController.Move(vector * Time.deltaTime);
}
else
{
if (Input.GetKeyDown(KeyCode.Space) && Mathf.Abs(verticalVelocity) < 0.001f)
{
verticalVelocity = Mathf.Sqrt(GetJumpHeight() * -2f * Physics.gravity.y);
}
verticalVelocity += Physics.gravity.y * Time.deltaTime;
rigRoot.position += (smoothedHorizontalVelocity + Vector3.up * verticalVelocity) * Time.deltaTime;
}
}
private void UpdateProxyHands()
{
if (!(headTransform == null))
{
Quaternion rotation = headTransform.rotation;
Vector3 position = headTransform.TransformPoint(new Vector3(0f - handHorizontalOffset, handVerticalOffset, handForwardOffset));
Vector3 position2 = headTransform.TransformPoint(new Vector3(handHorizontalOffset, handVerticalOffset, handForwardOffset));
if (leftHandTransform != null)
{
leftHandTransform.SetPositionAndRotation(position, rotation);
}
if (rightHandTransform != null)
{
rightHandTransform.SetPositionAndRotation(position2, rotation);
}
}
}
private float GetMoveSpeed()
{
if (!(moveProvider != null))
{
return defaultMoveSpeed;
}
return moveProvider.moveSpeed;
}
private float GetJumpHeight()
{
if (!(jumpProvider != null))
{
return defaultJumpHeight;
}
return jumpProvider.jumpHeight;
}
private float GetMouseLookSpeed()
{
if (turnProvider == null)
{
return mouseSensitivity;
}
return turnProvider.turnSpeed / 180f * mouseSensitivity;
}
private void ApplyCharacterControllerPhase(bool phaseThrough)
{
if (phaseThrough)
{
ApplyRigColliderPhase();
if (characterController != null && !characterControllerCollisionCached)
{
cachedCharacterControllerEnabled = characterController.enabled;
cachedCharacterControllerDetectCollisions = characterController.detectCollisions;
characterControllerCollisionCached = true;
}
if (characterController != null)
{
characterController.enabled = false;
characterController.detectCollisions = false;
}
}
else
{
RestoreCharacterControllerCollisions();
}
}
private void ApplyRigColliderPhase()
{
Collider[] componentsInChildren = ((rigRoot != null) ? rigRoot : base.transform).GetComponentsInChildren<Collider>(includeInactive: true);
foreach (Collider collider in componentsInChildren)
{
if (!(collider == null) && !collider.isTrigger && !(collider is CharacterController))
{
if (!phaseColliderStates.ContainsKey(collider))
{
phaseColliderStates.Add(collider, collider.enabled);
}
collider.enabled = false;
}
}
}
private void RestoreCharacterControllerCollisions()
{
if (!characterControllerCollisionCached || characterController == null)
{
characterControllerCollisionCached = false;
RestoreRigColliderPhase();
RestoreCharacterControllerForPcMovement();
}
else
{
characterController.enabled = cachedCharacterControllerEnabled;
characterController.detectCollisions = cachedCharacterControllerDetectCollisions;
characterControllerCollisionCached = false;
RestoreRigColliderPhase();
RestoreCharacterControllerForPcMovement();
}
}
private void RestoreCharacterControllerForPcMovement()
{
if (pcModeActive && !(characterController == null))
{
characterController.enabled = true;
characterController.detectCollisions = true;
}
}
private void RestoreRigColliderPhase()
{
foreach (KeyValuePair<Collider, bool> phaseColliderState in phaseColliderStates)
{
if (phaseColliderState.Key != null)
{
phaseColliderState.Key.enabled = phaseColliderState.Value;
}
}
phaseColliderStates.Clear();
}
private void EnsureCursorLocked()
{
if (Application.isFocused)
{
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
}
private static void UnlockCursor()
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
private static float NormalizeAngle(float angle)
{
if (angle > 180f)
{
angle -= 360f;
}
return angle;
}
private static bool DetectXRInputActive()
{
if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).isValid && !InputDevices.GetDeviceAtXRNode(XRNode.LeftHand).isValid)
{
return InputDevices.GetDeviceAtXRNode(XRNode.RightHand).isValid;
}
return true;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 673863217a26ab53a9cce8cfaf896017
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,432 @@
using System.Collections.Generic;
using UnityEngine;
namespace BlockSpace.Voxels
{
public static class GreedyMesher
{
public sealed class MeshData
{
public List<Vector3> Vertices { get; } = new List<Vector3>(256);
public List<Vector3> Normals { get; } = new List<Vector3>(256);
public List<Color32> Colors { get; } = new List<Color32>(256);
public List<Vector2> Uvs { get; } = new List<Vector2>(256);
public List<int>[] SubmeshTriangles { get; }
public MeshData(int submeshCount)
{
SubmeshTriangles = new List<int>[submeshCount];
for (int i = 0; i < submeshCount; i++)
{
SubmeshTriangles[i] = new List<int>(256);
}
}
}
private struct FaceDescriptor
{
public bool Valid;
public int Submesh;
public Color32 Color;
public bool Matches(FaceDescriptor other)
{
if (Valid == other.Valid && Submesh == other.Submesh && Color.r == other.Color.r && Color.g == other.Color.g && Color.b == other.Color.b)
{
return Color.a == other.Color.a;
}
return false;
}
}
public static MeshData BuildRenderMesh(Vector3Int size, byte[] voxels, bool creatorMode, float voxelSize)
{
MeshData meshData = new MeshData(4);
if (voxels == null || voxels.Length == 0 || size.x <= 0 || size.y <= 0 || size.z <= 0)
{
return meshData;
}
for (int i = 0; i < 3; i++)
{
int num = (i + 1) % 3;
int num2 = (i + 2) % 3;
int num3 = size[num];
int num4 = size[num2];
FaceDescriptor[] mask = new FaceDescriptor[num3 * num4];
BuildRenderFacesForDirection(size, voxels, creatorMode, voxelSize, meshData, mask, i, 1, num3, num4, num, num2);
BuildRenderFacesForDirection(size, voxels, creatorMode, voxelSize, meshData, mask, i, -1, num3, num4, num, num2);
}
return meshData;
}
public static MeshData BuildCulledRenderMesh(Vector3Int size, byte[] voxels, bool creatorMode, float voxelSize)
{
MeshData meshData = new MeshData(4);
if (voxels == null || voxels.Length == 0 || size.x <= 0 || size.y <= 0 || size.z <= 0)
{
return meshData;
}
for (int i = 0; i < size.z; i++)
{
for (int j = 0; j < size.y; j++)
{
for (int k = 0; k < size.x; k++)
{
byte voxel = GetVoxel(size, voxels, k, j, i);
if (!PackedVoxel.IsAir(voxel))
{
EmitCulledFaces(meshData, size, voxels, creatorMode, voxelSize, k, j, i, voxel);
}
}
}
}
return meshData;
}
public static MeshData BuildColliderMesh(Vector3Int size, byte[] voxels, float voxelSize)
{
MeshData meshData = new MeshData(1);
if (voxels == null || voxels.Length == 0 || size.x <= 0 || size.y <= 0 || size.z <= 0)
{
return meshData;
}
for (int i = 0; i < 3; i++)
{
int num = (i + 1) % 3;
int num2 = (i + 2) % 3;
int num3 = size[num];
int num4 = size[num2];
bool[] mask = new bool[num3 * num4];
BuildColliderFacesForDirection(size, voxels, voxelSize, meshData, mask, i, 1, num3, num4, num, num2);
BuildColliderFacesForDirection(size, voxels, voxelSize, meshData, mask, i, -1, num3, num4, num, num2);
}
return meshData;
}
private static void BuildRenderFacesForDirection(Vector3Int size, byte[] voxels, bool creatorMode, float voxelSize, MeshData meshData, FaceDescriptor[] mask, int axis, int direction, int width, int height, int u, int v)
{
for (int i = 0; i <= size[axis]; i++)
{
int num = 0;
for (int j = 0; j < height; j++)
{
for (int k = 0; k < width; k++)
{
Vector3Int zero = Vector3Int.zero;
zero[axis] = ((direction > 0) ? (i - 1) : i);
zero[u] = k;
zero[v] = j;
byte voxel = GetVoxel(size, voxels, zero.x, zero.y, zero.z);
FaceDescriptor descriptor = default(FaceDescriptor);
if (!PackedVoxel.IsAir(voxel))
{
Vector3Int vector3Int = zero;
vector3Int[axis] += direction;
byte voxel2 = GetVoxel(size, voxels, vector3Int.x, vector3Int.y, vector3Int.z);
if (TryCreateRenderFace(voxel, voxel2, creatorMode, out descriptor))
{
descriptor.Valid = true;
}
}
mask[num++] = descriptor;
}
}
GreedyMergeRenderMask(meshData, mask, axis, direction, i, width, height, u, v, voxelSize);
}
}
private static void BuildColliderFacesForDirection(Vector3Int size, byte[] voxels, float voxelSize, MeshData meshData, bool[] mask, int axis, int direction, int width, int height, int u, int v)
{
for (int i = 0; i <= size[axis]; i++)
{
int num = 0;
for (int j = 0; j < height; j++)
{
for (int k = 0; k < width; k++)
{
Vector3Int zero = Vector3Int.zero;
zero[axis] = ((direction > 0) ? (i - 1) : i);
zero[u] = k;
zero[v] = j;
if (PackedVoxel.IsAir(GetVoxel(size, voxels, zero.x, zero.y, zero.z)))
{
mask[num++] = false;
continue;
}
Vector3Int vector3Int = zero;
vector3Int[axis] += direction;
byte voxel = GetVoxel(size, voxels, vector3Int.x, vector3Int.y, vector3Int.z);
mask[num++] = PackedVoxel.IsAir(voxel);
}
}
GreedyMergeColliderMask(meshData, mask, axis, direction, i, width, height, u, v, voxelSize);
}
}
private static bool TryCreateRenderFace(byte current, byte neighbor, bool creatorMode, out FaceDescriptor descriptor)
{
descriptor = default(FaceDescriptor);
if (PackedVoxel.IsAir(current))
{
return false;
}
VoxelMaterial material = PackedVoxel.GetMaterial(current);
VoxelMaterial voxelMaterial = ((!PackedVoxel.IsAir(neighbor)) ? PackedVoxel.GetMaterial(neighbor) : VoxelMaterial.Lit);
bool flag = PackedVoxel.IsAir(neighbor);
bool flag2 = !flag && voxelMaterial == VoxelMaterial.Invisible;
switch (material)
{
case VoxelMaterial.Lit:
if (!flag && !flag2 && voxelMaterial != VoxelMaterial.Transparent)
{
return false;
}
descriptor.Submesh = 0;
descriptor.Color = Palette.GetPreviewColor(PackedVoxel.GetColorIndex(current), material);
return true;
case VoxelMaterial.Unlit:
if (!flag && !flag2 && voxelMaterial != VoxelMaterial.Transparent)
{
return false;
}
descriptor.Submesh = 1;
descriptor.Color = Palette.GetPreviewColor(PackedVoxel.GetColorIndex(current), material);
return true;
case VoxelMaterial.Transparent:
if (!flag && !flag2 && voxelMaterial == VoxelMaterial.Transparent)
{
return false;
}
descriptor.Submesh = 2;
descriptor.Color = Palette.GetPreviewColor(PackedVoxel.GetColorIndex(current), material);
return true;
case VoxelMaterial.Invisible:
if (!creatorMode || !flag)
{
return false;
}
descriptor.Submesh = 3;
descriptor.Color = Palette.GetPreviewColor(PackedVoxel.GetColorIndex(current), material);
return true;
default:
return false;
}
}
private static void EmitCulledFaces(MeshData meshData, Vector3Int size, byte[] voxels, bool creatorMode, float voxelSize, int x, int y, int z, byte current)
{
Vector3Int vector3Int = new Vector3Int(x, y, z);
for (int i = 0; i < 3; i++)
{
int num = (i + 1) % 3;
int num2 = (i + 2) % 3;
for (int j = -1; j <= 1; j += 2)
{
Vector3Int vector3Int2 = vector3Int;
vector3Int2[i] += j;
byte voxel = GetVoxel(size, voxels, vector3Int2.x, vector3Int2.y, vector3Int2.z);
if (TryCreateRenderFace(current, voxel, creatorMode, out var descriptor))
{
int slice = ((j > 0) ? (vector3Int[i] + 1) : vector3Int[i]);
EmitRenderQuad(meshData, descriptor, i, j, slice, vector3Int[num], vector3Int[num2], 1, 1, num, num2, voxelSize);
}
}
}
}
private static void GreedyMergeRenderMask(MeshData meshData, FaceDescriptor[] mask, int axis, int direction, int slice, int width, int height, int u, int v, float voxelSize)
{
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int num = j + i * width;
FaceDescriptor descriptor = mask[num];
if (!descriptor.Valid)
{
continue;
}
int k;
for (k = 1; j + k < width && descriptor.Matches(mask[num + k]); k++)
{
}
int num2 = 1;
bool flag = true;
while (i + num2 < height && flag)
{
for (int l = 0; l < k; l++)
{
if (!descriptor.Matches(mask[num + l + num2 * width]))
{
flag = false;
break;
}
}
if (flag)
{
num2++;
}
}
EmitRenderQuad(meshData, descriptor, axis, direction, slice, j, i, k, num2, u, v, voxelSize);
for (int m = 0; m < num2; m++)
{
for (int n = 0; n < k; n++)
{
mask[num + n + m * width] = default(FaceDescriptor);
}
}
j += k - 1;
}
}
}
private static void GreedyMergeColliderMask(MeshData meshData, bool[] mask, int axis, int direction, int slice, int width, int height, int u, int v, float voxelSize)
{
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int num = j + i * width;
if (!mask[num])
{
continue;
}
int k;
for (k = 1; j + k < width && mask[num + k]; k++)
{
}
int num2 = 1;
bool flag = true;
while (i + num2 < height && flag)
{
for (int l = 0; l < k; l++)
{
if (!mask[num + l + num2 * width])
{
flag = false;
break;
}
}
if (flag)
{
num2++;
}
}
EmitColliderQuad(meshData, axis, direction, slice, j, i, k, num2, u, v, voxelSize);
for (int m = 0; m < num2; m++)
{
for (int n = 0; n < k; n++)
{
mask[num + n + m * width] = false;
}
}
j += k - 1;
}
}
}
private static void EmitRenderQuad(MeshData meshData, FaceDescriptor descriptor, int axis, int direction, int slice, int startU, int startV, int width, int height, int u, int v, float voxelSize)
{
int count = meshData.Vertices.Count;
Vector3 item = AxisVector(axis) * direction;
BuildQuad(axis, direction, slice, startU, startV, width, height, u, v, voxelSize, out var p, out var p2, out var p3, out var p4);
meshData.Vertices.Add(p);
meshData.Vertices.Add(p2);
meshData.Vertices.Add(p3);
meshData.Vertices.Add(p4);
meshData.Normals.Add(item);
meshData.Normals.Add(item);
meshData.Normals.Add(item);
meshData.Normals.Add(item);
meshData.Colors.Add(descriptor.Color);
meshData.Colors.Add(descriptor.Color);
meshData.Colors.Add(descriptor.Color);
meshData.Colors.Add(descriptor.Color);
meshData.Uvs.Add(new Vector2(0f, 0f));
meshData.Uvs.Add(new Vector2(0f, height));
meshData.Uvs.Add(new Vector2(width, 0f));
meshData.Uvs.Add(new Vector2(width, height));
List<int> obj = meshData.SubmeshTriangles[descriptor.Submesh];
obj.Add(count);
obj.Add(count + 2);
obj.Add(count + 1);
obj.Add(count + 2);
obj.Add(count + 3);
obj.Add(count + 1);
}
private static void EmitColliderQuad(MeshData meshData, int axis, int direction, int slice, int startU, int startV, int width, int height, int u, int v, float voxelSize)
{
int count = meshData.Vertices.Count;
Vector3 item = AxisVector(axis) * direction;
BuildQuad(axis, direction, slice, startU, startV, width, height, u, v, voxelSize, out var p, out var p2, out var p3, out var p4);
meshData.Vertices.Add(p);
meshData.Vertices.Add(p2);
meshData.Vertices.Add(p3);
meshData.Vertices.Add(p4);
meshData.Normals.Add(item);
meshData.Normals.Add(item);
meshData.Normals.Add(item);
meshData.Normals.Add(item);
List<int> obj = meshData.SubmeshTriangles[0];
obj.Add(count);
obj.Add(count + 2);
obj.Add(count + 1);
obj.Add(count + 2);
obj.Add(count + 3);
obj.Add(count + 1);
}
private static void BuildQuad(int axis, int direction, int slice, int startU, int startV, int width, int height, int u, int v, float voxelSize, out Vector3 p0, out Vector3 p1, out Vector3 p2, out Vector3 p3)
{
Vector3 zero = Vector3.zero;
zero[axis] = (float)slice * voxelSize;
zero[u] = (float)startU * voxelSize;
zero[v] = (float)startV * voxelSize;
Vector3 zero2 = Vector3.zero;
zero2[u] = (float)width * voxelSize;
Vector3 zero3 = Vector3.zero;
zero3[v] = (float)height * voxelSize;
if (direction > 0)
{
p0 = zero;
p1 = zero + zero3;
p2 = zero + zero2;
p3 = zero + zero2 + zero3;
}
else
{
p0 = zero;
p1 = zero + zero2;
p2 = zero + zero3;
p3 = zero + zero2 + zero3;
}
}
private static byte GetVoxel(Vector3Int size, byte[] voxels, int x, int y, int z)
{
if (x < 0 || y < 0 || z < 0 || x >= size.x || y >= size.y || z >= size.z)
{
return 0;
}
int num = x + y * size.x + z * size.x * size.y;
return voxels[num];
}
private static Vector3 AxisVector(int axis)
{
return axis switch
{
0 => Vector3.right,
1 => Vector3.up,
_ => Vector3.forward,
};
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 0b06939556c6fce8da6b1b790c760091
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
using UnityEngine;
namespace BlockSpace.Voxels
{
public static class GridRotationUtility
{
public static byte Pack(Vector3Int quarterTurns)
{
return (byte)(Normalize(quarterTurns.x) | (Normalize(quarterTurns.y) << 2) | (Normalize(quarterTurns.z) << 4));
}
public static Vector3Int Unpack(byte packedRotation)
{
return new Vector3Int(packedRotation & 3, (packedRotation >> 2) & 3, (packedRotation >> 4) & 3);
}
public static Quaternion ToQuaternion(byte packedRotation)
{
Vector3Int vector3Int = Unpack(packedRotation);
return Quaternion.Euler((float)vector3Int.x * 90f, (float)vector3Int.y * 90f, (float)vector3Int.z * 90f);
}
public static Vector3Int Rotate(Vector3Int localOffset, byte packedRotation)
{
Vector3 vector = ToQuaternion(packedRotation) * localOffset;
return new Vector3Int(Mathf.RoundToInt(vector.x), Mathf.RoundToInt(vector.y), Mathf.RoundToInt(vector.z));
}
public static Vector3Int InverseRotate(Vector3Int worldOffset, byte packedRotation)
{
Vector3 vector = Quaternion.Inverse(ToQuaternion(packedRotation)) * worldOffset;
return new Vector3Int(Mathf.RoundToInt(vector.x), Mathf.RoundToInt(vector.y), Mathf.RoundToInt(vector.z));
}
private static int Normalize(int turns)
{
turns %= 4;
if (turns >= 0)
{
return turns;
}
return turns + 4;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1cb25749cb950a142b88f917a8649dea
@@ -0,0 +1,246 @@
using System;
using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
namespace BlockSpace.Voxels
{
public sealed class InventoryBackendClient
{
[Serializable]
public sealed class ListResponse
{
public bool ok;
public string[] ids;
public string equippedId;
public string error;
}
[Serializable]
public sealed class PutResponse
{
public bool ok;
public bool saved;
public string id;
public string error;
}
[Serializable]
public sealed class EquipResponse
{
public bool ok;
public string equippedId;
public string error;
}
[Serializable]
private sealed class EquipRequest
{
public string playerId;
public string avatarId;
}
private readonly string baseUrl;
public InventoryBackendClient(string baseUrl)
{
this.baseUrl = (baseUrl ?? string.Empty).Trim().TrimEnd('/');
}
public IEnumerator ListAvatars(string playerId, Action<ListResponse> onDone)
{
yield return GetJson("/api/inventory/avatars?playerId=" + UnityWebRequest.EscapeURL(playerId ?? ""), onDone);
}
public IEnumerator ListCreations(string playerId, Action<ListResponse> onDone)
{
yield return GetJson("/api/inventory/creations?playerId=" + UnityWebRequest.EscapeURL(playerId ?? ""), onDone);
}
public IEnumerator DownloadAvatar(string playerId, string avatarId, Action<string> onDone)
{
string uri = baseUrl + "/api/inventory/avatars/" + UnityWebRequest.EscapeURL(avatarId ?? "") + "?playerId=" + UnityWebRequest.EscapeURL(playerId ?? "");
using UnityWebRequest request = UnityWebRequest.Get(uri);
request.downloadHandler = new DownloadHandlerBuffer();
yield return request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success)
{
Debug.LogWarning($"DownloadAvatar failed: {request.result} {request.responseCode} {request.error}");
onDone?.Invoke(string.Empty);
yield break;
}
string text = request.downloadHandler.text ?? string.Empty;
Debug.Log($"DownloadAvatar ok: code={request.responseCode} bytes={((text != null) ? Encoding.UTF8.GetByteCount(text) : 0)}");
onDone?.Invoke(text);
}
public IEnumerator UploadAvatar(string playerId, string avatarId, string payloadJson, Action<PutResponse> onDone)
{
string url = baseUrl + "/api/inventory/avatars/" + UnityWebRequest.EscapeURL(avatarId ?? "") + "?playerId=" + UnityWebRequest.EscapeURL(playerId ?? "");
byte[] body = Encoding.UTF8.GetBytes(payloadJson ?? "{}");
using UnityWebRequest request = new UnityWebRequest(url, "PUT");
request.uploadHandler = new UploadHandlerRaw(body);
request.downloadHandler = new DownloadHandlerBuffer();
request.chunkedTransfer = false;
request.uploadHandler.contentType = "application/json; charset=utf-8";
request.SetRequestHeader("Content-Type", "application/json; charset=utf-8");
yield return request.SendWebRequest();
PutResponse putResponse = null;
if (request.result == UnityWebRequest.Result.Success)
{
try
{
putResponse = JsonUtility.FromJson<PutResponse>(request.downloadHandler.text);
}
catch
{
}
Debug.Log($"UploadAvatar ok: code={request.responseCode} bytes={body.Length} respLen={((request.downloadHandler.text != null) ? request.downloadHandler.text.Length : 0)}");
}
else
{
Debug.LogWarning($"UploadAvatar failed: {request.result} {request.responseCode} {request.error} bytes={body.Length}");
}
if (putResponse == null)
{
putResponse = new PutResponse
{
ok = false,
saved = false,
id = (avatarId ?? ""),
error = request.error
};
}
onDone?.Invoke(putResponse);
}
public IEnumerator EquipAvatar(string playerId, string avatarId, Action<EquipResponse> onDone)
{
string url = baseUrl + "/api/inventory/avatars/equip";
string s = JsonUtility.ToJson(new EquipRequest
{
playerId = (playerId ?? ""),
avatarId = (avatarId ?? "")
});
byte[] bytes = Encoding.UTF8.GetBytes(s);
using UnityWebRequest request = new UnityWebRequest(url, "POST");
request.uploadHandler = new UploadHandlerRaw(bytes);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json; charset=utf-8");
yield return request.SendWebRequest();
EquipResponse equipResponse = null;
if (request.result == UnityWebRequest.Result.Success)
{
try
{
equipResponse = JsonUtility.FromJson<EquipResponse>(request.downloadHandler.text);
}
catch
{
}
}
if (equipResponse == null)
{
equipResponse = new EquipResponse
{
ok = false,
equippedId = "",
error = request.error
};
}
onDone?.Invoke(equipResponse);
}
public IEnumerator DownloadCreation(string playerId, string creationId, Action<byte[]> onDone)
{
string uri = baseUrl + "/api/inventory/creations/" + UnityWebRequest.EscapeURL(creationId ?? "") + "?playerId=" + UnityWebRequest.EscapeURL(playerId ?? "");
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 UploadCreation(string playerId, string creationId, byte[] gzipBytes, Action<PutResponse> onDone)
{
string url = baseUrl + "/api/inventory/creations/" + UnityWebRequest.EscapeURL(creationId ?? "") + "?playerId=" + UnityWebRequest.EscapeURL(playerId ?? "");
byte[] body = gzipBytes ?? Array.Empty<byte>();
using UnityWebRequest request = new UnityWebRequest(url, "PUT");
request.uploadHandler = new UploadHandlerRaw(body);
request.downloadHandler = new DownloadHandlerBuffer();
request.chunkedTransfer = false;
request.uploadHandler.contentType = "application/octet-stream";
request.SetRequestHeader("Content-Type", "application/octet-stream");
yield return request.SendWebRequest();
PutResponse putResponse = null;
if (request.result == UnityWebRequest.Result.Success)
{
try
{
putResponse = JsonUtility.FromJson<PutResponse>(request.downloadHandler.text);
}
catch
{
}
Debug.Log($"UploadCreation ok: code={request.responseCode} bytes={body.Length}");
}
else
{
Debug.LogWarning($"UploadCreation failed: {request.result} {request.responseCode} {request.error}");
}
if (putResponse == null)
{
putResponse = new PutResponse
{
ok = false,
saved = false,
id = (creationId ?? ""),
error = request.error
};
}
onDone?.Invoke(putResponse);
}
private IEnumerator GetJson<T>(string pathAndQuery, Action<T> onDone) where T : class
{
if (string.IsNullOrEmpty(baseUrl))
{
onDone?.Invoke(null);
yield break;
}
string url = baseUrl + pathAndQuery;
using UnityWebRequest request = UnityWebRequest.Get(url);
request.downloadHandler = new DownloadHandlerBuffer();
yield return request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success)
{
Debug.LogWarning($"InventoryBackendClient GET failed: {url} {request.result} {request.responseCode} {request.error}");
onDone?.Invoke(null);
yield break;
}
T obj = null;
try
{
obj = JsonUtility.FromJson<T>(request.downloadHandler.text);
}
catch
{
}
onDone?.Invoke(obj);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 03d555513081e5324f351221a3628274
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,1160 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using BlockSpace.Account;
using BlockSpace.Worlds;
using UnityEngine;
namespace BlockSpace.Voxels
{
[DisallowMultipleComponent]
public sealed class InventoryController : MonoBehaviour
{
private enum InventoryMode
{
Avatars = 0,
Creations = 1
}
private const float ButtonPressCooldown = 0.2f;
private const float ButtonShrinkDuration = 0.08f;
private const float ButtonSettleDuration = 0.12f;
private const float ButtonPressedScale = 0.84f;
private const float ButtonOvershootScale = 1.04f;
private const string ButtonThockResourcePath = "Thock";
private const float AvatarVoxelSizeScale = 0.5f;
private const float CreationPreviewMaxSize = 3f;
[SerializeField]
private VoxelGroupManager groupManager;
[SerializeField]
private VRVoxelBrush voxelBrush;
[SerializeField]
private string backendBaseUrl = "https://blockspacebackend-production.up.railway.app";
[Header("Buttons")]
[SerializeField]
private Collider avatarsButton;
[SerializeField]
private Collider creationsButton;
[SerializeField]
private Collider backButton;
[SerializeField]
private Collider nextButton;
[SerializeField]
private Collider saveButton;
[SerializeField]
private Collider loadButton;
[Header("Previews")]
[SerializeField]
private Transform creationPreviewRoot;
[SerializeField]
private Transform previewHead;
[SerializeField]
private Transform previewLeft;
[SerializeField]
private Transform previewRight;
[SerializeField]
private AudioClip buttonThockClip;
[SerializeField]
private bool debugLogs;
private readonly Dictionary<Transform, Vector3> buttonBaseScales = new Dictionary<Transform, Vector3>();
private readonly Dictionary<Transform, Coroutine> buttonAnimationCoroutines = new Dictionary<Transform, Coroutine>();
private readonly Dictionary<string, Transform> transformCache = new Dictionary<string, Transform>(StringComparer.OrdinalIgnoreCase);
private InventoryMode mode;
private readonly List<string> ids = new List<string>();
private int slotIndex;
private string equippedAvatarId = string.Empty;
private bool slotsDirty = true;
private float nextButtonPressTime;
private bool avatarsInsidePrev;
private bool creationsInsidePrev;
private bool backInsidePrev;
private bool nextInsidePrev;
private bool saveInsidePrev;
private bool loadInsidePrev;
private VoxelGroup previewCreationGroup;
private readonly List<VoxelGroup> previewCreationGroups = new List<VoxelGroup>();
private VoxelGroup previewHeadGroup;
private VoxelGroup previewLeftGroup;
private VoxelGroup previewRightGroup;
private bool warnedMissingAvatarPreviewRoots;
private bool warnedMissingCreationPreviewRoot;
private InventoryBackendClient client;
private int previewRequestToken;
public void Configure(VoxelGroupManager manager, VRVoxelBrush brush)
{
if (manager != null)
{
groupManager = manager;
}
if (brush != null)
{
voxelBrush = brush;
}
ResolveSceneReferences();
EnsureButtonAudioAssigned();
EnsurePreviewGroups();
client = new InventoryBackendClient(backendBaseUrl);
slotsDirty = true;
}
private void Awake()
{
ResolveSceneReferences();
EnsureButtonAudioAssigned();
EnsurePreviewGroups();
client = new InventoryBackendClient(backendBaseUrl);
}
private void OnDisable()
{
StopAllCoroutines();
ResetAllButtonScales();
}
private void Update()
{
ResolveSceneReferences();
EnsurePreviewGroups();
if (!(groupManager == null))
{
HandleButton(avatarsButton, ref avatarsInsidePrev, delegate
{
SwitchMode(InventoryMode.Avatars);
});
HandleButton(creationsButton, ref creationsInsidePrev, delegate
{
SwitchMode(InventoryMode.Creations);
});
HandleButton(backButton, ref backInsidePrev, PrevSlot);
HandleButton(nextButton, ref nextInsidePrev, NextSlot);
HandleButton(saveButton, ref saveInsidePrev, SaveCurrentSlot);
HandleButton(loadButton, ref loadInsidePrev, LoadCurrentSlot);
if (slotsDirty)
{
slotsDirty = false;
StartCoroutine(RefreshSlots());
}
}
}
private void SwitchMode(InventoryMode newMode)
{
if (mode != newMode)
{
mode = newMode;
slotIndex = 0;
if (mode == InventoryMode.Avatars)
{
ApplyEmptyCreationPreview();
}
else
{
ApplyEmptyAvatarPreview();
}
slotsDirty = true;
}
}
private void PrevSlot()
{
if (ids.Count <= 0)
{
slotIndex = 0;
return;
}
slotIndex = (slotIndex - 1 + ids.Count) % ids.Count;
UpdatePreviewsForSelectedSlot();
}
private void NextSlot()
{
if (ids.Count <= 0)
{
slotIndex = 0;
return;
}
slotIndex = (slotIndex + 1) % ids.Count;
UpdatePreviewsForSelectedSlot();
}
private void SaveCurrentSlot()
{
string userFolderId = BlockSpaceAccountPrefs.GetUserFolderId();
if (string.IsNullOrEmpty(userFolderId) || client == null || (mode == InventoryMode.Creations && (groupManager == null || !groupManager.CreatorMode || groupManager.EditingLocked)))
{
return;
}
string orCreateSelectedId = GetOrCreateSelectedId();
if (!string.IsNullOrEmpty(orCreateSelectedId))
{
if (mode == InventoryMode.Avatars)
{
StartCoroutine(SaveAvatarSlot(userFolderId, orCreateSelectedId));
}
else
{
StartCoroutine(SaveCreationSlot(userFolderId, orCreateSelectedId));
}
}
}
private void LoadCurrentSlot()
{
string userFolderId = BlockSpaceAccountPrefs.GetUserFolderId();
if (!string.IsNullOrEmpty(userFolderId) && client != null && (mode != InventoryMode.Creations || (!(groupManager == null) && groupManager.CreatorMode && !groupManager.EditingLocked)) && TryGetSelectedId(out var id) && !string.IsNullOrEmpty(id))
{
if (mode == InventoryMode.Avatars)
{
StartCoroutine(LoadAndEquipAvatar(userFolderId, id));
}
else
{
StartCoroutine(LoadCreationIntoWorld(userFolderId, id));
}
}
}
private IEnumerator RefreshSlots()
{
string playerId = BlockSpaceAccountPrefs.GetUserFolderId();
if (string.IsNullOrEmpty(playerId) || client == null)
{
ids.Clear();
yield break;
}
InventoryBackendClient.ListResponse response = null;
if (mode == InventoryMode.Avatars)
{
yield return client.ListAvatars(playerId, delegate(InventoryBackendClient.ListResponse r)
{
response = r;
});
}
else
{
yield return client.ListCreations(playerId, delegate(InventoryBackendClient.ListResponse r)
{
response = r;
});
}
ids.Clear();
if (response != null && response.ok && response.ids != null)
{
ids.AddRange(response.ids);
}
ids.Add(string.Empty);
equippedAvatarId = ((response != null) ? (response.equippedId ?? string.Empty) : string.Empty);
if (debugLogs)
{
Debug.Log($"InventoryController: mode={mode} playerIdLen={playerId.Length} ids={ids.Count} equipped={equippedAvatarId}");
}
slotIndex = Mathf.Clamp(slotIndex, 0, Mathf.Max(0, ids.Count - 1));
UpdatePreviewsForSelectedSlot();
if (mode != InventoryMode.Avatars || !BlockSpaceAccountPrefs.IsLinked)
{
yield break;
}
bool flag = false;
for (int num = 0; num < ids.Count; num++)
{
if (!string.IsNullOrEmpty(ids[num]))
{
flag = true;
break;
}
}
if (flag || !AvatarVoxelStorage.TryLoadLocal(out var localPayload) || !AvatarPayloadHasVoxels(localPayload))
{
yield break;
}
string newId = MakeNewId();
InventoryBackendClient.PutResponse put = null;
yield return client.UploadAvatar(playerId, newId, AvatarVoxelStorage.Serialize(localPayload), delegate(InventoryBackendClient.PutResponse r)
{
put = r;
});
if (put != null && put.ok && put.saved)
{
InventoryBackendClient.EquipResponse equip = null;
yield return client.EquipAvatar(playerId, newId, delegate(InventoryBackendClient.EquipResponse r)
{
equip = r;
});
PhotonVoxelAvatar localOwnedInstance = PhotonVoxelAvatar.LocalOwnedInstance;
if (localOwnedInstance != null)
{
localOwnedInstance.ApplyAndBroadcastAvatar(localPayload);
}
if (PlayerPrefs.HasKey("BlockSpace.Avatar.Payload"))
{
PlayerPrefs.DeleteKey("BlockSpace.Avatar.Payload");
PlayerPrefs.Save();
}
slotsDirty = true;
}
}
private static bool AvatarPayloadHasVoxels(AvatarVoxelPayload payload)
{
if (payload == null)
{
return false;
}
if (!GroupDataHasVoxels(payload.headBody) && !GroupDataHasVoxels(payload.left))
{
return GroupDataHasVoxels(payload.right);
}
return true;
}
private static bool AvatarPayloadHasAnyBase64(AvatarVoxelPayload payload)
{
if (payload == null)
{
return false;
}
if (string.IsNullOrEmpty(payload.headBody?.voxelsBase64) && string.IsNullOrEmpty(payload.left?.voxelsBase64))
{
return !string.IsNullOrEmpty(payload.right?.voxelsBase64);
}
return true;
}
private static int AvatarPayloadBase64TotalLength(AvatarVoxelPayload payload)
{
if (payload == null)
{
return 0;
}
return (payload.headBody?.voxelsBase64 ?? string.Empty).Length + (payload.left?.voxelsBase64 ?? string.Empty).Length + (payload.right?.voxelsBase64 ?? string.Empty).Length;
}
private static bool GroupDataHasVoxels(AvatarVoxelGroupData group)
{
if (group == null)
{
return false;
}
if (group.sizeX <= 0 || group.sizeY <= 0 || group.sizeZ <= 0)
{
return false;
}
return true;
}
private IEnumerator SaveAvatarSlot(string playerId, string avatarId)
{
AvatarCustomizerController avatarCustomizerController = UnityEngine.Object.FindObjectOfType<AvatarCustomizerController>();
PhotonVoxelAvatar runtime = PhotonVoxelAvatar.LocalOwnedInstance;
AvatarVoxelPayload editorPayload = null;
VoxelGroup headBody = null;
VoxelGroup left = null;
VoxelGroup right = null;
if (avatarCustomizerController != null && avatarCustomizerController.TryGetEditorGroups(out headBody, out left, out right))
{
editorPayload = AvatarVoxelStorage.CapturePayload(headBody, left, right);
}
AvatarVoxelPayload avatarVoxelPayload = ((runtime != null) ? runtime.CaptureRuntimePayload() : null);
AvatarVoxelPayload payload;
AvatarVoxelPayload localPayload = (AvatarVoxelStorage.TryLoadLocal(out payload) ? payload : null);
AvatarVoxelPayload payload2 = (AvatarPayloadHasAnyBase64(editorPayload) ? editorPayload : (AvatarPayloadHasAnyBase64(avatarVoxelPayload) ? avatarVoxelPayload : (AvatarPayloadHasAnyBase64(localPayload) ? localPayload : (AvatarPayloadHasVoxels(editorPayload) ? editorPayload : (AvatarPayloadHasVoxels(avatarVoxelPayload) ? avatarVoxelPayload : (AvatarPayloadHasVoxels(localPayload) ? localPayload : (editorPayload ?? avatarVoxelPayload ?? localPayload ?? AvatarVoxelStorage.CreateEmptyPayload())))))));
if (runtime != null && AvatarPayloadBase64TotalLength(payload2) <= 0 && !runtime.HasRuntimeGroups)
{
float waited = 0f;
while (waited < 1.5f && !runtime.HasRuntimeGroups)
{
waited += 0.1f;
yield return new WaitForSeconds(0.1f);
}
avatarVoxelPayload = runtime.CaptureRuntimePayload();
if (AvatarPayloadHasAnyBase64(avatarVoxelPayload))
{
payload2 = avatarVoxelPayload;
}
else if (AvatarPayloadHasAnyBase64(localPayload))
{
payload2 = localPayload;
}
}
string text = AvatarVoxelStorage.Serialize(payload2);
if (text == "{}" || text.Length <= 4 || AvatarPayloadBase64TotalLength(payload2) <= 0)
{
Debug.LogWarning($"InventoryController: refusing to upload empty avatar payload slot={avatarId} jsonLen={text.Length} base64Total={AvatarPayloadBase64TotalLength(payload2)}");
yield break;
}
if (debugLogs)
{
int num = ((headBody != null && headBody.voxels != null) ? headBody.voxels.Length : 0);
int num2 = ((left != null && left.voxels != null) ? left.voxels.Length : 0);
int num3 = ((right != null && right.voxels != null) ? right.voxels.Length : 0);
int num4 = Mathf.Max(0, payload2.headBody?.sizeX ?? 0) * Mathf.Max(0, payload2.headBody?.sizeY ?? 0) * Mathf.Max(0, payload2.headBody?.sizeZ ?? 0);
int num5 = Mathf.Max(0, payload2.left?.sizeX ?? 0) * Mathf.Max(0, payload2.left?.sizeY ?? 0) * Mathf.Max(0, payload2.left?.sizeZ ?? 0);
int num6 = Mathf.Max(0, payload2.right?.sizeX ?? 0) * Mathf.Max(0, payload2.right?.sizeY ?? 0) * Mathf.Max(0, payload2.right?.sizeZ ?? 0);
int length = (payload2.headBody?.voxelsBase64 ?? string.Empty).Length;
int num7 = AvatarPayloadBase64TotalLength(payload2);
string text2 = ((payload2 == editorPayload) ? "editor" : ((payload2 == avatarVoxelPayload) ? "runtime" : ((payload2 == localPayload) ? "local" : "other")));
Debug.Log($"InventoryController: saving avatar slot={avatarId} chosen={text2} editor(head={num},left={num2},right={num3}) payloadExpected(head={num4},left={num5},right={num6}) headB64Len={length} b64Total={num7} jsonLen={text.Length}");
}
AvatarVoxelStorage.SaveLocal(payload2);
InventoryBackendClient.PutResponse put = null;
yield return client.UploadAvatar(playerId, avatarId, text, delegate(InventoryBackendClient.PutResponse r)
{
put = r;
});
if (put != null && put.ok && put.saved)
{
slotsDirty = true;
if (debugLogs)
{
string roundTrip = string.Empty;
yield return client.DownloadAvatar(playerId, avatarId, delegate(string t)
{
roundTrip = t;
});
int num8 = ((roundTrip != null) ? Encoding.UTF8.GetByteCount(roundTrip) : 0);
int num9 = 0;
if (!string.IsNullOrEmpty(roundTrip) && AvatarVoxelStorage.TryDeserialize(roundTrip, out var payload3))
{
num9 = (payload3.headBody?.voxelsBase64 ?? string.Empty).Length;
}
Debug.Log($"InventoryController: avatar roundtrip bytes={num8} headB64Len={num9}");
}
}
else if (debugLogs)
{
Debug.LogWarning(string.Format("InventoryController: UploadAvatar failed ok={0} saved={1} err={2}", put != null && put.ok, put != null && put.saved, (put != null) ? put.error : "null"));
}
}
private IEnumerator LoadAndEquipAvatar(string playerId, string avatarId)
{
string json = string.Empty;
yield return client.DownloadAvatar(playerId, avatarId, delegate(string t)
{
json = t;
});
if (!string.IsNullOrWhiteSpace(json) && AvatarVoxelStorage.TryDeserialize(json, out var payload))
{
AvatarCustomizerController avatarCustomizerController = UnityEngine.Object.FindObjectOfType<AvatarCustomizerController>();
if (avatarCustomizerController != null && avatarCustomizerController.TryGetEditorGroups(out var headBody, out var left, out var right))
{
AvatarVoxelStorage.ApplyPayload(payload, headBody, left, right, groupManager, useLocalGridTransform: true);
}
PhotonVoxelAvatar localOwnedInstance = PhotonVoxelAvatar.LocalOwnedInstance;
if (localOwnedInstance != null)
{
localOwnedInstance.ApplyAndBroadcastAvatar(payload);
}
InventoryBackendClient.EquipResponse equip = null;
yield return client.EquipAvatar(playerId, avatarId, delegate(InventoryBackendClient.EquipResponse r)
{
equip = r;
});
slotsDirty = true;
}
}
private IEnumerator SaveCreationSlot(string playerId, string creationId)
{
VRGroupSelector instance = VRGroupSelector.Instance;
List<VoxelGroup> list = ((instance != null) ? instance.GetSelectedGroupsSnapshot() : new List<VoxelGroup>());
NodeGraphManager instance2 = NodeGraphManager.Instance;
List<Node> list2 = ((instance2 != null) ? instance2.GetSelectedNodesSnapshot() : new List<Node>());
bool num = list != null && list.Count > 0;
bool flag = list2 != null && list2.Count > 0;
if (!num && !flag)
{
yield break;
}
HashSet<int> hashSet = null;
if (flag)
{
hashSet = new HashSet<int>();
for (int i = 0; i < list2.Count; i++)
{
if (list2[i] != null)
{
hashSet.Add(list2[i].NodeId);
}
}
}
byte[] gzipBytes = WorldGroupsCodec.EncodeGzip(list, (instance2 != null) ? instance2.BuildWorldNodeRecords(hashSet) : null, (instance2 != null) ? instance2.BuildWorldWireRecords(hashSet, BuildGroupSaveIndexMap(list)) : null);
InventoryBackendClient.PutResponse put = null;
yield return client.UploadCreation(playerId, creationId, gzipBytes, delegate(InventoryBackendClient.PutResponse r)
{
put = r;
});
if (put != null && put.ok && put.saved)
{
slotsDirty = true;
}
}
private IEnumerator LoadCreationIntoWorld(string playerId, string creationId)
{
byte[] gzip = null;
yield return client.DownloadCreation(playerId, creationId, delegate(byte[] b)
{
gzip = b;
});
if (gzip == null)
{
gzip = Array.Empty<byte>();
}
if (!WorldGroupsCodec.TryDecodeGzip(gzip, out WorldGroupsCodec.WorldSaveData saveData) || groupManager == null || !TryGetSpawnGridPosition(out var gridPosition))
{
yield break;
}
List<WorldGroupsCodec.WorldGroupRecord> list = saveData.groupRecords ?? new List<WorldGroupsCodec.WorldGroupRecord>();
List<WorldGroupsCodec.WorldNodeRecord> list2 = saveData.nodeRecords ?? new List<WorldGroupsCodec.WorldNodeRecord>();
if (list.Count == 0 && list2.Count == 0)
{
yield break;
}
float voxelSize = groupManager.VoxelSize;
Vector3 vector = ((list.Count > 0) ? new Vector3((float)list[0].position.x * voxelSize, (float)list[0].position.y * voxelSize, (float)list[0].position.z * voxelSize) : list2[0].position);
for (int num = 1; num < list.Count; num++)
{
Vector3 rhs = new Vector3((float)list[num].position.x * voxelSize, (float)list[num].position.y * voxelSize, (float)list[num].position.z * voxelSize);
vector = Vector3.Min(vector, rhs);
}
for (int num2 = 0; num2 < list2.Count; num2++)
{
vector = Vector3.Min(vector, list2[num2].position);
}
Dictionary<int, int> dictionary = new Dictionary<int, int>();
for (int num3 = 0; num3 < list.Count; num3++)
{
WorldGroupsCodec.WorldGroupRecord worldGroupRecord = list[num3];
VoxelGroupManager.SavedGroupData data = new VoxelGroupManager.SavedGroupData
{
position = worldGroupRecord.position,
packedRotation = worldGroupRecord.packedRotation,
size = worldGroupRecord.size,
voxels = worldGroupRecord.voxels
};
Vector3 vector2 = new Vector3((float)worldGroupRecord.position.x * voxelSize, (float)worldGroupRecord.position.y * voxelSize, (float)worldGroupRecord.position.z * voxelSize);
Vector3Int vector3Int = new Vector3Int(Mathf.RoundToInt((vector2.x - vector.x) / voxelSize), Mathf.RoundToInt((vector2.y - vector.y) / voxelSize), Mathf.RoundToInt((vector2.z - vector.z) / voxelSize));
VoxelGroup voxelGroup = groupManager.SpawnSavedGroup(data, gridPosition + vector3Int);
if (voxelGroup != null)
{
dictionary[num3] = voxelGroup.id;
}
}
if (list2.Count > 0)
{
NodeGraphManager instance = NodeGraphManager.Instance;
if (instance != null)
{
instance.ApplySavedGraph(positionOffset: new Vector3((float)gridPosition.x * voxelSize, (float)gridPosition.y * voxelSize, (float)gridPosition.z * voxelSize) - vector, nodeRecords: list2, wireRecords: saveData.wireRecords, clearExisting: false, broadcast: true, savedGroupIndexToRuntimeId: dictionary);
}
}
}
private static Dictionary<int, int> BuildGroupSaveIndexMap(IList<VoxelGroup> groups)
{
Dictionary<int, int> dictionary = new Dictionary<int, int>();
if (groups == null)
{
return dictionary;
}
for (int i = 0; i < groups.Count; i++)
{
VoxelGroup voxelGroup = groups[i];
if (voxelGroup != null)
{
dictionary[voxelGroup.id] = i;
}
}
return dictionary;
}
private void UpdatePreviewsForSelectedSlot()
{
previewRequestToken++;
if (mode == InventoryMode.Avatars)
{
ApplyEmptyAvatarPreview();
StartCoroutine(UpdateAvatarPreviews());
}
else
{
ApplyEmptyCreationPreview();
StartCoroutine(UpdateCreationPreview());
}
}
private IEnumerator UpdateAvatarPreviews()
{
EnsurePreviewGroups();
int token = previewRequestToken;
string userFolderId = BlockSpaceAccountPrefs.GetUserFolderId();
if (string.IsNullOrEmpty(userFolderId) || client == null)
{
yield break;
}
if (!TryGetSelectedId(out var id) || string.IsNullOrEmpty(id))
{
ApplyEmptyAvatarPreview();
yield break;
}
ApplyEmptyAvatarPreview();
string json = string.Empty;
yield return client.DownloadAvatar(userFolderId, id, delegate(string t)
{
json = t;
});
if (token != previewRequestToken)
{
yield break;
}
if (string.IsNullOrWhiteSpace(json) || !AvatarVoxelStorage.TryDeserialize(json, out var payload))
{
if (debugLogs)
{
string text = json ?? string.Empty;
if (text.Length > 120)
{
text = text.Substring(0, 120);
}
Debug.LogWarning($"InventoryController: avatar download/parse failed id={id} jsonLen={(json ?? string.Empty).Length} snippet={text}");
}
ApplyEmptyAvatarPreview();
yield break;
}
if (debugLogs)
{
int num = Mathf.Max(0, payload.headBody?.sizeX ?? 0) * Mathf.Max(0, payload.headBody?.sizeY ?? 0) * Mathf.Max(0, payload.headBody?.sizeZ ?? 0);
int num2 = Mathf.Max(0, payload.left?.sizeX ?? 0) * Mathf.Max(0, payload.left?.sizeY ?? 0) * Mathf.Max(0, payload.left?.sizeZ ?? 0);
int num3 = Mathf.Max(0, payload.right?.sizeX ?? 0) * Mathf.Max(0, payload.right?.sizeY ?? 0) * Mathf.Max(0, payload.right?.sizeZ ?? 0);
Debug.Log($"InventoryController: avatarId={id} headExpected={num} leftExpected={num2} rightExpected={num3} headB64Len={(payload.headBody?.voxelsBase64 ?? string.Empty).Length}");
}
ApplyAvatarPreviewPayload(payload);
}
private IEnumerator UpdateCreationPreview()
{
EnsurePreviewGroups();
int token = previewRequestToken;
string userFolderId = BlockSpaceAccountPrefs.GetUserFolderId();
if (string.IsNullOrEmpty(userFolderId) || client == null)
{
yield break;
}
if (!TryGetSelectedId(out var id) || string.IsNullOrEmpty(id))
{
ApplyEmptyCreationPreview();
yield break;
}
ApplyEmptyCreationPreview();
byte[] gzip = null;
yield return client.DownloadCreation(userFolderId, id, delegate(byte[] b)
{
gzip = b;
});
if (token == previewRequestToken)
{
if (gzip == null)
{
gzip = Array.Empty<byte>();
}
if (!WorldGroupsCodec.TryDecodeGzip(gzip, out WorldGroupsCodec.WorldSaveData saveData) || saveData.groupRecords == null || saveData.groupRecords.Count == 0)
{
ApplyEmptyCreationPreview();
}
else
{
ApplyCreationPreviewRecords(saveData.groupRecords);
}
}
}
private void ApplyCreationPreviewRecords(List<WorldGroupsCodec.WorldGroupRecord> records)
{
if (creationPreviewRoot == null || groupManager == null)
{
return;
}
EnsurePreviewGroups();
Vector3Int vector3Int = records[0].position;
Vector3Int vector3Int2 = records[0].position + records[0].size;
for (int i = 1; i < records.Count; i++)
{
Vector3Int position = records[i].position;
Vector3Int rhs = records[i].position + records[i].size;
vector3Int = Vector3Int.Min(vector3Int, position);
vector3Int2 = Vector3Int.Max(vector3Int2, rhs);
}
while (previewCreationGroups.Count < records.Count)
{
int count = previewCreationGroups.Count;
GameObject obj = new GameObject($"CreationGroupPreview_{count}");
obj.transform.SetParent(creationPreviewRoot, worldPositionStays: false);
VoxelGroup voxelGroup = obj.AddComponent<VoxelGroup>();
voxelGroup.id = (ushort)(62500 + count);
voxelGroup.SetOwner(groupManager);
voxelGroup.SetUseLocalGridTransform(useLocalGrid: true);
voxelGroup.SetColliderGeneration(enabled: false);
previewCreationGroups.Add(voxelGroup);
}
for (int j = records.Count; j < previewCreationGroups.Count; j++)
{
if (previewCreationGroups[j] != null)
{
previewCreationGroups[j].OverwriteData(Vector3Int.zero, 0, Vector3Int.zero, new byte[0], queueRebuild: false);
previewCreationGroups[j].SetVoxelSizeScale(1f, rebuild: false);
previewCreationGroups[j].RebuildMesh();
}
}
for (int k = 0; k < records.Count; k++)
{
WorldGroupsCodec.WorldGroupRecord worldGroupRecord = records[k];
VoxelGroup voxelGroup2 = previewCreationGroups[k];
if (!(voxelGroup2 == null))
{
Vector3Int gridPosition = worldGroupRecord.position - vector3Int;
voxelGroup2.SetUseLocalGridTransform(useLocalGrid: true);
voxelGroup2.SetColliderGeneration(enabled: false);
voxelGroup2.SetOwner(groupManager);
voxelGroup2.OverwriteData(gridPosition, worldGroupRecord.packedRotation, worldGroupRecord.size, worldGroupRecord.voxels, queueRebuild: false);
voxelGroup2.SetVoxelSizeScale(1f, rebuild: false);
voxelGroup2.RebuildMesh();
}
}
Vector3 vector = vector3Int2 - vector3Int;
float num = ((groupManager != null) ? groupManager.VoxelSize : 0.125f);
float num2 = Mathf.Max(vector.x, Mathf.Max(vector.y, vector.z)) * num;
float num3 = ((num2 > 0.0001f) ? Mathf.Min(1f, 3f / num2) : 1f);
creationPreviewRoot.localScale = Vector3.one * num3;
}
private void ApplyEmptyCreationPreview()
{
for (int i = 0; i < previewCreationGroups.Count; i++)
{
if (previewCreationGroups[i] != null)
{
previewCreationGroups[i].OverwriteData(Vector3Int.zero, 0, Vector3Int.zero, new byte[0], queueRebuild: false);
previewCreationGroups[i].RebuildMesh();
}
}
if (creationPreviewRoot != null)
{
creationPreviewRoot.localScale = Vector3.one;
}
}
private void ApplyAvatarPreviewPayload(AvatarVoxelPayload payload)
{
if (!(groupManager == null))
{
EnsurePreviewGroups();
if (previewHeadGroup != null)
{
AvatarVoxelStorage.ApplyGroupData(previewHeadGroup, payload.headBody, groupManager, useLocalGridTransform: true);
}
if (previewLeftGroup != null)
{
AvatarVoxelStorage.ApplyGroupData(previewLeftGroup, payload.left, groupManager, useLocalGridTransform: true);
}
if (previewRightGroup != null)
{
AvatarVoxelStorage.ApplyGroupData(previewRightGroup, payload.right, groupManager, useLocalGridTransform: true);
}
if (previewHeadGroup != null)
{
previewHeadGroup.SetVoxelSizeScale(0.5f, rebuild: false);
previewHeadGroup.RebuildMesh();
}
if (previewLeftGroup != null)
{
previewLeftGroup.SetVoxelSizeScale(0.5f, rebuild: false);
previewLeftGroup.RebuildMesh();
}
if (previewRightGroup != null)
{
previewRightGroup.SetVoxelSizeScale(0.5f, rebuild: false);
previewRightGroup.RebuildMesh();
}
}
}
private void ApplyEmptyAvatarPreview()
{
EnsurePreviewGroups();
if (!(previewHeadGroup == null))
{
previewHeadGroup.OverwriteData(Vector3Int.zero, 0, Vector3Int.zero, new byte[0], queueRebuild: false);
previewLeftGroup.OverwriteData(Vector3Int.zero, 0, Vector3Int.zero, new byte[0], queueRebuild: false);
previewRightGroup.OverwriteData(Vector3Int.zero, 0, Vector3Int.zero, new byte[0], queueRebuild: false);
previewHeadGroup.RebuildMesh();
previewLeftGroup.RebuildMesh();
previewRightGroup.RebuildMesh();
}
}
private void ResolveSceneReferences()
{
if (groupManager == null)
{
groupManager = ((VoxelGroupManager.Instance != null) ? VoxelGroupManager.Instance : UnityEngine.Object.FindObjectOfType<VoxelGroupManager>());
}
if (voxelBrush == null)
{
voxelBrush = ((VRVoxelBrush.Instance != null) ? VRVoxelBrush.Instance : UnityEngine.Object.FindObjectOfType<VRVoxelBrush>());
}
if (avatarsButton == null)
{
avatarsButton = FindCollider("Avatars");
}
if (creationsButton == null)
{
creationsButton = FindCollider("Creations");
}
if (backButton == null)
{
backButton = FindCollider("Back");
}
if (nextButton == null)
{
nextButton = FindCollider("Next");
}
if (saveButton == null)
{
saveButton = FindCollider("Save");
}
if (loadButton == null)
{
loadButton = FindCollider("Load");
}
if (creationPreviewRoot == null)
{
creationPreviewRoot = FindTransform("Preview");
}
if (previewRight == null)
{
previewRight = FindTransform("PrevR");
}
if (previewHead == null)
{
previewHead = FindTransform("PrevH");
}
if (previewLeft == null)
{
previewLeft = FindTransform("PrevL");
}
}
private Collider FindCollider(string name)
{
Transform transform = base.transform.Find("UI/" + name);
if (transform == null)
{
GameObject gameObject = GameObject.Find(name);
transform = ((gameObject != null) ? gameObject.transform : null);
}
if (!(transform != null))
{
return null;
}
return transform.GetComponent<Collider>();
}
private Transform FindTransform(string name)
{
if (string.IsNullOrEmpty(name))
{
return null;
}
if (transformCache.TryGetValue(name, out var value) && value != null)
{
return value;
}
Transform transform = base.transform.Find(name);
if (transform != null)
{
transformCache[name] = transform;
return transform;
}
Transform[] componentsInChildren = GetComponentsInChildren<Transform>(includeInactive: true);
foreach (Transform transform2 in componentsInChildren)
{
if (transform2 != null && string.Equals(transform2.name, name, StringComparison.OrdinalIgnoreCase))
{
transformCache[name] = transform2;
return transform2;
}
}
GameObject gameObject = GameObject.Find(name);
if (gameObject != null)
{
transformCache[name] = gameObject.transform;
return gameObject.transform;
}
Transform[] array = UnityEngine.Object.FindObjectsOfType<Transform>(includeInactive: true);
foreach (Transform transform3 in array)
{
if (transform3 != null && string.Equals(transform3.name, name, StringComparison.OrdinalIgnoreCase))
{
transformCache[name] = transform3;
return transform3;
}
}
return null;
}
private void EnsureButtonAudioAssigned()
{
if (!(buttonThockClip != null))
{
buttonThockClip = Resources.Load<AudioClip>("Thock");
}
}
private void EnsurePreviewGroups()
{
if (!(groupManager == null))
{
if (creationPreviewRoot == null)
{
creationPreviewRoot = FindTransform("Preview");
}
if (previewRight == null)
{
previewRight = FindTransform("PrevR");
}
if (previewHead == null)
{
previewHead = FindTransform("PrevH");
}
if (previewLeft == null)
{
previewLeft = FindTransform("PrevL");
}
if (creationPreviewRoot != null && previewCreationGroups.Count == 0)
{
GameObject gameObject = new GameObject("CreationGroupPreview_0");
gameObject.transform.SetParent(creationPreviewRoot, worldPositionStays: false);
previewCreationGroup = gameObject.AddComponent<VoxelGroup>();
previewCreationGroup.id = 62500;
previewCreationGroup.SetOwner(groupManager);
previewCreationGroup.SetUseLocalGridTransform(useLocalGrid: true);
previewCreationGroup.SetColliderGeneration(enabled: false);
previewCreationGroups.Add(previewCreationGroup);
}
else if (creationPreviewRoot == null && !warnedMissingCreationPreviewRoot)
{
warnedMissingCreationPreviewRoot = true;
Debug.LogWarning("InventoryController: could not find Creation Preview root named 'Preview'. Creation previews will be hidden until it exists.");
}
previewHeadGroup = EnsurePreviewGroupOn(previewHead, previewHeadGroup, 62000);
previewLeftGroup = EnsurePreviewGroupOn(previewLeft, previewLeftGroup, 62001);
previewRightGroup = EnsurePreviewGroupOn(previewRight, previewRightGroup, 62002);
if (!warnedMissingAvatarPreviewRoots && (previewHead == null || previewLeft == null || previewRight == null))
{
warnedMissingAvatarPreviewRoots = true;
Debug.LogWarning("InventoryController: could not find one or more avatar preview roots ('PrevH', 'PrevL', 'PrevR'). Avatar previews will be hidden until they exist.");
}
}
}
private VoxelGroup EnsurePreviewGroupOn(Transform root, VoxelGroup existing, ushort id)
{
if (root == null || groupManager == null)
{
return existing;
}
VoxelGroup voxelGroup = ((existing != null) ? existing : root.GetComponent<VoxelGroup>());
if (voxelGroup == null)
{
voxelGroup = root.gameObject.AddComponent<VoxelGroup>();
}
voxelGroup.id = id;
voxelGroup.SetOwner(groupManager);
voxelGroup.SetUseLocalGridTransform(useLocalGrid: true);
voxelGroup.SetColliderGeneration(enabled: false);
voxelGroup.SetVoxelSizeScale(0.5f);
return voxelGroup;
}
private void HandleButton(Collider button, ref bool insidePrevious, Action action)
{
bool flag = IsHandInside(button, (voxelBrush != null) ? voxelBrush.LeftHandAnchor : null) || IsHandInside(button, (voxelBrush != null) ? voxelBrush.RightHandAnchor : null);
if (flag && !insidePrevious)
{
PlayButtonEnterAnimation(button);
if (Time.unscaledTime >= nextButtonPressTime)
{
nextButtonPressTime = Time.unscaledTime + 0.2f;
PlayButtonClickFeedback(button);
action?.Invoke();
}
}
insidePrevious = flag;
}
private static bool IsHandInside(Collider button, Transform hand)
{
if (button == null || hand == null)
{
return false;
}
return button.bounds.Contains(hand.position);
}
private void PlayButtonEnterAnimation(Collider button)
{
if (!(button == null))
{
Transform transform = button.transform;
if (!buttonBaseScales.TryGetValue(transform, out var value))
{
value = transform.localScale;
buttonBaseScales.Add(transform, value);
}
if (buttonAnimationCoroutines.TryGetValue(transform, out var value2) && value2 != null)
{
StopCoroutine(value2);
}
transform.localScale = value;
buttonAnimationCoroutines[transform] = StartCoroutine(AnimateButtonScale(transform, value));
}
}
private IEnumerator AnimateButtonScale(Transform buttonTransform, Vector3 baseScale)
{
if (!(buttonTransform == null))
{
float startTime = Time.unscaledTime;
while (Time.unscaledTime - startTime < 0.08f)
{
float t = Mathf.Clamp01((Time.unscaledTime - startTime) / 0.08f);
buttonTransform.localScale = Vector3.Lerp(baseScale, baseScale * 0.84f, t);
yield return null;
}
startTime = Time.unscaledTime;
while (Time.unscaledTime - startTime < 0.12f)
{
float num = Mathf.Clamp01((Time.unscaledTime - startTime) / 0.12f);
float num2 = Mathf.Sin(num * MathF.PI);
Vector3 a = Vector3.Lerp(baseScale * 0.84f, baseScale, num);
buttonTransform.localScale = Vector3.LerpUnclamped(a, baseScale * 1.04f, num2 * 0.15f);
yield return null;
}
buttonTransform.localScale = baseScale;
}
}
private void PlayButtonClickFeedback(Collider button)
{
if (!(buttonThockClip == null) && !(button == null))
{
AudioSource.PlayClipAtPoint(buttonThockClip, button.bounds.center, 1f);
}
}
private void ResetAllButtonScales()
{
foreach (KeyValuePair<Transform, Vector3> buttonBaseScale in buttonBaseScales)
{
if (buttonBaseScale.Key != null)
{
buttonBaseScale.Key.localScale = buttonBaseScale.Value;
}
}
}
private bool TryGetSelectedId(out string id)
{
id = string.Empty;
if (slotIndex < 0 || slotIndex >= ids.Count)
{
return false;
}
id = ids[slotIndex] ?? string.Empty;
return true;
}
private string GetOrCreateSelectedId()
{
if (!TryGetSelectedId(out var id))
{
return string.Empty;
}
if (!string.IsNullOrEmpty(id))
{
return id;
}
string text = MakeNewId();
ids[slotIndex] = text;
return text;
}
private static string MakeNewId()
{
return DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
}
private bool TryGetSpawnGridPosition(out Vector3Int gridPosition)
{
gridPosition = Vector3Int.zero;
VRVoxelBrush vRVoxelBrush = ((voxelBrush != null) ? voxelBrush : VRVoxelBrush.Instance);
if (vRVoxelBrush != null && vRVoxelBrush.TryGetSpawnGridPosition(out gridPosition))
{
return true;
}
float num = ((groupManager != null) ? groupManager.VoxelSize : 0.125f);
Vector3 vector = ((Camera.main != null) ? Camera.main.transform.position : base.transform.position) + ((Camera.main != null) ? Camera.main.transform.forward : base.transform.forward).normalized * 2f;
gridPosition = new Vector3Int(Mathf.RoundToInt(vector.x / num), Mathf.RoundToInt(vector.y / num), Mathf.RoundToInt(vector.z / num));
return true;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 51879f2ce4c4957e1de2b069a9ed86a6
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,77 @@
using UnityEngine;
namespace BlockSpace.Voxels
{
[DisallowMultipleComponent]
public sealed class NametagBillboard : MonoBehaviour
{
[SerializeField]
private float heightOffsetMeters = 0.2f;
[SerializeField]
private float minLocalY = 0.5f;
private Transform headTransform;
private VoxelGroup headGroup;
private Camera cachedCamera;
private void Awake()
{
ResolveReferences();
}
private void LateUpdate()
{
ResolveReferences();
Camera camera = ((cachedCamera != null && cachedCamera.isActiveAndEnabled) ? cachedCamera : null);
if ((object)camera == null)
{
camera = Camera.main;
}
if (camera == null)
{
camera = Object.FindObjectOfType<Camera>();
}
if (camera != null)
{
cachedCamera = camera;
Vector3 forward = camera.transform.position - base.transform.position;
if (forward.sqrMagnitude > 0.0001f)
{
base.transform.rotation = Quaternion.LookRotation(forward, Vector3.up);
}
}
if (!(headTransform == null))
{
float y = minLocalY;
if (headGroup != null && headGroup.voxels != null && headGroup.voxels.Length != 0)
{
Bounds worldBounds = headGroup.GetWorldBounds();
Vector3 position = new Vector3(base.transform.position.x, worldBounds.max.y + heightOffsetMeters, base.transform.position.z);
y = headTransform.InverseTransformPoint(position).y;
}
Vector3 localPosition = base.transform.localPosition;
localPosition.y = Mathf.Max(minLocalY, y);
base.transform.localPosition = localPosition;
}
}
private void ResolveReferences()
{
if (headTransform == null)
{
headTransform = base.transform.parent;
}
if (!(headTransform == null) && !(headGroup != null))
{
Transform transform = headTransform.Find("Avatar Group - Head");
if (!(transform == null))
{
headGroup = transform.GetComponent<VoxelGroup>();
}
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 33d0ee93502c68f445810d9cc05b7ff4
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,108 @@
using System.Collections;
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.Management;
using UnityEngine.XR.OpenXR;
namespace BlockSpace.Voxels
{
internal static class OpenXRDesktopFallback
{
private sealed class OpenXRDesktopFallbackRunner : MonoBehaviour
{
private bool started;
private IEnumerator Start()
{
if (started)
{
yield break;
}
started = true;
float waitTime = 2f;
float timer = 0f;
while (timer < waitTime)
{
if (XRSettings.isDeviceActive)
{
Debug.Log("XR already active. Skipping fallback.");
yield break;
}
timer += Time.unscaledDeltaTime;
yield return null;
}
XRGeneralSettings instance = XRGeneralSettings.Instance;
XRManagerSettings manager = ((instance != null) ? instance.Manager : null);
if (manager == null)
{
Debug.LogWarning("No XR Manager found. Staying in desktop mode.");
yield break;
}
if (manager.activeLoader != null || XRSettings.isDeviceActive)
{
Debug.Log("XR initialized during wait. No fallback needed.");
yield break;
}
Debug.Log("XR not initialized. Attempting manual initialization...");
yield return manager.InitializeLoader();
if (manager.activeLoader != null)
{
manager.StartSubsystems();
if (XRSettings.isDeviceActive)
{
Debug.Log("XR successfully initialized via fallback.");
}
else
{
Debug.LogWarning("Loader started but no XR device active.");
}
}
else
{
Debug.Log("OpenXR failed to initialize. Running in desktop mode.");
}
}
}
private static bool installed;
private static bool runnerCreated;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void Install()
{
if (!installed)
{
installed = true;
OpenXRRuntime.retryInitializationOnFormFactorErrors = false;
OpenXRRuntime.wantsToQuit += PreventForcedQuit;
OpenXRRuntime.wantsToRestart += PreventForcedRestart;
}
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void CreateRunner()
{
if (!runnerCreated)
{
runnerCreated = true;
GameObject gameObject = new GameObject("OpenXRDesktopFallback");
Object.DontDestroyOnLoad(gameObject);
gameObject.hideFlags = HideFlags.HideAndDontSave;
gameObject.AddComponent<OpenXRDesktopFallbackRunner>();
}
}
private static bool PreventForcedQuit()
{
Debug.LogWarning("OpenXR requested application quit. Ignoring for desktop fallback.");
return false;
}
private static bool PreventForcedRestart()
{
Debug.LogWarning("OpenXR requested restart. Ignoring for desktop fallback.");
return false;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 4943cfadeb0ea29dbdef99c691cf7da2
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,40 @@
namespace BlockSpace.Voxels
{
public static class PackedVoxel
{
public const byte Air = 0;
public static byte Create(byte colorIndex, VoxelMaterial material)
{
colorIndex &= 0x3F;
if (material == VoxelMaterial.Invisible && colorIndex == 63)
{
colorIndex = 62;
}
return (byte)((byte)(((uint)material << 6) | colorIndex) + 1);
}
public static bool IsAir(byte voxel)
{
return voxel == 0;
}
public static byte GetColorIndex(byte voxel)
{
if (voxel != 0)
{
return (byte)((voxel - 1) & 0x3F);
}
return 0;
}
public static VoxelMaterial GetMaterial(byte voxel)
{
if (voxel != 0)
{
return (VoxelMaterial)((voxel - 1 >> 6) & 3);
}
return VoxelMaterial.Lit;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1249f5116b97a654b856eff71125c998
@@ -0,0 +1,126 @@
using UnityEngine;
namespace BlockSpace.Voxels
{
public static class Palette
{
public const int ColorCount = 64;
public static readonly Color32[] palette = BuildPalette();
private static readonly Color32 InvisibleCreatorPreviewColor = new Color32(64, 160, byte.MaxValue, 96);
public static Color32 GetColor(byte index)
{
return palette[index & 0x3F];
}
public static Color32 GetPreviewColor(byte index, VoxelMaterial material)
{
return material switch
{
VoxelMaterial.Transparent => WithAlpha(GetRenderColor(index), 112),
VoxelMaterial.Invisible => InvisibleCreatorPreviewColor,
_ => WithAlpha(GetRenderColor(index), byte.MaxValue),
};
}
public static Color32 GetMaterialPreviewColor(byte index, VoxelMaterial material)
{
return material switch
{
VoxelMaterial.Transparent => WithAlpha(GetColor(index), 112),
VoxelMaterial.Invisible => InvisibleCreatorPreviewColor,
_ => WithAlpha(GetColor(index), byte.MaxValue),
};
}
public static Color32 GetRenderColor(byte index)
{
return ToRenderColor(GetColor(index));
}
private static Color32 WithAlpha(Color32 color, byte alpha)
{
return new Color32(color.r, color.g, color.b, alpha);
}
private static Color32 ToRenderColor(Color32 color)
{
Color linear = ((Color)color).linear;
linear.a = (float)(int)color.a / 255f;
return linear;
}
private static Color32[] BuildPalette()
{
return new Color32[64]
{
new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue),
new Color32(217, 217, 217, byte.MaxValue),
new Color32(179, 179, 179, byte.MaxValue),
new Color32(140, 140, 140, byte.MaxValue),
new Color32(102, 102, 102, byte.MaxValue),
new Color32(64, 64, 64, byte.MaxValue),
new Color32(31, 31, 31, byte.MaxValue),
new Color32(0, 0, 0, byte.MaxValue),
new Color32(byte.MaxValue, 225, 225, byte.MaxValue),
new Color32(byte.MaxValue, 154, 154, byte.MaxValue),
new Color32(byte.MaxValue, 79, 79, byte.MaxValue),
new Color32(201, 47, 47, byte.MaxValue),
new Color32(122, 31, 31, byte.MaxValue),
new Color32(byte.MaxValue, 107, 138, byte.MaxValue),
new Color32(217, 58, 99, byte.MaxValue),
new Color32(122, 18, 52, byte.MaxValue),
new Color32(byte.MaxValue, 224, 184, byte.MaxValue),
new Color32(byte.MaxValue, 180, 92, byte.MaxValue),
new Color32(240, 122, 36, byte.MaxValue),
new Color32(184, 74, 27, byte.MaxValue),
new Color32(122, 52, 24, byte.MaxValue),
new Color32(201, 138, 74, byte.MaxValue),
new Color32(138, 90, 50, byte.MaxValue),
new Color32(74, 42, 24, byte.MaxValue),
new Color32(byte.MaxValue, 244, 184, byte.MaxValue),
new Color32(byte.MaxValue, 224, 102, byte.MaxValue),
new Color32(byte.MaxValue, 208, 46, byte.MaxValue),
new Color32(201, 154, 22, byte.MaxValue),
new Color32(122, 90, 10, byte.MaxValue),
new Color32(230, 210, 138, byte.MaxValue),
new Color32(184, 164, 90, byte.MaxValue),
new Color32(107, 98, 50, byte.MaxValue),
new Color32(216, byte.MaxValue, 200, byte.MaxValue),
new Color32(157, byte.MaxValue, 122, byte.MaxValue),
new Color32(82, 217, 79, byte.MaxValue),
new Color32(47, 158, 68, byte.MaxValue),
new Color32(31, 95, 42, byte.MaxValue),
new Color32(122, 214, 109, byte.MaxValue),
new Color32(79, 143, 58, byte.MaxValue),
new Color32(42, 74, 31, byte.MaxValue),
new Color32(200, byte.MaxValue, byte.MaxValue, byte.MaxValue),
new Color32(122, byte.MaxValue, byte.MaxValue, byte.MaxValue),
new Color32(54, 207, 224, byte.MaxValue),
new Color32(31, 143, 163, byte.MaxValue),
new Color32(20, 88, 102, byte.MaxValue),
new Color32(138, 199, byte.MaxValue, byte.MaxValue),
new Color32(58, 141, byte.MaxValue, byte.MaxValue),
new Color32(30, 63, 153, byte.MaxValue),
new Color32(230, 209, byte.MaxValue, byte.MaxValue),
new Color32(185, 138, byte.MaxValue, byte.MaxValue),
new Color32(138, 77, byte.MaxValue, byte.MaxValue),
new Color32(90, 43, 184, byte.MaxValue),
new Color32(48, 26, 107, byte.MaxValue),
new Color32(byte.MaxValue, 184, 240, byte.MaxValue),
new Color32(byte.MaxValue, 95, 210, byte.MaxValue),
new Color32(158, 38, 122, byte.MaxValue),
new Color32(244, 240, 216, byte.MaxValue),
new Color32(200, 191, 160, byte.MaxValue),
new Color32(143, 130, 102, byte.MaxValue),
new Color32(94, 85, 72, byte.MaxValue),
new Color32(139, 184, 200, byte.MaxValue),
new Color32(95, 120, 144, byte.MaxValue),
new Color32(62, 74, 89, byte.MaxValue),
new Color32(184, 115, 51, byte.MaxValue)
};
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 52a95181c30cbdde313319dfc15976f8
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,414 @@
using System.Collections;
using BlockSpace.Account;
using BlockSpace.Worlds;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
namespace BlockSpace.Voxels
{
[DisallowMultipleComponent]
public sealed class PhotonVoxelAvatar : MonoBehaviourPunCallbacks
{
private const string HeadAnchorName = "Avatar Group - Head";
private const string LeftAnchorName = "Avatar Group - Left";
private const string RightAnchorName = "Avatar Group - Right";
private const float AvatarVoxelSizeScale = 0.5f;
private const string InventoryBackendBaseUrl = "https://blockspacebackend-production.up.railway.app";
[SerializeField]
private Transform headTransform;
[SerializeField]
private Transform leftHandTransform;
[SerializeField]
private Transform rightHandTransform;
[SerializeField]
private Transform headBodyAnchor;
[SerializeField]
private Transform leftAvatarAnchor;
[SerializeField]
private Transform rightAvatarAnchor;
private VoxelGroupManager groupManager;
private VoxelGroup headBodyGroup;
private VoxelGroup leftGroup;
private VoxelGroup rightGroup;
private bool runtimeGroupsReady;
private bool localAvatarLoaded;
private bool roomSyncScheduled;
private string pendingAvatarPayloadJson;
private Coroutine roomSyncCoroutine;
private Coroutine localAvatarLoadCoroutine;
public static PhotonVoxelAvatar LocalOwnedInstance { get; private set; }
public bool HasRuntimeGroups => runtimeGroupsReady;
public void Configure(Transform head, Transform leftHand, Transform rightHand)
{
if (head != null)
{
headTransform = head;
}
if (leftHand != null)
{
leftHandTransform = leftHand;
}
if (rightHand != null)
{
rightHandTransform = rightHand;
}
ResolveAnchors();
TryInitializeRuntimeGroups();
}
public void ApplyAndBroadcastAvatar(AvatarVoxelPayload payload)
{
ApplyAvatarPayload(payload, saveLocally: true, PhotonNetwork.InRoom);
}
public void ApplyAvatarPayload(AvatarVoxelPayload payload, bool saveLocally, bool broadcastToOthers)
{
if (!TryInitializeRuntimeGroups())
{
pendingAvatarPayloadJson = AvatarVoxelStorage.Serialize(payload);
if (saveLocally)
{
AvatarVoxelStorage.SaveLocal(payload);
}
return;
}
AvatarVoxelPayload payload2 = payload ?? AvatarVoxelStorage.CreateEmptyPayload();
AvatarVoxelStorage.ApplyPayload(payload2, headBodyGroup, leftGroup, rightGroup, groupManager, useLocalGridTransform: true);
if (base.photonView.IsMine)
{
localAvatarLoaded = true;
}
if (saveLocally && base.photonView.IsMine)
{
AvatarVoxelStorage.SaveLocal(payload2);
}
if (broadcastToOthers && base.photonView.IsMine && PhotonNetwork.InRoom)
{
SendAvatarDataToOthers(payload2);
}
}
public AvatarVoxelPayload CaptureRuntimePayload()
{
return AvatarVoxelStorage.CapturePayload(headBodyGroup, leftGroup, rightGroup);
}
private void Awake()
{
if (base.photonView.IsMine)
{
LocalOwnedInstance = this;
}
ResolveAnchors();
}
private new void OnEnable()
{
if (base.photonView.IsMine)
{
LocalOwnedInstance = this;
}
roomSyncScheduled = false;
TryInitializeRuntimeGroups();
WorldLoadingScreenController.LoadingFinished += OnWorldLoadingFinished;
TryScheduleRoomSync();
}
private new void OnDisable()
{
WorldLoadingScreenController.LoadingFinished -= OnWorldLoadingFinished;
if (roomSyncCoroutine != null)
{
StopCoroutine(roomSyncCoroutine);
roomSyncCoroutine = null;
}
if (localAvatarLoadCoroutine != null)
{
StopCoroutine(localAvatarLoadCoroutine);
localAvatarLoadCoroutine = null;
}
}
private void OnDestroy()
{
if (LocalOwnedInstance == this)
{
LocalOwnedInstance = null;
}
}
private void Update()
{
if (!runtimeGroupsReady)
{
TryInitializeRuntimeGroups();
}
if (runtimeGroupsReady && !string.IsNullOrEmpty(pendingAvatarPayloadJson))
{
ApplyPendingAvatarPayload();
}
if (base.photonView.IsMine && runtimeGroupsReady && !localAvatarLoaded && !IsWaitingForLoadingScreen())
{
ApplyLocalAvatarAfterLoading();
}
if (base.photonView.IsMine)
{
TryScheduleRoomSync();
}
}
public override void OnJoinedRoom()
{
roomSyncScheduled = false;
TryScheduleRoomSync();
}
public override void OnLeftRoom()
{
roomSyncScheduled = false;
}
[PunRPC]
private void RPC_SendAvatarData(string payloadJson)
{
if (TryInitializeRuntimeGroups())
{
ApplySerializedAvatarPayload(payloadJson);
}
else
{
pendingAvatarPayloadJson = payloadJson;
}
}
[PunRPC]
private void RPC_RequestAvatarData(int requestingActorNumber)
{
if (!PhotonNetwork.InRoom)
{
return;
}
PhotonVoxelAvatar localOwnedInstance = LocalOwnedInstance;
if (!(localOwnedInstance == null) && localOwnedInstance.photonView.IsMine)
{
Player player = PhotonNetwork.CurrentRoom?.GetPlayer(requestingActorNumber);
if (player != null && player != PhotonNetwork.LocalPlayer)
{
localOwnedInstance.SendAvatarDataToPlayer(player, localOwnedInstance.CaptureRuntimePayload());
}
}
}
private bool TryInitializeRuntimeGroups()
{
if (runtimeGroupsReady)
{
return true;
}
if (groupManager == null)
{
groupManager = ((VoxelGroupManager.Instance != null) ? VoxelGroupManager.Instance : Object.FindObjectOfType<VoxelGroupManager>());
}
ResolveAnchors();
if (groupManager == null || headBodyAnchor == null || leftAvatarAnchor == null || rightAvatarAnchor == null)
{
return false;
}
headBodyGroup = PrepareRuntimeGroup(headBodyAnchor.gameObject, headBodyGroup, GetGroupIdBase());
leftGroup = PrepareRuntimeGroup(leftAvatarAnchor.gameObject, leftGroup, (ushort)(GetGroupIdBase() + 1));
rightGroup = PrepareRuntimeGroup(rightAvatarAnchor.gameObject, rightGroup, (ushort)(GetGroupIdBase() + 2));
runtimeGroupsReady = headBodyGroup != null && leftGroup != null && rightGroup != null;
return runtimeGroupsReady;
}
private VoxelGroup PrepareRuntimeGroup(GameObject groupObject, VoxelGroup existingGroup, ushort groupId)
{
if (groupObject == null || groupManager == null)
{
return null;
}
VoxelGroup voxelGroup = ((existingGroup != null) ? existingGroup : groupObject.GetComponent<VoxelGroup>());
if (voxelGroup == null)
{
voxelGroup = groupObject.AddComponent<VoxelGroup>();
}
AvatarVoxelStorage.ApplyGroupData(voxelGroup, AvatarVoxelStorage.CaptureGroup(voxelGroup), groupManager, useLocalGridTransform: true);
voxelGroup.SetVoxelSizeScale(0.5f);
voxelGroup.id = groupId;
return voxelGroup;
}
private void ResolveAnchors()
{
if (headBodyAnchor == null && headTransform != null)
{
headBodyAnchor = headTransform.Find("Avatar Group - Head");
}
if (leftAvatarAnchor == null && leftHandTransform != null)
{
leftAvatarAnchor = leftHandTransform.Find("Avatar Group - Left");
}
if (rightAvatarAnchor == null && rightHandTransform != null)
{
rightAvatarAnchor = rightHandTransform.Find("Avatar Group - Right");
}
}
private void TryScheduleRoomSync()
{
if (base.photonView.IsMine && PhotonNetwork.InRoom && runtimeGroupsReady && localAvatarLoaded && !roomSyncScheduled && base.photonView.ViewID != 0)
{
roomSyncScheduled = true;
if (roomSyncCoroutine != null)
{
StopCoroutine(roomSyncCoroutine);
}
roomSyncCoroutine = StartCoroutine(SendRoomAvatarSync());
}
}
private void OnWorldLoadingFinished()
{
if (base.photonView.IsMine)
{
TryInitializeRuntimeGroups();
if (runtimeGroupsReady)
{
ApplyLocalAvatarAfterLoading();
}
}
}
private bool IsWaitingForLoadingScreen()
{
WorldLoadingScreenController instance = WorldLoadingScreenController.Instance;
if (instance != null)
{
return instance.IsLoading;
}
return false;
}
private void ApplyLocalAvatarAfterLoading()
{
if (localAvatarLoadCoroutine == null)
{
localAvatarLoadCoroutine = StartCoroutine(ApplyLocalAvatarAfterLoadingRoutine());
}
}
private IEnumerator ApplyLocalAvatarAfterLoadingRoutine()
{
AvatarVoxelPayload payload = null;
bool saveLoadedPayload = false;
string playerId = BlockSpaceAccountPrefs.GetUserFolderId();
if (!string.IsNullOrEmpty(playerId))
{
InventoryBackendClient client = new InventoryBackendClient("https://blockspacebackend-production.up.railway.app");
InventoryBackendClient.ListResponse listResponse = null;
yield return client.ListAvatars(playerId, delegate(InventoryBackendClient.ListResponse response)
{
listResponse = response;
});
string text = ((listResponse != null) ? listResponse.equippedId : string.Empty);
if (!string.IsNullOrEmpty(text))
{
string json = string.Empty;
yield return client.DownloadAvatar(playerId, text, delegate(string text2)
{
json = text2;
});
if (!string.IsNullOrWhiteSpace(json) && AvatarVoxelStorage.TryDeserialize(json, out var payload2))
{
payload = payload2;
saveLoadedPayload = true;
}
}
}
if (payload == null)
{
payload = AvatarVoxelStorage.LoadLocalOrDefault();
}
ApplyAvatarPayload(payload, saveLoadedPayload, broadcastToOthers: false);
localAvatarLoaded = true;
localAvatarLoadCoroutine = null;
TryScheduleRoomSync();
}
private IEnumerator SendRoomAvatarSync()
{
yield return null;
if (!base.photonView.IsMine || !PhotonNetwork.InRoom)
{
roomSyncCoroutine = null;
yield break;
}
AvatarVoxelPayload payload = CaptureRuntimePayload();
SendAvatarDataToOthers(payload);
base.photonView.RPC("RPC_RequestAvatarData", RpcTarget.Others, PhotonNetwork.LocalPlayer.ActorNumber);
roomSyncCoroutine = null;
}
private void SendAvatarDataToOthers(AvatarVoxelPayload payload)
{
if (PhotonNetwork.InRoom && base.photonView.ViewID != 0)
{
base.photonView.RPC("RPC_SendAvatarData", RpcTarget.Others, AvatarVoxelStorage.Serialize(payload));
}
}
private void SendAvatarDataToPlayer(Player targetPlayer, AvatarVoxelPayload payload)
{
if (targetPlayer != null && PhotonNetwork.InRoom && base.photonView.ViewID != 0)
{
base.photonView.RPC("RPC_SendAvatarData", targetPlayer, AvatarVoxelStorage.Serialize(payload));
}
}
private void ApplySerializedAvatarPayload(string payloadJson)
{
if (!AvatarVoxelStorage.TryDeserialize(payloadJson, out var payload))
{
payload = AvatarVoxelStorage.CreateEmptyPayload();
}
ApplyAvatarPayload(payload, saveLocally: false, broadcastToOthers: false);
}
private void ApplyPendingAvatarPayload()
{
string payloadJson = pendingAvatarPayloadJson;
pendingAvatarPayloadJson = null;
ApplySerializedAvatarPayload(payloadJson);
}
private ushort GetGroupIdBase()
{
int num = ((base.photonView != null && base.photonView.Owner != null) ? base.photonView.Owner.ActorNumber : 0);
return (ushort)Mathf.Clamp(60000 + num * 8, 60000, 65520);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 0289603f75f3e33ed26196b83763b1f8
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,407 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using BlockSpace.Account;
using Photon.Pun;
using TMPro;
using UnityEngine;
using UnityEngine.Networking;
namespace BlockSpace.Voxels
{
[DisallowMultipleComponent]
public sealed class RandomLoginCodeButton : MonoBehaviour
{
[Serializable]
private sealed class LinkStatusRequest
{
public string code;
}
[Serializable]
private sealed class LinkStatusResponse
{
public bool ok;
public bool exists;
public bool claimed;
public bool expired;
public string code;
public string playerId;
public DiscordInfo discord;
}
[Serializable]
private sealed class DiscordInfo
{
public string id;
public string username;
public string globalName;
public string displayName;
}
private const float ButtonPressCooldown = 0.2f;
private const float ButtonShrinkDuration = 0.08f;
private const float ButtonSettleDuration = 0.12f;
private const float ButtonPressedScale = 0.84f;
private const float ButtonOvershootScale = 1.04f;
private const string ButtonThockResourcePath = "Thock";
private const string DefaultCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
[Header("Button")]
[SerializeField]
private Collider buttonCollider;
[SerializeField]
private VRVoxelBrush voxelBrush;
[Header("Code Output")]
[SerializeField]
private TMP_Text codeText;
[SerializeField]
private int codeLength = 6;
[SerializeField]
private string allowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
[SerializeField]
private bool generateOnStart;
[Header("Discord Link (optional)")]
[SerializeField]
private bool pollForDiscordLink = true;
[SerializeField]
private string backendBaseUrl = "https://blockspacebackend-production.up.railway.app";
[SerializeField]
private float pollIntervalSeconds = 2f;
[SerializeField]
private float pollTimeoutSeconds = 180f;
[Header("Audio")]
[SerializeField]
private AudioClip buttonThockClip;
private readonly Dictionary<Transform, Vector3> buttonBaseScales = new Dictionary<Transform, Vector3>();
private readonly Dictionary<Transform, Coroutine> buttonAnimationCoroutines = new Dictionary<Transform, Coroutine>();
private bool insidePrevious;
private float nextButtonPressTime;
private string currentCode = "";
private Coroutine pollCoroutine;
public string CurrentCode => currentCode;
private void Awake()
{
ResolveReferences();
EnsureButtonAudioAssigned();
if (generateOnStart)
{
GenerateAndShowCode();
}
}
private void OnDisable()
{
StopAllCoroutines();
ResetAllButtonScales();
}
private void Update()
{
ResolveReferences();
HandleButton();
}
private void ResolveReferences()
{
if (buttonCollider == null)
{
buttonCollider = GetComponent<Collider>();
}
if (voxelBrush == null)
{
voxelBrush = ((VRVoxelBrush.Instance != null) ? VRVoxelBrush.Instance : UnityEngine.Object.FindObjectOfType<VRVoxelBrush>());
}
if (codeText == null)
{
codeText = GetComponentInChildren<TMP_Text>(includeInactive: true);
}
}
private void HandleButton()
{
bool flag = IsHandInside(buttonCollider, (voxelBrush != null) ? voxelBrush.LeftHandAnchor : null) || IsHandInside(buttonCollider, (voxelBrush != null) ? voxelBrush.RightHandAnchor : null);
if (flag && !insidePrevious)
{
PlayButtonEnterAnimation(buttonCollider);
if (Time.unscaledTime >= nextButtonPressTime)
{
nextButtonPressTime = Time.unscaledTime + 0.2f;
PlayButtonClickFeedback(buttonCollider);
GenerateAndShowCode();
}
}
insidePrevious = flag;
}
public void GenerateAndShowCode()
{
currentCode = GenerateCode();
if (codeText != null)
{
codeText.text = currentCode;
}
else
{
Debug.LogWarning("[RandomLoginCodeButton] No TMP_Text assigned.");
}
Debug.Log("[RandomLoginCodeButton] Generated code: " + currentCode);
if (pollForDiscordLink)
{
if (pollCoroutine != null)
{
StopCoroutine(pollCoroutine);
pollCoroutine = null;
}
pollCoroutine = StartCoroutine(PollDiscordLink(currentCode));
}
}
private string GenerateCode()
{
int num = Mathf.Max(6, codeLength);
string text = (string.IsNullOrEmpty(allowedCharacters) ? "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" : allowedCharacters.ToUpperInvariant());
char[] array = new char[num];
for (int i = 0; i < array.Length; i++)
{
int index = UnityEngine.Random.Range(0, text.Length);
array[i] = text[index];
}
return new string(array);
}
private IEnumerator PollDiscordLink(string code)
{
if (string.IsNullOrWhiteSpace(code))
{
yield break;
}
string baseUrl = (backendBaseUrl ?? string.Empty).Trim();
if (string.IsNullOrEmpty(baseUrl))
{
Debug.LogWarning("[RandomLoginCodeButton] No backendBaseUrl set; cannot poll link status.");
yield break;
}
float start = Time.realtimeSinceStartup;
while (Time.realtimeSinceStartup - start <= Mathf.Max(1f, pollTimeoutSeconds))
{
string url = baseUrl.TrimEnd('/') + "/api/link-codes/status";
string s = JsonUtility.ToJson(new LinkStatusRequest
{
code = code
});
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)
{
LinkStatusResponse linkStatusResponse = null;
try
{
linkStatusResponse = JsonUtility.FromJson<LinkStatusResponse>(request.downloadHandler.text);
}
catch
{
}
if (linkStatusResponse != null && linkStatusResponse.ok && linkStatusResponse.exists && linkStatusResponse.claimed)
{
string text = linkStatusResponse.playerId ?? string.Empty;
string text2 = ((linkStatusResponse.discord != null) ? linkStatusResponse.discord.id : string.Empty);
string text3 = ((linkStatusResponse.discord != null) ? linkStatusResponse.discord.displayName : string.Empty);
if (string.IsNullOrEmpty(text3))
{
text3 = ((linkStatusResponse.discord != null) ? linkStatusResponse.discord.globalName : string.Empty);
}
if (string.IsNullOrEmpty(text3))
{
text3 = ((linkStatusResponse.discord != null) ? linkStatusResponse.discord.username : string.Empty);
}
string userFolderId = BlockSpaceAccountPrefs.GetUserFolderId();
BlockSpaceAccountPrefs.SetLinkedAccount(text, text2, text3);
TryMigrateAvatarPayload(userFolderId, text);
if (!string.IsNullOrEmpty(text3))
{
PlayerPrefs.SetString("Username", text3);
PlayerPrefs.Save();
if (PhotonNetwork.LocalPlayer != null)
{
PhotonNetwork.LocalPlayer.NickName = text3;
}
}
Debug.Log("[RandomLoginCodeButton] Linked to Discord user '" + text3 + "' (" + text2 + ").");
pollCoroutine = null;
yield break;
}
}
else if (request.result != UnityWebRequest.Result.ProtocolError)
{
Debug.LogWarning($"[RandomLoginCodeButton] Link poll failed ({request.result}): {request.error}");
}
}
yield return new WaitForSecondsRealtime(Mathf.Clamp(pollIntervalSeconds, 0.25f, 30f));
}
Debug.LogWarning("[RandomLoginCodeButton] Link poll timed out; generate a new code if needed.");
pollCoroutine = null;
}
private static void TryMigrateAvatarPayload(string fromUserId, string toUserId)
{
if (string.IsNullOrEmpty(fromUserId) || string.IsNullOrEmpty(toUserId) || fromUserId == toUserId)
{
return;
}
try
{
string text = "avatar_payload.json";
string text2 = Path.Combine(Application.persistentDataPath, "BlockSpace", "Users", fromUserId, text);
string text3 = Path.Combine(Application.persistentDataPath, "BlockSpace", "Users", toUserId, text);
if (File.Exists(text2) && !File.Exists(text3))
{
string directoryName = Path.GetDirectoryName(text3);
if (!string.IsNullOrEmpty(directoryName))
{
Directory.CreateDirectory(directoryName);
}
File.Copy(text2, text3, overwrite: false);
}
}
catch (Exception ex)
{
Debug.LogWarning("[RandomLoginCodeButton] Avatar migration failed: " + ex.Message);
}
}
private void PlayButtonEnterAnimation(Collider button)
{
if (!(button == null))
{
Transform transform = button.transform;
if (!buttonBaseScales.TryGetValue(transform, out var value))
{
value = transform.localScale;
buttonBaseScales.Add(transform, value);
}
if (buttonAnimationCoroutines.TryGetValue(transform, out var value2) && value2 != null)
{
StopCoroutine(value2);
}
transform.localScale = value;
buttonAnimationCoroutines[transform] = StartCoroutine(AnimateButtonScale(transform, value));
}
}
private void PlayButtonClickFeedback(Collider button)
{
if (!(buttonThockClip == null) && !(button == null))
{
AudioSource.PlayClipAtPoint(buttonThockClip, button.bounds.center, 1f);
}
}
private IEnumerator AnimateButtonScale(Transform buttonTransform, Vector3 baseScale)
{
Vector3 pressedScale = baseScale * 0.84f;
Vector3 overshootScale = baseScale * 1.04f;
float elapsed = 0f;
while (elapsed < 0.08f)
{
if (buttonTransform == null)
{
yield break;
}
elapsed += Time.unscaledDeltaTime;
float t = Mathf.Clamp01(elapsed / 0.08f);
buttonTransform.localScale = Vector3.LerpUnclamped(baseScale, pressedScale, t);
yield return null;
}
elapsed = 0f;
while (elapsed < 0.12f)
{
if (buttonTransform == null)
{
yield break;
}
elapsed += Time.unscaledDeltaTime;
float num = Mathf.Clamp01(elapsed / 0.12f);
float num2 = num * num * (3f - 2f * num);
Vector3 localScale = ((num2 < 0.5f) ? Vector3.LerpUnclamped(pressedScale, overshootScale, num2 / 0.5f) : Vector3.LerpUnclamped(overshootScale, baseScale, (num2 - 0.5f) / 0.5f));
buttonTransform.localScale = localScale;
yield return null;
}
if (buttonTransform != null)
{
buttonTransform.localScale = baseScale;
buttonAnimationCoroutines[buttonTransform] = null;
}
}
private void ResetAllButtonScales()
{
foreach (KeyValuePair<Transform, Vector3> buttonBaseScale in buttonBaseScales)
{
if (buttonBaseScale.Key != null)
{
buttonBaseScale.Key.localScale = buttonBaseScale.Value;
}
}
buttonAnimationCoroutines.Clear();
}
private void EnsureButtonAudioAssigned()
{
if (buttonThockClip == null)
{
buttonThockClip = Resources.Load<AudioClip>("Thock");
}
}
private static bool IsHandInside(Collider button, Transform handAnchor)
{
if (button == null || handAnchor == null)
{
return false;
}
Vector3 position = handAnchor.position;
return (button.ClosestPoint(position) - position).sqrMagnitude <= 1E-06f;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: e2c11e5f989ec71c1200e57827c3a533
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,1021 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
namespace BlockSpace.Voxels
{
[DefaultExecutionOrder(-100)]
[DisallowMultipleComponent]
public sealed class VRGroupSelector : MonoBehaviour
{
private sealed class GrabbedGroupState
{
public VoxelGroup group;
public Vector3 startWorldPosition;
public byte startPackedRotation;
public bool colliderWasEnabled;
public float lastPreviewSentAt = float.NegativeInfinity;
}
private struct GroupMergeAnimationState
{
public VoxelGroup group;
public Vector3 startTransformPosition;
public Vector3 startBoundsCenter;
public Vector3 burstStartCenter;
public Vector3 burstPeakCenter;
}
private const string GroupTickClipResourcePath = "Click";
private const string GroupImpactClipResourcePath = "Thock";
[SerializeField]
private XRNode rightHand = XRNode.RightHand;
[SerializeField]
private XRNode leftHand = XRNode.LeftHand;
[SerializeField]
private Transform selectorOrigin;
[SerializeField]
private float selectionRadius = 0.1f;
[SerializeField]
private KeyCode keyboardSelectKey = KeyCode.G;
[SerializeField]
private KeyCode keyboardMergeKey = KeyCode.F;
[SerializeField]
private KeyCode keyboardDuplicateKey = KeyCode.D;
[SerializeField]
private KeyCode keyboardPaintSelectionKey = KeyCode.Q;
[SerializeField]
private KeyCode keyboardInventoryKey = KeyCode.I;
[SerializeField]
private KeyCode keyboardDeleteKey = KeyCode.Delete;
[SerializeField]
private float gripGrabThreshold = 0.85f;
[SerializeField]
private float deleteTriggerThreshold = 0.7f;
[SerializeField]
private float undoChordWindow = 0.12f;
[SerializeField]
private float grabPositionSnap = 0.125f;
[SerializeField]
private float grabRotationSnapDegrees = 90f;
[SerializeField]
private float movePreviewSendInterval = 0.05f;
[Header("Grouping Audio")]
[SerializeField]
private AudioClip groupingClickClip;
[SerializeField]
private AudioClip groupingThockClip;
[SerializeField]
private VoxelGroupManager groupManager;
private readonly HashSet<VoxelGroup> selectedGroups = new HashSet<VoxelGroup>();
private readonly List<VoxelGroup> selectionBuffer = new List<VoxelGroup>();
private readonly List<VoxelGroup> cleanupBuffer = new List<VoxelGroup>();
private readonly List<GrabbedGroupState> grabbedGroupStates = new List<GrabbedGroupState>();
private InputDevice rightHandDevice;
private InputDevice leftHandDevice;
private bool mergeButtonPrevious;
private bool duplicateButtonPrevious;
private bool thumbstickClickPrevious;
private bool inventoryButtonPrevious;
private bool selectionHeld;
private bool selectionInputHeld;
private bool undoChordActive;
private bool suppressRightHandActionsUntilNeutral;
private bool interactionSuppressed;
private bool selectModeLatchedThisHold;
private bool gripHeldPrevious;
private bool triggerHeldPrevious;
private float lastGripPressTime = float.NegativeInfinity;
private float lastTriggerPressTime = float.NegativeInfinity;
private Transform trackingOrigin;
private bool groupingAnimationActive;
private bool selectionGrabActive;
private Vector3 grabStartReferencePosition;
private Quaternion grabStartReferenceRotation;
private Vector3 grabStartSelectionCenter;
private Vector3 grabStartSelectionOffset;
public static VRGroupSelector Instance { get; private set; }
public bool IsSelecting => selectionHeld;
public bool HasSelection => selectedGroups.Count > 0;
public float SelectionRadius => selectionRadius;
public bool BlocksRightHandActions
{
get
{
if (!undoChordActive && !suppressRightHandActionsUntilNeutral && !groupingAnimationActive)
{
return selectionGrabActive;
}
return true;
}
}
public void SetInteractionSuppressed(bool suppressed)
{
if (interactionSuppressed == suppressed)
{
return;
}
interactionSuppressed = suppressed;
if (interactionSuppressed)
{
if (HasSelection)
{
ClearSelectionInternal(suppressUntilNeutral: false);
}
selectionHeld = false;
selectionInputHeld = false;
undoChordActive = false;
suppressRightHandActionsUntilNeutral = false;
selectModeLatchedThisHold = false;
EndSelectionGrab();
}
}
public void Configure(VoxelGroupManager manager, Transform origin, Transform handAnchor)
{
if (manager != null)
{
groupManager = manager;
}
trackingOrigin = origin;
if (handAnchor != null)
{
selectorOrigin = handAnchor;
}
}
public bool TryGetSelectionCenter(out Vector3 worldPosition)
{
return TryGetSelectorPosition(out worldPosition);
}
public void SetSelectionRadius(float radius)
{
selectionRadius = Mathf.Max(0.001f, radius);
}
public void ClearSelectionForToolUse()
{
if (HasSelection)
{
ClearSelectionInternal(suppressUntilNeutral: false);
}
}
private void Awake()
{
if (Instance != null && Instance != this)
{
Debug.LogError("Multiple VRGroupSelector instances found.", this);
return;
}
Instance = this;
if (groupManager == null)
{
groupManager = ((VoxelGroupManager.Instance != null) ? VoxelGroupManager.Instance : UnityEngine.Object.FindObjectOfType<VoxelGroupManager>());
}
selectionRadius = Mathf.Max(0.001f, selectionRadius);
gripGrabThreshold = Mathf.Clamp(gripGrabThreshold, 0.5f, 1f);
undoChordWindow = Mathf.Max(0.01f, undoChordWindow);
grabPositionSnap = Mathf.Max(0.001f, grabPositionSnap);
grabRotationSnapDegrees = Mathf.Max(1f, grabRotationSnapDegrees);
EnsureGroupingClipsAssigned();
}
private void OnEnable()
{
rightHandDevice = InputDevices.GetDeviceAtXRNode(rightHand);
leftHandDevice = InputDevices.GetDeviceAtXRNode(leftHand);
}
private void OnDestroy()
{
if (Instance == this)
{
Instance = null;
}
}
private void Update()
{
if (groupManager == null)
{
return;
}
if (!groupManager.CreatorMode || groupManager.EditingLocked)
{
if (HasSelection)
{
ClearSelectionInternal(suppressUntilNeutral: false);
}
selectionHeld = false;
selectionInputHeld = false;
undoChordActive = false;
suppressRightHandActionsUntilNeutral = false;
selectModeLatchedThisHold = false;
EndSelectionGrab();
return;
}
VRVoxelBrush instance = VRVoxelBrush.Instance;
bool flag = instance == null || instance.UsingXRInput;
if (!rightHandDevice.isValid)
{
rightHandDevice = InputDevices.GetDeviceAtXRNode(rightHand);
}
if (!leftHandDevice.isValid)
{
leftHandDevice = InputDevices.GetDeviceAtXRNode(leftHand);
}
if (groupingAnimationActive)
{
return;
}
if (interactionSuppressed)
{
bool flag2 = ((!flag) ? (Input.GetMouseButton(2) || Input.GetKey(keyboardSelectKey)) : (ReadBool(rightHandDevice, CommonUsages.primary2DAxisClick) || Input.GetKey(keyboardSelectKey)));
bool flag3 = (flag ? ReadGrabHeld(rightHandDevice) : Input.GetMouseButton(1));
bool flag4 = flag && ReadFloat(rightHandDevice, CommonUsages.trigger) >= deleteTriggerThreshold;
bool flag5 = flag && ReadBool(rightHandDevice, CommonUsages.primaryButton);
bool flag6 = flag && ReadBool(leftHandDevice, CommonUsages.primaryButton);
bool flag7 = flag && ReadBool(rightHandDevice, CommonUsages.secondaryButton);
selectionHeld = false;
selectionInputHeld = false;
thumbstickClickPrevious = flag2;
mergeButtonPrevious = flag5;
duplicateButtonPrevious = flag6;
inventoryButtonPrevious = flag7;
gripHeldPrevious = flag3;
triggerHeldPrevious = flag4;
if (!flag3 && !flag4)
{
undoChordActive = false;
suppressRightHandActionsUntilNeutral = false;
}
return;
}
CleanupDestroyedSelections();
Vector3 worldPosition;
bool num = TryGetSelectorPosition(out worldPosition);
selectionBuffer.Clear();
if (num)
{
selectionBuffer.AddRange(groupManager.GetGroupsInRadius(worldPosition, selectionRadius));
}
bool flag8 = ((!flag) ? (Input.GetMouseButton(2) || Input.GetKey(keyboardSelectKey)) : (ReadBool(rightHandDevice, CommonUsages.primary2DAxisClick) || Input.GetKey(keyboardSelectKey)));
bool flag9 = (flag ? ReadGrabHeld(rightHandDevice) : Input.GetMouseButton(1));
bool flag10 = flag && ReadFloat(rightHandDevice, CommonUsages.trigger) >= deleteTriggerThreshold;
bool flag11 = flag && ReadBool(rightHandDevice, CommonUsages.primaryButton);
bool flag12 = flag && ReadBool(leftHandDevice, CommonUsages.primaryButton);
bool flag13 = flag && ReadBool(rightHandDevice, CommonUsages.secondaryButton);
bool flag14 = Input.GetKeyDown(keyboardDuplicateKey) && (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl));
if (flag9 && !gripHeldPrevious)
{
lastGripPressTime = Time.unscaledTime;
}
if (flag10 && !triggerHeldPrevious)
{
lastTriggerPressTime = Time.unscaledTime;
}
if (flag8 && !thumbstickClickPrevious)
{
selectModeLatchedThisHold = false;
}
if (!flag8 && thumbstickClickPrevious)
{
selectModeLatchedThisHold = false;
}
if (flag8 && selectionBuffer.Count > 0)
{
selectModeLatchedThisHold = true;
}
selectionInputHeld = flag8 && selectModeLatchedThisHold;
if (!undoChordActive && flag9 && flag10)
{
float num2 = Mathf.Max(lastGripPressTime, lastTriggerPressTime);
if (num2 > float.NegativeInfinity && Mathf.Abs(lastGripPressTime - lastTriggerPressTime) <= undoChordWindow && Time.unscaledTime - num2 <= undoChordWindow)
{
undoChordActive = true;
selectionHeld = false;
EndSelectionGrab();
groupManager.UndoLastOperation();
}
}
if (!undoChordActive && !suppressRightHandActionsUntilNeutral && HasSelection && ((flag12 && !duplicateButtonPrevious) || flag14))
{
DuplicateSelectedGroups();
}
if (!undoChordActive && HasSelection && flag9 && !flag10)
{
if (!selectionGrabActive)
{
BeginSelectionGrab();
}
UpdateSelectionGrab();
}
else if (selectionGrabActive)
{
EndSelectionGrab();
}
if (!selectionInputHeld && !flag9 && !flag10 && !flag11 && !flag12 && !flag13)
{
undoChordActive = false;
suppressRightHandActionsUntilNeutral = false;
}
selectionHeld = selectionInputHeld && !undoChordActive && !suppressRightHandActionsUntilNeutral && !selectionGrabActive;
if (selectionHeld)
{
for (int i = 0; i < selectionBuffer.Count; i++)
{
VoxelGroup voxelGroup = selectionBuffer[i];
if (!(voxelGroup == null) && selectedGroups.Add(voxelGroup))
{
voxelGroup.SetSelected(selected: true);
VRHaptics.Pulse(rightHandDevice, 0.18f, 0.035f);
}
}
}
if (!undoChordActive && !suppressRightHandActionsUntilNeutral && ((flag11 && !mergeButtonPrevious) || Input.GetKeyDown(keyboardMergeKey)) && HasSelection)
{
BeginMergeSelectedGroups();
suppressRightHandActionsUntilNeutral = true;
}
if (!undoChordActive && HasSelection && flag13 && !inventoryButtonPrevious)
{
PaintSelectedGroups();
}
if (!undoChordActive && HasSelection && Input.GetKeyDown(keyboardPaintSelectionKey))
{
PaintSelectedGroups();
}
if (!undoChordActive && Input.GetKeyDown(keyboardInventoryKey))
{
SendSelectedGroupsToInventory();
}
if (!undoChordActive && Input.GetKeyDown(keyboardDeleteKey) && HasSelection)
{
DeleteSelectedGroups();
suppressRightHandActionsUntilNeutral = true;
}
mergeButtonPrevious = flag11;
duplicateButtonPrevious = flag12;
thumbstickClickPrevious = flag8;
inventoryButtonPrevious = flag13;
gripHeldPrevious = flag9;
triggerHeldPrevious = flag10;
}
public void ClearSelection()
{
ClearSelectionInternal(suppressUntilNeutral: true);
}
private void ClearSelectionInternal(bool suppressUntilNeutral)
{
cleanupBuffer.Clear();
cleanupBuffer.AddRange(selectedGroups);
for (int i = 0; i < cleanupBuffer.Count; i++)
{
VoxelGroup voxelGroup = cleanupBuffer[i];
if (voxelGroup != null)
{
voxelGroup.SetSelected(selected: false);
}
}
selectedGroups.Clear();
cleanupBuffer.Clear();
selectionHeld = false;
selectionInputHeld = false;
if (suppressUntilNeutral)
{
suppressRightHandActionsUntilNeutral = true;
}
}
private void BeginMergeSelectedGroups()
{
CleanupDestroyedSelections();
if (selectedGroups.Count >= 2)
{
selectionBuffer.Clear();
selectionBuffer.AddRange(selectedGroups);
StartCoroutine(AnimateAndMergeSelectedGroups(new List<VoxelGroup>(selectionBuffer)));
selectionBuffer.Clear();
}
}
private void DuplicateSelectedGroups()
{
CleanupDestroyedSelections();
if (selectedGroups.Count == 0)
{
return;
}
selectionBuffer.Clear();
selectionBuffer.AddRange(selectedGroups);
ClearSelectionInternal(suppressUntilNeutral: false);
for (int i = 0; i < selectionBuffer.Count; i++)
{
VoxelGroup voxelGroup = groupManager.DuplicateGroup(selectionBuffer[i], Vector3Int.zero);
if (!(voxelGroup == null))
{
voxelGroup.SetSelected(selected: true);
selectedGroups.Add(voxelGroup);
}
}
selectionBuffer.Clear();
VRHaptics.Pulse(leftHandDevice, 0.32f, 0.055f);
}
private void BeginSelectionGrab()
{
CleanupDestroyedSelections();
if (selectedGroups.Count == 0 || !TryGetGrabReferencePose(out grabStartReferencePosition, out grabStartReferenceRotation) || !TryGetSelectedBounds(out var bounds))
{
return;
}
grabbedGroupStates.Clear();
foreach (VoxelGroup selectedGroup in selectedGroups)
{
if (!(selectedGroup == null))
{
grabbedGroupStates.Add(new GrabbedGroupState
{
group = selectedGroup,
startWorldPosition = selectedGroup.transform.position,
startPackedRotation = selectedGroup.packedRotation,
colliderWasEnabled = (selectedGroup.meshCollider == null || selectedGroup.meshCollider.enabled)
});
groupManager.SetGroupColliderEnabled(selectedGroup, enabled: false);
groupManager.BroadcastMovePreview(selectedGroup);
}
}
if (grabbedGroupStates.Count != 0)
{
grabStartSelectionCenter = bounds.center;
grabStartSelectionOffset = grabStartSelectionCenter - grabStartReferencePosition;
selectionGrabActive = true;
selectionHeld = false;
selectionInputHeld = false;
}
}
private void UpdateSelectionGrab()
{
if (!selectionGrabActive || groupManager == null || !TryGetGrabReferencePose(out var worldPosition, out var worldRotation))
{
return;
}
float voxelSize = groupManager.VoxelSize;
float step = Mathf.Max(0.001f, grabPositionSnap);
Quaternion quaternion = SnapRotation(worldRotation * Quaternion.Inverse(grabStartReferenceRotation));
Vector3 vector = SnapVector(worldPosition + grabStartSelectionOffset, step);
for (int num = grabbedGroupStates.Count - 1; num >= 0; num--)
{
GrabbedGroupState grabbedGroupState = grabbedGroupStates[num];
if (grabbedGroupState == null || grabbedGroupState.group == null || !selectedGroups.Contains(grabbedGroupState.group))
{
if (grabbedGroupState != null && grabbedGroupState.group != null)
{
groupManager.SetGroupColliderEnabled(grabbedGroupState.group, grabbedGroupState.colliderWasEnabled);
groupManager.BroadcastMoveFinal(grabbedGroupState.group);
}
grabbedGroupStates.RemoveAt(num);
}
else
{
Vector3 vector2 = grabbedGroupState.startWorldPosition - grabStartSelectionCenter;
Vector3Int gridPosition = WorldToGrid(vector + quaternion * vector2, voxelSize);
byte rotation = PackSnappedRotation(quaternion * GridRotationUtility.ToQuaternion(grabbedGroupState.startPackedRotation));
if (groupManager.TransformGroup(grabbedGroupState.group, gridPosition, rotation, sendNetworkUpdate: false))
{
grabbedGroupState.group.SetSelected(selected: true);
if (Time.unscaledTime - grabbedGroupState.lastPreviewSentAt >= Mathf.Max(0.01f, movePreviewSendInterval))
{
groupManager.BroadcastMovePreview(grabbedGroupState.group);
grabbedGroupState.lastPreviewSentAt = Time.unscaledTime;
}
}
}
}
}
private void EndSelectionGrab()
{
if (!selectionGrabActive)
{
grabbedGroupStates.Clear();
return;
}
selectionGrabActive = false;
for (int i = 0; i < grabbedGroupStates.Count; i++)
{
GrabbedGroupState grabbedGroupState = grabbedGroupStates[i];
if (grabbedGroupState != null && !(grabbedGroupState.group == null))
{
groupManager.SetGroupColliderEnabled(grabbedGroupState.group, grabbedGroupState.colliderWasEnabled);
groupManager.BroadcastMoveFinal(grabbedGroupState.group);
}
}
grabbedGroupStates.Clear();
}
private void DeleteSelectedGroups()
{
CleanupDestroyedSelections();
if (selectedGroups.Count == 0)
{
return;
}
selectionBuffer.Clear();
selectionBuffer.AddRange(selectedGroups);
ClearSelectionInternal(suppressUntilNeutral: true);
for (int i = 0; i < selectionBuffer.Count; i++)
{
VoxelGroup voxelGroup = selectionBuffer[i];
if (voxelGroup != null)
{
groupManager.RemoveGroup(voxelGroup);
}
}
selectionBuffer.Clear();
}
private void SendSelectedGroupsToInventory()
{
CleanupDestroyedSelections();
if (selectedGroups.Count != 0)
{
SendSelectedGroupsToInventoryStub(selectedGroups.Count);
}
}
public List<VoxelGroup> GetSelectedGroupsSnapshot()
{
CleanupDestroyedSelections();
if (selectedGroups.Count == 0)
{
return new List<VoxelGroup>();
}
List<VoxelGroup> list = new List<VoxelGroup>(selectedGroups.Count);
foreach (VoxelGroup selectedGroup in selectedGroups)
{
if (selectedGroup != null)
{
list.Add(selectedGroup);
}
}
return list;
}
private void PaintSelectedGroups()
{
CleanupDestroyedSelections();
if (selectedGroups.Count == 0)
{
return;
}
VRVoxelBrush instance = VRVoxelBrush.Instance;
byte voxelValue = ((instance != null) ? instance.GetSelectedVoxelValue() : PackedVoxel.Create(16, VoxelMaterial.Lit));
selectionBuffer.Clear();
selectionBuffer.AddRange(selectedGroups);
for (int i = 0; i < selectionBuffer.Count; i++)
{
VoxelGroup voxelGroup = selectionBuffer[i];
if (voxelGroup != null && groupManager.PaintWholeGroup(voxelGroup, voxelValue))
{
VRHaptics.Pulse(rightHandDevice, 0.32f, 0.055f);
}
}
selectionBuffer.Clear();
}
private void CleanupDestroyedSelections()
{
cleanupBuffer.Clear();
foreach (VoxelGroup selectedGroup in selectedGroups)
{
if (selectedGroup == null)
{
cleanupBuffer.Add(selectedGroup);
}
}
for (int i = 0; i < cleanupBuffer.Count; i++)
{
selectedGroups.Remove(cleanupBuffer[i]);
}
cleanupBuffer.Clear();
}
private bool TryGetSelectorPosition(out Vector3 worldPosition)
{
VRVoxelBrush instance = VRVoxelBrush.Instance;
if (instance != null && !instance.UsingXRInput && instance.TryGetDesktopSelectionPoint(out worldPosition))
{
return true;
}
if (selectorOrigin != null)
{
worldPosition = selectorOrigin.position;
return true;
}
if (rightHandDevice.isValid && rightHandDevice.TryGetFeatureValue(CommonUsages.devicePosition, out var value) && rightHandDevice.TryGetFeatureValue(CommonUsages.deviceRotation, out var value2))
{
if (trackingOrigin != null)
{
worldPosition = trackingOrigin.TransformPoint(value);
_ = trackingOrigin.rotation * value2;
}
else
{
worldPosition = value;
}
return true;
}
worldPosition = base.transform.position;
return true;
}
private bool TryGetGrabReferencePose(out Vector3 worldPosition, out Quaternion worldRotation)
{
VRVoxelBrush instance = VRVoxelBrush.Instance;
if (instance != null && !instance.UsingXRInput)
{
Camera main = Camera.main;
if (main != null)
{
worldPosition = main.transform.position;
worldRotation = main.transform.rotation;
return true;
}
}
if (selectorOrigin != null)
{
worldPosition = selectorOrigin.position;
worldRotation = selectorOrigin.rotation;
return true;
}
if (rightHandDevice.isValid && rightHandDevice.TryGetFeatureValue(CommonUsages.devicePosition, out var value) && rightHandDevice.TryGetFeatureValue(CommonUsages.deviceRotation, out var value2))
{
if (trackingOrigin != null)
{
worldPosition = trackingOrigin.TransformPoint(value);
worldRotation = trackingOrigin.rotation * value2;
}
else
{
worldPosition = value;
worldRotation = value2;
}
return true;
}
worldPosition = base.transform.position;
worldRotation = base.transform.rotation;
return true;
}
private bool TryGetSelectedBounds(out Bounds bounds)
{
CleanupDestroyedSelections();
bounds = default(Bounds);
bool flag = false;
foreach (VoxelGroup selectedGroup in selectedGroups)
{
if (!(selectedGroup == null))
{
if (!flag)
{
bounds = selectedGroup.GetWorldBounds();
flag = true;
}
else
{
bounds.Encapsulate(selectedGroup.GetWorldBounds());
}
}
}
return flag;
}
private Vector3 SnapVector(Vector3 value, float step)
{
return new Vector3(Mathf.Round(value.x / step) * step, Mathf.Round(value.y / step) * step, Mathf.Round(value.z / step) * step);
}
private Quaternion SnapRotation(Quaternion rotation)
{
Vector3 eulerAngles = rotation.eulerAngles;
return Quaternion.Euler(SnapAngle(eulerAngles.x), SnapAngle(eulerAngles.y), SnapAngle(eulerAngles.z));
}
private float SnapAngle(float angle)
{
return Mathf.Round(NormalizeSignedAngle(angle) / grabRotationSnapDegrees) * grabRotationSnapDegrees;
}
private static float NormalizeSignedAngle(float angle)
{
angle %= 360f;
if (angle > 180f)
{
angle -= 360f;
}
else if (angle < -180f)
{
angle += 360f;
}
return angle;
}
private static Vector3Int WorldToGrid(Vector3 worldPosition, float voxelSize)
{
return new Vector3Int(Mathf.RoundToInt(worldPosition.x / voxelSize), Mathf.RoundToInt(worldPosition.y / voxelSize), Mathf.RoundToInt(worldPosition.z / voxelSize));
}
private static byte PackSnappedRotation(Quaternion rotation)
{
Vector3 eulerAngles = rotation.eulerAngles;
return GridRotationUtility.Pack(new Vector3Int(AngleToQuarterTurns(eulerAngles.x), AngleToQuarterTurns(eulerAngles.y), AngleToQuarterTurns(eulerAngles.z)));
}
private static int AngleToQuarterTurns(float angle)
{
int num = Mathf.RoundToInt(NormalizeSignedAngle(angle) / 90f) % 4;
if (num >= 0)
{
return num;
}
return num + 4;
}
private static bool ReadBool(InputDevice device, InputFeatureUsage<bool> usage)
{
bool value = default(bool);
return device.isValid && device.TryGetFeatureValue(usage, out value) && value;
}
private bool ReadGrabHeld(InputDevice device)
{
if (!device.isValid)
{
return false;
}
bool value;
bool num = device.TryGetFeatureValue(CommonUsages.gripButton, out value) && value;
bool flag = ReadFloat(device, CommonUsages.grip) >= gripGrabThreshold;
return num || flag;
}
private static float ReadFloat(InputDevice device, InputFeatureUsage<float> usage)
{
if (!device.isValid || !device.TryGetFeatureValue(usage, out var value))
{
return 0f;
}
return value;
}
private IEnumerator AnimateAndMergeSelectedGroups(List<VoxelGroup> groupsToMerge)
{
groupingAnimationActive = true;
float num = Mathf.Max(((groupManager != null) ? groupManager.VoxelSize : 0.125f) * 0.9f, 0.12f);
List<GroupMergeAnimationState> states = new List<GroupMergeAnimationState>(groupsToMerge.Count);
Vector3 mergeCenter = Vector3.zero;
int num2 = 0;
for (int i = 0; i < groupsToMerge.Count; i++)
{
VoxelGroup voxelGroup = groupsToMerge[i];
if (!(voxelGroup == null))
{
mergeCenter += voxelGroup.GetWorldBounds().center;
num2++;
}
}
if (num2 < 2)
{
groupingAnimationActive = false;
yield break;
}
mergeCenter /= (float)num2;
PlayGroupingClip(groupingClickClip, mergeCenter, 0.85f);
VRHaptics.Pulse(rightHandDevice, 0.18f, 0.035f);
VRHaptics.Pulse(rightHandDevice, 0.65f, 0.11f);
for (int j = 0; j < groupsToMerge.Count; j++)
{
VoxelGroup voxelGroup2 = groupsToMerge[j];
if (!(voxelGroup2 == null))
{
Vector3 center = voxelGroup2.GetWorldBounds().center;
Vector3 vector = center - mergeCenter;
if (vector.sqrMagnitude < 0.0001f)
{
float f = MathF.PI * 2f * (float)j / (float)Mathf.Max(1, num2);
vector = new Vector3(Mathf.Cos(f), 0f, Mathf.Sin(f));
}
vector.Normalize();
GroupMergeAnimationState item = new GroupMergeAnimationState
{
group = voxelGroup2,
startTransformPosition = voxelGroup2.transform.position,
startBoundsCenter = center,
burstStartCenter = center,
burstPeakCenter = center + vector * num
};
states.Add(item);
}
}
float elapsed = 0f;
while (elapsed < 0.18f)
{
elapsed += Time.unscaledDeltaTime;
float num3 = Mathf.Clamp01(elapsed / 0.18f);
for (int k = 0; k < states.Count; k++)
{
GroupMergeAnimationState state = states[k];
if (!(state.group == null))
{
float t = 1f - Mathf.Pow(1f - num3, 2f);
Vector3 animatedCenter = Vector3.LerpUnclamped(state.burstStartCenter, state.burstPeakCenter, t);
SetAnimatedGroupCenter(state, animatedCenter);
}
}
yield return null;
}
elapsed = 0f;
while (elapsed < 0.17f)
{
elapsed += Time.unscaledDeltaTime;
float num4 = Mathf.Clamp01(elapsed / 0.17f);
for (int l = 0; l < states.Count; l++)
{
GroupMergeAnimationState state2 = states[l];
if (!(state2.group == null))
{
float t2 = num4 * num4 * (3f - 2f * num4);
Vector3 animatedCenter2 = Vector3.LerpUnclamped(state2.burstPeakCenter, state2.startBoundsCenter, t2);
SetAnimatedGroupCenter(state2, animatedCenter2);
}
}
yield return null;
}
for (int m = 0; m < states.Count; m++)
{
GroupMergeAnimationState groupMergeAnimationState = states[m];
if (!(groupMergeAnimationState.group == null))
{
groupMergeAnimationState.group.transform.position = groupMergeAnimationState.startTransformPosition;
}
}
selectionBuffer.Clear();
for (int n = 0; n < states.Count; n++)
{
if (states[n].group != null)
{
selectionBuffer.Add(states[n].group);
}
}
ClearSelectionInternal(suppressUntilNeutral: true);
VoxelGroup mergedGroup = groupManager.MergeGroups(selectionBuffer);
selectionBuffer.Clear();
PlayGroupingClip(groupingThockClip, mergeCenter, 1f);
VRHaptics.Pulse(rightHandDevice, 0.65f, 0.11f);
if (mergedGroup != null)
{
float mergedVoxelSize = ((groupManager != null) ? groupManager.VoxelSize : 0.125f);
Vector3 mergedCenter = mergedGroup.GetWorldBounds().center;
Quaternion mergedRotation = mergedGroup.transform.rotation;
Vector3 mergedLocalBoundsCenter = new Vector3(mergedGroup.size.x, mergedGroup.size.y, mergedGroup.size.z) * (mergedVoxelSize * 0.5f);
float impactElapsed = 0f;
while (impactElapsed < 0.05f)
{
impactElapsed += Time.unscaledDeltaTime;
float num5 = Mathf.Clamp01(impactElapsed / 0.05f);
float uniformScale = ((!(num5 < 0.5f)) ? Mathf.LerpUnclamped(1.05f, 1f, (num5 - 0.5f) / 0.5f) : Mathf.LerpUnclamped(0.95f, 1.05f, num5 / 0.5f));
ApplyCenteredGroupScale(mergedGroup, mergedCenter, mergedRotation, mergedLocalBoundsCenter, uniformScale);
yield return null;
}
ResetMergedGroupTransform(mergedGroup, mergedVoxelSize);
}
groupingAnimationActive = false;
}
private void SetAnimatedGroupCenter(GroupMergeAnimationState state, Vector3 animatedCenter)
{
if (!(state.group == null))
{
Vector3 vector = animatedCenter - state.startBoundsCenter;
state.group.transform.position = state.startTransformPosition + vector;
}
}
private static void ApplyCenteredGroupScale(VoxelGroup group, Vector3 worldCenter, Quaternion rotation, Vector3 localBoundsCenter, float uniformScale)
{
if (!(group == null))
{
Vector3 vector = localBoundsCenter * uniformScale;
Vector3 position = worldCenter - rotation * vector;
group.transform.SetPositionAndRotation(position, rotation);
group.transform.localScale = Vector3.one * uniformScale;
}
}
private static void ResetMergedGroupTransform(VoxelGroup group, float voxelSize)
{
if (!(group == null))
{
group.transform.localScale = Vector3.one;
group.transform.SetPositionAndRotation((Vector3)group.position * voxelSize, GridRotationUtility.ToQuaternion(group.packedRotation));
}
}
private void PlayGroupingClip(AudioClip clip, Vector3 position, float volume)
{
if (!(clip == null))
{
AudioSource.PlayClipAtPoint(clip, position, volume);
}
}
private void EnsureGroupingClipsAssigned()
{
if (groupingClickClip == null)
{
groupingClickClip = Resources.Load<AudioClip>("Click");
}
if (groupingThockClip == null)
{
groupingThockClip = Resources.Load<AudioClip>("Thock");
}
}
private void SendSelectedGroupsToInventoryStub(int selectedGroupCount)
{
if (selectedGroupCount > 0)
{
Debug.Log($"BlockSpace inventory send stub for {selectedGroupCount} selected groups.", this);
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: ca8353685b37ef0412e1736f20fa54e7
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
using UnityEngine;
using UnityEngine.XR;
namespace BlockSpace.Voxels
{
internal static class VRHaptics
{
public const float SmallAmplitude = 0.18f;
public const float MediumAmplitude = 0.32f;
public const float LargeAmplitude = 0.65f;
public const float TickDuration = 0.035f;
public const float ToolDuration = 0.055f;
public const float BigDuration = 0.11f;
public static void Pulse(InputDevice device, float amplitude, float duration)
{
if (device.isValid && !(amplitude <= 0f) && !(duration <= 0f))
{
device.SendHapticImpulse(0u, Mathf.Clamp01(amplitude), duration);
}
}
public static void Pulse(XRNode node, float amplitude, float duration)
{
Pulse(InputDevices.GetDeviceAtXRNode(node), amplitude, duration);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 293a1727064836e91c53f108b4ff7e09
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,2908 @@
using System.Collections.Generic;
using BlockSpace.Worlds;
using Photon.VR;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.XR;
using UnityEngine.XR.Interaction.Toolkit.Locomotion;
using UnityEngine.XR.Interaction.Toolkit.Locomotion.Movement;
namespace BlockSpace.Voxels
{
[DisallowMultipleComponent]
public sealed class VRVoxelBrush : MonoBehaviour
{
private sealed class AvatarEditSnapshot
{
public VoxelGroup group;
public Vector3Int position;
public byte packedRotation;
public Vector3Int size;
public byte[] voxels;
}
private sealed class AvatarEditUndoRecord
{
public AvatarEditSnapshot before;
public AvatarEditSnapshot after;
}
private const int PaletteColumns = 8;
private const int PaletteRows = 8;
private const float PaletteSize = 0.3f;
private const float PaletteCubeSize = 0.03f;
private const float PaletteSelectedCubeSize = 0.05f;
private const float PaletteUpOffset = 0.2f;
private const float PaletteForwardOffset = 0.14f;
private const float PaletteCellLift = 0.01f;
private const float PaletteCursorWidth = 0.0035f;
private const float ColorPickRadius = 0.2f;
private static readonly Color32 ColorPickIndicatorColor = new Color32(220, 120, byte.MaxValue, 140);
private static readonly Color32 PaletteCursorLightColor = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
private static readonly Color32 PaletteCursorDarkColor = new Color32(12, 12, 12, byte.MaxValue);
private static readonly Color32 IdleIndicatorColor = new Color32(64, 160, byte.MaxValue, 64);
private static readonly Color32 DrawingIndicatorColor = new Color32(64, 160, byte.MaxValue, 0);
private static readonly Color32 ErasingIndicatorColor = new Color32(byte.MaxValue, 72, 72, 64);
private static readonly Color32 PaintingIndicatorColor = new Color32(byte.MaxValue, 220, 72, 96);
private static readonly Color32 SelectingIndicatorColor = new Color32(72, byte.MaxValue, 96, 64);
[SerializeField]
private VoxelGroupManager groupManager;
[SerializeField]
private Transform trackingOrigin;
[SerializeField]
private Transform rightHandAnchor;
[SerializeField]
private Transform leftHandAnchor;
[SerializeField]
private float brushForwardOffset = 0.05f;
[SerializeField]
private float brushRadius = 0.125f;
[SerializeField]
private float minBrushRadius = 0.0625f;
[SerializeField]
private float maxBrushRadius = 0.625f;
[SerializeField]
private float brushRadiusStep = 0.0625f;
[SerializeField]
private float brushDistance = 1f;
[SerializeField]
private float minBrushDistance = 0.5f;
[SerializeField]
private float maxBrushDistance = 3f;
[SerializeField]
private float brushDistanceStep = 0.125f;
[SerializeField]
private float desktopSurfaceOffset = 0.01f;
[SerializeField]
private LayerMask desktopRaycastMask = -5;
[SerializeField]
private float triggerThreshold = 0.7f;
[SerializeField]
private float thumbstickDeadZone = 0.6f;
[SerializeField]
private float radiusRepeatDelay = 0.12f;
[SerializeField]
private float creatorToggleChordWindow = 0.12f;
[SerializeField]
private float avatarUndoChordWindow = 0.12f;
[SerializeField]
[Range(0f, 63f)]
private byte selectedColorIndex = 16;
[SerializeField]
private VoxelMaterial selectedMaterial;
private readonly HashSet<Vector3Int> strokeCells = new HashSet<Vector3Int>();
private readonly HashSet<VoxelGroup> activeEraseGroups = new HashSet<VoxelGroup>();
private readonly HashSet<VoxelGroup> activePaintGroups = new HashSet<VoxelGroup>();
private readonly Stack<AvatarEditUndoRecord> avatarUndoStack = new Stack<AvatarEditUndoRecord>();
private readonly List<ContinuousMoveProvider> moveProviders = new List<ContinuousMoveProvider>();
private readonly Dictionary<ContinuousMoveProvider, bool> moveProviderEnabledStates = new Dictionary<ContinuousMoveProvider, bool>();
private readonly List<LocomotionProvider> selectionLocomotionProviders = new List<LocomotionProvider>();
private readonly Dictionary<LocomotionProvider, bool> selectionLocomotionStates = new Dictionary<LocomotionProvider, bool>();
private readonly List<GameObject> paletteMoveObjects = new List<GameObject>();
private readonly Dictionary<GameObject, bool> paletteMoveObjectStates = new Dictionary<GameObject, bool>();
private InputDevice rightHand;
private InputDevice leftHand;
private bool useXRInput;
private bool lastDesktopBrushWasHit;
private bool lastDesktopBrushWasHitValid;
private bool lastBrushPointHadHit;
private bool lastErasePointHadHit;
private bool lastPaintPointHadHit;
private bool creatorMode = true;
private bool drawingStroke;
private bool erasing;
private bool painting;
private bool paletteOpen;
private byte activeStrokeVoxelValue;
private bool leftPrimaryPrevious;
private bool leftThumbstickClickPrevious;
private bool leftPaletteAxisLatched;
private bool leftCreatorChordConsumed;
private bool leftGripHeldPrevious;
private bool leftTriggerHeldPrevious;
private bool avatarUndoChordActive;
private bool suppressAvatarUndoUntilNeutral;
private float avatarClearChordStartTime = -1f;
private bool suppressAvatarClearUntilNeutral;
private float desktopAvatarClearStartTime = -1f;
private bool rightGripHeldPrevious;
private bool rightTriggerHeldPrevious;
private bool avatarEditUndoSessionActive;
private bool avatarEditUndoSessionChanged;
private bool selectionLocomotionBlocked;
private Vector3 lastBrushPoint;
private Vector3 lastErasePoint;
private Vector3 lastPaintPoint;
private float lastLeftGripPressTime = float.NegativeInfinity;
private float lastLeftTriggerPressTime = float.NegativeInfinity;
private float lastRightGripPressTime = float.NegativeInfinity;
private float lastRightTriggerPressTime = float.NegativeInfinity;
private float nextRadiusChangeTime;
private float nextToolHapticTime;
private float lastEraseChangedTime = float.NegativeInfinity;
private float lastPaintChangedTime = float.NegativeInfinity;
private bool colorPickerHeldPrevious;
private bool colorPickerActive;
private VoxelGroup activeStrokeGroup;
private bool activeStrokePreviewBroadcasting;
private bool activeErasePreviewBroadcasting;
private VoxelGroup avatarEditorTargetGroup;
private AvatarEditSnapshot avatarEditUndoBeforeSnapshot;
private Transform brushIndicator;
private MeshRenderer brushIndicatorRenderer;
private Transform colorPickIndicator;
private MeshRenderer colorPickIndicatorRenderer;
private Material brushIndicatorMaterial;
private Material colorPickIndicatorMaterial;
private Transform paletteRoot;
private LineRenderer paletteCursorRenderer;
private Material paletteCellMaterial;
private Material paletteCursorMaterial;
private readonly MeshRenderer[] paletteCellRenderers = new MeshRenderer[64];
private readonly MaterialPropertyBlock[] paletteCellBlocks = new MaterialPropertyBlock[64];
private int paletteColumn;
private int paletteRow;
public static VRVoxelBrush Instance { get; private set; }
public bool IsPaletteOpen => paletteOpen;
public bool UsingXRInput => useXRInput;
public Transform RightHandAnchor => rightHandAnchor;
public Transform LeftHandAnchor => leftHandAnchor;
public Transform TrackingOrigin => trackingOrigin;
public bool IsEditingAvatar => avatarEditorTargetGroup != null;
private void Awake()
{
Instance = this;
if (groupManager == null)
{
groupManager = Object.FindObjectOfType<VoxelGroupManager>();
}
if (groupManager != null)
{
creatorMode = groupManager.CreatorMode;
}
ApplyBrushRadiusLimits();
EnsureBrushIndicator();
EnsureColorPickIndicator();
EnsurePaletteUi();
EnsureGroupSelector();
EnsureNodeGraph();
EnsureDesktopRigController();
EnsureAvatarCustomizer();
RefreshMoveProviders();
RefreshSelectionLocomotionProviders();
SyncSelectionRadius();
SyncPaletteCursorToSelectedColor();
UpdatePaletteSelectionVisuals();
SetPaletteVisible(visible: false);
}
private void OnEnable()
{
AcquireDevices();
}
private void OnDisable()
{
CancelStroke();
if (erasing)
{
EndErase();
}
if (painting)
{
EndPaint();
}
paletteOpen = false;
ApplyPaletteLocomotionGate(disableMovement: false);
ApplySelectionLocomotionGate(disableMovement: false);
avatarUndoChordActive = false;
suppressAvatarUndoUntilNeutral = false;
SetPaletteVisible(visible: false);
SetBrushIndicatorVisible(visible: false);
SetColorPickIndicatorVisible(visible: false);
}
private void OnDestroy()
{
if (Instance == this)
{
Instance = null;
}
if (brushIndicatorMaterial != null)
{
Object.Destroy(brushIndicatorMaterial);
}
if (colorPickIndicatorMaterial != null)
{
Object.Destroy(colorPickIndicatorMaterial);
}
if (paletteCellMaterial != null)
{
Object.Destroy(paletteCellMaterial);
}
if (paletteCursorMaterial != null)
{
Object.Destroy(paletteCursorMaterial);
}
}
public void SetAvatarEditorTarget(VoxelGroup targetGroup)
{
if (!(avatarEditorTargetGroup == targetGroup))
{
FinishActiveAvatarEditSessions();
avatarEditorTargetGroup = targetGroup;
}
}
public void ClearAvatarEditorTarget()
{
if (!(avatarEditorTargetGroup == null))
{
FinishActiveAvatarEditSessions();
avatarEditorTargetGroup = null;
}
}
public bool UndoAvatarEditorAction()
{
if (drawingStroke || erasing || painting)
{
return false;
}
if (avatarUndoStack.Count == 0)
{
return false;
}
AvatarEditUndoRecord avatarEditUndoRecord = avatarUndoStack.Pop();
if (avatarEditUndoRecord == null || avatarEditUndoRecord.before == null)
{
return false;
}
avatarEditUndoBeforeSnapshot = null;
avatarEditUndoSessionActive = false;
avatarEditUndoSessionChanged = false;
RestoreAvatarEditSnapshot(avatarEditUndoRecord.before);
return true;
}
public void FinalizeAvatarEditorSessions()
{
FinishActiveAvatarEditSessions();
}
private void Update()
{
if (groupManager == null)
{
return;
}
creatorMode = groupManager.CreatorMode;
EnsureDevices();
useXRInput = DetectXRInputActive();
HandleCreatorToggle();
HandlePaletteToggleAndNavigation();
HandleMaterialCycle();
ApplyBrushRadiusLimits();
HandleBrushRadius();
SyncSelectionRadius();
UpdatePaletteUi();
UpdateToolHaptics();
HandleColorPickerTool();
VRGroupSelector instance = VRGroupSelector.Instance;
NodeGraphManager instance2 = NodeGraphManager.Instance;
bool flag = instance != null && instance.HasSelection;
bool flag2 = instance2 != null && instance2.BlocksRightHandActions;
ApplySelectionLocomotionGate((instance != null && (flag || instance.BlocksRightHandActions)) || flag2);
bool flag3 = instance != null && instance.IsSelecting;
bool flag4 = flag3;
bool flag5 = (instance != null && instance.BlocksRightHandActions) || flag2;
Vector3 worldPosition;
Quaternion worldRotation;
bool flag6 = TryGetActiveBrushPose(out worldPosition, out worldRotation);
bool currentHadHit = useXRInput || lastDesktopBrushWasHit;
bool flag7 = (useXRInput ? (ReadFloat(rightHand, CommonUsages.trigger) >= triggerThreshold) : Input.GetMouseButton(0));
bool flag8 = (useXRInput ? ReadGripHeld(rightHand) : Input.GetMouseButton(1));
bool flag9 = (useXRInput ? ReadBool(rightHand, CommonUsages.secondaryButton) : (Input.GetKey(KeyCode.Q) && !flag));
bool flag10 = HandleAvatarEditUndo(flag7, flag8);
bool flag11 = HandleDesktopUndoShortcut();
bool flag12 = HandleAvatarClearChord();
Vector3 vector = worldPosition;
if (flag6 && avatarEditorTargetGroup != null && creatorMode && !flag3 && (flag7 || drawingStroke))
{
vector = AdjustBrushPointForAvatarEditing(worldPosition);
}
if (!creatorMode)
{
if (paletteOpen)
{
paletteOpen = false;
leftPaletteAxisLatched = false;
ApplyPaletteLocomotionGate(disableMovement: false);
SetPaletteVisible(visible: false);
}
ApplySelectionLocomotionGate(disableMovement: false);
if (drawingStroke)
{
CancelStroke();
}
if (erasing)
{
EndErase();
}
if (painting)
{
EndPaint();
}
UpdateBrushIndicator(flag6, drawingStroke ? vector : worldPosition);
return;
}
if (flag12)
{
if (drawingStroke)
{
CancelStroke();
}
if (erasing)
{
EndErase();
}
if (painting)
{
EndPaint();
}
UpdateBrushIndicator(flag6, drawingStroke ? vector : worldPosition);
return;
}
if (flag10)
{
if (drawingStroke)
{
CancelStroke();
}
if (erasing)
{
EndErase();
}
if (painting)
{
EndPaint();
}
UpdateBrushIndicator(flag6, drawingStroke ? vector : worldPosition);
return;
}
if (flag11)
{
if (drawingStroke)
{
CancelStroke();
}
if (erasing)
{
EndErase();
}
if (painting)
{
EndPaint();
}
UpdateBrushIndicator(flag6, drawingStroke ? vector : worldPosition);
return;
}
if (flag5)
{
if (drawingStroke)
{
CancelStroke();
}
if (erasing)
{
EndErase();
}
if (painting)
{
EndPaint();
}
UpdateBrushIndicator(flag6, drawingStroke ? vector : worldPosition);
return;
}
if (flag6)
{
if (!flag3 && flag7)
{
if (painting)
{
EndPaint();
}
if (erasing)
{
EndErase();
}
if (!drawingStroke)
{
BeginStroke(vector);
}
else if (ShouldBreakContinuousEdit(lastBrushPoint, vector, lastBrushPointHadHit, currentHadHit))
{
lastBrushPoint = vector;
lastBrushPointHadHit = currentHadHit;
RasterizeSegment(vector, vector, brushRadius);
}
else
{
RasterizeSegment(lastBrushPoint, vector, brushRadius);
lastBrushPoint = vector;
lastBrushPointHadHit = currentHadHit;
}
}
else if (drawingStroke)
{
FinalizeStroke();
}
if (flag3)
{
if (drawingStroke)
{
CancelStroke();
}
if (erasing)
{
EndErase();
}
if (painting)
{
EndPaint();
}
if (instance2 != null && instance != null && instance.TryGetSelectionCenter(out var worldPosition2))
{
instance2.SelectNodesWithinSphere(worldPosition2, instance.SelectionRadius, additive: true);
}
}
else if (!drawingStroke)
{
if (flag8 && !flag4 && !flag)
{
if (painting)
{
EndPaint();
}
if (!erasing)
{
BeginErase(worldPosition);
}
else if (ShouldBreakContinuousEdit(lastErasePoint, worldPosition, lastErasePointHadHit, currentHadHit))
{
lastErasePoint = worldPosition;
lastErasePointHadHit = currentHadHit;
UpdateErase(worldPosition, worldPosition);
}
else
{
UpdateErase(lastErasePoint, worldPosition);
lastErasePoint = worldPosition;
lastErasePointHadHit = currentHadHit;
}
}
else if (erasing)
{
EndErase();
}
if (!erasing && !flag8 && !flag)
{
if (flag9)
{
if (!painting)
{
BeginPaint(worldPosition);
}
else if (ShouldBreakContinuousEdit(lastPaintPoint, worldPosition, lastPaintPointHadHit, currentHadHit))
{
lastPaintPoint = worldPosition;
lastPaintPointHadHit = currentHadHit;
UpdatePaint(worldPosition, worldPosition);
}
else
{
UpdatePaint(lastPaintPoint, worldPosition);
lastPaintPoint = worldPosition;
lastPaintPointHadHit = currentHadHit;
}
}
else if (painting)
{
EndPaint();
}
}
else if (painting)
{
EndPaint();
}
}
}
else
{
if (!flag7 && drawingStroke)
{
FinalizeStroke();
}
if (erasing)
{
EndErase();
}
if (painting)
{
EndPaint();
}
}
UpdateBrushIndicator(flag6, drawingStroke ? vector : worldPosition);
}
private void BeginStroke(Vector3 startPoint)
{
VRGroupSelector instance = VRGroupSelector.Instance;
if (instance != null && instance.HasSelection)
{
instance.ClearSelectionForToolUse();
}
NodeGraphManager.Instance?.ClearSelectionForToolUse();
strokeCells.Clear();
drawingStroke = true;
activeStrokeVoxelValue = GetCurrentBrushVoxelValue();
activeStrokeGroup = ((avatarEditorTargetGroup != null) ? avatarEditorTargetGroup : groupManager.BeginStrokeGroup(0));
activeStrokePreviewBroadcasting = false;
if (activeStrokeGroup == avatarEditorTargetGroup)
{
BeginAvatarEditUndoSession(activeStrokeGroup);
activeStrokeGroup.SetGreedyRenderMeshing(enabled: false);
}
else
{
BeginStrokePreviewBroadcast(startPoint);
}
lastBrushPoint = startPoint;
lastBrushPointHadHit = useXRInput || lastDesktopBrushWasHit;
RasterizeSegment(startPoint, startPoint, brushRadius);
}
private void FinalizeStroke()
{
drawingStroke = false;
if (activeStrokeGroup == null)
{
EndStrokePreviewBroadcast();
strokeCells.Clear();
return;
}
if (activeStrokeGroup == avatarEditorTargetGroup)
{
FinalizeAvatarEditorGroup(activeStrokeGroup);
}
else if (strokeCells.Count == 0)
{
EndStrokePreviewBroadcast();
groupManager.CancelStrokeGroup(activeStrokeGroup);
}
else
{
EndStrokePreviewBroadcast();
groupManager.FinalizeStrokeGroup(activeStrokeGroup);
}
activeStrokeGroup = null;
strokeCells.Clear();
}
private void CancelStroke()
{
drawingStroke = false;
if (activeStrokeGroup == avatarEditorTargetGroup)
{
FinalizeAvatarEditorGroup(activeStrokeGroup);
activeStrokeGroup = null;
}
else if (activeStrokeGroup != null && groupManager != null)
{
EndStrokePreviewBroadcast();
groupManager.CancelStrokeGroup(activeStrokeGroup);
activeStrokeGroup = null;
}
else
{
EndStrokePreviewBroadcast();
}
strokeCells.Clear();
}
private void BeginStrokePreviewBroadcast(Vector3 startPoint)
{
if (groupManager == null || activeStrokeGroup == null || activeStrokeGroup == avatarEditorTargetGroup)
{
activeStrokePreviewBroadcasting = false;
return;
}
activeStrokePreviewBroadcasting = true;
groupManager.BroadcastStrokePreviewBegin(activeStrokeVoxelValue, brushRadius, brushForwardOffset, startPoint);
}
private void UpdateStrokePreviewBroadcast(Vector3 point)
{
if (activeStrokePreviewBroadcasting && !(groupManager == null))
{
groupManager.BroadcastStrokePreviewUpdate(activeStrokeVoxelValue, brushRadius, brushForwardOffset, point);
}
}
private void EndStrokePreviewBroadcast()
{
if (activeStrokePreviewBroadcasting)
{
activeStrokePreviewBroadcasting = false;
groupManager?.BroadcastStrokePreviewEnd();
}
}
private void BeginErase(Vector3 startPoint)
{
VRGroupSelector instance = VRGroupSelector.Instance;
if (instance != null && instance.HasSelection)
{
instance.ClearSelectionForToolUse();
}
erasing = true;
activeEraseGroups.Clear();
activeErasePreviewBroadcasting = false;
lastEraseChangedTime = float.NegativeInfinity;
if (avatarEditorTargetGroup != null)
{
BeginAvatarEditUndoSession(avatarEditorTargetGroup);
activeEraseGroups.Add(avatarEditorTargetGroup);
avatarEditorTargetGroup.SetGreedyRenderMeshing(enabled: false);
}
else
{
groupManager.BeginEraseUndoSession();
BeginErasePreviewBroadcast(startPoint);
}
lastErasePoint = startPoint;
lastErasePointHadHit = useXRInput || lastDesktopBrushWasHit;
UpdateErase(startPoint, startPoint);
}
private void EndErase()
{
erasing = false;
EndErasePreviewBroadcast();
if (avatarEditorTargetGroup != null)
{
foreach (VoxelGroup activeEraseGroup in activeEraseGroups)
{
if (activeEraseGroup != null)
{
FinalizeAvatarEditorGroup(activeEraseGroup);
}
}
activeEraseGroups.Clear();
return;
}
if (groupManager == null)
{
activeEraseGroups.Clear();
return;
}
foreach (VoxelGroup activeEraseGroup2 in activeEraseGroups)
{
if (!(activeEraseGroup2 == null))
{
groupManager.FinalizeLiveEditedGroup(activeEraseGroup2, sendNetworkUpdate: true, rebuildCollider: true, playDestroyAnimationIfEmpty: false);
}
}
groupManager.CompleteEraseUndoSession();
activeEraseGroups.Clear();
}
private void BeginPaint(Vector3 startPoint)
{
if (!(groupManager == null))
{
VRGroupSelector instance = VRGroupSelector.Instance;
if (instance != null && instance.HasSelection)
{
instance.ClearSelectionForToolUse();
}
NodeGraphManager.Instance?.ClearSelectionForToolUse();
painting = true;
activePaintGroups.Clear();
lastPaintChangedTime = float.NegativeInfinity;
if (avatarEditorTargetGroup != null)
{
BeginAvatarEditUndoSession(avatarEditorTargetGroup);
activePaintGroups.Add(avatarEditorTargetGroup);
avatarEditorTargetGroup.SetGreedyRenderMeshing(enabled: false);
}
else
{
groupManager.BeginPaintUndoSession();
}
lastPaintPoint = startPoint;
lastPaintPointHadHit = useXRInput || lastDesktopBrushWasHit;
UpdatePaint(startPoint, startPoint);
}
}
private void EndPaint()
{
painting = false;
if (avatarEditorTargetGroup != null)
{
foreach (VoxelGroup activePaintGroup in activePaintGroups)
{
if (activePaintGroup != null)
{
FinalizeAvatarEditorGroup(activePaintGroup);
}
}
activePaintGroups.Clear();
return;
}
if (groupManager == null)
{
activePaintGroups.Clear();
return;
}
foreach (VoxelGroup activePaintGroup2 in activePaintGroups)
{
if (!(activePaintGroup2 == null))
{
groupManager.FinalizeLiveEditedGroup(activePaintGroup2, sendNetworkUpdate: true, rebuildCollider: false);
}
}
groupManager.CompletePaintUndoSession();
activePaintGroups.Clear();
}
private void UpdatePaint(Vector3 start, Vector3 end)
{
if (groupManager == null)
{
return;
}
if (avatarEditorTargetGroup != null)
{
UpdateAvatarEditorPaint(start, end);
return;
}
byte currentBrushVoxelValue = GetCurrentBrushVoxelValue();
float num = Mathf.Max(groupManager.VoxelSize * 0.5f, brushRadius * 0.5f);
float num2 = Vector3.Distance(start, end);
int num3 = Mathf.Max(1, Mathf.CeilToInt(num2 / num));
for (int i = 0; i <= num3; i++)
{
float t = ((num3 == 0) ? 0f : ((float)i / (float)num3));
Vector3 worldCenter = Vector3.Lerp(start, end, t);
if (groupManager.PaintSphereRealtime(worldCenter, brushRadius, currentBrushVoxelValue, activePaintGroups))
{
lastPaintChangedTime = Time.unscaledTime;
}
}
}
private void UpdateErase(Vector3 start, Vector3 end)
{
if (groupManager == null)
{
return;
}
if (avatarEditorTargetGroup != null)
{
UpdateAvatarEditorErase(start, end);
return;
}
float voxelSize = groupManager.VoxelSize;
float num = brushRadius;
float num2 = Mathf.Max(voxelSize * 0.5f, num * 0.5f);
float num3 = Vector3.Distance(start, end);
int num4 = Mathf.Max(1, Mathf.CeilToInt(num3 / num2));
for (int i = 0; i <= num4; i++)
{
float t = ((num4 == 0) ? 0f : ((float)i / (float)num4));
Vector3 worldCenter = Vector3.Lerp(start, end, t);
if (groupManager.EraseSphereRealtime(worldCenter, num, activeEraseGroups))
{
lastEraseChangedTime = Time.unscaledTime;
}
NodeGraphManager.Instance?.DeleteNodesWithinSphere(worldCenter, num, broadcast: true);
}
UpdateErasePreviewBroadcast(end);
}
private void BeginErasePreviewBroadcast(Vector3 startPoint)
{
if (groupManager == null || avatarEditorTargetGroup != null)
{
activeErasePreviewBroadcasting = false;
return;
}
activeErasePreviewBroadcasting = true;
groupManager.BroadcastErasePreviewBegin(brushRadius, brushForwardOffset, startPoint);
}
private void UpdateErasePreviewBroadcast(Vector3 point)
{
if (activeErasePreviewBroadcasting && !(groupManager == null))
{
groupManager.BroadcastErasePreviewUpdate(brushRadius, brushForwardOffset, point);
}
}
private void EndErasePreviewBroadcast()
{
if (activeErasePreviewBroadcasting)
{
activeErasePreviewBroadcasting = false;
groupManager?.BroadcastErasePreviewEnd();
}
}
private void UpdateToolHaptics()
{
if (useXRInput && !(Time.unscaledTime < nextToolHapticTime))
{
if (drawingStroke)
{
VRHaptics.Pulse(rightHand, 0.32f, 0.055f);
nextToolHapticTime = Time.unscaledTime + 0.05f;
}
else if (erasing && Time.unscaledTime - lastEraseChangedTime <= 0.2f)
{
VRHaptics.Pulse(rightHand, 0.32f, 0.055f);
nextToolHapticTime = Time.unscaledTime + 0.05f;
}
else if (painting && Time.unscaledTime - lastPaintChangedTime <= 0.2f)
{
VRHaptics.Pulse(rightHand, 0.32f, 0.055f);
nextToolHapticTime = Time.unscaledTime + 0.05f;
}
}
}
private void HandleColorPickerTool()
{
EnsureColorPickIndicator();
if (!creatorMode || groupManager == null)
{
colorPickerActive = false;
SetColorPickIndicatorVisible(visible: false);
colorPickerHeldPrevious = false;
return;
}
bool flag = false;
Vector3 worldPosition = Vector3.zero;
bool flag2 = false;
Quaternion worldRotation;
if (useXRInput)
{
bool num = ReadGripHeld(leftHand);
bool flag3 = ReadFloat(leftHand, CommonUsages.trigger) >= triggerThreshold;
flag = num && !flag3;
flag2 = TryGetBrushPose(leftHand, XRNode.LeftHand, leftHandAnchor, out worldPosition, out worldRotation);
}
else
{
flag = Input.GetKey(KeyCode.Z);
flag2 = TryGetActiveBrushPose(out worldPosition, out worldRotation);
}
if (flag && flag2)
{
if (!colorPickerHeldPrevious)
{
NodeGraphManager.Instance?.ClearSelectionForToolUse();
}
colorPickerActive = true;
float b = ((avatarEditorTargetGroup != null) ? avatarEditorTargetGroup.CurrentVoxelSize : groupManager.VoxelSize);
colorPickIndicator.position = worldPosition;
colorPickIndicator.rotation = Quaternion.identity;
colorPickIndicator.localScale = Vector3.one * Mathf.Max(0.001f, b);
if (colorPickIndicatorMaterial != null)
{
ApplyIndicatorMaterialColor(colorPickIndicatorMaterial, ColorPickIndicatorColor);
}
SetColorPickIndicatorVisible(visible: true);
}
else
{
SetColorPickIndicatorVisible(visible: false);
}
if (!flag && colorPickerHeldPrevious && colorPickerActive && flag2)
{
if (TryPickColorFromWorld(worldPosition, out var pickedVoxelValue))
{
ApplyPickedVoxelValueToPalette(pickedVoxelValue);
if (useXRInput)
{
VRHaptics.Pulse(leftHand, 0.18f, 0.055f);
}
}
colorPickerActive = false;
}
colorPickerHeldPrevious = flag;
}
private bool TryPickColorFromWorld(Vector3 worldPoint, out byte pickedVoxelValue)
{
pickedVoxelValue = 0;
if (avatarEditorTargetGroup != null)
{
if (!avatarEditorTargetGroup.IntersectsSphere(worldPoint, 0.2f))
{
return false;
}
Vector3 pickedWorldCenter;
float pickedDistanceSquared;
return avatarEditorTargetGroup.TryPickNearestVoxelValue(worldPoint, 0.2f, out pickedVoxelValue, out pickedWorldCenter, out pickedDistanceSquared);
}
if (groupManager != null)
{
return groupManager.TryPickNearestVoxelValue(worldPoint, 0.2f, out pickedVoxelValue);
}
return false;
}
private void ApplyPickedVoxelValueToPalette(byte voxelValue)
{
if (!PackedVoxel.IsAir(voxelValue))
{
selectedColorIndex = PackedVoxel.GetColorIndex(voxelValue);
selectedMaterial = PackedVoxel.GetMaterial(voxelValue);
SyncPaletteCursorToSelectedColor();
UpdatePaletteSelectionVisuals();
}
}
private void EnsureBrushIndicator()
{
if (!(brushIndicator != null))
{
GameObject gameObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
gameObject.name = "BrushRadiusIndicator";
gameObject.transform.SetParent(base.transform, worldPositionStays: false);
gameObject.layer = base.gameObject.layer;
Collider component = gameObject.GetComponent<Collider>();
if (component != null)
{
component.enabled = false;
}
brushIndicator = gameObject.transform;
brushIndicatorRenderer = gameObject.GetComponent<MeshRenderer>();
brushIndicatorRenderer.shadowCastingMode = ShadowCastingMode.Off;
brushIndicatorRenderer.receiveShadows = false;
brushIndicatorRenderer.sharedMaterial = GetOrCreateBrushIndicatorMaterial();
SetBrushIndicatorVisible(visible: false);
}
}
private void EnsureColorPickIndicator()
{
if (!(colorPickIndicator != null))
{
GameObject gameObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
gameObject.name = "ColorPickIndicator";
gameObject.transform.SetParent(base.transform, worldPositionStays: false);
gameObject.layer = base.gameObject.layer;
Collider component = gameObject.GetComponent<Collider>();
if (component != null)
{
component.enabled = false;
}
colorPickIndicator = gameObject.transform;
colorPickIndicatorRenderer = gameObject.GetComponent<MeshRenderer>();
colorPickIndicatorRenderer.shadowCastingMode = ShadowCastingMode.Off;
colorPickIndicatorRenderer.receiveShadows = false;
Material orCreateBrushIndicatorMaterial = GetOrCreateBrushIndicatorMaterial();
if (orCreateBrushIndicatorMaterial != null)
{
colorPickIndicatorMaterial = Object.Instantiate(orCreateBrushIndicatorMaterial);
colorPickIndicatorMaterial.name = "ColorPickIndicatorMaterial";
colorPickIndicatorRenderer.sharedMaterial = colorPickIndicatorMaterial;
}
SetColorPickIndicatorVisible(visible: false);
}
}
private void SetColorPickIndicatorVisible(bool visible)
{
if (colorPickIndicatorRenderer != null)
{
colorPickIndicatorRenderer.enabled = visible;
}
}
private void EnsurePaletteUi()
{
if (paletteRoot != null)
{
return;
}
paletteRoot = new GameObject("LeftPaletteUI").transform;
paletteRoot.SetParent(base.transform, worldPositionStays: false);
for (int i = 0; i < 64; i++)
{
GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
obj.name = $"PaletteCell_{i:D2}";
obj.transform.SetParent(paletteRoot, worldPositionStays: false);
obj.transform.localRotation = Quaternion.identity;
obj.transform.localScale = Vector3.one * 0.03f;
Collider component = obj.GetComponent<Collider>();
if (component != null)
{
component.enabled = false;
}
MeshRenderer component2 = obj.GetComponent<MeshRenderer>();
component2.shadowCastingMode = ShadowCastingMode.Off;
component2.receiveShadows = false;
component2.sharedMaterial = GetOrCreatePaletteCellMaterial();
paletteCellRenderers[i] = component2;
paletteCellBlocks[i] = new MaterialPropertyBlock();
}
GameObject gameObject = new GameObject("PaletteCursor");
gameObject.transform.SetParent(paletteRoot, worldPositionStays: false);
paletteCursorRenderer = gameObject.AddComponent<LineRenderer>();
paletteCursorRenderer.useWorldSpace = false;
paletteCursorRenderer.loop = true;
paletteCursorRenderer.positionCount = 4;
paletteCursorRenderer.alignment = LineAlignment.View;
paletteCursorRenderer.widthMultiplier = 0.0035f;
paletteCursorRenderer.numCapVertices = 0;
paletteCursorRenderer.numCornerVertices = 0;
paletteCursorRenderer.shadowCastingMode = ShadowCastingMode.Off;
paletteCursorRenderer.receiveShadows = false;
paletteCursorRenderer.sharedMaterial = GetOrCreatePaletteCursorMaterial();
paletteCursorRenderer.enabled = false;
UpdatePaletteSelectionVisuals();
}
private void UpdateBrushIndicator(bool hasBrushPose, Vector3 brushPoint)
{
EnsureBrushIndicator();
if (brushIndicator == null || brushIndicatorRenderer == null)
{
return;
}
if (!creatorMode)
{
SetBrushIndicatorVisible(visible: false);
return;
}
VRGroupSelector instance = VRGroupSelector.Instance;
Vector3 worldPosition = brushPoint;
bool flag = instance != null && instance.IsSelecting && instance.TryGetSelectionCenter(out worldPosition);
if (flag)
{
hasBrushPose = true;
brushPoint = worldPosition;
}
if (!hasBrushPose)
{
SetBrushIndicatorVisible(visible: false);
return;
}
brushIndicator.position = brushPoint;
brushIndicator.rotation = Quaternion.identity;
float num = (flag ? instance.SelectionRadius : brushRadius);
float num2 = Mathf.Max((groupManager != null) ? groupManager.VoxelSize : 0.125f, num * 2f);
brushIndicator.localScale = Vector3.one * num2;
Color32 brushIndicatorColor = GetBrushIndicatorColor();
ApplyBrushIndicatorColor(brushIndicatorColor);
SetBrushIndicatorVisible(brushIndicatorColor.a > 0);
}
private Color32 GetBrushIndicatorColor()
{
if (erasing)
{
return ErasingIndicatorColor;
}
if (painting)
{
return PaintingIndicatorColor;
}
if (drawingStroke)
{
return DrawingIndicatorColor;
}
VRGroupSelector instance = VRGroupSelector.Instance;
if (instance != null && instance.IsSelecting)
{
return SelectingIndicatorColor;
}
return IdleIndicatorColor;
}
private Material GetOrCreateBrushIndicatorMaterial()
{
if (brushIndicatorMaterial != null)
{
return brushIndicatorMaterial;
}
Shader shader = Shader.Find("Universal Render Pipeline/Particles/Unlit") ?? Shader.Find("Universal Render Pipeline/Unlit") ?? Shader.Find("Sprites/Default") ?? Shader.Find("Standard");
brushIndicatorMaterial = new Material(shader)
{
name = "BlockSpace_BrushIndicator"
};
if (brushIndicatorMaterial.HasProperty("_Surface"))
{
brushIndicatorMaterial.SetFloat("_Surface", 1f);
}
if (brushIndicatorMaterial.HasProperty("_Blend"))
{
brushIndicatorMaterial.SetFloat("_Blend", 0f);
}
if (brushIndicatorMaterial.HasProperty("_Cull"))
{
brushIndicatorMaterial.SetFloat("_Cull", 2f);
}
if (brushIndicatorMaterial.HasProperty("_SrcBlend"))
{
brushIndicatorMaterial.SetFloat("_SrcBlend", 5f);
}
if (brushIndicatorMaterial.HasProperty("_DstBlend"))
{
brushIndicatorMaterial.SetFloat("_DstBlend", 10f);
}
if (brushIndicatorMaterial.HasProperty("_SrcBlendAlpha"))
{
brushIndicatorMaterial.SetFloat("_SrcBlendAlpha", 1f);
}
if (brushIndicatorMaterial.HasProperty("_DstBlendAlpha"))
{
brushIndicatorMaterial.SetFloat("_DstBlendAlpha", 10f);
}
if (brushIndicatorMaterial.HasProperty("_ZWrite"))
{
brushIndicatorMaterial.SetFloat("_ZWrite", 0f);
}
brushIndicatorMaterial.renderQueue = 3000;
ApplyBrushIndicatorColor(IdleIndicatorColor);
return brushIndicatorMaterial;
}
private Material GetOrCreatePaletteCellMaterial()
{
if (paletteCellMaterial != null)
{
return paletteCellMaterial;
}
Shader paletteCellShader = GetPaletteCellShader(selectedMaterial == VoxelMaterial.Lit);
paletteCellMaterial = new Material(paletteCellShader)
{
name = "BlockSpace_PaletteCell"
};
if (paletteCellMaterial.HasProperty("_Surface"))
{
paletteCellMaterial.SetFloat("_Surface", 0f);
}
if (paletteCellMaterial.HasProperty("_Cull"))
{
paletteCellMaterial.SetFloat("_Cull", 0f);
}
if (paletteCellMaterial.HasProperty("_ZWrite"))
{
paletteCellMaterial.SetFloat("_ZWrite", 1f);
}
return paletteCellMaterial;
}
private void ConfigurePaletteCellMaterial(Material material)
{
if (!(material == null))
{
Shader paletteCellShader = GetPaletteCellShader(selectedMaterial == VoxelMaterial.Lit);
if (paletteCellShader != null && material.shader != paletteCellShader)
{
material.shader = paletteCellShader;
}
bool flag = selectedMaterial == VoxelMaterial.Transparent || selectedMaterial == VoxelMaterial.Invisible;
if (material.HasProperty("_Surface"))
{
material.SetFloat("_Surface", flag ? 1f : 0f);
}
if (material.HasProperty("_Mode"))
{
material.SetFloat("_Mode", flag ? 3f : 0f);
}
if (material.HasProperty("_Blend"))
{
material.SetFloat("_Blend", 0f);
}
if (material.HasProperty("_Cull"))
{
material.SetFloat("_Cull", 2f);
}
if (material.HasProperty("_SrcBlend"))
{
material.SetFloat("_SrcBlend", flag ? 5f : 1f);
}
if (material.HasProperty("_DstBlend"))
{
material.SetFloat("_DstBlend", flag ? 10f : 0f);
}
if (material.HasProperty("_SrcBlendAlpha"))
{
material.SetFloat("_SrcBlendAlpha", 1f);
}
if (material.HasProperty("_DstBlendAlpha"))
{
material.SetFloat("_DstBlendAlpha", flag ? 10f : 0f);
}
if (material.HasProperty("_ZWrite"))
{
material.SetFloat("_ZWrite", flag ? 0f : 1f);
}
if (material.HasProperty("_Smoothness"))
{
material.SetFloat("_Smoothness", (selectedMaterial == VoxelMaterial.Transparent) ? 0.9f : 0.08f);
}
if (material.HasProperty("_Metallic"))
{
material.SetFloat("_Metallic", 0f);
}
material.renderQueue = (flag ? 3000 : 2000);
material.SetOverrideTag("RenderType", flag ? "Transparent" : "Opaque");
}
}
private static Shader GetPaletteCellShader(bool useLitShader)
{
if (useLitShader)
{
return Shader.Find("Universal Render Pipeline/Particles/Lit") ?? Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard");
}
return Shader.Find("Universal Render Pipeline/Particles/Unlit") ?? Shader.Find("Universal Render Pipeline/Unlit") ?? Shader.Find("Sprites/Default") ?? Shader.Find("Standard");
}
private Color GetPalettePreviewColor(byte colorIndex)
{
return Palette.GetMaterialPreviewColor(colorIndex, selectedMaterial);
}
private Color GetPalettePreviewEmission(Color previewColor)
{
return Color.black;
}
private Material GetOrCreatePaletteCursorMaterial()
{
if (paletteCursorMaterial != null)
{
return paletteCursorMaterial;
}
Shader shader = Shader.Find("Universal Render Pipeline/Unlit") ?? Shader.Find("Universal Render Pipeline/Particles/Unlit") ?? Shader.Find("Sprites/Default") ?? Shader.Find("Standard");
paletteCursorMaterial = new Material(shader)
{
name = "BlockSpace_PaletteCursor"
};
if (paletteCursorMaterial.HasProperty("_Surface"))
{
paletteCursorMaterial.SetFloat("_Surface", 0f);
}
if (paletteCursorMaterial.HasProperty("_Cull"))
{
paletteCursorMaterial.SetFloat("_Cull", 0f);
}
return paletteCursorMaterial;
}
private void EnsureGroupSelector()
{
VRGroupSelector vRGroupSelector = GetComponent<VRGroupSelector>();
if (vRGroupSelector == null)
{
vRGroupSelector = base.gameObject.AddComponent<VRGroupSelector>();
}
vRGroupSelector.Configure(groupManager, trackingOrigin, rightHandAnchor);
vRGroupSelector.SetSelectionRadius(brushRadius);
}
private void EnsureNodeGraph()
{
NodeGraphManager nodeGraphManager = GetComponent<NodeGraphManager>();
if (nodeGraphManager == null)
{
nodeGraphManager = base.gameObject.AddComponent<NodeGraphManager>();
}
nodeGraphManager.Configure(groupManager, this, trackingOrigin, leftHandAnchor, rightHandAnchor);
if (GetComponent<NodeGraphPhotonSync>() == null)
{
base.gameObject.AddComponent<NodeGraphPhotonSync>();
}
}
private void EnsureDesktopRigController()
{
GameObject gameObject = base.gameObject;
DesktopRigController desktopRigController = gameObject.GetComponent<DesktopRigController>();
if (desktopRigController == null)
{
desktopRigController = gameObject.AddComponent<DesktopRigController>();
}
desktopRigController.Configure(base.transform, (Camera.main != null) ? Camera.main.transform : null, leftHandAnchor, rightHandAnchor);
}
private void EnsureAvatarCustomizer()
{
GameObject gameObject = GameObject.Find("Avatar Customizer");
if (!(gameObject == null))
{
AvatarCustomizerController avatarCustomizerController = gameObject.GetComponent<AvatarCustomizerController>();
if (avatarCustomizerController == null)
{
avatarCustomizerController = gameObject.AddComponent<AvatarCustomizerController>();
}
avatarCustomizerController.Configure(groupManager, this);
InventoryController inventoryController = gameObject.GetComponent<InventoryController>();
if (inventoryController == null)
{
inventoryController = gameObject.AddComponent<InventoryController>();
}
inventoryController.Configure(groupManager, this);
}
}
private void ApplyBrushIndicatorColor(Color32 color)
{
Material orCreateBrushIndicatorMaterial = GetOrCreateBrushIndicatorMaterial();
if (orCreateBrushIndicatorMaterial.HasProperty("_BaseColor"))
{
orCreateBrushIndicatorMaterial.SetColor("_BaseColor", color);
}
if (orCreateBrushIndicatorMaterial.HasProperty("_Color"))
{
orCreateBrushIndicatorMaterial.SetColor("_Color", color);
}
}
private static void ApplyIndicatorMaterialColor(Material material, Color32 color)
{
if (!(material == null))
{
if (material.HasProperty("_BaseColor"))
{
material.SetColor("_BaseColor", color);
}
if (material.HasProperty("_Color"))
{
material.SetColor("_Color", color);
}
}
}
private void SetBrushIndicatorVisible(bool visible)
{
if (brushIndicatorRenderer != null)
{
brushIndicatorRenderer.enabled = visible;
}
}
private void SetPaletteVisible(bool visible)
{
if (paletteRoot != null)
{
paletteRoot.gameObject.SetActive(visible);
}
}
private void HandleCreatorToggle()
{
if (WorldLoadingScreenController.Instance != null && WorldLoadingScreenController.Instance.IsLoading)
{
return;
}
if (!useXRInput)
{
if (!Input.GetKeyDown(KeyCode.LeftAlt))
{
return;
}
creatorMode = !creatorMode;
groupManager.SetCreatorMode(creatorMode);
creatorMode = groupManager.CreatorMode;
if (!creatorMode)
{
if (paletteOpen)
{
paletteOpen = false;
leftPaletteAxisLatched = false;
ApplyPaletteLocomotionGate(disableMovement: false);
SetPaletteVisible(visible: false);
}
ApplySelectionLocomotionGate(disableMovement: false);
CancelStroke();
if (erasing)
{
EndErase();
}
if (painting)
{
EndPaint();
}
VRGroupSelector instance = VRGroupSelector.Instance;
if (instance != null && instance.HasSelection)
{
instance.ClearSelectionForToolUse();
}
NodeGraphManager instance2 = NodeGraphManager.Instance;
if (instance2 != null)
{
instance2.CancelEditingInteractions(clearSelection: true);
}
}
return;
}
bool flag = ReadGripHeld(leftHand);
bool flag2 = ReadFloat(leftHand, CommonUsages.trigger) >= triggerThreshold;
if (flag && !leftGripHeldPrevious)
{
lastLeftGripPressTime = Time.unscaledTime;
}
if (flag2 && !leftTriggerHeldPrevious)
{
lastLeftTriggerPressTime = Time.unscaledTime;
}
if (!leftCreatorChordConsumed && flag && flag2)
{
float num = Mathf.Max(lastLeftGripPressTime, lastLeftTriggerPressTime);
if (num > float.NegativeInfinity && Mathf.Abs(lastLeftGripPressTime - lastLeftTriggerPressTime) <= creatorToggleChordWindow && Time.unscaledTime - num <= creatorToggleChordWindow)
{
creatorMode = !creatorMode;
groupManager.SetCreatorMode(creatorMode);
creatorMode = groupManager.CreatorMode;
if (!creatorMode)
{
if (paletteOpen)
{
paletteOpen = false;
leftPaletteAxisLatched = false;
ApplyPaletteLocomotionGate(disableMovement: false);
SetPaletteVisible(visible: false);
}
ApplySelectionLocomotionGate(disableMovement: false);
CancelStroke();
if (erasing)
{
EndErase();
}
VRGroupSelector instance3 = VRGroupSelector.Instance;
if (instance3 != null && instance3.HasSelection)
{
instance3.ClearSelectionForToolUse();
}
NodeGraphManager instance4 = NodeGraphManager.Instance;
if (instance4 != null)
{
instance4.CancelEditingInteractions(clearSelection: true);
}
}
leftCreatorChordConsumed = true;
}
}
if (!flag && !flag2)
{
leftCreatorChordConsumed = false;
}
leftGripHeldPrevious = flag;
leftTriggerHeldPrevious = flag2;
}
private void HandlePaletteToggleAndNavigation()
{
bool flag = (useXRInput ? ReadBool(leftHand, CommonUsages.secondaryButton) : Input.GetKeyDown(KeyCode.Tab));
if (flag && !leftPrimaryPrevious)
{
paletteOpen = !paletteOpen;
leftPaletteAxisLatched = false;
ApplyPaletteLocomotionGate(paletteOpen);
if (paletteOpen)
{
SyncPaletteCursorToSelectedColor();
UpdatePaletteSelectionVisuals();
}
else
{
SetPaletteVisible(visible: false);
}
leftPrimaryPrevious = flag;
return;
}
leftPrimaryPrevious = flag;
if (!paletteOpen)
{
SetPaletteVisible(visible: false);
return;
}
if (!useXRInput)
{
bool flag2 = false;
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
paletteColumn = WrapPaletteIndex(paletteColumn - 1, 8);
flag2 = true;
}
else if (Input.GetKeyDown(KeyCode.RightArrow))
{
paletteColumn = WrapPaletteIndex(paletteColumn + 1, 8);
flag2 = true;
}
else if (Input.GetKeyDown(KeyCode.UpArrow))
{
paletteRow = WrapPaletteIndex(paletteRow - 1, 8);
flag2 = true;
}
else if (Input.GetKeyDown(KeyCode.DownArrow))
{
paletteRow = WrapPaletteIndex(paletteRow + 1, 8);
flag2 = true;
}
if (flag2)
{
selectedColorIndex = (byte)(paletteRow * 8 + paletteColumn);
UpdatePaletteSelectionVisuals();
}
return;
}
Vector2 vector = ReadVector2(leftHand, CommonUsages.primary2DAxis);
if (Mathf.Abs(vector.x) < thumbstickDeadZone && Mathf.Abs(vector.y) < thumbstickDeadZone)
{
leftPaletteAxisLatched = false;
}
else if (!leftPaletteAxisLatched)
{
leftPaletteAxisLatched = true;
if (Mathf.Abs(vector.x) >= Mathf.Abs(vector.y))
{
paletteColumn = WrapPaletteIndex(paletteColumn + ((!(vector.x > 0f)) ? 1 : (-1)), 8);
}
else
{
paletteRow = WrapPaletteIndex(paletteRow + ((!(vector.y > 0f)) ? 1 : (-1)), 8);
}
selectedColorIndex = (byte)(paletteRow * 8 + paletteColumn);
UpdatePaletteSelectionVisuals();
}
}
private void HandleMaterialCycle()
{
bool flag = (useXRInput ? ReadBool(leftHand, CommonUsages.primary2DAxisClick) : Input.GetKeyDown(KeyCode.E));
if (flag && !leftThumbstickClickPrevious)
{
selectedMaterial = (selectedMaterial + 1) & VoxelMaterial.Invisible;
UpdatePaletteSelectionVisuals();
}
leftThumbstickClickPrevious = flag;
}
private void ApplyBrushRadiusLimits()
{
float num = ((groupManager != null) ? groupManager.VoxelSize : 0.125f);
if (avatarEditorTargetGroup != null)
{
num *= 0.5f;
}
minBrushRadius = num * 0.5f;
maxBrushRadius = num * 5f;
brushRadiusStep = num * 0.5f;
brushRadius = Mathf.Clamp(brushRadius, minBrushRadius, maxBrushRadius);
brushDistance = Mathf.Clamp(brushDistance, minBrushDistance, maxBrushDistance);
}
private Vector3 AdjustBrushPointForAvatarEditing(Vector3 worldPoint)
{
if (avatarEditorTargetGroup == null)
{
return worldPoint;
}
Vector3 position = ((avatarEditorTargetGroup.transform.parent != null) ? avatarEditorTargetGroup.transform.parent : avatarEditorTargetGroup.transform).position;
return position + (worldPoint - position) * 2f;
}
private void HandleBrushRadius()
{
if (!creatorMode || Time.unscaledTime < nextRadiusChangeTime)
{
return;
}
if (!useXRInput)
{
float y = Input.mouseScrollDelta.y;
if (!(Mathf.Abs(y) < 0.01f))
{
float num = Mathf.Sign(y);
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
brushDistance = Mathf.Clamp(brushDistance + num * brushDistanceStep, minBrushDistance, maxBrushDistance);
}
else
{
brushRadius = Mathf.Clamp(brushRadius + num * brushRadiusStep, minBrushRadius, maxBrushRadius);
}
nextRadiusChangeTime = Time.unscaledTime + radiusRepeatDelay;
}
}
else
{
Vector2 vector = ReadVector2(rightHand, CommonUsages.primary2DAxis);
if (vector.y >= thumbstickDeadZone)
{
brushRadius = Mathf.Min(maxBrushRadius, brushRadius + brushRadiusStep);
nextRadiusChangeTime = Time.unscaledTime + radiusRepeatDelay;
}
else if (vector.y <= 0f - thumbstickDeadZone)
{
brushRadius = Mathf.Max(minBrushRadius, brushRadius - brushRadiusStep);
nextRadiusChangeTime = Time.unscaledTime + radiusRepeatDelay;
}
}
}
private void SyncSelectionRadius()
{
VRGroupSelector instance = VRGroupSelector.Instance;
if (instance != null)
{
instance.SetSelectionRadius(brushRadius);
}
}
private void UpdatePaletteUi()
{
EnsurePaletteUi();
if (!paletteOpen)
{
SetPaletteVisible(visible: false);
return;
}
if (!TryGetPalettePose(out var worldPosition, out var worldRotation))
{
SetPaletteVisible(visible: false);
return;
}
SetPaletteVisible(visible: true);
paletteRoot.SetPositionAndRotation(worldPosition, worldRotation);
}
private void SyncPaletteCursorToSelectedColor()
{
int num = Mathf.Clamp(selectedColorIndex, 0, 63);
paletteRow = num / 8;
paletteColumn = num % 8;
selectedColorIndex = (byte)num;
}
private void UpdatePaletteSelectionVisuals()
{
if (paletteRoot == null)
{
return;
}
Material orCreatePaletteCellMaterial = GetOrCreatePaletteCellMaterial();
ConfigurePaletteCellMaterial(orCreatePaletteCellMaterial);
for (int i = 0; i < 64; i++)
{
MeshRenderer meshRenderer = paletteCellRenderers[i];
MaterialPropertyBlock materialPropertyBlock = paletteCellBlocks[i];
if (meshRenderer == null || materialPropertyBlock == null)
{
continue;
}
int num = i / 8;
int num2 = i % 8;
bool flag = num == paletteRow && num2 == paletteColumn;
Transform obj = meshRenderer.transform;
Vector3 paletteCellLocalPosition = GetPaletteCellLocalPosition(num, num2);
paletteCellLocalPosition.z = (flag ? 0.01f : 0f);
obj.localPosition = paletteCellLocalPosition;
obj.localScale = Vector3.one * (flag ? 0.05f : 0.03f);
materialPropertyBlock.Clear();
Color palettePreviewColor = GetPalettePreviewColor((byte)i);
Color palettePreviewEmission = GetPalettePreviewEmission(palettePreviewColor);
if (meshRenderer.sharedMaterial != null)
{
if (meshRenderer.sharedMaterial.HasProperty("_BaseColor"))
{
materialPropertyBlock.SetColor("_BaseColor", palettePreviewColor);
}
if (meshRenderer.sharedMaterial.HasProperty("_Color"))
{
materialPropertyBlock.SetColor("_Color", palettePreviewColor);
}
if (meshRenderer.sharedMaterial.HasProperty("_EmissionColor"))
{
materialPropertyBlock.SetColor("_EmissionColor", palettePreviewEmission);
}
if (meshRenderer.sharedMaterial.HasProperty("_EmissiveColor"))
{
materialPropertyBlock.SetColor("_EmissiveColor", palettePreviewEmission);
}
}
meshRenderer.SetPropertyBlock(materialPropertyBlock);
}
UpdatePaletteCursorVisual();
}
private void UpdatePaletteCursorVisual()
{
if (!(paletteCursorRenderer == null))
{
paletteCursorRenderer.enabled = false;
float num = 0.025f;
Vector3 vector = GetPaletteCellLocalPosition(paletteRow, paletteColumn) + new Vector3(0f, 0f, 0.02f);
paletteCursorRenderer.SetPosition(0, vector + new Vector3(0f - num, 0f - num, 0f));
paletteCursorRenderer.SetPosition(1, vector + new Vector3(0f - num, num, 0f));
paletteCursorRenderer.SetPosition(2, vector + new Vector3(num, num, 0f));
paletteCursorRenderer.SetPosition(3, vector + new Vector3(num, 0f - num, 0f));
Color32 color = Palette.GetColor(selectedColorIndex);
Color32 color2 = (((0.2126f * (float)(int)color.r + 0.7152f * (float)(int)color.g + 0.0722f * (float)(int)color.b) / 255f > 0.55f) ? PaletteCursorDarkColor : PaletteCursorLightColor);
paletteCursorRenderer.startColor = color2;
paletteCursorRenderer.endColor = color2;
}
}
private Vector3 GetPaletteCellLocalPosition(int row, int column)
{
float num = 0.03857143f;
float x = -0.135f + (float)column * num;
float y = 0.135f - (float)row * num;
return new Vector3(x, y, 0f);
}
private float GetPaletteCellSize()
{
return 0.03f;
}
private void RasterizeSegment(Vector3 start, Vector3 end, float radius)
{
if (activeStrokeGroup == null)
{
return;
}
if (activeStrokeGroup == avatarEditorTargetGroup)
{
RasterizeAvatarEditorSegment(start, end, radius);
return;
}
float voxelSize = groupManager.VoxelSize;
float num = Mathf.Max(voxelSize * 0.5f, radius * 0.5f);
float num2 = Vector3.Distance(start, end);
int num3 = Mathf.Max(1, Mathf.CeilToInt(num2 / num));
bool flag = false;
for (int i = 0; i <= num3; i++)
{
float t = ((num3 == 0) ? 0f : ((float)i / (float)num3));
Vector3 worldCenter = Vector3.Lerp(start, end, t);
flag |= AddSphere(worldCenter, radius, voxelSize);
}
if (flag)
{
groupManager.RefreshStrokeGroup(activeStrokeGroup);
UpdateStrokePreviewBroadcast(end);
}
}
private bool AddSphere(Vector3 worldCenter, float radius, float voxelSize)
{
float num = radius * radius;
int x = Mathf.RoundToInt(worldCenter.x / voxelSize - 0.5f);
int y = Mathf.RoundToInt(worldCenter.y / voxelSize - 0.5f);
int z = Mathf.RoundToInt(worldCenter.z / voxelSize - 0.5f);
Vector3Int vector3Int = new Vector3Int(x, y, z);
bool flag = false;
if (strokeCells.Add(vector3Int))
{
flag |= groupManager.AddVoxelToStrokeGroup(activeStrokeGroup, vector3Int, activeStrokeVoxelValue);
}
int num2 = Mathf.FloorToInt((worldCenter.x - radius) / voxelSize);
int num3 = Mathf.FloorToInt((worldCenter.y - radius) / voxelSize);
int num4 = Mathf.FloorToInt((worldCenter.z - radius) / voxelSize);
int num5 = Mathf.FloorToInt((worldCenter.x + radius) / voxelSize);
int num6 = Mathf.FloorToInt((worldCenter.y + radius) / voxelSize);
int num7 = Mathf.FloorToInt((worldCenter.z + radius) / voxelSize);
for (int i = num4; i <= num7; i++)
{
for (int j = num3; j <= num6; j++)
{
for (int k = num2; k <= num5; k++)
{
if (!((new Vector3(((float)k + 0.5f) * voxelSize, ((float)j + 0.5f) * voxelSize, ((float)i + 0.5f) * voxelSize) - worldCenter).sqrMagnitude > num))
{
Vector3Int vector3Int2 = new Vector3Int(k, j, i);
if (strokeCells.Add(vector3Int2))
{
flag |= groupManager.AddVoxelToStrokeGroup(activeStrokeGroup, vector3Int2, activeStrokeVoxelValue);
}
}
}
}
}
return flag;
}
private void RasterizeAvatarEditorSegment(Vector3 start, Vector3 end, float radius)
{
if (!(avatarEditorTargetGroup == null) && !(groupManager == null))
{
float voxelSize = groupManager.VoxelSize;
float num = Mathf.Max(voxelSize * 0.5f, radius * 0.5f);
float num2 = Vector3.Distance(start, end);
int num3 = Mathf.Max(1, Mathf.CeilToInt(num2 / num));
bool flag = false;
for (int i = 0; i <= num3; i++)
{
float t = ((num3 == 0) ? 0f : ((float)i / (float)num3));
Vector3 worldCenter = Vector3.Lerp(start, end, t);
flag |= AddSphereToAvatarGroup(avatarEditorTargetGroup, worldCenter, radius, voxelSize, activeStrokeVoxelValue);
}
if (flag)
{
avatarEditUndoSessionChanged = true;
avatarEditorTargetGroup.RebuildMesh();
DisableAvatarGroupCollider(avatarEditorTargetGroup);
}
}
}
private bool AddSphereToAvatarGroup(VoxelGroup group, Vector3 worldCenter, float radius, float voxelSize, byte voxelValue)
{
if (group == null)
{
return false;
}
Transform transform = ((group.UsesLocalGridTransform && group.transform.parent != null) ? group.transform.parent : null);
Vector3 vector = ((transform != null) ? transform.InverseTransformPoint(worldCenter) : worldCenter) / voxelSize;
float num = radius * radius;
float num2 = radius / voxelSize;
int x = Mathf.RoundToInt(vector.x - 0.5f);
int y = Mathf.RoundToInt(vector.y - 0.5f);
int z = Mathf.RoundToInt(vector.z - 0.5f);
bool flag = group.SetStoredVoxel(new Vector3Int(x, y, z), voxelValue, markMeshDirty: false, markColliderDirty: false);
int num3 = Mathf.FloorToInt(vector.x - num2);
int num4 = Mathf.FloorToInt(vector.y - num2);
int num5 = Mathf.FloorToInt(vector.z - num2);
int num6 = Mathf.FloorToInt(vector.x + num2);
int num7 = Mathf.FloorToInt(vector.y + num2);
int num8 = Mathf.FloorToInt(vector.z + num2);
for (int i = num5; i <= num8; i++)
{
for (int j = num4; j <= num7; j++)
{
for (int k = num3; k <= num6; k++)
{
Vector3 vector2 = new Vector3(((float)k + 0.5f) * voxelSize, ((float)j + 0.5f) * voxelSize, ((float)i + 0.5f) * voxelSize);
if (!((((transform != null) ? transform.TransformPoint(vector2) : vector2) - worldCenter).sqrMagnitude > num))
{
flag |= group.SetStoredVoxel(new Vector3Int(k, j, i), voxelValue, markMeshDirty: false, markColliderDirty: false);
}
}
}
}
return flag;
}
private void UpdateAvatarEditorErase(Vector3 start, Vector3 end)
{
if (!(avatarEditorTargetGroup == null) && !(groupManager == null))
{
float num = Mathf.Max(groupManager.VoxelSize * 0.5f, brushRadius * 0.5f);
float num2 = Vector3.Distance(start, end);
int num3 = Mathf.Max(1, Mathf.CeilToInt(num2 / num));
bool flag = false;
for (int i = 0; i <= num3; i++)
{
float t = ((num3 == 0) ? 0f : ((float)i / (float)num3));
Vector3 worldCenter = Vector3.Lerp(start, end, t);
flag |= avatarEditorTargetGroup.EraseSphere(worldCenter, brushRadius, markMeshDirty: false, markColliderDirty: false);
}
if (flag)
{
avatarEditUndoSessionChanged = true;
avatarEditorTargetGroup.RebuildMesh();
DisableAvatarGroupCollider(avatarEditorTargetGroup);
}
}
}
private void UpdateAvatarEditorPaint(Vector3 start, Vector3 end)
{
if (!(avatarEditorTargetGroup == null) && !(groupManager == null))
{
byte currentBrushVoxelValue = GetCurrentBrushVoxelValue();
float num = Mathf.Max(groupManager.VoxelSize * 0.5f, brushRadius * 0.5f);
float num2 = Vector3.Distance(start, end);
int num3 = Mathf.Max(1, Mathf.CeilToInt(num2 / num));
bool flag = false;
for (int i = 0; i <= num3; i++)
{
float t = ((num3 == 0) ? 0f : ((float)i / (float)num3));
Vector3 worldCenter = Vector3.Lerp(start, end, t);
flag |= avatarEditorTargetGroup.PaintSphere(worldCenter, brushRadius, currentBrushVoxelValue, markMeshDirty: false, markColliderDirty: false);
}
if (flag)
{
avatarEditUndoSessionChanged = true;
avatarEditorTargetGroup.RebuildMesh();
DisableAvatarGroupCollider(avatarEditorTargetGroup);
}
}
}
private void FinishActiveAvatarEditSessions()
{
if (drawingStroke && activeStrokeGroup == avatarEditorTargetGroup)
{
FinalizeStroke();
}
if (erasing)
{
EndErase();
}
if (painting)
{
EndPaint();
}
}
private bool HandleAvatarEditUndo(bool triggerHeld, bool gripHeld)
{
avatarUndoChordWindow = Mathf.Max(0.01f, avatarUndoChordWindow);
if (avatarEditorTargetGroup == null || !creatorMode)
{
if (!gripHeld && !triggerHeld)
{
avatarUndoChordActive = false;
suppressAvatarUndoUntilNeutral = false;
}
rightGripHeldPrevious = gripHeld;
rightTriggerHeldPrevious = triggerHeld;
return false;
}
if (gripHeld && !rightGripHeldPrevious)
{
lastRightGripPressTime = Time.unscaledTime;
}
if (triggerHeld && !rightTriggerHeldPrevious)
{
lastRightTriggerPressTime = Time.unscaledTime;
}
if (!avatarUndoChordActive && !drawingStroke && !erasing && !painting && gripHeld && triggerHeld)
{
float num = Mathf.Max(lastRightGripPressTime, lastRightTriggerPressTime);
if (num > float.NegativeInfinity && Mathf.Abs(lastRightGripPressTime - lastRightTriggerPressTime) <= avatarUndoChordWindow && Time.unscaledTime - num <= avatarUndoChordWindow)
{
avatarUndoChordActive = true;
suppressAvatarUndoUntilNeutral = true;
UndoAvatarEditorAction();
}
}
if (!gripHeld && !triggerHeld)
{
avatarUndoChordActive = false;
suppressAvatarUndoUntilNeutral = false;
}
rightGripHeldPrevious = gripHeld;
rightTriggerHeldPrevious = triggerHeld;
if (!avatarUndoChordActive)
{
return suppressAvatarUndoUntilNeutral;
}
return true;
}
private bool HandleDesktopUndoShortcut()
{
if (useXRInput)
{
return false;
}
bool num = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
bool flag = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
if (!num)
{
return false;
}
NodeGraphManager instance = NodeGraphManager.Instance;
if (instance != null)
{
if ((Input.GetKeyDown(KeyCode.Y) || (flag && Input.GetKeyDown(KeyCode.Z))) && instance.RedoLastCircuitEdit())
{
return true;
}
if (Input.GetKeyDown(KeyCode.Z) && instance.UndoLastCircuitEdit())
{
return true;
}
}
if (!Input.GetKeyDown(KeyCode.Z))
{
return false;
}
if (avatarEditorTargetGroup != null)
{
return UndoAvatarEditorAction();
}
if (groupManager != null)
{
return groupManager.UndoLastOperation();
}
return false;
}
private void BeginAvatarEditUndoSession(VoxelGroup group)
{
if (!(group == null) && (!avatarEditUndoSessionActive || avatarEditUndoBeforeSnapshot == null || !(avatarEditUndoBeforeSnapshot.group == group)))
{
avatarEditUndoBeforeSnapshot = CaptureAvatarEditSnapshot(group);
avatarEditUndoSessionActive = avatarEditUndoBeforeSnapshot != null;
avatarEditUndoSessionChanged = false;
}
}
private void CompleteAvatarEditUndoSession(VoxelGroup group)
{
if (!avatarEditUndoSessionActive || avatarEditUndoBeforeSnapshot == null)
{
return;
}
AvatarEditSnapshot avatarEditSnapshot = avatarEditUndoBeforeSnapshot;
avatarEditUndoBeforeSnapshot = null;
avatarEditUndoSessionActive = false;
if (!avatarEditUndoSessionChanged || avatarEditSnapshot.group != group)
{
avatarEditUndoSessionChanged = false;
return;
}
AvatarEditSnapshot avatarEditSnapshot2 = CaptureAvatarEditSnapshot(group);
avatarEditUndoSessionChanged = false;
if (avatarEditSnapshot2 != null && !AvatarSnapshotsMatch(avatarEditSnapshot, avatarEditSnapshot2))
{
avatarUndoStack.Push(new AvatarEditUndoRecord
{
before = avatarEditSnapshot,
after = avatarEditSnapshot2
});
}
}
private static AvatarEditSnapshot CaptureAvatarEditSnapshot(VoxelGroup group)
{
if (group == null)
{
return null;
}
byte[] voxels = ((group.voxels != null && group.voxels.Length != 0) ? ((byte[])group.voxels.Clone()) : new byte[0]);
return new AvatarEditSnapshot
{
group = group,
position = group.position,
packedRotation = group.packedRotation,
size = group.size,
voxels = voxels
};
}
private void RestoreAvatarEditSnapshot(AvatarEditSnapshot snapshot)
{
if (snapshot != null && !(snapshot.group == null))
{
snapshot.group.OverwriteData(snapshot.position, snapshot.packedRotation, snapshot.size, (snapshot.voxels != null) ? ((byte[])snapshot.voxels.Clone()) : new byte[0], queueRebuild: false);
snapshot.group.SetGreedyRenderMeshing(enabled: true);
snapshot.group.RebuildMesh();
snapshot.group.RebuildCollider();
DisableAvatarGroupCollider(snapshot.group);
}
}
private static bool AvatarSnapshotsMatch(AvatarEditSnapshot before, AvatarEditSnapshot after)
{
if (before == null || after == null)
{
return false;
}
if (before.group != after.group || before.position != after.position || before.packedRotation != after.packedRotation || before.size != after.size)
{
return false;
}
int num = ((before.voxels != null) ? before.voxels.Length : 0);
int num2 = ((after.voxels != null) ? after.voxels.Length : 0);
if (num != num2)
{
return false;
}
for (int i = 0; i < num; i++)
{
if (before.voxels[i] != after.voxels[i])
{
return false;
}
}
return true;
}
private void FinalizeAvatarEditorGroup(VoxelGroup group)
{
if (!(group == null))
{
if (!group.TrimToContents())
{
group.OverwriteData(Vector3Int.zero, 0, Vector3Int.zero, new byte[0], queueRebuild: false);
}
group.SetGreedyRenderMeshing(enabled: true);
group.RebuildMesh();
group.RebuildCollider();
DisableAvatarGroupCollider(group);
CompleteAvatarEditUndoSession(group);
}
}
private static void DisableAvatarGroupCollider(VoxelGroup group)
{
if (!(group == null) && !(group.meshCollider == null))
{
group.meshCollider.enabled = false;
}
}
private void EnsureDevices()
{
if (!rightHand.isValid || !leftHand.isValid)
{
AcquireDevices();
}
}
private void AcquireDevices()
{
rightHand = InputDevices.GetDeviceAtXRNode(XRNode.RightHand);
leftHand = InputDevices.GetDeviceAtXRNode(XRNode.LeftHand);
}
private void RefreshMoveProviders()
{
moveProviders.Clear();
moveProviderEnabledStates.Clear();
AddMoveProvidersFrom(base.gameObject.GetComponentsInParent<ContinuousMoveProvider>(includeInactive: true));
AddMoveProvidersFrom(base.gameObject.GetComponentsInChildren<ContinuousMoveProvider>(includeInactive: true));
AddMoveProvidersFrom(Object.FindObjectsByType<ContinuousMoveProvider>(FindObjectsInactive.Include, FindObjectsSortMode.None));
}
private void RefreshSelectionLocomotionProviders()
{
selectionLocomotionProviders.Clear();
selectionLocomotionStates.Clear();
AddSelectionLocomotionProvidersFrom(Object.FindObjectsByType<LocomotionProvider>(FindObjectsInactive.Include, FindObjectsSortMode.None));
}
private void AddMoveProvidersFrom(ContinuousMoveProvider[] providers)
{
if (providers == null)
{
return;
}
foreach (ContinuousMoveProvider continuousMoveProvider in providers)
{
if (!(continuousMoveProvider == null) && !moveProviders.Contains(continuousMoveProvider))
{
moveProviders.Add(continuousMoveProvider);
}
}
}
private void AddSelectionLocomotionProvidersFrom(LocomotionProvider[] providers)
{
if (providers == null)
{
return;
}
foreach (LocomotionProvider locomotionProvider in providers)
{
if (!(locomotionProvider == null) && !(locomotionProvider is ContinuousMoveProvider) && !selectionLocomotionProviders.Contains(locomotionProvider))
{
selectionLocomotionProviders.Add(locomotionProvider);
}
}
}
private void ApplyPaletteLocomotionGate(bool disableMovement)
{
if (useXRInput || !disableMovement)
{
ApplyPaletteMoveObjectGate(disableMovement);
}
if (disableMovement)
{
RefreshMoveProviders();
}
else if (moveProviders.Count == 0)
{
RefreshMoveProviders();
}
for (int num = moveProviders.Count - 1; num >= 0; num--)
{
ContinuousMoveProvider continuousMoveProvider = moveProviders[num];
bool value;
if (continuousMoveProvider == null)
{
moveProviders.RemoveAt(num);
}
else if (disableMovement)
{
if (!moveProviderEnabledStates.ContainsKey(continuousMoveProvider))
{
moveProviderEnabledStates.Add(continuousMoveProvider, continuousMoveProvider.enabled);
}
continuousMoveProvider.enabled = false;
}
else if (moveProviderEnabledStates.TryGetValue(continuousMoveProvider, out value))
{
continuousMoveProvider.enabled = value;
}
else
{
continuousMoveProvider.enabled = true;
}
}
if (!disableMovement)
{
moveProviderEnabledStates.Clear();
}
}
private void ApplyPaletteMoveObjectGate(bool disableMovement)
{
if (disableMovement)
{
RefreshPaletteMoveObjects();
}
for (int num = paletteMoveObjects.Count - 1; num >= 0; num--)
{
GameObject gameObject = paletteMoveObjects[num];
bool value;
if (gameObject == null)
{
paletteMoveObjects.RemoveAt(num);
}
else if (disableMovement)
{
if (!paletteMoveObjectStates.ContainsKey(gameObject))
{
paletteMoveObjectStates.Add(gameObject, gameObject.activeSelf);
}
gameObject.SetActive(value: false);
}
else if (paletteMoveObjectStates.TryGetValue(gameObject, out value))
{
gameObject.SetActive(value);
}
}
if (!disableMovement)
{
paletteMoveObjectStates.Clear();
}
}
private void RefreshPaletteMoveObjects()
{
paletteMoveObjects.Clear();
Transform transform = ResolveVrPlayerRoot();
if (transform != null)
{
AddPaletteMoveObjectFrom(transform);
}
Transform[] array = Object.FindObjectsByType<Transform>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (Transform transform2 in array)
{
if (!(transform2 == null) && !(transform2.name != "Move") && !(transform2.parent == null) && !(transform2.parent.name != "Locomotion") && !paletteMoveObjects.Contains(transform2.gameObject))
{
paletteMoveObjects.Add(transform2.gameObject);
}
}
}
private void AddPaletteMoveObjectFrom(Transform root)
{
Transform transform = root.Find("Locomotion/Move");
if (transform != null && !paletteMoveObjects.Contains(transform.gameObject))
{
paletteMoveObjects.Add(transform.gameObject);
}
}
private Transform ResolveVrPlayerRoot()
{
if (PhotonVRManager.Manager != null)
{
if (PhotonVRManager.Manager.LocalPlayer != null)
{
Transform transform = PhotonVRManager.Manager.LocalPlayer.transform;
Transform transform2 = FindAncestorNamed(transform, "VR Player");
if (!(transform2 != null))
{
return transform.root;
}
return transform2;
}
if (PhotonVRManager.Manager.Head != null)
{
Transform head = PhotonVRManager.Manager.Head;
Transform transform3 = FindAncestorNamed(head, "VR Player");
if (!(transform3 != null))
{
return head.root;
}
return transform3;
}
}
return null;
}
private static Transform FindAncestorNamed(Transform start, string name)
{
Transform transform = start;
while (transform != null)
{
if (transform.name == name)
{
return transform;
}
transform = transform.parent;
}
return null;
}
private void ApplySelectionLocomotionGate(bool disableMovement)
{
if (disableMovement == selectionLocomotionBlocked)
{
return;
}
if (disableMovement)
{
RefreshSelectionLocomotionProviders();
}
for (int num = selectionLocomotionProviders.Count - 1; num >= 0; num--)
{
LocomotionProvider locomotionProvider = selectionLocomotionProviders[num];
bool value;
if (locomotionProvider == null)
{
selectionLocomotionProviders.RemoveAt(num);
}
else if (disableMovement)
{
if (!selectionLocomotionStates.ContainsKey(locomotionProvider))
{
selectionLocomotionStates.Add(locomotionProvider, locomotionProvider.enabled);
}
locomotionProvider.enabled = false;
}
else if (selectionLocomotionStates.TryGetValue(locomotionProvider, out value))
{
locomotionProvider.enabled = value;
}
else
{
locomotionProvider.enabled = true;
}
}
selectionLocomotionBlocked = disableMovement;
if (!disableMovement)
{
selectionLocomotionStates.Clear();
}
}
private byte GetCurrentBrushVoxelValue()
{
selectedColorIndex = (byte)GetPaletteCursorIndex();
return PackedVoxel.Create(selectedColorIndex, selectedMaterial);
}
public byte GetSelectedVoxelValue()
{
return GetCurrentBrushVoxelValue();
}
public bool TryGetDesktopSelectionPoint(out Vector3 worldPosition)
{
worldPosition = Vector3.zero;
if (useXRInput)
{
return false;
}
Quaternion worldRotation;
return TryGetDesktopBrushPose(out worldPosition, out worldRotation);
}
public bool TryGetSpawnGridPosition(out Vector3Int gridPosition)
{
gridPosition = Vector3Int.zero;
if (groupManager == null)
{
return false;
}
EnsureDevices();
if (!TryGetActiveBrushPose(out var worldPosition, out var _))
{
return false;
}
float voxelSize = groupManager.VoxelSize;
gridPosition = new Vector3Int(Mathf.FloorToInt(worldPosition.x / voxelSize), Mathf.FloorToInt(worldPosition.y / voxelSize), Mathf.FloorToInt(worldPosition.z / voxelSize));
return true;
}
private bool TryGetActiveBrushPose(out Vector3 worldPosition, out Quaternion worldRotation)
{
if (!useXRInput)
{
return TryGetDesktopBrushPose(out worldPosition, out worldRotation);
}
return TryGetBrushPose(rightHand, XRNode.RightHand, rightHandAnchor, out worldPosition, out worldRotation);
}
private int GetPaletteCursorIndex()
{
return Mathf.Clamp(paletteRow * 8 + paletteColumn, 0, 63);
}
private static bool ReadGripHeld(InputDevice device)
{
if (!device.isValid)
{
return false;
}
bool value;
bool num = device.TryGetFeatureValue(CommonUsages.gripButton, out value) && value;
float value2;
bool flag = device.TryGetFeatureValue(CommonUsages.grip, out value2) && value2 >= 0.75f;
return num || flag;
}
private bool TryGetBrushPose(InputDevice device, XRNode node, Transform anchor, out Vector3 worldPosition, out Quaternion worldRotation)
{
if (!TryGetControllerPose(device, node, anchor, out worldPosition, out worldRotation))
{
worldPosition = Vector3.zero;
worldRotation = Quaternion.identity;
return false;
}
worldPosition += worldRotation * Vector3.forward * brushForwardOffset;
return true;
}
private bool TryGetDesktopBrushPose(out Vector3 worldPosition, out Quaternion worldRotation)
{
if (!TryGetDesktopViewCamera(out var viewCamera))
{
worldPosition = Vector3.zero;
worldRotation = Quaternion.identity;
return false;
}
Transform transform = viewCamera.transform;
worldRotation = transform.rotation;
if (Physics.Raycast(transform.position, transform.forward, out var hitInfo, brushDistance, GetDesktopRaycastMask(), QueryTriggerInteraction.Ignore))
{
worldPosition = hitInfo.point + hitInfo.normal * desktopSurfaceOffset;
lastDesktopBrushWasHit = true;
}
else
{
worldPosition = transform.position + transform.forward * brushDistance;
lastDesktopBrushWasHit = false;
}
lastDesktopBrushWasHitValid = true;
worldPosition = SnapToVoxelCenter(worldPosition);
return true;
}
private bool HandleAvatarClearChord()
{
if (avatarEditorTargetGroup == null || !creatorMode)
{
avatarClearChordStartTime = -1f;
suppressAvatarClearUntilNeutral = false;
desktopAvatarClearStartTime = -1f;
return false;
}
if (!useXRInput)
{
if (suppressAvatarClearUntilNeutral)
{
if (!Input.GetKey(KeyCode.O))
{
suppressAvatarClearUntilNeutral = false;
}
return true;
}
if (!Input.GetKey(KeyCode.O))
{
desktopAvatarClearStartTime = -1f;
return false;
}
if (desktopAvatarClearStartTime < 0f)
{
desktopAvatarClearStartTime = Time.unscaledTime;
return true;
}
if (Time.unscaledTime - desktopAvatarClearStartTime >= 5f)
{
ClearAvatarEditorGroupUndoable();
suppressAvatarClearUntilNeutral = true;
desktopAvatarClearStartTime = -1f;
}
return true;
}
bool flag = ReadBool(leftHand, CommonUsages.primaryButton);
bool flag2 = ReadBool(leftHand, CommonUsages.secondaryButton);
bool flag3 = ReadBool(rightHand, CommonUsages.primaryButton);
bool flag4 = ReadBool(rightHand, CommonUsages.secondaryButton);
bool flag5 = flag && flag2 && flag3 && flag4;
if (suppressAvatarClearUntilNeutral)
{
if (!flag && !flag2 && !flag3 && !flag4)
{
suppressAvatarClearUntilNeutral = false;
}
return true;
}
if (!flag5)
{
avatarClearChordStartTime = -1f;
return false;
}
if (avatarClearChordStartTime < 0f)
{
avatarClearChordStartTime = Time.unscaledTime;
return true;
}
if (Time.unscaledTime - avatarClearChordStartTime >= 5f)
{
ClearAvatarEditorGroupUndoable();
suppressAvatarClearUntilNeutral = true;
avatarClearChordStartTime = -1f;
}
return true;
}
private void ClearAvatarEditorGroupUndoable()
{
if (avatarEditorTargetGroup == null)
{
return;
}
AvatarCustomizerController avatarCustomizerController = Object.FindObjectOfType<AvatarCustomizerController>();
if (avatarCustomizerController != null && avatarCustomizerController.TryGetEditorGroups(out var headBody, out var left, out var right))
{
ClearSingleAvatarEditorGroupUndoable(headBody);
ClearSingleAvatarEditorGroupUndoable(left);
ClearSingleAvatarEditorGroupUndoable(right);
AvatarVoxelPayload payload = AvatarVoxelStorage.CapturePayload(headBody, left, right);
AvatarVoxelStorage.SaveLocal(payload);
PhotonVoxelAvatar localOwnedInstance = PhotonVoxelAvatar.LocalOwnedInstance;
if (localOwnedInstance != null)
{
localOwnedInstance.ApplyAndBroadcastAvatar(payload);
}
}
else
{
ClearSingleAvatarEditorGroupUndoable(avatarEditorTargetGroup);
}
}
private void ClearSingleAvatarEditorGroupUndoable(VoxelGroup group)
{
if (!(group == null))
{
BeginAvatarEditUndoSession(group);
group.OverwriteData(Vector3Int.zero, 0, Vector3Int.zero, new byte[0], queueRebuild: false);
avatarEditUndoSessionChanged = true;
FinalizeAvatarEditorGroup(group);
}
}
private bool ShouldBreakContinuousEdit(Vector3 previous, Vector3 current, bool previousHadHit, bool currentHadHit)
{
if (previousHadHit != currentHadHit)
{
return true;
}
if (groupManager == null)
{
return false;
}
float num = Mathf.Max(groupManager.VoxelSize * 3f, brushRadius * 2f);
return Vector3.Distance(previous, current) > num;
}
private int GetDesktopRaycastMask()
{
return desktopRaycastMask.value & -5;
}
private bool TryGetPalettePose(out Vector3 worldPosition, out Quaternion worldRotation)
{
if (!useXRInput)
{
if (!TryGetDesktopViewCamera(out var viewCamera))
{
worldPosition = Vector3.zero;
worldRotation = Quaternion.identity;
return false;
}
Transform transform = viewCamera.transform;
worldPosition = transform.position + transform.forward * 0.55f + -transform.right * 0.22f + transform.up * 0.08f;
worldRotation = Quaternion.LookRotation(transform.forward, Vector3.up);
return true;
}
if (!TryGetControllerPose(leftHand, XRNode.LeftHand, leftHandAnchor, out var worldPosition2, out var worldRotation2))
{
worldPosition = Vector3.zero;
worldRotation = Quaternion.identity;
return false;
}
worldPosition = worldPosition2 + worldRotation2 * Vector3.up * 0.2f + worldRotation2 * Vector3.forward * 0.14f;
Transform transform2 = ((Camera.main != null) ? Camera.main.transform : trackingOrigin);
Vector3 vector = ((transform2 != null) ? (transform2.position - worldPosition) : (-(worldRotation2 * Vector3.forward)));
if (vector.sqrMagnitude < 0.0001f)
{
vector = Vector3.back;
}
worldRotation = Quaternion.LookRotation(vector.normalized, Vector3.up);
return true;
}
private bool TryGetControllerPose(InputDevice device, XRNode node, Transform anchor, out Vector3 worldPosition, out Quaternion worldRotation)
{
if (anchor != null)
{
worldPosition = anchor.position;
worldRotation = anchor.rotation;
return true;
}
if (!device.isValid)
{
device = InputDevices.GetDeviceAtXRNode(node);
}
if (device.isValid && device.TryGetFeatureValue(CommonUsages.devicePosition, out var value) && device.TryGetFeatureValue(CommonUsages.deviceRotation, out var value2))
{
if (trackingOrigin != null)
{
worldPosition = trackingOrigin.TransformPoint(value);
worldRotation = trackingOrigin.rotation * value2;
}
else
{
worldPosition = value;
worldRotation = value2;
}
return true;
}
worldPosition = Vector3.zero;
worldRotation = Quaternion.identity;
return false;
}
private bool TryGetDesktopViewCamera(out Camera viewCamera)
{
viewCamera = Camera.main;
if (viewCamera != null)
{
return true;
}
Transform transform = ((PhotonVRManager.Manager != null) ? PhotonVRManager.Manager.Head : null);
if (transform != null)
{
viewCamera = transform.GetComponent<Camera>();
}
return viewCamera != null;
}
private Vector3 SnapToVoxelCenter(Vector3 worldPoint)
{
float num = ((groupManager != null) ? groupManager.VoxelSize : 0.125f);
if (avatarEditorTargetGroup != null)
{
num *= 0.5f;
}
return new Vector3((Mathf.Round(worldPoint.x / num - 0.5f) + 0.5f) * num, (Mathf.Round(worldPoint.y / num - 0.5f) + 0.5f) * num, (Mathf.Round(worldPoint.z / num - 0.5f) + 0.5f) * num);
}
private bool DetectXRInputActive()
{
if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).isValid && !InputDevices.GetDeviceAtXRNode(XRNode.LeftHand).isValid)
{
return InputDevices.GetDeviceAtXRNode(XRNode.RightHand).isValid;
}
return true;
}
private static int WrapPaletteIndex(int value, int length)
{
if (length <= 0)
{
return 0;
}
value %= length;
if (value >= 0)
{
return value;
}
return value + length;
}
private static bool ReadBool(InputDevice device, InputFeatureUsage<bool> usage)
{
bool value = default(bool);
return device.isValid && device.TryGetFeatureValue(usage, out value) && value;
}
private static float ReadFloat(InputDevice device, InputFeatureUsage<float> usage)
{
if (!device.isValid || !device.TryGetFeatureValue(usage, out var value))
{
return 0f;
}
return value;
}
private static Vector2 ReadVector2(InputDevice device, InputFeatureUsage<Vector2> usage)
{
if (!device.isValid || !device.TryGetFeatureValue(usage, out var value))
{
return Vector2.zero;
}
return value;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: a3e600c5e7a9035e46cb56660382b9cd
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,945 @@
using UnityEngine;
using UnityEngine.Rendering;
namespace BlockSpace.Voxels
{
[DisallowMultipleComponent]
public sealed class VoxelGroup : MonoBehaviour
{
private static readonly int BaseColorId = Shader.PropertyToID("_BaseColor");
private static readonly int ColorId = Shader.PropertyToID("_Color");
private static readonly int TintColorId = Shader.PropertyToID("_TintColor");
private static readonly int EmissionColorId = Shader.PropertyToID("_EmissionColor");
private static readonly int EmissiveColorId = Shader.PropertyToID("_EmissiveColor");
private static readonly Color SelectedTintColor = new Color(0.86f, 1f, 0.88f, 1f);
public ushort id;
[HideInInspector]
public int zoneID = -1;
public Vector3Int position;
public byte packedRotation;
public Vector3Int size;
public byte[] voxels;
[HideInInspector]
public MeshFilter meshFilter;
[HideInInspector]
public MeshRenderer meshRenderer;
[HideInInspector]
public MeshCollider meshCollider;
[HideInInspector]
public bool dirtyMesh = true;
[HideInInspector]
public bool dirtyCollider = true;
[SerializeField]
private bool creatorMode;
[SerializeField]
private bool useLocalGridTransform;
[SerializeField]
private float voxelSizeScale = 1f;
private VoxelGroupManager owner;
private Mesh renderMesh;
private Mesh renderMeshUnlit;
private Mesh renderMeshTransparent;
private Mesh renderMeshInvisible;
private Mesh collisionMesh;
private MeshFilter unlitMeshFilter;
private MeshRenderer unlitMeshRenderer;
private MeshFilter transparentMeshFilter;
private MeshRenderer transparentMeshRenderer;
private MeshFilter invisibleMeshFilter;
private MeshRenderer invisibleMeshRenderer;
private bool useGreedyRenderMeshing = true;
private bool allowColliderGeneration = true;
private MaterialPropertyBlock propertyBlock;
public bool IsSelected { get; private set; }
internal bool UsesLocalGridTransform => useLocalGridTransform;
internal float CurrentVoxelSize => ((owner != null) ? owner.VoxelSize : 0.125f) * Mathf.Max(0.001f, voxelSizeScale);
public void Initialize(VoxelGroupManager manager, ushort groupId, Vector3Int gridPosition, byte rotation, Vector3Int groupSize, byte[] voxelData, bool queueInitialRebuild = true)
{
owner = manager;
id = groupId;
position = gridPosition;
packedRotation = rotation;
size = groupSize;
voxels = voxelData;
creatorMode = owner != null && owner.CreatorMode;
EnsureComponents();
ApplyGridTransform();
BindMaterials((owner != null) ? owner.SharedMaterials : null);
if (queueInitialRebuild)
{
MarkDirty(mesh: true, collider: true);
return;
}
dirtyMesh = false;
dirtyCollider = false;
meshRenderer.enabled = false;
meshCollider.sharedMesh = null;
meshCollider.enabled = false;
}
public void SetVoxel(int x, int y, int z, byte value)
{
if (IsInBounds(x, y, z) && voxels != null)
{
int flatIndex = GetFlatIndex(x, y, z);
if (voxels[flatIndex] != value)
{
voxels[flatIndex] = value;
MarkDirty(mesh: true, collider: true);
}
}
}
public byte GetVoxel(int x, int y, int z)
{
if (!IsInBounds(x, y, z) || voxels == null)
{
return 0;
}
return voxels[GetFlatIndex(x, y, z)];
}
public Bounds GetWorldBounds()
{
if (size.x <= 0 || size.y <= 0 || size.z <= 0)
{
return new Bounds(base.transform.position, Vector3.zero);
}
Vector3 vector = new Vector3(size.x, size.y, size.z) * CurrentVoxelSize;
Vector3[] array = new Vector3[8]
{
base.transform.TransformPoint(new Vector3(0f, 0f, 0f)),
base.transform.TransformPoint(new Vector3(vector.x, 0f, 0f)),
base.transform.TransformPoint(new Vector3(0f, vector.y, 0f)),
base.transform.TransformPoint(new Vector3(0f, 0f, vector.z)),
base.transform.TransformPoint(new Vector3(vector.x, vector.y, 0f)),
base.transform.TransformPoint(new Vector3(vector.x, 0f, vector.z)),
base.transform.TransformPoint(new Vector3(0f, vector.y, vector.z)),
base.transform.TransformPoint(new Vector3(vector.x, vector.y, vector.z))
};
Bounds result = new Bounds(array[0], Vector3.zero);
for (int i = 1; i < array.Length; i++)
{
result.Encapsulate(array[i]);
}
return result;
}
public void RebuildMesh()
{
EnsureComponents();
if (renderMesh == null)
{
renderMesh = new Mesh
{
name = $"VoxelGroup_Render_{id}"
};
renderMesh.indexFormat = IndexFormat.UInt32;
}
if (renderMeshUnlit == null)
{
renderMeshUnlit = new Mesh
{
name = $"VoxelGroup_RenderUnlit_{id}"
};
renderMeshUnlit.indexFormat = IndexFormat.UInt32;
}
if (renderMeshTransparent == null)
{
renderMeshTransparent = new Mesh
{
name = $"VoxelGroup_RenderTransparent_{id}"
};
renderMeshTransparent.indexFormat = IndexFormat.UInt32;
}
if (renderMeshInvisible == null)
{
renderMeshInvisible = new Mesh
{
name = $"VoxelGroup_RenderInvisible_{id}"
};
renderMeshInvisible.indexFormat = IndexFormat.UInt32;
}
bool flag = owner != null && owner.CreatorMode;
GreedyMesher.MeshData meshData = (useGreedyRenderMeshing ? GreedyMesher.BuildRenderMesh(size, voxels, flag, CurrentVoxelSize) : GreedyMesher.BuildCulledRenderMesh(size, voxels, flag, CurrentVoxelSize));
ApplyMeshSubmesh(renderMesh, meshData, 0, applyColors: true);
meshFilter.sharedMesh = renderMesh;
meshRenderer.enabled = meshData.SubmeshTriangles.Length != 0 && meshData.SubmeshTriangles[0].Count > 0;
ApplyMeshSubmesh(renderMeshUnlit, meshData, 1, applyColors: true);
unlitMeshFilter.sharedMesh = renderMeshUnlit;
unlitMeshRenderer.enabled = meshData.SubmeshTriangles.Length > 1 && meshData.SubmeshTriangles[1].Count > 0;
ApplyMeshSubmesh(renderMeshTransparent, meshData, 2, applyColors: true);
transparentMeshFilter.sharedMesh = renderMeshTransparent;
transparentMeshRenderer.enabled = meshData.SubmeshTriangles.Length > 2 && meshData.SubmeshTriangles[2].Count > 0;
ApplyMeshSubmesh(renderMeshInvisible, meshData, 3, applyColors: true);
invisibleMeshFilter.sharedMesh = renderMeshInvisible;
invisibleMeshRenderer.enabled = meshData.SubmeshTriangles.Length > 3 && meshData.SubmeshTriangles[3].Count > 0;
dirtyMesh = false;
}
public void RebuildCollider()
{
EnsureComponents();
if (!allowColliderGeneration)
{
meshCollider.sharedMesh = null;
meshCollider.enabled = false;
dirtyCollider = false;
return;
}
if (collisionMesh == null)
{
collisionMesh = new Mesh
{
name = $"VoxelGroup_Collider_{id}"
};
collisionMesh.indexFormat = IndexFormat.UInt32;
}
GreedyMesher.MeshData meshData = GreedyMesher.BuildColliderMesh(size, voxels, CurrentVoxelSize);
ApplyMeshSubmesh(collisionMesh, meshData, 0, applyColors: false);
meshCollider.sharedMesh = null;
meshCollider.sharedMesh = ((collisionMesh.vertexCount > 0) ? collisionMesh : null);
meshCollider.enabled = collisionMesh.vertexCount > 0;
dirtyCollider = false;
}
public bool EraseSphere(Vector3 worldCenter, float radius, bool markMeshDirty = true, bool markColliderDirty = true)
{
if (voxels == null || voxels.Length == 0 || size.x <= 0 || size.y <= 0 || size.z <= 0)
{
return false;
}
float num = radius / CurrentVoxelSize;
float radiusSquared = num * num;
Vector3 center = base.transform.InverseTransformPoint(worldCenter) / CurrentVoxelSize;
float num2 = num + 0.5f;
int num3 = Mathf.Clamp(Mathf.FloorToInt(center.x - num2), 0, size.x - 1);
int num4 = Mathf.Clamp(Mathf.FloorToInt(center.y - num2), 0, size.y - 1);
int num5 = Mathf.Clamp(Mathf.FloorToInt(center.z - num2), 0, size.z - 1);
int num6 = Mathf.Clamp(Mathf.FloorToInt(center.x + num2), 0, size.x - 1);
int num7 = Mathf.Clamp(Mathf.FloorToInt(center.y + num2), 0, size.y - 1);
int num8 = Mathf.Clamp(Mathf.FloorToInt(center.z + num2), 0, size.z - 1);
bool flag = false;
for (int i = num5; i <= num8; i++)
{
for (int j = num4; j <= num7; j++)
{
for (int k = num3; k <= num6; k++)
{
if (!PackedVoxel.IsAir(GetVoxel(k, j, i)) && SphereIntersectsCell(center, radiusSquared, k, j, i))
{
voxels[GetFlatIndex(k, j, i)] = 0;
flag = true;
}
}
}
}
if (flag && (markMeshDirty || markColliderDirty))
{
MarkDirty(markMeshDirty, markColliderDirty);
}
return flag;
}
private static bool SphereIntersectsCell(Vector3 center, float radiusSquared, int x, int y, int z)
{
float num = 0f;
if (center.x < (float)x)
{
num = (float)x - center.x;
}
else if (center.x > (float)x + 1f)
{
num = center.x - ((float)x + 1f);
}
float num2 = 0f;
if (center.y < (float)y)
{
num2 = (float)y - center.y;
}
else if (center.y > (float)y + 1f)
{
num2 = center.y - ((float)y + 1f);
}
float num3 = 0f;
if (center.z < (float)z)
{
num3 = (float)z - center.z;
}
else if (center.z > (float)z + 1f)
{
num3 = center.z - ((float)z + 1f);
}
return num * num + num2 * num2 + num3 * num3 <= radiusSquared;
}
public bool PaintSphere(Vector3 worldCenter, float radius, byte value, bool markMeshDirty = true, bool markColliderDirty = true)
{
if (PackedVoxel.IsAir(value) || voxels == null || voxels.Length == 0 || size.x <= 0 || size.y <= 0 || size.z <= 0)
{
return false;
}
float num = radius / CurrentVoxelSize;
float num2 = num * num;
Vector3 vector = base.transform.InverseTransformPoint(worldCenter) / CurrentVoxelSize;
int num3 = Mathf.Clamp(Mathf.FloorToInt(vector.x - num), 0, size.x - 1);
int num4 = Mathf.Clamp(Mathf.FloorToInt(vector.y - num), 0, size.y - 1);
int num5 = Mathf.Clamp(Mathf.FloorToInt(vector.z - num), 0, size.z - 1);
int num6 = Mathf.Clamp(Mathf.FloorToInt(vector.x + num), 0, size.x - 1);
int num7 = Mathf.Clamp(Mathf.FloorToInt(vector.y + num), 0, size.y - 1);
int num8 = Mathf.Clamp(Mathf.FloorToInt(vector.z + num), 0, size.z - 1);
bool flag = false;
for (int i = num5; i <= num8; i++)
{
for (int j = num4; j <= num7; j++)
{
for (int k = num3; k <= num6; k++)
{
int flatIndex = GetFlatIndex(k, j, i);
byte b = voxels[flatIndex];
if (!PackedVoxel.IsAir(b) && b != value && !((new Vector3((float)k + 0.5f, (float)j + 0.5f, (float)i + 0.5f) - vector).sqrMagnitude > num2))
{
voxels[flatIndex] = value;
flag = true;
}
}
}
}
if (flag && (markMeshDirty || markColliderDirty))
{
MarkDirty(markMeshDirty, markColliderDirty);
}
return flag;
}
public bool RepaintAll(byte value, bool markMeshDirty = true, bool markColliderDirty = false)
{
if (PackedVoxel.IsAir(value) || voxels == null || voxels.Length == 0)
{
return false;
}
bool flag = false;
for (int i = 0; i < voxels.Length; i++)
{
byte b = voxels[i];
if (!PackedVoxel.IsAir(b) && b != value)
{
voxels[i] = value;
flag = true;
}
}
if (flag && (markMeshDirty || markColliderDirty))
{
MarkDirty(markMeshDirty, markColliderDirty);
}
return flag;
}
public bool IntersectsSphere(Vector3 worldCenter, float radius)
{
if (size.x <= 0 || size.y <= 0 || size.z <= 0)
{
return false;
}
Vector3 vector = base.transform.InverseTransformPoint(worldCenter) / CurrentVoxelSize;
Vector3 zero = Vector3.zero;
Vector3 vector2 = size;
float num = 0f;
for (int i = 0; i < 3; i++)
{
float num2 = vector[i];
if (num2 < zero[i])
{
float num3 = zero[i] - num2;
num += num3 * num3;
}
else if (num2 > vector2[i])
{
float num4 = num2 - vector2[i];
num += num4 * num4;
}
}
float num5 = radius / CurrentVoxelSize;
return num <= num5 * num5;
}
public bool TrimToContents()
{
if (voxels == null || voxels.Length == 0)
{
size = Vector3Int.zero;
return false;
}
int num = size.x;
int num2 = size.y;
int num3 = size.z;
int num4 = -1;
int num5 = -1;
int num6 = -1;
for (int i = 0; i < size.z; i++)
{
for (int j = 0; j < size.y; j++)
{
for (int k = 0; k < size.x; k++)
{
if (!PackedVoxel.IsAir(voxels[GetFlatIndex(k, j, i)]))
{
if (k < num)
{
num = k;
}
if (j < num2)
{
num2 = j;
}
if (i < num3)
{
num3 = i;
}
if (k > num4)
{
num4 = k;
}
if (j > num5)
{
num5 = j;
}
if (i > num6)
{
num6 = i;
}
}
}
}
}
if (num4 < 0 || num5 < 0 || num6 < 0)
{
size = Vector3Int.zero;
return false;
}
if (num == 0 && num2 == 0 && num3 == 0 && num4 == size.x - 1 && num5 == size.y - 1 && num6 == size.z - 1)
{
return true;
}
Vector3Int localOffset = new Vector3Int(num, num2, num3);
Vector3Int vector3Int = new Vector3Int(num4 - num + 1, num5 - num2 + 1, num6 - num3 + 1);
byte[] array = new byte[vector3Int.x * vector3Int.y * vector3Int.z];
for (int l = num3; l <= num6; l++)
{
for (int m = num2; m <= num5; m++)
{
for (int n = num; n <= num4; n++)
{
byte b = voxels[GetFlatIndex(n, m, l)];
if (!PackedVoxel.IsAir(b))
{
int num7 = n - num;
int num8 = m - num2;
int num9 = l - num3;
int num10 = num7 + num8 * vector3Int.x + num9 * vector3Int.x * vector3Int.y;
array[num10] = b;
}
}
}
}
voxels = array;
size = vector3Int;
position += GridRotationUtility.Rotate(localOffset, packedRotation);
ApplyGridTransform();
return true;
}
internal void SetOwner(VoxelGroupManager manager)
{
owner = manager;
creatorMode = owner != null && owner.CreatorMode;
ApplyGridTransform();
BindMaterials((owner != null) ? owner.SharedMaterials : null);
}
internal void SetUseLocalGridTransform(bool useLocalGrid)
{
if (useLocalGridTransform != useLocalGrid)
{
useLocalGridTransform = useLocalGrid;
ApplyGridTransform();
}
}
internal void OverwriteData(Vector3Int gridPosition, byte rotation, Vector3Int groupSize, byte[] voxelData, bool queueRebuild = true)
{
position = gridPosition;
packedRotation = rotation;
size = groupSize;
voxels = voxelData ?? new byte[0];
creatorMode = owner != null && owner.CreatorMode;
EnsureComponents();
ApplyGridTransform();
BindMaterials((owner != null) ? owner.SharedMaterials : null);
SetSelected(selected: false);
if (queueRebuild)
{
MarkDirty(mesh: true, collider: true);
return;
}
dirtyMesh = false;
dirtyCollider = false;
}
internal void SetGridPose(Vector3Int gridPosition, byte rotation)
{
if (!(position == gridPosition) || packedRotation != rotation)
{
position = gridPosition;
packedRotation = rotation;
ApplyGridTransform();
}
}
internal bool SetWorldVoxel(Vector3Int worldCell, byte value, bool markMeshDirty, bool markColliderDirty)
{
EnsureStorageForStoredCell(WorldGridCellToStoredCell(worldCell));
if (size.x <= 0 || size.y <= 0 || size.z <= 0 || voxels == null || voxels.Length == 0)
{
return false;
}
Vector3Int vector3Int = StoredCellToLocalCell(WorldGridCellToStoredCell(worldCell));
if (!IsInBounds(vector3Int.x, vector3Int.y, vector3Int.z))
{
return false;
}
int flatIndex = GetFlatIndex(vector3Int.x, vector3Int.y, vector3Int.z);
if (voxels[flatIndex] == value)
{
return false;
}
voxels[flatIndex] = value;
if (markMeshDirty || markColliderDirty)
{
MarkDirty(markMeshDirty, markColliderDirty);
}
return true;
}
internal bool SetStoredVoxel(Vector3Int storedCell, byte value, bool markMeshDirty, bool markColliderDirty)
{
EnsureStorageForStoredCell(storedCell);
if (size.x <= 0 || size.y <= 0 || size.z <= 0 || voxels == null || voxels.Length == 0)
{
return false;
}
Vector3Int vector3Int = StoredCellToLocalCell(storedCell);
if (!IsInBounds(vector3Int.x, vector3Int.y, vector3Int.z))
{
return false;
}
int flatIndex = GetFlatIndex(vector3Int.x, vector3Int.y, vector3Int.z);
if (voxels[flatIndex] == value)
{
return false;
}
voxels[flatIndex] = value;
if (markMeshDirty || markColliderDirty)
{
MarkDirty(markMeshDirty, markColliderDirty);
}
return true;
}
internal Vector3Int WorldPointToStoredGridCell(Vector3 worldPoint)
{
if (useLocalGridTransform && base.transform.parent != null)
{
Vector3 vector = base.transform.parent.InverseTransformPoint(worldPoint) / CurrentVoxelSize;
return new Vector3Int(Mathf.RoundToInt(vector.x), Mathf.RoundToInt(vector.y), Mathf.RoundToInt(vector.z));
}
Vector3 vector2 = worldPoint / CurrentVoxelSize;
return new Vector3Int(Mathf.RoundToInt(vector2.x), Mathf.RoundToInt(vector2.y), Mathf.RoundToInt(vector2.z));
}
internal void SetCreatorMode(bool enabled)
{
if (creatorMode != enabled)
{
creatorMode = enabled;
MarkDirty(mesh: true, collider: false);
}
}
internal bool TryPickNearestVoxelValue(Vector3 worldPoint, float maxRadius, out byte voxelValue, out Vector3 pickedWorldCenter, out float pickedDistanceSquared)
{
voxelValue = 0;
pickedWorldCenter = Vector3.zero;
pickedDistanceSquared = 0f;
if (voxels == null || voxels.Length == 0 || size.x <= 0 || size.y <= 0 || size.z <= 0)
{
return false;
}
float currentVoxelSize = CurrentVoxelSize;
if (currentVoxelSize <= 0f)
{
return false;
}
float num = maxRadius * maxRadius;
Vector3 vector = base.transform.InverseTransformPoint(worldPoint) / currentVoxelSize;
float num2 = maxRadius / currentVoxelSize;
int num3 = Mathf.Max(0, Mathf.FloorToInt(vector.x - num2));
int num4 = Mathf.Max(0, Mathf.FloorToInt(vector.y - num2));
int num5 = Mathf.Max(0, Mathf.FloorToInt(vector.z - num2));
int num6 = Mathf.Min(size.x - 1, Mathf.CeilToInt(vector.x + num2));
int num7 = Mathf.Min(size.y - 1, Mathf.CeilToInt(vector.y + num2));
int num8 = Mathf.Min(size.z - 1, Mathf.CeilToInt(vector.z + num2));
float num9 = num;
bool result = false;
for (int i = num5; i <= num8; i++)
{
for (int j = num4; j <= num7; j++)
{
for (int k = num3; k <= num6; k++)
{
byte voxel = GetVoxel(k, j, i);
if (!PackedVoxel.IsAir(voxel))
{
Vector3 vector2 = new Vector3(((float)k + 0.5f) * currentVoxelSize, ((float)j + 0.5f) * currentVoxelSize, ((float)i + 0.5f) * currentVoxelSize);
Vector3 vector3 = base.transform.TransformPoint(vector2);
float sqrMagnitude = (vector3 - worldPoint).sqrMagnitude;
if (!(sqrMagnitude > num9))
{
result = true;
num9 = sqrMagnitude;
voxelValue = voxel;
pickedWorldCenter = vector3;
}
}
}
}
}
pickedDistanceSquared = num9;
return result;
}
public void SetVoxelSizeScale(float scale, bool rebuild = true)
{
scale = Mathf.Max(0.001f, scale);
if (!Mathf.Approximately(voxelSizeScale, scale))
{
voxelSizeScale = scale;
ApplyGridTransform();
if (rebuild)
{
RebuildMesh();
RebuildCollider();
}
else
{
dirtyMesh = true;
dirtyCollider = true;
}
}
}
internal void SetSelected(bool selected)
{
if (IsSelected != selected)
{
IsSelected = selected;
ApplySelectionVisual();
}
}
internal void SetGreedyRenderMeshing(bool enabled)
{
useGreedyRenderMeshing = enabled;
if (!enabled)
{
EnsureComponents();
meshCollider.sharedMesh = null;
meshCollider.enabled = false;
dirtyCollider = false;
}
}
internal void SetColliderGeneration(bool enabled)
{
allowColliderGeneration = enabled;
if (!enabled)
{
EnsureComponents();
meshCollider.sharedMesh = null;
meshCollider.enabled = false;
dirtyCollider = false;
}
}
internal void BindMaterials(Material[] materials)
{
EnsureComponents();
if (materials != null && materials.Length != 0)
{
meshRenderer.sharedMaterial = ((materials.Length != 0) ? materials[0] : null);
unlitMeshRenderer.sharedMaterial = ((materials.Length > 1) ? materials[1] : meshRenderer.sharedMaterial);
transparentMeshRenderer.sharedMaterial = ((materials.Length > 2) ? materials[2] : meshRenderer.sharedMaterial);
invisibleMeshRenderer.sharedMaterial = ((materials.Length > 3) ? materials[3] : transparentMeshRenderer.sharedMaterial);
ApplySelectionVisual();
}
}
internal void MarkDirty(bool mesh, bool collider)
{
if (mesh)
{
dirtyMesh = true;
}
if (collider)
{
dirtyCollider = true;
}
owner?.QueueRebuild(this);
}
internal int GetFlatIndex(int x, int y, int z)
{
return x + y * size.x + z * size.x * size.y;
}
private void Awake()
{
EnsureComponents();
}
private void OnDestroy()
{
if (renderMesh != null)
{
Object.Destroy(renderMesh);
}
if (renderMeshUnlit != null)
{
Object.Destroy(renderMeshUnlit);
}
if (renderMeshTransparent != null)
{
Object.Destroy(renderMeshTransparent);
}
if (renderMeshInvisible != null)
{
Object.Destroy(renderMeshInvisible);
}
if (collisionMesh != null)
{
Object.Destroy(collisionMesh);
}
}
private void EnsureComponents()
{
if (meshFilter == null && !TryGetComponent<MeshFilter>(out meshFilter))
{
meshFilter = base.gameObject.AddComponent<MeshFilter>();
}
if (meshRenderer == null && !TryGetComponent<MeshRenderer>(out meshRenderer))
{
meshRenderer = base.gameObject.AddComponent<MeshRenderer>();
}
EnsureChildRenderer("Render_Unlit", ref unlitMeshFilter, ref unlitMeshRenderer);
EnsureChildRenderer("Render_Transparent", ref transparentMeshFilter, ref transparentMeshRenderer);
EnsureChildRenderer("Render_Invisible", ref invisibleMeshFilter, ref invisibleMeshRenderer);
if (meshCollider == null && !TryGetComponent<MeshCollider>(out meshCollider))
{
meshCollider = base.gameObject.AddComponent<MeshCollider>();
}
meshRenderer.shadowCastingMode = ShadowCastingMode.On;
meshRenderer.receiveShadows = true;
unlitMeshRenderer.shadowCastingMode = ShadowCastingMode.On;
unlitMeshRenderer.receiveShadows = true;
transparentMeshRenderer.shadowCastingMode = ShadowCastingMode.Off;
transparentMeshRenderer.receiveShadows = false;
invisibleMeshRenderer.shadowCastingMode = ShadowCastingMode.Off;
invisibleMeshRenderer.receiveShadows = false;
ApplySelectionVisual();
}
private void EnsureChildRenderer(string name, ref MeshFilter filter, ref MeshRenderer renderer)
{
if (!(filter != null) || !(renderer != null))
{
Transform transform = base.transform.Find(name);
if (transform == null)
{
GameObject obj = new GameObject(name);
obj.transform.SetParent(base.transform, worldPositionStays: false);
transform = obj.transform;
}
if (!transform.TryGetComponent<MeshFilter>(out filter))
{
filter = transform.gameObject.AddComponent<MeshFilter>();
}
if (!transform.TryGetComponent<MeshRenderer>(out renderer))
{
renderer = transform.gameObject.AddComponent<MeshRenderer>();
}
}
}
private void ApplyGridTransform()
{
Vector3 localPosition = (Vector3)position * CurrentVoxelSize;
Quaternion quaternion = GridRotationUtility.ToQuaternion(packedRotation);
if (useLocalGridTransform)
{
base.transform.localPosition = localPosition;
base.transform.localRotation = quaternion;
}
else
{
base.transform.SetPositionAndRotation(localPosition, quaternion);
}
}
private void ApplyMeshSubmesh(Mesh mesh, GreedyMesher.MeshData meshData, int submeshIndex, bool applyColors)
{
mesh.Clear();
mesh.indexFormat = IndexFormat.UInt32;
mesh.subMeshCount = 1;
mesh.SetVertices(meshData.Vertices);
mesh.SetNormals(meshData.Normals);
if (applyColors)
{
mesh.SetColors(meshData.Colors);
mesh.SetUVs(0, meshData.Uvs);
}
if (submeshIndex >= 0 && submeshIndex < meshData.SubmeshTriangles.Length)
{
mesh.SetTriangles(meshData.SubmeshTriangles[submeshIndex], 0, calculateBounds: false);
}
mesh.RecalculateBounds();
}
private void ApplySelectionVisual()
{
if (!(meshRenderer == null))
{
if (propertyBlock == null)
{
propertyBlock = new MaterialPropertyBlock();
}
propertyBlock.Clear();
Color color = (IsSelected ? SelectedTintColor : Color.white);
propertyBlock.SetColor(BaseColorId, color);
propertyBlock.SetColor(ColorId, color);
propertyBlock.SetColor(TintColorId, color);
propertyBlock.SetColor(EmissionColorId, IsSelected ? (color * 0.06f) : Color.black);
propertyBlock.SetColor(EmissiveColorId, IsSelected ? (color * 0.06f) : Color.black);
meshRenderer.SetPropertyBlock(propertyBlock);
unlitMeshRenderer?.SetPropertyBlock(propertyBlock);
transparentMeshRenderer?.SetPropertyBlock(propertyBlock);
invisibleMeshRenderer?.SetPropertyBlock(propertyBlock);
}
}
private bool IsInBounds(int x, int y, int z)
{
if (x >= 0 && y >= 0 && z >= 0 && x < size.x && y < size.y)
{
return z < size.z;
}
return false;
}
private void EnsureStorageForStoredCell(Vector3Int storedCell)
{
if (voxels == null || voxels.Length == 0 || size.x <= 0 || size.y <= 0 || size.z <= 0)
{
position = storedCell;
size = Vector3Int.one;
voxels = new byte[1];
ApplyGridTransform();
return;
}
Vector3Int vector3Int = StoredCellToLocalCell(storedCell);
Vector3Int vector3Int2 = new Vector3Int(Mathf.Min(0, vector3Int.x), Mathf.Min(0, vector3Int.y), Mathf.Min(0, vector3Int.z));
Vector3Int vector3Int3 = new Vector3Int(Mathf.Max(size.x - 1, vector3Int.x), Mathf.Max(size.y - 1, vector3Int.y), Mathf.Max(size.z - 1, vector3Int.z));
if (vector3Int2 == Vector3Int.zero && vector3Int3.x == size.x - 1 && vector3Int3.y == size.y - 1 && vector3Int3.z == size.z - 1)
{
return;
}
Vector3Int vector3Int4 = new Vector3Int(vector3Int3.x - vector3Int2.x + 1, vector3Int3.y - vector3Int2.y + 1, vector3Int3.z - vector3Int2.z + 1);
byte[] array = new byte[vector3Int4.x * vector3Int4.y * vector3Int4.z];
for (int i = 0; i < size.z; i++)
{
for (int j = 0; j < size.y; j++)
{
for (int k = 0; k < size.x; k++)
{
byte b = voxels[GetFlatIndex(k, j, i)];
if (!PackedVoxel.IsAir(b))
{
int num = k - vector3Int2.x;
int num2 = j - vector3Int2.y;
int num3 = i - vector3Int2.z;
int num4 = num + num2 * vector3Int4.x + num3 * vector3Int4.x * vector3Int4.y;
array[num4] = b;
}
}
}
}
voxels = array;
size = vector3Int4;
position += GridRotationUtility.Rotate(vector3Int2, packedRotation);
ApplyGridTransform();
}
private Vector3Int StoredCellToLocalCell(Vector3Int storedCell)
{
return GridRotationUtility.InverseRotate(storedCell - position, packedRotation);
}
private Vector3Int WorldGridCellToStoredCell(Vector3Int worldCell)
{
if (!useLocalGridTransform || base.transform.parent == null)
{
return worldCell;
}
Vector3 vector = base.transform.parent.InverseTransformPoint((Vector3)worldCell * CurrentVoxelSize) / CurrentVoxelSize;
return new Vector3Int(Mathf.RoundToInt(vector.x), Mathf.RoundToInt(vector.y), Mathf.RoundToInt(vector.z));
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: b7ef3e7bcbb3a48919c057e31858538e
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,1907 @@
using System;
using System.Collections;
using System.Collections.Generic;
using BlockSpace.Gamemodes;
using Photon.Pun;
using UnityEngine;
namespace BlockSpace.Voxels
{
[DisallowMultipleComponent]
public sealed class VoxelGroupManager : MonoBehaviour
{
private enum UndoOperationType : byte
{
Stroke = 0,
Erase = 1,
Merge = 2
}
private sealed class GroupSnapshot
{
public ushort id;
public Vector3Int position;
public byte packedRotation;
public Vector3Int size;
public byte[] voxels;
}
public struct SavedGroupData
{
public Vector3Int position;
public byte packedRotation;
public Vector3Int size;
public byte[] voxels;
public bool IsValid
{
get
{
if (voxels != null && voxels.Length != 0 && size.x > 0 && size.y > 0)
{
return size.z > 0;
}
return false;
}
}
}
private sealed class UndoRecord
{
public UndoOperationType type;
public readonly List<GroupSnapshot> before = new List<GroupSnapshot>();
public readonly List<GroupSnapshot> after = new List<GroupSnapshot>();
}
private const string SpawnClipPath = "Assets/Spawn.mp3";
private const string DeleteClipPath = "Assets/Delete.mp3";
private const string SpawnDestroyParticlePrefabPath = "Assets/Spawn_Destroy Particle.prefab";
public const float DefaultVoxelSize = 0.125f;
[SerializeField]
private float voxelSize = 0.125f;
[SerializeField]
private int maxRebuildsPerFrame = 2;
[SerializeField]
private bool creatorMode = true;
[SerializeField]
private Material opaqueMaterial;
[SerializeField]
private Material unlitMaterial;
[SerializeField]
private Material transparentMaterial;
[SerializeField]
private Material invisibleMaterial;
[SerializeField]
private AudioClip spawnClip;
[SerializeField]
private AudioClip deleteClip;
[SerializeField]
private GameObject spawnDestroyParticlePrefab;
private readonly Dictionary<ushort, VoxelGroup> groups = new Dictionary<ushort, VoxelGroup>();
private readonly Queue<VoxelGroup> rebuildQueue = new Queue<VoxelGroup>();
private readonly HashSet<ushort> queuedIds = new HashSet<ushort>();
private readonly List<ushort> deleteBuffer = new List<ushort>();
private readonly List<VoxelGroup> groupBuffer = new List<VoxelGroup>();
private readonly Stack<UndoRecord> undoStack = new Stack<UndoRecord>();
private readonly Dictionary<ushort, GroupSnapshot> activeEraseBeforeSnapshots = new Dictionary<ushort, GroupSnapshot>();
private readonly HashSet<ushort> activeEraseAffectedIds = new HashSet<ushort>();
private ushort nextId = 1;
private Material[] sharedMaterials;
private bool eraseUndoSessionActive;
private bool isUndoing;
private bool suppressNetworkBroadcast;
private bool editingLocked;
private VoxelGroupPhotonSync networkSync;
public const ushort AvatarGroupIdMin = 60000;
public static VoxelGroupManager Instance { get; private set; }
public float VoxelSize => voxelSize;
public bool CreatorMode => creatorMode;
public bool EditingLocked => editingLocked;
internal Material[] SharedMaterials => sharedMaterials;
internal void RegisterNetworkSync(VoxelGroupPhotonSync sync)
{
networkSync = sync;
}
public VoxelGroup CreateGroupFromStroke(ICollection<Vector3Int> worldCells, byte voxelValue, byte rotation = 0)
{
if (worldCells == null || worldCells.Count == 0 || PackedVoxel.IsAir(voxelValue))
{
return null;
}
Vector3Int vector3Int = new Vector3Int(int.MaxValue, int.MaxValue, int.MaxValue);
Vector3Int vector3Int2 = new Vector3Int(int.MinValue, int.MinValue, int.MinValue);
foreach (Vector3Int worldCell in worldCells)
{
if (worldCell.x < vector3Int.x)
{
vector3Int.x = worldCell.x;
}
if (worldCell.y < vector3Int.y)
{
vector3Int.y = worldCell.y;
}
if (worldCell.z < vector3Int.z)
{
vector3Int.z = worldCell.z;
}
if (worldCell.x > vector3Int2.x)
{
vector3Int2.x = worldCell.x;
}
if (worldCell.y > vector3Int2.y)
{
vector3Int2.y = worldCell.y;
}
if (worldCell.z > vector3Int2.z)
{
vector3Int2.z = worldCell.z;
}
}
Vector3Int size = new Vector3Int(vector3Int2.x - vector3Int.x + 1, vector3Int2.y - vector3Int.y + 1, vector3Int2.z - vector3Int.z + 1);
byte[] array = new byte[size.x * size.y * size.z];
foreach (Vector3Int worldCell2 in worldCells)
{
Vector3Int vector3Int3 = worldCell2 - vector3Int;
int num = vector3Int3.x + vector3Int3.y * size.x + vector3Int3.z * size.x * size.y;
array[num] = voxelValue;
}
VoxelGroup voxelGroup = CreateGroup(vector3Int, rotation, size, array, queueInitialRebuild: true, playSpawnAnimation: true, playSpawnParticles: true);
if (voxelGroup != null)
{
voxelGroup.zoneID = GetCurrentZoneIDForNewGroups();
}
return voxelGroup;
}
public VoxelGroup BeginStrokeGroup(byte rotation = 0)
{
VoxelGroup voxelGroup = CreateGroup(Vector3Int.zero, rotation, Vector3Int.zero, new byte[0], queueInitialRebuild: false, playSpawnAnimation: false, playSpawnParticles: false);
voxelGroup.zoneID = GetCurrentZoneIDForNewGroups();
voxelGroup.SetGreedyRenderMeshing(enabled: false);
return voxelGroup;
}
public bool AddVoxelToStrokeGroup(VoxelGroup group, Vector3Int worldCell, byte voxelValue)
{
if (!ContainsGroup(group) || PackedVoxel.IsAir(voxelValue))
{
return false;
}
return group.SetWorldVoxel(worldCell, voxelValue, markMeshDirty: false, markColliderDirty: false);
}
public void RefreshStrokeGroup(VoxelGroup group)
{
if (ContainsGroup(group))
{
group.RebuildMesh();
}
}
internal bool RasterizeStrokeSegment(VoxelGroup group, Vector3 start, Vector3 end, float radius, byte voxelValue, ISet<Vector3Int> trackedCells = null, bool refreshMesh = true)
{
if (!ContainsGroup(group) || PackedVoxel.IsAir(voxelValue))
{
return false;
}
float num = Mathf.Max(voxelSize * 0.5f, radius * 0.5f);
float num2 = Vector3.Distance(start, end);
int num3 = Mathf.Max(1, Mathf.CeilToInt(num2 / num));
bool flag = false;
for (int i = 0; i <= num3; i++)
{
float t = ((num3 == 0) ? 0f : ((float)i / (float)num3));
Vector3 worldCenter = Vector3.Lerp(start, end, t);
flag |= AddSphereToStrokeGroup(group, worldCenter, radius, voxelValue, trackedCells);
}
if (flag && refreshMesh)
{
RefreshStrokeGroup(group);
}
return flag;
}
internal void DiscardTransientStrokeGroup(VoxelGroup group)
{
if (ContainsGroup(group))
{
RemoveGroupInternal(group, sendRemoveHook: false, playDestroyAnimation: false, playDestroyParticles: false);
}
}
internal void BroadcastStrokePreviewBegin(byte voxelValue, float radius, float forwardOffset, Vector3 startPoint)
{
if (!suppressNetworkBroadcast)
{
ResolveNetworkSync()?.BroadcastStrokePreviewBegin(voxelValue, radius, forwardOffset, startPoint);
}
}
internal void BroadcastStrokePreviewUpdate(byte voxelValue, float radius, float forwardOffset, Vector3 point)
{
if (!suppressNetworkBroadcast)
{
ResolveNetworkSync()?.BroadcastStrokePreviewUpdate(voxelValue, radius, forwardOffset, point);
}
}
internal void BroadcastStrokePreviewEnd()
{
if (!suppressNetworkBroadcast)
{
ResolveNetworkSync()?.BroadcastStrokePreviewEnd();
}
}
internal void BroadcastErasePreviewBegin(float radius, float forwardOffset, Vector3 startPoint)
{
if (!suppressNetworkBroadcast)
{
ResolveNetworkSync()?.BroadcastErasePreviewBegin(radius, forwardOffset, startPoint);
}
}
internal void BroadcastErasePreviewUpdate(float radius, float forwardOffset, Vector3 point)
{
if (!suppressNetworkBroadcast)
{
ResolveNetworkSync()?.BroadcastErasePreviewUpdate(radius, forwardOffset, point);
}
}
internal void BroadcastErasePreviewEnd()
{
if (!suppressNetworkBroadcast)
{
ResolveNetworkSync()?.BroadcastErasePreviewEnd();
}
}
internal bool TryRestoreTransientGroup(ushort id, SavedGroupData data)
{
if (!data.IsValid)
{
ApplyNetworkDestroy(id, playDestroyAnimation: false, playDestroyParticles: false);
return true;
}
ApplyNetworkCreateOrReplace(id, data.position, data.packedRotation, data.size, data.voxels, playSpawnAnimation: false, playSpawnParticles: false);
return true;
}
public void FinalizeStrokeGroup(VoxelGroup group)
{
if (ContainsGroup(group))
{
FinalizeLiveEditedGroup(group, sendNetworkUpdate: false);
RecordStrokeUndo(group, playSpawnAnimationOnRemote: true);
}
}
public void CancelStrokeGroup(VoxelGroup group)
{
if (ContainsGroup(group))
{
DeleteGroup(group);
}
}
private bool AddSphereToStrokeGroup(VoxelGroup group, Vector3 worldCenter, float radius, byte voxelValue, ISet<Vector3Int> trackedCells)
{
float num = radius * radius;
int num2 = Mathf.FloorToInt((worldCenter.x - radius) / voxelSize);
int num3 = Mathf.FloorToInt((worldCenter.y - radius) / voxelSize);
int num4 = Mathf.FloorToInt((worldCenter.z - radius) / voxelSize);
int num5 = Mathf.FloorToInt((worldCenter.x + radius) / voxelSize);
int num6 = Mathf.FloorToInt((worldCenter.y + radius) / voxelSize);
int num7 = Mathf.FloorToInt((worldCenter.z + radius) / voxelSize);
bool flag = false;
for (int i = num4; i <= num7; i++)
{
for (int j = num3; j <= num6; j++)
{
for (int k = num2; k <= num5; k++)
{
if (!((new Vector3(((float)k + 0.5f) * voxelSize, ((float)j + 0.5f) * voxelSize, ((float)i + 0.5f) * voxelSize) - worldCenter).sqrMagnitude > num))
{
Vector3Int vector3Int = new Vector3Int(k, j, i);
if (trackedCells == null || trackedCells.Add(vector3Int))
{
flag |= AddVoxelToStrokeGroup(group, vector3Int, voxelValue);
}
}
}
}
}
return flag;
}
public void DeleteGroup(ushort id)
{
if (groups.TryGetValue(id, out var value))
{
RemoveGroup(value);
}
}
public void DeleteGroup(VoxelGroup group)
{
if (!(group == null))
{
RemoveGroup(group);
}
}
public VoxelGroup MergeGroups(IReadOnlyCollection<VoxelGroup> groupsToMerge)
{
if (groupsToMerge == null || groupsToMerge.Count < 2)
{
return null;
}
List<VoxelGroup> list = new List<VoxelGroup>(groupsToMerge.Count);
foreach (VoxelGroup item in groupsToMerge)
{
if (ContainsGroup(item) && item.size.x > 0 && item.size.y > 0 && item.size.z > 0)
{
list.Add(item);
}
}
if (list.Count < 2)
{
return null;
}
UndoRecord undoRecord = (isUndoing ? null : new UndoRecord
{
type = UndoOperationType.Merge
});
list.Sort((VoxelGroup a, VoxelGroup b) => a.id.CompareTo(b.id));
Vector3Int vector3Int = new Vector3Int(int.MaxValue, int.MaxValue, int.MaxValue);
Vector3Int vector3Int2 = new Vector3Int(int.MinValue, int.MinValue, int.MinValue);
bool flag = false;
for (int num = 0; num < list.Count; num++)
{
VoxelGroup voxelGroup = list[num];
for (int num2 = 0; num2 < voxelGroup.size.z; num2++)
{
for (int num3 = 0; num3 < voxelGroup.size.y; num3++)
{
for (int num4 = 0; num4 < voxelGroup.size.x; num4++)
{
if (!PackedVoxel.IsAir(voxelGroup.GetVoxel(num4, num3, num2)))
{
Vector3Int vector3Int3 = voxelGroup.position + GridRotationUtility.Rotate(new Vector3Int(num4, num3, num2), voxelGroup.packedRotation);
if (vector3Int3.x < vector3Int.x)
{
vector3Int.x = vector3Int3.x;
}
if (vector3Int3.y < vector3Int.y)
{
vector3Int.y = vector3Int3.y;
}
if (vector3Int3.z < vector3Int.z)
{
vector3Int.z = vector3Int3.z;
}
if (vector3Int3.x > vector3Int2.x)
{
vector3Int2.x = vector3Int3.x;
}
if (vector3Int3.y > vector3Int2.y)
{
vector3Int2.y = vector3Int3.y;
}
if (vector3Int3.z > vector3Int2.z)
{
vector3Int2.z = vector3Int3.z;
}
flag = true;
}
}
}
}
}
if (!flag)
{
return null;
}
Vector3Int groupSize = new Vector3Int(vector3Int2.x - vector3Int.x + 1, vector3Int2.y - vector3Int.y + 1, vector3Int2.z - vector3Int.z + 1);
byte[] array = new byte[groupSize.x * groupSize.y * groupSize.z];
for (int num5 = 0; num5 < list.Count; num5++)
{
VoxelGroup voxelGroup2 = list[num5];
for (int num6 = 0; num6 < voxelGroup2.size.z; num6++)
{
for (int num7 = 0; num7 < voxelGroup2.size.y; num7++)
{
for (int num8 = 0; num8 < voxelGroup2.size.x; num8++)
{
byte voxel = voxelGroup2.GetVoxel(num8, num7, num6);
if (!PackedVoxel.IsAir(voxel))
{
Vector3Int vector3Int4 = voxelGroup2.position + GridRotationUtility.Rotate(new Vector3Int(num8, num7, num6), voxelGroup2.packedRotation) - vector3Int;
int num9 = vector3Int4.x + vector3Int4.y * groupSize.x + vector3Int4.z * groupSize.x * groupSize.y;
array[num9] = voxel;
}
}
}
}
}
VoxelGroup voxelGroup3 = list[list.Count - 1];
if (undoRecord != null)
{
for (int num10 = 0; num10 < list.Count; num10++)
{
GroupSnapshot groupSnapshot = CaptureSnapshot(list[num10]);
if (groupSnapshot != null)
{
undoRecord.before.Add(groupSnapshot);
}
}
}
for (int num11 = 0; num11 < list.Count; num11++)
{
VoxelGroup voxelGroup4 = list[num11];
if (!(voxelGroup4 == voxelGroup3))
{
RemoveGroupInternal(voxelGroup4, !suppressNetworkBroadcast, playDestroyAnimation: false, playDestroyParticles: false);
}
}
voxelGroup3.OverwriteData(vector3Int, 0, groupSize, array);
if (undoRecord != null)
{
GroupSnapshot groupSnapshot2 = CaptureSnapshot(voxelGroup3);
if (groupSnapshot2 != null)
{
undoRecord.after.Add(groupSnapshot2);
PushUndoRecord(undoRecord);
}
}
SendUpdateGroupAfterMergeStub(voxelGroup3);
return voxelGroup3;
}
public bool TryCaptureGroupData(VoxelGroup group, out SavedGroupData data)
{
data = default(SavedGroupData);
if (!ContainsGroup(group) || group.voxels == null || group.voxels.Length == 0)
{
return false;
}
data = new SavedGroupData
{
position = group.position,
packedRotation = group.packedRotation,
size = group.size,
voxels = (byte[])group.voxels.Clone()
};
return data.IsValid;
}
public bool TryBuildCombinedGroupData(IReadOnlyCollection<VoxelGroup> groupsToCombine, out SavedGroupData data)
{
data = default(SavedGroupData);
if (groupsToCombine == null || groupsToCombine.Count == 0)
{
return false;
}
List<VoxelGroup> list = new List<VoxelGroup>(groupsToCombine.Count);
foreach (VoxelGroup item in groupsToCombine)
{
if (ContainsGroup(item) && item.size.x > 0 && item.size.y > 0 && item.size.z > 0)
{
list.Add(item);
}
}
if (list.Count == 0)
{
return false;
}
if (list.Count == 1)
{
return TryCaptureGroupData(list[0], out data);
}
list.Sort((VoxelGroup a, VoxelGroup b) => a.id.CompareTo(b.id));
Vector3Int vector3Int = new Vector3Int(int.MaxValue, int.MaxValue, int.MaxValue);
Vector3Int vector3Int2 = new Vector3Int(int.MinValue, int.MinValue, int.MinValue);
bool flag = false;
for (int num = 0; num < list.Count; num++)
{
VoxelGroup voxelGroup = list[num];
for (int num2 = 0; num2 < voxelGroup.size.z; num2++)
{
for (int num3 = 0; num3 < voxelGroup.size.y; num3++)
{
for (int num4 = 0; num4 < voxelGroup.size.x; num4++)
{
if (!PackedVoxel.IsAir(voxelGroup.GetVoxel(num4, num3, num2)))
{
Vector3Int vector3Int3 = voxelGroup.position + GridRotationUtility.Rotate(new Vector3Int(num4, num3, num2), voxelGroup.packedRotation);
if (vector3Int3.x < vector3Int.x)
{
vector3Int.x = vector3Int3.x;
}
if (vector3Int3.y < vector3Int.y)
{
vector3Int.y = vector3Int3.y;
}
if (vector3Int3.z < vector3Int.z)
{
vector3Int.z = vector3Int3.z;
}
if (vector3Int3.x > vector3Int2.x)
{
vector3Int2.x = vector3Int3.x;
}
if (vector3Int3.y > vector3Int2.y)
{
vector3Int2.y = vector3Int3.y;
}
if (vector3Int3.z > vector3Int2.z)
{
vector3Int2.z = vector3Int3.z;
}
flag = true;
}
}
}
}
}
if (!flag)
{
return false;
}
Vector3Int size = new Vector3Int(vector3Int2.x - vector3Int.x + 1, vector3Int2.y - vector3Int.y + 1, vector3Int2.z - vector3Int.z + 1);
byte[] array = new byte[size.x * size.y * size.z];
for (int num5 = 0; num5 < list.Count; num5++)
{
VoxelGroup voxelGroup2 = list[num5];
for (int num6 = 0; num6 < voxelGroup2.size.z; num6++)
{
for (int num7 = 0; num7 < voxelGroup2.size.y; num7++)
{
for (int num8 = 0; num8 < voxelGroup2.size.x; num8++)
{
byte voxel = voxelGroup2.GetVoxel(num8, num7, num6);
if (!PackedVoxel.IsAir(voxel))
{
Vector3Int vector3Int4 = voxelGroup2.position + GridRotationUtility.Rotate(new Vector3Int(num8, num7, num6), voxelGroup2.packedRotation) - vector3Int;
int num9 = vector3Int4.x + vector3Int4.y * size.x + vector3Int4.z * size.x * size.y;
array[num9] = voxel;
}
}
}
}
}
data = new SavedGroupData
{
position = vector3Int,
packedRotation = 0,
size = size,
voxels = array
};
return true;
}
public VoxelGroup SpawnSavedGroup(SavedGroupData data, Vector3Int gridPosition)
{
if (!data.IsValid)
{
return null;
}
VoxelGroup voxelGroup = CreateGroup(gridPosition, data.packedRotation, data.size, data.voxels, queueInitialRebuild: true, playSpawnAnimation: true, playSpawnParticles: true);
if (voxelGroup != null)
{
voxelGroup.zoneID = GetCurrentZoneIDForNewGroups();
}
RecordStrokeUndo(voxelGroup, playSpawnAnimationOnRemote: false, playSpawnParticlesOnRemote: false);
return voxelGroup;
}
public VoxelGroup DuplicateGroup(VoxelGroup source, Vector3Int gridOffset)
{
if (!ContainsGroup(source) || source.voxels == null || source.voxels.Length == 0)
{
return null;
}
VoxelGroup voxelGroup = CreateGroup(source.position + gridOffset, source.packedRotation, source.size, (byte[])source.voxels.Clone(), queueInitialRebuild: true, playSpawnAnimation: true, playSpawnParticles: true);
if (voxelGroup != null)
{
voxelGroup.zoneID = source.zoneID;
RecordStrokeUndo(voxelGroup, playSpawnAnimationOnRemote: false, playSpawnParticlesOnRemote: false);
}
return voxelGroup;
}
public bool TransformGroup(VoxelGroup group, Vector3Int gridPosition, byte rotation, bool sendNetworkUpdate = true)
{
if (!ContainsGroup(group))
{
return false;
}
if (group.position == gridPosition && group.packedRotation == rotation)
{
return true;
}
group.SetGridPose(gridPosition, rotation);
if (sendNetworkUpdate)
{
SendUpdateGroupStub(group);
}
return true;
}
public void SetGroupColliderEnabled(VoxelGroup group, bool enabled)
{
if (ContainsGroup(group) && !(group.meshCollider == null))
{
group.meshCollider.enabled = enabled;
if (!suppressNetworkBroadcast)
{
ResolveNetworkSync()?.BroadcastSetColliderEnabled(group, enabled);
}
}
}
public void BroadcastMovePreview(VoxelGroup group)
{
if (ContainsGroup(group) && !suppressNetworkBroadcast)
{
ResolveNetworkSync()?.BroadcastMovePreview(group);
}
}
public void BroadcastMoveFinal(VoxelGroup group)
{
if (ContainsGroup(group) && !suppressNetworkBroadcast)
{
ResolveNetworkSync()?.BroadcastMoveFinal(group);
}
}
public bool PaintWholeGroup(VoxelGroup group, byte voxelValue)
{
if (!ContainsGroup(group) || PackedVoxel.IsAir(voxelValue))
{
return false;
}
if (!group.RepaintAll(voxelValue))
{
return false;
}
SendUpdateGroupStub(group);
return true;
}
public void BeginEraseUndoSession()
{
if (!isUndoing)
{
activeEraseBeforeSnapshots.Clear();
activeEraseAffectedIds.Clear();
eraseUndoSessionActive = true;
}
}
public void BeginPaintUndoSession()
{
BeginEraseUndoSession();
}
public void CompleteEraseUndoSession()
{
if (!eraseUndoSessionActive)
{
return;
}
eraseUndoSessionActive = false;
if (activeEraseBeforeSnapshots.Count == 0)
{
activeEraseBeforeSnapshots.Clear();
activeEraseAffectedIds.Clear();
return;
}
UndoRecord undoRecord = new UndoRecord
{
type = UndoOperationType.Erase
};
foreach (KeyValuePair<ushort, GroupSnapshot> activeEraseBeforeSnapshot in activeEraseBeforeSnapshots)
{
undoRecord.before.Add(activeEraseBeforeSnapshot.Value);
}
foreach (ushort activeEraseAffectedId in activeEraseAffectedIds)
{
if (groups.TryGetValue(activeEraseAffectedId, out var value))
{
GroupSnapshot groupSnapshot = CaptureSnapshot(value);
if (groupSnapshot != null)
{
undoRecord.after.Add(groupSnapshot);
}
}
}
PushUndoRecord(undoRecord);
activeEraseBeforeSnapshots.Clear();
activeEraseAffectedIds.Clear();
}
public void CompletePaintUndoSession()
{
CompleteEraseUndoSession();
}
public bool UndoLastOperation()
{
if (undoStack.Count == 0)
{
return false;
}
UndoRecord undoRecord = undoStack.Pop();
isUndoing = true;
try
{
for (int i = 0; i < undoRecord.after.Count; i++)
{
GroupSnapshot groupSnapshot = undoRecord.after[i];
if (groups.TryGetValue(groupSnapshot.id, out var value))
{
RemoveGroupInternal(value, !suppressNetworkBroadcast, playDestroyAnimation: true, playDestroyParticles: false);
}
}
for (int j = 0; j < undoRecord.before.Count; j++)
{
RestoreSnapshot(undoRecord.before[j]);
}
}
finally
{
isUndoing = false;
}
return true;
}
public void EraseSphere(Vector3 worldCenter, float radius)
{
if (radius <= 0f || groups.Count == 0)
{
return;
}
groupBuffer.Clear();
foreach (VoxelGroup value2 in groups.Values)
{
groupBuffer.Add(value2);
}
deleteBuffer.Clear();
for (int i = 0; i < groupBuffer.Count; i++)
{
VoxelGroup voxelGroup = groupBuffer[i];
if (!(voxelGroup == null) && voxelGroup.IntersectsSphere(worldCenter, radius) && voxelGroup.EraseSphere(worldCenter, radius))
{
if (!voxelGroup.TrimToContents())
{
deleteBuffer.Add(voxelGroup.id);
continue;
}
QueueRebuild(voxelGroup);
SendUpdateGroupStub(voxelGroup);
}
}
for (int j = 0; j < deleteBuffer.Count; j++)
{
if (groups.TryGetValue(deleteBuffer[j], out var value))
{
RemoveGroupInternal(value, !suppressNetworkBroadcast, playDestroyAnimation: false, playDestroyParticles: false);
}
}
}
public bool EraseSphereRealtime(Vector3 worldCenter, float radius, ISet<VoxelGroup> editedGroups)
{
if (radius <= 0f || groups.Count == 0)
{
return false;
}
groupBuffer.Clear();
foreach (VoxelGroup value2 in groups.Values)
{
groupBuffer.Add(value2);
}
deleteBuffer.Clear();
bool result = false;
for (int i = 0; i < groupBuffer.Count; i++)
{
VoxelGroup voxelGroup = groupBuffer[i];
if (voxelGroup == null || !voxelGroup.IntersectsSphere(worldCenter, radius))
{
continue;
}
GroupSnapshot groupSnapshot = null;
if (eraseUndoSessionActive && !activeEraseBeforeSnapshots.ContainsKey(voxelGroup.id))
{
groupSnapshot = CaptureSnapshot(voxelGroup);
}
if (voxelGroup.EraseSphere(worldCenter, radius, markMeshDirty: false, markColliderDirty: false))
{
result = true;
if (groupSnapshot != null)
{
activeEraseBeforeSnapshots[voxelGroup.id] = groupSnapshot;
}
if (eraseUndoSessionActive)
{
activeEraseAffectedIds.Add(voxelGroup.id);
}
if (!voxelGroup.TrimToContents())
{
deleteBuffer.Add(voxelGroup.id);
editedGroups?.Remove(voxelGroup);
}
else
{
voxelGroup.SetGreedyRenderMeshing(enabled: false);
voxelGroup.RebuildMesh();
editedGroups?.Add(voxelGroup);
}
}
}
for (int j = 0; j < deleteBuffer.Count; j++)
{
if (groups.TryGetValue(deleteBuffer[j], out var value))
{
RemoveGroupInternal(value, !suppressNetworkBroadcast, playDestroyAnimation: false, playDestroyParticles: false);
}
}
return result;
}
public bool PaintSphereRealtime(Vector3 worldCenter, float radius, byte voxelValue, ISet<VoxelGroup> editedGroups)
{
if (radius <= 0f || groups.Count == 0 || PackedVoxel.IsAir(voxelValue))
{
return false;
}
groupBuffer.Clear();
foreach (VoxelGroup value in groups.Values)
{
groupBuffer.Add(value);
}
bool result = false;
for (int i = 0; i < groupBuffer.Count; i++)
{
VoxelGroup voxelGroup = groupBuffer[i];
if (voxelGroup == null || !voxelGroup.IntersectsSphere(worldCenter, radius))
{
continue;
}
GroupSnapshot groupSnapshot = null;
if (eraseUndoSessionActive && !activeEraseBeforeSnapshots.ContainsKey(voxelGroup.id))
{
groupSnapshot = CaptureSnapshot(voxelGroup);
}
if (voxelGroup.PaintSphere(worldCenter, radius, voxelValue, markMeshDirty: false, markColliderDirty: false))
{
result = true;
if (groupSnapshot != null)
{
activeEraseBeforeSnapshots[voxelGroup.id] = groupSnapshot;
}
if (eraseUndoSessionActive)
{
activeEraseAffectedIds.Add(voxelGroup.id);
}
voxelGroup.SetGreedyRenderMeshing(enabled: false);
voxelGroup.RebuildMesh();
editedGroups?.Add(voxelGroup);
}
}
return result;
}
internal bool EraseSpherePreview(Vector3 worldCenter, float radius, ISet<VoxelGroup> editedGroups)
{
bool flag = isUndoing;
bool flag2 = suppressNetworkBroadcast;
isUndoing = true;
suppressNetworkBroadcast = true;
try
{
return EraseSphereRealtime(worldCenter, radius, editedGroups);
}
finally
{
isUndoing = flag;
suppressNetworkBroadcast = flag2;
}
}
public void FinalizeLiveEditedGroup(VoxelGroup group, bool sendNetworkUpdate = true, bool rebuildCollider = true, bool playDestroyAnimationIfEmpty = true)
{
if (!ContainsGroup(group))
{
return;
}
if (!group.TrimToContents())
{
RemoveGroupInternal(group, !suppressNetworkBroadcast, playDestroyAnimationIfEmpty, playDestroyAnimationIfEmpty);
return;
}
group.SetGreedyRenderMeshing(enabled: true);
group.RebuildMesh();
if (rebuildCollider)
{
group.RebuildCollider();
}
else
{
group.dirtyCollider = false;
}
if (sendNetworkUpdate)
{
SendUpdateGroupStub(group);
}
}
public void SetCreatorMode(bool enabled)
{
if (enabled && editingLocked)
{
enabled = false;
}
if (creatorMode == enabled)
{
return;
}
creatorMode = enabled;
foreach (VoxelGroup value in groups.Values)
{
if (!(value == null))
{
value.SetCreatorMode(enabled);
}
}
}
public void SetEditingLocked(bool locked)
{
if (editingLocked != locked)
{
editingLocked = locked;
if (editingLocked && creatorMode)
{
SetCreatorMode(enabled: false);
}
}
}
internal int GetCurrentZoneIDForNewGroups()
{
GamemodeZone gamemodeZone = ((GamemodeZoneManager.Instance != null) ? GamemodeZoneManager.Instance.CurrentZone : null);
if (!(gamemodeZone != null))
{
return -1;
}
return gamemodeZone.zoneID;
}
public void RemoveGroupsByZoneID(int zoneID)
{
if (groups.Count == 0)
{
return;
}
groupBuffer.Clear();
foreach (VoxelGroup value in groups.Values)
{
if (value != null && value.zoneID == zoneID)
{
groupBuffer.Add(value);
}
}
for (int i = 0; i < groupBuffer.Count; i++)
{
RemoveGroup(groupBuffer[i]);
}
groupBuffer.Clear();
}
public int RemoveGroupsForZone(GamemodeZone zone)
{
if (zone == null || groups.Count == 0)
{
return 0;
}
groupBuffer.Clear();
foreach (VoxelGroup value in groups.Values)
{
if (!(value == null) && (value.zoneID == zone.zoneID || zone.ContainsGroup(value)))
{
groupBuffer.Add(value);
}
}
int count = groupBuffer.Count;
for (int i = 0; i < groupBuffer.Count; i++)
{
RemoveGroup(groupBuffer[i]);
}
groupBuffer.Clear();
return count;
}
public bool TryGetGroup(ushort id, out VoxelGroup group)
{
return groups.TryGetValue(id, out group);
}
public bool TryPickNearestVoxelValue(Vector3 worldPoint, float maxRadius, out byte voxelValue)
{
voxelValue = 0;
if (groups.Count == 0 || maxRadius <= 0f)
{
return false;
}
float num = maxRadius * maxRadius;
bool result = false;
groupBuffer.Clear();
foreach (VoxelGroup value in groups.Values)
{
groupBuffer.Add(value);
}
for (int i = 0; i < groupBuffer.Count; i++)
{
VoxelGroup voxelGroup = groupBuffer[i];
if (!(voxelGroup == null) && voxelGroup.IntersectsSphere(worldPoint, maxRadius) && voxelGroup.TryPickNearestVoxelValue(worldPoint, maxRadius, out var voxelValue2, out var _, out var pickedDistanceSquared) && !(pickedDistanceSquared > num))
{
result = true;
num = pickedDistanceSquared;
voxelValue = voxelValue2;
}
}
return result;
}
public List<VoxelGroup> GetGroupsInRadius(Vector3 worldPos, float radius)
{
List<VoxelGroup> list = new List<VoxelGroup>();
float num = radius * radius;
foreach (VoxelGroup value in groups.Values)
{
if (!(value == null) && value.size.x > 0 && value.size.y > 0 && value.size.z > 0 && value.GetWorldBounds().SqrDistance(worldPos) <= num)
{
list.Add(value);
}
}
return list;
}
public List<VoxelGroup> GetAllGroups()
{
List<VoxelGroup> result = new List<VoxelGroup>(groups.Count);
GetAllGroups(result);
return result;
}
public void GetAllGroups(List<VoxelGroup> result)
{
if (result == null)
{
return;
}
result.Clear();
foreach (VoxelGroup value in groups.Values)
{
if (value != null)
{
result.Add(value);
}
}
}
public void ClearAllGroupsForNetworkSync()
{
bool flag = isUndoing;
bool flag2 = suppressNetworkBroadcast;
isUndoing = true;
suppressNetworkBroadcast = true;
try
{
undoStack.Clear();
activeEraseBeforeSnapshots.Clear();
activeEraseAffectedIds.Clear();
eraseUndoSessionActive = false;
groupBuffer.Clear();
foreach (VoxelGroup value in groups.Values)
{
if (value != null)
{
groupBuffer.Add(value);
}
}
for (int i = 0; i < groupBuffer.Count; i++)
{
if (!(groupBuffer[i] != null) || groupBuffer[i].id < 60000)
{
RemoveGroupInternal(groupBuffer[i], sendRemoveHook: false, playDestroyAnimation: false, playDestroyParticles: false);
}
}
}
finally
{
groupBuffer.Clear();
isUndoing = flag;
suppressNetworkBroadcast = flag2;
}
}
public VoxelGroup ApplyNetworkCreateOrReplace(ushort id, Vector3Int position, byte rotation, Vector3Int size, byte[] voxelData, bool playSpawnAnimation, bool playSpawnParticles)
{
return ApplyNetworkCreateOrReplace(id, position, rotation, size, voxelData, -1, playSpawnAnimation, playSpawnParticles);
}
public VoxelGroup ApplyNetworkCreateOrReplace(ushort id, Vector3Int position, byte rotation, Vector3Int size, byte[] voxelData, int zoneID, bool playSpawnAnimation, bool playSpawnParticles)
{
bool flag = isUndoing;
bool flag2 = suppressNetworkBroadcast;
isUndoing = true;
suppressNetworkBroadcast = true;
try
{
VoxelGroup voxelGroup = UpsertGroup(id, position, rotation, size, voxelData, queueInitialRebuild: true, playSpawnAnimation, playSpawnParticles);
if (voxelGroup != null)
{
voxelGroup.zoneID = zoneID;
}
return voxelGroup;
}
finally
{
isUndoing = flag;
suppressNetworkBroadcast = flag2;
}
}
public void ApplyNetworkDestroy(ushort id, bool playDestroyAnimation, bool playDestroyParticles)
{
bool flag = isUndoing;
bool flag2 = suppressNetworkBroadcast;
isUndoing = true;
suppressNetworkBroadcast = true;
try
{
if (groups.TryGetValue(id, out var value))
{
RemoveGroupInternal(value, sendRemoveHook: false, playDestroyAnimation, playDestroyParticles);
}
}
finally
{
isUndoing = flag;
suppressNetworkBroadcast = flag2;
}
}
public void RemoveGroup(VoxelGroup group)
{
RemoveGroupInternal(group, !suppressNetworkBroadcast, playDestroyAnimation: true, playDestroyParticles: true);
}
public void RemoveGroupWithoutAnimation(VoxelGroup group)
{
RemoveGroupInternal(group, !suppressNetworkBroadcast, playDestroyAnimation: false, playDestroyParticles: false);
}
private void RemoveGroupInternal(VoxelGroup group, bool sendRemoveHook, bool playDestroyAnimation, bool playDestroyParticles)
{
if (!(group == null))
{
if (groups.Remove(group.id))
{
queuedIds.Remove(group.id);
}
group.SetSelected(selected: false);
if (sendRemoveHook)
{
SendRemoveGroupStub(group.id, playDestroyAnimation, playDestroyParticles);
}
if (playDestroyAnimation)
{
StartCoroutine(AnimateAndDestroyGroup(group, playDestroyParticles));
}
else
{
UnityEngine.Object.Destroy(group.gameObject);
}
}
}
public ushort GetNextGroupId()
{
return AllocateId();
}
public void AddGroup(VoxelGroup group)
{
if (group == null)
{
return;
}
if (group.id == 0)
{
group.id = GetNextGroupId();
}
if (!groups.ContainsKey(group.id))
{
group.transform.SetParent(base.transform, worldPositionStays: false);
group.SetOwner(this);
groups.Add(group.id, group);
AdvanceNextIdPast(group.id);
if (group.dirtyMesh || group.dirtyCollider)
{
QueueRebuild(group);
}
}
}
internal void QueueRebuild(VoxelGroup group)
{
if (!(group == null) && queuedIds.Add(group.id))
{
rebuildQueue.Enqueue(group);
}
}
private void Awake()
{
if (Instance != null && Instance != this)
{
Debug.LogError("Multiple VoxelGroupManager instances found.", this);
return;
}
Instance = this;
voxelSize = Mathf.Max(0.001f, voxelSize);
maxRebuildsPerFrame = Mathf.Max(1, maxRebuildsPerFrame);
EnsureEffectAssetsAssigned();
BuildSharedMaterials();
RebindExistingGroups();
}
private void Update()
{
int num = 0;
while (num < maxRebuildsPerFrame && rebuildQueue.Count > 0)
{
VoxelGroup voxelGroup = rebuildQueue.Dequeue();
if (voxelGroup == null)
{
continue;
}
queuedIds.Remove(voxelGroup.id);
if (groups.ContainsKey(voxelGroup.id))
{
if (voxelGroup.dirtyMesh)
{
voxelGroup.RebuildMesh();
}
if (voxelGroup.dirtyCollider)
{
voxelGroup.RebuildCollider();
}
num++;
if (voxelGroup.dirtyMesh || voxelGroup.dirtyCollider)
{
QueueRebuild(voxelGroup);
}
}
}
}
private void OnDestroy()
{
if (Instance == this)
{
Instance = null;
}
}
private void OnValidate()
{
EnsureEffectAssetsAssigned();
}
private ushort AllocateId()
{
if (PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null)
{
ushort num = (ushort)((byte)Mathf.Clamp(PhotonNetwork.LocalPlayer.ActorNumber, 1, 255) << 8);
for (ushort num2 = 1; num2 < 255; num2++)
{
ushort num3 = (ushort)(num | num2);
if (!groups.ContainsKey(num3))
{
return num3;
}
}
throw new InvalidOperationException("No free voxel group ids remain for the local network actor.");
}
ushort num4 = nextId;
for (int i = 0; i < 65535; i++)
{
if (num4 == 0)
{
num4 = 1;
}
if (!groups.ContainsKey(num4))
{
nextId = (ushort)(num4 + 1);
if (nextId == 0)
{
nextId = 1;
}
return num4;
}
num4++;
}
throw new InvalidOperationException("No free voxel group ids remain.");
}
private VoxelGroup CreateGroup(Vector3Int gridPosition, byte rotation, Vector3Int size, byte[] voxels, bool queueInitialRebuild, bool playSpawnAnimation, bool playSpawnParticles)
{
ushort nextGroupId = GetNextGroupId();
return UpsertGroup(nextGroupId, gridPosition, rotation, size, voxels, queueInitialRebuild, playSpawnAnimation, playSpawnParticles);
}
private bool ContainsGroup(VoxelGroup group)
{
if (group != null && groups.TryGetValue(group.id, out var value))
{
return value == group;
}
return false;
}
private void RebindExistingGroups()
{
groups.Clear();
queuedIds.Clear();
rebuildQueue.Clear();
VoxelGroup[] componentsInChildren = GetComponentsInChildren<VoxelGroup>(includeInactive: true);
ushort num = 0;
foreach (VoxelGroup voxelGroup in componentsInChildren)
{
if (!(voxelGroup == null))
{
if (voxelGroup.id == 0 || groups.ContainsKey(voxelGroup.id))
{
voxelGroup.id = AllocateSceneId(groups);
}
num = ((voxelGroup.id > num) ? voxelGroup.id : num);
groups[voxelGroup.id] = voxelGroup;
voxelGroup.SetOwner(this);
QueueRebuild(voxelGroup);
}
}
nextId = (ushort)(num + 1);
if (nextId == 0)
{
nextId = 1;
}
}
private ushort AllocateSceneId(Dictionary<ushort, VoxelGroup> existing)
{
for (ushort num = 1; num != 0; num++)
{
if (!existing.ContainsKey(num))
{
return num;
}
}
throw new InvalidOperationException("No free voxel group ids remain.");
}
private void BuildSharedMaterials()
{
opaqueMaterial = EnsureCompatibleMaterial(opaqueMaterial, "BlockSpace_Opaque", transparent: false, useLitShader: true);
unlitMaterial = EnsureCompatibleMaterial(unlitMaterial, "BlockSpace_Unlit", transparent: false, useLitShader: false);
transparentMaterial = EnsureCompatibleMaterial(transparentMaterial, "BlockSpace_Transparent", transparent: true, useLitShader: false);
invisibleMaterial = EnsureCompatibleMaterial(invisibleMaterial, "BlockSpace_Invisible", transparent: true, useLitShader: false);
sharedMaterials = new Material[4] { opaqueMaterial, unlitMaterial, transparentMaterial, invisibleMaterial };
}
private static Material CreateDefaultMaterial(string materialName, bool transparent, bool useLitShader)
{
Shader shader = Shader.Find(GetBlockSpaceVertexColorShaderName(transparent, useLitShader));
if (shader != null)
{
Material obj = new Material(shader)
{
name = materialName
};
ConfigureDefaultMaterial(obj, transparent);
return obj;
}
Shader shader2 = (useLitShader ? (Shader.Find("Universal Render Pipeline/Particles/Lit") ?? Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard")) : (Shader.Find("Universal Render Pipeline/Particles/Unlit") ?? Shader.Find("Universal Render Pipeline/Unlit") ?? Shader.Find("Sprites/Default") ?? Shader.Find("Standard")));
if (shader2 == null)
{
Debug.LogError("BlockSpace could not find a fallback shader for voxel materials.");
return null;
}
Material obj2 = new Material(shader2)
{
name = materialName
};
ConfigureDefaultMaterial(obj2, transparent);
return obj2;
}
private static Material EnsureCompatibleMaterial(Material material, string materialName, bool transparent, bool useLitShader)
{
bool flag = UsesBlockSpaceVertexColorShader(material);
bool flag2 = UsesBlockSpaceVertexColorShader(material, transparent, useLitShader);
if (material == null || !SupportsVertexColorParticles(material) || (flag && !flag2) || (!flag && useLitShader && !UsesLitShader(material)) || (!flag && !useLitShader && UsesLitShader(material)))
{
return CreateDefaultMaterial(materialName, transparent, useLitShader);
}
ConfigureDefaultMaterial(material, transparent);
return material;
}
private static void ConfigureDefaultMaterial(Material material, bool transparent)
{
if (!(material == null))
{
if (material.HasProperty("_BaseColor"))
{
material.SetColor("_BaseColor", Color.white);
}
if (material.HasProperty("_Color"))
{
material.SetColor("_Color", Color.white);
}
if (material.HasProperty("_Smoothness"))
{
material.SetFloat("_Smoothness", transparent ? 0.05f : 0.15f);
}
if (material.HasProperty("_Metallic"))
{
material.SetFloat("_Metallic", 0f);
}
if (material.HasProperty("_BaseMap"))
{
material.SetTexture("_BaseMap", Texture2D.whiteTexture);
}
if (material.HasProperty("_ColorMode"))
{
material.SetFloat("_ColorMode", 0f);
}
if (material.HasProperty("_BaseColorAddSubDiff"))
{
material.SetVector("_BaseColorAddSubDiff", Vector4.zero);
}
if (material.HasProperty("_Surface"))
{
material.SetFloat("_Surface", transparent ? 1f : 0f);
}
if (material.HasProperty("_Blend"))
{
material.SetFloat("_Blend", 0f);
}
if (material.HasProperty("_Cull"))
{
material.SetFloat("_Cull", 2f);
}
if (material.HasProperty("_SrcBlend"))
{
material.SetFloat("_SrcBlend", transparent ? 5f : 1f);
}
if (material.HasProperty("_DstBlend"))
{
material.SetFloat("_DstBlend", transparent ? 10f : 0f);
}
if (material.HasProperty("_SrcBlendAlpha"))
{
material.SetFloat("_SrcBlendAlpha", 1f);
}
if (material.HasProperty("_DstBlendAlpha"))
{
material.SetFloat("_DstBlendAlpha", transparent ? 10f : 0f);
}
if (material.HasProperty("_ZWrite"))
{
material.SetFloat("_ZWrite", transparent ? 0f : 1f);
}
if (material.HasProperty("_AlphaClip"))
{
material.SetFloat("_AlphaClip", 0f);
}
material.renderQueue = (transparent ? 3000 : 2000);
material.enableInstancing = false;
if (transparent)
{
material.SetOverrideTag("RenderType", "Transparent");
material.EnableKeyword("_SURFACE_TYPE_TRANSPARENT");
}
else
{
material.SetOverrideTag("RenderType", "Opaque");
material.DisableKeyword("_SURFACE_TYPE_TRANSPARENT");
}
material.DisableKeyword("_COLOROVERLAY_ON");
material.DisableKeyword("_COLORCOLOR_ON");
material.DisableKeyword("_COLORADDSUBDIFF_ON");
}
}
private static bool SupportsVertexColorParticles(Material material)
{
if (material == null || material.shader == null)
{
return false;
}
if (!material.HasProperty("_ColorMode") && !material.HasProperty("_BaseColorAddSubDiff") && !material.shader.name.Contains("Particles"))
{
return UsesBlockSpaceVertexColorShader(material);
}
return true;
}
private static bool UsesBlockSpaceVertexColorShader(Material material)
{
if (material == null || material.shader == null)
{
return false;
}
string text = material.shader.name;
if (!(text == "BlockSpace/VertexColorURP") && !(text == "BlockSpace/VertexColorURP_Unlit"))
{
return text == "BlockSpace/VertexColorURP_Transparent";
}
return true;
}
private static bool UsesBlockSpaceVertexColorShader(Material material, bool transparent, bool useLitShader)
{
if (material != null && material.shader != null)
{
return material.shader.name == GetBlockSpaceVertexColorShaderName(transparent, useLitShader);
}
return false;
}
private static string GetBlockSpaceVertexColorShaderName(bool transparent, bool useLitShader)
{
if (transparent)
{
return "BlockSpace/VertexColorURP_Transparent";
}
if (!useLitShader)
{
return "BlockSpace/VertexColorURP_Unlit";
}
return "BlockSpace/VertexColorURP";
}
private static bool UsesLitShader(Material material)
{
if (material == null || material.shader == null)
{
return false;
}
string text = material.shader.name;
if (text.Contains("Lit"))
{
return !text.Contains("Unlit");
}
return false;
}
private void SendUpdateGroupAfterMergeStub(VoxelGroup newGroup)
{
if (!(newGroup == null) && !suppressNetworkBroadcast)
{
ResolveNetworkSync()?.BroadcastModify(newGroup);
}
}
private void SendUpdateGroupStub(VoxelGroup group)
{
if (!(group == null) && !suppressNetworkBroadcast)
{
ResolveNetworkSync()?.BroadcastModify(group);
}
}
public void BroadcastGroupModify(VoxelGroup group)
{
SendUpdateGroupStub(group);
}
private void SendRemoveGroupStub(ushort groupId, bool playDestroyAnimation, bool playDestroyParticles)
{
if (groupId != 0 && !suppressNetworkBroadcast)
{
ResolveNetworkSync()?.BroadcastDestroy(groupId, playDestroyAnimation, playDestroyParticles);
}
}
private void RecordStrokeUndo(VoxelGroup group, bool playSpawnAnimationOnRemote, bool playSpawnParticlesOnRemote = true)
{
if (!isUndoing)
{
GroupSnapshot groupSnapshot = CaptureSnapshot(group);
if (groupSnapshot != null)
{
UndoRecord undoRecord = new UndoRecord
{
type = UndoOperationType.Stroke
};
undoRecord.after.Add(groupSnapshot);
PushUndoRecord(undoRecord);
ResolveNetworkSync()?.BroadcastCreate(group, playSpawnAnimationOnRemote, playSpawnParticlesOnRemote);
}
}
}
private GroupSnapshot CaptureSnapshot(VoxelGroup group)
{
if (group == null || group.voxels == null)
{
return null;
}
return new GroupSnapshot
{
id = group.id,
position = group.position,
packedRotation = group.packedRotation,
size = group.size,
voxels = (byte[])group.voxels.Clone()
};
}
private void RestoreSnapshot(GroupSnapshot snapshot)
{
if (snapshot == null)
{
return;
}
bool flag = groups.ContainsKey(snapshot.id);
VoxelGroup voxelGroup = UpsertGroup(snapshot.id, snapshot.position, snapshot.packedRotation, snapshot.size, (byte[])snapshot.voxels.Clone(), queueInitialRebuild: true, !flag, playSpawnParticlesForNewGroup: false);
if (!(voxelGroup == null))
{
if (flag)
{
SendUpdateGroupStub(voxelGroup);
}
else
{
ResolveNetworkSync()?.BroadcastCreate(voxelGroup, playSpawnAnimation: true, playSpawnParticles: false);
}
}
}
private void PushUndoRecord(UndoRecord undoRecord)
{
if (!isUndoing && undoRecord != null && (undoRecord.before.Count != 0 || undoRecord.after.Count != 0))
{
undoStack.Push(undoRecord);
}
}
private VoxelGroup UpsertGroup(ushort id, Vector3Int position, byte rotation, Vector3Int size, byte[] voxels, bool queueInitialRebuild, bool playSpawnAnimationForNewGroup, bool playSpawnParticlesForNewGroup)
{
byte[] voxelData = ((voxels != null) ? ((byte[])voxels.Clone()) : new byte[0]);
if (groups.TryGetValue(id, out var value))
{
value.OverwriteData(position, rotation, size, voxelData, queueInitialRebuild);
AdvanceNextIdPast(id);
return value;
}
GameObject obj = new GameObject($"VoxelGroup_{id:D5}");
obj.transform.SetParent(base.transform, worldPositionStays: false);
VoxelGroup voxelGroup = obj.AddComponent<VoxelGroup>();
voxelGroup.Initialize(this, id, position, rotation, size, voxelData, queueInitialRebuild);
AddGroup(voxelGroup);
if (playSpawnAnimationForNewGroup)
{
StartCoroutine(AnimateSpawnedGroup(voxelGroup, playSpawnParticlesForNewGroup));
}
return voxelGroup;
}
private void AdvanceNextIdPast(ushort id)
{
if (!PhotonNetwork.InRoom && id >= nextId)
{
nextId = (ushort)(id + 1);
if (nextId == 0)
{
nextId = 1;
}
}
}
private void EnsureEffectAssetsAssigned()
{
}
private IEnumerator AnimateSpawnedGroup(VoxelGroup group, bool playParticles)
{
if (!(group == null))
{
if (group.dirtyMesh)
{
group.RebuildMesh();
}
if (group.meshCollider != null)
{
group.meshCollider.enabled = false;
}
Vector3 center = group.GetWorldBounds().center;
Quaternion rotation = group.transform.rotation;
Vector3 localBoundsCenter = GetGroupLocalBoundsCenter(group);
ApplyCenteredAnimatedScale(group, center, rotation, localBoundsCenter, 0.01f);
PlayLifecycleClip(spawnClip, center, 1f);
if (playParticles)
{
SpawnLifecycleParticle(center, rotation);
}
float elapsed = 0f;
while (elapsed < 0.17f)
{
elapsed += Time.unscaledDeltaTime;
float t = Mathf.Clamp01(elapsed / 0.17f);
float uniformScale = Mathf.LerpUnclamped(0.01f, 1.2f, t);
ApplyCenteredAnimatedScale(group, center, rotation, localBoundsCenter, uniformScale);
yield return null;
}
elapsed = 0f;
while (elapsed < 0.18f)
{
elapsed += Time.unscaledDeltaTime;
float t2 = Mathf.Clamp01(elapsed / 0.18f);
float uniformScale2 = Mathf.LerpUnclamped(1.2f, 1f, t2);
ApplyCenteredAnimatedScale(group, center, rotation, localBoundsCenter, uniformScale2);
yield return null;
}
ResetAnimatedGroupTransform(group);
if (group != null && group.meshCollider != null)
{
group.meshCollider.enabled = true;
}
}
}
private IEnumerator AnimateAndDestroyGroup(VoxelGroup group, bool playParticles)
{
if (!(group == null))
{
Vector3 center = group.GetWorldBounds().center;
Quaternion rotation = group.transform.rotation;
Vector3 localBoundsCenter = GetGroupLocalBoundsCenter(group);
if (group.meshCollider != null)
{
group.meshCollider.enabled = false;
}
PlayLifecycleClip(deleteClip, center, 1f);
float elapsed = 0f;
while (elapsed < 0.18f)
{
elapsed += Time.unscaledDeltaTime;
float t = Mathf.Clamp01(elapsed / 0.18f);
float uniformScale = Mathf.LerpUnclamped(1f, 1.2f, t);
ApplyCenteredAnimatedScale(group, center, rotation, localBoundsCenter, uniformScale);
yield return null;
}
elapsed = 0f;
while (elapsed < 0.17f)
{
elapsed += Time.unscaledDeltaTime;
float t2 = Mathf.Clamp01(elapsed / 0.17f);
float uniformScale2 = Mathf.LerpUnclamped(1.2f, 0.01f, t2);
ApplyCenteredAnimatedScale(group, center, rotation, localBoundsCenter, uniformScale2);
yield return null;
}
if (playParticles)
{
SpawnLifecycleParticle(center, rotation);
}
if (group != null)
{
UnityEngine.Object.Destroy(group.gameObject);
}
}
}
private Vector3 GetGroupLocalBoundsCenter(VoxelGroup group)
{
return new Vector3(group.size.x, group.size.y, group.size.z) * (voxelSize * 0.5f);
}
private void ApplyCenteredAnimatedScale(VoxelGroup group, Vector3 worldCenter, Quaternion rotation, Vector3 localBoundsCenter, float uniformScale)
{
if (!(group == null))
{
Vector3 vector = localBoundsCenter * uniformScale;
Vector3 position = worldCenter - rotation * vector;
group.transform.SetPositionAndRotation(position, rotation);
group.transform.localScale = Vector3.one * uniformScale;
}
}
private void ResetAnimatedGroupTransform(VoxelGroup group)
{
if (!(group == null))
{
group.transform.localScale = Vector3.one;
group.transform.SetPositionAndRotation((Vector3)group.position * voxelSize, GridRotationUtility.ToQuaternion(group.packedRotation));
}
}
private void PlayLifecycleClip(AudioClip clip, Vector3 worldPosition, float volume)
{
if (!(clip == null))
{
AudioSource.PlayClipAtPoint(clip, worldPosition, volume);
}
}
private void SpawnLifecycleParticle(Vector3 worldPosition, Quaternion rotation)
{
if (!(spawnDestroyParticlePrefab == null))
{
UnityEngine.Object.Destroy(UnityEngine.Object.Instantiate(spawnDestroyParticlePrefab, worldPosition, rotation), 0.7f);
}
}
private VoxelGroupPhotonSync ResolveNetworkSync()
{
if (networkSync == null)
{
TryGetComponent<VoxelGroupPhotonSync>(out networkSync);
}
return networkSync;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: e86aa0bc365a32736368b1d0f08298f0
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,1494 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using BlockSpace.Account;
using BlockSpace.Nodes;
using BlockSpace.Worlds;
using Photon.Pun;
using Photon.Realtime;
using Photon.VR;
using Photon.VR.Player;
using UnityEngine;
namespace BlockSpace.Voxels
{
[DisallowMultipleComponent]
[RequireComponent(typeof(PhotonView))]
public sealed class VoxelGroupPhotonSync : MonoBehaviourPunCallbacks
{
private sealed class StrokePreviewState
{
public VoxelGroup group;
public byte voxelValue;
public float radius;
public float forwardOffset;
public Vector3 lastPoint;
public readonly HashSet<Vector3Int> cells = new HashSet<Vector3Int>();
}
private sealed class ErasePreviewState
{
public float radius;
public float forwardOffset;
public Vector3 lastPoint;
public readonly Dictionary<ushort, VoxelGroupManager.SavedGroupData> beforeByGroupId = new Dictionary<ushort, VoxelGroupManager.SavedGroupData>();
}
private sealed class MovePreviewState
{
public VoxelGroup group;
public Vector3 targetPosition;
public Quaternion targetRotation;
public Vector3 velocity;
public float lastPacketTime;
}
[SerializeField]
private VoxelGroupManager groupManager;
[SerializeField]
private NodeGraphManager nodeGraphManager;
[SerializeField]
private float lateJoinReplayDelay = 0.5f;
[Header("World Persistence")]
[SerializeField]
private bool enableWorldPersistence = true;
[SerializeField]
private string backendBaseUrl = "https://blockspacebackend-production.up.railway.app";
[SerializeField]
private float autoSaveIntervalSeconds = 30f;
[SerializeField]
private float permissionRefreshIntervalSeconds = 5f;
[Header("World Sync")]
[SerializeField]
private float authoritySnapshotIntervalSeconds = 5f;
[SerializeField]
private float localEditSnapshotGraceSeconds = 3f;
private readonly List<VoxelGroup> syncBuffer = new List<VoxelGroup>();
private readonly List<VoxelGroup> persistBuffer = new List<VoxelGroup>();
private readonly Dictionary<int, StrokePreviewState> strokePreviews = new Dictionary<int, StrokePreviewState>();
private readonly Dictionary<int, ErasePreviewState> erasePreviews = new Dictionary<int, ErasePreviewState>();
private readonly Dictionary<int, Transform> rightHandCache = new Dictionary<int, Transform>();
private readonly Dictionary<ushort, MovePreviewState> movePreviews = new Dictionary<ushort, MovePreviewState>();
private readonly List<ushort> movePreviewCleanup = new List<ushort>();
private PhotonView cachedPhotonView;
private Coroutine worldJoinCoroutine;
private Coroutine worldAutoSaveCoroutine;
private Coroutine worldPermissionRefreshCoroutine;
private Coroutine worldTeleportCoroutine;
private bool pendingWorldTeleportRestart;
private bool currentTeleportShouldCompleteLoading;
private bool pendingTeleportShouldCompleteLoading;
private Coroutine authoritySnapshotCoroutine;
private WorldBackendClient worldBackendClient;
private string currentWorldId = string.Empty;
private bool currentWorldExists;
private bool isCreatorForCurrentWorld;
private bool worldDirty;
private bool suppressWorldDirty;
private string cachedPlayerId = string.Empty;
private string cachedBaseWorldName = string.Empty;
private float lastLocalNetworkEditSentAt = float.NegativeInfinity;
private void Reset()
{
groupManager = GetComponent<VoxelGroupManager>();
nodeGraphManager = GetComponent<NodeGraphManager>();
}
private void Awake()
{
cachedPhotonView = GetComponent<PhotonView>();
if (groupManager == null)
{
groupManager = GetComponent<VoxelGroupManager>();
if (groupManager == null)
{
groupManager = UnityEngine.Object.FindAnyObjectByType<VoxelGroupManager>();
}
}
if (nodeGraphManager == null)
{
nodeGraphManager = GetComponent<NodeGraphManager>();
if (nodeGraphManager == null)
{
nodeGraphManager = UnityEngine.Object.FindAnyObjectByType<NodeGraphManager>();
}
}
groupManager?.RegisterNetworkSync(this);
}
public override void OnEnable()
{
base.OnEnable();
groupManager?.RegisterNetworkSync(this);
}
public override void OnDisable()
{
ClearStrokePreviews();
ClearMovePreviews(restoreColliders: true);
rightHandCache.Clear();
StopWorldCoroutines();
base.OnDisable();
}
private void Update()
{
UpdateMovePreviews();
}
public override void OnJoinedRoom()
{
Debug.Log($"[VoxelGroupPhotonSync] OnJoinedRoom: joined room '{PhotonNetwork.CurrentRoom?.Name}', isMaster={PhotonNetwork.IsMasterClient}.");
SetLoadingCheckpoint(0.68f);
if (!(groupManager == null))
{
ClearStrokePreviews();
ClearMovePreviews(restoreColliders: false);
rightHandCache.Clear();
lastLocalNetworkEditSentAt = float.NegativeInfinity;
if (!PhotonNetwork.IsMasterClient)
{
groupManager.ClearAllGroupsForNetworkSync();
}
if (enableWorldPersistence)
{
StopWorldCoroutines();
worldJoinCoroutine = StartCoroutine(WorldResolveAndMaybeLoad());
}
StartAuthoritySnapshotLoopIfNeeded();
}
}
public override void OnLeftRoom()
{
StopWorldCoroutines();
StopAuthoritySnapshotLoop();
ClearMovePreviews(restoreColliders: false);
lastLocalNetworkEditSentAt = float.NegativeInfinity;
if (groupManager != null)
{
groupManager.ClearAllGroupsForNetworkSync();
}
base.OnLeftRoom();
}
public override void OnMasterClientSwitched(Player newMasterClient)
{
base.OnMasterClientSwitched(newMasterClient);
if (enableWorldPersistence && newMasterClient != null && newMasterClient == PhotonNetwork.LocalPlayer)
{
StartWorldAutoSaveIfNeeded();
}
}
public override void OnPlayerEnteredRoom(Player newPlayer)
{
if (PhotonNetwork.IsMasterClient && !(groupManager == null) && newPlayer != null)
{
StartCoroutine(SendExistingGroupsToPlayerAfterDelay(newPlayer));
}
}
public override void OnPlayerLeftRoom(Player otherPlayer)
{
if (otherPlayer != null)
{
EndStrokePreview(otherPlayer.ActorNumber);
EndErasePreview(otherPlayer.ActorNumber);
rightHandCache.Remove(otherPlayer.ActorNumber);
}
}
private IEnumerator SendExistingGroupsToPlayerAfterDelay(Player newPlayer)
{
float num = Mathf.Max(0f, lateJoinReplayDelay);
if (num > 0f)
{
yield return new WaitForSecondsRealtime(num);
}
else
{
yield return null;
}
if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || groupManager == null || newPlayer == null)
{
yield break;
}
syncBuffer.Clear();
syncBuffer.AddRange(groupManager.GetAllGroups());
for (int i = 0; i < syncBuffer.Count; i++)
{
if (!IsStrokePreviewGroup(syncBuffer[i]))
{
BroadcastCreate(syncBuffer[i], newPlayer, playSpawnAnimation: false, playSpawnParticles: false);
}
}
}
internal void BroadcastCreate(VoxelGroup group, bool playSpawnAnimation, bool playSpawnParticles = true)
{
BroadcastCreate(group, null, playSpawnAnimation, playSpawnParticles);
}
internal void BroadcastModify(VoxelGroup group)
{
if (CanSendRpc() && !(group == null))
{
MarkLocalNetworkEditSent();
MarkWorldDirty();
byte[] array = ((group.voxels != null) ? ((byte[])group.voxels.Clone()) : new byte[0]);
cachedPhotonView.RPC("RPC_ModifyGroup", RpcTarget.Others, (int)group.id, group.position.x, group.position.y, group.position.z, group.zoneID, (int)group.packedRotation, group.size.x, group.size.y, group.size.z, array);
}
}
internal void BroadcastMovePreview(VoxelGroup group)
{
if (CanSendRpc() && !(group == null) && PhotonNetwork.LocalPlayer != null)
{
MarkLocalNetworkEditSent();
cachedPhotonView.RPC("RPC_MovePreview", RpcTarget.Others, PhotonNetwork.LocalPlayer.ActorNumber, (int)group.id, group.position.x, group.position.y, group.position.z, (int)group.packedRotation);
}
}
internal void BroadcastMoveFinal(VoxelGroup group)
{
BroadcastModify(group);
}
internal void BroadcastSetColliderEnabled(VoxelGroup group, bool enabled)
{
if (CanSendRpc() && !(group == null))
{
MarkLocalNetworkEditSent();
MarkWorldDirty();
cachedPhotonView.RPC("RPC_SetGroupColliderEnabled", RpcTarget.Others, (int)group.id, enabled);
}
}
internal void BroadcastDestroy(ushort groupId, bool playDestroyAnimation = true, bool playDestroyParticles = true)
{
if (CanSendRpc() && groupId != 0)
{
MarkLocalNetworkEditSent();
MarkWorldDirty();
cachedPhotonView.RPC("RPC_DestroyGroup", RpcTarget.Others, (int)groupId, playDestroyAnimation, playDestroyParticles);
}
}
internal void BroadcastStrokePreviewBegin(byte voxelValue, float radius, float forwardOffset, Vector3 startPoint)
{
if (CanSendStrokePreviewRpc(voxelValue, radius))
{
MarkLocalNetworkEditSent();
cachedPhotonView.RPC("RPC_BeginStrokePreview", RpcTarget.Others, PhotonNetwork.LocalPlayer.ActorNumber, (int)voxelValue, radius, forwardOffset, startPoint.x, startPoint.y, startPoint.z);
}
}
internal void BroadcastStrokePreviewUpdate(byte voxelValue, float radius, float forwardOffset, Vector3 point)
{
if (CanSendStrokePreviewRpc(voxelValue, radius))
{
MarkLocalNetworkEditSent();
cachedPhotonView.RPC("RPC_UpdateStrokePreview", RpcTarget.Others, PhotonNetwork.LocalPlayer.ActorNumber, (int)voxelValue, radius, forwardOffset, point.x, point.y, point.z);
}
}
internal void BroadcastStrokePreviewEnd()
{
if (CanSendRpc() && PhotonNetwork.LocalPlayer != null)
{
MarkLocalNetworkEditSent();
cachedPhotonView.RPC("RPC_EndStrokePreview", RpcTarget.Others, PhotonNetwork.LocalPlayer.ActorNumber);
}
}
internal void BroadcastErasePreviewBegin(float radius, float forwardOffset, Vector3 startPoint)
{
if (CanSendRpc() && PhotonNetwork.LocalPlayer != null && !(radius <= 0f))
{
MarkLocalNetworkEditSent();
cachedPhotonView.RPC("RPC_BeginErasePreview", RpcTarget.Others, PhotonNetwork.LocalPlayer.ActorNumber, radius, forwardOffset, startPoint.x, startPoint.y, startPoint.z);
}
}
internal void BroadcastErasePreviewUpdate(float radius, float forwardOffset, Vector3 point)
{
if (CanSendRpc() && PhotonNetwork.LocalPlayer != null && !(radius <= 0f))
{
MarkLocalNetworkEditSent();
cachedPhotonView.RPC("RPC_UpdateErasePreview", RpcTarget.Others, PhotonNetwork.LocalPlayer.ActorNumber, radius, forwardOffset, point.x, point.y, point.z);
}
}
internal void BroadcastErasePreviewEnd()
{
if (CanSendRpc() && PhotonNetwork.LocalPlayer != null)
{
MarkLocalNetworkEditSent();
cachedPhotonView.RPC("RPC_EndErasePreview", RpcTarget.Others, PhotonNetwork.LocalPlayer.ActorNumber);
}
}
[PunRPC]
private void RPC_CreateGroup(int groupId, int posX, int posY, int posZ, int zoneId, int packedRotation, int sizeX, int sizeY, int sizeZ, byte[] voxelData, bool playSpawnAnimation, bool playSpawnParticles, PhotonMessageInfo info)
{
if (!(groupManager == null))
{
EndPreviewForIncomingGroupEdit(groupId, info, includeStrokePreview: true);
groupManager.ApplyNetworkCreateOrReplace((ushort)groupId, new Vector3Int(posX, posY, posZ), (byte)packedRotation, new Vector3Int(sizeX, sizeY, sizeZ), voxelData, zoneId, playSpawnAnimation, playSpawnParticles);
EndMovePreview((ushort)groupId, restoreCollider: true);
if (PhotonNetwork.IsMasterClient)
{
MarkWorldDirty();
}
}
}
[PunRPC]
private void RPC_ModifyGroup(int groupId, int posX, int posY, int posZ, int zoneId, int packedRotation, int sizeX, int sizeY, int sizeZ, byte[] voxelData, PhotonMessageInfo info)
{
if (!(groupManager == null))
{
EndPreviewForIncomingGroupEdit(groupId, info, includeStrokePreview: false);
groupManager.ApplyNetworkCreateOrReplace((ushort)groupId, new Vector3Int(posX, posY, posZ), (byte)packedRotation, new Vector3Int(sizeX, sizeY, sizeZ), voxelData, zoneId, playSpawnAnimation: false, playSpawnParticles: false);
EndMovePreview((ushort)groupId, restoreCollider: true);
if (PhotonNetwork.IsMasterClient)
{
MarkWorldDirty();
}
}
}
[PunRPC]
private void RPC_MovePreview(int actorNumber, int groupId, int posX, int posY, int posZ, int packedRotation, PhotonMessageInfo info)
{
if (!(groupManager == null) && groupId > 0 && (PhotonNetwork.LocalPlayer == null || actorNumber != PhotonNetwork.LocalPlayer.ActorNumber) && groupManager.TryGetGroup((ushort)groupId, out var group) && !(group == null))
{
ushort key = (ushort)groupId;
Vector3 vector = new Vector3(posX, posY, posZ) * groupManager.VoxelSize;
Quaternion targetRotation = GridRotationUtility.ToQuaternion((byte)packedRotation);
float realtimeSinceStartup = Time.realtimeSinceStartup;
if (!movePreviews.TryGetValue(key, out var value) || value.group == null)
{
value = new MovePreviewState
{
group = group,
targetPosition = group.transform.position,
targetRotation = group.transform.rotation,
lastPacketTime = realtimeSinceStartup
};
movePreviews[key] = value;
}
float num = Mathf.Max(0.001f, realtimeSinceStartup - value.lastPacketTime);
value.velocity = (vector - value.targetPosition) / num;
value.targetPosition = vector;
value.targetRotation = targetRotation;
value.lastPacketTime = realtimeSinceStartup;
groupManager.SetGroupColliderEnabled(group, enabled: false);
}
}
[PunRPC]
private void RPC_DestroyGroup(int groupId, bool playDestroyAnimation, bool playDestroyParticles, PhotonMessageInfo info)
{
if (!(groupManager == null))
{
EndPreviewForIncomingGroupEdit(groupId, info, includeStrokePreview: false);
groupManager.ApplyNetworkDestroy((ushort)groupId, playDestroyAnimation, playDestroyParticles);
EndMovePreview((ushort)groupId, restoreCollider: false);
if (PhotonNetwork.IsMasterClient)
{
MarkWorldDirty();
}
}
}
[PunRPC]
private void RPC_BeginStrokePreview(int actorNumber, int voxelValue, float radius, float forwardOffset, float pointX, float pointY, float pointZ)
{
if (!ShouldIgnoreStrokePreview(actorNumber, voxelValue, radius))
{
EndStrokePreview(actorNumber);
byte voxelValue2 = (byte)voxelValue;
Vector3 fallbackPoint = new Vector3(pointX, pointY, pointZ);
Vector3 vector = ResolveRightHandStrokePoint(actorNumber, fallbackPoint, forwardOffset);
VoxelGroup voxelGroup = groupManager.BeginStrokeGroup(0);
StrokePreviewState strokePreviewState = new StrokePreviewState
{
group = voxelGroup,
voxelValue = voxelValue2,
radius = radius,
forwardOffset = forwardOffset,
lastPoint = vector
};
strokePreviews[actorNumber] = strokePreviewState;
groupManager.RasterizeStrokeSegment(voxelGroup, vector, vector, radius, voxelValue2, strokePreviewState.cells);
}
}
[PunRPC]
private void RPC_UpdateStrokePreview(int actorNumber, int voxelValue, float radius, float forwardOffset, float pointX, float pointY, float pointZ)
{
if (!ShouldIgnoreStrokePreview(actorNumber, voxelValue, radius))
{
byte voxelValue2 = (byte)voxelValue;
Vector3 fallbackPoint = new Vector3(pointX, pointY, pointZ);
Vector3 vector = ResolveRightHandStrokePoint(actorNumber, fallbackPoint, forwardOffset);
if (!strokePreviews.TryGetValue(actorNumber, out var value) || value.group == null)
{
RPC_BeginStrokePreview(actorNumber, voxelValue, radius, forwardOffset, pointX, pointY, pointZ);
return;
}
value.voxelValue = voxelValue2;
value.radius = radius;
value.forwardOffset = forwardOffset;
groupManager.RasterizeStrokeSegment(value.group, value.lastPoint, vector, radius, voxelValue2, value.cells);
value.lastPoint = vector;
}
}
[PunRPC]
private void RPC_EndStrokePreview(int actorNumber)
{
EndStrokePreview(actorNumber);
}
[PunRPC]
private void RPC_BeginErasePreview(int actorNumber, float radius, float forwardOffset, float pointX, float pointY, float pointZ)
{
if (!ShouldIgnoreErasePreview(actorNumber, radius))
{
EndErasePreview(actorNumber);
Vector3 fallbackPoint = new Vector3(pointX, pointY, pointZ);
Vector3 vector = ResolveRightHandStrokePoint(actorNumber, fallbackPoint, forwardOffset);
ErasePreviewState erasePreviewState = new ErasePreviewState
{
radius = radius,
forwardOffset = forwardOffset,
lastPoint = vector
};
erasePreviews[actorNumber] = erasePreviewState;
ApplyErasePreviewSegment(erasePreviewState, vector, vector);
}
}
[PunRPC]
private void RPC_UpdateErasePreview(int actorNumber, float radius, float forwardOffset, float pointX, float pointY, float pointZ)
{
if (!ShouldIgnoreErasePreview(actorNumber, radius))
{
Vector3 fallbackPoint = new Vector3(pointX, pointY, pointZ);
Vector3 vector = ResolveRightHandStrokePoint(actorNumber, fallbackPoint, forwardOffset);
if (!erasePreviews.TryGetValue(actorNumber, out var value))
{
RPC_BeginErasePreview(actorNumber, radius, forwardOffset, pointX, pointY, pointZ);
return;
}
value.radius = radius;
value.forwardOffset = forwardOffset;
ApplyErasePreviewSegment(value, value.lastPoint, vector);
value.lastPoint = vector;
}
}
[PunRPC]
private void RPC_EndErasePreview(int actorNumber)
{
EndErasePreview(actorNumber);
}
private void BroadcastCreate(VoxelGroup group, Player targetPlayer, bool playSpawnAnimation, bool playSpawnParticles)
{
if (CanSendRpc() && !(group == null))
{
byte[] array = ((group.voxels != null) ? ((byte[])group.voxels.Clone()) : new byte[0]);
if (targetPlayer == null)
{
MarkLocalNetworkEditSent();
MarkWorldDirty();
cachedPhotonView.RPC("RPC_CreateGroup", RpcTarget.Others, (int)group.id, group.position.x, group.position.y, group.position.z, group.zoneID, (int)group.packedRotation, group.size.x, group.size.y, group.size.z, array, playSpawnAnimation, playSpawnParticles);
}
else
{
cachedPhotonView.RPC("RPC_CreateGroup", targetPlayer, (int)group.id, group.position.x, group.position.y, group.position.z, group.zoneID, (int)group.packedRotation, group.size.x, group.size.y, group.size.z, array, playSpawnAnimation, playSpawnParticles);
}
}
}
private void StopWorldCoroutines()
{
if (worldJoinCoroutine != null)
{
StopCoroutine(worldJoinCoroutine);
worldJoinCoroutine = null;
}
if (worldAutoSaveCoroutine != null)
{
StopCoroutine(worldAutoSaveCoroutine);
worldAutoSaveCoroutine = null;
}
if (worldPermissionRefreshCoroutine != null)
{
StopCoroutine(worldPermissionRefreshCoroutine);
worldPermissionRefreshCoroutine = null;
}
if (worldTeleportCoroutine != null)
{
StopCoroutine(worldTeleportCoroutine);
worldTeleportCoroutine = null;
}
pendingWorldTeleportRestart = false;
currentTeleportShouldCompleteLoading = false;
pendingTeleportShouldCompleteLoading = false;
StopAuthoritySnapshotLoop();
currentWorldId = string.Empty;
currentWorldExists = false;
isCreatorForCurrentWorld = false;
worldDirty = false;
suppressWorldDirty = false;
cachedBaseWorldName = string.Empty;
}
private void StartAuthoritySnapshotLoopIfNeeded()
{
if (authoritySnapshotCoroutine == null && PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && !(groupManager == null))
{
authoritySnapshotCoroutine = StartCoroutine(AuthoritySnapshotLoop());
}
}
private void StopAuthoritySnapshotLoop()
{
if (authoritySnapshotCoroutine != null)
{
StopCoroutine(authoritySnapshotCoroutine);
authoritySnapshotCoroutine = null;
}
}
private IEnumerator AuthoritySnapshotLoop()
{
while (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && cachedPhotonView != null && groupManager != null)
{
float time = Mathf.Clamp(authoritySnapshotIntervalSeconds, 1.5f, 30f);
yield return new WaitForSecondsRealtime(time);
if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && !(groupManager == null))
{
List<VoxelGroup> persistableWorldGroups = GetPersistableWorldGroups();
int[] array = new int[persistableWorldGroups.Count];
int[] array2 = new int[persistableWorldGroups.Count];
for (int i = 0; i < persistableWorldGroups.Count; i++)
{
VoxelGroup voxelGroup = persistableWorldGroups[i];
array[i] = ((voxelGroup != null) ? voxelGroup.id : 0);
array2[i] = ((voxelGroup != null) ? ComputeFastGroupHash(voxelGroup) : 0);
}
cachedPhotonView.RPC("RPC_AuthoritySnapshot", RpcTarget.Others, array, array2);
}
}
authoritySnapshotCoroutine = null;
}
private List<VoxelGroup> GetPersistableWorldGroups()
{
persistBuffer.Clear();
if (groupManager == null)
{
return persistBuffer;
}
syncBuffer.Clear();
syncBuffer.AddRange(groupManager.GetAllGroups());
for (int i = 0; i < syncBuffer.Count; i++)
{
VoxelGroup voxelGroup = syncBuffer[i];
if (!(voxelGroup == null) && !IsStrokePreviewGroup(voxelGroup) && voxelGroup.id < 60000)
{
persistBuffer.Add(voxelGroup);
}
}
return persistBuffer;
}
[PunRPC]
private void RPC_SetGroupColliderEnabled(int groupId, bool enabled, PhotonMessageInfo info)
{
if (!(groupManager == null) && groupId > 0)
{
if (groupManager.TryGetGroup((ushort)groupId, out var group) && group != null)
{
groupManager.SetGroupColliderEnabled(group, enabled);
}
if (PhotonNetwork.IsMasterClient)
{
MarkWorldDirty();
}
}
}
private static Dictionary<int, int> BuildGroupSaveIndexMap(IList<VoxelGroup> groups)
{
Dictionary<int, int> dictionary = new Dictionary<int, int>();
if (groups == null)
{
return dictionary;
}
for (int i = 0; i < groups.Count; i++)
{
VoxelGroup voxelGroup = groups[i];
if (voxelGroup != null)
{
dictionary[voxelGroup.id] = i;
}
}
return dictionary;
}
private static int ComputeFastGroupHash(VoxelGroup group)
{
uint num = 2166136261u;
num = (num ^ (uint)group.position.x) * 16777619;
num = (num ^ (uint)group.position.y) * 16777619;
num = (num ^ (uint)group.position.z) * 16777619;
num = (num ^ group.packedRotation) * 16777619;
num = (num ^ (uint)group.size.x) * 16777619;
num = (num ^ (uint)group.size.y) * 16777619;
num = (num ^ (uint)group.size.z) * 16777619;
byte[] voxels = group.voxels;
int num2 = ((voxels != null) ? voxels.Length : 0);
num = (num ^ (uint)num2) * 16777619;
if (num2 <= 0)
{
return (int)num;
}
int num3 = Mathf.Max(1, num2 / 256);
for (int i = 0; i < num2; i += num3)
{
num = (num ^ voxels[i]) * 16777619;
}
num = (num ^ voxels[0]) * 16777619;
return (int)((num ^ voxels[num2 - 1]) * 16777619);
}
[PunRPC]
private void RPC_AuthoritySnapshot(int[] groupIds, int[] hashes, PhotonMessageInfo info)
{
if (groupManager == null || PhotonNetwork.IsMasterClient || groupIds == null || hashes == null || groupIds.Length != hashes.Length || ShouldDeferAuthoritySnapshot())
{
return;
}
int num = 0;
for (int i = 0; i < groupIds.Length; i++)
{
int num2 = groupIds[i];
if (num2 > 0 && num2 < 60000)
{
if (!groupManager.TryGetGroup((ushort)num2, out var group) || group == null)
{
num++;
}
else if (ComputeFastGroupHash(group) != hashes[i])
{
num++;
}
}
}
if (num <= 0 || cachedPhotonView == null)
{
return;
}
if (num > 6 || groupIds.Length > 64)
{
groupManager.ClearAllGroupsForNetworkSync();
cachedPhotonView.RPC("RPC_RequestFullSnapshot", RpcTarget.MasterClient, PhotonNetwork.LocalPlayer.ActorNumber);
return;
}
for (int j = 0; j < groupIds.Length; j++)
{
int num3 = groupIds[j];
if (num3 > 0 && num3 < 60000 && (!groupManager.TryGetGroup((ushort)num3, out var group2) || group2 == null || ComputeFastGroupHash(group2) != hashes[j]))
{
cachedPhotonView.RPC("RPC_RequestGroupSnapshot", RpcTarget.MasterClient, num3, PhotonNetwork.LocalPlayer.ActorNumber);
}
}
}
[PunRPC]
private void RPC_RequestFullSnapshot(int actorNumber, PhotonMessageInfo info)
{
if (PhotonNetwork.IsMasterClient && !(groupManager == null))
{
Player player = ((PhotonNetwork.CurrentRoom != null) ? PhotonNetwork.CurrentRoom.GetPlayer(actorNumber) : null);
if (player != null)
{
StartCoroutine(SendExistingGroupsToPlayerAfterDelay(player));
}
}
}
[PunRPC]
private void RPC_RequestGroupSnapshot(int groupId, int actorNumber, PhotonMessageInfo info)
{
if (PhotonNetwork.IsMasterClient && !(groupManager == null) && groupId > 0)
{
Player player = ((PhotonNetwork.CurrentRoom != null) ? PhotonNetwork.CurrentRoom.GetPlayer(actorNumber) : null);
if (player != null && groupManager.TryGetGroup((ushort)groupId, out var group) && !(group == null))
{
BroadcastCreate(group, player, playSpawnAnimation: false, playSpawnParticles: false);
}
}
}
private void MarkLocalNetworkEditSent()
{
lastLocalNetworkEditSentAt = Time.realtimeSinceStartup;
}
private void EndPreviewForIncomingGroupEdit(int groupId, PhotonMessageInfo info, bool includeStrokePreview)
{
int num = ((info.Sender != null) ? info.Sender.ActorNumber : 0);
if (num <= 0)
{
num = GetActorNumberFromGroupId(groupId);
}
if (num > 0)
{
EndErasePreview(num);
if (includeStrokePreview)
{
EndStrokePreview(num);
}
}
}
private bool ShouldDeferAuthoritySnapshot()
{
if (strokePreviews.Count > 0 || erasePreviews.Count > 0)
{
return true;
}
float num = Mathf.Clamp(localEditSnapshotGraceSeconds, 0f, 10f);
if (num <= 0f)
{
return false;
}
return Time.realtimeSinceStartup - lastLocalNetworkEditSentAt < num;
}
private void MarkWorldDirty()
{
if (!suppressWorldDirty && enableWorldPersistence && currentWorldExists && !string.IsNullOrEmpty(currentWorldId))
{
worldDirty = true;
}
}
private void EnsureWorldBackendClient()
{
if (worldBackendClient == null)
{
worldBackendClient = new WorldBackendClient(backendBaseUrl);
}
}
private void ScheduleTeleportAfterWorldLoad(bool isMaster, bool forceRestart = false, bool completeLoading = false)
{
Debug.Log($"[VoxelGroupPhotonSync] ScheduleTeleportAfterWorldLoad: isMaster={isMaster}, forceRestart={forceRestart}, completeLoading={completeLoading}, alreadyScheduled={worldTeleportCoroutine != null}.");
SetLoadingCheckpoint(forceRestart ? 0.95f : 0.82f);
if (forceRestart && worldTeleportCoroutine != null)
{
pendingWorldTeleportRestart = true;
pendingTeleportShouldCompleteLoading = completeLoading;
return;
}
if (worldTeleportCoroutine != null)
{
currentTeleportShouldCompleteLoading |= completeLoading;
return;
}
pendingWorldTeleportRestart = false;
pendingTeleportShouldCompleteLoading = false;
currentTeleportShouldCompleteLoading = completeLoading;
float delaySeconds = (isMaster ? 0.5f : Mathf.Clamp(lateJoinReplayDelay + 0.5f, 0.5f, 5f));
worldTeleportCoroutine = StartCoroutine(TeleportLocalPlayerAfterDelay(delaySeconds));
}
private IEnumerator TeleportLocalPlayerAfterDelay(float delaySeconds)
{
Debug.Log($"[VoxelGroupPhotonSync] TeleportLocalPlayerAfterDelay: waiting for Photon lobby/room connectivity. delay={delaySeconds}.");
SetLoadingCheckpoint(0.84f);
float connectionWaitStart = Time.realtimeSinceStartup;
while (!PhotonNetwork.IsConnectedAndReady || (!PhotonNetwork.InLobby && !PhotonNetwork.InRoom))
{
if (Time.realtimeSinceStartup - connectionWaitStart > 10f)
{
Debug.LogWarning($"[VoxelGroupPhotonSync] TeleportLocalPlayerAfterDelay: timed out waiting for Photon connection. state={PhotonNetwork.NetworkClientState}, inRoom={PhotonNetwork.InRoom}, inLobby={PhotonNetwork.InLobby}.");
if (currentTeleportShouldCompleteLoading)
{
CompleteLoadingScreen();
}
FinishTeleportCoroutine();
yield break;
}
yield return null;
}
Debug.Log($"[VoxelGroupPhotonSync] TeleportLocalPlayerAfterDelay: Photon connected. InLobby={PhotonNetwork.InLobby}, InRoom={PhotonNetwork.InRoom}.");
SetLoadingCheckpoint(0.86f);
yield return new WaitForSecondsRealtime(Mathf.Max(0f, delaySeconds));
if (!PhotonNetwork.InRoom || PhotonNetwork.LocalPlayer == null)
{
Debug.LogWarning("[VoxelGroupPhotonSync] TeleportLocalPlayerAfterDelay: aborting because not in room or local player is missing after delay.");
if (currentTeleportShouldCompleteLoading && PhotonNetwork.InRoom)
{
CompleteLoadingScreen();
}
FinishTeleportCoroutine();
yield break;
}
PhotonVRPlayer localPlayer = null;
float playerWaitStart = Time.realtimeSinceStartup;
while (localPlayer == null && Time.realtimeSinceStartup - playerWaitStart < 3f)
{
PhotonVRPlayer[] array = UnityEngine.Object.FindObjectsByType<PhotonVRPlayer>(FindObjectsInactive.Include);
foreach (PhotonVRPlayer photonVRPlayer in array)
{
if (!(photonVRPlayer == null))
{
PhotonView component = photonVRPlayer.GetComponent<PhotonView>();
if (!(component == null) && component.OwnerActorNr == PhotonNetwork.LocalPlayer.ActorNumber)
{
localPlayer = photonVRPlayer;
break;
}
}
}
if (localPlayer != null)
{
break;
}
yield return null;
}
if (localPlayer == null)
{
Debug.LogWarning($"[VoxelGroupPhotonSync] TeleportLocalPlayerAfterDelay: no local PhotonVRPlayer found after {Time.realtimeSinceStartup - playerWaitStart:F2}s.");
if (currentTeleportShouldCompleteLoading)
{
CompleteLoadingScreen();
}
FinishTeleportCoroutine();
yield break;
}
SetLoadingCheckpoint(0.9f);
Vector3 vector = new Vector3(0f, 5f, 0f);
Transform localVrTeleportRoot = GetLocalVrTeleportRoot();
if (localVrTeleportRoot != null)
{
MoveTransformTo(localVrTeleportRoot, vector);
Debug.Log($"[VoxelGroupPhotonSync] TeleportLocalPlayerAfterDelay: teleported VR root '{localVrTeleportRoot.name}' to {vector}.");
NotifyLoadingPlayerTeleported(currentTeleportShouldCompleteLoading);
FinishTeleportCoroutine();
yield break;
}
Debug.LogWarning("[VoxelGroupPhotonSync] TeleportLocalPlayerAfterDelay: no VR root found; teleporting local PhotonVRPlayer only.");
if (localPlayer != null)
{
MoveTransformTo(localPlayer.transform, vector);
Debug.Log($"[VoxelGroupPhotonSync] TeleportLocalPlayerAfterDelay: teleported local PhotonVRPlayer '{localPlayer.name}' to {vector}.");
NotifyLoadingPlayerTeleported(currentTeleportShouldCompleteLoading);
}
FinishTeleportCoroutine();
}
private void FinishTeleportCoroutine()
{
worldTeleportCoroutine = null;
currentTeleportShouldCompleteLoading = false;
if (pendingWorldTeleportRestart)
{
bool completeLoading = pendingTeleportShouldCompleteLoading;
pendingWorldTeleportRestart = false;
pendingTeleportShouldCompleteLoading = false;
ScheduleTeleportAfterWorldLoad(isMaster: true, forceRestart: false, completeLoading);
}
}
private static void MoveTransformTo(Transform target, Vector3 position)
{
if (target == null)
{
return;
}
CharacterController component = target.GetComponent<CharacterController>();
if (component != null)
{
bool flag = component.enabled;
component.enabled = false;
target.position = position;
component.enabled = flag;
Rigidbody component2 = target.GetComponent<Rigidbody>();
if (component2 != null)
{
component2.linearVelocity = Vector3.zero;
component2.angularVelocity = Vector3.zero;
}
}
else
{
target.position = position;
Rigidbody component3 = target.GetComponent<Rigidbody>();
if (component3 != null)
{
component3.linearVelocity = Vector3.zero;
component3.angularVelocity = Vector3.zero;
}
}
}
private 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;
}
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 WorldResolveAndMaybeLoad()
{
Debug.Log("[VoxelGroupPhotonSync] WorldResolveAndMaybeLoad: starting world resolve and load.");
SetLoadingCheckpoint(0.7f);
if (groupManager == null || !PhotonNetwork.InRoom)
{
Debug.LogWarning("[VoxelGroupPhotonSync] WorldResolveAndMaybeLoad: aborted because groupManager is null or not in room.");
yield break;
}
string text = ((PhotonNetwork.CurrentRoom != null) ? PhotonNetwork.CurrentRoom.Name : string.Empty);
Debug.Log("[VoxelGroupPhotonSync] WorldResolveAndMaybeLoad: current room name='" + text + "'.");
SetLoadingCheckpoint(0.72f);
string baseWorldName = GetBaseWorldName(text);
if (string.IsNullOrEmpty(baseWorldName))
{
Debug.LogWarning("[VoxelGroupPhotonSync] WorldResolveAndMaybeLoad: could not determine base world name from room.");
yield break;
}
cachedPlayerId = BlockSpaceAccountPrefs.GetUserFolderId();
cachedBaseWorldName = baseWorldName;
EnsureWorldBackendClient();
WorldBackendClient.ResolveWorldResponse resolve = null;
SetLoadingCheckpoint(0.74f);
yield return worldBackendClient.ResolveWorld(baseWorldName, cachedPlayerId, delegate(WorldBackendClient.ResolveWorldResponse r)
{
resolve = r;
});
if (resolve == null || !resolve.ok)
{
Debug.LogWarning("[VoxelGroupPhotonSync] WorldResolveAndMaybeLoad: resolve failed. resolve=" + ((resolve == null) ? "null" : resolve.ok.ToString()) + ", worldId=" + ((resolve != null) ? resolve.worldId : "<null>") + ", error=" + ((resolve != null) ? resolve.error : "<null>"));
groupManager.SetEditingLocked(locked: true);
ScheduleTeleportAfterWorldLoad(PhotonNetwork.IsMasterClient, forceRestart: false, completeLoading: true);
yield break;
}
currentWorldExists = resolve.exists;
currentWorldId = resolve.worldId ?? string.Empty;
isCreatorForCurrentWorld = resolve.isCreator;
bool flag = string.Equals(cachedBaseWorldName, "Lobby", StringComparison.OrdinalIgnoreCase);
groupManager.SetEditingLocked(!isCreatorForCurrentWorld && !flag);
Debug.Log($"[VoxelGroupPhotonSync] WorldResolveAndMaybeLoad: resolved baseWorldName='{cachedBaseWorldName}', worldExists={currentWorldExists}, worldId='{currentWorldId}', isCreator={isCreatorForCurrentWorld}.");
SetLoadingCheckpoint(0.78f);
StartWorldAutoSaveIfNeeded();
StartWorldPermissionRefreshIfNeeded();
Debug.Log("[VoxelGroupPhotonSync] WorldResolveAndMaybeLoad: scheduling teleport after world load.");
ScheduleTeleportAfterWorldLoad(PhotonNetwork.IsMasterClient, forceRestart: false, !PhotonNetwork.IsMasterClient);
if (!currentWorldExists || string.IsNullOrEmpty(currentWorldId) || !PhotonNetwork.IsMasterClient)
{
ScheduleTeleportAfterWorldLoad(PhotonNetwork.IsMasterClient, forceRestart: false, completeLoading: true);
yield break;
}
groupManager.ClearAllGroupsForNetworkSync();
byte[] gzipBytes = null;
SetLoadingCheckpoint(0.88f);
yield return worldBackendClient.DownloadGroups(currentWorldId, delegate(byte[] bytes)
{
gzipBytes = bytes;
});
if (gzipBytes == null)
{
gzipBytes = Array.Empty<byte>();
}
if (!WorldGroupsCodec.TryDecodeGzip(gzipBytes, out WorldGroupsCodec.WorldSaveData saveData))
{
Debug.LogWarning("[VoxelGroupPhotonSync] Failed to decode world groups; starting with an empty world.");
ScheduleTeleportAfterWorldLoad(isMaster: true, forceRestart: false, completeLoading: true);
yield break;
}
List<WorldGroupsCodec.WorldGroupRecord> list = saveData.groupRecords ?? new List<WorldGroupsCodec.WorldGroupRecord>();
Debug.Log($"[VoxelGroupPhotonSync] WorldResolveAndMaybeLoad: decoded {list.Count} world group records.");
SetLoadingCheckpoint(0.91f);
if (list.Count == 0)
{
Debug.Log("[VoxelGroupPhotonSync] WorldResolveAndMaybeLoad: world contains no saved groups.");
}
suppressWorldDirty = true;
try
{
Dictionary<int, int> dictionary = new Dictionary<int, int>();
for (int num = 0; num < list.Count; num++)
{
WorldGroupsCodec.WorldGroupRecord worldGroupRecord = list[num];
VoxelGroupManager.SavedGroupData data = new VoxelGroupManager.SavedGroupData
{
position = worldGroupRecord.position,
packedRotation = worldGroupRecord.packedRotation,
size = worldGroupRecord.size,
voxels = worldGroupRecord.voxels
};
VoxelGroup voxelGroup = groupManager.SpawnSavedGroup(data, worldGroupRecord.position);
if (voxelGroup != null)
{
dictionary[num] = voxelGroup.id;
}
}
if (nodeGraphManager != null)
{
nodeGraphManager.ApplySavedGraph(saveData.nodeRecords, saveData.wireRecords, clearExisting: true, broadcast: false, Vector3.zero, dictionary);
(nodeGraphManager.GetComponent<NodeWorldDefaultSpawner>() ?? nodeGraphManager.gameObject.AddComponent<NodeWorldDefaultSpawner>()).EnsureDefaultNodesSpawnedIfEmpty(saveData.nodeRecords, PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient);
}
worldDirty = false;
SetLoadingCheckpoint(0.93f);
}
finally
{
suppressWorldDirty = false;
}
WorldBackendClient.VisitResponse visit = null;
yield return worldBackendClient.IncrementVisit(currentWorldId, delegate(WorldBackendClient.VisitResponse r)
{
visit = r;
});
SetLoadingCheckpoint(0.94f);
ScheduleTeleportAfterWorldLoad(isMaster: true, forceRestart: true, completeLoading: true);
}
private static void SetLoadingCheckpoint(float progress)
{
((WorldLoadingScreenController.Instance != null) ? WorldLoadingScreenController.Instance : UnityEngine.Object.FindFirstObjectByType<WorldLoadingScreenController>())?.SetCheckpoint(progress);
}
private static void NotifyLoadingPlayerTeleported(bool completeLoading)
{
((WorldLoadingScreenController.Instance != null) ? WorldLoadingScreenController.Instance : UnityEngine.Object.FindFirstObjectByType<WorldLoadingScreenController>())?.NotifyLocalPlayerTeleported(completeLoading);
}
private static void CompleteLoadingScreen()
{
((WorldLoadingScreenController.Instance != null) ? WorldLoadingScreenController.Instance : UnityEngine.Object.FindFirstObjectByType<WorldLoadingScreenController>())?.CompleteLoading();
}
private void StartWorldPermissionRefreshIfNeeded()
{
if (worldPermissionRefreshCoroutine == null && enableWorldPersistence && !string.IsNullOrEmpty(cachedBaseWorldName) && !string.IsNullOrEmpty(cachedPlayerId))
{
worldPermissionRefreshCoroutine = StartCoroutine(WorldPermissionRefreshLoop());
}
}
private IEnumerator WorldPermissionRefreshLoop()
{
while (PhotonNetwork.InRoom && enableWorldPersistence && !string.IsNullOrEmpty(cachedBaseWorldName))
{
yield return new WaitForSecondsRealtime(Mathf.Clamp(permissionRefreshIntervalSeconds, 2f, 30f));
if (groupManager == null)
{
continue;
}
EnsureWorldBackendClient();
WorldBackendClient.ResolveWorldResponse resolve = null;
yield return worldBackendClient.ResolveWorld(cachedBaseWorldName, cachedPlayerId, delegate(WorldBackendClient.ResolveWorldResponse r)
{
resolve = r;
});
if (resolve != null && resolve.ok)
{
bool isCreator = resolve.isCreator;
if (isCreator != isCreatorForCurrentWorld)
{
isCreatorForCurrentWorld = isCreator;
bool flag = string.Equals(cachedBaseWorldName, "Lobby", StringComparison.OrdinalIgnoreCase);
groupManager.SetEditingLocked(!isCreatorForCurrentWorld && !flag);
}
}
}
worldPermissionRefreshCoroutine = null;
}
private void StartWorldAutoSaveIfNeeded()
{
if (worldAutoSaveCoroutine == null && PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && enableWorldPersistence && currentWorldExists && !string.IsNullOrEmpty(currentWorldId))
{
worldAutoSaveCoroutine = StartCoroutine(WorldAutoSaveLoop());
}
}
private IEnumerator WorldAutoSaveLoop()
{
while (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && enableWorldPersistence && currentWorldExists && !string.IsNullOrEmpty(currentWorldId))
{
yield return new WaitForSecondsRealtime(Mathf.Clamp(autoSaveIntervalSeconds, 5f, 300f));
if (worldDirty && !suppressWorldDirty && isCreatorForCurrentWorld && !(groupManager == null))
{
EnsureWorldBackendClient();
List<VoxelGroup> persistableWorldGroups = GetPersistableWorldGroups();
byte[] gzipBytes = WorldGroupsCodec.EncodeGzip(persistableWorldGroups, (nodeGraphManager != null) ? nodeGraphManager.BuildWorldNodeRecords() : null, (nodeGraphManager != null) ? nodeGraphManager.BuildWorldWireRecords(null, BuildGroupSaveIndexMap(persistableWorldGroups)) : null);
WorldBackendClient.UploadGroupsResponse upload = null;
yield return worldBackendClient.UploadGroups(currentWorldId, gzipBytes, cachedPlayerId, delegate(WorldBackendClient.UploadGroupsResponse r)
{
upload = r;
});
if (upload != null && upload.ok && upload.saved)
{
worldDirty = false;
}
}
}
worldAutoSaveCoroutine = null;
}
public IEnumerator ForceSaveNow()
{
if (!enableWorldPersistence || !currentWorldExists || string.IsNullOrEmpty(currentWorldId) || !PhotonNetwork.InRoom || !isCreatorForCurrentWorld || groupManager == null)
{
yield break;
}
EnsureWorldBackendClient();
List<VoxelGroup> persistableWorldGroups = GetPersistableWorldGroups();
byte[] gzipBytes = WorldGroupsCodec.EncodeGzip(persistableWorldGroups, (nodeGraphManager != null) ? nodeGraphManager.BuildWorldNodeRecords() : null, (nodeGraphManager != null) ? nodeGraphManager.BuildWorldWireRecords(null, BuildGroupSaveIndexMap(persistableWorldGroups)) : null);
WorldBackendClient.UploadGroupsResponse upload = null;
yield return worldBackendClient.UploadGroups(currentWorldId, gzipBytes, cachedPlayerId, delegate(WorldBackendClient.UploadGroupsResponse r)
{
upload = r;
});
if (upload != null && upload.ok && upload.saved)
{
worldDirty = false;
WorldBackendClient.BroadcastReloadResponse broadcast = null;
yield return worldBackendClient.BroadcastReload(cachedPlayerId, cachedBaseWorldName, delegate(WorldBackendClient.BroadcastReloadResponse r)
{
broadcast = r;
});
}
}
private bool CanSendRpc()
{
if (!PhotonNetwork.InRoom || cachedPhotonView == null)
{
return false;
}
if (cachedPhotonView.ViewID == 0)
{
Debug.LogWarning("VoxelGroupPhotonSync requires a valid PhotonView ViewID to send RPCs.", this);
return false;
}
return true;
}
private bool CanSendStrokePreviewRpc(byte voxelValue, float radius)
{
if (CanSendRpc() && PhotonNetwork.LocalPlayer != null && radius > 0f)
{
return !PackedVoxel.IsAir(voxelValue);
}
return false;
}
private bool ShouldIgnoreStrokePreview(int actorNumber, int voxelValue, float radius)
{
if (!(groupManager == null) && actorNumber > 0 && (PhotonNetwork.LocalPlayer == null || actorNumber != PhotonNetwork.LocalPlayer.ActorNumber) && voxelValue >= 0 && voxelValue <= 255 && !(radius <= 0f))
{
return PackedVoxel.IsAir((byte)voxelValue);
}
return true;
}
private bool ShouldIgnoreErasePreview(int actorNumber, float radius)
{
if (!(groupManager == null) && actorNumber > 0 && (PhotonNetwork.LocalPlayer == null || actorNumber != PhotonNetwork.LocalPlayer.ActorNumber))
{
return radius <= 0f;
}
return true;
}
private Vector3 ResolveRightHandStrokePoint(int actorNumber, Vector3 fallbackPoint, float forwardOffset)
{
if (TryFindRightHand(actorNumber, out var rightHand))
{
return rightHand.position + rightHand.rotation * Vector3.forward * forwardOffset;
}
return fallbackPoint;
}
private bool TryFindRightHand(int actorNumber, out Transform rightHand)
{
if (rightHandCache.TryGetValue(actorNumber, out rightHand) && rightHand != null)
{
return true;
}
PhotonVRPlayer[] array = UnityEngine.Object.FindObjectsByType<PhotonVRPlayer>(FindObjectsInactive.Include);
foreach (PhotonVRPlayer photonVRPlayer in array)
{
if (photonVRPlayer == null)
{
continue;
}
PhotonView component = photonVRPlayer.GetComponent<PhotonView>();
if (!(component == null) && component.OwnerActorNr == actorNumber)
{
rightHand = ((photonVRPlayer.RightHand != null) ? photonVRPlayer.RightHand : photonVRPlayer.transform.Find("RightHand"));
if (!(rightHand != null))
{
break;
}
rightHandCache[actorNumber] = rightHand;
return true;
}
}
rightHandCache.Remove(actorNumber);
rightHand = null;
return false;
}
private void EndStrokePreview(int actorNumber)
{
if (actorNumber > 0 && strokePreviews.TryGetValue(actorNumber, out var value))
{
strokePreviews.Remove(actorNumber);
if (groupManager != null && value.group != null)
{
groupManager.DiscardTransientStrokeGroup(value.group);
}
}
}
private void ApplyErasePreviewSegment(ErasePreviewState state, Vector3 start, Vector3 end)
{
if (!(groupManager == null) && state != null)
{
float num = Mathf.Max(groupManager.VoxelSize * 0.5f, state.radius * 0.5f);
float num2 = Vector3.Distance(start, end);
int num3 = Mathf.Max(1, Mathf.CeilToInt(num2 / num));
HashSet<VoxelGroup> editedGroups = new HashSet<VoxelGroup>();
for (int i = 0; i <= num3; i++)
{
float t = ((num3 == 0) ? 0f : ((float)i / (float)num3));
Vector3 vector = Vector3.Lerp(start, end, t);
SnapshotErasePreviewGroups(state, vector, state.radius);
groupManager.EraseSpherePreview(vector, state.radius, editedGroups);
}
}
}
private void SnapshotErasePreviewGroups(ErasePreviewState state, Vector3 point, float radius)
{
List<VoxelGroup> groupsInRadius = groupManager.GetGroupsInRadius(point, radius);
for (int i = 0; i < groupsInRadius.Count; i++)
{
VoxelGroup voxelGroup = groupsInRadius[i];
if (!(voxelGroup == null) && !state.beforeByGroupId.ContainsKey(voxelGroup.id) && groupManager.TryCaptureGroupData(voxelGroup, out var data))
{
state.beforeByGroupId[voxelGroup.id] = data;
}
}
}
private void EndErasePreview(int actorNumber)
{
if (actorNumber <= 0 || !erasePreviews.TryGetValue(actorNumber, out var value))
{
return;
}
erasePreviews.Remove(actorNumber);
foreach (KeyValuePair<ushort, VoxelGroupManager.SavedGroupData> item in value.beforeByGroupId)
{
groupManager?.TryRestoreTransientGroup(item.Key, item.Value);
}
}
private void UpdateMovePreviews()
{
if (movePreviews.Count == 0 || groupManager == null)
{
return;
}
float realtimeSinceStartup = Time.realtimeSinceStartup;
float t = 1f - Mathf.Exp(-18f * Time.deltaTime);
movePreviewCleanup.Clear();
foreach (KeyValuePair<ushort, MovePreviewState> movePreview in movePreviews)
{
MovePreviewState value = movePreview.Value;
if (value == null || value.group == null)
{
movePreviewCleanup.Add(movePreview.Key);
continue;
}
float num = Mathf.Clamp(realtimeSinceStartup - value.lastPacketTime, 0f, 0.15f);
Vector3 b = value.targetPosition + value.velocity * num;
Quaternion rotation = value.group.transform.rotation;
value.group.transform.SetPositionAndRotation(Vector3.Lerp(value.group.transform.position, b, t), Quaternion.Slerp(rotation, value.targetRotation, t));
if (realtimeSinceStartup - value.lastPacketTime > 2f)
{
movePreviewCleanup.Add(movePreview.Key);
}
}
for (int i = 0; i < movePreviewCleanup.Count; i++)
{
EndMovePreview(movePreviewCleanup[i], restoreCollider: true);
}
movePreviewCleanup.Clear();
}
private void EndMovePreview(ushort groupId, bool restoreCollider)
{
if (movePreviews.Remove(groupId) && restoreCollider && groupManager != null && groupManager.TryGetGroup(groupId, out var group))
{
groupManager.SetGroupColliderEnabled(group, enabled: true);
}
}
private void ClearMovePreviews(bool restoreColliders)
{
if (movePreviews.Count != 0)
{
movePreviewCleanup.Clear();
movePreviewCleanup.AddRange(movePreviews.Keys);
for (int i = 0; i < movePreviewCleanup.Count; i++)
{
EndMovePreview(movePreviewCleanup[i], restoreColliders);
}
movePreviewCleanup.Clear();
}
}
private void ClearStrokePreviews()
{
if (strokePreviews.Count != 0 || erasePreviews.Count != 0)
{
List<int> list = new List<int>(strokePreviews.Keys);
for (int i = 0; i < list.Count; i++)
{
EndStrokePreview(list[i]);
}
list.Clear();
list.AddRange(erasePreviews.Keys);
for (int j = 0; j < list.Count; j++)
{
EndErasePreview(list[j]);
}
}
}
private bool IsStrokePreviewGroup(VoxelGroup group)
{
if (group == null)
{
return false;
}
foreach (StrokePreviewState value in strokePreviews.Values)
{
if (value.group == group)
{
return true;
}
}
return false;
}
private static int GetActorNumberFromGroupId(int groupId)
{
return (groupId >> 8) & 0xFF;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: c7ed27668f452564b74a91e7c480f596
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
namespace BlockSpace.Voxels
{
public enum VoxelMaterial : byte
{
Lit = 0,
Unlit = 1,
Transparent = 2,
Invisible = 3
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 13d7341a618e1ab49ba0dd583fc614d9
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1b1f2d708e7b80649bf2c2f8b2959aca
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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:
@@ -0,0 +1,21 @@
using UnityEngine;
[DisallowMultipleComponent]
public sealed class ClampBehavior : GuardedValueNodeBehavior
{
protected override void RecomputeCore()
{
double inputNumber = GetInputNumber("Value");
double num = GetInputNumber("Min");
double num2 = GetInputNumber("Max");
if (num > num2)
{
double num3 = num2;
double num4 = num;
num = num3;
num2 = num4;
}
double value = ((inputNumber < num) ? num : ((inputNumber > num2) ? num2 : inputNumber));
SetOutputValueIfChanged("Output", BSValue.FromNumber(value));
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f16b6ceee0f057442a4150851bd5042c
@@ -0,0 +1,21 @@
using System;
using UnityEngine;
[DisallowMultipleComponent]
public sealed class CooldownBehavior : NodeBehavior
{
private float nextAllowedAt;
public override void OnRun(string inputPortName)
{
if (base.IsAuthoritative && string.Equals(inputPortName, "In", StringComparison.Ordinal))
{
float num = Mathf.Max(0f, (float)GetInputNumber("Seconds"));
if (!(Time.unscaledTime < nextAllowedAt))
{
nextAllowedAt = Time.unscaledTime + num;
TriggerOutput("Out");
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ad351146dfda65949ad679e63adef3b1
@@ -0,0 +1,12 @@
using System;
using UnityEngine;
[DisallowMultipleComponent]
public sealed class CosineBehavior : GuardedValueNodeBehavior
{
protected override void RecomputeCore()
{
double d = GetInputNumber("Degrees") * (Math.PI / 180.0);
SetOutputValueIfChanged("Output", BSValue.FromNumber(Math.Cos(d)));
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e90903dfc0702514484c26f5909b3830
@@ -0,0 +1,39 @@
using System;
using UnityEngine;
[DisallowMultipleComponent]
public sealed class CounterBehavior : NodeBehavior
{
private int count;
private void Start()
{
if (base.IsAuthoritative)
{
SetOutputValueIfChanged("Count", BSValue.FromNumber(count));
}
}
public override void OnRun(string inputPortName)
{
if (!base.IsAuthoritative)
{
return;
}
if (string.Equals(inputPortName, "Reset", StringComparison.Ordinal))
{
count = 0;
SetOutputValueIfChanged("Count", BSValue.FromNumber(count));
}
else if (string.Equals(inputPortName, "In", StringComparison.Ordinal))
{
count++;
SetOutputValueIfChanged("Count", BSValue.FromNumber(count));
int num = Mathf.Max(1, Mathf.RoundToInt((float)GetInputNumber("Target")));
if (count >= num)
{
TriggerOutput("Reached");
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More