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
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!
Fantastic job figuring that out!
This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.