using System; using System.Collections.Generic; using System.Linq; using NodeCanvas.Framework; using ParadoxNotion; using UnityEngine; using Logger = ParadoxNotion.Services.Logger; namespace NodeCanvas.DialogueTrees { /// Use DialogueTrees to create Dialogues between Actors [GraphInfo( packageName = "NodeCanvas", docsURL = "https://nodecanvas.paradoxnotion.com/documentation/", resourcesURL = "https://nodecanvas.paradoxnotion.com/downloads/", forumsURL = "https://nodecanvas.paradoxnotion.com/forums-page/" )] [CreateAssetMenu(menuName = "ParadoxNotion/NodeCanvas/Dialogue Tree Asset")] public class DialogueTree : Graph { ///---------------------------------------------------------------------------------------------- [System.Serializable] class DerivedSerializationData { public List actorParameters; } public override object OnDerivedDataSerialization() { var data = new DerivedSerializationData(); data.actorParameters = this.actorParameters; return data; } public override void OnDerivedDataDeserialization(object data) { if ( data is DerivedSerializationData ) { this.actorParameters = ( (DerivedSerializationData)data ).actorParameters; } } ///---------------------------------------------------------------------------------------------- ///An Actor Parameter [System.Serializable] public class ActorParameter { [SerializeField] private string _keyName; [SerializeField] private string _id; [SerializeField] private UnityEngine.Object _actorObject; [System.NonSerialized] private IDialogueActor _actor; ///Key name of the parameter public string name { get { return _keyName; } set { _keyName = value; } } ///ID of the parameter public string ID => string.IsNullOrEmpty(_id) ? _id = System.Guid.NewGuid().ToString() : _id; ///The reference actor of the parameter public IDialogueActor actor { get { if ( _actor == null ) { _actor = _actorObject as IDialogueActor; } return _actor; } set { _actor = value; _actorObject = value as UnityEngine.Object; } } public ActorParameter() { } public ActorParameter(string name) { this.name = name; } public ActorParameter(string name, IDialogueActor actor) { this.name = name; this.actor = actor; } public override string ToString() { return name; } } ///---------------------------------------------------------------------------------------------- ///The string used for the starting actor" public const string INSTIGATOR_NAME = "SELF"; ///The dialogue actor parameters. We let Unity serialize this as well [SerializeField] public List actorParameters = new List(); private bool enterStartNodeFlag; public static event Action OnDialogueStarted; public static event Action OnDialoguePaused; public static event Action OnDialogueFinished; public static event Action OnSubtitlesRequest; public static event Action OnMultipleChoiceRequest; ///The current DialogueTree running public static DialogueTree currentDialogue { get; private set; } ///The previous DialogueTree running public static DialogueTree previousDialogue { get; private set; } ///The current node of this DialogueTree public DTNode currentNode { get; private set; } ///---------------------------------------------------------------------------------------------- public override System.Type baseNodeType => typeof(DTNode); public override bool requiresAgent => false; public override bool requiresPrimeNode => true; public override bool isTree => true; public override bool allowBlackboardOverrides => true; sealed public override bool canAcceptVariableDrops => false; public sealed override PlanarDirection flowDirection => PlanarDirection.Vertical; ///---------------------------------------------------------------------------------------------- ///A list of the defined names for the involved actor parameters public List definedActorParameterNames { get { var list = actorParameters.Select(r => r.name).ToList(); list.Insert(0, INSTIGATOR_NAME); return list; } } ///Returns the ActorParameter by id public ActorParameter GetParameterByID(string id) { return actorParameters.Find(p => p.ID == id); } ///Returns the ActorParameter by name public ActorParameter GetParameterByName(string paramName) { return actorParameters.Find(p => p.name == paramName); } ///Returns the actor by parameter id. public IDialogueActor GetActorReferenceByID(string id) { var param = GetParameterByID(id); return param != null ? GetActorReferenceByName(param.name) : null; } ///Resolves and gets an actor based on the key name public IDialogueActor GetActorReferenceByName(string paramName) { //Check for INSTIGATOR selection if ( paramName == INSTIGATOR_NAME ) { //return it directly if it implements IDialogueActor if ( agent is IDialogueActor ) { return (IDialogueActor)agent; } //Otherwise use the default actor and set name and transform from agent if ( agent != null ) { return new ProxyDialogueActor(agent.gameObject.name, agent.transform); } return new ProxyDialogueActor("NO ACTOR", null); } //Check for non INSTIGATOR selection. If there IS an actor reference return it var refData = actorParameters.Find(r => r.name == paramName); if ( refData != null && refData.actor != null ) { return refData.actor; } //Otherwise use the default actor and set the name to the key and null transform Logger.Log(string.Format("An actor entry '{0}' on DialogueTree has no reference. A dummy Actor will be used with the entry Key for name", paramName), "Dialogue Tree", this); return new ProxyDialogueActor(paramName, null); } ///Set the target IDialogueActor for the provided key parameter name public void SetActorReference(string paramName, IDialogueActor actor) { var param = actorParameters.Find(p => p.name == paramName); if ( param == null ) { Logger.LogError(string.Format("There is no defined Actor key name '{0}'", paramName), "Dialogue Tree", this); return; } param.actor = actor; } ///Set all target IDialogueActors at once by provided dictionary public void SetActorReferences(Dictionary actors) { foreach ( var pair in actors ) { var param = actorParameters.Find(p => p.name == pair.Key); if ( param == null ) { Logger.LogWarning(string.Format("There is no defined Actor key name '{0}'. Seting actor skiped", pair.Key), "Dialogue Tree", this); continue; } param.actor = pair.Value; } } ///Continues the DialogueTree at provided child connection index of currentNode public void Continue(int index = 0) { if ( index < 0 || index > currentNode.outConnections.Count - 1 ) { Stop(true); return; } currentNode.outConnections[index].status = Status.Success; //editor vis EnterNode((DTNode)currentNode.outConnections[index].targetNode); } ///Enters the provided node public void EnterNode(DTNode node) { currentNode = node; currentNode.Reset(false); if ( currentNode.Execute(agent, blackboard) == Status.Error ) { Stop(false); } } ///Raise the OnSubtitlesRequest event public static void RequestSubtitles(SubtitlesRequestInfo info) { if ( OnSubtitlesRequest != null ) OnSubtitlesRequest(info); else Logger.LogWarning("Subtitle Request event has no subscribers. Make sure to add the default '@DialogueGUI' prefab or create your own GUI.", "Dialogue Tree"); } ///Raise the OnMultipleChoiceRequest event public static void RequestMultipleChoices(MultipleChoiceRequestInfo info) { if ( OnMultipleChoiceRequest != null ) OnMultipleChoiceRequest(info); else Logger.LogWarning("Multiple Choice Request event has no subscribers. Make sure to add the default '@DialogueGUI' prefab or create your own GUI.", "Dialogue Tree"); } protected override void OnGraphStarted() { previousDialogue = currentDialogue; currentDialogue = this; Logger.Log(string.Format("Dialogue Started '{0}'", this.name), "Dialogue Tree", this); if ( OnDialogueStarted != null ) { OnDialogueStarted(this); } if ( !( agent is IDialogueActor ) ) { Logger.Log("Agent used in DialogueTree does not implement IDialogueActor. A dummy actor will be used.", "Dialogue Tree", this); } enterStartNodeFlag = true; } protected override void OnGraphUpdate() { if ( enterStartNodeFlag ) { //use a flag so that other nodes can do stuff on graph started enterStartNodeFlag = false; EnterNode(currentNode != null ? currentNode : (DTNode)primeNode); } if ( currentNode is IUpdatable ) { ( currentNode as IUpdatable ).Update(); } } protected override void OnGraphStoped() { currentDialogue = previousDialogue; previousDialogue = null; currentNode = null; Logger.Log(string.Format("Dialogue Finished '{0}'", this.name), "Dialogue Tree", this); if ( OnDialogueFinished != null ) { OnDialogueFinished(this); } } protected override void OnGraphPaused() { Logger.Log(string.Format("Dialogue Paused '{0}'", this.name), "Dialogue Tree", this); if ( OnDialoguePaused != null ) { OnDialoguePaused(this); } } protected override void OnGraphUnpaused() { EnterNode(currentNode != null ? currentNode : (DTNode)primeNode); Logger.Log(string.Format("Dialogue Resumed '{0}'", this.name), "Dialogue Tree", this); if ( OnDialogueStarted != null ) { OnDialogueStarted(this); } } ///---------------------------------------------------------------------------------------------- ///---------------------------------------UNITY EDITOR------------------------------------------- #if UNITY_EDITOR [UnityEditor.MenuItem("Tools/ParadoxNotion/NodeCanvas/Create/Dialogue Tree Object", false, 2)] static void Editor_CreateGraph() { var dt = new GameObject("DialogueTree").AddComponent(); UnityEditor.Selection.activeObject = dt; } #endif } }