Error when displaying

Issue #939 resolved
Václav Soukup created an issue

Hi Iam geeting an error when opening a GO with a monobehaviour below. I cannot seem to be able to trace what is the problem as this is somehow connected to Odin. Is it? Can you please help? I do not see anything in the inspector for this monobehaviour and the error is repeating fast all the time.

I have a version 3.1.12.2 and Unity 2022.3.1f (Silicon) on Mac M1, not Editor only mode

NullReferenceException: Object reference not set to an instance of an object
UnityEditor.EditorGUI.DoTextField (UnityEditor.EditorGUI+RecycledTextEditor editor, System.Int32 id, UnityEngine.Rect position, System.String text, UnityEngine.GUIStyle style, System.String allowedletters, System.Boolean& changed, System.Boolean reset, System.Boolean multiline, System.Boolean passwordField, UnityEngine.GUIStyle cancelButtonStyle, System.Boolean checkTextLimit) (at /Users/bokken/build/output/unity/unity/Editor/Mono/EditorGUI.cs:1346)
UnityEditor.EditorGUI.DoTextField (UnityEditor.EditorGUI+RecycledTextEditor editor, System.Int32 id, UnityEngine.Rect position, System.String text, UnityEngine.GUIStyle style, System.String allowedletters, System.Boolean& changed, System.Boolean reset, System.Boolean multiline, System.Boolean passwordField) (at /Users/bokken/build/output/unity/unity/Editor/Mono/EditorGUI.cs:928)
UnityEditor.EditorGUI.TextFieldInternal (UnityEngine.Rect position, System.String text, UnityEngine.GUIStyle style) (at /Users/bokken/build/output/unity/unity/Editor/Mono/EditorGUI.cs:1848)
UnityEditor.EditorGUI.TextField (UnityEngine.Rect position, System.String text, UnityEngine.GUIStyle style) (at /Users/bokken/build/output/unity/unity/Editor/Mono/EditorGUI.cs:8021)
Sirenix.Utilities.Editor.SirenixEditorGUI.SearchField (UnityEngine.Rect rect, System.String searchText, System.Boolean forceFocus, System.String controlName) (at C:/Sirenix/Sirenix Solution/Sirenix.Utilities.Editor/GUI/SirenixEditorGUI.cs:1978)
Sirenix.OdinInspector.Editor.PropertySearchFilter.DrawDefaultSearchFieldLayout (UnityEngine.GUIContent label) (at C:/Sirenix/Sirenix Solution/Sirenix.OdinInspector.Editor/Misc/PropertySearchFilter.cs:302)
Sirenix.OdinInspector.Editor.PropertyTree.DrawSearch () (at C:/Sirenix/Sirenix Solution/Sirenix.OdinInspector.Editor/Core/PropertyTree.cs:569)
Sirenix.OdinInspector.Editor.PropertyTree.DrawProperties () (at C:/Sirenix/Sirenix Solution/Sirenix.OdinInspector.Editor/Core/PropertyTree.cs:553)
Sirenix.OdinInspector.Editor.PropertyTree.Draw (System.Boolean applyUndo) (at C:/Sirenix/Sirenix Solution/Sirenix.OdinInspector.Editor/Core/PropertyTree.cs:448)
Sirenix.OdinInspector.Editor.OdinEditor.DrawTree () (at C:/Sirenix/Sirenix Solution/Sirenix.OdinInspector.Editor/Drawers/OdinEditor.cs:97)
Sirenix.OdinInspector.Editor.OdinEditor.DrawOdinInspector () (at C:/Sirenix/Sirenix Solution/Sirenix.OdinInspector.Editor/Drawers/OdinEditor.cs:254)
Sirenix.OdinInspector.Editor.OdinEditor.OnInspectorGUI () (at C:/Sirenix/Sirenix Solution/Sirenix.OdinInspector.Editor/Drawers/OdinEditor.cs:79)
UnityEditor.UIElements.InspectorElement+<>c__DisplayClass72_0.<CreateInspectorElementUsingIMGUI>b__0 () (at /Users/bokken/build/output/unity/unity/Editor/Mono/UIElements/Inspector/InspectorElement.cs:713)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&) (at /Users/bokken/build/output/unity/unity/Modules/IMGUI/GUIUtility.cs:190)

This is my code causing the problem:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using CharacterLogic.StatCalculations;
using Core;
using Core.EngineExtentions;
using DG.Tweening;
using Items.Inventory;
using Pathfinding;
using RPGLogic;
using RPGLogic.StatBases;
using RPGLogic.StatEffects;
using Sirenix.OdinInspector;
using UI;
using UnityEngine;
using VisualEffects;

namespace CharacterLogic
{
    [Searchable]
    public class CharacterStats : MonoBehaviour
    {
        [SerializeField] private ComboResolver comboResolver;
        public ComboResolver ComboResolver => comboResolver;

