boredom. / Assets / Scripts / UIController.cs
UIController.cs
Raw
using DG.Tweening;
using JetBrains.Annotations;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using TMPro;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class UIController : MonoBehaviour {

    [Header("References")]
    private PlayerController playerController;
    private TaskManager taskManager;
    private GameData gameData;
    private Animator animator;

    [Header("Tasks")]
    [SerializeField] private CanvasGroup mainHUD;
    [SerializeField] private float mainHUDFadeDuration;
    [SerializeField] private Transform taskInfo;
    [SerializeField] private TMP_Text taskHeaderText;
    [SerializeField] private TMP_Text taskNameText;
    [SerializeField] private TMP_Text taskDescriptionText;

    [Header("Timer")]
    [SerializeField] private TMP_Text timerText; // timer text is part of HUD
    [SerializeField] private TMP_Text taskTimerText; // task timer text is part of HUD (but it only shows up for certain tasks)
    [SerializeField] private float flashThreshold;
    [SerializeField] private float flashWaitDuration;
    [SerializeField] private Color flashColor;
    private Coroutine flashTimerCoroutine;
    private Coroutine timerCoroutine;
    private int timerSeconds;

    [Header("Quiz")]
    [SerializeField] private GameObject homework;
    [SerializeField] private float homeworkFadeDuration;
    [SerializeField] private Transform homeworkContent;
    [SerializeField] private HomeworkQuestionUI homeworkQuestionPrefab;
    [SerializeField] private Button submitButtonPrefab;
    [SerializeField] private float homeworkCompleteWaitDuration;
    private List<HomeworkQuestionUI> questionPrefabs;
    private bool homeworkOpen;

    [Header("TV Repair")]
    [SerializeField] private CanvasGroup tvRepair;
    [SerializeField] private float tvRepairFadeDuration;
    [SerializeField] private float tvRepairCompleteWaitDuration;
    [SerializeField] private List<TVRepairQuestionUI> tvRepairQuestionObjs;
    [SerializeField] private List<TVRepairAnswerUI> tvRepairAnswerObjs;
    [SerializeField] private GameObject checkmark;
    private TVRepairQuestion[] tvRepairQuestions;
    private List<string> orderedAnswers;
    private bool tvRepairOpen;

    [Header("Victory Screen")]
    [SerializeField] private CanvasGroup victoryScreen;
    [SerializeField] private float victoryFadeDuration;
    [SerializeField] private Button victoryMenuButton;
    [SerializeField] private Button victoryRestartButton;
    [SerializeField] private Button victoryQuitButton;

    [Header("Loss Screen")]
    [SerializeField] private CanvasGroup lossScreen;
    [SerializeField] private float lossFadeDuration;
    [SerializeField] private Button lossMenuButton;
    [SerializeField] private Button lossRestartButton;
    [SerializeField] private Button lossQuitButton;

    [Header("Pause Menu")]
    [SerializeField] private CanvasGroup pauseMenu;
    [SerializeField] private Button resumeButton;
    [SerializeField] private Button restartButton;
    [SerializeField] private Button mainMenuButton;

    [Header("Loading Screen")]
    [SerializeField] private CanvasGroup loadingScreen;
    [SerializeField] private float loadingFadeDuration;
    private AsyncOperation sceneLoad;

    private void Start() {

        playerController = FindObjectOfType<PlayerController>();
        taskManager = FindObjectOfType<TaskManager>();
        gameData = FindObjectOfType<GameData>();
        animator = GetComponent<Animator>();

        victoryScreen.gameObject.SetActive(false);
        lossScreen.gameObject.SetActive(false);
        pauseMenu.gameObject.SetActive(false);
        HideLoadingScreen();

        homework.gameObject.SetActive(false);
        questionPrefabs = new List<HomeworkQuestionUI>();
        //quizCloseButton.onClick.AddListener(() => StartCoroutine(CloseQuiz(false)));

        tvRepair.gameObject.SetActive(false);
        checkmark.SetActive(false);

        victoryMenuButton.onClick.AddListener(LoadMainMenu);
        victoryRestartButton.onClick.AddListener(ReloadLevel);
        victoryQuitButton.onClick.AddListener(() => Application.Quit());

        lossMenuButton.onClick.AddListener(LoadMainMenu);
        lossRestartButton.onClick.AddListener(ReloadLevel);
        lossQuitButton.onClick.AddListener(() => Application.Quit());

        /* order tv repair answers by index */
        List<TVRepairAnswerUI> ordered = new List<TVRepairAnswerUI>(new TVRepairAnswerUI[tvRepairAnswerObjs.Count]);

        for (int i = 0; i < tvRepairAnswerObjs.Count; i++) {

            tvRepairAnswerObjs[i].Initialize();
            ordered[tvRepairAnswerObjs[i].GetIndex()] = tvRepairAnswerObjs[i];

        }

        tvRepairAnswerObjs = ordered;

        taskTimerText.gameObject.SetActive(false);

        StartCoroutine(RebuildLayout(taskInfo));

        resumeButton.onClick.AddListener(taskManager.TogglePause);
        restartButton.onClick.AddListener(ReloadLevel);
        mainMenuButton.onClick.AddListener(LoadMainMenu);

        ResetTaskInfo();

    }

    //private void Update() {

    //    /* QUIZ */
    //    if (Input.GetKeyDown(KeyCode.Escape))
    //        StartCoroutine(CloseQuiz(false)); // close quiz

    //}

    #region Timer

    public void InitializeTimer(int seconds) {

        timerCoroutine = StartCoroutine(HandleTimer(seconds));

    }

    public void ResumeTimer() {

        timerCoroutine = StartCoroutine(HandleTimer(timerSeconds));

    }

    public void PauseTimer() {

        if (timerCoroutine != null) StopCoroutine(timerCoroutine);

    }

    private IEnumerator HandleTimer(int seconds) {

        this.timerSeconds = seconds;

        while (timerSeconds > 0) {

            if (timerSeconds <= flashThreshold) {

                if (flashTimerCoroutine == null) // make sure timer isn't already flashing
                    flashTimerCoroutine = StartCoroutine(FlashTimer()); // start flashing

            } else if (flashTimerCoroutine != null) { // timer is already flashing but time is above threshold

                StopCoroutine(flashTimerCoroutine); // stop flashing

            }

            taskTimerText.text = timerText.text = string.Format("{0:0}:{1:00}", timerSeconds / 60, timerSeconds % 60);
            yield return new WaitForSeconds(1f);
            timerSeconds--;

        }

        if (flashTimerCoroutine != null)
            StopCoroutine(flashTimerCoroutine); // stop flashing

        taskTimerText.text = timerText.text = "0:00";
        taskManager.OnGameLoss();

    }

    private IEnumerator FlashTimer() {

        while (true) {

            taskTimerText.color = timerText.color = Color.clear;
            yield return new WaitForSeconds(flashWaitDuration);
            taskTimerText.color = timerText.color = flashColor;
            yield return new WaitForSeconds(flashWaitDuration);

        }
    }

    #endregion

    #region Tasks

    private void ShowMainHUD() {

        mainHUD.gameObject.SetActive(true);
        mainHUD.DOFade(1f, mainHUDFadeDuration).SetUpdate(true);

    }

    private void HideMainHUD() {

        mainHUD.DOFade(0f, mainHUDFadeDuration).SetUpdate(true).OnComplete(() => mainHUD.gameObject.SetActive(false));

    }

    public void SetTaskInfo(int taskNum, string taskName, string taskDescription) {

        if (taskNum == 0)
            taskHeaderText.text = "No Task";
        else
            taskHeaderText.text = "Task " + taskNum + "/" + taskManager.GetTotalTasks() + ":";

        taskNameText.text = taskName;
        taskDescriptionText.text = taskDescription;
        StartCoroutine(RebuildLayout(taskInfo));

    }

    public void ResetTaskInfo() {

        SetTaskInfo(0, "", "");

    }

    #endregion

    #region Homework

    public void OpenHomework() {

        if (homeworkOpen) return; // homework already open

        taskTimerText.gameObject.SetActive(true);
        playerController.PauseBoredomTick(); // stop ticking boredom
        playerController.SetMechanicStatus(MechanicType.Movement, false);

        foreach (Transform child in homeworkContent)
            Destroy(child.gameObject);

        Homework homework = gameData.GetQuiz();
        HomeworkQuestion[] homeworkQuestions = homework.GetRandomQuestions();

        foreach (HomeworkQuestion question in homeworkQuestions) {

            HomeworkQuestionUI questionObject = Instantiate(homeworkQuestionPrefab, homeworkContent);
            questionObject.SetQuestionText(question.GetQuestionText());
            questionObject.SetOptionTexts(question.GetOptions());
            questionPrefabs.Add(questionObject); // track for validation purposes (added in same order as homework questions)

        }

        Button submitButton = Instantiate(submitButtonPrefab, homeworkContent);
        submitButton.onClick.AddListener(() => {

            submitButton.interactable = false; // prevent multiple submissions
            StartCoroutine(OnHomeworkComplete(homework.ValidateAnswers(questionPrefabs)));

        });

        this.homework.gameObject.SetActive(true);
        StartCoroutine(RebuildLayout(homeworkContent, this.homework.transform)); // rebuild layout AFTER making it visible

        animator.SetTrigger("openHomework");
        homeworkOpen = true;

    }

    private IEnumerator OnHomeworkComplete(bool pass) {

        yield return new WaitForSeconds(homeworkCompleteWaitDuration);
        yield return StartCoroutine(CloseHomework(pass)); // wait for homework to close

    }

    private IEnumerator CloseHomework(bool pass) {

        if (!homeworkOpen) yield break; // homework already closed

        taskTimerText.gameObject.SetActive(false);
        animator.SetTrigger("closeHomework");
        yield return new WaitForEndOfFrame(); // wait for animation to start playing

        if (pass)
            taskManager.CompleteCurrentTask(); // complete task
        else
            taskManager.FailCurrentTask(); // fail task

        // clear all homework data
        questionPrefabs.Clear();

        playerController.SetMechanicStatus(MechanicType.Movement, true); // allow movement before homework closes fully

        yield return new WaitForSeconds(animator.GetCurrentAnimatorStateInfo(0).length); // wait for animation to finish playing

        homework.gameObject.SetActive(false);
        homeworkOpen = false;

        if (!taskManager.IsGameComplete())
            playerController.StartBoredomTick(); // start ticking boredom again only if game is still going

    }

    #endregion

    #region TV Repair

    public IEnumerator OpenTVRepair() {

        if (tvRepairOpen) yield break; // tv repair already open

        taskTimerText.gameObject.SetActive(true);
        this.tvRepair.blocksRaycasts = true; // allow interactions

        playerController.PauseBoredomTick(); // stop ticking boredom
        playerController.SetMechanicStatus(MechanicType.Movement, false);
        checkmark.SetActive(false);

        TVRepair tvRepair = gameData.GetTVRepair();
        tvRepairQuestions = tvRepair.GetRandomQuestions();
        List<string> availableAnswers = new List<string>();
        orderedAnswers = new List<string>(new string[tvRepairQuestions.Length]);

        // set questions
        for (int i = 0; i < tvRepairQuestions.Length; i++) {

            tvRepairQuestionObjs[i].SetQuestionText(tvRepairQuestions[i]);
            availableAnswers.Add(tvRepairQuestions[i].GetAnswerText());

        }

        // randomize answers
        int guaranteedRandom = Random.Range(0, tvRepairQuestions.Length); // INDEX OF ANSWER THAT WILL BE INSERTED (FROM AVAILABLE ANSWERS)
        int randIndex; // INDEX OF WHICH POSITION ANSWER IS INSERTED INTO

        if (guaranteedRandom == 0) {

            randIndex = Random.Range(1, tvRepairQuestions.Length); // random index from 1 to length - 1

        } else if (guaranteedRandom == tvRepairQuestions.Length - 1) {

            randIndex = Random.Range(0, tvRepairQuestions.Length - 1); // random index from 0 to length - 2

        } else {

            int[] ranges = { Random.Range(0, guaranteedRandom), Random.Range(guaranteedRandom + 1, tvRepairQuestions.Length) }; // random ranges from 0 to guaranteedRandom - 1 and guaranteedRandom + 1 to length - 1
            randIndex = ranges[Random.Range(0, ranges.Length)]; // random index from ranges

        }

        orderedAnswers[randIndex] = availableAnswers[guaranteedRandom]; // insert guaranteed random answer at random index
        tvRepairAnswerObjs[randIndex].SetAnswerText(availableAnswers[guaranteedRandom]); // set answer text (same index as insert index of ordered answers)
        availableAnswers.RemoveAt(guaranteedRandom); // remove guaranteed random answer

        for (int i = 0; i < tvRepairQuestions.Length; i++) { // I IS THE INDEX OF THE ANSWER THAT WILL BE INSERTED (FROM AVAILABLE ANSWERS)

            if (availableAnswers.Count == 0) break; // no more answers to randomize

            do {

                yield return null;
                randIndex = Random.Range(0, tvRepairQuestions.Length); // random index from ranges

            } while (orderedAnswers[randIndex] != null);

            if (i == guaranteedRandom) continue; // skip guaranteed random answer

            orderedAnswers[randIndex] = availableAnswers[availableAnswers.Count - 1]; // insert random answer at random index
            tvRepairAnswerObjs[randIndex].SetAnswerText(availableAnswers[availableAnswers.Count - 1]); // set answer text (same index as insert index of ordered answers)
            availableAnswers.RemoveAt(availableAnswers.Count - 1); // remove random answer

        }

        this.tvRepair.gameObject.SetActive(true);

        animator.SetTrigger("openTVRepair");
        tvRepairOpen = true;

    }

    private IEnumerator OnTVRepairComplete(bool pass) {

        checkmark.SetActive(true);
        animator.SetTrigger("completeTVRepair");
        tvRepair.blocksRaycasts = false; // stop interactions
        yield return new WaitForSeconds(tvRepairCompleteWaitDuration);
        yield return StartCoroutine(CloseTVRepair(pass)); // wait for tv repair to close

    }

    private IEnumerator CloseTVRepair(bool pass) {

        if (!tvRepairOpen) yield break; // tv repair already closed

        taskTimerText.gameObject.SetActive(false);
        animator.SetTrigger("closeTVRepair");
        yield return new WaitForEndOfFrame(); // wait for animation to start playing

        if (pass)
            taskManager.CompleteCurrentTask(); // complete task
        else
            taskManager.FailCurrentTask(); // fail task

        playerController.SetMechanicStatus(MechanicType.Movement, true); // allow movement before tv repair closes fully

        yield return new WaitForSeconds(animator.GetCurrentAnimatorStateInfo(0).length); // wait for animation to finish playing

        tvRepair.gameObject.SetActive(false);
        tvRepairOpen = false;

        if (!taskManager.IsGameComplete())
            playerController.StartBoredomTick(); // start ticking boredom again only if game is still going

    }

    public void OnTVRepairDrop() {

        /* order tv repair questions by index */
        List<TVRepairQuestionUI> ordered = new List<TVRepairQuestionUI>(new TVRepairQuestionUI[tvRepairQuestionObjs.Count]);

        for (int i = 0; i < tvRepairQuestionObjs.Count; i++)
            ordered[tvRepairQuestionObjs[i].GetIndex()] = tvRepairQuestionObjs[i];

        if (gameData.GetTVRepair().ValidateAnswers(ordered, orderedAnswers)) StartCoroutine(OnTVRepairComplete(true));

    }

    #endregion

    #region Scene Loading

    private void LoadMainMenu() {

        sceneLoad = SceneManager.LoadSceneAsync(0);
        sceneLoad.allowSceneActivation = false;
        ShowLoadingScreen();

    }

    private void ReloadLevel() {

        sceneLoad = SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().buildIndex);
        sceneLoad.allowSceneActivation = false;
        ShowLoadingScreen();

    }

    #endregion

    #region Pausing

    public void OpenPauseMenu() {

        HideMainHUD();
        pauseMenu.gameObject.SetActive(true);
        pauseMenu.DOFade(1f, mainHUDFadeDuration).SetUpdate(true);
        animator.SetTrigger("openPauseMenu");

    }

    public void ClosePauseMenu() {

        ShowMainHUD();
        animator.SetTrigger("closePauseMenu");
        pauseMenu.DOFade(0f, lossFadeDuration).SetUpdate(true);
        StartCoroutine(HidePauseMenu());

    }

    private IEnumerator HidePauseMenu() { // helper method, actually closes the menu

        yield return new WaitForEndOfFrame(); // wait for animation to start playing
        yield return new WaitForSeconds(animator.GetCurrentAnimatorStateInfo(0).length); // wait for animation to finish playing
        pauseMenu.gameObject.SetActive(false); // hide pause menu after animation ends

    }

    #endregion

    #region Victory/Loss

    public void ShowVictoryScreen() {

        StartCoroutine(CloseHomework(false));
        StartCoroutine(CloseTVRepair(false));

        victoryScreen.alpha = 0f;
        victoryScreen.gameObject.SetActive(true);
        mainHUD.DOFade(0f, victoryFadeDuration).SetEase(Ease.InCubic).OnComplete(() => mainHUD.gameObject.SetActive(false));
        victoryScreen.DOFade(1f, victoryFadeDuration).SetEase(Ease.InCubic);

    }

    public void ShowLossScreen() {

        StartCoroutine(CloseHomework(false));
        StartCoroutine(CloseTVRepair(false));

        lossScreen.alpha = 0f;
        lossScreen.gameObject.SetActive(true);
        mainHUD.DOFade(0f, lossFadeDuration).SetEase(Ease.InCubic).OnComplete(() => mainHUD.gameObject.SetActive(false));
        lossScreen.DOFade(1f, lossFadeDuration).SetEase(Ease.InCubic);

    }

    #endregion

    #region Loading Screen

    private void ShowLoadingScreen() {

        loadingScreen.alpha = 0f;
        loadingScreen.gameObject.SetActive(true);
        loadingScreen.DOFade(1f, loadingFadeDuration).SetUpdate(true).SetEase(Ease.InCubic).OnComplete(() => {

            if (sceneLoad != null) sceneLoad.allowSceneActivation = true;

        });
    }

    private void HideLoadingScreen() {

        loadingScreen.alpha = 1f;
        loadingScreen.gameObject.SetActive(true);
        loadingScreen.DOFade(0f, loadingFadeDuration).SetEase(Ease.InCubic).OnComplete(() => loadingScreen.gameObject.SetActive(false));

    }

    #endregion

    #region Utility

    private void RefreshLayout(Transform layout) {

        LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>());

    }

    private IEnumerator RebuildLayout(Transform layout) { // make sure to maintain order

        layout.gameObject.SetActive(false);
        yield return new WaitForEndOfFrame();
        layout.gameObject.SetActive(true);
        RefreshLayout(layout);

    }

    private IEnumerator RebuildLayout(Transform layout1, Transform layout2) { // make sure to maintain order

        RefreshLayout(layout1);
        yield return new WaitForEndOfFrame();
        RefreshLayout(layout2);

    }

    #endregion

}