141 lines
5.8 KiB
C#
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;
|
|
}
|
|
}
|
|
} |