Files
TheVVaS-Assets/RPGCore.Stats/Runtime/ObjectModules/StatsObjectModule/StatDefinitionSO.cs
T
2026-04-25 23:37:10 +02:00

141 lines
5.8 KiB
C#

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 <b>{nameof(attachedTo)}</b> MUST BE child child type of <b>{nameof(attachedTo)}</b>");
overrides = null;
return;
}
name = overrides.name;
type = overrides.type;
}
/// <summary>
/// <see cref="StatsModule"/> has custom value provider <see cref="StatsModule.StatValueProvider"/> that matches stats by their name.
/// We will match these names with marker's parameter to create list of other <see cref="StatDefinitionSO"/> that this one uses.
/// Main purpose of dependencies is caching, specially when there is a lot of definitions and dependencies.
/// </summary>
private void FixDependencies()
{
var tempDependencies = new List<StatDefinitionSO>();
var allDefinitions = SettingsManager.Get<StatsSettings>().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 <b>{(string)memberParameter}</b> doesn't exist " +
$"or is not added to {nameof(StatsSettings)}!" +
$"Marker <b>{marker.wholeMarker}</b> is invalid!", this);
}
}
dependencies = tempDependencies.Distinct().ToArray();
}
/// <summary>
/// Iterates via dependencies to check if none of them loops.
/// If looping is found then dependencies will be cleared, preventing application freeze!
/// </summary>
/// <param name="definitionsChain"></param>
private void CheckDependenciesLooping(List<StatDefinitionSO> definitionsChain = null)
{
definitionsChain ??= new List<StatDefinitionSO>();
if (definitionsChain.Contains(this))
{
definitionsChain[0].dependencies = Array.Empty<StatDefinitionSO>();
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
/// <summary>
/// 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.
/// </summary>
/// <returns>Last overriden definition</returns>
public StatDefinitionSO GetBaseDefinition()
{
var tempDefinition = this;
while (tempDefinition.overrides)
tempDefinition = tempDefinition.overrides;
return tempDefinition;
}
}
}