AggroGroup for third person

Checking every frame is a massive code drag… events are far more efficient.

Make a variable
lastHealth
Compare it to the currentHealth
If it’s different, return true and set lastHealth to currentHealth
Complain about your lowered framerate

I won’t argue against that, I know that not everything has to be done in ‘Update’ (learned that the hard way). All I’m asking is, is there a simpler solution to check for the event in that if statement?

That method has other issues… GameObject.FindWithTag(“Enemy”) will find the first enemy (and only the FIRST enemy) IN THE SCENE (ONLY THE FIRST ENEMY)…
I’m in the middle of something else… I’ll try to make some sense of this later… did you mean CompareTag???

if I’m being brutally honest, I don’t know the big difference between “FindWithTag”, “FindGameObjectWithTag” and “CompareTag”, so… I will just go with “yes, I meant CompareTag” :stuck_out_tongue: (although Unity doesn’t like that…)

Naming-wise, to me they all seem to be doing the same thing

CompareTag is different than FindWithTag… I suggest checking the docs on those

1 Like

Ok, here’s how I understand Aggrogroup context, from the Dialogues and Quests course…

In that, the AggroGroup usually starts out not aggro, but then, if you say the wrong thing, a DialogueTrigger calls Activate(true);

That generally activates or deactivates the Fighter Component, and that’s about it

This method… Ok, you’re removing destroyed fighters, but then you’re checking them for null again in the foreach they will never be null, as they were removed in the RemoveAll() method
And otherwise, it re-equip’s the weapon? Why??

The Fighter component being active or not is irrelevant to the Third Person course, so we need a completely different approach anyways…

The most sensible approach is to have an IsHostile variable on the EnemyStateMachine… we discussed this in another thread.

So first, I would change things to be List<EnemyStateMachine> stateMachines = new(); instead of tracking Fighters. Fighters don’t Update anymore and aren’t in the aggro equation anymore.

public void Activate(bool shouldActivate)
{
    stateMachines.RemoveAll(stateMachine=>stateMachine==null || stateMachine.IsDestroyed());
    foreach(EnemyStateMachine machine in stateMachines)
    {
         machine.SetHostile(shouldActivate);
    }
}

Now, before you remind me about your respawnables, you’ll have to change the RespawnManager to assign the enemy’s EnemyStateMachine to the stateMachines rather than the Fighter to the fighters. You can do this, I have confidence in you.

Now… if you want the aggroGroup to go aggro if the Player hits them, all you have to do, once again in AggroGroup is subscribe to each enemy’s Health’s OnTakenHit, either in Awake() or when your RespawnManager adds the enemy to the Aggrogroup… Add this method:

void OnTakenHit()
{
    Activate(true);
}

No need to unsubscribe because of the direction of the AggroGroup → Health relationship (the AggroGroup never dies, the Health does die)

lol I’m lowkey now scared to tell you that I was slowly finding my way around it. I literally just finished coding a properly working function that debugs when the enemy is hit, and I was going to introduce the mechanics in that to get the other enemies in the group to start chasing the player down

By all means, I’ll go through your solution. If you’re interested in mine though, here’s what I came up with so far (and I used ChatGPT for help. I know it’s wrong, but I would’ve struggled terribly otherwise (and it refreshed my memory on what a lambda expression is, so that’s a bonus :stuck_out_tongue:)):

