[#14] rozbudowa stats + dodanie modyfikatorów

This commit is contained in:
2026-06-30 20:28:51 +02:00
parent 86b4a8a93f
commit 8326c4b279
33 changed files with 444 additions and 129 deletions
@@ -0,0 +1,148 @@
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 Value Definition")]
public class StatValueDefinitionSO : ScriptableObject
{
[SerializableType(typeof(BaseObject), allowAbstract: true)] public SerializableType attachedTo;
public StatValueDefinitionSO overrides;
[ReadOnly(true)] public StatValueDefinitionSO[] 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);
dynamicValue.SetDynamicType("stats", typeof(StatsModule));
}
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;
}
// Overriding only for same types to prevent overriding VALUE <=> RESOURCE
if (overrides.GetType() != GetType())
{
Debug.LogWarning("Only overriding of same stat types is allowed! VALUE and RESOURCE is not allowed!");
overrides = null;
return;
}
name = overrides.name;
}
/// <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="StatValueDefinitionSO"/> 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<StatValueDefinitionSO>();
var allDefinitions = SettingsManager.Get<StatsSettings>().allValues;
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<StatValueDefinitionSO> definitionsChain = null)
{
definitionsChain ??= new List<StatValueDefinitionSO>();
if (definitionsChain.Contains(this))
{
definitionsChain[0].dependencies = Array.Empty<StatValueDefinitionSO>();
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 StatValueDefinitionSO GetBaseDefinition()
{
var tempDefinition = this;
while (tempDefinition.overrides)
tempDefinition = tempDefinition.overrides;
return tempDefinition;
}
}
}