// Start the conversation if possible
        public void StartConversation(Conversation conversation)
        {
            if (_inConversation)
            {
                DialogueLogger.LogWarning($"Trying to start a conversation while already in a conversation");
                return;
            }

            if (_uiController == null)
            {
                DialogueLogger.LogError("Trying to start a conversation, but there's not DialogueUIController assigned");
                return;
            }

            // Find starting point
            _currentConversation = null;
            _currentDialogue     = null;
            _currentSentence     = 0;
            foreach (var diag in conversation.Dialogues)
            {
                if ((_currentDialogue == null || diag.Id > _currentDialogue.Id) && diag.CanBeUsedAsStartingPoint)
                {
                    if (diag.StartConditions.Count > 0)
                    {
                        if (diag.EvaluateStartingConditions())
                        {
                            _currentDialogue = diag;
                        }
                    }
                    else
                    {
                        _currentDialogue = diag;
                    }
                }
            }

            if (_currentDialogue == null)
            {
                DialogueLogger.LogError($"Couldn't find starting point for conversation");
                return;
            }

            // Start the conversation
            _inConversation      = true;
            _currentConversation = conversation;

            if (needToChangeTheme())
            {
                ChangeTheme(_currentDialogue.Theme);
            }

            _uiController.ShowSentence(_currentDialogue.SpeakersName,
                                       parseSentenceForCustomTags(_currentDialogue.Sentences[_currentSentence]),
                                       SpriteRepo.Instance.RetrieveSprite(_currentDialogue.CharacterSpritesName, string.IsNullOrEmpty(_currentDialogue.StartingSprite) ? "Default" : _currentDialogue.StartingSprite),
                                       _currentDialogue.AutoProceed);
            ConversationStarted?.Invoke();
        }
Ejemplo n.º 2
0
        // Add a theme to the repo if it's valid
        public void RegisterThemeSprites(Theme_SO sprites)
        {
            if (_themeSprites.ContainsKey(sprites.Name))
            {
                DialogueLogger.LogWarning($"Theme sprites for {sprites.Name} already exist, overwritting");
            }

            if (validateSprites(sprites))
            {
                _themeSprites[sprites.Name] = sprites;
            }
        }
Ejemplo n.º 3
0
 // Get the variable if it exists and we don't care about the type
 public object Retrieve(string name)
 {
     if (!_variables.ContainsKey(name))
     {
         DialogueLogger.LogWarning($"Trying to retrieve the variable {name} but it hasn't been registered");
         return(null);
     }
     else
     {
         return(_variables[name].Value);
     }
 }
Ejemplo n.º 4
0
        // Add the sprites to the repo ready for retrieval
        public void RegisterCharacterSprites(DialogueSprites_SO sprites)
        {
            if (_characterSprites.ContainsKey(sprites.CharactersName))
            {
                DialogueLogger.LogWarning($"Character sprites for {sprites.CharactersName} already exist, overwritting");
            }

            if (validateSprites(sprites))
            {
                _characterSprites[sprites.CharactersName] = sprites;
            }
        }
Ejemplo n.º 5
0
 // Return the variable if it exists
 public T Retrieve <T>(string name)
 {
     // I don't like returning a default like this, but it's better than an exception
     if (!_variables.ContainsKey(name))
     {
         DialogueLogger.LogWarning($"Trying to retrieve the variable {name} but it hasn't been registered. Returning default");
         return(default(T));
     }
     else
     {
         return(_variables[name].GetValue <T>());
     }
 }
