Tracking kills for Quests?

In my quest list class, I added this little bit to place the token into the achievement counter because I was having issues with my UI updating and realized that the token was never initialized when the player recieved the quest so kills were not updating the objective.

public void AddQuest(Quest quest)
{
if (HasQuest(quest)) return;
QuestStatus newStatus = new QuestStatus(quest);
statuses.Add(newStatus);

        GetComponent<Sounds>().PlayAudioClip(questRecievedSound);

        foreach (Quest.Objective obj in quest.GetObjectives())
        {
            if (obj.GetPredicateConditionType() == PredicateType.HasKilled)
            {
                achievementCounter.RegisterCounter(obj.GetConditionParamater(0), true);
            }
        }
        OnQuestListEvent?.Invoke();
    }

What do you think? I think its the right way or is there a better way?

Hmmm… if the original QuestList.Update() method was doing the work, it should have automagically initialized the counter. There’s nothing wrong with a little brute force, though. :slight_smile:

You can do that if you wish, but we’re already calling CompleteObjectivesByPredicate in the Update loop

CompleteObjectivesByPredicate: where does this come from?

The next step is going to be a specialized QuestCompletion. I’ll write that out when I get home.

Was this shared anywhere?

Hmmm… if the original QuestList.Update() method was doing the work

When was an update method added?

Thanks.

These things were added in the Shops and Abilities course to extend the Conditions and Quests, allowing you to specify a condition to complete a quest.

Thank you again for replying, so to be able to finish this do i have to start that course or is there a way to accomplish before going through shops and abilities?

I think the best way to understand what Sam has done to make the Quest conditions work, would be to go through the Shops and Abilities course. That being said, if you go to the course repo and browse through the files, you’ll find the two methods in QuestList that are added to make Quest Conditions work (as well as the slight change to the Quest itself to allow a condition to be evaluated for completion).

You may also want to take a look at this post, the first part of which will guide you through changing the predicate from a string (easily misspelled) to an enum. To add HasKilled, you just need to add another value to the Enum that post provides.

The rest of the post deals with actually building a property drawer for the condition, so that it will fill in Quest names, objective names, inventory items, etc. That does rely on code from both the Quests course and the Shops and Abilities course.

I’m currently with near to 100 loose ends. I was trying to clear some before going into shops and abilities which will probably add more loose ends…

Yes i have that post and 2 others from the Brians tips & tricks flagged to do.
I’ll have to see the best approach then.
Thank you again for helping!

Best Regards!

Welp… for some reason my predicate evaluate function on the achievement counter is not being called after the counter updates and the questlist runs through the various predicate evaluators with the various quests… i can’t get it to get called and I dont know what I’m doing wrong. >.> So at this point I can upate the counter but when I reach the required amount for the kill quest it won’t complete the objective.

I’ll give it a run through later tonight or tommorrow morning to see if there’s something oblivious I missed. You’ve got the IPredicateEvaluator interface in the class declaration, right?

Hey Brian, here is my code:

First my Questlist. It has the IPredicateEvaluator interface on it.

   void Start()
   {
            achievementCounter.onCountChanged += CompleteObjectivesByPredicates;
   }


       public void AddQuest(Quest quest)
        {
            if (HasQuest(quest)) return;
            QuestStatus newStatus = new QuestStatus(quest);
            statuses.Add(newStatus);

            GetComponent<Sounds>().PlayAudioClip(questRecievedSound);

            foreach (Quest.Objective obj in quest.GetObjectives())
            {
                if (obj.GetPredicateConditionType() == PredicateType.HasKilled)
                {
                    achievementCounter.RegisterCounter(obj.GetConditionParamater(0), true);
                    Int32.TryParse(obj.GetConditionParamater(1), out int maxAmount);
                    achievementCounter.RegisterMaxCounter(obj.GetConditionParamater(0),maxAmount, false);
                }
            }

            OnQuestListEvent?.Invoke();
        }

        public void CompleteObjective(Quest quest, string objective)
        {
            var status = GetQuestStatus(quest);

            if (status != null)
            {
                if (status.IsComplete())
                {
                    Debug.Log("Status is complete");
                }

                status.CompleteObjective(objective);

                OnQuestListEvent?.Invoke();
            }
        }

 // Called by the UI when player hits complete quest to give player the quest reward
       public bool CompleteQuest(Quest quest)
        {
            var status = GetQuestStatus(quest);

            if (status != null)
            {
                if (status.IsComplete())
                {
                    GiveReward(quest);
                    return true;
                }
            }

            return false;
        }

  private void CompleteObjectivesByPredicates()
        {
            foreach (QuestStatus status in statuses)
            {
                if (status.IsComplete()) continue;
                Quest quest = status.GetQuest();
                foreach (var objective in quest.GetObjectives())
                {
                    // Debug.Log($"Evaluating objective{objective.description}");
                    // Debug.Log($"Objective type: {objective.GetPredicateConditionType()}");
                    // Debug.Log($"Objective Paramater 0: {objective.GetConditionParamater(0)}");
                    // Debug.Log($"Objective Paramater 1: {objective.GetConditionParamater(1)}");
                    if (status.IsObjectiveComplete(objective.reference)) continue;
                    if (!objective.usesCondition) continue;
                    IPredicateEvaluator[] predArray = transform.GetComponents<IPredicateEvaluator>();
                    Debug.Log(predArray.Length); // this gives out an array length of 6, so its being captured
                    if (objective.completionCondition.Check(predArray))
                    { 

                          // This line of code is never called it seems
                            Debug.Log("objective completed.");
                            CompleteObjective(quest, objective.reference);
                    }
                    
                }
            }
        }

