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 { #region Editor [ShowInInspector] [NonSerialized] [ListDrawerSettings( OnTitleBarGUI = "CreateMarkersInfoBox", DraggableItems = false, HideRemoveButton = true, HideAddButton = true, DefaultExpandedState = false)] private List _availableMarkers = new(); private void CreateMarkersInfoBox() { if (!SirenixEditorGUI.ToolbarButton(EditorIcons.Refresh)) return; Debug.Log("Marker list refreshed!", parent); CreateCalcGroups(); _availableMarkers = new List(); _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() .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().ForEach(Queue); while (!_calcQueue.IsNullOrEmpty()) ExecuteQueue(); } #endregion [Serializable] private class MainAttributeFormulaDict : SerializedDictionary {} [Serializable] private class SubAttributeFormulaDict : SerializedDictionary {} [Serializable] private class BasicResourceFormulaDict : SerializedDictionary {} [Serializable] private class UnitResourceFormulaDict : SerializedDictionary {} [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 _formulas = new(); private Dictionary> _dependencies = new(); private List _calcQueue = new(); private List _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(_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 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(arrayMarker[0], arrayMarker[1]); } } }