Abilties: Preventing premature consumption of timers, mana, and charges

You love to play stump the TA…
If noTargetsRequired is Checked, then data.GetTargets().Any() should never be called…

Hmmm… I ran this without noTargetsRequired and it gave me a null reference error on this line with data.GetTargets… or more to the point, since there was a filter, it showed the error in the filter, but the callback trace was on the if(noTargetRequired) line…

Working on a patch

Believe it or not, I’m not trying to stump you, and I do apologize if it came along this way. I just have an idea in mind I’d love to see come to life one day :slight_smile:

Hehe, the Stump the TA line is a bit of a jest.
Ok, so here’s the problem… If no targets were ever assigned, then the IEnumerable is technically null… this actually isn’t noticed in the case of FireSpray with the old paradigm because an IEnumerable is a bit like a query statement. It isn’t “real” until you do something with it, like iterate over it or use a Link method like .ToList(), .ToArray(), or in this case .Any()

So the solution is to null check the data.GetTargets() before actualizing it. Here’s my revised method:

        private void TargetAquired(AbilityData data)
        {
            if (data.IsCancelled()) return;
            foreach (var filterStrategy in filterStrategies)
            {
                if (data.GetTargets() == null || !data.GetTargets().Any()) continue;
                data.SetTargets(filterStrategy.Filter(data.GetTargets()));
            }

            if(noTargetRequired || (data.GetTargets()!=null && data.GetTargets().Any()))
            {
                var mana = data.GetUser().GetComponent<Mana>();
                if (!mana.UseMana(manaCost)) return;
                data.InvokeConsumedCallback();
                var cooldownStore = data.GetUser().GetComponent<CooldownStore>();
                cooldownStore.StartCooldown(this, cooldownTime);
                foreach (var effect in effectStrategies) effect.StartEffect(data, EffectFinished);
            }
        }

Any other fixes that might need to be done? Because surprisingly, it’s not working for me

Can you be more specific? New error?

No errors this time (thank you Brian), just that… in-game, it’s not working, like… at all, no response, nada :stuck_out_tongue_winking_eye:

Is noTargetRequired checked?

I forgot to turn that on… Yup, works now, apologies :slight_smile:

(I might still consider trying to filter my enemies though, because I don’t want players to abuse this ability xD)

For that, you’ll need to look for a target under the mouse with a raytrace or something similar… running the enemies filter or any filter doesn’t make any sense with directional targeting, because directional targeting returns no targets to filter.

Yup, that’s exactly what I want to do, I was hoping I can draw a Raycast under the mouse, and if you don’t spot something the Game says is a target you can fire at, this ability does not get induced

Can we also add the option to make it a homing ability? (Ok I’ll take it slow…)

Since you’re firing a projectile, homing is easy if you have a target…

So your challenge (as I go to bed and prepare for my day job) is to write a Targeting ability that checks to see if there is a CombatTarget under the mouse via raytrace, and if there is to set Targets with this CombatTarget’s gameobject in the IEnumerable.

Sure thing Brian, thanks again for spending the time today to help me out with my issues. I’ll give the Challenge a go, and update you on my code :slight_smile:

Hi Brian, I gave the code a shot, but it’s not working. I’m not exactly the best at this, but I wanted to give it a go anyway. Please have a look and let me know what changes can be done (I might edit this again if I can figure out how to make it work, or be better):

public void TargetUnderMouse() {

                // 1. Unleash a Raycast Under the Mouse
                var raycastUnderMouse = Camera.main.ScreenPointToRay(Input.mousePosition);

                // 2. Store the Raycast Information
                RaycastHit hit;
                bool hasHit = Physics.Raycast(raycastUnderMouse, out hit);
                var enemies = GameObject.FindGameObjectsWithTag("Enemy");
                AbilityData data;

                // 3. If the target under the mouse is an enemy, unleash the ability
                if (hasHit && enemies) {

                    foreach (var enemy in enemies) {

                        TargetAcquired(data);

                    }

                }

            }

Your enemies list will return every enemy in the game, whether it’s under the mouse at all…

What we need is an effect that returns all of the GameObjects under the mouse. We’ll let our FilterStrategies narrow down the collection.

UnderMouseTargeting.cs
using System;
using System.Collections.Generic;
using RPG.Control;
using UnityEngine;

