405 lines
10 KiB
C#
405 lines
10 KiB
C#
|
|
using UdonSharp;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using VRC.SDK3.Data;
|
|
using VRC.SDK3.UdonNetworkCalling;
|
|
using VRC.Udon.Common.Interfaces;
|
|
using VRC.SDKBase;
|
|
using VRC.SDK3.StringLoading;
|
|
using TMPro;
|
|
using VRC.SDK3.Components;
|
|
using HarmonyLib;
|
|
|
|
|
|
public enum QuestionType
|
|
{
|
|
None,
|
|
MultipleChoice,
|
|
LightningRound,
|
|
DumpsterDive,
|
|
TheChase,
|
|
FinalRound,
|
|
Tiebreaker
|
|
}
|
|
|
|
public enum MusicEventType
|
|
{
|
|
None,
|
|
WhereInTheWorld,
|
|
RockapellaIdent
|
|
}
|
|
|
|
public enum SFXEventType
|
|
{
|
|
None,
|
|
Buzzer
|
|
}
|
|
|
|
|
|
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
|
|
public class GameManager : UdonSharpBehaviour
|
|
{
|
|
[UdonSynced] private bool BuzzInAllowed = false;
|
|
[UdonSynced] private bool[] PlayerBuzzInAllowed;
|
|
[UdonSynced] private int _BuzzedInPlayer = 0;
|
|
|
|
private DataList _QuestionsList = new DataList();
|
|
private int _QuestionIndex = 0;
|
|
private DataDictionary _CurrentQuestion;
|
|
[UdonSynced] private int _QuestionStage = 0;
|
|
[UdonSynced] private int _QuestionCorrectResponse = 0;
|
|
|
|
public PlayerPodium[] PlayerPodiums;
|
|
|
|
[UdonSynced] public VRCUrl QuestionURL;
|
|
|
|
[Header("Multiple Choice Card UI")]
|
|
[SerializeField] private TextMeshProUGUI _InfoHeader;
|
|
[SerializeField] private TextMeshProUGUI[] _InfoClues;
|
|
[SerializeField] private TextMeshProUGUI[] _InfoChoices;
|
|
[SerializeField] private Image[] _InfoChoiceButtons;
|
|
[SerializeField] private TextMeshProUGUI _Answer;
|
|
|
|
[Header("Audio")]
|
|
[SerializeField] private AudioClip BuzzerSound = 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 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)
|
|
{
|
|
Debug.LogError("Unable to find any questions. Ensure the root array elements are all objects.");
|
|
return;
|
|
}
|
|
|
|
_CurrentQuestion = _QuestionsList[0].DataDictionary;
|
|
|
|
Debug.Log("Found " + _QuestionsList.Count + " questions in this case file.");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("Malformed case file. Ensure the first element is an array of objects.");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void NewMultipleChoiceQuestion()
|
|
{
|
|
_InfoHeader.text = _CurrentQuestion["Type"].Number.ToString();
|
|
|
|
DataList ClueStrings = _CurrentQuestion["Clues"].DataList;
|
|
for (int i = 0; i < _InfoClues.Length && i < ClueStrings.Count; i++)
|
|
{
|
|
_InfoClues[i].text = ClueStrings[i].ToString();
|
|
}
|
|
|
|
DataList Choices = _CurrentQuestion["Choices"].DataList;
|
|
_InfoChoices[0].text = Choices[0].ToString();
|
|
_InfoChoices[1].text = Choices[1].ToString();
|
|
_InfoChoices[2].text = Choices[2].ToString();
|
|
|
|
_QuestionCorrectResponse = (int)_CurrentQuestion["Correct Response"].Number;
|
|
_Answer.text = Choices[_QuestionCorrectResponse].ToString();
|
|
|
|
DisableInteractive = false;
|
|
}
|
|
|
|
private void MultipleChoiceRevealChoices()
|
|
{
|
|
SendCustomEvent(nameof(MultipleChoiceRevealChoice1));
|
|
SendCustomEventDelayedSeconds(nameof(MultipleChoiceRevealChoice2), 1.25f);
|
|
SendCustomEventDelayedSeconds(nameof(MultipleChoiceRevealChoice3), 2.5f);
|
|
}
|
|
public void MultipleChoiceRevealChoice1()
|
|
{
|
|
_InfoChoiceButtons[0].color = (_QuestionCorrectResponse == 1) ? Color.green : Color.red;
|
|
}
|
|
public void MultipleChoiceRevealChoice2()
|
|
{
|
|
_InfoChoiceButtons[1].color = (_QuestionCorrectResponse == 2) ? Color.green : Color.red;
|
|
}
|
|
public void MultipleChoiceRevealChoice3()
|
|
{
|
|
_InfoChoiceButtons[2].color = (_QuestionCorrectResponse == 3) ? Color.green : Color.red;
|
|
DataList Choices = _CurrentQuestion["Choices"].DataList;
|
|
EnableChoiceCards();
|
|
|
|
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);
|
|
}
|
|
|
|
DisableInteractive = false;
|
|
}
|
|
|
|
private void MultipleChoiceLockAnswers()
|
|
{
|
|
for (int i = 0; i < PlayerPodiums.Length; i++)
|
|
{
|
|
NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)PlayerPodiums[i], NetworkEventTarget.All, "LockInChoice");
|
|
}
|
|
|
|
_InfoHeader.text = "LOCKED IN";
|
|
for (int i = 0; i < _InfoClues.Length; i++)
|
|
{
|
|
_InfoClues[i].text = "";
|
|
}
|
|
|
|
for (int i = 0; i < _InfoChoices.Length; i++)
|
|
{
|
|
if (i != (_QuestionCorrectResponse - 1))
|
|
{
|
|
_InfoChoices[i].text = "";
|
|
}
|
|
}
|
|
|
|
DisableInteractive = false;
|
|
}
|
|
|
|
private void MultipleChoiceConfirmAnswers()
|
|
{
|
|
_InfoHeader.text = "ANSWER REVEALED";
|
|
|
|
for (int i = 0; i < PlayerPodiums.Length; i++)
|
|
{
|
|
NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)PlayerPodiums[i], NetworkEventTarget.All, "VerifyResponse", _QuestionCorrectResponse);
|
|
}
|
|
|
|
DisableInteractive = false;
|
|
}
|
|
|
|
|
|
private void AdvanceToNextQuestion()
|
|
{
|
|
DisableChoiceCards();
|
|
|
|
_QuestionIndex++;
|
|
if (_QuestionIndex >= _QuestionsList.Count)
|
|
{
|
|
Debug.LogError("No more questions.");
|
|
return;
|
|
}
|
|
|
|
_CurrentQuestion = _QuestionsList[_QuestionIndex].DataDictionary;
|
|
_QuestionCorrectResponse = (int)_CurrentQuestion["Correct Response"].Number;
|
|
_QuestionStage = 0;
|
|
|
|
ResetInfoCard("Next Question");
|
|
|
|
DisableInteractive = false;
|
|
}
|
|
|
|
private void ResetInfoCard(string Header = "")
|
|
{
|
|
_InfoHeader.text = Header;
|
|
|
|
for (int i = 0; i < _InfoClues.Length; i++)
|
|
{
|
|
_InfoClues[i].text = "";
|
|
}
|
|
|
|
_InfoChoiceButtons[0].color = Color.white;
|
|
_InfoChoiceButtons[1].color = Color.white;
|
|
_InfoChoiceButtons[2].color = Color.white;
|
|
|
|
_InfoChoices[0].text = "";
|
|
_InfoChoices[1].text = "";
|
|
_InfoChoices[2].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);
|
|
}
|
|
}
|
|
|
|
|
|
public void EnableBuzzInPeriodForAllPlayers()
|
|
{
|
|
BuzzInAllowed = true;
|
|
ResetBuzzers();
|
|
}
|
|
|
|
[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 WaitForBuzzInsWithoutLastPlayer() {
|
|
BuzzInAllowed = true;
|
|
NetworkCalling.SendCustomNetworkEvent(
|
|
(IUdonEventReceiver)PlayerPodiums[_BuzzedInPlayer - 1],
|
|
NetworkEventTarget.All,
|
|
"EnableBuzzInEffect", false);
|
|
_BuzzedInPlayer = -1;
|
|
RequestSerialization();
|
|
}
|
|
|
|
public void EndBuzzInPeriod()
|
|
{
|
|
BuzzInAllowed = false;
|
|
ResetBuzzers();
|
|
}
|
|
|
|
public void ResetBuzzers()
|
|
{
|
|
for (int i = 0; i < PlayerPodiums.Length; i++) { PlayerBuzzInAllowed[i] = true; }
|
|
_BuzzedInPlayer = -1;
|
|
}
|
|
|
|
|
|
private void AdvanceQuestion()
|
|
{
|
|
// Disable interaction until we unlock it again later.
|
|
DisableInteractive = true;
|
|
|
|
_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 just two numbers, they're the same".
|
|
switch (_CurrentQuestion["Type"].Number)
|
|
{
|
|
case (int)QuestionType.MultipleChoice:
|
|
AdvanceMultipleChoiceStage();
|
|
break;
|
|
}
|
|
|
|
RequestSerialization();
|
|
}
|
|
|
|
private void AdvanceMultipleChoiceStage()
|
|
{
|
|
switch(_QuestionStage)
|
|
{
|
|
case 1: NewMultipleChoiceQuestion(); break;
|
|
case 2: MultipleChoiceRevealChoices(); break;
|
|
case 3: MultipleChoiceLockAnswers(); break;
|
|
case 4: MultipleChoiceConfirmAnswers(); break;
|
|
case 5: AdvanceToNextQuestion(); break;
|
|
default: return;
|
|
}
|
|
}
|
|
|
|
|
|
[NetworkCallable]
|
|
public void PlayMusic(MusicEventType MusicEvent)
|
|
{
|
|
MusicPlayer.Stop();
|
|
|
|
switch (MusicEvent)
|
|
{
|
|
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)
|
|
{
|
|
SFXPlayer.Stop();
|
|
|
|
switch (SFXEvent)
|
|
{
|
|
case SFXEventType.Buzzer: SFXPlayer.clip = BuzzerSound; break;
|
|
default: SFXPlayer.clip = null; break;
|
|
}
|
|
|
|
if (SFXPlayer.clip != null)
|
|
SFXPlayer.Play();
|
|
}
|
|
|
|
|
|
public override void OnPickupUseDown()
|
|
{
|
|
if (DisableInteractive) { return; }
|
|
|
|
AdvanceQuestion();
|
|
|
|
base.OnPickupUseDown();
|
|
}
|
|
}
|