Tracking kills without implementing the "Improved Conditions"

So I was following the topic about “Tracking kills for Quest” and wantet to implement the same for my game but the problem is, that I dont have implemented the Improved Conditions in my game. Is there a possability or easy way to achieve the Killtracker without implementing the Improved Conditionst? Cause he of course shooting errors, cause he cant find the EPredicate in the IPredicateEvaluator.

achievemenbt
predicate

It’s simple, really. The NPCs have a health component that triggers a UnityEvent when the NPC dies. If you just want to count kills, add a listener that will increment a counter every time the trigger is invoked

Yes, thats exactly what Brian also does on his DeathCounter Script. But of course I also want to have it as a predicate HasKilled. So I want to have a Quest where your Task for example is to kill 10 Bandits. My Question was just, if there is a workaround to implement the stuff Brian did without having the ImrpovedConditions.

https://community.gamedev.tv/t/tracking-kills-for-quests/194127/7

You would have to take Brian’s solution and dumb it down a bit.

First, you will need somewhere to keep track of all enemies that are killed. You can simply add a script to the player that holds this info, and add a way for the enemies to update this script (using the OnDie event of the Health component)

On the player, we can keep the death counter

public class DeathCounter : MonoBehaviour
{
    private Dictionary<string, int> counterDict = new Dictionary<string, int>();

        public void RegisterCounter(string victim)
        {
            if (!counterDict.ContainsKey(victim))
            {
                counterDict.Add(victim, 0);
            }
        }
        public void RecordDeath(string victim)
        {
            if (!counterDict.ContainsKey(victim))
            {
                return;
            }
            counterDict[victim]++;
        }
    public int GetDeathCount(string victim)
    {
        if (!counterDict.ContainsKey(victim)) return 0;
        return counterDict[victim];
    }
}

On the enemies, we need to add the death recorder

public class DeathRecorder : MonoBehaviour
{
    [SerializeField] string enemyIdentifier;
    public void RecordDeath()
    {
        var counter = GameObject.FindWithTag("Player").GetComponent<DeathCounter>();
        counter.RecordDeath(enemyIdentifier);
    }
}

And this goes on the enemies

Now, create a script that could be used to check the kills (I have no safety checks in here)

public class KillEvaluator : MonoBehaviour, IPredicateEvaluator
{
        [SerializeField] DeathCounter deathCounter;

        public bool? Evaluate(string predicate, string[] parameters)
        {
            if (predicate != "Has Killed") return null;

            var victim = parameters[0];
            var requiredKills = int.Parse(parameters[1]);
            deathCounter.RegisterCounter(victim);
            return deathCounter.GetDeathCount(victim) >= requiredKills;
        }
}

Put this, and the DeathCounter script on the player
image

and use it in your dialogues (My predicates are scriptable objects, but you get the gist)
image

NOTE I haven’t tested this properly yet, and my own dialogues are broken at the moment. Busy sorting it out so I can test this

1 Like

Wow!
I really have to say thank you for your help already bixarrio! That you put the time and effort to this and help me out with it. Well I already implemented everything you suggested me into my game. Problem is, that the predicate doesnt really work. He shows the dialogue node, even if I havent killed any bandit at all yet. So there must be a mistake in there still.

I just tested it and it works. The only problem is that the quest does not complete. The objective completes, but not the quest. I’ll look into that. Edit It does complete the quest, but by dialog only.

What does your node settings look like?

Well with the first Condition I look if he has the quest and then I am asking, if he killed 3 of the Archers. But already when I accept the Quest he shows me this Dialogue Node. So the Condition for Has Killed seems not to work somehow.

OK, One issue I found was that the counter wasn’t being registered when the quest was accepted. I solved this by making a new component to do this when the ‘give quest’ dialog action was fired

public class QuestDeathCounterRegistration : MonoBehaviour
{
    [SerializeField] string enemyIdentifier;

    public void Register()
    {
        var counter = GameObject.FindWithTag("Player").GetComponent<DeathCounter>();
        counter.RegisterCounter(enemyIdentifier);
    }
}

image

I’m still looking into completing the objective when the number of kills have been achieved. Stay tuned

1 Like

Thank you so so much for your helpf but I am actually still wondering, why my condition doesn’t work. Like why does he shows the dialogue node when I havent killed the 3 Archers yet…

I don’t know. It works for me. Did you get all the code and put it in the right places?

Okai I figured out my problem :slight_smile: Well now he only shows the dialogue node, when I killed the 3 Archers. And you are right, I can only complete the quest via Dialogue, also when I kill the 3 Archers it doesnt checkmark the objective in the Questbook.

OK, Completing the quest when the required number of kills were reached:

Add an event to the DeathCounter

public event Action<string> DeathRecorded;
    
public void RecordDeath(string victim)
{
    if (!counterDict.ContainsKey(victim))
    {
        return;
    }
    counterDict[victim]++;
    DeathRecorded?.Invoke(victim); // <- invoke it here
}

