Strange behavior with level checking

Hello.
I’m finishing to test every condition.
Everyone works fine except the Has Level one…
It’s like if it doesn’t check.
I’ve Two nodes.
I add the check condtion “Has Level” and put it on 1 for the first and for the second, put it on 2.
I precise it’s in checking Dialog, but quest react as same as Dialogue with Has Level Condition.
It acts randomly, without caring of the level of the player.
We are agree that when it wrote Level Required 2, it means 2 and more ?
Not At least 2?
If not , At least 2 encompass level 1 no ?
Thanks.
François

Just to get some quick clarification…

Condition 1:
HasLevel(1)
This should pretty much always return true because the player starts at level 1. If the player is level 2, then this will still return true because 2 > 1

HasLevel(2)
This should be false if the player is level 1, true if the player is level 2 or higher

Is this what you’re getting?

Hello Brian.
Well, fist I must admit I forgot the code in baseStats to evaluate…
I added it, at the end of TraitStore:
My BaseStats script:

using System;

using GameDevTV.Utils;

using Unity.VisualScripting;

using UnityEngine;

namespace RPG.Stats

{

        public class BaseStats : MonoBehaviour

    {

        [Range(1,99)]//Bornage du niveau min et max

        [SerializeField] int startingLevel = 1;

        [SerializeField] CharacterClass characterClass;

        [SerializeField] Progression progression = null;

        [SerializeField] GameObject levelupParticleEffect = null;

        [SerializeField] bool shouldUseModifiers = false;// Pour ne faire bénéficer des bonus que le joueur

        public event Action onLevelUp;//Création du pointeur vers l'evenement onLevelUp

        LazyValue<int> currentLevel;

        Experience experience;

        private void Awake()

        {

            experience = GetComponent<Experience>();

            currentLevel = new LazyValue<int>(CalculateLevel);

        }

        private void Start()

        {

            currentLevel.ForceInit();

        }

        private void OnEnable()

        {

            if(experience != null)

            {

                experience.onExperienceGained += UpdateLevel;//Sans parenthèse, on ajoute la méthode UpdateLevel dans la liste de onExperienceGained

            }

        }

        private void OnDisable()

        {

            if(experience != null)

            {

                experience.onExperienceGained -= UpdateLevel;//Sans parenthèse, on retire la méthode UpdateLevel dans la liste de onExperienceGained

            }

        }

        private void UpdateLevel()

        {

            int newLevel = CalculateLevel();//On créé la variable local NewLevel

            if(newLevel > currentLevel.value)

            {

                currentLevel.value = newLevel;

                LevelupEffect();

                onLevelUp();

                print("Niveau gagné");

            }

        }

        private void LevelupEffect()

        {

            Instantiate(levelupParticleEffect, transform);//Cela lie l'effet au porteur du script.

        }

//Methode pour recupérer la stat de base et lui ajouter des modificateurs.

        public float GetStat(Stat stat)

        {

            return (GetBaseStat(stat) + GetAdditiveModifier(stat)) * (1 + GetPercentageModifier(stat)/100);

        }

        private float GetBaseStat(Stat stat)

        {

            return progression.GetStat(stat, characterClass, GetLevel());

        }

        public int GetLevel()

        {

            return currentLevel.value;

        }

        private float GetAdditiveModifier(Stat stat)

        {

            if(!shouldUseModifiers) return 0;//Si le personnage ne devrait pas bénéficer d'avantages modifieurs, le bonus est nul.

           

            float total = 0;//Créationd e la variable de point de modification de stat total

            foreach(IModifierProvider provider in GetComponents<IModifierProvider>())

            {

                //Pour chaque modificateur de stat présent dans la liste récupérée ci-dessus:

                foreach(float modifier in provider.GetAdditiveModifiers(stat))

                {

                    total += modifier;

                }

            }

                return total;

        }

        private float GetPercentageModifier(Stat stat)

        {

            if(!shouldUseModifiers) return 0;//Si le personnage ne devrait pas bénéficer d'avantages modifieurs, le bonus est nul.

            float total = 0;//Créationd e la variable de point de modification de stat total

            foreach(IModifierProvider provider in GetComponents<IModifierProvider>())

            {

                //Pour chaque modificateur de stat présent dans la liste récupérée ci-dessus:

                foreach(float modifier in provider.GetPercentageModifiers(stat))

                {

                    total += modifier;

                }

            }

                return total;

        }