        [SerializeField] private CharacterStatsDataSO characterStatsDataSo;

        //attributes, stats etc, including properties
        [SerializeField] private Progression soul;

        [ListDrawerSettings(ShowIndexLabels = true, ListElementLabelName = "statType")] [SerializeField]
        private List<Stat> stats = new List<Stat>();

        [ListDrawerSettings(ShowIndexLabels = true, ListElementLabelName = "statType")] [SerializeField]
        private List<Progression> attributes = new List<Progression>();

        [ListDrawerSettings(ShowIndexLabels = true, ListElementLabelName = "statType")] [SerializeField]
        private List<Skill> skills = new List<Skill>();

        public Progression Soul
        {
            get => soul;
            set
            {
                soul = value;
                RecalculateStatInfluencing();
            }
        }

        public List<Stat> Stats
        {
            get => stats;
            set
            {
                stats = value;
                RecalculateStatInfluencing();
            }
        }

        public List<Progression> Attributes
        {
            get => attributes;
            set
            {
                attributes = value;
                RecalculateStatInfluencing();
            }
        }

        public List<Skill> Skills
        {
            get => skills;
            set
            {
                skills = value;
                RecalculateStatInfluencing();
            }
        }

        public int ViewDistance
        {
            get
            {
                var lightRadiusStat = StatOperations.GetStatTypeFromEnum(StatTypeEnum.LightRadius);

                return (int)GetStat(lightRadiusStat).ActualValue;

            }
        }


        private bool isCalculatingStatInfluencing = false;
        public bool IsCalculatingStatInfluencing => isCalculatingStatInfluencing;


        [SerializeField] private bool aliveState = true;

        public bool IsAlive
        {
            get => aliveState;
            set => aliveState = value;
        }

        public CharacterCombatActions characterCombatActions;

        public Action<CharacterStats> OnKilled;

        private MainAnimationWrapper mainAnimationWrapper;
        private DamageTextSpawner damageTextSpawnerSpawner;
        private SpawnGore goreSpawner;
        [SerializeField] private SkillGoverningAttributes SkillGoverningAttributes;

        private InventoryEquipController inventoryEquipController;
        [SerializeField]private CharacterAbilities characterAbilities;
        public CharacterAbilities CharacterAbilities => characterAbilities;

        public InventoryEquipController InventoryEquipController => inventoryEquipController;

        private AppliedStatusEffectList appliedStatusEffects = new AppliedStatusEffectList();
        public AppliedStatusEffectList AppliedStatusEffects => appliedStatusEffects;

        [SerializeField] private GameObject bodyGfx;


        [HideInEditorMode] [ShowInInspector]
        public bool IsOnTurn
        {
            get
            {
                if (!Application.isPlaying) return false;
                return GameComponentAggregator.Instance.TurnController.IsCharaterOnTurn(this);
            }
        }

        private void Awake()
        {
            characterCombatActions = transform.root.GetComponentInChildren<CharacterCombatActions>();
            var troot = transform.root;
            damageTextSpawnerSpawner = troot.GetComponentInChildren<DamageTextSpawner>();
            goreSpawner = EngineExtention.FindInActiveObjectByName("GoreSpawner").GetComponent<SpawnGore>();
            characterStatsDataSo = troot.GetComponentInChildren<CharacterDataWrapper>().CharacterStatsDataSo;
            SetAllStatsFromSOs();
            mainAnimationWrapper = troot.GetComponentInChildren<MainAnimationWrapper>();
        }

        //this should be in characted data wrapper but I was not lucky putting it there, too much work...
        private void SetAllStatsFromSOs()
        {
            soul = characterStatsDataSo.soul.GetDeepCopyValues();
            soul.ValueChangedExcludingModifications += RecalculateStatInfluencing;
            foreach (var stat in characterStatsDataSo.stats)
            {
                var statToAdd = stat.GetDeepCopyValues();
                statToAdd.owner = this;
                stats.Add(statToAdd);
                statToAdd.ValueChangedExcludingModifications += RecalculateStatInfluencing;
            }
            foreach (var attribute in characterStatsDataSo.attributes)
            {
                var attributeToAdd = attribute.GetDeepCopyValues();
                attributeToAdd.owner = this;
                attributes.Add(attributeToAdd);
                attributeToAdd.ValueChangedExcludingModifications += RecalculateStatInfluencing;
            }
            foreach (var skill in characterStatsDataSo.skills)
            {
                var skillToAdd = skill.GetDeepCopyValues();
                skillToAdd.owner = this;
                skills.Add(skillToAdd);
                skillToAdd.ValueChangedExcludingModifications += RecalculateStatInfluencing;
            }
        }