Here is my ‘Activate()’ so far:

        public void Activate(bool shouldActivate)
        {
            // Remove nullified or destroyed fighters:
            fighters.RemoveAll(fighter => fighter == null || fighter.IsDestroyed());

            foreach (Fighter fighter in fighters) 
            {
                if (fighter == null) RemoveFighterFromGroup(fighter);

                else
                {
                    WeaponConfig enemyWeaponConfig = fighter.currentWeaponConfig;
                    fighter.AttachWeapon(enemyWeaponConfig);
                }

                // if the fighter is an enemy, and his recent health took a hit, aggregate others in the group:
                // NOTE: ONLY USE "CompareTag" HERE, BECAUSE FindWithTag 
                // WILL ONLY RETURN THE FIRST ENEMY, NOT EVERYONE INVOLVED, WHICH IS NOT WHAT WE WANT!
                if (fighter.CompareTag("Enemy")) 
                {
                    Health fighterHealth = fighter.GetComponent<Health>();

                    if (fighterHealth != null) 
                    {
                        fighterHealth.OnTakenHit += OnTakenHitEventHandler;
                        // when the enemy dies, or his health is nullified, unsubscribe from the event:
                        fighterHealth.onDie.AddListener((/* no parameters for this lambda expression, just the content function of this lambda expression after the arrow */) => OnFighterDeath(fighterHealth));
                    }
                }
            }
        }

and here are my new two functions that subscribed to the event handlers:

        void OnTakenHitEventHandler(GameObject instigator)
        {
            Debug.Log("Enemy was hit by: " + instigator.name); // so far, this one works
        }

        void OnFighterDeath(Health health) 
        {
            health.OnTakenHit -= OnTakenHitEventHandler;
        }

Anyway, I’ll go through your solution as well and decide on the next step :slight_smile:

That’s an interesting question… I don’t know what weird bug do I have in my project, but if I don’t do that, my enemy’s weapon will never ever show until he swings his first swing against me, and boom all of a sudden the player just discovered the enemy had an invisible weapon. Surprises would be nice, but that’s not the type of surprise that I want

In fact, the exact same problem happens when the enemy respawns, so I added this (Incomplete) block as well under “RespawnManager.Respawn()”, right after we re-instantiate the enemy:

            // (TEST BLOCK BELOW, ADDED BY BAHAA INDIVIDUALLY - SUCCESS):
            // If the enemy has a weapon that's supposed to be in his hand, make him wear it:
            if (spawnedEnemy.GetComponent<Fighter>().GetCurrentWeaponConfig() != null) 
            {
                WeaponConfig enemyWeaponConfig = spawnedEnemy.GetComponent<Fighter>().currentWeaponConfig;
                spawnedEnemy.GetComponent<Fighter>().AttachWeapon(enemyWeaponConfig);
            } // write an else here to wear the "Unarmed" (i.e: default boxing hands) if it's null, to avoid weird glitches

This fixes my invisible sword problem

good memory :laughing:

Anyway, I’ll go ahead and probably delete what I was doing in favor of yours :stuck_out_tongue:

Update: Something is COMPLETELY WRONG… The enemies don’t patrol after the update, they don’t fight back, they can’t be pushed around… they’re basically statues again :sweat_smile:

No debugs, here’s the changes I did:

// Fighters to StateMachines:
        // We're Dealing with State Machines Now:
        [SerializeField] List<EnemyStateMachine> enemies = new List<EnemyStateMachine>();

// Complete overhaul of my 'Activate' function:

        public void Activate(bool shouldActivate)
        {
            // Remove nullified or destroyed fighters:
            enemies.RemoveAll(enemy => enemy == null || enemy.IsDestroyed());

            foreach (EnemyStateMachine enemy in enemies) 
            {
                enemy.SetHostile(shouldActivate);
            }
        }

// AddFighterToGroup:

        public void AddFighterToGroup(EnemyStateMachine enemy) {

            // If you got the fighter you want to add on your list, return:
            if (enemies.Contains(enemy)) return;
            // For other fighters on the list, add them lah:
            enemies.Add(enemy);
            enemy.enabled = isActivated;
            enemy.GetComponent<Health>().OnTakenHit += OnTakenHit; // NEW LINE
            if (enemy.TryGetComponent(out CombatTarget target)) target.enabled = isActivated;

        }

// OnTakenHit:

        void OnTakenHit(GameObject instigator)
        {
            Activate(true);
        }

