1070 lines
32 KiB
C#
1070 lines
32 KiB
C#
|
|
using UdonSharp;
|
|
using UnityEngine;
|
|
using VRC.SDK3.Data;
|
|
using VRC.SDK3.UdonNetworkCalling;
|
|
using VRC.Udon.Common.Interfaces;
|
|
using VRC.SDKBase;
|
|
using VRC.SDK3.StringLoading;
|
|
using VRC.SDK3.Components;
|
|
using VRC.Udon.Common;
|
|
|
|
|
|
public enum QuestionType
|
|
{
|
|
None,
|
|
MultipleChoice,
|
|
LightningRound,
|
|
DumpsterDive,
|
|
TheChase,
|
|
FinalRound,
|
|
Tiebreaker
|
|
}
|
|
|
|
public enum MusicEventType
|
|
{
|
|
None,
|
|
TheChase,
|
|
ThinkAboutIt,
|
|
WhereInTheWorld,
|
|
RockapellaIdent
|
|
}
|
|
|
|
public enum SFXEventType
|
|
{
|
|
None,
|
|
Ding,
|
|
Buzzer
|
|
}
|
|
|
|
|
|
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
|
|
public class GameManager : UdonSharpBehaviour
|
|
{
|
|
[UdonSynced] private bool _BuzzInAllowed = false;
|
|
[UdonSynced] private bool[] _PlayerBuzzInAllowed;
|
|
[UdonSynced] private int _BuzzedInPlayer = 0;
|
|
|
|
[UdonSynced] private bool _GameHasBegun = false;
|
|
[UdonSynced] private int _QuestionIndex = 0;
|
|
[UdonSynced] private int _QuestionStage = 0;
|
|
[UdonSynced] private int _QuestionCorrectResponse = 0;
|
|
private DataList _QuestionsList = new DataList();
|
|
private DataDictionary _CurrentQuestion;
|
|
|
|
private int[] _FinalRoundPlayersSortedByScore;
|
|
|
|
private bool _IsBeingHeld = false;
|
|
private float _StoredJumpImpulse = 0.0f;
|
|
|
|
[SerializeField] private PlayerPodium[] _PlayerPodiums;
|
|
|
|
[UdonSynced] public VRCUrl QuestionURL;
|
|
|
|
[Header("UI")]
|
|
[SerializeField] private HostCardBetweenRoundsInterface _BetweenRoundsInterface;
|
|
[SerializeField] private HostCardMultipleChoiceInterface _MultipleChoiceInterface;
|
|
[SerializeField] private HostCardLightningRoundInterface _LightningRoundInterface;
|
|
[SerializeField] private HostCardTheChaseInterface _TheChaseInterface;
|
|
[SerializeField] private HostCardFinalRoundInterface _FinalRoundInterface;
|
|
|
|
[SerializeField] private HostPanelInterface _AdminPanelInterface;
|
|
|
|
[Header("Audio")]
|
|
[SerializeField] private AudioClip _Ding = null;
|
|
[SerializeField] private AudioClip _Buzzer = null;
|
|
|
|
[SerializeField] private AudioClip _TheChase = null;
|
|
[SerializeField] private AudioClip _ThinkAboutIt = null;
|
|
[SerializeField] private AudioClip _WhereInTheWorld = null;
|
|
[SerializeField] private AudioClip _RockapellaIdent = null;
|
|
|
|
[SerializeField] private AudioSource _MusicPlayer = null;
|
|
[SerializeField] private AudioSource _SFXPlayer = null;
|
|
|
|
void Start()
|
|
{
|
|
_PlayerBuzzInAllowed = new bool[_PlayerPodiums.Length];
|
|
ResetBuzzers();
|
|
|
|
// Download our test question.
|
|
VRCStringDownloader.LoadUrl(QuestionURL, (IUdonEventReceiver)this);
|
|
}
|
|
|
|
public override void OnPickup()
|
|
{
|
|
Networking.SetOwner(Networking.LocalPlayer, gameObject);
|
|
_StoredJumpImpulse = Networking.LocalPlayer.GetJumpImpulse();
|
|
Networking.LocalPlayer.SetJumpImpulse(0.0f);
|
|
_IsBeingHeld = true;
|
|
|
|
base.OnPickup();
|
|
}
|
|
|
|
public override void OnDrop()
|
|
{
|
|
Networking.SetOwner(Networking.InstanceOwner, gameObject);
|
|
Networking.LocalPlayer.SetJumpImpulse(_StoredJumpImpulse);
|
|
_StoredJumpImpulse = 0.0f;
|
|
_IsBeingHeld = false;
|
|
|
|
base.OnDrop();
|
|
}
|
|
|
|
public override bool OnOwnershipRequest(VRCPlayerApi RequestingPlayer, VRCPlayerApi RequestedOwner)
|
|
{
|
|
Debug.LogError(RequestingPlayer.displayName + " is requesting that " + RequestedOwner.displayName + " become the new owner of the host card. No fucking way.");
|
|
|
|
return false;
|
|
}
|
|
|
|
public override void OnOwnershipTransferred(VRCPlayerApi Player)
|
|
{
|
|
Debug.LogError("New owner is " + Player);
|
|
}
|
|
|
|
public override void InputJump(bool Value, UdonInputEventArgs Args)
|
|
{
|
|
if (Value && _IsBeingHeld)
|
|
{
|
|
_AdminPanelInterface.gameObject.SetActive(!_AdminPanelInterface.gameObject.activeSelf);
|
|
}
|
|
|
|
base.InputJump(Value, Args);
|
|
}
|
|
|
|
public override void OnStringLoadSuccess(IVRCStringDownload DownloadedString)
|
|
{
|
|
string JSONString = DownloadedString.Result;
|
|
if (VRCJson.TryDeserializeFromJson(JSONString, out DataToken JSONResult))
|
|
{
|
|
if (JSONResult.TokenType == TokenType.DataList)
|
|
{
|
|
_QuestionsList.Clear();
|
|
_QuestionIndex = 0;
|
|
|
|
for (int i = 0; i < JSONResult.DataList.Count; i++)
|
|
{
|
|
if (JSONResult.DataList[i].TokenType == TokenType.DataDictionary)
|
|
{
|
|
_QuestionsList.Add(JSONResult.DataList[i]);
|
|
}
|
|
}
|
|
|
|
if (_QuestionsList.Count == 0)
|
|
{
|
|
_BetweenRoundsInterface.HeaderUI.text =
|
|
"Unable to find any questions. Ensure the root array elements are all objects.";
|
|
return;
|
|
}
|
|
|
|
_CurrentQuestion = _QuestionsList[0].DataDictionary;
|
|
|
|
_BetweenRoundsInterface.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");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("Malformed case file. Ensure the first element is an array of objects.");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private void NewMultipleChoiceQuestion()
|
|
{
|
|
_MultipleChoiceInterface.HeaderUI.text = QuestionTypeToString((QuestionType)((int)_CurrentQuestion["Type"].Number));
|
|
|
|
DataList ClueStrings = _CurrentQuestion["Clues"].DataList;
|
|
for (int i = 0; i < _MultipleChoiceInterface.CluesUI.Length && i < ClueStrings.Count; i++)
|
|
{
|
|
_MultipleChoiceInterface.CluesUI[i].text = ClueStrings[i].ToString();
|
|
}
|
|
|
|
DataList Choices = _CurrentQuestion["Choices"].DataList;
|
|
for (int i = 0; i < Choices.Count && i < _MultipleChoiceInterface.ChoiceUI.Length; i++)
|
|
{
|
|
_MultipleChoiceInterface.ChoiceUI[i].text = Choices[i].ToString();
|
|
}
|
|
|
|
_QuestionCorrectResponse = (int)_CurrentQuestion["Correct Response"].Number;
|
|
|
|
EnableHostCardDisplay(QuestionType.MultipleChoice);
|
|
|
|
EnableInteraction("Reveal Choice 1");
|
|
}
|
|
|
|
private void MultipleChoiceRevealChoices()
|
|
{
|
|
SendCustomEvent(nameof(MultipleChoiceRevealChoice1));
|
|
SendCustomEventDelayedSeconds(nameof(MultipleChoiceRevealChoice2), 1.25f);
|
|
SendCustomEventDelayedSeconds(nameof(MultipleChoiceRevealChoice3), 2.5f);
|
|
}
|
|
public void MultipleChoiceRevealChoice1()
|
|
{
|
|
_MultipleChoiceInterface.ChoiceButtonImages[0].color = (_QuestionCorrectResponse == 1) ? Color.green : Color.red;
|
|
|
|
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(PlaySFXAtPitch), SFXEventType.Ding, As5);
|
|
|
|
EnableInteraction("Reveal Choice 2");
|
|
}
|
|
public void MultipleChoiceRevealChoice2()
|
|
{
|
|
_MultipleChoiceInterface.ChoiceButtonImages[1].color = (_QuestionCorrectResponse == 2) ? Color.green : Color.red;
|
|
|
|
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(PlaySFXAtPitch), SFXEventType.Ding, C6);
|
|
|
|
EnableInteraction("Reveal Choice 3");
|
|
}
|
|
public void MultipleChoiceRevealChoice3()
|
|
{
|
|
_MultipleChoiceInterface.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);
|
|
}
|
|
|
|
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(PlaySFXAtPitch), SFXEventType.Ding, D6);
|
|
|
|
EnableInteraction("Lock Answers");
|
|
}
|
|
|
|
private void MultipleChoiceLockAnswers()
|
|
{
|
|
for (int i = 0; i < _PlayerPodiums.Length; i++)
|
|
{
|
|
NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "LockInChoice");
|
|
}
|
|
|
|
_MultipleChoiceInterface.HeaderUI.text = "LOCKED IN";
|
|
for (int i = 0; i < _MultipleChoiceInterface.CluesUI.Length; i++)
|
|
{
|
|
_MultipleChoiceInterface.CluesUI[i].text = "";
|
|
}
|
|
|
|
for (int i = 0; i < _MultipleChoiceInterface.ChoiceUI.Length; i++)
|
|
{
|
|
if (i != (_QuestionCorrectResponse - 1))
|
|
{
|
|
_MultipleChoiceInterface.ChoiceUI[i].text = "";
|
|
}
|
|
}
|
|
|
|
EnableInteraction("Reveal Answers And Assign Points");
|
|
}
|
|
|
|
private void MultipleChoiceRevealAnswersAndAssignPoints()
|
|
{
|
|
_MultipleChoiceInterface.HeaderUI.text = "ANSWER REVEALED";
|
|
|
|
for (int i = 0; i < _PlayerPodiums.Length; i++)
|
|
{
|
|
NetworkCalling.SendCustomNetworkEvent(
|
|
(IUdonEventReceiver)_PlayerPodiums[i],
|
|
NetworkEventTarget.All,
|
|
"VerifyMultipleChoiceResponse", _QuestionCorrectResponse);
|
|
}
|
|
|
|
EnableInteraction("Next Question");
|
|
}
|
|
|
|
|
|
private void BeginLightningRound()
|
|
{
|
|
_LightningRoundInterface.HeaderUI.text = QuestionTypeToString((QuestionType)((int)_CurrentQuestion["Type"].Number)) + " | " + _CurrentQuestion["Location"].ToString();
|
|
_LightningRoundInterface.QuestionUI.text = "";
|
|
for (int i = 0; i < _LightningRoundInterface.ChoiceUI.Length && i < _LightningRoundInterface.ChoiceButtons.Length; i++)
|
|
{
|
|
_LightningRoundInterface.ChoiceUI[i].text = "";
|
|
}
|
|
|
|
EnableHostCardDisplay(QuestionType.LightningRound);
|
|
EnableBuzzers();
|
|
|
|
EnableInteraction("Next Question");
|
|
}
|
|
|
|
private void NewLightningRoundQuestion(int Question)
|
|
{
|
|
DataDictionary CurrentQuestion = _CurrentQuestion["Questions"].DataList[Question - 1].DataDictionary;
|
|
|
|
_LightningRoundInterface.QuestionUI.text = CurrentQuestion["Question"].ToString();
|
|
|
|
DataList Choices = CurrentQuestion["Choices"].DataList;
|
|
for (int i = 0; i < Choices.Count && i < _LightningRoundInterface.ChoiceUI.Length; i++)
|
|
{
|
|
_LightningRoundInterface.ChoiceUI[i].text = Choices[i].ToString();
|
|
}
|
|
|
|
_QuestionCorrectResponse = (int)CurrentQuestion["Correct Response"].Number;
|
|
|
|
for (int i = 0; i < _LightningRoundInterface.ChoiceButtons.Length && i < _LightningRoundInterface.ChoiceButtonImages.Length; i++)
|
|
{
|
|
_LightningRoundInterface.ChoiceButtonImages[i].color = (_QuestionCorrectResponse == (i + 1)) ? Color.green : Color.red;
|
|
_LightningRoundInterface.ChoiceButtons[i].interactable = true;
|
|
}
|
|
_LightningRoundInterface.OtherButton.interactable = true;
|
|
|
|
EnableBuzzInPeriodForAllPlayers();
|
|
}
|
|
|
|
private void LightningRoundCheckAnswer(int Answer)
|
|
{
|
|
if (_QuestionCorrectResponse == Answer)
|
|
{
|
|
for (int i = 0; i < _LightningRoundInterface.ChoiceButtons.Length; i++)
|
|
{
|
|
_LightningRoundInterface.ChoiceButtons[i].interactable = false;
|
|
}
|
|
_LightningRoundInterface.OtherButton.interactable = false;
|
|
|
|
int PodiumIndex = _BuzzedInPlayer - 1;
|
|
if (PodiumIndex >= 0 && PodiumIndex < _PlayerPodiums.Length)
|
|
{
|
|
NetworkCalling.SendCustomNetworkEvent(
|
|
(IUdonEventReceiver)_PlayerPodiums[PodiumIndex],
|
|
NetworkEventTarget.All,
|
|
"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()
|
|
{
|
|
_TheChaseInterface.HeaderUI.text = QuestionTypeToString((QuestionType)((int)_CurrentQuestion["Type"].Number));
|
|
_TheChaseInterface.ClueUI.text = "";
|
|
for (int i = 0; i < _TheChaseInterface.ChoiceUI.Length && i < _TheChaseInterface.ChoiceButtons.Length; i++)
|
|
{
|
|
_TheChaseInterface.ChoiceUI[i].text = "";
|
|
}
|
|
|
|
EnableHostCardDisplay(QuestionType.TheChase);
|
|
EnableBuzzers();
|
|
|
|
EnableInteraction("Next Question");
|
|
}
|
|
|
|
private void NewTheChaseClue(int Clue)
|
|
{
|
|
DataDictionary CurrentClue = _CurrentQuestion["Clues"].DataList[Clue - 1].DataDictionary;
|
|
|
|
_TheChaseInterface.ClueUI.text = CurrentClue["Clue"].ToString();
|
|
|
|
DataList Choices = CurrentClue["Choices"].DataList;
|
|
for (int i = 0; i < Choices.Count && i < _TheChaseInterface.ChoiceUI.Length; i++)
|
|
{
|
|
_TheChaseInterface.ChoiceUI[i].text = Choices[i].ToString();
|
|
}
|
|
_TheChaseInterface.OtherButton.interactable = true;
|
|
|
|
_QuestionCorrectResponse = (int)CurrentClue["Correct Response"].Number;
|
|
|
|
for (int i = 0; i < _TheChaseInterface.ChoiceButtons.Length && i < _TheChaseInterface.ChoiceButtonImages.Length; i++)
|
|
{
|
|
_TheChaseInterface.ChoiceButtonImages[i].color = (_QuestionCorrectResponse == (i + 1)) ? Color.green : Color.red;
|
|
_TheChaseInterface.ChoiceButtons[i].interactable = true;
|
|
}
|
|
|
|
EnableBuzzInPeriodForAllPlayers();
|
|
}
|
|
|
|
private void TheChaseCheckAnswer(int Answer)
|
|
{
|
|
Debug.LogError("Answer is " + Answer + ", correct response is " + _QuestionCorrectResponse);
|
|
|
|
if (_QuestionCorrectResponse == Answer)
|
|
{
|
|
int PodiumIndex = _BuzzedInPlayer - 1;
|
|
if (PodiumIndex >= 0 && PodiumIndex < _PlayerPodiums.Length)
|
|
{
|
|
NetworkCalling.SendCustomNetworkEvent(
|
|
(IUdonEventReceiver)_PlayerPodiums[PodiumIndex],
|
|
NetworkEventTarget.All,
|
|
"AdjustScore", 5);
|
|
}
|
|
}
|
|
|
|
TheChaseEndClue();
|
|
}
|
|
|
|
public void TheChaseCheckAnswer1()
|
|
{
|
|
TheChaseCheckAnswer(1);
|
|
}
|
|
public void TheChaseCheckAnswer2()
|
|
{
|
|
TheChaseCheckAnswer(2);
|
|
}
|
|
public void TheChaseCheckAnswer3()
|
|
{
|
|
TheChaseCheckAnswer(3);
|
|
}
|
|
public void TheChaseEndClue()
|
|
{
|
|
for (int i = 0; i < _TheChaseInterface.ChoiceButtons.Length; i++)
|
|
{
|
|
_TheChaseInterface.ChoiceButtons[i].interactable = false;
|
|
}
|
|
_TheChaseInterface.OtherButton.interactable = false;
|
|
|
|
EndBuzzInPeriod();
|
|
EnableInteraction("Next Question");
|
|
}
|
|
|
|
private void BeginFinalRound()
|
|
{
|
|
_FinalRoundInterface.HeaderUI.text = QuestionTypeToString((QuestionType)((int)_CurrentQuestion["Type"].Number)) + " | Showing Map Preview";
|
|
|
|
EnableRiskCards();
|
|
|
|
DataList ClueStrings = _CurrentQuestion["Clues"].DataList;
|
|
for (int i = 0; i < _FinalRoundInterface.CluesUI.Length && i < ClueStrings.Count; i++)
|
|
{
|
|
_FinalRoundInterface.CluesUI[i].text = ClueStrings[i].ToString();
|
|
}
|
|
|
|
DataList Choices = _CurrentQuestion["Choices"].DataList;
|
|
for (int i = 0; i < Choices.Count && i < _FinalRoundInterface.ChoiceUI.Length; i++)
|
|
{
|
|
_FinalRoundInterface.ChoiceUI[i].text = Choices[i].ToString();
|
|
}
|
|
|
|
_QuestionCorrectResponse = (int)_CurrentQuestion["Correct Response"].Number;
|
|
|
|
EnableHostCardDisplay(QuestionType.FinalRound);
|
|
|
|
EnableInteraction("Think About It");
|
|
}
|
|
|
|
private void PlayThinkingMusic()
|
|
{
|
|
EnableHostCardDisplay(QuestionType.None);
|
|
|
|
NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)this, NetworkEventTarget.All, nameof(PlayMusic), MusicEventType.ThinkAboutIt);
|
|
SendCustomEventDelayedSeconds(nameof(CountdownFinished), 15.0f);
|
|
}
|
|
public void CountdownFinished()
|
|
{
|
|
EnableHostCardDisplay(QuestionType.FinalRound);
|
|
|
|
for (int i = 0; i < _PlayerPodiums.Length; i++)
|
|
{
|
|
NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "LockInRisk");
|
|
}
|
|
|
|
EnableInteraction("Reveal Choice 1");
|
|
}
|
|
|
|
private void FinalRoundRevealChoices()
|
|
{
|
|
SendCustomEvent(nameof(FinalRoundRevealChoice1));
|
|
SendCustomEventDelayedSeconds(nameof(FinalRoundRevealChoice2), 1.25f);
|
|
SendCustomEventDelayedSeconds(nameof(FinalRoundRevealChoice3), 2.5f);
|
|
}
|
|
public void FinalRoundRevealChoice1()
|
|
{
|
|
_FinalRoundInterface.ChoiceButtonImages[0].color = (_QuestionCorrectResponse == 1) ? Color.green : Color.red;
|
|
|
|
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(PlaySFXAtPitch), SFXEventType.Ding, As5);
|
|
|
|
EnableInteraction("Reveal Choice 2");
|
|
}
|
|
public void FinalRoundRevealChoice2()
|
|
{
|
|
_FinalRoundInterface.ChoiceButtonImages[1].color = (_QuestionCorrectResponse == 2) ? Color.green : Color.red;
|
|
|
|
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(PlaySFXAtPitch), SFXEventType.Ding, C6);
|
|
|
|
EnableInteraction("Reveal Choice 3");
|
|
}
|
|
public void FinalRoundRevealChoice3()
|
|
{
|
|
_FinalRoundInterface.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);
|
|
}
|
|
|
|
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(PlaySFXAtPitch), SFXEventType.Ding, D6);
|
|
|
|
EnableInteraction("Lock Answers");
|
|
}
|
|
|
|
private void FinalRoundLockAnswers()
|
|
{
|
|
for (int i = 0; i < _PlayerPodiums.Length; i++)
|
|
{
|
|
NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "LockInChoice");
|
|
}
|
|
|
|
_FinalRoundInterface.HeaderUI.text = "LOCKED IN";
|
|
for (int i = 0; i < _FinalRoundInterface.CluesUI.Length; i++)
|
|
{
|
|
_FinalRoundInterface.CluesUI[i].text = "";
|
|
}
|
|
|
|
for (int i = 0; i < _FinalRoundInterface.ChoiceUI.Length; i++)
|
|
{
|
|
if (i != (_QuestionCorrectResponse - 1))
|
|
{
|
|
_FinalRoundInterface.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];
|
|
|
|
_FinalRoundInterface.HeaderUI.text = "Number " + PlayerPlace + ": Player " + PlayerNumber;
|
|
_PlayerPodiums[PlayerNumber - 1].EnableBuzzInEffect(true);
|
|
|
|
EnableInteraction("Assign Points");
|
|
}
|
|
|
|
private void FinalRoundAssignPointsToPlayerPlace(int PlayerPlace)
|
|
{
|
|
int PlayerNumber = _FinalRoundPlayersSortedByScore[PlayerPlace - 1];
|
|
|
|
_PlayerPodiums[PlayerNumber - 1].EnableBuzzInEffect(false);
|
|
NetworkCalling.SendCustomNetworkEvent(
|
|
(IUdonEventReceiver)_PlayerPodiums[PlayerNumber - 1],
|
|
NetworkEventTarget.All,
|
|
"VerifyFinalRoundResponse", _QuestionCorrectResponse);
|
|
|
|
EnableInteraction("Reveal Next Player Answer");
|
|
}
|
|
|
|
|
|
private void AdvanceToNextQuestion()
|
|
{
|
|
DisableChoiceCards();
|
|
DisableRiskCards();
|
|
DisableBuzzers();
|
|
|
|
_QuestionIndex++;
|
|
if (_QuestionIndex >= _QuestionsList.Count)
|
|
{
|
|
_BetweenRoundsInterface.HeaderUI.text = "No More Questions";
|
|
EnableHostCardDisplay(QuestionType.None);
|
|
DisableInteraction();
|
|
return;
|
|
}
|
|
|
|
_CurrentQuestion = _QuestionsList[_QuestionIndex].DataDictionary;
|
|
_QuestionStage = 0;
|
|
|
|
// Again, why does this work, but not just casting to an enum?
|
|
_BetweenRoundsInterface.HeaderUI.text = "Upcoming Question: " + QuestionTypeToString((QuestionType)((int)_CurrentQuestion["Type"].Number));
|
|
EnableHostCardDisplay(QuestionType.None);
|
|
|
|
ResetMultipleChoiceInterface();
|
|
|
|
EnableInteraction("Show Next Question");
|
|
}
|
|
|
|
private void ResetMultipleChoiceInterface()
|
|
{
|
|
_MultipleChoiceInterface.HeaderUI.text = "";
|
|
|
|
for (int i = 0; i < _MultipleChoiceInterface.CluesUI.Length; i++)
|
|
{
|
|
_MultipleChoiceInterface.CluesUI[i].text = "";
|
|
}
|
|
|
|
for (int i = 0; i < _MultipleChoiceInterface.ChoiceButtonImages.Length; i++)
|
|
{
|
|
_MultipleChoiceInterface.ChoiceButtonImages[i].color = Color.white;
|
|
}
|
|
|
|
for (int i = 0; i < _MultipleChoiceInterface.ChoiceUI.Length; i++)
|
|
{
|
|
_MultipleChoiceInterface.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);
|
|
}
|
|
}
|
|
|
|
|
|
public void EnableBuzzers()
|
|
{
|
|
for (int i = 0; i < _PlayerPodiums.Length; i++)
|
|
{
|
|
NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "EnableBuzzer", true);
|
|
}
|
|
}
|
|
|
|
public void DisableBuzzers()
|
|
{
|
|
for (int i = 0; i < _PlayerPodiums.Length; i++)
|
|
{
|
|
NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)_PlayerPodiums[i], NetworkEventTarget.All, "EnableBuzzer", false);
|
|
}
|
|
}
|
|
|
|
public void EnableBuzzInPeriodForAllPlayers()
|
|
{
|
|
_BuzzInAllowed = true;
|
|
ResetBuzzers();
|
|
}
|
|
|
|
public void WaitForBuzzInsWithoutLastPlayer() {
|
|
_BuzzInAllowed = true;
|
|
int PodiumIndex = _BuzzedInPlayer - 1;
|
|
if (PodiumIndex >= 0 && PodiumIndex < _PlayerPodiums.Length)
|
|
{
|
|
NetworkCalling.SendCustomNetworkEvent(
|
|
(IUdonEventReceiver)_PlayerPodiums[_BuzzedInPlayer - 1],
|
|
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.
|
|
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(PlaySFX), SFXEventType.Buzzer);
|
|
}
|
|
|
|
public void EndBuzzInPeriod()
|
|
{
|
|
_BuzzInAllowed = false;
|
|
ResetBuzzers();
|
|
}
|
|
|
|
public 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();
|
|
|
|
_QuestionStage++;
|
|
|
|
// 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 ((int)_CurrentQuestion["Type"].Number)
|
|
{
|
|
case (int)QuestionType.MultipleChoice: AdvanceMultipleChoiceStage(); break;
|
|
case (int)QuestionType.LightningRound: AdvanceLightningRoundQuestion(); break;
|
|
case (int)QuestionType.TheChase: AdvanceTheChase(); break;
|
|
case (int)QuestionType.FinalRound: AdvanceFinalRound(); break;
|
|
}
|
|
|
|
RequestSerialization();
|
|
}
|
|
|
|
private void AdvanceMultipleChoiceStage()
|
|
{
|
|
switch(_QuestionStage)
|
|
{
|
|
case 1: NewMultipleChoiceQuestion(); 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: NewTheChaseClue(1); break;
|
|
case 3: NewTheChaseClue(2); break;
|
|
case 4: NewTheChaseClue(3); break;
|
|
case 5: NewTheChaseClue(4); break;
|
|
case 6: NewTheChaseClue(5); break;
|
|
case 7: AdvanceToNextQuestion(); break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
private void AdvanceFinalRound()
|
|
{
|
|
switch (_QuestionStage)
|
|
{
|
|
case 1: BeginFinalRound(); break;
|
|
case 2: PlayThinkingMusic(); break;
|
|
case 3: FinalRoundRevealChoice1(); break;
|
|
case 4: FinalRoundRevealChoice2(); break;
|
|
case 5: FinalRoundRevealChoice3(); break;
|
|
case 6: FinalRoundLockAnswers(); break;
|
|
case 7: FinalRoundRevealPlayerPlace(3); break;
|
|
case 8: FinalRoundAssignPointsToPlayerPlace(3); break;
|
|
case 9: FinalRoundRevealPlayerPlace(2); break;
|
|
case 10: FinalRoundAssignPointsToPlayerPlace(2); break;
|
|
case 11: FinalRoundRevealPlayerPlace(1); break;
|
|
case 12: FinalRoundAssignPointsToPlayerPlace(1); break;
|
|
case 13: AdvanceToNextQuestion(); break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
|
|
[NetworkCallable]
|
|
public void PlayMusic(MusicEventType MusicEvent)
|
|
{
|
|
_MusicPlayer.Stop();
|
|
|
|
switch (MusicEvent)
|
|
{
|
|
case MusicEventType.TheChase: _MusicPlayer.clip = _TheChase; break;
|
|
case MusicEventType.ThinkAboutIt: _MusicPlayer.clip = _ThinkAboutIt; break;
|
|
case MusicEventType.WhereInTheWorld: _MusicPlayer.clip = _WhereInTheWorld; break;
|
|
case MusicEventType.RockapellaIdent: _MusicPlayer.clip = _RockapellaIdent; break;
|
|
default: _MusicPlayer.clip = null; break;
|
|
}
|
|
|
|
if (_MusicPlayer.clip != null)
|
|
_MusicPlayer.Play();
|
|
}
|
|
|
|
|
|
[NetworkCallable]
|
|
public void PlaySFX(SFXEventType SFXEvent)
|
|
{
|
|
PlaySFXInternal(SFXEvent);
|
|
}
|
|
[NetworkCallable]
|
|
public void PlaySFXAtPitch(SFXEventType SFXEvent, float Pitch)
|
|
{
|
|
PlaySFXInternal(SFXEvent, Pitch);
|
|
}
|
|
|
|
private void PlaySFXInternal(SFXEventType SFXEvent, float Pitch = 1.0f)
|
|
{
|
|
_SFXPlayer.Stop();
|
|
|
|
switch (SFXEvent)
|
|
{
|
|
case SFXEventType.Ding: _SFXPlayer.clip = _Ding; break;
|
|
case SFXEventType.Buzzer: _SFXPlayer.clip = _Buzzer; break;
|
|
default: _SFXPlayer.clip = null; break;
|
|
}
|
|
|
|
if (_SFXPlayer.clip != null)
|
|
_SFXPlayer.pitch = Pitch;
|
|
_SFXPlayer.Play();
|
|
}
|
|
|
|
|
|
public override void OnPickupUseDown()
|
|
{
|
|
if (IsInteractionDisabled()) { return; }
|
|
|
|
if (!_GameHasBegun)
|
|
{
|
|
for (int i = 0; i < _PlayerPodiums.Length; i++)
|
|
{
|
|
NetworkCalling.SendCustomNetworkEvent(
|
|
(IUdonEventReceiver)_PlayerPodiums[i],
|
|
NetworkEventTarget.All,
|
|
"DisplayScore");
|
|
}
|
|
|
|
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(PlaySFXAtPitch), SFXEventType.Ding, D6);
|
|
_BetweenRoundsInterface.HeaderUI.text = "Upcoming Question: " + QuestionTypeToString((QuestionType)((int)_CurrentQuestion["Type"].Number));
|
|
EnableHostCardDisplay(QuestionType.None);
|
|
|
|
_GameHasBegun = true;
|
|
return;
|
|
}
|
|
|
|
AdvanceQuestion();
|
|
|
|
base.OnPickupUseDown();
|
|
}
|
|
|
|
|
|
private void EnableHostCardDisplay(QuestionType Type)
|
|
{
|
|
_BetweenRoundsInterface.gameObject.SetActive(false);
|
|
_MultipleChoiceInterface.gameObject.SetActive(false);
|
|
_LightningRoundInterface.gameObject.SetActive(false);
|
|
_TheChaseInterface.gameObject.SetActive(false);
|
|
_FinalRoundInterface.gameObject.SetActive(false);
|
|
|
|
switch (Type)
|
|
{
|
|
case QuestionType.None:
|
|
_BetweenRoundsInterface.gameObject.SetActive(true);
|
|
break;
|
|
case QuestionType.MultipleChoice:
|
|
_MultipleChoiceInterface.gameObject.SetActive(true);
|
|
break;
|
|
case QuestionType.LightningRound:
|
|
_LightningRoundInterface.gameObject.SetActive(true);
|
|
break;
|
|
case QuestionType.DumpsterDive: break;
|
|
case QuestionType.TheChase:
|
|
_TheChaseInterface.gameObject.SetActive(true);
|
|
break;
|
|
case QuestionType.FinalRound:
|
|
_FinalRoundInterface.gameObject.SetActive(true);
|
|
break;
|
|
case QuestionType.Tiebreaker:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void EnableInteraction(string NextInteraction = "Advance")
|
|
{
|
|
DisableInteractive = false;
|
|
VRCPickup Pickup = GetComponent<VRCPickup>();
|
|
if (Pickup != null)
|
|
{
|
|
Pickup.UseText = InteractionText;
|
|
}
|
|
}
|
|
private void DisableInteraction()
|
|
{
|
|
DisableInteractive = true;
|
|
}
|
|
|
|
private bool IsInteractionDisabled()
|
|
{
|
|
return DisableInteractive;
|
|
}
|
|
|
|
|
|
private string QuestionTypeToString(QuestionType Type)
|
|
{
|
|
switch((int)Type)
|
|
{
|
|
case (int)QuestionType.None: return "None";
|
|
case (int)QuestionType.MultipleChoice: return "Standard Round";
|
|
case (int)QuestionType.LightningRound: return "Lightning Round";
|
|
case (int)QuestionType.DumpsterDive: return "Dumpster Dive";
|
|
case (int)QuestionType.TheChase: return "The Chase";
|
|
case (int)QuestionType.FinalRound: return "Final Round";
|
|
case (int)QuestionType.Tiebreaker: return "Tiebreaker";
|
|
default: return "[[ERROR]]";
|
|
}
|
|
}
|
|
|
|
|
|
// A messy group of variables that are used for pitch correction of sound effects
|
|
// Since Udon doesn't support accessing public static values in structs, we're doing it here
|
|
// As5 = 932.33 Hz, PitchShift = TargetNoteFreq / As5Freq
|
|
private const float As4 = 0.49999463709201677517617152724894f;
|
|
private const float B4 = 0.52972659895101519848122445915073f;
|
|
private const float C5 = 0.56122832044447781364967339890382f;
|
|
private const float Cs5 = 0.5946070597320691171580878015295f;
|
|
private const float D5 = 0.62995934915748715583538017654693f;
|
|
private const float Ds5 = 0.66741389851232932545343386998166f;
|
|
private const float E5 = 0.70709941758819302178413222785923f;
|
|
private const float F5 = 0.74915534199264209024701554170734f;
|
|
private const float Fs5 = 0.79369965570130747696631021204938f;
|
|
private const float G5 = 0.84089324595368592665687042141731f;
|
|
private const float Gs5 = 0.89089699998927418403355035234305f;
|
|
private const float A5 = 0.94387180504756899381120418735855f;
|
|
private const float As5 = 1.0f;
|
|
private const float B5 = 1.0594639237179968466101058638036f;
|
|
private const float C6 = 1.1224566408889556272993467978076f;
|
|
private const float Cs6 = 1.1892033936481717846685186575569f;
|
|
private const float D6 = 1.2599186983149743116707603530939f;
|
|
private const float Ds6 = 1.3348385228406251005545246854654f;
|
|
private const float E6 = 1.4142095609923524932159214012206f;
|
|
private const float F6 = 1.4982999581693177308463741379125f;
|
|
private const float Fs6 = 1.5873993114026149539326204240988f;
|
|
private const float G6 = 1.6817864919073718533137408428346f;
|
|
private const float Gs6 = 1.7817939999785483680671007046861f;
|
|
private const float A6 = 1.8877436100951379876224083747171f;
|
|
private const float As6 = 2.0f;
|
|
private const float B6 = 2.1183486533738054122467366704922f;
|
|
private const float C7 = 2.2449132817779112545986935956153f;
|
|
private const float Cs7 = 2.3784067872963435693370373151137f;
|
|
}
|