Conversations and Dialogue Triggers

[SOLVED]

I can reverse it, but what’s a better idea to invite and eliminate NPCs to your AggroGroup then? You know I can’t just leave this concept flying :sweat_smile:

If it helps though, here’s a quick summary of exactly what I added in to try and make it happen:

  1. I created a new “AIAggroGroupSwitcher.cs” script, and placed it on the enemy NPC:
using UnityEngine;
using RPG.States.Enemies;
using RPG.Respawnables;
using RPG.Dialogue;

namespace RPG.Combat {

public class AIAggroGroupSwitcher : MonoBehaviour
{
    // This script is placed on NPCs, and is essentially responsible for
    // allowing them to enter and exit the player's AggroGroup, through
    // dialogue:

    private AggroGroup playerAggroGroup;

    [Tooltip("This is the conversation you would get if you want to invite an NPC to your AggroGroup (used in 'AIConversant.cs', setup in 'RespawnManager.cs')")]
    [SerializeField] private Dialogue.Dialogue NPCOutOfPlayerGroupInviteDialogue;
    [Tooltip("This is the conversation you would get if you want to kick out an NPC ally from your AggroGroup (used in 'AIConversant.cs', setup in 'RespawnManager.cs')")]
    [SerializeField] private Dialogue.Dialogue NPCInPlayerGroupKickoutDialogue;

    private void Awake()
    {
        playerAggroGroup = GameObject.Find("PlayerAggroGroup").GetComponent<AggroGroup>();
    }

    public void AddNPCToPlayerAggroGroup()
    {
        Debug.Log($"{gameObject.name} added to player AggroGroup - 1");

        // This function essentially deletes the Enemy 
        // that calls it from his own AggroGroup, and adds him to
        // the Player's AggroGroup, and is meant to be called 
        // at the end of the dialogues that lead to that action:
        if (playerAggroGroup.GetGroupMembers().Contains(this.GetComponent<EnemyStateMachine>())) return; // if this script holder is part of the Player's AggroGroup, don't add him again
        Debug.Log($"{gameObject.name} added to player AggroGroup - 2");

        // First, Delete him from his own AggroGroup:
        this.GetComponent<EnemyStateMachine>().GetComponentInParent<RespawnManager>().GetComponentInParent<AggroGroup>().RemoveFighterFromGroup(this.GetComponent<EnemyStateMachine>());
        Debug.Log($"{gameObject.name} added to player AggroGroup - 3");
        // And now, add him to the player's AggroGroup:
        playerAggroGroup.AddFighterToGroup(this.GetComponent<EnemyStateMachine>());
        Debug.Log($"{gameObject.name} added to player AggroGroup - 4");
        // Set the RespawnManager's Dialogue accordingly:
        GetComponentInParent<RespawnManager>().SetCurrentConversation(GetNPCInPlayerGroupKickOutDialogue());
        Debug.Log($"{gameObject.name} added to player AggroGroup - 5");
        // Switch the AggroGroup of the enemy holding this script, to the Player's AggroGroup:
        GetComponentInParent<RespawnManager>().SetAggroGroup(playerAggroGroup);
        Debug.Log($"{gameObject.name} added to player AggroGroup - 6");
        // Set the AIConversant's Dialogue accordingly (this is the core value that determines who holds the conversation):
        GetComponent<AIConversant>().SetDialogue(GetNPCInPlayerGroupKickOutDialogue());
        Debug.Log($"{gameObject.name} added to player AggroGroup - 7");
        // Set the 'AggroGroup' within the 'EnemyStateMachine' as well, otherwise you're getting a NullReferenceException:
        GetComponent<EnemyStateMachine>().SetAggroGroup(playerAggroGroup);
        Debug.Log($"{gameObject.name} added to player AggroGroup - 8");
    }

    public void RemoveNPCFromPlayerAggroGroup()
    {
        Debug.Log($"{gameObject.name} kicked out of player AggroGroup - 1");
        // Similar to how 'AddNPCToPlayerAggroGroup()' adds the script holder to
        // the player's AggroGroup, this function is supposed to delete the script
        // holder from the Player's AggroGroup:

        // The following line is correct, but for the system to fully work as expected, the Saving and Restoring System for 'AggroGroup.cs' must be developed!
        if (!playerAggroGroup.GetGroupMembers().Contains(this.GetComponent<EnemyStateMachine>())) return; // if this script holder is no longer part of the Player's AggroGroup, don't try deleting him again
        Debug.Log($"{gameObject.name} kicked out of player AggroGroup - 2");

        // First, Add the NPC back to his original Individual AggroGroup:
        this.GetComponent<EnemyStateMachine>().GetComponentInParent<RespawnManager>().GetComponentInParent<AggroGroup>().AddFighterToGroup(this.GetComponent<EnemyStateMachine>());
        Debug.Log($"{gameObject.name} kicked out of player AggroGroup - 3");
        // Remove the NPC from the Player's AggroGroup:
        playerAggroGroup.RemoveFighterFromGroup(this.GetComponent<EnemyStateMachine>());
        Debug.Log($"{gameObject.name} kicked out of player AggroGroup - 4");
        // Set the RespawnManager's Dialogue accordingly:
        GetComponentInParent<RespawnManager>().SetCurrentConversation(GetNPCOutOfPlayerGroupInviteDialogue());
        Debug.Log($"{gameObject.name} kicked out of player AggroGroup - 5");
        // Switch the AggroGroup of the enemy holding this script, back to original AggroGroup:
        GetComponentInParent<RespawnManager>().SetAggroGroup(GetComponentInParent<AggroGroup>());
        Debug.Log($"{gameObject.name} kicked out of player AggroGroup - 6");
        // Set the AIConversant's Dialogue accordingly (this is the core value that determines who holds the conversation):
        GetComponent<AIConversant>().SetDialogue(GetNPCOutOfPlayerGroupInviteDialogue());
        Debug.Log($"{gameObject.name} kicked out of player AggroGroup - 7");
        // Set the 'AggroGroup' within the 'EnemyStateMachine' as well, otherwise you're getting a NullReferenceException:
        GetComponent<EnemyStateMachine>().SetAggroGroup(GetComponentInParent<RespawnManager>().GetComponentInParent<AggroGroup>());
        Debug.Log($"{gameObject.name} kicked out of player AggroGroup - 8");
    }