and in ‘EnemyStateMachine.cs’:

    public void SetHostile(bool isHostile) 
    {
        this.IsHostile = isHostile;
    }

and then in ‘RespawnManager.cs’, I changed it all from Fighter to just the state machine

In the meanwhile I’ll go give my previous solution a full attempt and see what I can come up with and update this comment accordingly :slight_smile:

Not a clue… none of this should affect patrolling at all…

IsHostile does need to be utilized, of course, to decide whether to chase or not. We discussed that in the other thread.

I must off to bed be.

no worries, let’s continue this tomorrow. I’m still prototyping with my own solution as we speak :slight_smile: - let’s see if I can pull this off, xD

Edit: It got significantly worse… somehow, the “AggroGroup.cs” logic, I’m not sure where I went terribly wrong, gets the player to be invincible to each enemy in the AggroGroup once they killed him ONCE. So when he respawns, and he meets the enemy that killed him, he’s invincible to that enemy now:

using System.Collections.Generic;
using UnityEngine;
using Unity.VisualScripting;
using RPG.Attributes;
using UnityEngine.AI;
using RPG.States.Enemies;

namespace RPG.Combat {

    public class AggroGroup : MonoBehaviour
    {

        [SerializeField] List<Fighter> fighters = new List<Fighter>();    // fighters to aggregate when our player takes a wrong dialogue turn (and pisses everyone off)

        [SerializeField] bool activateOnStart = false; // make the group aggressive when the game starts
        [SerializeField] bool resetIfPlayerDies = true; // this is left as a boolean because it allows us to choose which aggroGroup members reset their settings on players' death, and which groups may not reset themselves on death (depending on your games' difficulty)

        private bool isActivated;   // Activates/deactivates the 'aggroGroup'
        private bool playerActivated;   // this boolean ensures that the group does not give up on trying to fight the player, if the main character dies, ensuring that they still fight him, even without the leader
        private Health playerHealth;

        private void Start() {

            // Ensures guards are not active to fight you, if you didn't trigger them:
            Activate(activateOnStart);
            
        }

        private void OnEnable() {

            if (resetIfPlayerDies) {

                playerHealth = GameObject.FindGameObjectWithTag("Player").GetComponent<Health>();
                if (playerHealth != null) playerHealth.onDie.AddListener(ResetGroupMembers);

            }

        }

        public void OnDisable() {

            if (resetIfPlayerDies) {

                Health playerHealth = GameObject.FindGameObjectWithTag("Player").GetComponent<Health>();
                if (playerHealth != null) playerHealth.onDie.RemoveListener(ResetGroupMembers);

            }

        }

        public void ResetGroupMembers() {

            foreach (Fighter fighter in fighters) 
            {
                if (fighter != null && fighter.GetComponent<Health>() != null) 
                {
                    fighter.GetComponent<Health>().OnTakenHit -= OnTakenHitEventHandler;
                }
            }

            Activate(false);
            Debug.Log("AggroGroup Deactivated");
        }

        public void Activate(bool shouldActivate)
        {
            // Remove nullified or destroyed fighters:
            fighters.RemoveAll(fighter => fighter == null || fighter.IsDestroyed());

            foreach (Fighter fighter in fighters)
            {

                WeaponConfig enemyWeaponConfig = fighter.currentWeaponConfig;
                fighter.AttachWeapon(enemyWeaponConfig);

                // if the fighter is an enemy, and his recent health took a hit, aggregate others in the group:
                // NOTE: ONLY USE "CompareTag" HERE, BECAUSE FindWithTag 
                // WILL ONLY RETURN THE FIRST ENEMY, NOT EVERYONE INVOLVED, WHICH IS NOT WHAT WE WANT!
                if (fighter.CompareTag("Enemy"))
                {
                    Health fighterHealth = fighter.GetComponent<Health>();

                    if (fighterHealth != null)
                    {
                        fighterHealth.OnTakenHit += OnTakenHitEventHandler;
                        // when the enemy dies, or his health is nullified, unsubscribe from the event:
                        fighterHealth.onDie.AddListener((/* no parameters for this lambda expression, just the content function of this lambda expression after the arrow */) => OnFighterDeath(fighterHealth));
                    }
                Debug.Log("AggroGroup Activated");
                }
            }
        }