        private void Start()
        {
            inventoryEquipController = transform.root.GetComponentInChildren<InventoryEquipController>();
            inventoryEquipController.InventoriesLoaded += RecalculateStatInfluencing;
            inventoryEquipController.AnyItemEquiped += RecalculateStatInfluencing;
            characterCombatActions.OnTurnStarted += OnCharacterTurnStarted;
        }

        // private void OnDestroy()
        // {
        //     inventoryEquipController.InventoriesLoaded -= InitiateAllItemEquipEventHandlers;
        //     characterTurnController.OnTurnStarted -= StartTurn;
        // }


        #region GetStat and other

        public Stat GetStatOrAttributeOrSkill(StatType item)
        {
            Stat itemToReturn = null;
            if (item == StatOperations.GetStatTypeFromEnum(StatTypeEnum.Soul))
                itemToReturn = Soul;

            foreach (Stat i in Stats)
            {
                if (i.statType == item)
                    return i;
            }

            foreach (Progression i in Attributes)
            {
                if (i.statType == item)
                    return i;
            }

            foreach (Skill i in Skills)
            {
                if (i.statType == item)
                    return i;
            }

            if (itemToReturn == null)
                new Exception(this.gameObject.name + " - " +
                              string.Concat("Critical error: Stat or Attribute or Skill not found: ", item));
            return itemToReturn;
        }

        public Stat GetStat(StatType statType)
        {
            Stat itemToReturn = null;

            foreach (Stat i in Stats)
            {
                if (i.statType == statType)
                    return i;
            }

            new Exception(string.Concat("Critical error: Stat not found: ", statType));
            return itemToReturn;
        }

        public Progression GetAttribute(StatType item)
        {
            Progression itemToReturn = null;
            foreach (Progression i in Attributes)
            {
                if (i.statType == item)
                    return i;
            }

            new Exception(string.Concat("Critical error: Attribute not found: ", item));
            return itemToReturn;
        }

        public Skill GetSkill(StatType item)
        {
            Skill itemToReturn = null;
            foreach (Skill i in Skills)
            {
                if (i.statType == item)
                    return i;
            }

            new Exception(string.Concat("Critical error: Skill not found: ", item));
            return itemToReturn;
        }

        public SkillCheckResult PerformSkillCheck(StatType stat, int difficulty)
        {
            return new SkillCheckResult(GetStatOrAttributeOrSkill(stat), difficulty);
        }

        #endregion

        #region Fighting and defence //to be moved to other file/class

//        private ActionOutcomeAndXPInfo actualActionOutcomeAndXpBeingReceived; // damage done over more instances

        public void DefendOrApplyHeal(ActionOutcomeAndXPInfo thisActionOutcomeAndXp)
        {
            var asf = thisActionOutcomeAndXp.AppliedStatusEffect;


            if (asf != null && asf.StatusEffectData.hasRegeneration)
                GetStatOrAttributeOrSkill(asf.StatusEffectData.regeneration.InfluencedStat)
                    .ConsumeValue(thisActionOutcomeAndXp.HealAppliedFromAllRanges);

            if (thisActionOutcomeAndXp.DamageDone > 0)
            {
                foreach (var dmg in thisActionOutcomeAndXp.AppliedDamages)
                {
                    GetStatOrAttributeOrSkill(dmg.damage.influencedStat).ConsumeValue(dmg.ReducedDamage);
                }
            }
        }

        public void DoAllPostDamageProcessing(ActionOutcomeAndXPInfo actualDamage)
        {
            //apply damage, display it and spawn blood
            if (actualDamage.DamageDone != 0)
                goreSpawner.Spawn(transform.position,
                    actualDamage.DamageDonePercentageOfMax);
            damageTextSpawnerSpawner.Spawn(actualDamage);

            //todo maybe just add xpinfo to some logger or something

            if (GetStat(StatOperations.GetStatTypeFromEnum(StatTypeEnum.Health)).ActualValue < 0)
                KillChracter();
        }

        public void KillChracter()
        {
            // do not destroy player object
            if (!transform.root.CompareTag("Player"))
            {
                aliveState = false;
                goreSpawner.Spawn(transform.position, 1);
                transform.root.GetComponent<SingleNodeBlocker>().Unblock();
                StartCoroutine(DoDeathAnimation());
                OnKilled?.Invoke(this);
            }
            else
            {
                aliveState = false;
                OnKilled?.Invoke(this);
                //TODO: missing player death - see on gui
            }
        }

        private IEnumerator DoDeathAnimation()
        {
            mainAnimationWrapper.SetDisplayPriorityToCompleteChar("MapObjects");
            Tween animationTween = bodyGfx.transform.DORotate(new Vector3(0, 0, -90), 0.5f).SetEase(Ease.InCirc);
            yield return animationTween.WaitForCompletion();
        }

        #endregion