Ejemplo n.º 6
0
        // Validate then execute
        private static void performAction(DialogueAction action)
        {
            if (!validateAction(action))
            {
                return;
            }

            switch (action.ActionType)
            {
            case DialogueAction.Types.LOG: DialogueLogger.Log(action.Message); break;

            case DialogueAction.Types.LOG_WARNING: DialogueLogger.LogWarning(action.Message); break;

            case DialogueAction.Types.LOG_ERROR: DialogueLogger.LogError(action.Message); break;

            case DialogueAction.Types.CLOSE_CONVERSATION: DialogueController.Instance?.StopCurrentConversation(); break;

            case DialogueAction.Types.SEND_MESSAGE:
                var targetObject = GameObject.Find(action.Target);

                if (targetObject == null)
                {
                    DialogueLogger.LogError($"Trying to execute a send message action, but GameObject {action.Target} was not found. Skipping action");
                    return;
                }
                targetObject.SendMessage(action.Message, SendMessageOptions.DontRequireReceiver);
                break;

            case DialogueAction.Types.CHANGE_THEME:
                DialogueController.Instance?.ChangeTheme(action.Message);
                break;

            case DialogueAction.Types.CLOSE_BG_CONVERSATIONS:
                BackgroundDialogueController.Instance?.CloseConversations();
                break;

            case DialogueAction.Types.START_BG_CONVERSATION:
                BackgroundDialogueController.Instance?.StartConversation(action.Message);
                break;

            default:
                DialogueLogger.LogError($"Action with the name {action.Name} has na unrecognised action type {action.ActionType}. The action type loaded from the conversation JSON is {action.Type}. Skipping action");
                break;
            }
        }
Ejemplo n.º 7
0
        // Register and validate a conversation in real-time
        public void RegisterConversation(string name, TextAsset file)
        {
            var tempObject = _deserializer.Deserialize <Conversation>(file.text);

            if (tempObject == null)
            {
                DialogueLogger.LogError($"There was an error deserializing file {file.name}");
                return;
            }

            tempObject.PreValidation();
            if (valdateConversation(tempObject, name))
            {
                if (_conversations.ContainsKey(name))
                {
                    DialogueLogger.LogWarning($"Conversation {name} already registered, overwritting");
                }

                _conversations[name] = tempObject;
                tempObject.FinishedParsing();
            }
        }
Ejemplo n.º 8
0
        // Test the variables according to the comparison string
        public bool Evaluate()
        {
            // Check the comparer
            if (_comparer == null)
            {
                DialogueLogger.LogError("Trying to evaluate a condition, but the comparison operator is null");
                return(false);
            }

            // Get values
            var var1 = Variables[0].GetValue();
            var var2 = Variables[1].GetValue();

            if (var1 == null || var2 == null)
            {
                DialogueLogger.LogWarning($"Trying to evaluate a condition but one or more of the variables are null");
                return(false);
            }

            // Evaluate
            // We're using dynamics here, so be careful. We'll have to validate types on the conversation load
            return(_comparer.Execute(var1, var2));
        }
Ejemplo n.º 9
0
        // Check all the values are valid
        private bool validateSprites(Theme_SO sprites)
        {
            var names = new List <string>();

            if (sprites.ThemeSprites.Count == 0)
            {
                DialogueLogger.LogWarning($"Trying to register theme sprites for {sprites.Name} but the sprite list is empty");
            }

            foreach (var spritePair in sprites.ThemeSprites)
            {
                // Check name
                if (string.IsNullOrEmpty(spritePair.Name) || string.IsNullOrWhiteSpace(spritePair.Name))
                {
                    DialogueLogger.LogError($"Error registering theme sprites for {sprites.Name}, one or more of the names is empty");
                    return(false);
                }

                // Check for duplicate names
                if (names.Contains(spritePair.Name))
                {
                    DialogueLogger.LogError($"Error registering theme sprites for {sprites.Name}, there is already a sprite with the name {spritePair.Name}. Each sprite must have a unique name");
                    return(false);
                }

                names.Add(spritePair.Name);

                // Check sprites
                if (spritePair.Sprite == null)
                {
                    DialogueLogger.LogError($"Error registering theme sprites for {sprites.Name} with the sprite name {spritePair.Name}, but the sprite is empty");
                    return(false);
                }
            }

            return(true);
        }