        private int CalculateLevel()//Pour récupérer l'information du niveau actuel

        {

            Experience experience = GetComponent<Experience>();

            if(experience == null) return startingLevel;

            float currentXP = experience.GetPoints();

            int penUltimateLevel = progression.GetLevels(Stat.ExperienceToLevelUp, characterClass);//PenultimateLevel = avant dernier niveau Ultime

            for(int level=1; level<=penUltimateLevel; level++)

            {

                float XPToLevelUP = progression.GetStat(Stat.ExperienceToLevelUp, characterClass, level);

                if(XPToLevelUP > currentXP)//Si l'XP pour UP est supèrieure à l'XP actuelle on renvoie le niveau actuel

                {

                    return level;

                }

            }//Sinon on est au niveau max soit l'avantdernier +1

            return penUltimateLevel + 1;

        }

        public bool? Evaluate(EPredicate predicate, string[] parameters)

        {

            if (predicate == EPredicate.HasLevel)

            {

                if (int.TryParse(parameters[0], out int testLevel))

                {

                    return currentLevel.value >= testLevel;

                }

            }

            return null;

        }

       

    }

}

Here are my dialogue nodes:
To test if level 1:
image

To test if level 2:
image

To test If level 3:
image

But it’s still acting as if it is random dialogue…
Very strange.
It doesn’t work too with dialogue conditon and Quest condition, and Equipement condition…
Any Idea ?

Edit:
HasLevel looks to be a Lazy Value.
Here is my basestats script:

using System;

using GameDevTV.Utils;

using Unity.VisualScripting;

using UnityEngine;

namespace RPG.Stats

{

        public class BaseStats : MonoBehaviour

    {

        [Range(1,99)]//Bornage du niveau min et max

        [SerializeField] int startingLevel = 1;

        [SerializeField] CharacterClass characterClass;

        [SerializeField] Progression progression = null;

        [SerializeField] GameObject levelupParticleEffect = null;

        [SerializeField] bool shouldUseModifiers = false;// Pour ne faire bénéficer des bonus que le joueur

        public event Action onLevelUp;//Création du pointeur vers l'evenement onLevelUp

        LazyValue<int> currentLevel;

        Experience experience;

        private void Awake()

        {

            experience = GetComponent<Experience>();

            currentLevel = new LazyValue<int>(CalculateLevel);

        }

        private void Start()

        {

            currentLevel.ForceInit();

        }

        private void OnEnable()

        {

            if(experience != null)

            {

                experience.onExperienceGained += UpdateLevel;//Sans parenthèse, on ajoute la méthode UpdateLevel dans la liste de onExperienceGained

            }

        }

        private void OnDisable()

        {

            if(experience != null)

            {

                experience.onExperienceGained -= UpdateLevel;//Sans parenthèse, on retire la méthode UpdateLevel dans la liste de onExperienceGained

            }

        }

        private void UpdateLevel()

        {

            int newLevel = CalculateLevel();//On créé la variable local NewLevel

            if(newLevel > currentLevel.value)

            {

                currentLevel.value = newLevel;

                LevelupEffect();

                onLevelUp();

                print("Niveau gagné");

            }

        }

        private void LevelupEffect()

        {

            Instantiate(levelupParticleEffect, transform);//Cela lie l'effet au porteur du script.

        }

//Methode pour recupérer la stat de base et lui ajouter des modificateurs.

        public float GetStat(Stat stat)

        {

            return (GetBaseStat(stat) + GetAdditiveModifier(stat)) * (1 + GetPercentageModifier(stat)/100);

        }

        private float GetBaseStat(Stat stat)

        {

            return progression.GetStat(stat, characterClass, GetLevel());

        }

        public int GetLevel()

        {

            return currentLevel.value;

        }

        private float GetAdditiveModifier(Stat stat)

        {

            if(!shouldUseModifiers) return 0;//Si le personnage ne devrait pas bénéficer d'avantages modifieurs, le bonus est nul.

           

            float total = 0;//Créationd e la variable de point de modification de stat total

            foreach(IModifierProvider provider in GetComponents<IModifierProvider>())

            {

                //Pour chaque modificateur de stat présent dans la liste récupérée ci-dessus:

                foreach(float modifier in provider.GetAdditiveModifiers(stat))

                {

                    total += modifier;

                }

            }

                return total;

        }

