/// author: Torben Borghoff
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using wssdk;
using System.Linq;
using UnityEngine.SceneManagement;
public class DialogueController : MonoBehaviour
{
/// <summary>
/// Struct holding the information for the dialogue. This is what the writer has to fill
/// </summary>
[System.Serializable]
public struct Dialogue
{
[Tooltip("Language the text is written in")]
public publicEnums.Language language;
[Tooltip("The circumstance under that the text triggers. Note: IF THE CIRCUMSTANCE IS STORYCUTSCENE, THERE CAN ONLY BE ONE PER POTATOSTEP")]
public publicEnums.TriggerCircumstance trigger;
[Tooltip("How big does the potato have to be minimally, to be triggered")]
public uint potatoStepTriggerMinimum;
[Tooltip("How big does the potato have to be maximally, to be triggered")]
public uint potatoStepTriggerMaximum;
[Tooltip("ONLY FOR TUTORIAL, which step in the tutorial is to be triggered. Note: HAS TO BE 0 IF NOT IN THE TUTORIAL")]
public uint tutorialStepTrigger;
[Tooltip("The name that should be displayed. Note: ALL ARRAYS NEED TO BE THE SAME SIZE")]
public string[] names;
[Tooltip("The text that should be displayed. Note: ALL ARRAYS NEED TO BE THE SAME SIZE")]
[TextArea(10, 3)]
public string[] sentences;
[Tooltip("The sprites that should be displayed. Note: ALL ARRAYS NEED TO BE THE SAME SIZE")]
public Sprite[] sprites;
[Tooltip("Only for Story and Tutorial: Point where the prefab spawns, that is selected")]
public Transform spawnPoint;
[Tooltip("Only for Story and Tutorial: spawn prefab, for the story (if you want to see Alberta)")]
public GameObject spawnPrefab;
}
[SerializeField]
private List<Dialogue> m_Dialogues = new List<Dialogue>();
/// <summary>
/// Trigger that has been activated last
/// </summary>
[System.NonSerialized]
public DialogueTrigger m_LastTrigger;
[Tooltip("Text for the name within the game world.")]
[SerializeField]
private TextMeshProUGUI m_NameText;
[Tooltip("Text for the dialogue within the game world")]
[SerializeField]
private TextMeshProUGUI m_DialogueText;
[Tooltip("Image showing who is speaking within the game world")]
[SerializeField]
private Image m_DialogueImage;
[Tooltip("Point where the image is positioned when the speaker is schown left")]
[SerializeField]
private Transform m_ImageLeftTransform;
[Tooltip("Point where the image is positioned when the speaker is schown right")]
[SerializeField]
private Transform m_ImageRightTransform;
[Tooltip("Animator for the animations of the texts")]
[SerializeField]
private Animator m_Animator;
[Tooltip("Object that has to be spawned in for the dialogue")]
[SerializeField]
private GameObject m_SpawnedObject;
/// <summary>
/// True if the player has not finished the tutorial (for loading purposes)
/// </summary>
private bool m_IsInTutorial = false;
/// <summary>
/// True if the text is still typing. This is used to show the full text before the next text is displayed when pressing a button.
/// </summary>
private bool m_IsTyping = false;
/// <summary>
/// Contains the whole dialogue for the story in english within the game object. Is used to set the queue for the dialogue and names
/// </summary>
private List<Dialogue> m_EngStoryDialogues = new List<Dialogue>();
/// <summary>
/// Contains the whole dialogue for the tutorial in english within the game object. Is used to set the queue for the dialogue and names
/// </summary>
private List<Dialogue> m_EngTutorialDialogues = new List<Dialogue>();
/// <summary>
/// Contains the whole dialogue for the random in english within the game object. Is used to set the queue for the dialogue and names
/// </summary>
private List<Dialogue> m_EngRandomDialogues = new List<Dialogue>();
/// <summary>
/// Contains the whole dialogue for the story in german within the game object. Is used to set the queue for the dialogue and names
/// </summary>
private List<Dialogue> m_GerStoryDialogues = new List<Dialogue>();
/// <summary>
/// Contains the whole dialogue for the random in german within the game object. Is used to set the queue for the dialogue and names
/// </summary>
private List<Dialogue> m_GerTutorialDialogues = new List<Dialogue>();
/// <summary>
/// Contains the whole dialogue for the random in german within the game object. Is used to set the queue for the dialogue and names
/// </summary>
private List<Dialogue> m_GerRandomDialogues = new List<Dialogue>();
/// <summary>
/// Queue of names. Used to display the names of the speakers in the right order
/// </summary>
private Queue<string> m_Names;
/// <summary>
/// Queue of dialogue text. Used to display the texts of the speakers in the right order
/// </summary>
private Queue<string> m_Sentences;
/// <summary>
/// Queue of sprites. Used to display the sprites of the speakers in the right order
/// </summary>
private Queue<Sprite> m_Sprites;
/// <summary>
/// Coroutine that types the sentences one by one, used to have controll over when the coroutine is done (being finished, or pressing a button)
/// </summary>
private Coroutine TypesSentence;
/// <summary>
/// GameObject of the fastselection bar. Used to activate and deactivate it.
/// </summary>
private GameObject m_FastSelectionBarGameObject;
[Tooltip("List of objects that have to be deactivated in the tutorial, because they would break the game when used too early")]
[SerializeField]
private GameObject[] m_GameObjectsToInactiveWhileInTutorial;
/// <summary>
/// Sentence that will be displayed when the dialogue box is typing
/// </summary>
private string m_Sentence;
/// <summary>
/// This bool is used to check if the tutorial is working correctly
/// </summary>
bool m_TutHasTools = false;
/// <summary>
/// This bool is used to check if the tutorial is working correctly
/// </summary>
bool m_TutHasSeeds = false;
/// <summary>
/// This bool is used to check if the tutorial is working correctly
/// </summary>
bool m_TutHasBerry = false;
/// <summary>
/// This bool is used to check if the tutorial is working correctly
/// </summary>
bool m_TutHasFertiliser = false;
/// <summary>
/// Checks for unused/wrong input, starts the dialogue when the game object is set active.
/// </summary>
private void Start()
{
RectTransform[] uI_Elements = GameManager.Instance?.GetFastSelectionBarController()?.gameObject.GetComponentsInChildren<RectTransform>();
foreach (RectTransform element in uI_Elements)
{
if (element.gameObject.name == "FastSelectionBar")
{
m_FastSelectionBarGameObject = element.gameObject;
}
}
InputManager.Instance.RegisterButtonListener(InputContext.Dialogue, InputPhase.Down, "Fire1", DisplayNextSentence);
m_SpawnedObject = GameObject.FindWithTag("AlbertaTutorial");
if (!m_NameText) { Debug.LogWarning("NameText is not set in " + gameObject.name); }
if (!m_DialogueText) { Debug.LogWarning("" + gameObject.name); }
if (!m_DialogueImage) { Debug.LogWarning("" + gameObject.name); }
if (!m_ImageLeftTransform) { Debug.LogWarning("" + gameObject.name); }
if (!m_ImageRightTransform) { Debug.LogWarning("" + gameObject.name); }
if (!m_Animator) { Debug.LogWarning("Animator is not set in " + gameObject.name); }
if (m_Dialogues.Count == 0) { Debug.LogWarning("There are no dialogues found in " + gameObject.name); }
PreSelectDialogues();
m_Names = new Queue<string>();
m_Sentences = new Queue<string>();
m_Sprites = new Queue<Sprite>();
StartStoryDialogue();
}
private void OnDestroy()
{
if (InputManager.Instance)
InputManager.Instance.UnregisterButtonListener(InputContext.Dialogue, InputPhase.Down, "Fire1");
}
/// <summary>
/// Sorts the dialogues from m_Dialogues to their respective trigger and language variable dependent on the current growth of the potato
/// </summary>
private void PreSelectDialogues()
{
foreach (Dialogue dialogue in m_Dialogues)
{
if (dialogue.potatoStepTriggerMinimum > dialogue.potatoStepTriggerMaximum)
{
Debug.LogWarning("Maximum is smaller than minimum for " + dialogue.sentences[0]);
}
switch (dialogue.language)
{
case publicEnums.Language.English:
switch (dialogue.trigger)
{
case publicEnums.TriggerCircumstance.Random:
if (GameManager.Instance.m_CurrentPotatoStep >= dialogue.potatoStepTriggerMinimum &&
GameManager.Instance.m_CurrentPotatoStep <= dialogue.potatoStepTriggerMaximum)
{
m_EngRandomDialogues.Add(dialogue);
}
break;
case publicEnums.TriggerCircumstance.StoryCutScene:
if (GameManager.Instance.m_CurrentPotatoStep >= dialogue.potatoStepTriggerMinimum &&
GameManager.Instance.m_CurrentPotatoStep <= dialogue.potatoStepTriggerMaximum)
{
m_EngStoryDialogues.Add(dialogue);
}
break;
case publicEnums.TriggerCircumstance.Tutorial:
if (GameManager.Instance.m_CurrentPotatoStep <= 1)
{
m_EngTutorialDialogues.Add(dialogue);
}
break;
default:
break;
}
break;
case publicEnums.Language.German:
switch (dialogue.trigger)
{
case publicEnums.TriggerCircumstance.Random:
if (GameManager.Instance.m_CurrentPotatoStep > dialogue.potatoStepTriggerMinimum &&
GameManager.Instance.m_CurrentPotatoStep < dialogue.potatoStepTriggerMaximum)
{
m_GerRandomDialogues.Add(dialogue);
}
break;
case publicEnums.TriggerCircumstance.StoryCutScene:
if (GameManager.Instance.m_CurrentPotatoStep > dialogue.potatoStepTriggerMinimum &&
GameManager.Instance.m_CurrentPotatoStep < dialogue.potatoStepTriggerMaximum)
{
m_GerStoryDialogues.Add(dialogue);
}
break;
case publicEnums.TriggerCircumstance.Tutorial:
if (GameManager.Instance.m_CurrentPotatoStep < 1)
{
m_GerTutorialDialogues.Add(dialogue);
}
break;
default:
break;
}
break;
default:
break;
}
}
}
/// <summary>
/// Selects a dialogue when triggered from within the game without having the player sleep.
/// </summary>
/// <param name="trigger">The trigger which is used to trigger the dialogue</param>
public void SelectDialogue(publicEnums.TriggerCircumstance trigger)
{
// selects dialogue dependent on how big the crop is and if it has the right trigger circumstance
Dialogue dialogue = new Dialogue { names = null, sentences = null, sprites = null };
if (m_SpawnedObject == null)
{
m_SpawnedObject = GameObject.FindWithTag("AlbertaTutorial");
}
switch (trigger)
{
case publicEnums.TriggerCircumstance.Random:
switch (GameManager.Instance.m_LanguageSetting)
{
// Does selection of random dialogue dependent on Potato size
case publicEnums.Language.English:
List<Dialogue> selectedEngDialogues = new List<Dialogue>();
foreach (Dialogue unselectedDialogue in m_EngRandomDialogues)
{
if (unselectedDialogue.potatoStepTriggerMinimum == GameManager.Instance.m_CurrentPotatoStep)
{
selectedEngDialogues.Add(unselectedDialogue);
}
}
dialogue = selectedEngDialogues.PickRandom();
break;
case publicEnums.Language.German:
List<Dialogue> selectedGerDialogues = new List<Dialogue>();
foreach (Dialogue unselectedDialogue in m_GerRandomDialogues)
{
if (unselectedDialogue.potatoStepTriggerMinimum == GameManager.Instance.m_CurrentPotatoStep)
{
selectedGerDialogues.Add(unselectedDialogue);
}
}
dialogue = selectedGerDialogues.PickRandom();
break;
default:
break;
}
break;
case publicEnums.TriggerCircumstance.StoryCutScene:
// Does selection of story dialogue dependent on Potato size
switch (GameManager.Instance.m_LanguageSetting)
{
case publicEnums.Language.English:
foreach (Dialogue unselectedDialogue in m_EngStoryDialogues)
{
if (GameManager.Instance.m_LastStorySegment < GameManager.Instance.m_CurrentPotatoStep && unselectedDialogue.potatoStepTriggerMinimum == GameManager.Instance.m_CurrentPotatoStep)
{
GameManager.Instance.m_LastStorySegment = (uint)GameManager.Instance.m_CurrentPotatoStep;
dialogue = unselectedDialogue;
if (dialogue.spawnPoint && dialogue.spawnPrefab)
{
m_SpawnedObject.transform.position = dialogue.spawnPoint.position;
}
break;
}
}
break;
case publicEnums.Language.German:
foreach (Dialogue unselectedDialogue in m_GerStoryDialogues)
{
if (GameManager.Instance.m_LastStorySegment < GameManager.Instance.m_CurrentPotatoStep && unselectedDialogue.potatoStepTriggerMinimum == GameManager.Instance.m_CurrentPotatoStep)
{
GameManager.Instance.m_LastStorySegment = (uint)GameManager.Instance.m_CurrentPotatoStep;
dialogue = unselectedDialogue;
if (dialogue.spawnPoint && dialogue.spawnPrefab)
{
m_SpawnedObject.transform.position = dialogue.spawnPoint.position;
}
break;
}
}
break;
default:
break;
}
break;
case publicEnums.TriggerCircumstance.Tutorial:
if (GameManager.Instance.m_CurrentTutorialStep < m_EngTutorialDialogues.Count)
{
switch (GameManager.Instance.m_LanguageSetting)
{
case publicEnums.Language.English:
// Does selection of tutorial dialogue dependent on tutorial advancements
foreach (Dialogue unselectedDialogue in m_EngTutorialDialogues)
{
if (unselectedDialogue.tutorialStepTrigger == GameManager.Instance.m_CurrentTutorialStep)
{
dialogue = unselectedDialogue;
if (dialogue.spawnPoint && dialogue.spawnPrefab)
{
m_IsInTutorial = true;
m_SpawnedObject.transform.position = dialogue.spawnPoint.position;
}
break;
}
}
break;
case publicEnums.Language.German:
foreach (Dialogue unselectedDialogue in m_GerTutorialDialogues)
{
if (unselectedDialogue.tutorialStepTrigger == GameManager.Instance.m_CurrentTutorialStep)
{
dialogue = unselectedDialogue;
if (dialogue.spawnPoint && dialogue.spawnPrefab)
{
m_IsInTutorial = true;
m_SpawnedObject.transform.position = dialogue.spawnPoint.position;
}
break;
}
}
break;
default:
break;
}
}
break;
default:
break;
}
if (m_EngTutorialDialogues.Count == GameManager.Instance.m_CurrentTutorialStep + 1)
{
m_IsInTutorial = false;
}
if (dialogue.names == null)
{
return;
}
StartDialogue(dialogue);
}
/// <summary>
/// Clears the last read dialogue and adds the texts and sprites to the queue to display them later
/// </summary>
/// <param name="dialogue">Dialogue of the current speaker</param>
private void StartDialogue(Dialogue dialogue)
{
if (m_FastSelectionBarGameObject)
{
m_FastSelectionBarGameObject.SetActive(false);
}
// So no time is lost when in dialogue (time is a precious recource in this game)
GameManager.Instance?.GetDayNightManager()?.SetFreezeTime(true);
// Changes the controls, so the player can not move
InputManager.Instance.CurrentInputContext = InputContext.Dialogue;
// Starts the animation for the text to appear
m_Animator.SetBool("IsOpen", true);
m_Sentences.Clear();
m_Sprites.Clear();
foreach (string name in dialogue.names)
{
m_Names.Enqueue(name);
}
foreach (string sentence in dialogue.sentences)
{
m_Sentences.Enqueue(sentence);
}
foreach (Sprite sprite in dialogue.sprites)
{
m_Sprites.Enqueue(sprite);
}
if (!m_IsTyping)
DisplayNextSentence();
}
/// <summary>
/// Displays the next sentence in the queue with names and sprites. Also resets the sound for the dialogue
/// </summary>
public void DisplayNextSentence()
{
GameManager.Instance.m_SpokeToNPC = true;
if (m_IsTyping)
{
m_DialogueText.text = m_Sentence;
if (TypesSentence != null)
StopCoroutine(TypesSentence);
AkSoundEngine.PostEvent("V_FemVoice_Stop", GameManager.Instance.AK_GameObject);
AkSoundEngine.PostEvent("V_ManVoice_Stop", GameManager.Instance.AK_GameObject);
m_IsTyping = false;
return;
}
if (m_Sentences.Count == 0)
{
EndDialogue();
return;
}
string name = m_Names.Dequeue();
m_Sentence = m_Sentences.Dequeue();
Sprite sprite = null;
if (m_Sprites.Count() > 0)
{
sprite = m_Sprites.Dequeue();
}
if (TypesSentence != null)
StopCoroutine(TypesSentence);
// Makes sprite invisible when there is none
if (!sprite)
{
Debug.LogWarning("No sprite found for sentence: " + m_Sentence + " in " + gameObject.name);
m_DialogueImage.color = new Color(0, 0, 0, 0);
}
else
{
// Since sprites can be invisible, the color is set every time a new sprite is shown
m_DialogueImage.color = new Color(255, 255, 255, 255);
m_DialogueImage.sprite = sprite;
m_DialogueImage.transform.position = m_ImageRightTransform.position;
if (sprite.name.Contains("Ham"))
{
m_DialogueImage.transform.position = m_ImageLeftTransform.position;
}
m_DialogueImage.GetComponent<RectTransform>().sizeDelta = new Vector2(m_DialogueImage.sprite.rect.width * (100 / m_DialogueImage.sprite.rect.height), 100);
}
m_DialogueText.text = "";
m_NameText.text = name;
AkSoundEngine.PostEvent("V_FemVoice_Stop", GameManager.Instance.AK_GameObject);
AkSoundEngine.PostEvent("V_ManVoice_Stop", GameManager.Instance.AK_GameObject);
TypesSentence = StartCoroutine(TypeSentence(m_Sentence, name));
}
/// <summary>
/// Types the sentence in the game and starts the audio
/// </summary>
/// <param name="sentence">Sentence that is going to be typed</param>
/// <param name="name">Name of the speaker</param>
/// <returns>Wait time</returns>
private IEnumerator TypeSentence(string sentence, string name)
{
m_IsTyping = true;
yield return new WaitForSecondsRealtime(0.5f);
// This would have been done in another way, if there were more than two characters
if (name.Contains("Ham"))
{
AkSoundEngine.PostEvent("V_ManVoice_Start", GameManager.Instance.AK_GameObject);
}
else
{
AkSoundEngine.PostEvent("V_FemVoice_Start", GameManager.Instance.AK_GameObject);
}
foreach (char letter in sentence.ToCharArray())
{
m_DialogueText.text += letter;
if (!char.IsWhiteSpace(letter))
{
yield return new WaitForSecondsRealtime(0.05f);
}
else
{
yield return null;
}
}
AkSoundEngine.PostEvent("V_FemVoice_Stop", GameManager.Instance.AK_GameObject);
AkSoundEngine.PostEvent("V_ManVoice_Stop", GameManager.Instance.AK_GameObject);
m_IsTyping = false;
}
/// <summary>
/// Ends the dialogue and closes the dialogue box / resets the sound and gives the tutorial relevant items
/// </summary>
private void EndDialogue()
{
if (m_FastSelectionBarGameObject)
{
m_FastSelectionBarGameObject.SetActive(true);
}
if (GameManager.Instance.m_CurrentTutorialStep == 0 && !m_TutHasTools)
{
GameManager.Instance?.GetInventoryController()?.GiveTools();
m_TutHasTools = true;
}
if (GameManager.Instance.m_CurrentTutorialStep == 1 && !m_TutHasSeeds)
{
GameManager.Instance?.GetInventoryController()?.GiveItem("CornSeed", 6);
GameManager.Instance?.GetInventoryController()?.GiveItem("CarrotSeed", 2);
m_TutHasSeeds = true;
}
if (GameManager.Instance.m_CurrentTutorialStep == 2 && !m_TutHasBerry)
{
GameManager.Instance?.GetInventoryController()?.GiveItem("Strawberry", 1);
m_TutHasBerry = true;
}
if (GameManager.Instance.m_CurrentTutorialStep == 3 && !m_TutHasFertiliser)
{
GameManager.Instance?.GetInventoryController()?.GiveItem("Fertiliser", 10);
m_TutHasFertiliser = true;
}
if (GameManager.Instance.m_CurrentTutorialStep == 4)
{
GameManager.Instance.m_CurrentTutorialStep = 99;
}
m_IsTyping = false;
AkSoundEngine.PostEvent("V_FemVoice_Stop", GameManager.Instance.AK_GameObject);
AkSoundEngine.PostEvent("V_ManVoice_Stop", GameManager.Instance.AK_GameObject);
m_Animator.SetBool("IsOpen", false);
if (m_LastTrigger != null)
{
m_LastTrigger = null;
}
InputManager.Instance.CurrentInputContext = InputContext.Game;
if (!m_IsInTutorial)
{
if (m_SpawnedObject && !m_IsInTutorial)
{
FadeToBlack.Instance.m_ActionOnBlack += DestroySpawnedObject;
FadeToBlack.Instance.HideAction(0.05f, 0.5f);
}
}
GameManager.Instance?.GetDayNightManager()?.SetFreezeTime(false);
if (GameManager.Instance?.m_CurrentTutorialStep == 99)
{
foreach (GameObject inactiveGameObject in m_GameObjectsToInactiveWhileInTutorial)
{
inactiveGameObject.SetActive(true);
}
}
}
/// <summary>
/// Destroys the object so it is not standing around in the game world
/// </summary>
private void DestroySpawnedObject()
{
m_SpawnedObject.transform.position = new Vector3(0, -100, 0);
FindObjectOfType<PlayerInteraction>().RegisterTryUseCurrentSelectedItemToButton();
m_SpawnedObject = null;
}
/// <summary>
/// Used to start tutorial dialoge from a trigger
/// Here implementations for the different languages should have been done (if they were not unused anyway in the end)
/// </summary>
public void StartTutorialDialogue()
{
if (GameManager.Instance.m_CurrentTutorialStep < m_EngTutorialDialogues.Count)
{
if (GameManager.Instance?.m_CurrentTutorialStep == 0)
{
foreach (GameObject inactiveGameObject in m_GameObjectsToInactiveWhileInTutorial)
{
inactiveGameObject.SetActive(false);
}
}
SelectDialogue(publicEnums.TriggerCircumstance.Tutorial);
}
}
/// <summary>
/// Used to start story dialoge from a trigger
/// Here implementations for the different languages should have been done (if they were not unused anyway in the end)
/// </summary>
public void StartStoryDialogue()
{
foreach (Dialogue dialogue in m_EngStoryDialogues)
{
if (GameManager.Instance.m_CurrentPotatoStep == dialogue.potatoStepTriggerMinimum)
{
SelectDialogue(publicEnums.TriggerCircumstance.StoryCutScene);
}
}
}
}