415 lines
11 KiB
C#
415 lines
11 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|