        private float GetPercentageModifier(Stat stat)

        {

            if(!shouldUseModifiers) return 0;//Si le personnage ne devrait pas bénéficer d'avantages modifieurs, le bonus est nul.

            float total = 0;//Créationd e la variable de point de modification de stat total

            foreach(IModifierProvider provider in GetComponents<IModifierProvider>())

            {

                //Pour chaque modificateur de stat présent dans la liste récupérée ci-dessus:

                foreach(float modifier in provider.GetPercentageModifiers(stat))

                {

                    total += modifier;

                }

            }

                return total;

        }

        private int CalculateLevel()//Pour récupérer l'information du niveau actuel

        {

            Experience experience = GetComponent<Experience>();

            if(experience == null) return startingLevel;

            float currentXP = experience.GetPoints();

            int penUltimateLevel = progression.GetLevels(Stat.ExperienceToLevelUp, characterClass);//PenultimateLevel = avant dernier niveau Ultime

            for(int level=1; level<=penUltimateLevel; level++)

            {

                float XPToLevelUP = progression.GetStat(Stat.ExperienceToLevelUp, characterClass, level);

                if(XPToLevelUP > currentXP)//Si l'XP pour UP est supèrieure à l'XP actuelle on renvoie le niveau actuel

                {

                    return level;

                }

            }//Sinon on est au niveau max soit l'avantdernier +1

            return penUltimateLevel + 1;

        }

        public bool? Evaluate(EPredicate predicate, string[] parameters)

        {

            if (predicate == EPredicate.HasLevel)

            {

                if (int.TryParse(parameters[0], out int testLevel))

                {

                    return currentLevel.value >= testLevel;

                }

            }

            return null;

        }

       

    }

}

If all three nodes are attached to the same group, i.e. are all children of another node, if a player is level 3, then HasLevel 1 will pass, HasLevel2 will pass, and HasLevel3 will pass because 3 is greater or equal to 1 or 2…
So at level 1, only Hello Level 1 will show
At level 2, a random choice of Level 1 or 2 will show
and at level 3, a random choice of all three will show.