Here is the counter class as you provided , it should be identical. It also has the IPredicateEvaluator on it just like the quest list.

I didn’t post ALL the script because you already have it but this is the evaluator we used.

public bool? Evaluate(PredicateType predicate, string[] parameters)
    {
        
        if (predicate == PredicateType.HasKilled)
        {
            if (int.TryParse(parameters[1], out int intParameter))
            {
                RegisterCounter(parameters[0]);
                Debug.Log(counts[parameters[0]] >= intParameter);
                return counts[parameters[0]] >= intParameter;
            }

            return false;
        }

        return false;
    }

Both the quest list and achievement counter sit on the player object.

Just for completion sake, here is my condition script and predicate class.

 [System.Serializable]
    public class Condition
    {
        [SerializeField]
        Disjunction[] and;

        public PredicateType GetConditionType()
        {
            return and[0].GetPredicate().GetPredicateType();
        }

        public string GetPredicateParamater(int index)
        {
            return and[0].GetPredicate().GetParamaters(index);
        }
        public bool Check(IEnumerable<IPredicateEvaluator> evaluators)
        {
            foreach (Disjunction dis in and)
            {
                if (!dis.Check(evaluators))
                {
                    return false;
                }
            }
            return true;
        }

        [System.Serializable]
        class Disjunction
        {
            [SerializeField]
            Predicate[] or;


            public Predicate GetPredicate()
            {
                return or[0];
            }
            
            public bool Check(IEnumerable<IPredicateEvaluator> evaluators)
            {
                foreach (Predicate p in or)
                {
                    if (p.Check(evaluators))
                    {
                        return true;
                    }
                }

                return false;

            }
        }

        [System.Serializable]
        public class Predicate
        {

            [SerializeField] PredicateType predicate;
            [SerializeField] bool negate = false;
            [SerializeField] string[] parameters;

            public PredicateType GetPredicateType()
            {
                return predicate;
            }

            public string GetParamaters(int index)
            {
                return parameters[index];
            }

            public bool Check(IEnumerable<IPredicateEvaluator> evaluators)
            {
                foreach (var evaluator in evaluators)
                {
                    bool? result = evaluator.Evaluate(predicate, parameters);
                    if (result == null)
                    {
                        continue;
                    }

                    if (result == negate) return false;
                }
                return true;
            }
        }
    }


Predicate enum and class.

 public enum PredicateType
  {
      Select, // placeholder, reminds you to select a predicate
      HasQuest,
      HasCompletedObjective,
      HasCompletedQuest,
      HasLevel,
      MinimumTrait,
      HasItem,
      HasItems,
      HasItemEquipped,
      HasKilled
  }
  
  public interface IPredicateEvaluator
  {
      bool? Evaluate(PredicateType predicate, string[] paramters);
  }

Hopefully this helps figure it out. I’m just stuck. Quest list is getting the predicate evlauator call when the counter updates because of myu debug logs but it never gets passed the Check bool function. Furthermore, the achievementcounter evaluator is not being called because my debug.log in that function is never called so I know its not being called which I can’t figure out why. I left some comments in that function to give you a sense of where it breaksdown.

One other question, for the IItemStore , do I add this interface to any monobehaviour I want the quest to be able to try an add a reward to when the give reward function is called? so instead of querying the GetComponent<Inventory>().AddItemToFirstSlot(), I instead GetComponents<IItemStore>().AddItem()?

