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()); } 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()); if (voxelGroup == null) { voxelGroup = groupObject.AddComponent(); } 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); } } }