NavMeshAgent obstacle avoidance whilst Patrolling

And now I have a new problem. When the Player is killed by an animal, the player does not transition to death state for some reason, 100% of the time, and naturally no respawning either, and it’s driving me nuts as of why (the enemy does not have this problem. In other words, if the player gets killed by an NPC, there’s no problems with that. If the Player is killed by an animal, the bug happens)

(along with an ‘Animal does not chase down the NPC that attacked them, if it’s not the player’ problem, but I’ll get to that later)

I’m not sure if this is relevant or not, but here it goes anyway. For the Horse, I haven’t done any tests yet, but for the Raven, the attacks are done either through the beak of the bird, or it’s legs. I modified ‘Fighter.TryHit’ a little to adjust for accepting legs and beak, but I’m not 100% sure if that’s the reason of the failure of the death state of the player or not, so here’s the code. Would appreciate any sort of help @Brian_Trotter :slight_smile:

    // For the brand new Attack Array System (part of the Point-And-Click -> Third Person System implementation)
    // this function is called by animations to perform hits, based on the hand/foot/weapon, doing the hit:
    private void TryHit(int slot)
    {
        // if no current attack (or follow up) exists, return null:
        if (currentAttack == null) 
        {
            if (AnimalMountManager.isOnAnimalMount) 
            {
                // Assign the correct 'currentAttack' for the animal mount:
                currentAttack = GetCurrentWeaponConfig().OnAnimalAttacks[0];
            }
            else 
            {
                Debug.Log($"TryHit is called, but current attack is null");
                return;
            }
        }

        Debug.Log($"TryHit has been called");

        // radius of the damage of the weapon:
        float damageRadius = 0.5f;

        // To trigger this animation, the event in the animation takes the 1 and 2 values in 'Int' slot
        Vector3 transformPoint;
        switch (slot)
        {
            case 0: 
            transformPoint = currentWeapon.value.DamagePoint;   // weapon damage
            if (AnimalMountManager.isOnAnimalMount) // for cases when you need a larger hit radius whilst attacking by mounting
            {
               damageRadius = currentWeapon.value.DamageRadius * 2.0f; // you'll need a bigger radius to strike others whilst on the run on a mount
            }
            else damageRadius = currentWeapon.value.DamageRadius; // weapon damage radius
            break;
            case 1: transformPoint = rightHandTransform.position;   // for right-handed cases
            break;
            case 2: transformPoint = leftHandTransform.position;    // for left-handed cases
            break;
            case 3: 
            if (GetComponent<PlayerStateMachine>()) return; // the player's Parrying is handled in 'ShieldTriggerEnter.cs'
            if (GetCurrentShieldConfig() != null && GetComponent<EnemyStateMachine>() != null)
            {
                // use this call on the Animation Event Line on Shield Parrying animations
                Debug.Log($"{GetCurrentShieldConfig().name} has been found in shield hand");
                transformPoint = currentEquippedShield.value.DamagePoint;
                damageRadius = currentEquippedShield.value.DamageRadius;
            }
            else
            {
                transformPoint = rightHandTransform.position; // no shield? Just use your rightHandTransform
            }
            break;
            case 4: transformPoint = rightLegTransform.position;
            break;
            case 5: transformPoint = leftLegTransform.position;
            break;
            case 6:
            transformPoint = beakTransform.position; // The beak of the animal, a Raven for example or something (OPTIONAL)
            break;
            default: transformPoint = rightHandTransform.position;  // for cases when things make no sense
            break;
        }
        Debug.Log($"Attacking with slot {slot}, position {transformPoint}");

        // This list ensures that the health component of the player is accessed once only. This problem
        // came up when I introduced a second Capsule Collider on my player, so he can mount animals using
        // Malbers' Scripts (basically, 2 colliders on the player = 2x damage dealt to him... 
        // we needed to get rid of that by ensuring the health component is accessed once, through this list):
        List<Health> alreadyHit = new List<Health>();

            foreach (Collider other in Physics.OverlapSphere(transformPoint, damageRadius))
            {
                if (other.gameObject == gameObject) continue;   // don't hit yourself

                // Stop the player from hitting the animal he's currently mounting:
                if (AnimalMountManager.isOnAnimalMount && this.gameObject.CompareTag("Player"))
                {
                    var mountedAnimal = GetComponent<PlayerOnAnimalStateMachine>()?.GetAnimal();
                    if (other.gameObject == mountedAnimal.gameObject)
                    {
                        // If you got this far, it means the player is mounted to an animal, and he's
                        // trying to hit his mount with a melee weapon, which should be ignored!
                        continue;
                    }
                }

                if (other.TryGetComponent(out Health otherHealth) && !otherHealth.IsDead())
                {
                    // If one of the players' colliders (he has a Character Controller and a Capsule Collider
                    // on him) is hit, don't take damage again (fixes the 2 colliders-on-player-taking-damage problem):
                    if (alreadyHit.Contains(otherHealth)) continue;
                    // if you didn't take a hit yet, go ahead and do so now:
                    alreadyHit.Add(otherHealth);
                    
                    float damage = GetDamage();
                    damage *= currentAttack.DamageModifier;

                    // player/enemy
                    if (other.TryGetComponent(out BaseStats otherBaseStats))
                    {
                        float defence;
                        // if its the player (the only in-game character with a skillStore)
                        if (other.TryGetComponent(out SkillStore skillStore))
                        {
                            Debug.Log($"{other.gameObject.name} has a SkillStore");
                            defence = otherBaseStats.GetStatBySpecifiedLevel(Stat.Defence, skillStore.GetSkillLevel(Skill.Defence));
                        }
                        // the enemy
                        else 
                        {
                            if (other.GetComponent<Fighter>().GetCurrentShieldConfig() != null)
                            {
                                Debug.Log($"{other.gameObject.name} is an enemy with a shield");
                                defence = otherBaseStats.GetStat(Stat.Defence) + (other.GetComponent<Fighter>().GetCurrentShieldConfig().GetDefenseBonus() * (1 + other.GetComponent<Fighter>().GetCurrentShieldConfig().GetPercentageBonus()/100));
                            }
                            else
                            {
                                Debug.Log($"{other.gameObject.name} has no shield");
                                defence = otherBaseStats.GetStat(Stat.Defence);
                            }
                        }
                        damage /= 1 + defence/damage; // Replaced by a 'TEST' below

                        // Randomize the Damage:
                        // damage *= UnityEngine.Random.Range(0f, 1.25f);
                        // if (UnityEngine.Random.Range(0,100) > 95) damage *= 2.0f;
                    }

                    // if the player is invulnerable, ignore trying to damage him, and continue to the next cycle of damaging enemies in the radius, which was hurt by the sword:
                    if (other.CompareTag("Player") && other.GetComponent<Health>().GetInvulnerable())
                    {
                        continue; // later on, integrate logic here to accept a percentage of damage, before continuing to the next Foreach loop
                    }

                    // (TEMP - GetComponent<Health>().IsDead()) Temporarily here, to ensure ghost enemies deal no damage and mess with the NPC hunting for enemies system:
                    if (otherHealth.IsDead() || GetComponent<Health>().IsDead())
                    {
                        return;
                    }

                    otherHealth.TakeDamage(gameObject, damage, currentWeaponConfig.GetSkill());
                    TryApplyHitForce(other, transform.position);
                }
            }
        }