        public void AddFighterToGroup(Fighter fighter) {

            // If you got the fighter you want to add on your list, return:
            if (fighters.Contains(fighter)) return;
            // For other fighters on the list, add them lah:
            fighters.Add(fighter);
            fighter.enabled = isActivated;
            fighter.GetComponent<Health>().OnTakenHit += OnTakenHitEventHandler; // NEW LINE
            if (fighter.TryGetComponent(out CombatTarget target)) target.enabled = isActivated;

        }

        public void RemoveFighterFromGroup(Fighter fighter) {

            // Remove fighters from the list
            fighters.Remove(fighter);

        }

        void OnTakenHitEventHandler(GameObject instigator)
        {
            Debug.Log("Enemy was hit by: " + instigator.name); // so far, this one works

            // Hit registered, now get everyone in the group to chase the player down:
            if (instigator.CompareTag("Player")) 
            {
                foreach (Fighter fighter in fighters) 
                {
                    if (fighter.CompareTag("Enemy")) 
                    {
                        EnemyStateMachine stateMachine = fighter.GetComponent<EnemyStateMachine>();
                        stateMachine.SwitchState(new EnemyChasingState(stateMachine));
                    }
                }
            }
        }

        void OnFighterDeath(Health health)
        {
            health.OnTakenHit -= OnTakenHitEventHandler;
        }

    }

}

And… the AggroGroup members still won’t chase him down if he hit one of their friends :sweat_smile:

I’ll just leave this here (I know it’s from this script because the glitch is good as gone for anyone not in an AggroGroup), in case you or @bixarrio would like to have a look - this is what I can come up with before my brain got fried…

Edit
Nevermind any of this, there’s been a lot happening since I started writing this post and much of it is now pointless


You’re disabling the navMesh when enemies should not attack, so they won’t patrol.


The AggroGroup from the course should work just fine. Not sure why you have so much other stuff here. Looks like the only things you have extra is the ability to add enemies at runtime, the ability to stay aggro through player respawns and something about showing the weapon…

To simplify adding an enemy at runtime, I’d just move the code from (the original) Activate() into a ‘single enemy’ function. You’d also keep track of the current active state (as you’ve done)

public void Activate(bool shouldActivate)
{
    fighters.RemoveAll(f => f == null);

    foreach (Fighter fighter in fighters)
    {
        ActivateFighter(fighter, shouldActivate);
    }
    isActive = shouldActivate; // keep track of current state
}

private void ActivateFighter(Fighter fighter, bool shouldActivate)
{
    CombatTarget target = fighter.GetComponent<CombatTarget>();
    if (target != null)
    {
        target.enabled = shouldActivate;
    }
    fighter.enabled = shouldActivate;
}

Then, when you add an enemy, just activate them with the new function

public void AddFighter(Fighter fighter)
{
    if (fighters.Contains(fighter)) return;
    fighters.Add(fighter);
    ActivateFighter(fighter, isActive);
}

And when you remove a fighter, you’d probably need to deactivate that fighter

public void RemoveFighter(Fighter fighter)
{
    // Can't properly deactivate a 'null' fighter
    if (fighter == null) return;
    ActivateFighter(fighter, false);
    fighters.Remove(fighter);
}

I reversed literally everything and decided to start from scratch, just to follow along with what you’re saying :slight_smile: - I’ll test it and comment after that the update

and… nope, didn’t work :sweat_smile: - if it helps, this is the updated script:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using RPG.Movement;
using Unity.VisualScripting;
using RPG.Attributes;

namespace RPG.Combat
{

    public class AggroGroup : MonoBehaviour
    {

