Single Instance vs Multiple Instances

Hello again @Brian_Trotter I have a bit of a funky problem

Recently, I started working on the Respawn Manager for my animals, so they can respawn after their death and all of that stuff, and here’s my current code:

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

namespace RPG.Animals.Respawnables 
{

[RequireComponent(typeof(JSONSaveableEntity))]
public class AnimalRespawnManager : MonoBehaviour, IJsonSaveable
{
    [SerializeField] AnimalStateMachine spawnableAnimal; // the animal to be respawned
    [SerializeField] private float hideTime; // how long will it take for this animal to hide before respawning
    [SerializeField] private float respawnTime; // how long will this animal take until it respawns
    [SerializeField] private PatrolPath patrolPath; // the patrol path for the respawned animal to follow
    // AggroGroup, if you're building that
    [SerializeField] bool hasBeenRestored; // has this animal been restored from a save, or do we need to restore it?

    // TimeKeeper
    private double destroyedTime;
    private TimeKeeper timeKeeper;

    private void Awake() 
    {
        // Time Keeper, to keep track of the in-game time
        timeKeeper = TimeKeeper.GetTimeKeeper();

        // Respawn the NPC, if it hasn't been respawned yet
        if (!hasBeenRestored) 
        {
            Respawn();
        }
    }

    private void Respawn() 
    {
        var spawnedAnimal = GetSpawnedAnimal();

        if (spawnedAnimal != null) 
        {
            spawnedAnimal.Health.onDie.RemoveListener(OnDeath);
        }

        foreach (Transform child in transform) 
        {
            // Destroy all components of the animal on Respawn
            Destroy(child.gameObject);
        }

        spawnedAnimal = Instantiate(spawnableAnimal, transform);

        spawnedAnimal.Health.onDie.AddListener(OnDeath);

        if (patrolPath != null) 
        {
            spawnedAnimal.AssignPatrolPath(patrolPath);
            // Add Hostility down the line, depending on the type of animal
            spawnedAnimal.SwitchState(new AnimalIdleState(spawnedAnimal));
        }
    }

    // No AggroGroups, skip that mechanic

    public float GetHideTime()
    {
        return hideTime;
    }

    private IEnumerator HideAnimal()
    {
        var spawnedAnimal = GetSpawnedAnimal();
        if (spawnedAnimal == null) yield break;

        yield return new WaitForSecondsRealtime(hideTime);
        if (spawnedAnimal != null)
        {
            Destroy(spawnedAnimal.gameObject);
        }
    }

    private void OnDeath() 
    {
        var spawnedAnimal = GetSpawnedAnimal();

        if (spawnedAnimal != null) 
        {
            spawnedAnimal.Health.onDie.RemoveListener(OnDeath);
            StartCoroutine(HideAnimal());
            destroyedTime = timeKeeper.GetGlobalTime();
            StartCoroutine(WaitAndRespawn());
            // No AggroGroup for the animals just yet
        }
    }

    // Animal is not an ally of the player yet, so skip this mechanic

    // Animal has it's own following state, so let this go too

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

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

    private AnimalStateMachine GetSpawnedAnimal() 
    {
        return GetComponentInChildren<AnimalStateMachine>();
    }

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

        var isDead = IsDead();
        var data = new RespawnData(destroyedTime, isDead);
        stateDict["RespawnData"] = JToken.FromObject(data);

        // No AggroGroup for animals just yet

        // No Conversations for animals

        // Save the data of the alive children of this script holder
        if (!isDead) 
        {
            var spawnedAnimal = GetSpawnedAnimal();
            foreach (IJsonSaveable JSONSaveable in spawnedAnimal.GetComponents<IJsonSaveable>()) 
            {
                JToken token = JSONSaveable.CaptureAsJToken();
                string component = JSONSaveable.GetType().ToString();
                stateDict[component] = token;
            }
        }

        // Animals are not allies of the player just yet

        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>();
        }

        // No AggroGroup for the animal just yet

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

