using System; using System.Collections.Generic; using System.Linq; using RPGCore.Core.Objects; using RPGCoreCommon.DynamicValues; using RPGCoreCommon.Helpers; using RPGCoreCommon.Helpers.CustomTypes; using RPGCoreCommon.Helpers.PropertyAttributeDrawers; using RPGCoreCommon.Settings; using UnityEngine; namespace RPGCore.Stats.ObjectModules.StatsObjectModule { [CreateAssetMenu(menuName = "RPG Core/Object Stat Definition")] public sealed class StatDefinitionSO : ScriptableObject { [SerializableType(typeof(BaseObject), allowAbstract: true)] public SerializableType attachedTo; public StatType type; public StatDefinitionSO overrides; [ReadOnly(true)] public StatDefinitionSO[] dependencies = {}; [DynamicValue(DynamicValueType.ByDynamicTypes)] public DynamicValue dynamicValue = new(); #if UNITY_EDITOR private void OnValidate() { FixDynamicValueType(); FixOverride(); FixDependencies(); CheckDependenciesLooping(); } private void FixDynamicValueType() { dynamicValue.RemoveDynamicTypes(); dynamicValue.SetDynamicType("object", attachedTo); } private void FixOverride() { if (!overrides) return; // Without attachedTo selected we can't be sure if this overriding can be even done if (attachedTo == null) { Debug.LogWarning($"{nameof(attachedTo)} is required before stat overriding"); overrides = null; return; } // We can override only child of "attachedTo" object, otherwise it is pointless if (overrides.attachedTo.type == attachedTo.type || overrides.attachedTo.type.IsAssignableFrom(attachedTo.type) == false) { Debug.LogWarning($"When overriding this {nameof(attachedTo)} MUST BE child child type of {nameof(attachedTo)}"); overrides = null; return; } name = overrides.name; type = overrides.type; } /// /// has custom value provider that matches stats by their name. /// We will match these names with marker's parameter to create list of other that this one uses. /// Main purpose of dependencies is caching, specially when there is a lot of definitions and dependencies. /// private void FixDependencies() { var tempDependencies = new List(); var allDefinitions = SettingsManager.Get().allStats; foreach (var marker in dynamicValue.GetMarkers()) { var members = marker.GetMembers(out var membersParameters); for (var i = 0; i < members.Length; i++) { var member = members[i]; var memberParameter = membersParameters[i]; // Dependencies are created only by StatsModule's StatValueProvider! if (member.DeclaringType != typeof(StatsModule)) continue; if (member.Name != nameof(StatsModule.StatValueProvider)) continue; var dependency = allDefinitions.FirstOrDefault(definition => definition.name.Equals((string)memberParameter, StringComparison.OrdinalIgnoreCase)); if (dependency) { tempDependencies.Add(dependency.GetBaseDefinition()); break; } Debug.LogError($"Stat definition with name {(string)memberParameter} doesn't exist " + $"or is not added to {nameof(StatsSettings)}!" + $"Marker {marker.wholeMarker} is invalid!", this); } } dependencies = tempDependencies.Distinct().ToArray(); } /// /// Iterates via dependencies to check if none of them loops. /// If looping is found then dependencies will be cleared, preventing application freeze! /// /// private void CheckDependenciesLooping(List definitionsChain = null) { definitionsChain ??= new List(); if (definitionsChain.Contains(this)) { definitionsChain[0].dependencies = Array.Empty(); var definitionsChainString = definitionsChain.Append(this).Select(d => d.name).StringJoin(" -> "); Debug.LogError($"Looped dependencies found! {definitionsChainString}", this); return; } dependencies.ForEach(dependency => dependency.CheckDependenciesLooping(definitionsChain.Append(this).ToList())); } #endif /// /// Finds first element in this overriding chain. /// Thanks to OnValidate its impossible to create endless overriding loop, it is possible to override only child object. /// /// Last overriden definition public StatDefinitionSO GetBaseDefinition() { var tempDefinition = this; while (tempDefinition.overrides) tempDefinition = tempDefinition.overrides; return tempDefinition; } } }