        [SerializeField] List<Fighter> fighters = new List<Fighter>();    // fighters to aggregate when our player takes a wrong dialogue turn (and pisses everyone off)
        [SerializeField] bool activateOnStart = false;
        [SerializeField] bool resetIfPlayerDies = true; // this is left as a boolean because it allows us to choose which aggroGroup members reset their settings on players' death, and which groups may not reset themselves on death (depending on your games' difficulty)

        private bool isActivated;   // Activates/deactivates the 'aggroGroup'
        private bool playerActivated;   // this boolean ensures that the group does not give up on trying to fight the player, if the main character dies, ensuring that they still fight him, even without the leader
        private Health playerHealth;


        private void Start()
        {

            // Ensures guards are not active to fight you, if you didn't trigger them:
            Activate(activateOnStart);

        }

        private void OnEnable()
        {

            if (resetIfPlayerDies)
            {

                playerHealth = GameObject.FindGameObjectWithTag("Player").GetComponent<Health>();
                if (playerHealth != null) playerHealth.onDie.AddListener(ResetGroupMembers);

            }

        }

        public void OnDisable()
        {

            if (resetIfPlayerDies)
            {

                Health playerHealth = GameObject.FindGameObjectWithTag("Player").GetComponent<Health>();
                if (playerHealth != null) playerHealth.onDie.RemoveListener(ResetGroupMembers);

            }

        }

        public void ResetGroupMembers()
        {
            Activate(false);
        }

        public void Activate(bool shouldActivate) 
        {
            fighters.RemoveAll(fighter => fighter == null || fighter.IsDestroyed());

            foreach (Fighter fighter in fighters) 
            {
                ActivateFighter(fighter, shouldActivate);
            }

            isActivated = shouldActivate;

        }

        private void ActivateFighter(Fighter fighter, bool shouldActivate) 
        {

            CombatTarget target = fighter.GetComponent<CombatTarget>();
            if (target != null) 
            {
                target.enabled = shouldActivate;
            }

            fighter.enabled = shouldActivate;

        }

        public void AddFighterToGroup(Fighter fighter)
        {

            // If you got the fighter you want to add on your list, return:
            if (fighters.Contains(fighter)) return;
            // For other fighters on the list, add them lah:
            fighters.Add(fighter);
            ActivateFighter(fighter, isActivated);
        }

        public void RemoveFighterFromGroup(Fighter fighter)
        {

            // Remove fighters from the list
            if (fighter == null) return;
            ActivateFighter(fighter, false);
            fighters.Remove(fighter);

        }

    }

}

It won’t work, hence the ‘edit’ note.

like… zero chance? :sweat_smile:

(At least the bug of invincibility of the player after death is gone with that reverse…)

Anyway, I’m back to square one with the AggroGroup… all I need is a way for the other NPCs to chase me down if I attack one of their friends

Yes. Like @Brian_Trotter said:

so you need to stop using it

fair enough. I’ll reverse to his solution then when he returns… :slight_smile: (Sorry I was incredibly tired, so I went into deep sleep and literally just woke up)

oh well… I re-followed Brian’s instructions as best as I could, and I’m back to “my enemies are frozen statues” state again, when they’re under the AggroGroup. If any of you both would be kind enough to help me investigate what went wrong, because there’s literally no debuggers coming out of this one, that would be awesome. Here’s my current code:

// AggroGroup.cs:

using System.Collections.Generic;
using UnityEngine;
using Unity.VisualScripting;
using RPG.Attributes;
using RPG.States.Enemies;

namespace RPG.Combat
{

    public class AggroGroup : MonoBehaviour
    {

        [SerializeField] List<EnemyStateMachine> enemies = new List<EnemyStateMachine>();
        [SerializeField] bool activateOnStart = false;
        [SerializeField] bool resetIfPlayerDies = true; // this is left as a boolean because it allows us to choose which aggroGroup members reset their settings on players' death, and which groups may not reset themselves on death (depending on your games' difficulty)

