[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
If it helps though, here’s a quick summary of exactly what I added in to try and make it happen:
- 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;
}
}
}
- 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);
}
- 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