Now we’ll make a component that will check a condition each time someone dies

    public class QuestCompletionByPredicates : MonoBehaviour
    {
        #region Properties and Fields

        [SerializeField] Quest quest;
        [SerializeField] Condition condition;
        [SerializeField] string objective;
        [SerializeField] QuestItem[] itemsToRemove = null;

        private GameObject player;

        #endregion

        #region Unity Methods

        private void Start()
        {
            player = GameObject.FindWithTag("Player");
            player.GetComponent<DeathCounter>().DeathRecorded += DeathRecorded;
        }

        private void OnDestroy()
        {
            if (player != null)
                player.GetComponent<DeathCounter>().DeathRecorded -= DeathRecorded;
        }

        #endregion

        #region Public Methods

        public void TryCompleteObjective()
        {
            var evaluators = player.GetComponents<MonoBehaviour>().OfType<IPredicateEvaluator>();
            if (!CheckCondition(evaluators)) return;

            var questList = player.GetComponent<QuestList>();
            questList.CompleteObjective(quest, objective);

            if (itemsToRemove == null || itemsToRemove.Length == 0) return;

            var inventory = Inventory.GetPlayerInventory();
            foreach (var item in itemsToRemove)
                inventory.RemoveItem(item.Item, item.Number);

            player.GetComponent<DeathCounter>().DeathRecorded -= DeathRecorded;
        }

        #endregion

        #region Private Methods

        private void DeathRecorded(string victim)
            => TryCompleteObjective();

        private bool CheckCondition(IEnumerable<IPredicateEvaluator> evaluators)
            => condition.Check(evaluators);

        #endregion
    }

Add it to the ‘quest giver’ and set up the condition

That should do it. Each time someone dies, this will evaluate the condition and complete the quest if the condition is satisfied. It will also stop listening to events when the quest is completed

It’s all pretty messy. It gets the job done, but it’s not ideal.

Thank you so much for your effort but hes actually shooting me a n error that the namespace name QuestItem could not be found

 [SerializeField] QuestItem[] itemsToRemove = null;

Which QuestItem you are actually refering to?

Just remove that. I think it’s either my own code, or something you may not have reached yet in the course. I don’t know how far you are into the course.

    public class QuestCompletionByPredicates : MonoBehaviour
    {
        #region Properties and Fields

        [SerializeField] Quest quest;
        [SerializeField] Condition condition;
        [SerializeField] string objective;

        private GameObject player;

        #endregion

        #region Unity Methods

        private void Start()
        {
            player = GameObject.FindWithTag("Player");
            player.GetComponent<DeathCounter>().DeathRecorded += DeathRecorded;
        }

        private void OnDestroy()
        {
            if (player != null)
                player.GetComponent<DeathCounter>().DeathRecorded -= DeathRecorded;
        }

        #endregion

        #region Public Methods

        public void TryCompleteObjective()
        {
            var evaluators = player.GetComponents<MonoBehaviour>().OfType<IPredicateEvaluator>();
            if (!CheckCondition(evaluators)) return;

            var questList = player.GetComponent<QuestList>();
            questList.CompleteObjective(quest, objective);

            player.GetComponent<DeathCounter>().DeathRecorded -= DeathRecorded;
        }

        #endregion

        #region Private Methods

        private void DeathRecorded(string victim)
            => TryCompleteObjective();

        private bool CheckCondition(IEnumerable<IPredicateEvaluator> evaluators)
            => condition.Check(evaluators);

        #endregion
    }

So I did exactly the same but the objectives are still not marked as complete when I defeat the Archers

I have left out a lot of things that should be there. Like the DeathCounter does not save the deaths, etc. These are things you’d need to add.

public class DeathCounter : MonoBehaviour, ISaveable
{
    /*
    Other code stays
    */

    [Serializable]
    struct DeathCounterRecord
    {
        public string Victim;
        public int Count;
    }

    object ISaveable.CaptureState()
    {
        var records = new List<DeathCounterRecord>();
        foreach (var key in counterDict.Keys)
            records.Add(new DeathCounterRecord { Victim = key, Count = counterDict[key] });
        return records.ToArray();
    }

    void ISaveable.RestoreState(object state)
    {
        var records = state as DeathCounterRecord[];
        counterDict.Clear();
        for (int i = 0; i < records.Length; i++)
            counterDict.Add(records[i].Victim, records[i].Count);
    }
}

Soory for all the questions and problems shooting. I added this code piece aswell but the objective still stays 0/1 when I kill all the archers.Anything else you left out?

Probably. Have you set everything up correctly? Maybe put some Debug.Log in QuestCompletionByPredicates and see what it returns. And check the counts when NPCs die. All of this worked for me, so we’d just have to troubleshoot your side

So in the QuestCompletionByPredicates.cs he actually goes into the TryCompleteObjective function every time I kill an Archer, so that is right. When i killed all the three archers he also goes into the CompleteObjective function from the QuestList.cs. Also there he goes into onUpdate() and everything. So I am just confused why hes not checkmarking the objective…

I really dont understand why he dont update it

Is the objective spelt correct? with correct case?

Privacy & Terms