For this we need a new condition UnderLevel (you would think a negation of HasLevel would do it, but that would actually not work because we’re testing >=

if(predicate == EPredicate.UnderLevel)
{
    if(int.TryParse(parameters[0], out int testLevel))
    {
         return currentLevel.value < testLevel;
    }
}

So for HasLevel 1, use UnderLevel 2 instead.
for HasLevel 2, use HasLevel 2 and UnderLevel 3
For HasLevel 3 use HasLevel 3

Well…
I’m turning crazy :wink:
it changes absolutly nothing at all lol :slight_smile:
I had a third condition called OverLevel to test (strcitly) .
When I start game, my character is level 1.
So My condition is strictly under 2:
image

If I’m level two I limited with 3 tests:
image

and for level 3 the same:

I implemented my BaseStats script like that in the Evaluator:

public bool? Evaluate(EPredicate predicate, string[] parameters)

        {

            if (predicate == EPredicate.HasLevel)

            {

                if (int.TryParse(parameters[0], out int testLevel))

                {

                    return currentLevel.value == testLevel;

                }

            }            

            if(predicate == EPredicate.UnderLevel)

            {

                if(int.TryParse(parameters[0], out int testLevel))

                {

                    return currentLevel.value < testLevel;

                }

            }

            if(predicate == EPredicate.OverLevel)

            {

                if(int.TryParse(parameters[0], out int testLevel))

                {

                    return currentLevel.value > testLevel;

                }

            }

            return null;

        }

I had in the IPredicteEvaluator the two new lines:
UnderLevel, //1, BaseStats nécessite strictement inférieur au niveau visé
OverLevel, //1, BaseStats nécessite strictement supèrieur au niveau visé

And in the PredicatePropertyDrawer this two new line:

            if (selectedPredicate == EPredicate.UnderLevel)
            {
                position.y += propHeight;
                DrawIntSlider(position, "Level Minimum", parameterOne, 1,100);
            }
            if (selectedPredicate == EPredicate.OverLevel)
            {
                position.y += propHeight;
                DrawIntSlider(position, "Level Maximum", parameterOne, 1,100);
            }

Lol what happens ? :slight_smile:
François

And this is still returning random values??

            if (predicate == EPredicate.HasLevel)
            {
                if (int.TryParse(parameters[0], out int testLevel))
                {
                    Debug.Log($"Testing HasLevel({testLevel}) against character level {currentLevel.value} : {currentLevel.value == testLevel}");
                    return currentLevel.value == testLevel;
                }
            }            

            if(predicate == EPredicate.UnderLevel)
            {            
                if(int.TryParse(parameters[0], out int testLevel))
                Debug.Log($"Testing UnderLevel({testLevel}) against character level {currentLevel.value} : {currentLevel.value<testLevel}");
                {
                    return currentLevel.value < testLevel;
                }
            }

            if(predicate == EPredicate.OverLevel)
            {
                if(int.TryParse(parameters[0], out int testLevel))
                {
                    Debug.Log($"Testing OverLevel({testLevel}) against character level {currentLevel.value} : {currentLevel.value > testLevel}");
                    return currentLevel.value > testLevel;
                }
            }

Can you show me the console logs with these Debugs running, testing a level 1 player, level 2 player, level 3 player, and level 4 player?

Hello Brian, thanks for your feedback.
scuse for the late, I was supporting my wife running the “Marathon de Paris” :slight_smile:
Unfortunatly I haven’t any log message after adding your debug log lol.
it’s like it doesn’t test anyway the evaluator…
What I’ve missed???
Thank you.
François

I should have caught this earlier… You’re not showing the IPredicateEvaluator on the BaseStats.

public class BaseStats : MonoBehaviour, IPredicateEvaluator


How to say…
Sorry Brian.
But unfortunatly, it doesn’t change anything :crazy_face:
here is my level 2 test:

nothing happen when I become level 2…

here is my BaseStats updated:

using System;

using GameDevTV.Utils;

using Unity.VisualScripting;

using UnityEngine;

namespace RPG.Stats

{

        public class BaseStats : MonoBehaviour,IPredicateEvaluator

    {

        [Range(1,99)]//Bornage du niveau min et max

        [SerializeField] int startingLevel = 1;

        [SerializeField] CharacterClass characterClass;

        [SerializeField] Progression progression = null;

        [SerializeField] GameObject levelupParticleEffect = null;

        [SerializeField] bool shouldUseModifiers = false;// Pour ne faire bénéficer des bonus que le joueur

        public event Action onLevelUp;//Création du pointeur vers l'evenement onLevelUp

        LazyValue<int> currentLevel;

        Experience experience;

        private void Awake()

        {

            experience = GetComponent<Experience>();

            currentLevel = new LazyValue<int>(CalculateLevel);

        }

        private void Start()

        {

            currentLevel.ForceInit();

        }

        private void OnEnable()

        {

            if(experience != null)

            {

                experience.onExperienceGained += UpdateLevel;//Sans parenthèse, on ajoute la méthode UpdateLevel dans la liste de onExperienceGained

            }

        }

        private void OnDisable()

        {

            if(experience != null)

            {

                experience.onExperienceGained -= UpdateLevel;//Sans parenthèse, on retire la méthode UpdateLevel dans la liste de onExperienceGained

            }

        }

        private void UpdateLevel()

        {

            int newLevel = CalculateLevel();//On créé la variable local NewLevel

            if(newLevel > currentLevel.value)

            {

                currentLevel.value = newLevel;

                LevelupEffect();

                onLevelUp();

                print("Niveau gagné");

            }

        }

        private void LevelupEffect()

        {

            Instantiate(levelupParticleEffect, transform);//Cela lie l'effet au porteur du script.

        }

//Methode pour recupérer la stat de base et lui ajouter des modificateurs.

        public float GetStat(Stat stat)

        {

            return (GetBaseStat(stat) + GetAdditiveModifier(stat)) * (1 + GetPercentageModifier(stat)/100);

        }

        private float GetBaseStat(Stat stat)

        {

            return progression.GetStat(stat, characterClass, GetLevel());

        }

        public int GetLevel()

        {

            return currentLevel.value;

        }

        private float GetAdditiveModifier(Stat stat)

        {

            if(!shouldUseModifiers) return 0;//Si le personnage ne devrait pas bénéficer d'avantages modifieurs, le bonus est nul.

           

            float total = 0;//Créationd e la variable de point de modification de stat total

            foreach(IModifierProvider provider in GetComponents<IModifierProvider>())

            {

                //Pour chaque modificateur de stat présent dans la liste récupérée ci-dessus:

                foreach(float modifier in provider.GetAdditiveModifiers(stat))

                {

                    total += modifier;

                }

            }

                return total;

        }

        private float GetPercentageModifier(Stat stat)

        {

            if(!shouldUseModifiers) return 0;//Si le personnage ne devrait pas bénéficer d'avantages modifieurs, le bonus est nul.

            float total = 0;//Créationd e la variable de point de modification de stat total

            foreach(IModifierProvider provider in GetComponents<IModifierProvider>())

            {

                //Pour chaque modificateur de stat présent dans la liste récupérée ci-dessus:

                foreach(float modifier in provider.GetPercentageModifiers(stat))

                {

                    total += modifier;

                }

            }

                return total;

        }

        private int CalculateLevel()//Pour récupérer l'information du niveau actuel

        {

            Experience experience = GetComponent<Experience>();

            if(experience == null) return startingLevel;

            float currentXP = experience.GetPoints();

            int penUltimateLevel = progression.GetLevels(Stat.ExperienceToLevelUp, characterClass);//PenultimateLevel = avant dernier niveau Ultime

            for(int level=1; level<=penUltimateLevel; level++)

            {

                float XPToLevelUP = progression.GetStat(Stat.ExperienceToLevelUp, characterClass, level);

                if(XPToLevelUP > currentXP)//Si l'XP pour UP est supèrieure à l'XP actuelle on renvoie le niveau actuel

                {

                    return level;

                }

            }//Sinon on est au niveau max soit l'avantdernier +1

            return penUltimateLevel + 1;

        }

        public bool? Evaluate(EPredicate predicate, string[] parameters)

        {

            if (predicate == EPredicate.HasLevel)

            {

                if (int.TryParse(parameters[0], out int testLevel))

                {

                    Debug.Log($"Testing HasLevel({testLevel}) against character level {currentLevel.value} : {currentLevel.value == testLevel}");

                    return currentLevel.value == testLevel;

                }

            }            

            if(predicate == EPredicate.UnderLevel)

            {

                if(int.TryParse(parameters[0], out int testLevel))

                Debug.Log($"Testing UnderLevel({testLevel}) against character level {currentLevel.value} : {currentLevel.value<testLevel}");

                {

                    return currentLevel.value < testLevel;

                }

            }

            if(predicate == EPredicate.OverLevel)

            {

                if(int.TryParse(parameters[0], out int testLevel))

                {

                    Debug.Log($"Testing OverLevel({testLevel}) against character level {currentLevel.value} : {currentLevel.value > testLevel}");

                    return currentLevel.value > testLevel;

                }

            }

            return null;

        }

       

    }

}

sorry for this very strange troubleshooting…

François

Edit:
Here are my IPredicateEvaluator script.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace GameDevTV.Utils
{
    public enum EPredicate
    {
        Select, //0 - aucun gestionnaire ne gérera Select, c'est un espace réservé pour nous rappeler de choisir un prédicat
        HasQuest, //1, QuestList nécessite le nom de la quête
        CompletedObjective, //2, QuestList nécessite le nom de la quête et l'identifiant de l'objectif
        CompletedQuest, //1, QuestList nécessite le nom de la quête
        HasLevel, //1, BaseStats nécessite le niveau visé
        UnderLevel, //1, BaseStats nécessite strictement inférieur au niveau visé
        OverLevel, //1, BaseStats nécessite strictement supèrieur au niveau visé
        MinimumTrait, //2, TraitStore nécessite le trait et son niveau minimum
        HasInventoryItem, //1, Inventory nécessite l'ID de l'objet
        HasItems, //2, Inventory nécessite l'ID de l'objet et la quantité requise
        HasItemEquipped //1, Equipment nécessite l'ID de l'objet.
    }
    public interface IPredicateEvaluator
    {//Session 5 Chap 64
        bool? Evaluate(EPredicate predicate, string[] parameters);//Le ? permet une 3ème condition, "je ne sais pas".
    }
}

And why not my PropertyPredicateDrawer:

using System;
using System.Collections.Generic;
using System.Linq;
using GameDevTV.Inventories;
using RPG.Quests;
using RPG.Stats;
using UnityEditor;
using UnityEngine;

namespace GameDevTV.Utils.Editor
{
    [CustomPropertyDrawer(typeof(Condition.Predicate))]
    public class PredicatePropertyDrawer : PropertyDrawer
    {
        private Dictionary<string,Quest> quests;
        private Dictionary<string, InventoryItem> items;
        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            SerializedProperty predicate = property.FindPropertyRelative("predicate");
            SerializedProperty parameters = property.FindPropertyRelative("parameters");
            SerializedProperty negate = property.FindPropertyRelative("negate");
            float propHeight = EditorGUI.GetPropertyHeight(predicate);
            position.height = propHeight;
            EditorGUI.PropertyField(position, predicate);

            EPredicate selectedPredicate = (EPredicate)predicate.enumValueIndex;
            
            if (selectedPredicate == EPredicate.Select) return; //Stop drawing if there's no predicate
            while(parameters.arraySize<2)
            {
                parameters.InsertArrayElementAtIndex(0);
            }
            SerializedProperty parameterZero = parameters.GetArrayElementAtIndex(0);
            SerializedProperty parameterOne = parameters.GetArrayElementAtIndex(1); //Edit, was accidentally 0 in first draft
            if (selectedPredicate == EPredicate.HasQuest || selectedPredicate == EPredicate.CompletedQuest || selectedPredicate == EPredicate.CompletedObjective)
            {
                position.y += propHeight;
                DrawQuest(position, parameterZero);
            }
            if (selectedPredicate == EPredicate.CompletedObjective)
            {
                position.y += propHeight;
                DrawObjective(position, parameterOne, parameterZero);
            }
            if (selectedPredicate == EPredicate.HasInventoryItem || selectedPredicate== EPredicate.HasItems || selectedPredicate == EPredicate.HasItemEquipped)
            {
                position.y += propHeight;
                DrawInventoryItemList(position, parameterZero, selectedPredicate==EPredicate.HasItems, selectedPredicate == EPredicate.HasItemEquipped);
            }
            if (selectedPredicate == EPredicate.HasItems)
            {
                position.y += propHeight;
                DrawIntSlider(position, "Qty Needed", parameterOne, 1,100);
            }
            if (selectedPredicate == EPredicate.HasLevel)
            {
                position.y += propHeight;
                DrawIntSlider(position, "Level Required", parameterOne, 1,100);
            }
            if (selectedPredicate == EPredicate.UnderLevel)
            {
                position.y += propHeight;
                DrawIntSlider(position, "Level Minimum", parameterOne, 1,100);
            }
            if (selectedPredicate == EPredicate.OverLevel)
            {
                position.y += propHeight;
                DrawIntSlider(position, "Level Maximum", parameterOne, 1,100);
            }
            if (selectedPredicate == EPredicate.MinimumTrait)
            {
                position.y += propHeight;
                DrawTrait(position, parameterZero);
                position.y += propHeight;
                DrawIntSlider(position, "Minimum", parameterOne, 1,100);
            }
            position.y+=propHeight;
            EditorGUI.PropertyField(position, negate);            
        }
        private void DrawQuest(Rect position, SerializedProperty element)
        {
            BuildQuestList();
            var names = quests.Keys.ToList();
            Debug.Log(names.Count());
            int index = names.IndexOf(element.stringValue);
            
            EditorGUI.BeginProperty(position, new GUIContent("Quest:"), element);
            int newIndex = EditorGUI.Popup(position,"Quest:", index, names.ToArray());
            if (newIndex != index)
            {
                element.stringValue = names[newIndex];
            }

            EditorGUI.EndProperty();
        }
        void BuildQuestList()
        {
            Debug.Log("BuildQuests()");
            if (quests != null) return;
            quests = new Dictionary<string, Quest>();
            foreach (Quest quest in Resources.LoadAll<Quest>(""))
            {
                Debug.Log($"Adding Quest {quest.name}");
                quests[quest.name] = quest;
            }
        }
        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            SerializedProperty predicate = property.FindPropertyRelative("predicate");
            float propHeight = EditorGUI.GetPropertyHeight(predicate);
            EPredicate selectedPredicate = (EPredicate)predicate.enumValueIndex;
            switch (selectedPredicate)
            {
                case EPredicate.Select: //No parameters, we only want the bare enum. 
                    return propHeight; 
                case EPredicate.UnderLevel:       //All of these take 1 parameter
                case EPredicate.HasLevel:
                case EPredicate.CompletedQuest:
                case EPredicate.HasQuest:
                case EPredicate.HasInventoryItem:
                case EPredicate.HasItemEquipped:
                    return propHeight * 3.0f; //Predicate + one parameter + negate
                case EPredicate.CompletedObjective: //All of these take 2 parameters
                case EPredicate.HasItems:
                case EPredicate.MinimumTrait:
                    return propHeight * 4.0f; //Predicate + 2 parameters + negate;
            }
            return propHeight * 2.0f;
        }

        void DrawObjective(Rect position, SerializedProperty element, SerializedProperty selectedQuest)
        {
            string questName = selectedQuest.stringValue;
            if (!quests.ContainsKey(questName))
            {
                EditorGUI.HelpBox(position, "Please Select A Quest", MessageType.Error);
                return;
            }

            List<string> references = new List<string>();
            List<string> descriptions = new List<string>();
            foreach (Quest.Objective objective in quests[questName].GetObjectives())
            {
                references.Add(objective.reference);
                descriptions.Add(objective.description);
            }
            int index = references.IndexOf(element.stringValue);
            EditorGUI.BeginProperty(position, new GUIContent("objective"), element);
            int newIndex = EditorGUI.Popup(position, "Objective:", index, descriptions.ToArray());
            if (newIndex != index)
            {
                element.stringValue = references[newIndex];
            }
            EditorGUI.EndProperty();
        }
        void BuildInventoryItemsList()
        {
            if (items != null) return;
            items = new Dictionary<string, InventoryItem>();
            foreach (InventoryItem item in Resources.LoadAll<InventoryItem>(""))
            {
                items[item.GetItemID()] = item;
            }
        }
        void DrawInventoryItemList(Rect position, SerializedProperty element, bool stackable = false, bool equipment = false)
        {
            BuildInventoryItemsList();
            List<string> ids = items.Keys.ToList();
            if (stackable) ids = ids.Where(s => items[s].IsStackable()).ToList();
            if (equipment) ids = ids.Where(s => items[s] is EquipableItem e).ToList();
            List<string> displayNames = new List<string>();
            foreach (string id in ids)
            {
                displayNames.Add(items[id].GetDisplayName());
            }
            int index = ids.IndexOf(element.stringValue);
            EditorGUI.BeginProperty(position, new GUIContent("items"), element);
            int newIndex = EditorGUI.Popup(position, "Item", index, displayNames.ToArray());
            if (newIndex != index)
            {
                element.stringValue = ids[newIndex];
            }
        }
        private static void DrawIntSlider(Rect position, string caption, SerializedProperty intParameter, int minLevel=1,
                                         int maxLevel=100)
        {
            EditorGUI.BeginProperty(position, new GUIContent(caption), intParameter);
            if (!int.TryParse(intParameter.stringValue, out int value))
            {
                value = 1;
            }
            EditorGUI.BeginChangeCheck();
            int result = EditorGUI.IntSlider(position, caption, value, minLevel, maxLevel);
            if (EditorGUI.EndChangeCheck())
            {
                intParameter.stringValue = $"{result}";
            }
            EditorGUI.EndProperty();
        }
        void DrawTrait(Rect position, SerializedProperty element)
        {
            if (!Enum.TryParse(element.stringValue, out Trait trait))
            {
                trait = Trait.Puissance;
            }
            EditorGUI.BeginProperty(position,new GUIContent("Trait"), element);
            Trait newTrait = (Trait)EditorGUI.EnumPopup(position, "Trait:", trait);
            if (newTrait != trait)
            {
                element.stringValue = $"{newTrait}";
            }
            EditorGUI.EndProperty();
        }
    }
}