        // No Conversations or AIConversants for animals

        // 'isPlayerAlly' setup, probably won't be there in the end

        // No Target Points, MalberS will be handling the following system

        // Death Checker:
        if (isDead && !IsDead()) 
        {
            Debug.Log($"Should be dead, but isn't");
            var spawnedAnimal = GetSpawnedAnimal();
            spawnedAnimal.Health.onDie.AddListener(OnDeath);
            spawnedAnimal.Health.Kill();
            StartCoroutine(WaitAndRespawn());
            // No AggroGroup yet
            StartCoroutine(HideAnimal());
        }
        else if (isDead && IsDead()) 
        {
            Debug.Log($"Should be dead, and is indeed dead");
            StopAllCoroutines();
            StartCoroutine(WaitAndRespawn());
            StartCoroutine(HideAnimal());
        }
        else if (!isDead && IsDead()) 
        {
            Debug.Log($"Should be alive, but is dead");
            Respawn();
            LoadEnemyState(stateDict);
        }
        else 
        {
            Debug.Log($"Should be alive, and is alive");
            LoadEnemyState(stateDict);
        }
    }

    private void LoadEnemyState(IDictionary<string, JToken> stateDict) 
    {
        var spawnedAnimal = GetSpawnedAnimal();
        foreach (IJsonSaveable jsonSaveable in spawnedAnimal.GetComponents<IJsonSaveable>()) 
        {
            string component = jsonSaveable.GetType().ToString();
            if (stateDict.ContainsKey(component)) 
            {
                jsonSaveable.RestoreFromJToken(stateDict[component]);
            }
        }
    }

    // No AggroGroup for the animal yet, so don't try to find it

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

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

But I have a major problem. Ever since this code got introduced, along with it’s architecture in the game (i.e: the Respawn Manager is an empty gameObject right above the animal, and it handles the death and re-instantiating of the animals), anywhere I go in the code, let’s say for death state for example, when the animal dies, I get this error:

NullReferenceException: Object reference not set to an instance of an object
AnimalDeathState.Enter () (at Assets/Project Backup/Scripts/State Machines/Animals/AnimalDeathState.cs:58)
RPG.States.StateMachine.SwitchState (RPG.States.State newState) (at Assets/Project Backup/Scripts/State Machines/StateMachine.cs:13)
AnimalStateMachine.<Start>b__122_0 () (at Assets/Project Backup/Scripts/State Machines/Animals/AnimalStateMachine.cs:86)
UnityEngine.Events.InvokableCall.Invoke () (at <ba783288ca164d3099898a8819fcec1c>:0)
UnityEngine.Events.UnityEvent.Invoke () (at <ba783288ca164d3099898a8819fcec1c>:0)
RPG.Attributes.Health.TakeDamage (UnityEngine.GameObject instigator, System.Single damage, RPG.Skills.Skill skill) (at Assets/Project Backup/Scripts/Attributes/Health.cs:182)
RPG.Combat.Fighter.TryHit (System.Int32 slot) (at Assets/Project Backup/Scripts/Combat/Fighter.cs:743)

Which leads to this line in ‘AnimalDeathState.Enter()’:

        // Solution to disable Malbers' MountTrigger.OnTriggerEnter()' when the animal is dead
        var animalDeathChecker = stateMachine.AnimalDeathChecker;
        if (animalDeathChecker != null && stateMachine.PlayerStateMachine.GetLastFailedMountAnimal().GetComponent<Animal>().GetUniqueID() == stateMachine.GetComponent<Animal>().GetUniqueID())
        {
            animalDeathChecker.SetIsThisAnimalDead(true);
            Debug.Log($"{stateMachine.gameObject.name} has been marked as dead");
        }

(The insanely long line is the troublesome one)

And let’s say the animal comes back from a save, and try mount an animal again, I get this error:

MissingReferenceException: The object of type 'AnimalStateMachine' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
UnityEngine.MonoBehaviour.StartCoroutine (System.Collections.IEnumerator routine) (at <ba783288ca164d3099898a8819fcec1c>:0)
AnimalPatrolState.DetermineChanceOfMountSuccess (System.Action`1[T] callback) (at Assets/Project Backup/Scripts/State Machines/Animals/AnimalPatrolState.cs:290)
EquestrianismEvents.InvokeOnSkillBasedMountAttempt (System.Action`1[T] callback) (at Assets/Project Backup/Scripts/MalbersAccess/EquestrianismEvents.cs:41)
MalbersAnimations.HAP.MRider.MountAnimal () (at Assets/Malbers Animations/Common/Scripts/Riding System/Rider/MRider.cs:432)
UnityEngine.Events.InvokableCall.Invoke () (at <ba783288ca164d3099898a8819fcec1c>:0)
UnityEngine.Events.UnityEvent.Invoke () (at <ba783288ca164d3099898a8819fcec1c>:0)
MalbersAnimations.InputRow.get_GetValue () (at Assets/Malbers Animations/Common/Scripts/Input/MInput.cs:622)
MalbersAnimations.MInput.SetInput () (at Assets/Malbers Animations/Common/Scripts/Input/MInput.cs:208)
MalbersAnimations.MalbersInput.SetInput () (at Assets/Malbers Animations/Common/Scripts/Input/MalbersInput.cs:138)
MalbersAnimations.MalbersInput.Update () (at Assets/Malbers Animations/Common/Scripts/Input/MalbersInput.cs:116)

Which leads to this block in ‘AnimalPatrolState.cs’:

    public void DetermineChanceOfMountSuccess(Action<bool> callback)
    {
        stateMachine.StartCoroutine(DelayedDetermineChanceOfMountSuccess(callback));
    }

    private IEnumerator DelayedDetermineChanceOfMountSuccess(Action<bool> callback)
    {
        yield return null;
        if (stateMachine.PlayerStateMachine.SkillStore.GetNearestAnimalStateMachine().GetComponent<Animal>().GetUniqueID() == stateMachine.GetComponent<Animal>().GetUniqueID())
        {
            Debug.Log($"DetermineChanceOfMountSuccess called from {stateMachine.gameObject.name} Patrol State");
            // If the animal was not mounted before, there's a chance of resistance
            var playerEquestrianismLevel = stateMachine.PlayerStateMachine.SkillStore.GetSkillLevel(Skill.Equestrianism);

            // Values between 0 and 1
            // Chance of successful mounting
            float successfulMountChance = Mathf.Clamp01(
                ((float)playerEquestrianismLevel - (float)stateMachine.MinimumEquestrianismLevelToMount) /
                ((float)stateMachine.MaximumMountResistanceEquestrianismLevel - (float)stateMachine.MinimumEquestrianismLevelToMount)
            );

            // Chance of random mounting
            float randomMountChance = UnityEngine.Random.value;

            Debug.Log($"player equestrianism level: {playerEquestrianismLevel}, minimum level: {stateMachine.MinimumEquestrianismLevelToMount}, max mount level: {stateMachine.MaximumMountResistanceEquestrianismLevel}, minimum level: {stateMachine.MinimumEquestrianismLevelToMount}, successful mount chance: {(float)((playerEquestrianismLevel - stateMachine.MinimumEquestrianismLevelToMount) / (stateMachine.MaximumMountResistanceEquestrianismLevel - stateMachine.MinimumEquestrianismLevelToMount))}, random mount chance: {randomMountChance}");

            if (randomMountChance <= successfulMountChance)
            {
                MalbersAnimations.InventorySystem.NotificationManager.Instance.OpenNotification($"Mounting Success:\nRandomChance: {randomMountChance}\nSuccess Chance: {successfulMountChance}");
                callback?.Invoke(true);
            }
            else
            {
                MalbersAnimations.InventorySystem.NotificationManager.Instance.OpenNotification($"Mounting Failed:\nRandom Chance: {randomMountChance}\nSuccess Chance: {successfulMountChance}");
                callback?.Invoke(false);
            }
        }
    }