Ejemplo n.º 10
0
        // If you're UI supports tags, use this to execute all tags at a given position in the sentence
        // I don't like this here. But unfortunately, the default and background conversations are too different (in my example implementation) to put it in a parent class.
        // Please note, not all tags are used here
        protected IEnumerator processTagsForPosition(TextModifications textMod, int index)
        {
            var mods = textMod.GetAnyTextModsForPosition(index);

            // Check for custom modifications
            foreach (var mod in mods)
            {
                // Commands
                if (mod.ModType == TextModifications.Modifications.CLOSE_BG_CONVERSATIONS)
                {
                    BackgroundDialogueController.Instance?.CloseConversations();
                }

                // Simple modifications e.g. <command=value>
                if (mod.ModType == TextModifications.Modifications.SPEED)
                {
                    _speedMultiplyer = (mod as SimpleModification).GetValue <float>();
                }
                else if (mod.ModType == TextModifications.Modifications.REMOVE_VARAIBLE)
                {
                    VariableRepo.Instance?.Remove((mod as SimpleModification).GetValue <string>());
                }
                else if (mod.ModType == TextModifications.Modifications.WAIT)
                {
                    yield return(new WaitForSeconds((mod as SimpleModification).GetValue <float>()));
                }
                else if (mod.ModType == TextModifications.Modifications.ACTION)
                {
                    performAction((mod as SimpleModification).GetValue <string>());
                }
                else if (mod.ModType == TextModifications.Modifications.LOG)
                {
                    DialogueLogger.Log((mod as SimpleModification).GetValue <string>());
                }
                else if (mod.ModType == TextModifications.Modifications.LOG_WARNING)
                {
                    DialogueLogger.LogWarning((mod as SimpleModification).GetValue <string>());
                }
                else if (mod.ModType == TextModifications.Modifications.LOG_ERROR)
                {
                    DialogueLogger.LogError((mod as SimpleModification).GetValue <string>());
                }
                else if (mod.ModType == TextModifications.Modifications.BG_CONVERSATION)
                {
                    BackgroundDialogueController.Instance?.StartConversation((mod as SimpleModification).GetValue <string>());
                }

                // Complex modifications e.g. <command=value>content</command>
                else if (mod.ModType == TextModifications.Modifications.SEND_MESSAGE)
                {
                    var revievingObject = GameObject.Find((mod as SimpleModification).GetValue <string>());
                    if (revievingObject == null)
                    {
                        DialogueLogger.LogError($"Trying to execute a send message command, but GameObject {(mod as SimpleModification).GetValue<string>()} was not found");
                        continue;
                    }

                    revievingObject.SendMessage((mod as ComplexModification).GetContent <string>(), SendMessageOptions.DontRequireReceiver);
                }
                else if (mod.ModType == TextModifications.Modifications.ACTION_WITH_MESSAGE)
                {
                    performActionWithMessage((mod as SimpleModification).GetValue <string>(), (mod as ComplexModification).GetContent <string>());
                }
                else if (mod.ModType == TextModifications.Modifications.ACTION_WITH_TARGET)
                {
                    performActionWithTarget((mod as SimpleModification).GetValue <string>(), (mod as ComplexModification).GetContent <string>());
                }
            }
        }