I don't understand what goes wrong with HasLevel and friend lol...

Just to clarify… are you getting the debugs at all after adding in the IPredicateEvaluator to the BaseStats? If you’re not, I’m really in the realm of head scratching… (sorry about the delay, I was clicking on the wrong check for msgs filter)

Hello Brian.
No, Unfortunatly I haven’t any test in the console after adding IPredicatorEvaluator after ManoBehavior in my BaseStats script.
When I gain a level the only message wivh appears his:
You gain a level.
If I put the HasLevel Condition in an object like shoes And I put Haslevel to 2, nothing happens and I can wear them while I’m Level 1.
Very strange, all the other tests work…
Thank you.
François

I’m not quite sure why this wouldn’t be the case…

Zip up your project and upload it to https://gdev.tv/projectupload and I’ll take a look. Be sure to remove the Library folder to conserve space.

Hello Brian,
I’ve an error message when deposite the rar file.
image

The file size looks correct:
image

The server access looks forbiden:

Not sure what’s going on there, checking with our tech support, but the drive is hosted by Google. Do you have your own Google Drive? Perhaps you could put the zip there and use the Share feature to copy the link (I suggest sending the link as a PM).

I granted you editor :wink:

I was scratching my head for quite a while on this one… because I was able to absolutely confirm that the IPredicateEvaluator was being called on BaseStats, but the debugs inside the int.TryParse(parameters[0]) were not showing, which seemed to indicate that there was no parameter…

