554 lines
12 KiB
C#

using UdonSharp;
using UnityEngine;
using VRC.SDK3.Components.Video;
using VRC.SDK3.Image;
using VRC.SDK3.UdonNetworkCalling;
using VRC.SDK3.Video.Components.Base;
using VRC.SDKBase;
using VRC.Udon.Common;
using VRC.Udon.Common.Interfaces;
public enum ClueScreenType
{
Blank,
Video,
Map
}
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class CaseVideoSyncPlayer : UdonSharpBehaviour
{
[SerializeField] private BaseVRCVideoPlayer _VideoPlayer;
[Header("Meshes")]
[SerializeField] private MeshRenderer _BlankScreenMesh;
[SerializeField] private MeshRenderer _VideoScreenMesh;
[SerializeField] private MeshRenderer _MapScreenMesh;
[Header("Materials")]
[SerializeField] private Material _MapScreenMaterial;
[SerializeField] private Texture2D _PlaceholderMapTexture;
[Header("Video Status")]
[SerializeField] private VideoLoadIndicator[] _VideoLoadIndicators;
[UdonSynced, FieldChangeCallback(nameof(SubMapIndex))] private int _SubMapIndex = -1;
[UdonSynced, FieldChangeCallback(nameof(ShowScreen))] private ClueScreenType _ShowScreen = ClueScreenType.Blank;
[UdonSynced, FieldChangeCallback(nameof(FlashCorrectAnswer))] private bool _FlashCorrectAnswer = false;
[UdonSynced] private int _VideoIndex = -1;
private int _OldVideoIndex = -1;
[UdonSynced, FieldChangeCallback(nameof(PlayVideo))] private bool _VideoIsPlaying;
[UdonSynced] private VRCUrl[] _CaseMapsList = new VRCUrl[0];
private VRCUrl[] _CaseMapsListCache = new VRCUrl[0];
[UdonSynced] private int[] _CachedMapIndices = new int[0];
private int[] _LoadedMaps = new int[0];
[UdonSynced] private VRCUrl[] _CaseVideoList = new VRCUrl[0];
private VRCUrl[] _CaseVideoListCache = new VRCUrl[0];
private VRCImageDownloader _MapDownloader;
private Texture2D[] _MapImages = new Texture2D[0];
private IUdonEventReceiver _UdonEventReceiverThis;
private int _MapDownloadIndex = 0;
private bool _MapDownloadsInProgress = false;
private int _VideoLoadAttemptCounter = 0;
private bool _UseFallback = false;
private bool _FirstDeserialisationComplete = false;
private const int IMAGES_PER_MAP_ATLAS = 6;
private const int MAX_VIDEO_LOAD_ATTEMPTS = 5;
void Start()
{
_MapDownloader = new VRCImageDownloader();
_UdonEventReceiverThis = (IUdonEventReceiver)this;
_MapScreenMesh.sharedMaterial = _MapScreenMaterial;
_UpdateMap();
}
void OnDestroy()
{
_MapDownloader.Dispose();
}
public override void OnDeserialization(DeserializationResult Result)
{
{ // Map syncing and redownloading
_ReloadMapList();
if (!_MapDownloadsInProgress)
{
bool MapsNeedLoading = false;
if (_LoadedMaps.Length == _CachedMapIndices.Length)
{
for (int i = 0; i < _CachedMapIndices.Length; i++)
{
if (_CachedMapIndices[i] != _LoadedMaps[i])
{
MapsNeedLoading = true;
break;
}
}
}
else
{
MapsNeedLoading = true;
}
if (MapsNeedLoading)
{
QueueMapDownloads(_CachedMapIndices);
}
}
}
{ // Video list syncing
_ReloadVideoList();
}
base.OnDeserialization(Result);
}
public void InitialiseLists(VRCUrl[] Maps, VRCUrl[] Videos)
{
_CaseMapsList = Maps;
_ReloadMapList();
_CaseVideoList = Videos;
_ReloadVideoList();
RequestSerialization();
}
private void _ReloadMapList()
{
if (_IsUrlListDifferent(_CaseMapsList, _CaseMapsListCache))
{
_CaseMapsListCache = (VRCUrl[])_CaseMapsList.Clone();
}
}
private void _ReloadVideoList()
{
if (_IsUrlListDifferent(_CaseVideoList, _CaseVideoListCache))
{
_OldVideoIndex = -1;
_CaseVideoListCache = (VRCUrl[])_CaseVideoList.Clone();
}
if (_VideoIndex != _OldVideoIndex)
{
_LoadNewVideoIndex();
}
}
[NetworkCallable]
public void QueueMapDownloads(int[] MapIndices)
{
if (_MapDownloadsInProgress)
{
// Concatenate both caches into a new bigger cache and await new downloads as usual
int[] NewMapIndicesCache = new int[_CachedMapIndices.Length + MapIndices.Length];
Texture2D[] NewMapImages = new Texture2D[_CachedMapIndices.Length + MapIndices.Length];
for (int i = 0; i < _CachedMapIndices.Length; i++)
{
NewMapIndicesCache[i] = _CachedMapIndices[i];
NewMapImages[i] = _MapImages[i];
}
for (int i = 0; i < MapIndices.Length; i++)
{
NewMapIndicesCache[i + _CachedMapIndices.Length] = MapIndices[i];
NewMapImages[i + _CachedMapIndices.Length] = _PlaceholderMapTexture;
}
_CachedMapIndices = NewMapIndicesCache;
_MapImages = NewMapImages;
}
else
{
_MapDownloadsInProgress = true;
_CachedMapIndices = MapIndices;
_MapImages = new Texture2D[_CachedMapIndices.Length];
for (int i = 0; i < _MapImages.Length; i++)
{
_MapImages[i] = _PlaceholderMapTexture;
}
_MapDownloadIndex = 0;
LoadMapFromIndex(_CachedMapIndices[_MapDownloadIndex]);
}
_LoadedMaps = (int[])_CachedMapIndices.Clone();
}
private void LoadMapFromIndex(int MapIndex)
{
VRCUrl MapURL = _CaseMapsList[MapIndex];
TextureInfo AdditionalTextureInfo = new TextureInfo();
AdditionalTextureInfo.WrapModeU = TextureWrapMode.Clamp;
AdditionalTextureInfo.WrapModeV = TextureWrapMode.Clamp;
AdditionalTextureInfo.GenerateMipMaps = true;
_MapDownloader.DownloadImage(MapURL, null, _UdonEventReceiverThis, AdditionalTextureInfo);
}
public override void OnImageLoadSuccess(IVRCImageDownload Result)
{
_MapImages[_MapDownloadIndex] = Result.Result;
int MapPage = SubMapIndex / IMAGES_PER_MAP_ATLAS;
if (MapPage == _MapDownloadIndex)
{
_UpdateSubMap(MapPage);
}
_MapDownloadIndex++;
if (_MapDownloadIndex >= _CachedMapIndices.Length)
{
_MapDownloadsInProgress = false;
}
else
{
LoadMapFromIndex(_CachedMapIndices[_MapDownloadIndex]);
}
base.OnImageLoadSuccess(Result);
}
public override void OnImageLoadError(IVRCImageDownload Result)
{
_MapDownloadsInProgress = false;
base.OnImageLoadError(Result);
}
[NetworkCallable]
public void NextCorrectAnswerFrame()
{
if (FlashCorrectAnswer)
{
SubMapIndex = (SubMapIndex == 4) ? 3 : 4;
_UpdateMap(false);
SendCustomEventDelayedSeconds(nameof(NextCorrectAnswerFrame), 0.2f);
}
else
{
_VideoPlayer.Stop();
SubMapIndex = 0;
ShowScreen = ClueScreenType.Blank;
RequestSerialization();
}
}
private void _UpdateMap(bool Sync = true)
{
int MapPage = SubMapIndex / IMAGES_PER_MAP_ATLAS;
if (MapPage < _MapImages.Length)
{
_UpdateSubMap(MapPage);
ShowScreen = ClueScreenType.Map;
}
if (Sync)
{
RequestSerialization();
}
}
private void _UpdateSubMap(int MapPage = -1)
{
int SubmapIndexWrapped = SubMapIndex % IMAGES_PER_MAP_ATLAS;
switch (SubmapIndexWrapped)
{
case 0: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.0f, 0.66666666f)); break;
case 1: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.5f, 0.66666666f)); break;
case 2: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.0f, 0.33333333f)); break;
case 3: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.5f, 0.33333333f)); break;
case 4: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.0f, 0.0f)); break;
case 5: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.5f, 0.0f)); break;
}
if (MapPage >= 0)
{
_MapScreenMaterial.SetTexture("_EmissionMap", _MapImages[MapPage]);
}
}
public void SetVideoIndex(int Index)
{
if (Index != _OldVideoIndex)
{
_VideoIndex = Index;
_LoadNewVideoIndex();
RequestSerialization();
}
}
private void _LoadNewVideoIndex()
{
_OldVideoIndex = _VideoIndex;
_VideoLoadAttemptCounter = 0;
_UseFallback = false;
_LoadVideo_Private();
}
private void _LoadVideo_Private()
{
TryLoadURL();
}
public void TryLoadURL()
{
if (_VideoIndex >= 0 && _VideoIndex < _CaseVideoList.Length)
{
_VideoPlayer.LoadURL(_CaseVideoList[_VideoIndex]);
SetVideoLoadStatus(IndicationStatus.Loading);
}
}
private void _PlayVideo_Private()
{
_VideoPlayer.Play();
}
private void _StopVideo_Private()
{
_VideoPlayer.Stop();
_UseFallback = false;
_VideoIndex = -1;
SetVideoLoadStatus(IndicationStatus.Idle);
}
public override void OnVideoReady()
{
if (_VideoIsPlaying)
{
_PlayVideo_Private();
}
SetVideoLoadStatus(IndicationStatus.LoadSuccess);
base.OnVideoReady();
}
public override void OnVideoError(VideoError VideoError)
{
switch(VideoError)
{
case VideoError.Unknown:
Debug.LogError("[CaseVideoSyncPlayer] Unknown playback error.");
break;
case VideoError.InvalidURL:
Debug.LogError("[CaseVideoSyncPlayer] Invalid URL.");
break;
case VideoError.AccessDenied:
Debug.LogError("[CaseVideoSyncPlayer] Access denied.");
break;
case VideoError.PlayerError:
Debug.LogError("[CaseVideoSyncPlayer] Error with video player.");
break;
case VideoError.RateLimited:
Debug.LogError("[CaseVideoSyncPlayer] Rate limited. Attempting another reload in 2 seconds...");
SendCustomEventDelayedSeconds(nameof(TryLoadURL), 2.1f);
return;
}
//if (_UseFallback)
//{
_StopVideo_Private();
//}
//else
//{
// Debug.Log("[CaseVideoSyncPlayer] Attempting fallback in 5 seconds...");
// SendCustomEventDelayedSeconds(nameof(TryLoadFallbackURL), 5.5f);
//}
SetVideoLoadStatus(IndicationStatus.LoadFailure);
base.OnVideoError(VideoError);
}
public void TryLoadFallbackURL()
{
_UseFallback = true;
TryLoadURL();
}
private void SetPlaybackStatus(bool Value)
{
if (_VideoIsPlaying != Value)
{
_VideoIsPlaying = Value;
if (_VideoIsPlaying) _PlayVideo_Private();
else _StopVideo_Private();
RequestSerialization();
}
}
public override void OnVideoStart()
{
if (_VideoIsPlaying)
{
ShowScreen = ClueScreenType.Video;
}
else
{
_VideoPlayer.Stop();
}
base.OnVideoStart();
}
public override void OnVideoEnd()
{
_SwapToScreen(ClueScreenType.Blank, false);
base.OnVideoEnd();
}
public float GetVideoDuration()
{
if (_VideoPlayer.IsReady || _VideoPlayer.IsPlaying)
{
return _VideoPlayer.GetDuration();
}
return 0.0f;
}
public float GetVideoTime()
{
if (_VideoPlayer.IsPlaying)
{
return _VideoPlayer.GetTime();
}
return 0.0f;
}
public void SetVideoLoadStatus(IndicationStatus Status)
{
foreach (VideoLoadIndicator Indicator in _VideoLoadIndicators)
{
if (Indicator.GetOwner() == Networking.LocalPlayer.displayName)
{
Indicator.IndicateStatus = Status;
}
}
}
[NetworkCallable]
public void ClearScreen()
{
FlashCorrectAnswer = false;
ShowScreen = ClueScreenType.Blank;
}
private void _SwapToScreen(ClueScreenType Screen, bool StopAllVideos = true)
{
if (ShowScreen == Screen) return;
switch (Screen)
{
case ClueScreenType.Blank:
{
_BlankScreenMesh.transform.localPosition = new Vector3(0.0f, 0.0f, 0.0f);
_VideoScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f);
_MapScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f);
if (StopAllVideos) PlayVideo = false;
break;
}
case ClueScreenType.Video:
{
_BlankScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f);
_VideoScreenMesh.transform.localPosition = new Vector3(0.0f, 0.0f, 0.0f);
_MapScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f);
break;
}
case ClueScreenType.Map:
{
_BlankScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f);
_VideoScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f);
_MapScreenMesh.transform.localPosition = new Vector3(0.0f, 0.0f, 0.0f);
if (StopAllVideos) PlayVideo = false;
break;
}
}
}
private bool _IsUrlListDifferent(VRCUrl[] New, VRCUrl[] Cached)
{
if (New.Length == Cached.Length)
{
for (int i = 0; i < New.Length; i++)
{
if (New[i].ToString() != Cached[i].ToString())
{
return true;
}
}
}
else
{
return true;
}
return false;
}
public int SubMapIndex
{
set
{
_SubMapIndex = value;
if (_SubMapIndex >= 0 && !FlashCorrectAnswer)
{
_UpdateMap();
}
}
get => _SubMapIndex;
}
public ClueScreenType ShowScreen
{
set
{
_SwapToScreen(value);
_ShowScreen = value;
}
get => _ShowScreen;
}
public bool FlashCorrectAnswer
{
set
{
_FlashCorrectAnswer = value;
NextCorrectAnswerFrame();
RequestSerialization();
}
get => _FlashCorrectAnswer;
}
public bool PlayVideo
{
set
{
SetPlaybackStatus(value);
}
get => _VideoIsPlaying;
}
}