        private bool isActivated;   // Activates/deactivates the 'aggroGroup'
        private Health playerHealth;


        private void Start()
        {

            // Ensures guards are not active to fight you, if you didn't trigger them:
            Activate(activateOnStart);

        }

        private void OnEnable()
        {

            if (resetIfPlayerDies)
            {

                playerHealth = GameObject.FindGameObjectWithTag("Player").GetComponent<Health>();
                if (playerHealth != null) playerHealth.onDie.AddListener(ResetGroupMembers);

            }

        }

        public void OnDisable()
        {

            if (resetIfPlayerDies)
            {

                Health playerHealth = GameObject.FindGameObjectWithTag("Player").GetComponent<Health>();
                if (playerHealth != null) playerHealth.onDie.RemoveListener(ResetGroupMembers);

            }

        }

        public void ResetGroupMembers()
        {
            Activate(false);
        }

        public void Activate(bool shouldActivate) 
        {
            enemies.RemoveAll(enemy => enemy == null || enemy.IsDestroyed());

            foreach (EnemyStateMachine enemy in enemies) 
            {
                enemy.SetHostile(shouldActivate);
            }
        }

        public void AddFighterToGroup(EnemyStateMachine enemy)
        {
            // If you got the fighter you want to add on your list, return:
            if (enemies.Contains(enemy)) return;
            // For other fighters on the list, add them lah:
            enemies.Add(enemy);
            enemy.enabled = isActivated;
            enemy.GetComponent<Health>().OnTakenHit += OnTakenHit; // NEW LINE
            if (enemy.TryGetComponent(out CombatTarget target)) target.enabled = isActivated;
        }

        public void RemoveFighterFromGroup(EnemyStateMachine enemy)
        {
            // Remove fighters from the list
            if (enemy == null) return;
            enemies.Remove(enemy);
        }

        void OnTakenHit(GameObject instigator) 
        {
            Activate(true);
        }

    }

}

RespawnManager.cs, after the changes:

using System.Collections.Generic;
using GameDevTV.Saving;
using RPG.Control;
using UnityEngine;
using RPG.Combat;
using Newtonsoft.Json.Linq;
using RPG.States.Enemies;
using System.Collections;
using System;

namespace RPG.Respawnables
{
    [RequireComponent(typeof(JSONSaveableEntity))]
    public class RespawnManager : MonoBehaviour, IJsonSaveable
    {
        [SerializeField] EnemyStateMachine spawnableEnemy;  // prefab of the enemy (was 'AIController.cs' type previously)

        [SerializeField] private float hideTime;   // time before hiding our dead character
        [SerializeField] private float respawnTime;    // time before respawning our hidden dead character, as another alive, respawned character
        [SerializeField] PatrolPath patrolPath; // the path our character will follow, from his spawn point
        [SerializeField] AggroGroup aggroGroup; // aggrevated group of guards, based on wrong dialogue player has said
        [SerializeField] bool hasBeenRestored;  // checks if the enemy has been restored before fading in from the main menu, or a load scene, or not

        // private AIController spawnedEnemy; // changed to EnemyStateMachine type below
        // private EnemyStateMachine spawnedEnemy; // in-game instance of the enemy

        // TEST (TimeKeeper)
        private double destroyedTime;
        private TimeKeeper timeKeeper;

        // --------------------------- NOTE: RestoreState() occurs BEFORE Start(), hence we need to change Start() to Awake() --------------
        private void Awake()
        {
            // TEST
            timeKeeper = TimeKeeper.GetTimeKeeper();

            // Check if the Enemy has been restored first or not, prior to Respawning him (ensuring RestoreState(), which occurs first, works properly)
            if (!hasBeenRestored) Respawn();
        }
        // --------------------------------------------------------------------------------------------------------------------------------

