using UdonSharp; using UnityEngine; using VRC.SDK3.Data; using VRC.SDK3.UdonNetworkCalling; using VRC.Udon.Common.Interfaces; using VRC.SDKBase; using VRC.SDK3.StringLoading; public enum PresentationMedium { VideoClue, VideoMusicClue, ACMECrimenetComputer } [UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] public class GameManagerRound1 : GameManagerBase { [SerializeField] private CaseManager _CaseManager; [SerializeField] private CaseVideoSyncPlayer _VideoPlayer; [SerializeField] private VRCUrl _TiebreakerData; [UdonSynced] protected bool _GameHasBegun = false; [UdonSynced] private int _QuestionIndex = 0; [UdonSynced] private int _QuestionStage = 0; [UdonSynced] private int _QuestionSubstage = 0; [UdonSynced] private int _QuestionCorrectResponse = 0; [UdonSynced] private RoundSegmentType _CurrentQuestionType = RoundSegmentType.BetweenSegments; private DataList _QuestionsList = new DataList(); private DataDictionary _CurrentQuestion; [UdonSynced] private bool _BuzzInAllowed = false; [UdonSynced] private bool[] _PlayerBuzzInAllowed; [UdonSynced] private int _BuzzedInPlayer = 0; [UdonSynced] private int[] _FinalRoundPlayersSortedByScore; [UdonSynced] private int[] _TiebreakerPlayerNumbers; [SerializeField] private PlayerPodium[] _PlayerPodiums; [SerializeField] private AudioManager _AudioManager; public override void InitialiseGameMode() { _GameHasBegun = false; _QuestionIndex = 0; _QuestionStage = 0; _PlayerBuzzInAllowed = new bool[_PlayerPodiums.Length]; RequestSerialization(); base.InitialiseGameMode(); } public override void LoadQuestionData(DataToken Data) { _QuestionsList.Clear(); HostCardBetweenRoundsInterface Interface = (HostCardBetweenRoundsInterface)GetHostCardInterface(RoundSegmentType.BetweenSegments); DataList DataDict = Data.DataList; for (int i = 0; i < DataDict.Count; i++) { if (DataDict[i].TokenType == TokenType.DataDictionary) { _QuestionsList.Add(DataDict[i]); } } if (_QuestionsList.Count == 0) { Interface.HeaderUI.text = "Unable to find any questions. Ensure the root array elements are all objects."; return; } Interface.HeaderUI.text = "Found " + _QuestionsList.Count + " questions in this case file. Press 'Use' button to show scores."; // Reset podiums on a successful case load for (int i = 0; i < _PlayerPodiums.Length; i++) { NetworkCalling.SendCustomNetworkEvent( (IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "ResetPodium"); } _GameHasBegun = false; EnableInteraction("Start Game"); } private void PlayIntroVideo() { HostCardBetweenRoundsInterface Interface = (HostCardBetweenRoundsInterface)GetHostCardInterface(RoundSegmentType.BetweenSegments); Interface.HeaderUI.text = "Playing intro video..."; _VideoPlayer.VideoURL = _CaseManager.GetIntroVideo(); EnableInteraction("Assign Points"); } private void InitialiseQuestion() { _CurrentQuestion = _QuestionsList[_QuestionIndex].DataDictionary; // Again, why does this work, but not just casting to an enum? _CurrentQuestionType = (RoundSegmentType)(int)_CurrentQuestion["Type"].Number; _QuestionStage = 0; } private void AssignStarterPoints() { InitialiseQuestion(); for (int i = 0; i < _PlayerPodiums.Length; i++) { NetworkCalling.SendCustomNetworkEvent( (IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "DisplayScore"); } NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_AudioManager, NetworkEventTarget.All, "PlaySFXAtPitch", SFXEventType.Ding, AudioManager.D6); ShowBetweenQuestionsInterface(); DisableChoiceCards(); DisableRiskCards(); DisableBuzzers(); EnableInteraction("Show First Question"); _GameHasBegun = true; } private void InitialiseCluePresentation() { HostCardBetweenRoundsInterface Interface; if (!_CurrentQuestion.ContainsKey("Presentation") || _CurrentQuestion["Presentation"].TokenType != TokenType.DataDictionary || !_CurrentQuestion["Presentation"].DataDictionary.ContainsKey("Medium")) { Interface = (HostCardBetweenRoundsInterface)GetHostCardInterface(RoundSegmentType.BetweenSegments); Interface.HeaderUI.text = "No special presentation available. Continuing on..."; EnableInteraction("Reveal Question"); return; } if (_CurrentQuestion.ContainsKey("Maps") && _CurrentQuestion["Maps"].TokenType == TokenType.DataList) { DataList Maps = _CurrentQuestion["Maps"].DataList; for (int i = 0; i < Maps.Count; i++) { NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_VideoPlayer, NetworkEventTarget.All, "LoadMap", (int)Maps[i].Number); } } DataDictionary Presentation = _CurrentQuestion["Presentation"].DataDictionary; switch ((PresentationMedium)(int)Presentation["Medium"].Number) { case PresentationMedium.VideoClue: InitialiseVideoClue(Presentation); break; case PresentationMedium.VideoMusicClue: InitialiseVideoMusicClue(Presentation); break; case PresentationMedium.ACMECrimenetComputer: InitialiseACMECrimenetComputer(Presentation); break; } } private void InitialiseVideoClue(DataDictionary Presentation) { DataList Videos = Presentation["Videos"].DataList; if (_QuestionSubstage < Videos.Count) { HostCardBetweenRoundsInterface Interface = (HostCardBetweenRoundsInterface)GetHostCardInterface(RoundSegmentType.BetweenSegments); Interface.HeaderUI.text = "Playing video..."; _VideoPlayer.VideoURL = _CaseManager.GetVideo((int)Videos[_QuestionSubstage].Number); _QuestionSubstage++; EnableInteraction("Play Video"); } else { MultipleChoiceRevealQuestion(); } } private void InitialiseVideoMusicClue(DataDictionary Presentation) { // The full presentation is not ready yet, so temporarily treat it as // a regular video clue. InitialiseVideoClue(Presentation); } private void InitialiseACMECrimenetComputer(DataDictionary Presentation) { HostCardBetweenRoundsInterface Interface = (HostCardBetweenRoundsInterface)GetHostCardInterface(RoundSegmentType.BetweenSegments); Interface.HeaderUI.text = "This part isn't done yet. This will eventually have a really cool computer give you the clue."; _QuestionSubstage = 0; MultipleChoiceRevealQuestion(); } private void MultipleChoiceRevealQuestion() { _QuestionSubstage = 0; _VideoPlayer.SubMapIndex = SubMap.NoLabels; HostCardMultipleChoiceInterface Interface = (HostCardMultipleChoiceInterface)GetHostCardInterface(RoundSegmentType.MultipleChoice); ResetMultipleChoiceInterface(Interface); Interface.HeaderUI.text = RoundSegmentTypeToString(_CurrentQuestionType); DataList ClueStrings = _CurrentQuestion["Clues"].DataList; for (int i = 0; i < Interface.CluesUI.Length && i < ClueStrings.Count; i++) { Interface.CluesUI[i].text = ClueStrings[i].ToString(); } DataList Choices = _CurrentQuestion["Choices"].DataList; for (int i = 0; i < Choices.Count && i < Interface.ChoiceUI.Length; i++) { Interface.ChoiceUI[i].text = Choices[i].ToString(); } _QuestionCorrectResponse = (int)_CurrentQuestion["Correct Response"].Number; EnableInteraction("Reveal Choice 1"); } private void MultipleChoiceRevealChoices() { SendCustomEvent(nameof(MultipleChoiceRevealChoice1)); SendCustomEventDelayedSeconds(nameof(MultipleChoiceRevealChoice2), 1.25f); SendCustomEventDelayedSeconds(nameof(MultipleChoiceRevealChoice3), 2.5f); } private void MultipleChoiceRevealChoice1() { _VideoPlayer.SubMapIndex = SubMap.OneLabel; HostCardMultipleChoiceInterface Interface = (HostCardMultipleChoiceInterface)GetHostCardInterface(RoundSegmentType.MultipleChoice); Interface.ChoiceButtonImages[0].color = (_QuestionCorrectResponse == 1) ? Color.green : Color.red; NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_AudioManager, NetworkEventTarget.All, "PlaySFXAtPitch", SFXEventType.Ding, AudioManager.As5); EnableInteraction("Reveal Choice 2"); } private void MultipleChoiceRevealChoice2() { _VideoPlayer.SubMapIndex = SubMap.TwoLabels; HostCardMultipleChoiceInterface Interface = (HostCardMultipleChoiceInterface)GetHostCardInterface(RoundSegmentType.MultipleChoice); Interface.ChoiceButtonImages[1].color = (_QuestionCorrectResponse == 2) ? Color.green : Color.red; NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_AudioManager, NetworkEventTarget.All, "PlaySFXAtPitch", SFXEventType.Ding, AudioManager.C6); EnableInteraction("Reveal Choice 3"); } private void MultipleChoiceRevealChoice3() { _VideoPlayer.SubMapIndex = SubMap.ThreeLabels; HostCardMultipleChoiceInterface Interface = (HostCardMultipleChoiceInterface)GetHostCardInterface(RoundSegmentType.MultipleChoice); Interface.ChoiceButtonImages[2].color = (_QuestionCorrectResponse == 3) ? Color.green : Color.red; DataList Choices = _CurrentQuestion["Choices"].DataList; EnableChoiceCards(); // Complex per-podium randomiser, to prevent peeking Random.InitState(Networking.GetServerTimeInMilliseconds()); for (int i = 0; i < _PlayerPodiums.Length; i++) { int[] Indices = { 0, 1, 2 }; int[] ChoiceOrder = { -1, -1, -1 }; int Choice1Index = Indices[Random.Range(0, 3)]; ChoiceOrder[0] = Choice1Index; Indices[Choice1Index] = -1; int Choice2Index = -1; while (Choice2Index == -1) { Choice2Index = Indices[Random.Range(0, 3)]; } ChoiceOrder[1] = Choice2Index; Indices[Choice2Index] = -1; int Choice3Index = -1; while (Choice3Index == -1) { Choice3Index = Indices[Random.Range(0, 3)]; } ChoiceOrder[2] = Choice3Index; Indices[Choice3Index] = -1; string[] ChoiceStrings = { Choices[0].ToString(), Choices[1].ToString(), Choices[2].ToString() }; NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "SetCardChoices", ChoiceStrings, ChoiceOrder); } NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_AudioManager, NetworkEventTarget.All, "PlaySFXAtPitch", SFXEventType.Ding, AudioManager.D6); EnableInteraction("Lock Answers"); } private void MultipleChoiceLockAnswers() { for (int i = 0; i < _PlayerPodiums.Length; i++) { NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "LockInChoice"); } HostCardMultipleChoiceInterface Interface = (HostCardMultipleChoiceInterface)GetHostCardInterface(RoundSegmentType.MultipleChoice); Interface.HeaderUI.text = "LOCKED IN"; for (int i = 0; i < Interface.CluesUI.Length; i++) { Interface.CluesUI[i].text = ""; } for (int i = 0; i < Interface.ChoiceUI.Length; i++) { if (i != (_QuestionCorrectResponse - 1)) { Interface.ChoiceUI[i].text = ""; } } EnableInteraction("Reveal Answers And Assign Points"); } private void MultipleChoiceRevealAnswersAndAssignPoints() { HostCardMultipleChoiceInterface Interface = (HostCardMultipleChoiceInterface)GetHostCardInterface(RoundSegmentType.MultipleChoice); Interface.HeaderUI.text = "ANSWER REVEALED"; bool IsSomeoneCorrect = false; for (int i = 0; i < _PlayerPodiums.Length; i++) { bool Correct = _PlayerPodiums[i].VerifyMultipleChoiceResponse(_QuestionCorrectResponse); IsSomeoneCorrect = IsSomeoneCorrect ? true : Correct; } if (IsSomeoneCorrect) { NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_AudioManager, NetworkEventTarget.All, "PlaySFX", SFXEventType.Round1Correct); } else { NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_AudioManager, NetworkEventTarget.All, "PlaySFX", SFXEventType.Round1Incorrect); } _VideoPlayer.FlashCorrectAnswer = true; EnableInteraction("Next Question"); } private void BeginLightningRound() { HostCardLightningRoundInterface Interface = (HostCardLightningRoundInterface)GetHostCardInterface(RoundSegmentType.LightningRound); Interface.HeaderUI.text = RoundSegmentTypeToString(_CurrentQuestionType) + " | " + _CurrentQuestion["Location"].ToString(); for (int i = 0; i < Interface.ChoiceUI.Length && i < Interface.ChoiceButtons.Length; i++) { Interface.ChoiceUI[i].text = ""; } Interface.QuestionUI.text = "All of these questions are worth 5 Acme Crimebucks. These questions are all about [[CROOK]]'s most recent whereabouts, which is " + _CurrentQuestion["Location"].ToString() + ". Hands on your buzzers. Here's the first question..."; EnableBuzzers(); EnableInteraction("First Question"); } private void NewLightningRoundQuestion(int Question) { DataDictionary CurrentQuestion = _CurrentQuestion["Questions"].DataList[Question - 1].DataDictionary; HostCardLightningRoundInterface Interface = (HostCardLightningRoundInterface)GetHostCardInterface(RoundSegmentType.LightningRound); Interface.QuestionUI.text = CurrentQuestion["Question"].ToString(); DataList Choices = CurrentQuestion["Choices"].DataList; for (int i = 0; i < Choices.Count && i < Interface.ChoiceUI.Length; i++) { Interface.ChoiceUI[i].text = Choices[i].ToString(); } _QuestionCorrectResponse = (int)CurrentQuestion["Correct Response"].Number; for (int i = 0; i < Interface.ChoiceButtons.Length && i < Interface.ChoiceButtonImages.Length; i++) { Interface.ChoiceButtonImages[i].color = (_QuestionCorrectResponse == (i + 1)) ? Color.green : Color.red; Interface.ChoiceButtons[i].interactable = true; } Interface.OtherButton.interactable = true; EnableBuzzInPeriodForAllPlayers(); } private void LightningRoundCheckAnswer(int Answer) { if (_QuestionCorrectResponse == Answer) { HostCardLightningRoundInterface Interface = (HostCardLightningRoundInterface)GetHostCardInterface(RoundSegmentType.LightningRound); for (int i = 0; i < Interface.ChoiceButtons.Length; i++) { Interface.ChoiceButtons[i].interactable = false; } Interface.OtherButton.interactable = false; int PodiumIndex = _BuzzedInPlayer - 1; if (PodiumIndex >= 0 && PodiumIndex < _PlayerPodiums.Length) { _PlayerPodiums[PodiumIndex].SendCustomNetworkEvent(NetworkEventTarget.Owner, "AdjustScore", 5); } EndBuzzInPeriod(); EnableInteraction("Next Question"); } else { WaitForBuzzInsWithoutLastPlayer(); } } public void LightningRoundCheckAnswer1() { LightningRoundCheckAnswer(1); } public void LightningRoundCheckAnswer2() { LightningRoundCheckAnswer(2); } public void LightningRoundCheckAnswer3() { LightningRoundCheckAnswer(3); } public void LightningRoundOtherOptionChosen() { WaitForBuzzInsWithoutLastPlayer(); } private void BeginTheChase() { HostCardTheChaseInterface Interface = (HostCardTheChaseInterface)GetHostCardInterface(RoundSegmentType.TheChase); Interface.HeaderUI.text = RoundSegmentTypeToString(_CurrentQuestionType); Interface.ClueUI.text = ""; for (int i = 0; i < Interface.ChoiceUI.Length && i < Interface.ChoiceButtons.Length; i++) { Interface.ChoiceUI[i].text = ""; } EnableBuzzers(); EnableInteraction("Play The Music"); } private void PlayTheChaseMusic() { HostCardTheChaseInterface Interface = (HostCardTheChaseInterface)GetHostCardInterface(RoundSegmentType.TheChase); NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_AudioManager, NetworkEventTarget.All, "PlayMusic", MusicEventType.TheChase); Interface.HeaderUI.text = RoundSegmentTypeToString(RoundSegmentType.TheChase); Interface.ClueUI.text = "All of these questions are worth 5 Acme Crimebucks. Hands on your buzzers. Listen carefully. Here we go."; EnableBuzzers(); EnableInteraction("Here We Go"); } private void NewTheChaseClue(int Clue) { DataDictionary CurrentClue = _CurrentQuestion["Clues"].DataList[Clue - 1].DataDictionary; HostCardTheChaseInterface Interface = (HostCardTheChaseInterface)GetHostCardInterface(RoundSegmentType.TheChase); Interface.ClueUI.text = CurrentClue["Clue"].ToString(); DataList Choices = CurrentClue["Choices"].DataList; for (int i = 0; i < Choices.Count && i < Interface.ChoiceUI.Length; i++) { Interface.ChoiceUI[i].text = Choices[i].ToString(); } Interface.OtherButton.interactable = true; _QuestionCorrectResponse = (int)CurrentClue["Correct Response"].Number; for (int i = 0; i < Interface.ChoiceButtons.Length && i < Interface.ChoiceButtonImages.Length; i++) { Interface.ChoiceButtonImages[i].color = (_QuestionCorrectResponse == (i + 1)) ? Color.green : Color.red; Interface.ChoiceButtons[i].interactable = true; } EnableBuzzInPeriodForAllPlayers(); } private void TheChaseCheckAnswer(int Answer) { if (_QuestionCorrectResponse == Answer) { int PodiumIndex = _BuzzedInPlayer - 1; if (PodiumIndex >= 0 && PodiumIndex < _PlayerPodiums.Length) { _PlayerPodiums[PodiumIndex].SendCustomNetworkEvent(NetworkEventTarget.Owner, "AdjustScore", 5); } } TheChaseEndClue(); } public void TheChaseCheckAnswer1() { TheChaseCheckAnswer(1); } public void TheChaseCheckAnswer2() { TheChaseCheckAnswer(2); } public void TheChaseCheckAnswer3() { TheChaseCheckAnswer(3); } public void TheChaseEndClue() { HostCardTheChaseInterface Interface = (HostCardTheChaseInterface)GetHostCardInterface(RoundSegmentType.TheChase); for (int i = 0; i < Interface.ChoiceButtons.Length; i++) { Interface.ChoiceButtons[i].interactable = false; } Interface.OtherButton.interactable = false; EndBuzzInPeriod(); EnableInteraction("Next Question"); } private void BeginFinalRound() { HostCardBetweenRoundsInterface Interface = (HostCardBetweenRoundsInterface)GetHostCardInterface(RoundSegmentType.BetweenSegments); Interface.HeaderUI.text = "Introduce the round here."; // Preload the maps for this section. if (_CurrentQuestion.ContainsKey("Maps") && _CurrentQuestion["Maps"].TokenType == TokenType.DataList) { DataList Maps = _CurrentQuestion["Maps"].DataList; for (int i = 0; i < Maps.Count; i++) { NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_VideoPlayer, NetworkEventTarget.All, "LoadMap", (int)Maps[i].Number); } } EnableInteraction("Show Map Preview"); } private void FinalRoundShowMapPreview() { _VideoPlayer.SubMapIndex = SubMap.ThreeLabels; EnableRiskCards(); HostCardBetweenRoundsInterface Interface = (HostCardBetweenRoundsInterface)GetHostCardInterface(RoundSegmentType.BetweenSegments); Interface.HeaderUI.text = "Introduce the round here."; EnableInteraction("Think About It"); } private void FinalRoundPlayThinkingMusic() { HostCardBetweenRoundsInterface Interface = (HostCardBetweenRoundsInterface)GetHostCardInterface(RoundSegmentType.BetweenSegments); Interface.HeaderUI.text = "Think about it..."; NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_AudioManager, NetworkEventTarget.All, "PlayMusic", MusicEventType.ThinkAboutIt); SendCustomEventDelayedSeconds(nameof(ThinkAboutItCountdownFinished), 15.0f); } public void ThinkAboutItCountdownFinished() { HostCardMultipleChoiceInterface Interface = (HostCardMultipleChoiceInterface)GetHostCardInterface(RoundSegmentType.FinalRound); Interface.HeaderUI.text = RoundSegmentTypeToString(_CurrentQuestionType); DataList ClueStrings = _CurrentQuestion["Clues"].DataList; for (int i = 0; i < Interface.CluesUI.Length && i < ClueStrings.Count; i++) { Interface.CluesUI[i].text = ClueStrings[i].ToString(); } DataList Choices = _CurrentQuestion["Choices"].DataList; for (int i = 0; i < Choices.Count && i < Interface.ChoiceUI.Length; i++) { Interface.ChoiceUI[i].text = Choices[i].ToString(); } _QuestionCorrectResponse = (int)_CurrentQuestion["Correct Response"].Number; for (int i = 0; i < _PlayerPodiums.Length; i++) { NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "LockInRisk"); } EnableInteraction("Present Clues"); } private void FinalRoundRevealChoices() { SendCustomEvent(nameof(FinalRoundRevealChoice1)); SendCustomEventDelayedSeconds(nameof(FinalRoundRevealChoice2), 1.25f); SendCustomEventDelayedSeconds(nameof(FinalRoundRevealChoice3), 2.5f); } private void FinalRoundRevealChoice1() { _VideoPlayer.SubMapIndex = SubMap.OneLabel; HostCardMultipleChoiceInterface Interface = (HostCardMultipleChoiceInterface)GetHostCardInterface(RoundSegmentType.FinalRound); Interface.ChoiceButtonImages[0].color = (_QuestionCorrectResponse == 1) ? Color.green : Color.red; NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_AudioManager, NetworkEventTarget.All, "PlaySFXAtPitch", SFXEventType.Ding, AudioManager.As5); EnableInteraction("Reveal Choice 2"); } private void FinalRoundRevealChoice2() { _VideoPlayer.SubMapIndex = SubMap.TwoLabels; HostCardMultipleChoiceInterface Interface = (HostCardMultipleChoiceInterface)GetHostCardInterface(RoundSegmentType.FinalRound); Interface.ChoiceButtonImages[1].color = (_QuestionCorrectResponse == 2) ? Color.green : Color.red; NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_AudioManager, NetworkEventTarget.All, "PlaySFXAtPitch", SFXEventType.Ding, AudioManager.C6); EnableInteraction("Reveal Choice 3"); } private void FinalRoundRevealChoice3() { _VideoPlayer.SubMapIndex = SubMap.ThreeLabels; HostCardMultipleChoiceInterface Interface = (HostCardMultipleChoiceInterface)GetHostCardInterface(RoundSegmentType.FinalRound); Interface.ChoiceButtonImages[2].color = (_QuestionCorrectResponse == 3) ? Color.green : Color.red; DataList Choices = _CurrentQuestion["Choices"].DataList; EnableChoiceCards(); // Complex per-podium randomiser, to prevent peeking Random.InitState(Networking.GetServerTimeInMilliseconds()); for (int i = 0; i < _PlayerPodiums.Length; i++) { int[] Indices = { 0, 1, 2 }; int[] ChoiceOrder = { -1, -1, -1 }; int Choice1Index = Indices[Random.Range(0, 3)]; ChoiceOrder[0] = Choice1Index; Indices[Choice1Index] = -1; int Choice2Index = -1; while (Choice2Index == -1) { Choice2Index = Indices[Random.Range(0, 3)]; } ChoiceOrder[1] = Choice2Index; Indices[Choice2Index] = -1; int Choice3Index = -1; while (Choice3Index == -1) { Choice3Index = Indices[Random.Range(0, 3)]; } ChoiceOrder[2] = Choice3Index; Indices[Choice3Index] = -1; string[] ChoiceStrings = { Choices[0].ToString(), Choices[1].ToString(), Choices[2].ToString() }; NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "SetCardChoices", ChoiceStrings, ChoiceOrder); } NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_AudioManager, NetworkEventTarget.All, "PlaySFXAtPitch", SFXEventType.Ding, AudioManager.D6); EnableInteraction("Lock Answers"); } private void FinalRoundLockAnswers() { for (int i = 0; i < _PlayerPodiums.Length; i++) { NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "LockInChoice"); } HostCardMultipleChoiceInterface Interface = (HostCardMultipleChoiceInterface)GetHostCardInterface(RoundSegmentType.FinalRound); Interface.HeaderUI.text = "LOCKED IN"; for (int i = 0; i < Interface.CluesUI.Length; i++) { Interface.CluesUI[i].text = ""; } for (int i = 0; i < Interface.ChoiceUI.Length; i++) { if (i != (_QuestionCorrectResponse - 1)) { Interface.ChoiceUI[i].text = ""; } } SortPlayersHighToLowScore(); EnableInteraction("Reveal Third Place"); } private void SortPlayersHighToLowScore() { int PlayerPodiumsCount = _PlayerPodiums.Length; // We're gonna pull a trick here that lets us sort the scores and // players properly. By multiplying the score by 1000 and then adding // the player number to it, we can easily sort by score, followed by // player number, and then convert that back to the needed information // later. We also store the inverse player number (1000 - player no.) // so that they are sorted in the opposite order they normally would // when scores are equal, ensuring that if there is a 3-way tie, the // order will still be Player 1 -> Player 2 -> Player 3. DataList ScoresList = new DataList(); for (int i = 0; i < PlayerPodiumsCount; i++) { ScoresList.Add(((_PlayerPodiums[i].PlayerScore) * 1000) + (1000 - _PlayerPodiums[i].PlayerNumber)); } ScoresList.Sort(); _FinalRoundPlayersSortedByScore = new int[ScoresList.Count]; for (int i = 0; i < _FinalRoundPlayersSortedByScore.Length; i++) { int DecodedPlayerNumber = 1000 - (ScoresList[i].Int % 1000); _FinalRoundPlayersSortedByScore[_FinalRoundPlayersSortedByScore.Length - i - 1] = DecodedPlayerNumber; } } private void FinalRoundRevealPlayerPlace(int PlayerPlace) { int PlayerNumber = _FinalRoundPlayersSortedByScore[PlayerPlace - 1]; HostCardBetweenRoundsInterface Interface = (HostCardBetweenRoundsInterface)GetHostCardInterface(RoundSegmentType.BetweenSegments); Interface.HeaderUI.text = "Player: " + _PlayerPodiums[PlayerNumber - 1].PlayerName; _PlayerPodiums[PlayerNumber - 1].EnableBuzzInEffect(true); EnableInteraction("Assign Points"); } private void FinalRoundAssignPointsToPlayerPlace(int PlayerPlace) { int PlayerNumber = _FinalRoundPlayersSortedByScore[PlayerPlace - 1]; _PlayerPodiums[PlayerNumber - 1].EnableBuzzInEffect(false); _PlayerPodiums[PlayerNumber - 1].VerifyFinalRoundResponse(_QuestionCorrectResponse); // If we're assigning points to the first-place player, then we're // about to finish up, so just move on to determining winners. if (PlayerPlace == 1) { EnableInteraction("End Of Round"); } else { EnableInteraction("Reveal Next Player Answer"); } } private bool FinalRoundCheckNeedForTiebreaker() { DisableChoiceCards(); DisableRiskCards(); DisableBuzzers(); _VideoPlayer.ClearScreen(); SortPlayersHighToLowScore(); int[] SortedPlayerScores = new int[_PlayerPodiums.Length]; for (int i = 0; i < _PlayerPodiums.Length; i++) { SortedPlayerScores[i] = _PlayerPodiums[_FinalRoundPlayersSortedByScore[i] - 1].PlayerScore; } HostCardBetweenRoundsInterface Interface = (HostCardBetweenRoundsInterface)GetHostCardInterface(RoundSegmentType.BetweenSegments); PlayerPodium Number1Podium = _PlayerPodiums[_FinalRoundPlayersSortedByScore[0] - 1]; PlayerPodium Number2Podium = _PlayerPodiums[_FinalRoundPlayersSortedByScore[1] - 1]; PlayerPodium Number3Podium = _PlayerPodiums[_FinalRoundPlayersSortedByScore[2] - 1]; VRCPlayerApi Number1 = Networking.GetOwner(Number1Podium.gameObject); VRCPlayerApi Number2 = Networking.GetOwner(Number2Podium.gameObject); VRCPlayerApi Number3 = Networking.GetOwner(Number3Podium.gameObject); bool TiebreakerNeeded = false; if (SortedPlayerScores[1] == SortedPlayerScores[2]) { if (SortedPlayerScores[0] == SortedPlayerScores[1]) { Interface.HeaderUI.text = "We have a three-way tie. We'll start with a tiebreaker between " + Number2.displayName + " and " + Number3.displayName + "."; } else { Interface.HeaderUI.text = Number1.displayName + " is in first place and will be moving on to the next round. There is a tie for second place between " + Number2.displayName + " and " + Number3.displayName + ", so we will move on to a tiebreaker."; } TiebreakerNeeded = true; PrepareTiebreakerRound(Number2Podium.PlayerNumber, Number3Podium.PlayerNumber); } else { if (SortedPlayerScores[0] == SortedPlayerScores[1]) { // Randomly choose which player gets the first turn in the next round. Random.InitState(Networking.GetServerTimeInMilliseconds()); VRCPlayerApi[] Randomiser = new VRCPlayerApi[2]; Randomiser[0] = Number1; Randomiser[1] = Number2; int RandomIndex = Random.Range(0, 2); Randomiser[RandomIndex].SetPlayerTag("Round1State", "FirstPlace"); Interface.HeaderUI.text = Number1.displayName + " and " + Number2.displayName + " are tied for first place; both will move on to the next round."; } else { // Tag first place player as first place. Number1.SetPlayerTag("Round1State", "FirstPlace"); Interface.HeaderUI.text = Number1.displayName + " and " + Number2.displayName + " will move on to the next round."; } } return TiebreakerNeeded; } private void PrepareTiebreakerRound(int FirstPlayer, int SecondPlayer) { _QuestionStage = 0; _TiebreakerPlayerNumbers = new int[2]; _TiebreakerPlayerNumbers[0] = FirstPlayer; _TiebreakerPlayerNumbers[1] = SecondPlayer; } private void AdvanceToNextQuestion() { DisableChoiceCards(); DisableRiskCards(); DisableBuzzers(); _VideoPlayer.ClearScreen(); _QuestionIndex++; if (_QuestionIndex >= _QuestionsList.Count) { PrepareEndGame(); } else { PrepareNextQuestion(); } } private void PrepareNextQuestion() { InitialiseQuestion(); ShowBetweenQuestionsInterface(); EnableInteraction("Show Next Question"); } private void PrepareEndGame() { bool TiebreakerNeeded = FinalRoundCheckNeedForTiebreaker(); if (TiebreakerNeeded) { _CurrentQuestionType = RoundSegmentType.Tiebreaker; EnableInteraction("Advance To Tiebreaker"); } else { HostCardBetweenRoundsInterface Interface = (HostCardBetweenRoundsInterface)GetHostCardInterface(RoundSegmentType.BetweenSegments); Interface.HeaderUI.text = "Round is over. Move to the loot recovery area."; _CaseManager.ContinueToRound2(); EnableInteraction("End Game"); } } private void BeginTiebreakerRound() { HostCardBetweenRoundsInterface Interface = (HostCardBetweenRoundsInterface)GetHostCardInterface(RoundSegmentType.BetweenSegments); Interface.HeaderUI.text = "Preparing tiebreaker..."; VRCStringDownloader.LoadUrl(_TiebreakerData, (IUdonEventReceiver)this); } public override void OnStringLoadSuccess(IVRCStringDownload TiebreakerString) { HostCardTiebreakerInterface Interface = (HostCardTiebreakerInterface)GetHostCardInterface(RoundSegmentType.Tiebreaker); string ErrorString = ""; string JSONString = TiebreakerString.Result; if (VRCJson.TryDeserializeFromJson(JSONString, out DataToken JSONResult)) { if (JSONResult.TokenType == TokenType.DataDictionary) { DataDictionary TiebreakerDictionary = JSONResult.DataDictionary; DataList TiebreakerRegions = TiebreakerDictionary.GetKeys(); Random.InitState(Networking.GetServerTimeInMilliseconds()); int TiebreakerIndex = Random.Range(0, TiebreakerRegions.Count); string TiebreakerRegion = TiebreakerRegions[TiebreakerIndex].String; Interface.ChoiceUI[1].text = TiebreakerRegion; if (TiebreakerDictionary[TiebreakerRegion].TokenType == TokenType.DataDictionary) { DataDictionary RegionDictionary = TiebreakerDictionary[TiebreakerRegion].DataDictionary; if (RegionDictionary.ContainsKey("Type") && RegionDictionary.ContainsKey("Key Locations")) { Interface.HeaderUI.text = "Tiebreaker | " + RegionDictionary["Type"].String; if (RegionDictionary["Key Locations"].TokenType == TokenType.DataList) { for (int i = 0; i < Interface.CluesUI.Length; i++) { Interface.CluesUI[i].text = ""; } DataList KeyLocations = RegionDictionary["Key Locations"].DataList; for (int i = 0; i < KeyLocations.Count && i < Interface.CluesUI.Length; i++) { Interface.CluesUI[i].text = KeyLocations[i].String; } EnableBuzzInPeriodForPlayer(_TiebreakerPlayerNumbers[0]); EnableBuzzInPeriodForPlayer(_TiebreakerPlayerNumbers[1]); } else { ErrorString = "The Key Locations key should be a list of locations."; } } else { ErrorString = "Each region entry should have a region type and a list of key locations."; } } else { ErrorString = "Ensure the elements of the root dictionary are all dictionaries."; } } else { ErrorString = "Ensure the first element is a dictionary."; } } else { ErrorString = "Could not parse JSON document."; } if (ErrorString != "") { Debug.LogError("Malformed tiebreaker data file. " + ErrorString); } } public void TiebreakerIncorrectResponse() { int PodiumIndex = _BuzzedInPlayer - 1; if (PodiumIndex >= 0 && PodiumIndex < _PlayerPodiums.Length) { NetworkCalling.SendCustomNetworkEvent( (IUdonEventReceiver)_PlayerPodiums[PodiumIndex], NetworkEventTarget.All, "EnableBuzzInEffect", false); } _BuzzedInPlayer = -1; _BuzzInAllowed = true; RequestSerialization(); } public void TiebreakerCorrectResponse() { int PodiumIndex = _BuzzedInPlayer - 1; if (PodiumIndex >= 0 && PodiumIndex < _PlayerPodiums.Length) { _PlayerPodiums[PodiumIndex].SendCustomNetworkEvent(NetworkEventTarget.Owner, "AdjustScore", 5); } _BuzzedInPlayer = -1; DisableBuzzers(); EndBuzzInPeriod(); EnableInteraction("End Tiebreaker"); } private void ResetMultipleChoiceInterface(HostCardMultipleChoiceInterface Interface) { Interface.HeaderUI.text = ""; for (int i = 0; i < Interface.CluesUI.Length; i++) { Interface.CluesUI[i].text = ""; } for (int i = 0; i < Interface.ChoiceButtonImages.Length; i++) { Interface.ChoiceButtonImages[i].color = Color.white; } for (int i = 0; i < Interface.ChoiceUI.Length; i++) { Interface.ChoiceUI[i].text = ""; } } private void EnableChoiceCards() { for (int i = 0; i < _PlayerPodiums.Length; i++) { NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "EnableChoiceCards", true); } } private void DisableChoiceCards() { for (int i = 0; i < _PlayerPodiums.Length; i++) { NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "EnableChoiceCards", false); } } private void EnableRiskCards() { for (int i = 0; i < _PlayerPodiums.Length; i++) { NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "EnableRiskCards", true); } } private void DisableRiskCards() { for (int i = 0; i < _PlayerPodiums.Length; i++) { NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "EnableRiskCards", false); } } private void EnableBuzzers() { for (int i = 0; i < _PlayerPodiums.Length; i++) { NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "EnableBuzzer", true); } } private void DisableBuzzers() { for (int i = 0; i < _PlayerPodiums.Length; i++) { NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "EnableBuzzer", false); } } private void EnableBuzzInPeriodForAllPlayers() { _BuzzInAllowed = true; ResetBuzzers(); } private void EnableBuzzInPeriodForPlayer(int PlayerNumber) { for (int i = 0; i < _PlayerPodiums.Length; i++) { if (_PlayerPodiums[i].PlayerNumber == PlayerNumber) { _PlayerBuzzInAllowed[i] = true; NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "EnableBuzzer", true); _BuzzedInPlayer = -1; break; } } _BuzzInAllowed = true; } private void WaitForBuzzInsWithoutLastPlayer() { _BuzzInAllowed = true; int PodiumIndex = _BuzzedInPlayer - 1; if (PodiumIndex >= 0 && PodiumIndex < _PlayerPodiums.Length) { NetworkCalling.SendCustomNetworkEvent( (IUdonEventReceiver)_PlayerPodiums[PodiumIndex], NetworkEventTarget.All, "EnableBuzzInEffect", false); _BuzzedInPlayer = -1; } RequestSerialization(); } [NetworkCallable] public void PlayerBuzzedIn(int PlayerNumber) { int PlayerIndex = PlayerNumber - 1; if (!_BuzzInAllowed || !_PlayerBuzzInAllowed[PlayerIndex]) { return; } // Prevent new buzz-ins and store which player is currently buzzed in. _BuzzInAllowed = false; _PlayerBuzzInAllowed[PlayerIndex] = false; _BuzzedInPlayer = PlayerNumber; RequestSerialization(); NetworkCalling.SendCustomNetworkEvent( (IUdonEventReceiver)_PlayerPodiums[PlayerIndex], NetworkEventTarget.All, "EnableBuzzInEffect", true); // Play the buzzer sound globally. NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_AudioManager, NetworkEventTarget.All, "PlaySFX", SFXEventType.Buzzer); } private void EndBuzzInPeriod() { _BuzzInAllowed = false; ResetBuzzers(); } private void ResetBuzzers() { for (int i = 0; i < _PlayerPodiums.Length; i++) { _PlayerBuzzInAllowed[i] = true; NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "EnableBuzzInEffect", false); } _BuzzedInPlayer = -1; } private void AdvanceQuestion() { // Disable interaction until we unlock it again later. DisableInteraction(); // Only advance to the next stage if we aren't in the middle of a substage if (_QuestionSubstage <= 0) { _QuestionStage++; } // Handle the intro until the game properly begins. if (!_GameHasBegun) { AdvanceIntro(); return; } // TO-DO: Ask someone at either Microsoft or VRChat why the VM crashes if you // cast an int to an enum in a switch parameter, but not if you cast an enum // to an int in a case statement. I'm starting to wonder if either C# or U# // are just fucking terrible languages. C++ figured this problem out in at // least 1985, and it turns out the proper solution was "it's not a problem, // it's two numbers, they're the same fucking thing". switch (_CurrentQuestionType) { case RoundSegmentType.MultipleChoice: AdvanceMultipleChoiceStage(); break; case RoundSegmentType.LightningRound: AdvanceLightningRoundQuestion(); break; case RoundSegmentType.TheChase: AdvanceTheChase(); break; case RoundSegmentType.FinalRound: AdvanceFinalRound(); break; case RoundSegmentType.Tiebreaker: AdvanceTiebreaker(); break; } RequestSerialization(); } private void AdvanceIntro() { switch (_QuestionStage) { case 1: PlayIntroVideo(); break; case 2: AssignStarterPoints(); break; default: break; } } private void AdvanceMultipleChoiceStage() { switch (_QuestionStage) { case 1: InitialiseCluePresentation(); break; case 2: MultipleChoiceRevealChoice1(); break; case 3: MultipleChoiceRevealChoice2(); break; case 4: MultipleChoiceRevealChoice3(); break; case 5: MultipleChoiceLockAnswers(); break; case 6: MultipleChoiceRevealAnswersAndAssignPoints(); break; case 7: AdvanceToNextQuestion(); break; default: break; } } private void AdvanceLightningRoundQuestion() { switch (_QuestionStage) { case 1: BeginLightningRound(); break; case 2: NewLightningRoundQuestion(1); break; case 3: NewLightningRoundQuestion(2); break; case 4: NewLightningRoundQuestion(3); break; case 5: AdvanceToNextQuestion(); break; default: break; } } private void AdvanceTheChase() { switch (_QuestionStage) { case 1: BeginTheChase(); break; case 2: PlayTheChaseMusic(); break; case 3: NewTheChaseClue(1); break; case 4: NewTheChaseClue(2); break; case 5: NewTheChaseClue(3); break; case 6: NewTheChaseClue(4); break; case 7: NewTheChaseClue(5); break; case 8: AdvanceToNextQuestion(); break; default: break; } } private void AdvanceFinalRound() { switch (_QuestionStage) { case 1: BeginFinalRound(); break; case 2: FinalRoundShowMapPreview(); break; case 3: FinalRoundPlayThinkingMusic(); break; case 4: InitialiseCluePresentation(); break; case 5: FinalRoundRevealChoice1(); break; case 6: FinalRoundRevealChoice2(); break; case 7: FinalRoundRevealChoice3(); break; case 8: FinalRoundLockAnswers(); break; case 9: FinalRoundRevealPlayerPlace(3); break; case 10: FinalRoundAssignPointsToPlayerPlace(3); break; case 11: FinalRoundRevealPlayerPlace(2); break; case 12: FinalRoundAssignPointsToPlayerPlace(2); break; case 13: FinalRoundRevealPlayerPlace(1); break; case 14: FinalRoundAssignPointsToPlayerPlace(1); break; case 15: AdvanceToNextQuestion(); break; default: break; } } private void AdvanceTiebreaker() { switch(_QuestionStage) { case 1: BeginTiebreakerRound(); break; case 2: AdvanceToNextQuestion(); break; default: break; } } public VRCUrl GetMapURL(int MapIndex) { return _CaseManager.GetMap(MapIndex); } protected override void _HostCardUseButtonDown_Internal() { AdvanceQuestion(); } protected override HostCardInterfaceBase GetHostCardInterface(RoundSegmentType Question) { return _HostCard.EnableHostCardDisplay(RoundType.LocateTheCrook, Question); } private HostCardBetweenRoundsInterface ShowBetweenQuestionsInterface() { HostCardBetweenRoundsInterface Interface = (HostCardBetweenRoundsInterface)GetHostCardInterface(RoundSegmentType.BetweenSegments); Interface.HeaderUI.text = "Upcoming Question: " + RoundSegmentTypeToString(_CurrentQuestionType); return Interface; } }