Ejemplo n.º 11
0
        // Do simple validation on the conversation e.g. That it's not empy, that all nextIds go somewhere, things like that
        // TODO: Move all validation code to the relevant class in the Conversation.cs
        private bool valdateConversation(Conversation tempConversation, string fileName)
        {
            var dialogueIds = new List <int>();
            var actionNames = new List <string>();

            // Get all the action names for this conversation
            foreach (var action in tempConversation.Actions)
            {
                // Check we at least have a name and type
                if (string.IsNullOrEmpty(action.Name) || string.IsNullOrWhiteSpace(action.Name) ||
                    string.IsNullOrEmpty(action.Type) || string.IsNullOrWhiteSpace(action.Type))
                {
                    DialogueLogger.LogError($"An action in file {fileName} has an empty name or type value");
                    return(false);
                }

                // Check if it already exists
                if (actionNames.Contains(action.Name))
                {
                    DialogueLogger.LogError($"An action in file {fileName} does not have a unique name");
                    return(false);
                }
                actionNames.Add(action.Name);

                // Check action type and add name to the list
                // Errors in DialogueAction class
                if (!action.GetActionType())
                {
                    return(false);
                }

                // An action's message and target can be set in real time via the dialogue, so it'll have to be verified in the DialogueController.
                // Verifiy all of the essential values here
            }

            // Check number of dialogues
            if (tempConversation.Dialogues.Count == 0)
            {
                DialogueLogger.LogError($"Empty conversation in file {fileName}");
                return(false);
            }

            // Check dialogues sentences and populate Id list
            foreach (var diag in tempConversation.Dialogues)
            {
                // Check sentence count
                if (diag.Sentences.Count == 0)
                {
                    DialogueLogger.LogError($"Dialogue in file {fileName} has no sentences");
                    return(false);
                }

                // Check sentence for empty
                foreach (var sentence in diag.Sentences)
                {
                    if (string.IsNullOrEmpty(sentence))
                    {
                        DialogueLogger.LogError($"Empty sentence in file {fileName}");
                        return(false);
                    }
                }

                // Check CharacterSpriteNames and StartingSprites
                // Check the there's a CharacterSpriteName if we've sepecified a StartingSprite
                if (!string.IsNullOrEmpty(diag.StartingSprite) && string.IsNullOrEmpty(diag.CharacterSpritesName))
                {
                    DialogueLogger.LogError($"Dialogue in file {fileName} has a startingSprite but no characterSpritesName is specified");
                    return(false);
                }

                // Check the OnFinishedActionNames
                // Loop through all of the action and make sure they exist
                foreach (var action in diag.OnFinishedActionNames)
                {
                    if (!actionNames.Contains(action))
                    {
                        DialogueLogger.LogError($"An dialogues OnFinishActions in file {fileName} attemps to call an action with the name {action} that doesn't exist.");
                        return(false);
                    }
                }

                dialogueIds.Add(diag.Id);
            }

            // Loop through dialogues and check Ids, Conditions, and Options
            var startingPriorities = new List <int>();

            foreach (var diag in tempConversation.Dialogues)
            {
                // Check for dialogue next Ids values
                if (diag.NextId != -1)
                {
                    if (!dialogueIds.Contains(diag.NextId))
                    {
                        DialogueLogger.LogError($"Dialoge in file {fileName} references a dialogue by id {diag.NextId} that doesn't exist");
                        return(false);
                    }
                }

                // Check option texts and nextIds
                foreach (var option in diag.Options)
                {
                    // Check if any of the option has an action
                    // loop through all of the action and make sure they exist
                    foreach (var action in option.SelectedActionNames)
                    {
                        if (!actionNames.Contains(action))
                        {
                            DialogueLogger.LogError($"An option's selectedAction in file {fileName} attemps to call an action with the name {action} that doesn't exist.");
                            return(false);
                        }
                    }

                    // Check text
                    if (string.IsNullOrEmpty(option.Text))
                    {
                        DialogueLogger.LogError($"Empty option in file {fileName}");
                        return(false);
                    }

                    // Check the next dialogue exists if there's no action
                    if (option.SelectedActionNames.Count == 0 && (!dialogueIds.Contains(option.NextId) || option.NextId == -1))
                    {
                        DialogueLogger.LogError($"An option in file {fileName} has a nextId {option.NextId} that doesn't exist. If an option hasn't got an action, it needs a nextId");
                        return(false);
                    }
                }

                // Check Conditions and Variables
                foreach (var con in diag.StartConditions)
                {
                    // Can only test 2 varaibles against each other. When a conversation is picked all conditions must evaluate to true, so if more tests are needed just stack conditions
                    if (con.Variables.Count != 2)
                    {
                        DialogueLogger.LogError($"A condition in file {fileName} does not contains {con.Variables.Count} variables, must have 2");
                        return(false);
                    }

                    // Check the type, value, and name individually
                    foreach (var variable in con.Variables)
                    {
                        // Check there's a value if we're not retrieving from the repo
                        if (string.IsNullOrEmpty(variable.Value))
                        {
                            if (!variable.FromRepo)
                            {
                                DialogueLogger.LogError($"A condition variable in file {fileName} has a conditional varialbe without a value. One is required if not retrieving from the variable repo");
                                return(false);
                            }
                        }
                        else
                        {
                            if (variable.FromRepo)
                            {
                                DialogueLogger.LogWarning($"A condition variable in file {fileName} has a value it will be ignored as the variable is marked to be retrieved from the variable repo");
                            }
                        }

                        // A type is required if we're not retrieving from the repo
                        if (string.IsNullOrEmpty(variable.Type))
                        {
                            if (!variable.FromRepo)
                            {
                                DialogueLogger.LogError($"A condition in file {fileName} has a conditional variable without a type. One is required if not retrieving from the variable repo");
                                return(false);
                            }
                        }
                        else
                        {
                            if (variable.FromRepo)
                            {
                                DialogueLogger.LogWarning($"A condition variable in file {fileName} has a type but will be ignored as the variable is marked to be retrieved from the variable repo");
                            }
                        }

                        // Check there's a name if we're retrieving from the repo
                        if (string.IsNullOrEmpty(variable.Name))
                        {
                            if (variable.FromRepo)
                            {
                                DialogueLogger.LogError($"A condition variable in the file {fileName} does not have a name value, one is required for retrieval from the varialbe repo");
                                return(false);
                            }
                        }
                        else
                        {
                            if (!variable.FromRepo)
                            {
                                DialogueLogger.LogWarning($"A condition variable in the file {fileName} has name value but will be ignored as the variable is not marked to be retrieved from the variable repo");
                            }
                        }
                    }


                    var var1LowerType = con.Variables[0].Type?.ToLower();
                    var var2LowerType = con.Variables[1].Type?.ToLower();

                    // Check the comparison operator if either of the types is a string or bool
                    if (var1LowerType == "string" || var1LowerType == "bool" || var2LowerType == "string" || var2LowerType == "bool")
                    {
                        if (con.Comparison != "==" && con.Comparison != "!=")
                        {
                            DialogueLogger.LogError($"String and booleans can only use the == and != equality operators. Condition in file {fileName} is trying to use {con.Comparison}");
                            return(false);
                        }
                    }

                    // String and bool are only tested against each other. This isn't quite true, normally, but it's a lot more readable to only allow string and bools to be compared against each other
                    if (!con.Variables[0].FromRepo && (var1LowerType == "bool" || var1LowerType == "string") &&
                        !con.Variables[1].FromRepo && (var2LowerType == "bool" || var2LowerType == "string"))
                    {
                        // Check they're compatible
                        if (var1LowerType != var2LowerType)
                        {
                            DialogueLogger.LogError($"Conversation file {fileName} has a condition with an unsupported variable comparison. Strings or booleans can only be tested against each other");
                            return(false);
                        }
                    }
                }

                // Warn if autoProceed is false and the conversation type is background, we'll autoproceed anyway
                if (tempConversation.ConversationType == Conversation.Types.BACKGROUND)
                {
                    if (!diag.AutoProceed)
                    {
                        DialogueLogger.LogWarning($"Dialogue in the file {fileName} is of type background and autoProceed is false. Background conversations always autoproceed");
                    }
                }
            }

            return(true);
        }