        private void Respawn()
        {
            var spawnedEnemy = GetSpawnedEnemy();
            if (spawnedEnemy)
            {
                // Dude is not dead no longer, so delete his previous 'onDeath' record after he's respawned
                spawnedEnemy.Health.onDie.RemoveListener(OnDeath);
            }

            foreach (Transform child in transform)
            {
                // Start the Respawn by deleting any existing gameObjects
                Destroy(child.gameObject);
            }

            // Respawn the enemy, and parent the enemy to our respawnManagers' transform
            spawnedEnemy = Instantiate(spawnableEnemy, transform);

            // (TEST BLOCK BELOW, ADDED BY BAHAA INDIVIDUALLY - SUCCESS):
            // If the enemy has a weapon that's supposed to be in his hand, make him wear it:
            if (spawnedEnemy.GetComponent<Fighter>().GetCurrentWeaponConfig() != null) 
            {
                WeaponConfig enemyWeaponConfig = spawnedEnemy.GetComponent<Fighter>().currentWeaponConfig;
                spawnedEnemy.GetComponent<Fighter>().AttachWeapon(enemyWeaponConfig);
            } // write an else here to wear the "Unarmed" (i.e: default boxing hands) if it's null, to avoid weird glitches

            // Get the spawned/respawned enemies' health, and listen for death notifications
            spawnedEnemy.Health.onDie.AddListener(OnDeath);

            if (patrolPath != null)
            {
                Debug.Log($"Assigning Patrol Path {patrolPath} to {spawnedEnemy.name}");
                // spawnedEnemy.AssignPatrolPath(patrolPath); // Swapped to state machine on next line
                spawnedEnemy.AssignPatrolPath(patrolPath);
                spawnedEnemy.SwitchState(new EnemyPatrolState(spawnedEnemy));
            }
            else
            {
                Debug.Log($"No Patrol Path to assign");
            }
            // --------------------------- Extra Functionality: Setting up Aggro Group + Adding Fighters ---------------
            if (aggroGroup != null)
            {
                aggroGroup.AddFighterToGroup(spawnedEnemy);
                if (spawnedEnemy.TryGetComponent(out DialogueAggro dialogueAggro)) //aggrogroup is at this point valid
                {
                    dialogueAggro.SetAggroGroup(aggroGroup);
                }
            }
            // ---------------------------------------------------------------------------------------------------------
        }

        private IEnumerator HideCharacter() 
        {
            var spawnedEnemy = GetSpawnedEnemy();
            if (spawnedEnemy == null) yield break;
            spawnedEnemy.transform.SetParent(null);
            yield return new WaitForSecondsRealtime(hideTime);
            Destroy(spawnedEnemy.gameObject);
        }

        void OnDeath()
        {
            var spawnedEnemy = GetSpawnedEnemy();
            spawnedEnemy.Health.onDie.RemoveListener(OnDeath);

            StartCoroutine(HideCharacter());

            destroyedTime = timeKeeper.GetGlobalTime();
            StartCoroutine(WaitAndRespawn());

            if (aggroGroup != null)
            {
                aggroGroup.RemoveFighterFromGroup(spawnedEnemy);
            }
        }

        private IEnumerator WaitAndRespawn()
        {
            var elapsedTime = (float)(timeKeeper.GetGlobalTime() - destroyedTime);
            yield return new WaitForSecondsRealtime(respawnTime - elapsedTime);
            Respawn();
        }

        private bool IsDead()
        {
            var spawnedEnemy = GetSpawnedEnemy();
            return spawnedEnemy == null || spawnedEnemy.Health.IsDead();
        }

        private EnemyStateMachine GetSpawnedEnemy()
        {
            return GetComponentInChildren<EnemyStateMachine>();
        }

