init
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RPGCore.Core;
|
||||
using RPGCore.Core.Objects;
|
||||
using RPGCoreCommon.DynamicValues;
|
||||
using RPGCoreCommon.Helpers;
|
||||
using RPGCoreCommon.Helpers.CustomTypes;
|
||||
using RPGCoreCommon.Settings;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCore.Stats.ObjectModules.StatsObjectModule
|
||||
{
|
||||
[Serializable]
|
||||
public class StatsModule : ObjectModule<BaseObject>
|
||||
{
|
||||
[SerializeField]
|
||||
[SerializableDictionary("Stat Definition", "Base Value", isKeyEditable: false)]
|
||||
private SerializableDictionary<StatDefinitionSO, int> _serializedStats;
|
||||
|
||||
private Dictionary<StatDefinitionSO, StatValue> _stats = new();
|
||||
private List<StatDefinitionSO> _queue = new();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
FixSerializedStats();
|
||||
}
|
||||
|
||||
private void FixSerializedStats()
|
||||
{
|
||||
// fixing only when variable is serialized (sometimes OnValidate can be called before deserializing on project open)
|
||||
if (_serializedStats == 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()))
|
||||
.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));
|
||||
|
||||
// ADD MISSING
|
||||
var missingStats = correctStats.Except(_serializedStats.Keys).ToList();
|
||||
if (missingStats.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);
|
||||
UnityEditor.EditorUtility.SetDirty(parent.gameObject);
|
||||
}
|
||||
|
||||
// REMOVE INVALID
|
||||
var invalidStats = _serializedStats.Keys.Except(correctStats).ToList();
|
||||
if (invalidStats.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);
|
||||
UnityEditor.EditorUtility.SetDirty(parent.gameObject);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[DynamicValueProvider]
|
||||
internal StatValue StatValueProvider(string name)
|
||||
{
|
||||
return _stats.GetValueOrDefault(_stats.Keys.FirstOrDefault(def => def.name == name));
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Create instances of all stats that will be used runtime
|
||||
CreateStats();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// First stats refresh
|
||||
_stats.Keys.ForEach(AddToQueueRefresh);
|
||||
QueueRefresh();
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (_queue.Count > 0) QueueRefresh();
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
private void CreateStats()
|
||||
{
|
||||
_serializedStats.DictForEach((statDefinition, baseValue) =>
|
||||
{
|
||||
var baseDefinition = statDefinition.GetBaseDefinition();
|
||||
var statValue = new StatValue(statDefinition, baseValue, parent);
|
||||
statValue.sourceContext.SetSource("object", parent);
|
||||
_stats.Add(baseDefinition, statValue);
|
||||
});
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying) CreateStats();
|
||||
#endif
|
||||
return _stats.GetValueOrDefault(statDefinitionSO.GetBaseDefinition());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshing stats, every stat only once even if queued multiple times.
|
||||
/// To ensure that dependencies should be refreshed before definition that is using it.
|
||||
/// </summary>
|
||||
private void QueueRefresh()
|
||||
{
|
||||
_queue
|
||||
.Select(def => Get(def).definition)
|
||||
.Sort((d1, d2) => d2.dependencies.Contains(d1) ? - 1 : d1.dependencies.Contains(d2) ? 1 : 0)
|
||||
.Select(Get)
|
||||
.ForEach(stat => stat.Refresh());
|
||||
_queue.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select <see cref="StatDefinitionSO"/> along with its dependencies to refresh next frame.
|
||||
/// </summary>
|
||||
public void AddToQueueRefresh(StatDefinitionSO statDefinitionSO)
|
||||
{
|
||||
statDefinitionSO = statDefinitionSO.GetBaseDefinition();
|
||||
|
||||
// Add definition to refresh queue
|
||||
if (!_queue.Contains(statDefinitionSO)) _queue.Add(statDefinitionSO);
|
||||
|
||||
// All definitions that uses this one as dependency should be refreshed too
|
||||
_stats.Keys
|
||||
.Where(otherStatDefinition => otherStatDefinition.dependencies.Contains(statDefinitionSO))
|
||||
.ForEach(AddToQueueRefresh);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user