    public Dialogue.Dialogue GetNPCOutOfPlayerGroupInviteDialogue() 
    {
        return NPCOutOfPlayerGroupInviteDialogue;
    }

    public void SetNPCOutOfPlayerGroupInviteDialogue(Dialogue.Dialogue dialogue) 
    {
        this.NPCOutOfPlayerGroupInviteDialogue = dialogue;
    }

    public Dialogue.Dialogue GetNPCInPlayerGroupKickOutDialogue() 
    {
        return NPCInPlayerGroupKickoutDialogue;
    }

    public void SetNPCInPlayerGroupKickOutDialogue(Dialogue.Dialogue dialogue) 
    {
        this.NPCInPlayerGroupKickoutDialogue = dialogue;
    }

}

}
  1. I created a ‘currentConversation’ in ‘RespawnManager.cs’, which gets saved and restored in ‘RespawnManager.cs’, and then does an override to ‘dialogue’ in ‘AIConversant.cs’ at restoration, and ‘RespawnManager.cs’ is placed on the parent of the enemy NPC, which spawns and despawns the NPC. Each NPC has its own individual parent ‘RespawnManager.cs’:
        [Header("Conversation Changers, when an NPC\nJoins or Leaves the Player's AggroGroup:")]
        [Tooltip("Is this enemy part of the player's AggroGroup? Based on this AUTOMATICALLY CHANGING VALUE, the dialogue in 'AIConversant.cs' is being setup accordingly, in the instantiated child of this gameObject when the game runs")]
        [SerializeField] private Dialogue.Dialogue currentConversation;

// in 'CaptureAsJToken()':
            // Capture the Current Conversation (so our allies who are on our team can be kicked out when we return to the game,
            // and foreigners can join our team as well when necessary):
            stateDict["CurrentConversation"] = currentConversation != null ? currentConversation.name : null;


// in 'RestoreFromJToken()':
            // Restore the Current Conversation (so our allies who are on our team can be kicked out when we return to the game,
            // and foreigners can join our team as well when necessary):
            if (stateDict.TryGetValue("CurrentConversation", out var conversationToken))
            {
                var conversationName = conversationToken.ToObject<string>();
                currentConversation = !string.IsNullOrEmpty(conversationName) ? Dialogue.Dialogue.GetByName(conversationName) : null;
            }

            // Update AIConversant with the restored currentConversation:
            var aiConversant = GetComponentInChildren<AIConversant>();
            if (aiConversant != null) 
            {
                Debug.Log($"Updating 'AIConversant.cs' in children");
                aiConversant.SetDialogue(currentConversation);
            }
  1. I made a dialogue setter in ‘AIConversant.cs’:
        // This setter will set the dialogue, based on the state of the conversant.
        // (For now it's used to change the dialogue for NPCs that join and leave the player's
        // AggroGroup, so we can kick and invite them as we please)
        public void SetDialogue(Dialogue dialogue) 
        {
            this.dialogue = dialogue;
        }

and 4. Create the Saving System in ‘AggroGroup.cs’:

// ---------------------- TEST - SAVING SYSTEM FOR 'AGGROGROUP.CS' --------------------------

        public JToken CaptureAsJToken()
        {
            JObject state = new JObject();
            JArray enemiesArray = new JArray();

            foreach (var enemy in enemies) 
            {
                enemiesArray.Add(enemy.name);
            }

            state["enemies"] = enemiesArray;
            return state;
        }

        public void RestoreFromJToken(JToken state)
        {
            JObject stateObject = (JObject)state;
            JArray enemiesArray = (JArray)stateObject["enemies"];

            enemies.Clear();
            foreach (var enemyName in enemiesArray) 
            {
                GameObject enemy = GameObject.Find((string)enemyName);
                if (enemy != null) 
                {
                    enemies.Add(enemy.GetComponent<EnemyStateMachine>());
                    if (enemy.GetComponentInParent<RespawnManager>() != null) 
                    {
                        enemy.GetComponentInParent<RespawnManager>().GetAggroGroup();
                        if (enemy.GetComponentInParent<RespawnManager>().GetAggroGroup() != null) 
                        {
                            enemy.GetComponentInParent<RespawnManager>().SetAggroGroup(this);
                        }
                    }
                }
            }
        }

        // ------------------------------ END OF TEST ----------------------------------------------