So I dug into the Dialogue itself (Bart) in Notepad, because I could see that the parameters were set…
And I found this:

  isPlayerSpeaking: 0
  text: Hello Level 3
  children: []
  rect:
    serializedVersion: 2
    x: 507
    y: 529
    width: 130
    height: 130
  onEnterAction: 
  onExitAction: Exit
  condition:
    and:
    - or:
      - predicate: 4
        parameters:
        - 
        - 3
        negate: 0
    - or: []
    - or:
      - predicate: 6
        parameters:
        - 
        - 2
        negate: 0

So… Parameters[0] is… blank for both of the Predicates listed… which doesn’t seem quite right… Parameters[1], however, is set, to 3 and 2 respectively.

So that means that somehow the Dialogue is being serialized with the parameters in ParameterOne instead of ParameterZero…

So I headed into PredicatePropertyDrawer and discovered that this is exactly what was happening:

          if (selectedPredicate == EPredicate.HasLevel)
            {
                position.y += propHeight;
                DrawIntSlider(position, "Level Required", parameterOne, 1,100);
            }
            if (selectedPredicate == EPredicate.UnderLevel)
            {
                position.y += propHeight;
                DrawIntSlider(position, "Level Minimum", parameterOne, 1,100);
            }
            if (selectedPredicate == EPredicate.OverLevel)
            {
                position.y += propHeight;
                DrawIntSlider(position, "Level Maximum", parameterOne, 1,100);
            }

