523 lines
19 KiB
C#
523 lines
19 KiB
C#
|
using System.Collections;
|
|||
|
using System.Collections.Generic;
|
|||
|
using UnityEngine;
|
|||
|
using RPGCreationKit;
|
|||
|
using RPGCreationKit.DialogueSystem;
|
|||
|
using XNode;
|
|||
|
|
|||
|
namespace RPGCreationKit.AI
|
|||
|
{
|
|||
|
public class AITalkative : AIStatus, IDialoguable
|
|||
|
{
|
|||
|
public bool dialogueSystemEnabled = true;
|
|||
|
|
|||
|
public IDialoguable speakingTo;
|
|||
|
public AILookAt aiLookAt;
|
|||
|
|
|||
|
public DialogueGraph currentDialogueGraph;
|
|||
|
[HideInInspector] public DialogueGraph previousDialogueGraph;
|
|||
|
public DialogueGraph defaultDialogueGraph;
|
|||
|
|
|||
|
// Variables to update the NPC state
|
|||
|
[HideInInspector] public bool isInConversation = false;
|
|||
|
[HideInInspector] public bool isTalking = false;
|
|||
|
[HideInInspector] public bool isListening = false;
|
|||
|
|
|||
|
|
|||
|
// Setting this in the consequences will allow NPC to follow and speak to the player
|
|||
|
[HideInInspector] public bool mustSpeakToPlayer = false;
|
|||
|
[HideInInspector] public bool mustSpeakToEntity = false;
|
|||
|
|
|||
|
public int speakerIndex = -1;
|
|||
|
|
|||
|
IEnumerator dialogueCoroutine = null;
|
|||
|
|
|||
|
public void ChangeDialogueGraph(DialogueGraph _newDialogueGraph)
|
|||
|
{
|
|||
|
previousDialogueGraph = currentDialogueGraph;
|
|||
|
currentDialogueGraph = _newDialogueGraph;
|
|||
|
}
|
|||
|
|
|||
|
public void ChangeDialogueGraph(string newDialogueID)
|
|||
|
{
|
|||
|
DialogueGraph newDialogueGraph = DialoguesDatabase.GetItem(newDialogueID);
|
|||
|
|
|||
|
previousDialogueGraph = currentDialogueGraph;
|
|||
|
currentDialogueGraph = newDialogueGraph;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
[AIInvokable]
|
|||
|
public void SpeakToAI(string _speakToID)
|
|||
|
{
|
|||
|
// Get the RckAI if possible
|
|||
|
RckAI sTo = null;
|
|||
|
CellsSystem.CellInformation.TryToGetAI(_speakToID, out sTo);
|
|||
|
|
|||
|
if(sTo != null)
|
|||
|
{
|
|||
|
IDialoguable[] ppl = { this, sTo };
|
|||
|
DialogueLogic(ppl, currentDialogueGraph);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[AIInvokable]
|
|||
|
public void SetSpeakerIndexToAI(string _rckAI, int _index)
|
|||
|
{
|
|||
|
// Get the RckAI if possible
|
|||
|
RckAI sTo = null;
|
|||
|
CellsSystem.CellInformation.TryToGetAI(_rckAI, out sTo);
|
|||
|
|
|||
|
if (sTo != null)
|
|||
|
{
|
|||
|
sTo.SetSpeakerIndex(_index);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void DialogueLogic(IDialoguable[] entities, DialogueGraph dialogue)
|
|||
|
{
|
|||
|
dialogueCoroutine = (ExecuteDialogueNode(dialogue.GetEntryNode(), entities));
|
|||
|
StartCoroutine(dialogueCoroutine);
|
|||
|
}
|
|||
|
|
|||
|
[SerializeField] public IDialoguable[] entitiesTalkingWith;
|
|||
|
public Node currentNode;
|
|||
|
|
|||
|
public IEnumerator ExecuteDialogueNode(Node _node, IDialoguable[] _entities)
|
|||
|
{
|
|||
|
// Trigger Events and Script
|
|||
|
DialogueNode globalNode = (DialogueNode)_node;
|
|||
|
|
|||
|
RCKFunctions.ExecuteEvents(globalNode.events);
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(globalNode.resultScript))
|
|||
|
RCKFunctions.ExecuteScript(globalNode.resultScript);
|
|||
|
|
|||
|
currentNode = _node;
|
|||
|
entitiesTalkingWith = _entities;
|
|||
|
|
|||
|
for (int i = 0; i < _entities.Length; i++)
|
|||
|
{
|
|||
|
_entities[i].OnDialogueStarts(null);
|
|||
|
}
|
|||
|
|
|||
|
if (_node is EntryNode)
|
|||
|
{
|
|||
|
EntryNode thisNode = (EntryNode)_node;
|
|||
|
dialogueCoroutine = (ExecuteDialogueNode(thisNode.GetOutputPort("firstNode").Connection.node, _entities));
|
|||
|
StartCoroutine(dialogueCoroutine);
|
|||
|
}
|
|||
|
else if (_node is ConditionsNode)
|
|||
|
{
|
|||
|
ConditionsNode thisNode = (ConditionsNode)_node;
|
|||
|
|
|||
|
bool conditionsResult = RCKFunctions.VerifyConditions(thisNode.conditions);
|
|||
|
|
|||
|
if (conditionsResult)
|
|||
|
{
|
|||
|
dialogueCoroutine = (ExecuteDialogueNode(thisNode.GetOutputPort("resultTrue").Connection.node, _entities));
|
|||
|
StartCoroutine(dialogueCoroutine);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
dialogueCoroutine = (ExecuteDialogueNode(thisNode.GetOutputPort("resultFalse").Connection.node, _entities));
|
|||
|
StartCoroutine(dialogueCoroutine);
|
|||
|
}
|
|||
|
}
|
|||
|
else if(_node is NPCDialogueLineNode)
|
|||
|
{
|
|||
|
|
|||
|
NPCDialogueLineNode thisNode = (NPCDialogueLineNode)_node;
|
|||
|
|
|||
|
int speakerID = -1;
|
|||
|
|
|||
|
// reset anim
|
|||
|
for (int i = 0; i < _entities.Length; i++)
|
|||
|
{
|
|||
|
if (thisNode.speakerID == _entities[i].GetSpeakerIndex())
|
|||
|
{
|
|||
|
speakerID = _entities[i].GetSpeakerIndex();
|
|||
|
}
|
|||
|
|
|||
|
_entities[i].GetAnimator().SetTrigger("CancelDialogueAnimation");
|
|||
|
}
|
|||
|
|
|||
|
for (int i = 0; i < _entities.Length; i++)
|
|||
|
{
|
|||
|
if (thisNode.speakerID == _entities[i].GetSpeakerIndex())
|
|||
|
{
|
|||
|
// Set look at
|
|||
|
AILookAt lookAt = _entities[i].GetEntityGameObject().GetComponent<AILookAt>();
|
|||
|
|
|||
|
if (lookAt != null)
|
|||
|
lookAt.ForceToLookAtTarget(_entities[thisNode.lookAtEntityID].GetEntityLookAtPos());
|
|||
|
|
|||
|
_entities[i].GetAnimator().ResetTrigger("CancelDialogueAnimation");
|
|||
|
|
|||
|
// We found who have to speak:
|
|||
|
_entities[i].OnSpeakALine(thisNode);
|
|||
|
|
|||
|
// Play talking animation if choosen
|
|||
|
if (!string.IsNullOrEmpty(thisNode.dialogueAnimationStr))
|
|||
|
{
|
|||
|
_entities[i].GetAnimator().CrossFade(thisNode.dialogueAnimationStr, .1f);
|
|||
|
}
|
|||
|
else
|
|||
|
_entities[i].GetAnimator().SetTrigger("CancelDialogueAnimation");
|
|||
|
|
|||
|
|
|||
|
// if player is close enough
|
|||
|
if (!isInOfflineMode && !Player.RckPlayer.instance.isInConversation && Vector3.Distance(Player.RckPlayer.instance.transform.position, _entities[i].GetEntityGameObject().transform.position) <= RCKSettings.PLAYER_HEARS_NPC_DIALOGUES_DISTANCE)
|
|||
|
RCKFunctions.DisplayHeardLine(thisNode.line, thisNode.lineTime);
|
|||
|
|
|||
|
// Play the audio if exists
|
|||
|
if (thisNode.audioClip != null)
|
|||
|
{
|
|||
|
_entities[i].PlayAudioClip(thisNode.audioClip);
|
|||
|
}
|
|||
|
|
|||
|
// Check for the timing of the line
|
|||
|
bool outOfTime = false;
|
|||
|
float startTime = Time.time;
|
|||
|
|
|||
|
// To wait until the line ends
|
|||
|
while (!outOfTime)
|
|||
|
{
|
|||
|
// If the time expires
|
|||
|
if (!thisNode.useLenghtOfClip)
|
|||
|
{
|
|||
|
if (Time.time - startTime >= thisNode.lineTime)
|
|||
|
outOfTime = true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (Time.time - startTime >= thisNode.audioClip.length)
|
|||
|
outOfTime = true;
|
|||
|
}
|
|||
|
|
|||
|
yield return null;
|
|||
|
}
|
|||
|
|
|||
|
_entities[i].StopAudio();
|
|||
|
|
|||
|
// Check what we have to do next
|
|||
|
switch (thisNode.afterLine)
|
|||
|
{
|
|||
|
case AfterLine.NPC_DialogueLine:
|
|||
|
// If we have to trigger another dialogue line, we just re-start this courutine with the new line
|
|||
|
dialogueCoroutine = (ExecuteDialogueNode(thisNode.GetOutputPort("nextLine").Connection.node, _entities));
|
|||
|
StartCoroutine(dialogueCoroutine);
|
|||
|
yield break;
|
|||
|
|
|||
|
case AfterLine.PlayerQuestions:
|
|||
|
/*
|
|||
|
// Play listening animation if choosen
|
|||
|
if (!string.IsNullOrEmpty(thisNode.dialogueAnimationListeningStr))
|
|||
|
{
|
|||
|
speakingTo.GetAnimator().CrossFade(thisNode.dialogueAnimationListeningStr, .1f);
|
|||
|
}
|
|||
|
|
|||
|
if (thisNode.removePreviousQuestions)
|
|||
|
{
|
|||
|
foreach (Transform t in uiManager.playerQuestionsContent)
|
|||
|
Destroy(t.gameObject);
|
|||
|
}
|
|||
|
else if (thisNode.questionsToRemove.Length > 0)
|
|||
|
{
|
|||
|
List<GameObject> toDelete = new List<GameObject>();
|
|||
|
foreach (Transform t in uiManager.playerQuestionsContent)
|
|||
|
{
|
|||
|
for (int i = 0; i < thisNode.questionsToRemove.Length; i++)
|
|||
|
{
|
|||
|
if (t.GetComponent<PlayerQuestionUI>().question.qID == thisNode.questionsToRemove[i])
|
|||
|
toDelete.Add(t.gameObject);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
foreach (GameObject go in toDelete)
|
|||
|
{
|
|||
|
Destroy(go);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
List<PlayerQuestionUI> questions = new List<PlayerQuestionUI>();
|
|||
|
// Check if we have to add some new question
|
|||
|
for (int i = 0; i < thisNode.playerQuestions.Count; i++)
|
|||
|
{
|
|||
|
int tempPosition = thisNode.playerQuestions[i].Position;
|
|||
|
// In case, add them
|
|||
|
GameObject newQuestion = Instantiate(uiManager.playerQuestionPrefab, uiManager.playerQuestionsContent);
|
|||
|
|
|||
|
if (tempPosition == 0)
|
|||
|
tempPosition = i;
|
|||
|
|
|||
|
questions.Add(newQuestion.GetComponent<PlayerQuestionUI>());
|
|||
|
|
|||
|
newQuestion.GetComponent<PlayerQuestionUI>()
|
|||
|
.Init(thisNode.playerQuestions[i], speakingTo,
|
|||
|
(DialogueNode)thisNode.GetOutputPort("playerQuestions " + i).Connection.node,
|
|||
|
thisNode.playerQuestions[i].Question, thisNode.playerQuestions[i].DeleteAfterAnswer,
|
|||
|
tempPosition);
|
|||
|
|
|||
|
newQuestion.GetComponent<RectTransform>().SetSiblingIndex(tempPosition);
|
|||
|
}
|
|||
|
|
|||
|
questions.Sort(SortByPosition);
|
|||
|
for (int i = 0; i < questions.Count; i++)
|
|||
|
{
|
|||
|
questions[i].GetComponent<RectTransform>().SetSiblingIndex(questions[i].position);
|
|||
|
}
|
|||
|
|
|||
|
// Set the UI to allow the player to ask questions
|
|||
|
uiManager.npc_Line.gameObject.SetActive(false);
|
|||
|
uiManager.playerQuestionsContainer.SetActive(true);
|
|||
|
|
|||
|
// Reset the scroll bar of the PlayerQuestionScrollView
|
|||
|
if (uiManager.playerQuestionsContainer.GetComponentInChildren<Scrollbar>() != null)
|
|||
|
uiManager.playerQuestionsContainer.GetComponentInChildren<Scrollbar>().value = 1f;
|
|||
|
|
|||
|
speakingTo.OnFinishedSpeakingALine();
|
|||
|
|
|||
|
// Sleect first for input system if gamepad
|
|||
|
if (RckInput.isUsingGamepad)
|
|||
|
questions[0].GetComponent<Button>().Select();
|
|||
|
*/
|
|||
|
yield break;
|
|||
|
|
|||
|
case AfterLine.EndDialogue:
|
|||
|
_entities[i].OnDialogueEnds(this.gameObject);
|
|||
|
|
|||
|
// Reset the player status
|
|||
|
isInConversation = false;
|
|||
|
|
|||
|
|
|||
|
// Reset speaking to animator
|
|||
|
_entities[i].GetAnimator().SetTrigger("CancelDialogueAnimation");
|
|||
|
|
|||
|
if (lookAt != null)
|
|||
|
lookAt.StopForcingLookAtTarget();
|
|||
|
|
|||
|
speakingTo = null;
|
|||
|
yield break;
|
|||
|
|
|||
|
case AfterLine.Continue:
|
|||
|
dialogueCoroutine = (ExecuteDialogueNode(thisNode.GetOutputPort("output").Connection.node, _entities));
|
|||
|
StartCoroutine(dialogueCoroutine);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
//AILookAt lookAt = _entities[i].GetEntityGameObject().GetComponent<AILookAt>();
|
|||
|
|
|||
|
//if (lookAt != null)
|
|||
|
// lookAt.ForceToLookAtTarget(_entities[speakerID].GetEntityLookAtPos());
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else if (_node is ChangeDialogueNode)
|
|||
|
{
|
|||
|
ChangeDialogueNode thisNode = (ChangeDialogueNode)_node;
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(thisNode.npcRef))
|
|||
|
RCKFunctions.ChangeDialogueToRckAI(thisNode.npcRef, thisNode.newDialogue.ID);
|
|||
|
|
|||
|
StartCoroutine(ExecuteDialogueNode(thisNode.GetOutputPort("output").Connection.node, _entities));
|
|||
|
}
|
|||
|
else if(_node is EndNode)
|
|||
|
{
|
|||
|
for (int i = 0; i < _entities.Length; i++)
|
|||
|
{
|
|||
|
_entities[i].OnDialogueEnds(this.gameObject);
|
|||
|
// Reset speaking to animator
|
|||
|
_entities[i].GetAnimator().SetTrigger("CancelDialogueAnimation");
|
|||
|
|
|||
|
AILookAt lookAt = _entities[i].GetEntityGameObject().GetComponent<AILookAt>();
|
|||
|
|
|||
|
if (lookAt != null)
|
|||
|
lookAt.StopForcingLookAtTarget();
|
|||
|
}
|
|||
|
|
|||
|
entitiesTalkingWith = null;
|
|||
|
}
|
|||
|
|
|||
|
yield return null;
|
|||
|
}
|
|||
|
|
|||
|
[AIInvokable]
|
|||
|
public void SetSpeakerIndex(int index)
|
|||
|
{
|
|||
|
speakerIndex = index;
|
|||
|
}
|
|||
|
|
|||
|
public virtual void SpeakToEntity(IDialoguable target)
|
|||
|
{
|
|||
|
throw new System.NotImplementedException();
|
|||
|
}
|
|||
|
|
|||
|
public bool CanDialogue()
|
|||
|
{
|
|||
|
return (dialogueSystemEnabled && isAlive && !isHostile && !isHostileAgainstPC && !isInCombat && !isInConversation && !isDrawingWeapon
|
|||
|
&& (currentDialogueGraph != null || defaultDialogueGraph != null && isLoaded));
|
|||
|
}
|
|||
|
|
|||
|
public string GetDialoguableName()
|
|||
|
{
|
|||
|
return entityName;
|
|||
|
}
|
|||
|
|
|||
|
public virtual void OnDialogueStarts(GameObject target)
|
|||
|
{
|
|||
|
if(target != null)
|
|||
|
speakingTo = target.GetComponent<IDialoguable>();
|
|||
|
|
|||
|
// Rotate the Npc to look at us
|
|||
|
isInConversation = true;
|
|||
|
|
|||
|
if (target != null && aiLookAt != null)
|
|||
|
aiLookAt.ForceToLookAtTarget(target.transform);
|
|||
|
|
|||
|
//npc.SetTarget(transform);
|
|||
|
//npc.FaceTarget(true);
|
|||
|
|
|||
|
// Stop the agent
|
|||
|
//npc.components.agent.isStopped = true;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
public virtual void OnDialogueEnds(GameObject target)
|
|||
|
{
|
|||
|
isInConversation = false;
|
|||
|
isTalking = false;
|
|||
|
isListening = false;
|
|||
|
speakingTo = null;
|
|||
|
entitiesTalkingWith = null;
|
|||
|
|
|||
|
if (aiLookAt != null)
|
|||
|
aiLookAt.StopForcingLookAtTarget();
|
|||
|
|
|||
|
UpdateConversationsAnimator();
|
|||
|
|
|||
|
m_Anim.SetTrigger("CancelDialogueAnimation");
|
|||
|
|
|||
|
// Reset the NPC state
|
|||
|
//npcSpeaking.ResetStates();
|
|||
|
|
|||
|
if (!isHostile)
|
|||
|
{
|
|||
|
// Send the NPC to his path if he have got one
|
|||
|
//if (npcSpeaking.PatrolPath != null)
|
|||
|
// npcSpeaking.StartCoroutine("GoToPath");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public DialogueGraph GetCurrentDialogueGraph()
|
|||
|
{
|
|||
|
if (currentDialogueGraph != null)
|
|||
|
return currentDialogueGraph;
|
|||
|
else if(defaultDialogueGraph != null)
|
|||
|
{
|
|||
|
Debug.Log("AITalkative: " + this.gameObject.name + " has no currentDialogueGraph assigned, falling back to default");
|
|||
|
return defaultDialogueGraph;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Debug.LogWarning("AITalkative: " + this.gameObject.name + " has no currentDialogueGraph or defaultDialogueGraph assigned.");
|
|||
|
OnDialogueEnds(null);
|
|||
|
return null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public virtual void OnSpeakALine(NPCDialogueLineNode currentNode)
|
|||
|
{
|
|||
|
// Set the NPC state
|
|||
|
isTalking = true;
|
|||
|
isListening = false;
|
|||
|
|
|||
|
UpdateConversationsAnimator();
|
|||
|
//npcSpeaking.state = NPC_State.Speaking;
|
|||
|
}
|
|||
|
|
|||
|
public virtual void OnFinishedSpeakingALine()
|
|||
|
{
|
|||
|
// Set the NPC state
|
|||
|
isTalking = false;
|
|||
|
isListening = true;
|
|||
|
UpdateConversationsAnimator();
|
|||
|
}
|
|||
|
|
|||
|
public Transform GetEntityLookAtPos()
|
|||
|
{
|
|||
|
return entityFocusPart;
|
|||
|
}
|
|||
|
|
|||
|
public void PlayAudioClip(AudioClip clip)
|
|||
|
{
|
|||
|
audioSource.PlayOneShot(clip);
|
|||
|
}
|
|||
|
|
|||
|
public void StopAudio()
|
|||
|
{
|
|||
|
audioSource.Stop();
|
|||
|
}
|
|||
|
|
|||
|
public override void Die()
|
|||
|
{
|
|||
|
base.Die();
|
|||
|
}
|
|||
|
|
|||
|
public GameObject GetEntityGameObject()
|
|||
|
{
|
|||
|
return this.gameObject;
|
|||
|
}
|
|||
|
|
|||
|
public bool IsTalking()
|
|||
|
{
|
|||
|
return isTalking;
|
|||
|
}
|
|||
|
|
|||
|
public void UpdateConversationsAnimator()
|
|||
|
{
|
|||
|
if (!isAlive) return;
|
|||
|
|
|||
|
if (m_Anim != null)
|
|||
|
{
|
|||
|
m_Anim.SetBool("Talking", isTalking);
|
|||
|
m_Anim.SetBool("Listening", isListening);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Animator IDialoguable.GetAnimator()
|
|||
|
{
|
|||
|
return m_Anim;
|
|||
|
}
|
|||
|
|
|||
|
int IDialoguable.GetSpeakerIndex()
|
|||
|
{
|
|||
|
return speakerIndex;
|
|||
|
}
|
|||
|
|
|||
|
public bool forceStopDialogue = false;
|
|||
|
public void ForceToStopDialogue()
|
|||
|
{
|
|||
|
if(dialogueCoroutine != null)
|
|||
|
StopCoroutine(dialogueCoroutine);
|
|||
|
|
|||
|
StopCoroutine(ExecuteDialogueNode(currentNode, entitiesTalkingWith));
|
|||
|
|
|||
|
isInConversation = false;
|
|||
|
isTalking = false;
|
|||
|
isListening = false;
|
|||
|
speakingTo = null;
|
|||
|
entitiesTalkingWith = null;
|
|||
|
|
|||
|
if (aiLookAt != null)
|
|||
|
aiLookAt.StopForcingLookAtTarget();
|
|||
|
|
|||
|
UpdateConversationsAnimator();
|
|||
|
m_Anim.SetTrigger("CancelDialogueAnimation");
|
|||
|
|
|||
|
OnDialogueEnds(null);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|