[#14] rozbudowa stats + dodanie modyfikatorów
This commit is contained in:
@@ -12,57 +12,109 @@ using UnityEngine;
|
||||
namespace RPGCore.Stats.ObjectModules.StatsObjectModule
|
||||
{
|
||||
[Serializable]
|
||||
[ObjectModule(
|
||||
name: "[Stats] Statistics",
|
||||
description: "Attached to any implementation of <b>BaseObject</b>. " +
|
||||
"Create scriptable object <b>"+nameof(StatValueDefinitionSO)+"</b> to define new stats. " +
|
||||
"Stats will be automatically created in any matching implementation of <b>BaseObject</b>. " +
|
||||
"Remember to add all definitions to generated <b>"+nameof(StatsSettings)+"</b>."
|
||||
)]
|
||||
public class StatsModule : ObjectModule<BaseObject>
|
||||
{
|
||||
[SerializeField]
|
||||
[SerializableDictionary("Stat Definition", "Base Value", isKeyEditable: false)]
|
||||
private SerializableDictionary<StatDefinitionSO, int> _serializedStats;
|
||||
[SerializableDictionary("Value Definition", "Base Value", isKeyEditable: false)]
|
||||
private SerializableDictionary<StatValueDefinitionSO, int> _serializedValues;
|
||||
[SerializeField]
|
||||
[SerializableDictionary("Resource Definition", "Default Value", isKeyEditable: false)]
|
||||
private SerializableDictionary<StatResourceDefinitionSO, int> _serializedResources;
|
||||
|
||||
private Dictionary<StatDefinitionSO, StatValue> _stats = new();
|
||||
private List<StatDefinitionSO> _queue = new();
|
||||
private Dictionary<StatValueDefinitionSO, StatValue> _values = new();
|
||||
private Dictionary<StatResourceDefinitionSO, StatResource> _resources = new();
|
||||
private List<StatValueDefinitionSO> _queue = new();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
FixSerializedStats();
|
||||
FixSerializedStatValues();
|
||||
FixSerializedStatResources();
|
||||
}
|
||||
|
||||
private void FixSerializedStats()
|
||||
private void FixSerializedStatValues()
|
||||
{
|
||||
// fixing only when variable is serialized (sometimes OnValidate can be called before deserializing on project open)
|
||||
if (_serializedStats == null) return;
|
||||
if (_serializedValues == null) return;
|
||||
|
||||
// All stat definitions that should be added to this object...
|
||||
var correctStats = SettingsManager.Get<StatsSettings>().allStats
|
||||
.Where(stat => stat)
|
||||
.Where(stat => stat.attachedTo.type.IsAssignableFrom(parent.GetType()))
|
||||
// All value definitions that should be added to this object...
|
||||
var correctValues = SettingsManager.Get<StatsSettings>().allValues
|
||||
.Where(value => value)
|
||||
.Where(value => value.attachedTo.type.IsAssignableFrom(parent.GetType()))
|
||||
.ToList();
|
||||
|
||||
//... but also remember that some of them can override other's, overriden ones should not be here
|
||||
correctStats.Select(stat => stat.overrides)
|
||||
.Where(overridenStat => overridenStat)
|
||||
.ForEach(overridenStat => correctStats.Remove(overridenStat));
|
||||
correctValues.Select(value => value.overrides)
|
||||
.Where(overridenValue => overridenValue)
|
||||
.ForEach(overridenValue => correctValues.Remove(overridenValue));
|
||||
|
||||
// ADD MISSING
|
||||
var missingStats = correctStats.Except(_serializedStats.Keys).ToList();
|
||||
if (missingStats.Any())
|
||||
var missingValues = correctValues.Except(_serializedValues.Keys).ToList();
|
||||
if (missingValues.Any())
|
||||
{
|
||||
missingStats.ForEach(missingStat => _serializedStats.Add(missingStat, 0));
|
||||
var missingStatsString = string.Join(", ", missingStats.Select(s => s.name));
|
||||
Debug.LogWarning($"[StatsModule] automatically adding missing stats: {missingStatsString}", parent);
|
||||
missingValues.ForEach(missingValue => _serializedValues.Add(missingValue, 0));
|
||||
var missingValuesString = string.Join(", ", missingValues.Select(s => s.name));
|
||||
Debug.LogWarning($"[StatsModule] automatically adding missing stats: {missingValuesString}", parent);
|
||||
UnityEditor.EditorUtility.SetDirty(parent.gameObject);
|
||||
}
|
||||
|
||||
// REMOVE INVALID
|
||||
var invalidStats = _serializedStats.Keys.Except(correctStats).ToList();
|
||||
if (invalidStats.Any())
|
||||
var invalidValues = _serializedValues.Keys.Except(correctValues).ToList();
|
||||
if (invalidValues.Any())
|
||||
{
|
||||
invalidStats.ForEach(invalidStat => _serializedStats.Remove(invalidStat));
|
||||
var invalidStatsString = string.Join(", ", invalidStats.Select(missingStat => missingStat.name));
|
||||
Debug.LogWarning($"[StatsModule] automatically removing invalid stats: {invalidStatsString}", parent);
|
||||
invalidValues.ForEach(invalidValue => _serializedValues.Remove(invalidValue));
|
||||
var invalidValuesString = string.Join(", ", invalidValues.Select(missingValue => missingValue.name));
|
||||
Debug.LogWarning($"[StatsModule] automatically removing invalid values: {invalidValuesString}", parent);
|
||||
UnityEditor.EditorUtility.SetDirty(parent.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void FixSerializedStatResources()
|
||||
{
|
||||
// fixing only when variable is serialized (sometimes OnValidate can be called before deserializing on project open)
|
||||
if (_serializedResources == null) return;
|
||||
|
||||
// All stat definitions that should be added to this object...
|
||||
var correctResources = SettingsManager.Get<StatsSettings>().allResources
|
||||
.Where(resource => resource)
|
||||
.Where(resource => resource.valueDefinitionSO.attachedTo.type.IsAssignableFrom(parent.GetType()))
|
||||
.ToList();
|
||||
|
||||
//... but also remember that some of them can override other's, overriden ones should not be here
|
||||
var baseValues = SettingsManager.Get<StatsSettings>().allValues
|
||||
.Where(value => value)
|
||||
.Where(value => !value.overrides)
|
||||
.ToList();
|
||||
correctResources
|
||||
.Where(resource => !baseValues.Contains(resource.valueDefinitionSO))
|
||||
.ForEach(resource => correctResources.Remove(resource));
|
||||
|
||||
// ADD MISSING
|
||||
var missingResources = correctResources.Except(_serializedResources.Keys).ToList();
|
||||
if (missingResources.Any())
|
||||
{
|
||||
missingResources.ForEach(missingResource => _serializedResources.Add(missingResource, 0));
|
||||
var missingResourcesString = string.Join(", ", missingResources.Select(s => s.name));
|
||||
Debug.LogWarning($"[StatsModule] automatically adding missing resources: {missingResourcesString}", parent);
|
||||
UnityEditor.EditorUtility.SetDirty(parent.gameObject);
|
||||
}
|
||||
|
||||
// REMOVE INVALID
|
||||
var invalidResources = _serializedResources.Keys.Except(correctResources).ToList();
|
||||
if (invalidResources.Any())
|
||||
{
|
||||
invalidResources.ForEach(invalidResource => _serializedResources.Remove(invalidResource));
|
||||
var invalidResourcesString = string.Join(", ", invalidResources.Select(missingStat => missingStat.name));
|
||||
Debug.LogWarning($"[StatsModule] automatically removing invalid resources: {invalidResourcesString}", parent);
|
||||
UnityEditor.EditorUtility.SetDirty(parent.gameObject);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,19 +123,20 @@ namespace RPGCore.Stats.ObjectModules.StatsObjectModule
|
||||
[DynamicValueProvider]
|
||||
internal StatValue StatValueProvider(string name)
|
||||
{
|
||||
return _stats.GetValueOrDefault(_stats.Keys.FirstOrDefault(def => def.name == name));
|
||||
return _values.GetValueOrDefault(_values.Keys.FirstOrDefault(def => def.name == name));
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Create instances of all stats that will be used runtime
|
||||
CreateStats();
|
||||
CreateResources();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// First stats refresh
|
||||
_stats.Keys.ForEach(AddToQueueRefresh);
|
||||
_values.Keys.ForEach(AddToQueueRefresh);
|
||||
QueueRefresh();
|
||||
}
|
||||
|
||||
@@ -93,32 +146,55 @@ namespace RPGCore.Stats.ObjectModules.StatsObjectModule
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Really important part - we create stats for runtime usage.
|
||||
/// <see cref="StatDefinitionSO"/> can also override another one, in that case <see cref="StatValue"/>
|
||||
/// uses main definition, but it is visible as that overriden one.
|
||||
/// Really important part - we create values for runtime usage.
|
||||
/// </summary>
|
||||
private void CreateStats()
|
||||
{
|
||||
_serializedStats.DictForEach((statDefinition, baseValue) =>
|
||||
_serializedValues.DictForEach((definition, baseValue) =>
|
||||
{
|
||||
var baseDefinition = statDefinition.GetBaseDefinition();
|
||||
var statValue = new StatValue(statDefinition, baseValue, parent);
|
||||
statValue.sourceContext.SetSource("object", parent);
|
||||
_stats.Add(baseDefinition, statValue);
|
||||
var baseDefinition = definition.GetBaseDefinition();
|
||||
var value = new StatValue(definition, baseValue, parent);
|
||||
value.sources.SetSource("object", parent);
|
||||
value.sources.SetSource("stats", this);
|
||||
_values.Add(baseDefinition, value);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Really important part - we create resources for runtime usage.
|
||||
/// </summary>
|
||||
private void CreateResources()
|
||||
{
|
||||
_serializedResources.DictForEach((definition, startingResource) =>
|
||||
{
|
||||
_resources.Add(
|
||||
definition,
|
||||
new StatResource(definition, startingResource, Get(definition.valueDefinitionSO))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns runtime values for given stat definition.
|
||||
/// </summary>
|
||||
/// <param name="statDefinitionSO">Runtime stats will be found by this definition (or overriden definition if given)</param>
|
||||
/// <returns>Stat values (baseValue, value, resource)</returns>
|
||||
public StatValue Get(StatDefinitionSO statDefinitionSO)
|
||||
/// <param name="valueDefinitionSO">Runtime stats will be found by this definition (or overriden definition if given)</param>
|
||||
/// <returns>Stat value (baseValue, value)</returns>
|
||||
public StatValue Get(StatValueDefinitionSO valueDefinitionSO)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying) CreateStats();
|
||||
#endif
|
||||
return _stats.GetValueOrDefault(statDefinitionSO.GetBaseDefinition());
|
||||
return _values.GetValueOrDefault(valueDefinitionSO.GetBaseDefinition());
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Get(StatValueDefinitionSO)"/>
|
||||
/// <returns>Stat resource (baseValue, value, resource)</returns>
|
||||
public StatResource Get(StatResourceDefinitionSO resourceDefinitionSO)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying) CreateResources();
|
||||
#endif
|
||||
return _resources.GetValueOrDefault(resourceDefinitionSO);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -136,9 +212,9 @@ namespace RPGCore.Stats.ObjectModules.StatsObjectModule
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select <see cref="StatDefinitionSO"/> along with its dependencies to refresh next frame.
|
||||
/// Select <see cref="StatValueDefinitionSO"/> along with its dependencies to refresh next frame.
|
||||
/// </summary>
|
||||
public void AddToQueueRefresh(StatDefinitionSO statDefinitionSO)
|
||||
public void AddToQueueRefresh(StatValueDefinitionSO statDefinitionSO)
|
||||
{
|
||||
statDefinitionSO = statDefinitionSO.GetBaseDefinition();
|
||||
|
||||
@@ -146,7 +222,7 @@ namespace RPGCore.Stats.ObjectModules.StatsObjectModule
|
||||
if (!_queue.Contains(statDefinitionSO)) _queue.Add(statDefinitionSO);
|
||||
|
||||
// All definitions that uses this one as dependency should be refreshed too
|
||||
_stats.Keys
|
||||
_values.Keys
|
||||
.Where(otherStatDefinition => otherStatDefinition.dependencies.Contains(statDefinitionSO))
|
||||
.ForEach(AddToQueueRefresh);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user