with line 290 being the line in ‘DetermineChanceOfMountSuccess’ (THE FUNCTION, NOT THE IENUMERATOR).

This tells me that the respawn manager failed to address ‘AnimalStateMachine’ when respawning…?! I honestly don’t know

And that second problem then goes on for literally every single animal, which tells me it’s something to do with saving and restoring the animals in the scene

What seems to be the problem here? I know it’s something because death management for these animals was not done before, but I don’t know what it is

In other words, how do we handle destroying the data and re-instantiating them after resurrection, AND MAKE SURE THAT WE LOSE THE STATE MACHINE ON DEATH ONLY AFTER WE HAVE SET THE ‘IsThisAnimalDead’ TO TRUE, IN THE ‘AnimalDeathState.Enter()’?

(I’m not yelling, just trying to highlight something anyone can potentially accidentally miss out on, xD)

    public void DetermineChanceOfMountSuccess(Action<bool> callback)
    {
        if(!stateMachine) 
        {
             callback?Invoke(false);
             return;
        }
        stateMachine.StartCoroutine(DelayedDetermineChanceOfMountSuccess(callback));
    }

@Brian_Trotter And that just sent me on a coding fix spree, now I have A LOT of code to fix :sweat_smile: - but it did solve the core problem I was complaining about. I wish I knew about this earlier

For example:

    public static void InvokeOnLowLevelMount(Action<bool> callback)
    {
        OnLowLevelMount?.Invoke(callback);
    }

This is now forced upon me, which lead to other changes, like going from this:

                    EquestrianismEvents.InvokeOnLowLevelMount();

To this:

                    EquestrianismEvents.InvokeOnLowLevelMount(isMountSuccessful => {});

This line is in ‘MRider.cs’. For the time being, this one works. It doesn’t do anything, it just obeys the context laws so we don’t break a thousand things by accident

As a result, you got stuff like this all around the codebase (the example below is from ‘AnimalImpactState.cs’ - for documentation purposes):

// Enter():

        EquestrianismEvents.OnLowLevelMount += SwitchToChasingState;

// Exit():

        EquestrianismEvents.OnLowLevelMount -= SwitchToChasingState;


    public void SwitchToChasingState(Action<bool> callback)
    {
        if (!stateMachine) 
        {
            callback?.Invoke(false);
            return;
        }

        if (stateMachine.GetComponent<Animal>().GetUniqueID() == stateMachine.PlayerStateMachine.GetLastFailedMountAnimal().GetComponent<Animal>().GetUniqueID())
        {
            stateMachine.CooldownTokenManager.SetCooldown("AnimalAggro", stateMachine.CooldownTimer, true);
            stateMachine.SetLastAttacker(stateMachine.PlayerStateMachine.gameObject);
            stateMachine.SwitchState(new AnimalChasingState(stateMachine));
        }
    }

And now I probably have to do something like that as well, to this:

    // Similar to DodgeManager, this static class is carefully controlled from multiple scripts
    // to make sure the player can act accordingly, and perform other complex operations, based
    // on whether the player is mounting an animal or not
    public static bool isPlayerOnAnimalMount = false;

Otherwise this block in ‘AnimalChasingState.cs’ won’t work as expected:

        // Block the Mounted Animal from getting involved in Combat, to avoid
        // weird behaviour
        if (AnimalMountManager.isPlayerOnAnimalMount)
        {
            if (stateMachine.PlayerOnAnimalStateMachine.GetAnimalDelayed().GetUniqueID() == stateMachine.GetComponent<Animal>().GetUniqueID())
            {
                stateMachine.SwitchState(new AnimalIdleState(stateMachine));
                return;
            }
        }

