// Copyright (c) Pixel Crushers. All rights reserved. using UnityEngine; using System.Collections.Generic; namespace PixelCrushers.DialogueSystem { /// /// A dialogue entry is a single line of dialogue in a Conversation, with a list of links to /// possible responses (see Link). The dialogue entries in a conversation form a dialogue tree, /// where earlier entries branch out using links to point to later entries. Technically, the /// conversation forms a directed graph, not a true tree, because links can also loop back to /// earlier entries. /// /// A dialogue entry has a speaker (the "actor") and a listener (the "conversant"). /// [System.Serializable] public class DialogueEntry { /// /// The dialogue entry ID. Links reference dialogue entries by ID. /// public int id = 0; /// /// The dialogue entry's field list. (See Field) /// public List fields = null; /// /// The ID of the conversation that this dialogue entry belongs to. /// public int conversationID = 0; /// /// true if this is the root (first) entry in a conversation; otherwise /// false. /// public bool isRoot = false; /// /// true if this entry is an empty line used to group a subtree of dialogue entries. /// public bool isGroup = false; /// /// Currently unused by the dialogue system, this is the nodeColor value defined in Chat /// Mapper. /// public string nodeColor = null; /// /// Currently unused by the dialogue system, this is the delay value to use in Chat /// Mapper's conversation simulator. /// public bool delaySimStatus = false; /// /// Specifies how to proceed when encountering a link whose entry's condition is false. /// Valid values are "Block" and "Passthrough". If the value is "Block", no further links /// are considered. If the value is "Passthrough", the dialogue system ignores this link /// and continues evaluating the next link. /// public string falseConditionAction = null; /// /// The priority of the link. In a dialogue entry, links are evaluated in priority order /// from High to Low. /// public ConditionPriority conditionPriority = ConditionPriority.Normal; /// /// The list of outgoing links from this dialogue entry. /// public List outgoingLinks = new List(); /// /// A Lua conditional expression. When evaluating links, the dialogue system will only /// present links whose conditions are true, empty, or null. /// public string conditionsString = null; /// /// Lua code to run when this dialogue entry is spoken. If userScript is null or /// empty, it's ignored. /// public string userScript = null; /// /// /// public UnityEngine.Events.UnityEvent onExecute = new UnityEngine.Events.UnityEvent(); public const float CanvasRectWidth = 160f; public const float CanvasRectHeight = 30f; /// /// The position of this entry on the Dialogue Editor's node canvas. /// public Rect canvasRect = new Rect(0, 0, CanvasRectWidth, CanvasRectHeight); public const string SceneEventGuidFieldName = "EventGuid"; /// /// Gets or sets the ID of the line's actor (speaker). /// /// /// The ID of the actor. /// public int ActorID { get { return Field.LookupInt(fields, "Actor"); } set { Field.SetValue(fields, "Actor", value.ToString(), FieldType.Actor); } } /// /// Gets or sets the ID of the line's conversant (listener). /// /// /// The ID of the conversant. /// public int ConversantID { get { return Field.LookupInt(fields, "Conversant"); } set { Field.SetValue(fields, "Conversant", value.ToString(), FieldType.Actor); } } /// /// Gets or sets the title of the dialogue entry, which is an optional field typically only /// used internally by the developer. /// /// /// The title of the dialogue entry. /// public string Title { get { return Field.LookupLocalizedValue(fields, "Title"); } set { Field.SetValue(fields, "Title", value); } } /// /// Gets or sets the menu text, which is the text displayed when offering a list of player /// responses. Menu text can be used to present a shortened or paraphrased version of the /// full dialogue text. If menuText is null, the dialogue system uses dialogueText when /// offering player responses. /// /// /// The menu text. The actual field used depends on the current Localization. If using the /// default language, the field is "Menu Text". Otherwise the field is "Menu Text LN", /// where LN is the current language. /// public string currentMenuText { get { return Field.FieldValue(GetCurrentMenuTextField()); } set { Field field = GetCurrentMenuTextField(); if (field != null) field.value = value; } } /// /// Gets the current menu text field: the localized field if using localization and the /// localized field exists; otherwise the default field. /// /// /// The current menu text field. /// private Field GetCurrentMenuTextField() { return Field.AssignedField(fields, Field.LocalizedTitle("Menu Text")) ?? Field.Lookup(fields, "Menu Text"); } /// /// Gets or sets the default menu text ("Menu Text"). /// /// /// The default menu text. /// public string MenuText { get { return Field.LookupValue(fields, "Menu Text"); } set { Field.SetValue(fields, "Menu Text", value); } } /// /// Gets or sets the localized menu text (e.g., "Menu Text LN", where LN is the current language). /// /// /// The localized menu text. /// public string currentLocalizedMenuText { get { return Field.LookupValue(fields, Field.LocalizedTitle("Menu Text")); } set { Field.SetValue(fields, Field.LocalizedTitle("Menu Text"), value); } } /// /// Gets or sets the dialogue text, which is the subtitle text displayed when a line is /// spoken. /// /// /// The dialogue text. The actual field used depends on the current Localization. If using the /// default language, the field is "Dialogue Text". Otherwise the field is "LN", where LN is /// the current language. /// public string currentDialogueText { get { return Field.FieldValue(GetCurrentDialogueTextField()); } set { Field field = GetCurrentDialogueTextField(); if (field != null) field.value = value; } } /// /// Gets the current dialogue text field: the localized field if using localization and the /// localized field exists; otherwise the default field. /// /// /// The current dialogue text field. /// private Field GetCurrentDialogueTextField() { if (string.IsNullOrEmpty(Localization.language)) return Field.Lookup(fields, "Dialogue Text"); return Field.AssignedField(fields, Localization.language) ?? Field.Lookup(fields, "Dialogue Text"); } /// /// Gets or sets the default dialogue text ("Dialogue Text"). /// /// /// The default dialogue text. /// public string DialogueText { get { return Field.LookupValue(fields, "Dialogue Text"); } set { Field.SetValue(fields, "Dialogue Text", value); } } /// /// Gets or sets the localized dialogue text ("LN", where LN is the current language). /// /// /// The localized dialogue text. /// public string currentLocalizedDialogueText { get { return Field.LookupValue(fields, Localization.language); } set { Field.SetValue(fields, Localization.language, value); } } /// /// Gets the subtitle text, which is normally the dialogue text. If the dialogue text /// isn't assigned, the menu text is used. /// /// /// The subtitle text. /// public string subtitleText { get { return string.IsNullOrEmpty(currentDialogueText) ? currentMenuText : currentDialogueText; } } /// /// Gets the response button menu text, which is normally the menu text. If the menu text /// isn't assigned, the dialogue text is used. /// /// /// The response button text. /// public string responseButtonText { get { return string.IsNullOrEmpty(currentMenuText) ? currentDialogueText : currentMenuText; } } /// /// Gets or sets the sequence string. This is a custom field, and is used to specify /// Sequencer commands to play as the line is spoken. These sequencer commands can /// include animation, lip sync, audio, camera work, etc. (See @ref sequencer) /// /// /// The sequencer commands to play. /// /// /// Prior to version 1.0.6, the Dialogue System used the VideoFile field. Use the /// Sequence field instead now. /// public string currentSequence { get { return Field.FieldValue(GetCurrentSequenceField()); } set { SetCurrentSequenceField(value); } // Was: Field field = GetCurrentSequenceField(); if (field != null) field.value = value; } /// /// Gets the current sequence field: the localized field if using localization and the /// localized field exists; otherwise the default field. /// /// /// The current sequence field. /// private Field GetCurrentSequenceField() { return Field.AssignedField(fields, Field.LocalizedTitle("Sequence")) ?? Field.Lookup(fields, "Sequence"); } private void SetCurrentSequenceField(string value) { Field field = GetCurrentSequenceField(); if ((field == null) && Localization.isDefaultLanguage) { fields.Add(new Field("Sequence", value, FieldType.Text)); } else { if (field != null) field.value = value; } } /// /// Gets or sets the default, non-localized sequence ("Sequence"). Note that this is /// different from the DialogueManager display settings Default Sequence, which is /// the sequence used whenever a dialogue entry doesn't have a sequence defined. /// /// /// The default sequence. /// public string Sequence { get { return Field.LookupValue(fields, "Sequence"); } set { SetTextField("Sequence", value); } } /// /// Gets or sets the localized sequence (e.g., "Sequence LN", where LN is the current language). /// /// /// The localized sequence. /// public string currentLocalizedSequence { get { return Field.LookupValue(fields, Field.LocalizedTitle("Sequence")); } set { SetTextField(Field.LocalizedTitle("Sequence"), value); } } /// /// Gets or sets the guid corresponding to an entry in the current scene's /// DialogueSystemSceneEvents list, or blank if none. /// public string sceneEventGuid { get { return Field.LookupValue(fields, SceneEventGuidFieldName); } set { SetTextField(SceneEventGuidFieldName, value); } } /// /// Sets the sequence field, adding it if it doesn't already exist. /// /// /// Title of the sequence field to use (default or localized). /// /// /// Value to assign to the sequence field. /// private void SetTextField(string title, string value) { Field field = Field.Lookup(fields, title); if (field != null) { field.value = value; } else { fields.Add(new Field(title, value, FieldType.Text)); } } /// /// Indicates whether this entry has a response menu sequence. /// /// true if it has a response menu sequence; otherwise, false. public bool HasResponseMenuSequence() { return Field.FieldExists(fields, "Response Menu Sequence"); } /// /// Gets or sets the response menu sequence string. This is a custom field, and is used /// to specify Sequencer commands to play as the line is spoken. These sequencer commands /// can include animation, lip sync, audio, camera work, etc. (See @ref sequencer) /// /// /// The sequencer commands to play. /// public string currentResponseMenuSequence { get { return Field.FieldValue(GetCurrentResponseMenuSequenceField()); } set { SetCurrentResponseMenuSequenceField(value); } } /// /// Gets the current response menu sequence field: the localized field if using localization /// and the localized field exists; otherwise the default field. /// /// /// The current response menu sequence field. /// private Field GetCurrentResponseMenuSequenceField() { return Field.AssignedField(fields, Field.LocalizedTitle("Response Menu Sequence")) ?? Field.Lookup(fields, "Response Menu Sequence"); } private void SetCurrentResponseMenuSequenceField(string value) { Field field = GetCurrentSequenceField(); if ((field == null) && Localization.isDefaultLanguage) { fields.Add(new Field("Response Menu Sequence", value, FieldType.Text)); } else { if (field != null) field.value = value; } } /// /// Gets or sets the default, non-localized response menu sequence ("Response Menu Sequence"). /// /// /// The default response menu sequence. /// public string ResponseMenuSequence { get { return Field.LookupValue(fields, "Response Menu Sequence"); } set { SetTextField("Response Menu Sequence", value); } } /// /// Gets or sets the localized response menu sequence (e.g., "Response Menu Sequence LN", /// where LN is the current language). /// /// /// The localized sequence. /// public string currentLocalizedResponseMenuSequence { get { return Field.LookupValue(fields, Field.LocalizedTitle("Response Menu Sequence")); } set { SetTextField(Field.LocalizedTitle("Response Menu Sequence"), value); } } /// /// Gets or sets the video file string. This field is no longer used for cutscene sequences! /// /// /// Whatever you want. This is no longer used for cutscene sequences. /// /// /// Prior to version 1.0.6, the Dialogue System used this field for cutscene sequences. /// It now uses the custom Sequence field. /// public string VideoFile { get { return Field.LookupValue(fields, "Video File"); } set { Field.SetValue(fields, "Video File", value); } } /// /// Gets or sets the audio files string. This field is defined in Chat Mapper but doesn't /// do anything in the Dialogue System. Instead, the Sequence field should contain /// Sequencer commands to do things like play audio. /// /// /// The audio files string. /// public string AudioFiles { get { return Field.LookupValue(fields, "Audio Files"); } set { Field.SetValue(fields, "Audio Files", value); } } /// /// Gets or sets the animation files string. This field is defined in Chat Mapper, and /// doesn't do anything in the dialogue system. Instead, the Sequence field should contain /// Sequencer commands to do things like play animations. /// /// /// The animation files string. /// public string AnimationFiles { get { return Field.LookupValue(fields, "Animation Files"); } set { Field.SetValue(fields, "Animation Files", value); } } /// /// Gets or sets the lipsync files string. This field is defined in Chat Mapper, and /// doesn't do anything in the Dialogue System. Instead, the videoFile field should contain /// Sequencer commands to do things like play lip sync animation and audio. /// /// /// The lip sync files string. /// public string LipsyncFiles { get { return Field.LookupValue(fields, "Lipsync Files"); } set { Field.SetValue(fields, "Lipsync Files", value); } } /// /// Initializes a new DialogueEntry. /// public DialogueEntry() { } /// /// Initializes a new DialogueEntry copied from a Chat Mapper DialogEntry. /// /// /// The Chat Mapper dialog entry to copy. /// public DialogueEntry(ChatMapper.DialogEntry chatMapperDialogEntry) { if (chatMapperDialogEntry != null) { id = chatMapperDialogEntry.ID; fields = Field.CreateListFromChatMapperFields(chatMapperDialogEntry.Fields); UseCanvasRectField(); //--- Removed in Chat Mapper 1.7: conversationID = chatMapperDialogEntry.ConversationID; isRoot = chatMapperDialogEntry.IsRoot; isGroup = chatMapperDialogEntry.IsGroup; if (isGroup) Sequence = ""; //"Continue()"; nodeColor = chatMapperDialogEntry.NodeColor; delaySimStatus = chatMapperDialogEntry.DelaySimStatus; falseConditionAction = chatMapperDialogEntry.FalseCondtionAction; conditionPriority = ConditionPriorityUtility.StringToConditionPriority(chatMapperDialogEntry.ConditionPriority); foreach (var chatMapperLink in chatMapperDialogEntry.OutgoingLinks) { outgoingLinks.Add(new Link(chatMapperLink)); } conditionsString = chatMapperDialogEntry.ConditionsString; userScript = chatMapperDialogEntry.UserScript; } } /// /// If the dialogue entry has a canvasRect field, extract it and use it to set the canvas rect position. /// public void UseCanvasRectField() { var canvasRectField = Field.Lookup(fields, "canvasRect"); if (canvasRectField != null && !string.IsNullOrEmpty(canvasRectField.value)) { var values = canvasRectField.value.Split(';'); var canvasX = (values.Length >= 1) ? Tools.StringToFloat(values[0]) : 0; var canvasY = (values.Length >= 2) ? Tools.StringToFloat(values[1]) : 0; if (canvasX > 0 && canvasY > 0) canvasRect = new Rect(canvasX, canvasY, canvasRect.width, canvasRect.height); fields.Remove(canvasRectField); } } /// /// Copy constructor. /// /// Source entry. public DialogueEntry(DialogueEntry sourceEntry) { this.id = sourceEntry.id; this.fields = Field.CopyFields(sourceEntry.fields); this.conversationID = sourceEntry.conversationID; this.isRoot = sourceEntry.isRoot; this.isGroup = sourceEntry.isGroup; this.nodeColor = sourceEntry.nodeColor; this.delaySimStatus = sourceEntry.delaySimStatus; this.falseConditionAction = sourceEntry.falseConditionAction; this.conditionPriority = ConditionPriority.Normal; this.outgoingLinks = CopyLinks(sourceEntry.outgoingLinks); this.conditionsString = sourceEntry.conditionsString; this.userScript = sourceEntry.userScript; this.canvasRect = sourceEntry.canvasRect; } private List CopyLinks(List sourceLinks) { List links = new List(); foreach (var sourceLink in sourceLinks) { links.Add(new Link(sourceLink)); } return links; } } }