using UdonSharp; using UnityEngine; using UnityEngine.UI; using VRC.SDK3.Data; using VRC.SDK3.StringLoading; using VRC.SDKBase; using VRC.Udon.Common.Interfaces; public enum AccusedCrook { Contessa, DoubleTrouble, EarthaBrute, Kneemoi, PattyLarceny, Robocrook, SarahNade, TopGrunge, VicTheSlick, WonderRat, INDEX_MAX } [UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] public class CaseManager : UdonSharpBehaviour { [Header("Global Case Assets")] [SerializeField] private Texture[] CrookPortraits; [Space, Header("Managers")] [SerializeField] private PermissionsPanel _PermissionsPanel; [SerializeField] private GameManagerRound1 _Round1Manager; [SerializeField] private GameManagerRound2 _Round2Manager; [SerializeField] private GameManagerRound3 _Round3Manager; [Space] [SerializeField] private HostCardManager _HostCard; [Space] [SerializeField] private CaseManagerListView _CaseManagerList; [SerializeField] private Button _HostTeleportButton; [Space] [SerializeField] private LiveIndicator _LiveIndicator; [UdonSynced] private VRCUrl _CaseFileCluesURL; [UdonSynced] private VRCUrl _CaseFileLootImage; [UdonSynced] private VRCUrl[] _CaseFileMaps = new VRCUrl[0]; [UdonSynced] private VRCUrl[] _CaseFileVideos = new VRCUrl[0]; [UdonSynced] private VRCUrl[] _CaseFileVideosFallback = new VRCUrl[0]; [UdonSynced] private VRCUrl[] _CaseFileClueImages = new VRCUrl[0]; [UdonSynced] private string _CaseTitle = ""; [UdonSynced] private string _CaseDescription = ""; [UdonSynced] private int _CaseIntroVideo = -1; [UdonSynced] private string _CaseIntroVideoTranscript = ""; [UdonSynced] private string _StolenLoot = ""; [UdonSynced] private AccusedCrook _AccusedCrook = AccusedCrook.INDEX_MAX; [UdonSynced] private ContinentMap _FinalRoundContinent = ContinentMap.INDEX_MAX; [UdonSynced] private string[] _CurrentWinningPlayers = new string[3]; private DataDictionary _CaseFileDictionary; public override void OnPlayerJoined(VRCPlayerApi Player) { if (Player.isLocal) { _CaseManagerList.gameObject.SetActive(Player.isInstanceOwner || Player.isMaster || _PermissionsPanel.IsPlayerHost(Player)); } base.OnPlayerJoined(Player); } public override void OnOwnershipTransferred(VRCPlayerApi Player) { _Round1Manager.SetOwnershipOfObjects(Player); _Round2Manager.SetOwnershipOfObjects(Player); _Round3Manager.SetOwnershipOfObjects(Player); base.OnOwnershipTransferred(Player); } public VRCPlayerApi GetHostOwner() { return Networking.GetOwner(gameObject); } public void HostEnabled() { _CaseManagerList.gameObject.SetActive(true); _HostCard.EnablePickup(true); } public void HostDisabled() { _CaseManagerList.gameObject.SetActive(false); _HostCard.EnablePickup(false); } public void LoadCaseFile(CaseManagerListEntry CaseFile) { Networking.SetOwner(Networking.LocalPlayer, gameObject); Networking.SetOwner(Networking.LocalPlayer, _Round1Manager.gameObject); Networking.SetOwner(Networking.LocalPlayer, _Round2Manager.gameObject); Networking.SetOwner(Networking.LocalPlayer, _Round3Manager.gameObject); Networking.SetOwner(Networking.LocalPlayer, _HostCard.gameObject); Networking.SetOwner(Networking.LocalPlayer, _LiveIndicator.gameObject); _CaseFileCluesURL = CaseFile.CaseFileURL; _CaseFileLootImage = CaseFile.LootImage; _CaseFileMaps = CaseFile.MapFiles; _CaseFileVideos = CaseFile.VideoFiles; _CaseFileVideosFallback = CaseFile.FallbackVideoFiles; _CaseFileClueImages = CaseFile.ClueImages; VRCStringDownloader.LoadUrl(_CaseFileCluesURL, (IUdonEventReceiver)this); } public override void OnStringLoadSuccess(IVRCStringDownload DownloadedString) { string ErrorString = ""; string JSONString = DownloadedString.Result; if (VRCJson.TryDeserializeFromJson(JSONString, out DataToken JSONResult)) { if (JSONResult.TokenType == TokenType.DataDictionary) { _CaseFileDictionary = JSONResult.DataDictionary; if (_CaseFileDictionary.ContainsKey("Case Title") && _CaseFileDictionary.ContainsKey("Case Description") && _CaseFileDictionary.ContainsKey("Stolen Loot") && _CaseFileDictionary.ContainsKey("Accused Crook")) { _CaseTitle = _CaseFileDictionary["Case Title"].String; _CaseDescription = _CaseFileDictionary["Case Description"].String; _StolenLoot = _CaseFileDictionary["Stolen Loot"].String; _AccusedCrook = (AccusedCrook)(int)_CaseFileDictionary["Accused Crook"].Number; if (_CaseFileDictionary.ContainsKey("Intro Video")) { _CaseIntroVideo = (int)_CaseFileDictionary["Intro Video"].Number; } if (_CaseFileDictionary.ContainsKey("Intro Video Transcript")) { _CaseIntroVideoTranscript = _CaseFileDictionary["Intro Video Transcript"].String; } if (_CaseFileDictionary.ContainsKey("Round 1") && _CaseFileDictionary.ContainsKey("Round 2") && _CaseFileDictionary.ContainsKey("Round 3")) { // Attempt to load Round 1 data if (_CaseFileDictionary["Round 1"].TokenType == TokenType.DataList) { _Round1Manager.LoadQuestionData(_CaseFileDictionary["Round 1"]); } else { ErrorString = "Ensure the 'Round 1' entry is an array of dictionaries."; } // Attempt to load Round 2 data if (_CaseFileDictionary["Round 2"].TokenType == TokenType.DataDictionary) { _Round2Manager.LoadQuestionData(_CaseFileDictionary["Round 2"]); } else { ErrorString = "Ensure the 'Round 2' entry is a dictionary with a location name and a list of landmarks."; } // Attempt to load Round 3 data if (_CaseFileDictionary["Round 3"].TokenType == TokenType.DataDictionary) { _Round3Manager.LoadQuestionData(_CaseFileDictionary["Round 3"]); // Get the final round continent so we can reference it in Round 2 _FinalRoundContinent = (ContinentMap)(int)_CaseFileDictionary["Round 3"].DataDictionary["Continent"].Number; } else { ErrorString = "Ensure the 'Round 3' dictionary entry is a dictionary with a key called 'Continent' and an integer value."; } ContinueToRound1(); } else { ErrorString = "Could not find all the necessary keys for game rounds."; } } else { ErrorString = "All case files must include a case title and description, plus the stolen loot and accused crook."; } } else { ErrorString = "Ensure the first element is a dictionary"; } } else { ErrorString = "JSON failed to parse."; } if (ErrorString != "") { Debug.LogError("[CaseManager] Malformed case file. " + ErrorString); } RequestSerialization(); } public void ContinueToRound1() { _Round3Manager.DeinitialiseGameMode(); _HostCard.SetGameManager(_Round1Manager); _Round1Manager.InitialiseGameMode(); _LiveIndicator.Active = true; } public void ContinueToRound2() { _Round1Manager.DeinitialiseGameMode(); _HostCard.SetGameManager(_Round2Manager); _Round2Manager.InitialiseGameMode(); _LiveIndicator.Active = true; } public void ContinueToRound3() { _Round2Manager.DeinitialiseGameMode(); _HostCard.SetGameManager(_Round3Manager); _Round3Manager.InitialiseGameMode(); _LiveIndicator.Active = true; } public void EndGame() { _LiveIndicator.Active = false; } public string GetCaseTitle() { return _CaseTitle; } public string GetCaseDescription() { return _CaseDescription; } public string GetIntroVideoTranscript() { return _CaseIntroVideoTranscript; } public string GetLoot() { return _StolenLoot; } public AccusedCrook GetCrook() { return _AccusedCrook; } public string GetCrookName() { return CrookToString(_AccusedCrook); } public VRCUrl[] GetMaps() { return _CaseFileMaps; } public VRCUrl GetMap(int MapIndex) { return _CaseFileMaps[MapIndex]; } public int GetMapCount() { return _CaseFileMaps.Length; } public VRCUrl GetLootImage() { return _CaseFileLootImage; } public VRCUrl[] GetVideos() { return _CaseFileVideos; } public int GetIntroVideo() { return _CaseIntroVideo; } public VRCUrl GetVideo(int VideoIndex, bool Fallback = false) { return (Fallback && _CaseFileVideosFallback.Length >= VideoIndex) ? _CaseFileVideosFallback[VideoIndex] : _CaseFileVideos[VideoIndex]; } public int GetVideoCount() { return _CaseFileVideos.Length; } public VRCUrl GetClueImage(int ImageIndex) { return _CaseFileClueImages[ImageIndex]; } public ContinentMap GetFinalRoundContinent() { return _FinalRoundContinent; } public void SetCurrentWinningPlayers(string[] NewWinners) { _CurrentWinningPlayers = NewWinners; RequestSerialization(); } public string[] GetCurrentWinningPlayers() { return _CurrentWinningPlayers; } public string CrookToString(AccusedCrook Crook) { switch (Crook) { case AccusedCrook.Contessa: return "Contessa"; case AccusedCrook.DoubleTrouble: return "Double Trouble"; case AccusedCrook.EarthaBrute: return "Eartha Brute"; case AccusedCrook.Kneemoi: return "Kneemoi"; case AccusedCrook.PattyLarceny: return "Patty Larceny"; case AccusedCrook.Robocrook: return "Robocrook"; case AccusedCrook.SarahNade: return "Sarah Nade"; case AccusedCrook.TopGrunge: return "Top Grunge"; case AccusedCrook.VicTheSlick: return "Vic The Slick"; case AccusedCrook.WonderRat: return "Wonder Rat"; } return "[[ERROR]]"; } public string ContinentToString(ContinentMap Continent) { switch (Continent) { case ContinentMap.Africa: return "Africa"; case ContinentMap.Asia: return "Asia"; case ContinentMap.Europe: return "Europe"; case ContinentMap.NorthAmerica: return "North America"; case ContinentMap.Oceania: return "Oceania"; case ContinentMap.SouthAmerica: return "South America"; } return "[[ERROR]]"; } public string GetAccusedCrook() { return CrookToString(_AccusedCrook); } public Texture GetAccusedCrookPortrait() { return CrookPortraits[(int)_AccusedCrook]; } }