Not to mention the death state has it’s own problems, which also need to be addressed:

        // Solution to disable Malbers' MountTrigger.OnTriggerEnter()' when the animal is dead
        var animalDeathChecker = stateMachine.AnimalDeathChecker;
        if (animalDeathChecker != null && stateMachine.PlayerStateMachine.GetLastFailedMountAnimal().GetComponent<Animal>().GetUniqueID() == stateMachine.GetComponent<Animal>().GetUniqueID())
        {
            animalDeathChecker.SetIsThisAnimalDead(true); // Block the Mount Triggers from working with dead animals
            Debug.Log($"{stateMachine.gameObject.name} has been marked as dead");
        }
        else 
        {
            Debug.Log($"Last Failed Mount Animal has not been found");
        }

So, for now, to keep it short and sweet, I need to deal with only two big problems:

  1. This block in ‘AnimalChasingState.cs’:
        // Block the Mounted Animal from getting involved in Combat, to avoid
        // weird behaviour
        if (AnimalMountManager.isPlayerOnAnimalMount) // <-- this is a static bool btw
        {
            if (stateMachine.PlayerOnAnimalStateMachine.GetAnimalDelayed().GetUniqueID() == stateMachine.GetComponent<Animal>().GetUniqueID())
            {
                stateMachine.SwitchState(new AnimalIdleState(stateMachine));
                return;
            }
        }

Which is not part of the same event system as the rest. In fact, it’s not part of any event subscription at all, so I can’t exactly do a ‘callback?.Invoke(false);’ here. It only exists to avoid the animals from going into AI Combat mode when they’re mounted, if they get hit with the player mounting them, without the permission of the player

and 2, this block in ‘AnimalDeathState.cs’:

        // Solution to disable Malbers' MountTrigger.OnTriggerEnter()' when the animal is dead
        var animalDeathChecker = stateMachine.AnimalDeathChecker;
        if (animalDeathChecker != null && stateMachine.PlayerStateMachine.GetLastFailedMountAnimal().GetComponent<Animal>().GetUniqueID() == stateMachine.GetComponent<Animal>().GetUniqueID())
        {
            animalDeathChecker.SetIsThisAnimalDead(true); // Block the Mount Triggers from working with dead animals
            Debug.Log($"{stateMachine.gameObject.name} has been marked as dead");
        }
        else 
        {
            Debug.Log($"Last Failed Mount Animal has not been found");
        }

Which, like the chasing problem above, does not belong to any event, so it can’t have a ‘callback?.Invoke(false);’ either, to my knowledge

These two are still troublesome lines of code for me. Other than that, I just made some significant changes to my code to adapt to this new Respawn Manager :slight_smile:

(and 3. Why exactly are we doing all these heavy modifications again? Apologies, I’m still rusty on that part. For all I know, this looks a little like scary code to me)

Apologies for bringing you into this… this is all really baffling to me :sweat_smile:

I would have the animal in a specific state when it’s being ridden… Since it’s being controlled by the player, it doesn’t make much sense for it to be in the Chasing state in the first place. AnimalMountedState or something like that. Even an attack would be separate, like an AnimalMountedAttackState.

What does AnimalDeathChecker represent, exactly?

When the animal is being ridden, it goes into idle state. Malbers’ controls take over in that state (and when it’s dismounted, an event call in the idle state sends it back to patrolling. I have a plan for something and this is part of it)

The whole idea for this one is to simply block the animal from being in chase state when it’s mounted

AnimalDeathChecker is linked to 'MountTriggers.'cs, a script from MalberS. This little code block essentially forces the mount trigger (permission to drive the animal) to be turned off if the animal is killed, so you don’t end up driving dead animals

For more context, this is ‘AnimalDeathChecker.cs’:

using UnityEngine;

namespace RPG.MalbersAccess

{

public class AnimalDeathChecker : MonoBehaviour

{

    [Tooltip("Is this Animal Dead?")]

    public bool isThisAnimalDead = false;

    public bool GetIsThisAnimalDead()

    {

        return isThisAnimalDead;

    }