and a screenshot of the hierarchy of the Raven’s fighter.cs script (in case the differences in there are of any help):

Through changing the Physics layer collision matrix? You don’t. It applies to everything on that layer. What you need instead is check to see if the NPC is mounted on the animal before applying damage.

You might consider turning gravity off in the ForceReceiver (or outright ignoring the ForceReceiver’s Y component when in a Riding state… This is tricky, because you are trying to force the integration of two systems that were designed independently and use different theory of motion.

This makes no sense at all… as going to Death State (and eventually respawning) should be completely independent of the source of the damage).

The culprit behind this is likely something in your TakeDamage in Health… it’s registering the damage, but for some reason not calling OnDeath which is what the StateMachine uses to transition to the Death state.

For that one, surprise surprise, it’s because of code I wrote independently. The code was structured for Enemy State Machines, and the introduction of Animal State Machines has not been considered yet. Here’s the code block that’s responsible for this last bug (ahh… when I finally decide to have a look at the debugger):

            // if only the enemy hitting the player is left, get all available player Allies to aim for that enemy:
            if (instigator.GetComponent<EnemyStateMachine>().GetAggroGroup() != null && instigator.GetComponent<EnemyStateMachine>().GetAggroGroup().GetGroupMembers().Count == 1)
            {
                foreach (EnemyStateMachine playerAllyEnemy in this.GetAggroGroup().GetGroupMembers().Where(playerAllyEnemy => playerAllyEnemy != null && playerAllyEnemy.GetOpponent() == null && !playerAllyEnemy.GetHasOpponent()))
                {
                    if (playerAllyEnemy != instigator.GetComponent<EnemyStateMachine>()) // This if statement fixed a 3-month old 'Self-Attack' bug that's been bothering me because of how hard it was to replicate
                    {
                        playerAllyEnemy.SetHostile(true, instigator.gameObject);
                        playerAllyEnemy.SetHasOpponent(true);
                        playerAllyEnemy.SetOpponent(instigator.gameObject);
                    }
                }
            }

