Files
VOIDRPG/Assets/90 - Scripts/Generic/Objects/Unit/UnitObjectDataCalculator.cs
T
2026-07-09 21:33:33 +02:00

177 lines
7.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Sirenix.OdinInspector;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using UnityEngine;
using VOID.Generic.Dict;
using VOID.Generic.Objects.Unit.DataCalculator;
namespace VOID.Generic.Objects.Unit
{
[Serializable]
public class UnitObjectDataCalculator : ObjectComponent<UnitObject>
{
#region Editor
[ShowInInspector]
[NonSerialized]
[ListDrawerSettings(
OnTitleBarGUI = "CreateMarkersInfoBox",
DraggableItems = false,
HideRemoveButton = true,
HideAddButton = true,
DefaultExpandedState = false)]
private List<string> _availableMarkers = new();
private void CreateMarkersInfoBox()
{
if (!SirenixEditorGUI.ToolbarButton(EditorIcons.Refresh)) return;
Debug.Log("Marker list refreshed!", parent);
CreateCalcGroups();
_availableMarkers = new List<string>();
_calcGroups.ForEach(calcGroup =>
calcGroup.GetMarkerItems()
.Select(item => $"{{{calcGroup.GetMarkerGroup()}.{item}}}")
.ForEach(marker => _availableMarkers.Add(marker)));
}
#endregion
#region Initialize
protected override void SelfInitialize()
{
MergeFormulas();
CreateCalcGroups();
CreateDependencyMap();
}
protected override void Initialize()
{
CalculateAllInstant();
}
private void MergeFormulas()
{
_mainAttributeFormulas.ForEach(formula => _formulas.Add(formula.Key, formula.Value));
_subAttributeFormulas.ForEach(formula => _formulas.Add(formula.Key, formula.Value));
_basicResourceFormulas.ForEach(formula => _formulas.Add(formula.Key, formula.Value));
_unitResourceFormulas.ForEach(formula => _formulas.Add(formula.Key, formula.Value));
}
private void CreateCalcGroups()
{
_calcGroups = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.Where(type => typeof(ICalcGroup).IsAssignableFrom(type) && type.IsClass)
.Select(Activator.CreateInstance)
.Cast<ICalcGroup>()
.ToList();
}
private void CreateDependencyMap()
{
_formulas.ForEach(formulaPair =>
{
var forEnum = formulaPair.Key;
var formula = formulaPair.Value;
var markers = ParseFormulaMarkers(formula);
markers.ForEach(marker => GetMarkerValue(marker));
_dependencies.Add(forEnum, markers.Select(GetMarkerEnum).ToList());
});
}
private void CalculateAllInstant()
{
// Force to calculate main attributes (all other should be queued to calculate by dependencies)
Enum.GetValues(typeof(MainAttributeType)).Cast<Enum>().ForEach(Queue);
while (!_calcQueue.IsNullOrEmpty()) ExecuteQueue();
}
#endregion
[Serializable] private class MainAttributeFormulaDict : SerializedDictionary<MainAttributeType, string> {}
[Serializable] private class SubAttributeFormulaDict : SerializedDictionary<SubAttributeType, string> {}
[Serializable] private class BasicResourceFormulaDict : SerializedDictionary<BasicResourceType, string> {}
[Serializable] private class UnitResourceFormulaDict : SerializedDictionary<UnitResourceType, string> {}
[Title("Formulas used to calculate")]
[SerializeField, EnableIn(PrefabKind.PrefabAsset)] private MainAttributeFormulaDict _mainAttributeFormulas = new();
[SerializeField, EnableIn(PrefabKind.PrefabAsset)] private SubAttributeFormulaDict _subAttributeFormulas = new();
[SerializeField, EnableIn(PrefabKind.PrefabAsset)] private BasicResourceFormulaDict _basicResourceFormulas = new();
[SerializeField, EnableIn(PrefabKind.PrefabAsset)] private UnitResourceFormulaDict _unitResourceFormulas = new();
private Dictionary<Enum, string> _formulas = new();
private Dictionary<Enum, List<Enum>> _dependencies = new();
private List<Enum> _calcQueue = new();
private List<ICalcGroup> _calcGroups;
public void Queue(Enum @enum)
{
if (_calcGroups.All(calcGroup => calcGroup.GetEnumType() != @enum.GetType()))
throw new Exception($"Enum '{@enum}' is not acceptable in {nameof(UnitObjectDataCalculator)}.");
if (_calcQueue.Contains(@enum)) return;
_calcQueue.Add(@enum);
parent.basicEvents.onFixedUpdate.AddListener(ExecuteQueue);
}
private void ExecuteQueue()
{
var tempCalcQueue = new List<Enum>(_calcQueue);
_calcQueue.Clear();
parent.basicEvents.onFixedUpdate.RemoveListener(ExecuteQueue);
tempCalcQueue.ForEach(Calculate);
}
private void Calculate(Enum @enum)
{
var calcGroup = _calcGroups.FirstOrDefault(calcGroup => calcGroup.CheckEnum(@enum));
if (calcGroup == null) throw new Exception($"Marker '{@enum}' does not exists!");
calcGroup.UpdateWithValue(parent, @enum.ToString(), GetFormulaValue(@enum));
_dependencies.Where(pair => pair.Value.Contains(@enum)).ForEach(pair => Queue(pair.Key));
}
private float GetFormulaValue(Enum @enum)
{
var formula = _formulas.GetValueOrDefault(@enum, "");
ParseFormulaMarkers(formula).ForEach(marker => formula = formula.Replace(marker, GetMarkerValue(marker)));
var isValid = ExpressionEvaluator.Evaluate(formula, out float result);
if (!isValid) Debug.LogError($"Formula `{formula}` is invalid and can't be calculated! Using zero!");
return result;
}
private List<string> ParseFormulaMarkers(string formula)
{
return Regex.Matches(formula, "{(.*?)}").Select(match => match.Value).Distinct().ToList();
}
private string GetMarkerValue(string marker)
{
var parsedMarker = ParseMarker(marker);
var calcGroup = _calcGroups.FirstOrDefault(cg => cg.CheckMarker(parsedMarker.group, parsedMarker.item));
if (calcGroup == null) throw new Exception($"Marker '{marker}' does not exists!");
return calcGroup.GetValue(parent, parsedMarker.item).ToString("F2", CultureInfo.InvariantCulture);
}
private Enum GetMarkerEnum(string marker)
{
var parsedMarker = ParseMarker(marker);
var calcGroup = _calcGroups.FirstOrDefault(cg => cg.CheckMarker(parsedMarker.group, parsedMarker.item));
if (calcGroup == null) throw new Exception($"Marker '{marker}' does not exists!");
return calcGroup.GetEnum(parsedMarker.item);
}
private (string group, string item) ParseMarker(string marker)
{
var arrayMarker = marker.Trim('{', '}').Split('.');
return new ValueTuple<string, string>(arrayMarker[0], arrayMarker[1]);
}
}
}