    public void SetIsThisAnimalDead(bool isThisAnimalDead)

    {

        this.isThisAnimalDead = isThisAnimalDead;

    }

}

}

and this is how it’s used:

// FUNCTION ADDED BY BAHAA

        void OnTriggerStay()

        {

            // EXTRA CODE, ADDED BY BAHAA AS WELL - If the animal is dead, you can't mount it

            AnimalDeathChecker animalDeathChecker = GetComponentInParent<AnimalDeathChecker>();

            if (animalDeathChecker != null && animalDeathChecker.GetIsThisAnimalDead())

            {

                Debug.Log($"This animal is dead, you can't mount it");

                return;

            }

        }

So what is the Animal Chasing State code for, as it’s in neither of those states?

// FUNCTION ADDED BY BAHAA

        void OnTriggerStay()

        {

            // EXTRA CODE, ADDED BY BAHAA AS WELL - If the animal is dead, you can't mount it

            if (GetComponentInParent<Health>().IsDead)

            {

                Debug.Log($"This animal is dead, you can't mount it");

                return;

            }

        }

The animal chasing state is for the Animal AI. When it’s not mounted by the player, it has it’s own set of state machine rules to follow

When the animal is mounted, we give the controls back to MalberS, and the player switches to a ‘PlayerOnAnimalStateMachine’ that I created not too long ago. The rules are applied from there at that point in time

What I’m trying to do, is when it’s mounted, I want to break a few rules to make it convenient for the player

I’m pretty sure switching to AI Chasing state when the player is mounted would be really annoying to work with, xD

public class AnimalDeathChecker : MonoBehaviour

{
     public bool GetIsThisAnimalDead() => GetComponentInParent<Health>().IsDead;
}

OK I apologize if I’m being a bit of a nuisance tonight, but… this context isn’t exactly feasible for the death checker :sweat_smile: - mainly because that script is stored in a ‘MalbersAccess’ folder (A unique folder I created for mediators between our code, and MalberS code, with an assembly reference in it)

In other words, I literally can’t access ‘RPG.Attributes’ or anything from our RPG course in that script

Any other solution you can think of, please?

When the animal is mounted, everything it does (except Death state) should be in it’s own set of states, which don’t co-exist with the not-mounted state. In other words, it should never get to the chasing state in the first place from a Mounted state. The animal will know it’s mounted because it’s in a Mounted state.

AnimalIdleState is not a good name for a state where the animal is mounted because nobody else on your team of programmers can tell simply by looking at the name of the State() that this is what the purpose of the state is. For this, the State should be something that explicitly says what is happening, like AnimalIsControlledByMalbersState or AnimalControlledState or AnimalMountedState.

You don’t fall out of the AnimalMountedState into the AnimalChasingState.

Once the animal is no longer mounted… if you’re looking to give the player a chance to get away from the animal before it attacks you out of anger for your having the gall to write it, use the Cooldown manager to set an “IsPeaceful” cooldown, and states that would fall into chasing can check the “IsPeaceful” cooldown first.

In that case, hooking it up through OnDie’s UnityEvent would be better.

Then you can use the existing AnimalDeathChecker and just hook that up to OnDead with True.

The one we created very early on in the course, right? Because I really don’t want to be modifying our real onDie with a System.Action context that would really REALLY REALLY force a ton of changes :sweat_smile:

Thinking about it now, I 100% agree with you. It’ll be the next task once this is done, before we get to flying (tomorrow. Once this is done I’ll go back to sleep, xD)