        public JToken CaptureAsJToken()
        {
            JObject state = new JObject();
            IDictionary<string, JToken> stateDict = state;

            // TEST (Adding data to the JObject Dictionary):
            var isDead = IsDead();
            var data = new RespawnData(destroyedTime, isDead);
            stateDict["RespawnData"] = JToken.FromObject(data);

            // we only care about data of alive enemies
            if (!isDead)
            {
                var spawnedEnemy = GetSpawnedEnemy();
                foreach (IJsonSaveable JSONSaveable in spawnedEnemy.GetComponents<IJsonSaveable>())
                {
                    JToken token = JSONSaveable.CaptureAsJToken();
                    string component = JSONSaveable.GetType().ToString();
                    Debug.Log($"{name} Capture {component} = {token.ToString()}");
                    stateDict[component] = token;
                }
            }
            return state;
        }

        public void RestoreFromJToken(JToken s)
        {
            
            JObject state = s.ToObject<JObject>();
            IDictionary<string, JToken> stateDict = state;

            var data = default(RespawnData);
            if (stateDict.TryGetValue("RespawnData", out var dataToken))
            {
                data = dataToken.ToObject<RespawnData>();
            }

            var isDead = data.IsDead;
            destroyedTime = data.DestroyedTime;

            // Should be dead
            if (isDead && !IsDead())
            {
                Debug.Log("Should be dead, but isn't...");
                var spawnedEnemy = GetSpawnedEnemy();
                Debug.Log($"Listeners before: {spawnedEnemy.Health.onDie.GetPersistentEventCount()}");
                spawnedEnemy.Health.onDie.RemoveListener(OnDeath);
                Debug.Log($"Listeners after: {spawnedEnemy.Health.onDie.GetPersistentEventCount()}");
                Debug.Log($"Health Before: {spawnedEnemy.Health.GetHealthPoints()}");
                spawnedEnemy.Health.Kill();
                Debug.Log($"Health After: {spawnedEnemy.Health.GetHealthPoints()}");
                StartCoroutine(WaitAndRespawn());
                if (aggroGroup != null)
                {
                    aggroGroup.RemoveFighterFromGroup(spawnedEnemy);
                }
                StartCoroutine(HideCharacter());
                // HideCharacter();
                Debug.Log($"Spawned Enemy: {GetSpawnedEnemy()}");
            }
            else if (isDead && IsDead())
            {
                Debug.Log("Should be dead, and is indeed dead...");
                StopAllCoroutines();
                StartCoroutine(WaitAndRespawn());
                StartCoroutine(HideCharacter());
                // HideCharacter();
            }
            // Should be alive
            else if (!isDead && IsDead())
            {
                Debug.Log("Shouldn't be dead, but is dead...");
                Respawn();
                LoadEnemyState(stateDict);
            }
            else
            {
                Debug.Log("Shouldn't be dead, and isn't dead...");
                LoadEnemyState(stateDict);
            }
        }

        private void LoadEnemyState(IDictionary<string, JToken> stateDict)
        {
            var spawnedEnemy = GetSpawnedEnemy();
            foreach (IJsonSaveable jsonSaveable in spawnedEnemy.GetComponents<IJsonSaveable>())
            {
                string component = jsonSaveable.GetType().ToString();
                if (stateDict.ContainsKey(component))
                {
                    // NORMAL CODE (Don't delete if test failed):
                    Debug.Log($"{name} Restore {component} => {stateDict[component].ToString()}");
                    jsonSaveable.RestoreFromJToken(stateDict[component]);
                }
            }
        }
    }

    [Serializable]
    public struct RespawnData
    {
        public bool IsDead;
        public double DestroyedTime;

        public RespawnData(double destroyedTime, bool isDead)
        {
            IsDead = isDead;
            DestroyedTime = destroyedTime;
        }
    }
}

and the new “SetHostile()” function in ‘EnemyStateMachine.cs’:

    public void SetHostile(bool IsHostile) 
    {
        this.IsHostile = IsHostile;
    }

Yeah, your code can’t be the same anymore. You are now disabling the enemy’s state machine, so there’s no way they’ll be doing anything

Privacy & Terms