Is that the gist of it? Because otherwise I’m not quite sure how the give reward is able to give out experience for example of it only tries to add to the inventory class itself.

Did you go through the Shops and Abilities course (I’d assumed you had because that’s where Sam introduces Quest Objectives being able to be completed by a Condition attached to the Objective)? It’s also where IItemStore is introduced.

The trick with the IItemStore is that it any class on your player that may have a use for the item when it’s picked up implements the IItemStore interface, with the AddItem method.
When an inventory item is added to the player’s inventory.AddToFirstEmptySlot(), the AddToFirstEmptySlot first looks to see if the item can be used elsewhere. It does this by cycling through all of the IItemStore classes on the component, and one at a time, it calls AddItem(item, number). The result of that call is the number of items that the IItemStore took and did something with, so we deduct that result from the number until the number is zero, and if there are any left over, they go into inventory.
This lets us have coin pickups, as the Purse class in the Shops and Abilities sees that it’s a coin pickup and adds the number of coints to the player’s current balance. It can be extended to experience (experience sees it’s an experience token and increases the experience. It can be used as an instant heal, or whatever thing you need it to).

Ok, on to the issue at hand… It looks like you’ve put in a Debug that ensures that the CompleteObjectivesByPredicates() is getting called… Let’s make sure that the Evaluate on the Counter is being called… Let’s add some debugs to AchievementCounter.Evaluate()

public bool? Evaluate(PredicateType predicate, string[] parameters)
    {
        Debug.Log($"AchievementCounter is evaluating {predicate}");
        if (predicate == PredicateType.HasKilled)
        {
            Debug.Log($"Condition is:  if({predicate}({parameters[0]}, {parameters[1]})");
            if (int.TryParse(parameters[1], out int intParameter))
            {
                RegisterCounter(parameters[0]);
                Debug.Log(counts[parameters[0]] >= intParameter);
                return counts[parameters[0]] >= intParameter;
            } 
            Debug.Log($"Parameters[1] ({parameters[1]}) is not an integer.");
            return false;
        }
        return false;
    }

Hah, what a clever trick. I see what youre saying with the Inventory.AddItemtoFirstEmptySlot.

I have completed the inventory , dialogue, and shops videos including the abilities but I seemed to have missed the IItemStore interface. I’m not sure how I did miss it nor do I know exactly what video it is on but I see it in the github.

Now as pertains to the code above, it isn’t even being called and i dont know why :confused:

Right, you should be getting spammed with Achievement Counter is evaluating [various predicates]

Which leads me to an earlier question…

Yes

Here is the top of my code:

  public class AchievementCounter : MonoBehaviour, ISaveable, IPredicateEvaluator
{

So that first message in the debugs should be spamming you like crazy every time a condition is evaluated… Same GameObject as the player I’m not at all sure why it wouldn’t fire.

ok nevermind… wasn’t what I thought… BUT i did figure out that in my condition class, these parts of the check function are never called, it goes all the way up to this but no further and I don’t know why.

 public bool Check(IEnumerable<IPredicateEvaluator> evaluators)
            {
                foreach (var evaluator in evaluators)
                { 
                 
                    bool? result = evaluator.Evaluate(predicate, parameters);
                    if (result == null)
                    {
                        Debug.Log(predicate);
                        Debug.Log(parameters[0]);
                        Debug.Log(parameters[1]);
                        continue;
                    }
                  
                    if (result == negate) return false;
                    
                    Debug.Log(predicate);
                    Debug.Log(parameters[0]);
                    Debug.Log(parameters[1]);
                }
                return true;
            }

It tells me when I debug.log(evaluator) that its RPG.Stats.BaseStats but it doesn’t call anything else like it doesn’t show the achievement counter in the debug log… its wierd.
Any ideas?

So I think what’s happening is that the function above is returning false when the basestat evaluates the haskilled enum and this is not allowing for any of the other predicate evaluators to evaluate enum and parameter.

Notice the function returns false if the result == negate… so you never get to go through the other evaluators even though my debug.log is showing 6 in total.

Is this a fundamental flaw in our design over all or am I the only on having this strange phenomenon?

Brian… i figured out the problem… all the predicate evaluators i had for trait store and base stat were returning false if they failed… instead of NULL. It should be null to continue in the foreach loop.

THAT FIXED IT!! WOOOO. Omg lol what madness. I feel like a true computer science programmer, problem solving and debugging down a long rabbit hole!

2 Likes

Fantastic job figuring that out!

Privacy & Terms