It doesn’t have to… You can also have a simple OnDeath that’s a UnityEvent, and you call them both (I thought the RPG Course originally started with an OnDeath or OnDie UnityEvent, maybe it’s just the OnTakenDamage that was the UnityEvent.

public UnityEvent OnDeath;

It’s Invoked the same way as an event System.Action. You call it just for things that are hooked up in the inspector.

I think it’s best we do that through code, right? I have a feeling that the inspector would be a lot of unwanted labour work. I’ll give this a go and update you on how it goes, before returning to the chasing problem :smiley:

Here’s the beautiful thing about UnityEvents… they don’t give a hoot or a holler what Assembly the thing they’re hooking up is in… this makes them ideal for situations where the Malbers Assembly doesn’t have any access at all to the RPG assembly. Once you’ve set it up in the animal’s prefab, it’s done, and you don’t have to worry about it anymore.

Here’s something a little… umm… MIND-BLOWINGLY WRONG

I went to ‘AnimalDeathState.cs’ and created this function:

// in 'Enter':
stateMachine.Health.OnAnimalDeath += AcitvateAnimalDeathChecker;

// in 'Exit':
stateMachine.Health.OnAnimalDeath -= ActivateAnimalDeathChecker;

    private void ActivateAnimalDeathChecker(Action<bool> callback) 
    {
        if (!stateMachine) 
        {
            callback?.Invoke(false);
            return;
        }

        // Solution to disable Malbers' MountTrigger.OnTriggerEnter()' when the animal is dead
        var animalDeathChecker = stateMachine.AnimalDeathChecker;
        if (animalDeathChecker != null && stateMachine.PlayerStateMachine.GetLastFailedMountAnimal().GetComponent<Animal>().GetUniqueID() == stateMachine.GetComponent<Animal>().GetUniqueID())
        {
            animalDeathChecker.SetIsThisAnimalDead(true); // Block the Mount Triggers from working with dead
            Debug.Log($"{stateMachine.gameObject.name} has been marked as dead");
        }
        else
        {
            Debug.Log($"Last Failed Mount Animal has not been found");
        }
    }

and in ‘Health.cs’, I created this:

        [Tooltip("FOR ANIMALS ONLY")]
        public event System.Action<Action<bool>> OnAnimalDeath;

// in 'TakeDamage', when death happens:
        OnAnimalDeath?.Invoke(isAnimalDead => {}); // FOR ANIMALS ONLY, USED IN 'AnimalDeathState.cs' TO TOGGLE THEIR TRIGGER MOUNTS TO FALSE!

(I figured this would make things cleaner tbh)

NOW… When I mount an animal with this code in place, every single animal in the scene responds like I drove them for god knows what reason… Am I doing this right?

Who does Unity think itself it is, to not want to be tormented like the rest of us? :stuck_out_tongue:

I’m lost at this point as to what’s going on… as you don’t appear to be using statics here anymore… That makes it likely that it’s something in Malber’s code… was it intended for multiple animals? Once you’re sure you’re not telling these animals something via a static bus (event) or method somewhere on your end… you’ll need to get with Malbers and figure out why they’re all responding when only one should be.

I believe so

But I still don’t think it has anything to do with Malbers’ code, because 100% of the code working right now is mine. Probably something wrong from my end, but I’m not sure what it is because here’s the thing

If you put this block in ‘Enter()’, without any event subscriptions or a function of any sort:

        // Solution to disable Malbers' MountTrigger.OnTriggerEnter()' when the animal is dead
        var animalDeathChecker = stateMachine.AnimalDeathChecker;
        if (animalDeathChecker != null && stateMachine.PlayerStateMachine.GetLastFailedMountAnimal().GetComponent<Animal>().GetUniqueID() == stateMachine.GetComponent<Animal>().GetUniqueID())
        {
            callback?.Invoke(true);
            animalDeathChecker.SetIsThisAnimalDead(true); // Block the Mount Triggers from working with dead
            Debug.Log($"{stateMachine.gameObject.name} has been marked as dead");
        }
        else
        {
            Debug.Log($"Last Failed Mount Animal has not been found");
        }

It does not have this problem. It only complains when the animal dies because there’s no sort of return, which is my main problem ever since I introduced that Respawn Manager

Look for the word static throughout your code, especially when it comes to events.

Quite the search history for this word… but nothing I can automatically relate to this specific problem :sweat_smile:

Privacy & Terms