init
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user