The ‘if’ statement line is the source of the bug. The code was written for Enemy State Machines, and has not been optimized for Animal State Machines just yet.

If you recall, this exact same block of code was also responsible once for my Player getting attacked by his allies, because they are told to attack whoever last hit the player (which, if he’s taking fall damage, he’s the one hurting himself, so now his allies are attacking him :rofl:). Unfortunately, ignoring the damage because an animal has done the damage right now won’t work for this scenario. I have to be smart about this!

Check for NPCs, got it!

Not every problem requires a complex solution. Some solutions are significantly easier than we expect, and cleaner as well. This is something I eventually learned due to my huge fear of heavily modifying code that doesn’t belong to me :smiley: - if I was solely programming this game, trust me this wouldn’t have ever happened. Manipulating me into complex and wrong solutions would’ve been extremely easy!

In the end, like I said, just get the characters and animals collision layer to ignore each other when the player mounts an animal, and that fixes a ton of problems!

I set the damage for falls to be null (and of course null check this before attempting to assign experience or in your case blame). QED.

Only if the player is the only one on the characters layer.

I completely forgot about that, and went ahead and assigned damage for falls with some complex code in ‘PlayerFallingState.cs’ (I don’t think you made a tutorial for that, but yeah… I created one and started making ways around problems for that). Right now it’s a rough algorithm, because it calculates based on addition rather than as a percentage of the total health, which, on the long term, will make a lot more sense

What’s QED?

I will eventually strengthen this part of the code. For the time being I’m still making sure animals can properly fight the player and other last attackers. Right now the goal is to almost eliminate the probability of the Raven having missed hits because of the lack of a collider, but also ensure that the animals don’t go crazy because of collider collision when they’re mounted


Anyway, here’s the solution to my if statement bug from above:

            // if only the enemy hitting the player is left, get all available player Allies to aim for that enemy:
            if (instigator.GetComponent<EnemyStateMachine>() != null /* <-- check for enemies first, you don't want animals involved in this */ && instigator.GetComponent<EnemyStateMachine>().GetAggroGroup() != null && instigator.GetComponent<EnemyStateMachine>().GetAggroGroup().GetGroupMembers().Count == 1)
            {
                foreach (EnemyStateMachine playerAllyEnemy in this.GetAggroGroup().GetGroupMembers().Where(playerAllyEnemy => playerAllyEnemy != null && playerAllyEnemy.GetOpponent() == null && !playerAllyEnemy.GetHasOpponent()))
                {
                    if (playerAllyEnemy != instigator.GetComponent<EnemyStateMachine>()) // This if statement fixed a 3-month old 'Self-Attack' bug that's been bothering me because of how hard it was to replicate
                    {
                        playerAllyEnemy.SetHostile(true, instigator.gameObject);
                        playerAllyEnemy.SetHasOpponent(true);
                        playerAllyEnemy.SetOpponent(instigator.gameObject);
                    }
                }
            }

(All I did was check that the enemy state machine was not null)

I didn’t, but there’s really no change from the Third Person State for Jumping, Falling, or Landing

It’s one of those old folk sayings… at least in England and America “Quite Easily Done”

Those mounted Malbers don’t seem worth the trouble to me.

First of all, good to hear from you again :smiley:

I fixed a ton of bugs with getting the damage when falling, but still… somehow some of them escaped my radar, and soon enough I will start investigating them again. For the time being I’m just working on the last step for equestrianism

My biggest (and honestly, only remaining bug) is that if you hit the right mouse button a lot when mounting an animal, the moment you dismount the animal, you take a significant ton of damage, and I still don’t know why

That one was a huge headache to integrate, because getting Malbers’ code and mine to communicate was nearly impossible. Again, Mediators saved the day, combined with smart tactics to get the correct information, and my recent setup of Unique Identifiers for the animals

But in the end, it all worked for the greater good (but the mathematical formula for reducing probability of failures will be quite hard to figure out, but I can fix it. xD)

I had to google it, it gave me a weird result, but yeah it’s good to know :slight_smile:

For that one, I managed to code the system to get them to ignore each other when mounted. I just need to introduce a small dismount delay, and ensure dismounting is only on the sides of the animal, because right now Malbers also has an option for “on-top-of” the animal, which has a small chance (but it exists) of burying an animal underground

The problem is, making flying states will be an absolute nightmare, because his systems are incredibly complex, but hopefully that can be worked out too (I’ll get to that on the proper forum link)

Honestly speaking though, you should see how lively the game got all of a sudden with interactive animals. Hands-down, it’s really worth the headache (the new Equestrianism skill is a game changer in my opinion. Players who want to fly a dragon now will really have a reason to grind, because they’ll want to blow up places with better animals, evade areas quicker, etc… depending on the strength of the animal!)

Privacy & Terms