        #region Exp Aggregation

        public void GainAndDistributeXPFromAttack(List<ActionOutcomeAndXPInfo> gainedXPs)
        {
            foreach (var gainedXP in gainedXPs)
            {
                float experience = 0f;
                experience += gainedXP.xpGivenToAttackerFromGivenDamage;

                var ability = gainedXP.ability.AbilityContainer;
                var item = gainedXP.ability.item;

                ability.Progression.GainXpAndDistributeToParent(experience);


                if (item != null)
                {
                    item.progression.GainXpAndDistributeToParent(experience);
                    GetSkill(item.governingSkill).GainXpAndDistributeToParent(experience);
                    SkillGoverningAttributes.MultiplyAndDistributeExpForAttributes(
                        this, experience, item.governingSkill);
                }
                else
                {
                    GetSkill(ability.governingSkill).GainXpAndDistributeToParent(experience);
                    SkillGoverningAttributes.MultiplyAndDistributeExpForAttributes(
                        this, experience, ability.governingSkill);
                }

                soul.GainXpAndDistributeToParent(experience);
            }
        }

        public void GainAndDistributeXPFromDefence(ActionOutcomeAndXPInfo gainedXP)
        {
            float experience = 0f;
            experience += gainedXP.xpGivenToDefenderFromReceivedDamage;

            var itemsEquiped = inventoryEquipController.Equips.Count(x => x.EquipedItem != null);

            if (itemsEquiped > 0)
            {
                var expForItems = experience / itemsEquiped;
                foreach (var item in inventoryEquipController.Equips.ConvertAll(x => x.EquipedItem))
                {
                    if (item == null) continue;

                    var count = gainedXP.AppliedDamages.Count(dmg =>
                        item.modifiers.Any(x => x.modifiedStat == dmg.DamageType.ReductionDefender));

                    item.progression.GainXpAndDistributeToParent(expForItems*count);

                    if (item.governingSkill == null) continue;

                    GetSkill(item.governingSkill).GainXpAndDistributeToParent(expForItems*count);
                    SkillGoverningAttributes.MultiplyAndDistributeExpForAttributes(
                        this, expForItems*count, item.governingSkill);
                }
            }

            soul.GainXpAndDistributeToParent(experience);
        }

        public float GiveXPFromThisCharacter()
        {
            //TODO:missing the summarization and main level
            float returnXP = 0;
            foreach (var stat in Stats)
                returnXP += stat.ActualValue;
            foreach (var item in inventoryEquipController.Equips)
            {
                if (item.EquipedItem != null)
                    returnXP += item.EquipedItem.GiveXP();
            }
            return returnXP;
        }

        #endregion

        #region Stat influencing and status effects

        public Action StatInfluencingRecalculated;

        private void RecalculateStatInfluencing()
        {
            if (!isCalculatingStatInfluencing)
            {
                isCalculatingStatInfluencing = true;
                foreach (var stat in stats)
                {
                    stat.ResetValues();
                }
                GameComponentAggregator.Instance.statInfluencing.ApplyStatModifications(this);
                isCalculatingStatInfluencing = false;
                characterAbilities.RecalculateAbilityStatInfluencing(this);
                StatInfluencingRecalculated?.Invoke();
            }
        }

        public AppliedStatusEffect AddStatusEffect(AppliedStatusEffect asfToAdd)
        {
            //todo prevent effects multiplication
            appliedStatusEffects.Add(asfToAdd);
            return asfToAdd;
        }

        private void MakeStatusEffectTicks()
        {
            RecalculateStatInfluencing();

            foreach (var asf in appliedStatusEffects)
            {
                DoSingleStatusEffectTick(asf);
            }

            appliedStatusEffects.RemoveAll(x => !x.IsDurationValid);
        }

        public void DoSingleStatusEffectTick(AppliedStatusEffect asf)
        {
            var xpInfo = asf.MakeDurationTickAndApplySE(this); 

            xpInfo.CalculateXP();

            GainAndDistributeXPFromDefence(xpInfo);

            xpInfo.Attacker.GainAndDistributeXPFromAttack(xpInfo.ToOneObjectList());

            DefendOrApplyHeal(xpInfo);

            DoAllPostDamageProcessing(xpInfo);

        }

        #endregion

        //this is public only because of testing
        public void OnCharacterTurnStarted()
        {
            GetStat(StatOperations.GetStatTypeFromEnum(StatTypeEnum.ActionPoints)).FullHealValue();
            MakeStatusEffectTicks();
        }
    }
}

Comments (4)

  1. Yunus Alkan

    Thank you for reply @TorVestergaard.

    I wanted to go with Unity 2022 LTS and was waiting for this 'releasing very shortly patch' :). 2 days have been passed and still no patch released.

    Could you please tell us exact date for this patch?

  2. Log in to comment