All of these should be ParameterZero, or alternatively, you could change the int.TryParse to use parameters[1] instead of parameters[0]

To test this, I actually changed the code in BaseStats because it was quicker… I didn’t have to edit Bart’s Dialogue that way:

This resolved the issue. Remember that for predicates that only compare to a level, only ParameterZero should be used. For predicates that include something besides (HasTrait, for example), then the int is on Parameter1.

Hello Brian.
Thank you very to have spend time in my problem.
So I replaced the 0 by 1 for the 3 conditions in base stat and made some couples of test in it looks working in dialogue node.
For items the test level work perfectly too.
But I 'm little bit confuse because in your last reply you write

“Remember that for predicates that only compare to a level, only ParameterZero should be used”

but my problem with level checking is fixed by replacing 0 by 1 .
But it’s not too much important, the essential being that working now :slight_smile: Thanks again.
Take care.
François

That may have been a bit confusing, It was pretty late when I wrote it.

What I meant was that generally speaking, we should only have the number of parameters we actually need… Since HasLevel needs only one parameter, logically it should be ParameterZero. On the other hand, if it requires two parameters, such as HasTrait, ParameterZero would be the trait, and ParameterOne would be the required level of the trait.

You could leave it as is, and check parameters[1] instead of parameters[0], because if you do change the parameter in the PredicatePropertyDrawer that would mean that you would need to change any existing uses of HasLevel, OverLevel, UnderLevel within your Dialogues. Because it was 1 in the morning for me when I finally discovered the mismatch, I saved time when testing and just changed the code in BaseStats to use parameters[1] instead of parameters[0]

Perfect :wink:
As usual.
Thank you to spend time to explain, as usual :slight_smile:
Have a nice day, night ? :slight_smile:
François