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

1495 lines
49 KiB
C#

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;
}
}
}