namespace RPG.Abilities.Targeting
{
    [CreateAssetMenu(fileName = "UnderMouseTargeting", menuName = "Abilities/Targeting/Under Mouse Targeting", order = 0)]
    public class UnderMouseTargeting : TargetingStrategy
    {
        [SerializeField] private LayerMask layerMask;
        [SerializeField] private float groundOffset = 1;
        public override void StartTargeting(AbilityData data, Action finished)
        {
            data.SetTargets(GetGameObjectsUnderMouse());
            var ray = PlayerController.GetMouseRay();
            if (Physics.Raycast(ray, out RaycastHit raycastHit, 1000, layerMask))
                data.SetTargetedPoint(raycastHit.point + ray.direction * groundOffset / ray.direction.y);
            finished();
        }

        IEnumerable<GameObject> GetGameObjectsUnderMouse()
        {
            foreach (var hit in Physics.RaycastAll(PlayerController.GetMouseRay()))
            {
                yield return hit.transform.gameObject;
            }
        }
    }
}

This will return the GameObjects under the mouse, as well as return the same Targeted Point as the Directional Targeting (be sure to copy the settings from your DirectionalTargeting.

Now you should be able to turn off the NoTargetRequired boolean and replace the DirectionalTargeting with the UnderMouseTargeting.

Add an EnemyFilter and IsAliveFilter to the Filter Strategies.

Hi there,

I am not sure if I am still ok to reply here. I have been trying to implement this so that the health ability doesn’t get used unless I need it. But unfortunately I must be doing something wrong as I can use the health ability anytime regardless if I am at full health or not. I would ideally like to use the health ability only when im losing health.

I have altered my code to showcase this tutorial. I dont have any errors. When I add the filter strategy to the health potion ability it doesnt work if the no targets is false. If its true it “works” but still i can use it anytime. I was wondering if I could get some help please?

Appreciate your time and help.

I managed to fix it I think. I applied these changes to your suggested IsInJuredFilter.cs and took the bool off for this no target required for the healthPotion. This seems to have fixed the issue for this particular case. Thank you for the tutorial however. I wouldn’t have been able to attempt this without your help above. Thank you.

namespace RPG.Abilities.Filters
{
    [CreateAssetMenu(fileName = "Is Injured Filter", menuName = "Abilities/Filters/Is Injured Filter", order = 0)]
    public class IsInjuredFilter : FilterStrategy
    {
        public override IEnumerable<GameObject> Filter(IEnumerable<GameObject> objectsToFilter)
        {
            foreach (GameObject gameObject in objectsToFilter)
            {    
                    if (gameObject.TryGetComponent(out Health health) && health.GetHeatlh()< health.GetMaxHealth())
                    {
                        yield return gameObject;
                    }
            }
        }
    }
}

That filter looks perfect!

1 Like

Hi Brian,

I had another question please regarding quests and rewards please.

        public void CompleteObjective(string objective)
        {
            if (quest.HasObjective(objective) && !completedObjectives.Contains(objective))
            {
                completedObjectives.Add(objective);
            }
        } 

I implemented this in my CompleteObjective from QuestStatus. However the thing is even though I cant complete the objective the rewards still goes through so I get infinite rewards.
My aim with this quest is that I wish to get a character to ask my player character to show him his sword and if he does gets a reward. So the quest giver and completion is on the same NPC.

I think the problem lies here

        public void CompleteObjective(Quest quest, string objective)
        {
            QuestStatus status = GetQuestStatus(quest);
            status.CompleteObjective(objective);
            if (status.IsComplete())
            {
                GiveReward(quest);
            }

            if (onUpdate != null)
            {
                onUpdate();
            }
        }

However not sure how to get it so that if its completed once it doesnt offer the reward again?

Thank you for any help.

Hey there,

Sorry for the trouble I actually managed to fix this part without any added coding but by adding an extra condition to the dialog.
Thank you for all the help .

Well done, that is technically the correct method of handling this.

You could also add this line just before status.CompleteObjective(objective)

if(status.IsComplete()) return;
1 Like

Thank you again very helpful. This will probably work so that I dont need to do conditions for all the quests around my levels.
Thank you again.

Privacy & Terms