which, at the moment, does the saving and restoring by names, and also takes care of (for my case) RespawnManager’s AggroGroup variable, to keep consistency

And since ‘AggroGroup.cs’ has a saving system now, make sure to have this header above your class declaration:

[RequireComponent(typeof(JSONSaveableEntity))]

and finally, you need to reset the conversation whenever the enemy dies and respawns, in ‘RespawnManager.Respawn()’:

            if (currentConversation != null) 
            {
                var enemyAIConversant = GetComponentInChildren<AIConversant>();
                if (enemyAIConversant != null) 
                {
                    enemyAIConversant.SetDialogue(currentConversation);
                }
            }

I think that’s about all the changes that I did

(And I also had two ‘DialogueTrigger.cs’ scripts, each connected to one of the functions in ‘AIAggroGroupSwitcher.cs’, which are connected to the exit action of the dialogues, where they belong, and not to forget the AIConversant.cs script itself on the enemy, which holds the dialogue that pops up to the player)

I reversed this as a test, and… yeah, that’s pretty much all I added in

It’s a bigger thing than I’m ready to tackle right now (I think I mentioned that before several times).

fair enough… :slight_smile:

WELL… I have some great news. I solved the problem, although there’s one last step missing, for consistency’s sake

Anyway, here’s what I came up with for the saving system of ‘AggroGroup.cs’:

        // ---------------------- TEST - SAVING SYSTEM FOR 'AGGROGROUP.CS' --------------------------

        public JToken CaptureAsJToken()
        {
            JObject state = new JObject();
            JArray enemiesArray = new JArray();

            foreach (var enemy in enemies) 
            {
                enemiesArray.Add(enemy.name);
            }

            state["enemies"] = enemiesArray;
            return state;
        }

        public void RestoreFromJToken(JToken state)
        {
            JObject stateObject = (JObject)state;
            JArray enemiesArray = (JArray)stateObject["enemies"];

            enemies.Clear();
            foreach (var enemyName in enemiesArray) 
            {
                GameObject enemy = GameObject.Find((string)enemyName);
                if (enemy != null) 
                {
                    enemies.Add(enemy.GetComponent<EnemyStateMachine>());
                    if (enemy.GetComponentInParent<RespawnManager>() != null) 
                    {
                        enemy.GetComponentInParent<RespawnManager>().GetAggroGroup();
                        if (enemy.GetComponentInParent<RespawnManager>().GetAggroGroup() != null) 
                        {
                            enemy.GetComponentInParent<RespawnManager>().SetAggroGroup(this);
                        }
                    }
                }
            }
        }

        // ------------------------------ END OF TEST ----------------------------------------------

And, needless to say, don’t forget to throw on a ‘JSONSaveableEntity.cs’ script onto your AggroGroup’s Script holder. To do it quickly, at the top of ‘AggroGroup.cs’, just place this above the class:

[RequireComponent(typeof(JSONSaveableEntity))]

It does tracking down by names, but for the time being I think it works just fine, as long as two characters don’t have the same name, which means I’ll need to be creative with my character’s names…

It also takes care of connecting the RespawnManager’s ‘AggroGroup’ variable to the correct one (and that was the missing, and solved, step)

I’ll update the comment above as well, to include that

When a better solution is available, I’ll reverse all of this (I can’t believe I actually pulled this off :sweat_smile:)

Now I can peacefully go back to studying git :stuck_out_tongue:

Edit 1: AND… I still have a Respawning issue, where the dialogue does not get correctly transferred to the AIConversant.cs, I forgot to deal with that part…

Edit 2: And… I fixed it. I wrote the code in such a way that this whole thing is optional, and surprisingly it’s only a few lines long. The following code is integrated in ‘RespawnManager.Respawn()’:

            if (currentConversation != null) 
            {
                var enemyAIConversant = GetComponentInChildren<AIConversant>();
                if (enemyAIConversant != null) 
                {
                    enemyAIConversant.SetDialogue(currentConversation);
                }
            }

I also have to highlight that due to the nature that it’s programmed in, THIS IS ALL OPTIONAL, so it won’t hurt the main code in any way shape or form

Edit 3: I also posted my game PRIVATELY on GitHub, yay :stuck_out_tongue:

Edit 4: PLEASE BE PATIENT WITH ME WHEN IT COMES TO MERGING AND DEALING WITH GITHUB… I have a funny feeling like it’ll be a while before I can get this thing running properly :sweat_smile: (the merging lectures, 3.5 and 3.6, aren’t something I have easily caught up with. I’ll proceed with section 4 over the next 2 days, and section 5 on the third day, and then skip on to the next course. For now, I’ll return to coding)

1 Like

Privacy & Terms