using System; using System.Collections.Generic; using BepInEx.Configuration; using UnityEngine; namespace BanquetForCyka { public class ExpressionConfigEntry { private readonly ConfigEntry _configEntry; private readonly ConfigEntry _debug; private Func _compiledExpression; private string _lastValidExpression; private bool _isValid = true; public string Value { get => _configEntry.Value; set { if (value == _configEntry.Value) return; // Try to compile the expression try { LogDebug($"Attempting to compile expression: '{value}'"); var newExpr = ParseExpression(value); _compiledExpression = newExpr; _lastValidExpression = value; _isValid = true; _configEntry.Value = value; LogDebug($"Expression '{value}' compiled successfully"); } catch (Exception e) { _isValid = false; LogDebug($"Failed to compile expression '{value}': {e.Message}"); LogDebug($"Stack trace: {e.StackTrace}"); // Don't update the config value if invalid } } } public bool IsValid => _isValid; public string LastValidExpression => _lastValidExpression; public ExpressionConfigEntry(ConfigFile config, string section, string key, string defaultValue, string description, ConfigEntry debug = null) { _debug = debug; LogDebug($"Initializing ExpressionConfigEntry for {section}.{key}"); _configEntry = config.Bind( section, key, defaultValue, new ConfigDescription(description + "\nUse 'v' to represent the input value. Examples: 'v*4', 'v+13', '(v+5)*2'")); // Initial compilation try { LogDebug($"Attempting initial compilation of expression: '{_configEntry.Value}'"); _compiledExpression = ParseExpression(_configEntry.Value); _lastValidExpression = _configEntry.Value; LogDebug($"Initial expression '{_configEntry.Value}' compiled successfully"); } catch (Exception e) { LogDebug($"Failed to compile initial expression '{_configEntry.Value}': {e.Message}"); LogDebug($"Stack trace: {e.StackTrace}"); } // Recompile when config changes _configEntry.SettingChanged += (sender, args) => { try { LogDebug($"Config changed, attempting to recompile expression: '{_configEntry.Value}'"); _compiledExpression = ParseExpression(_configEntry.Value); _lastValidExpression = _configEntry.Value; _isValid = true; LogDebug($"Expression '{_configEntry.Value}' recompiled successfully"); } catch (Exception e) { _isValid = false; LogDebug($"Failed to recompile expression '{_configEntry.Value}': {e.Message}"); LogDebug($"Stack trace: {e.StackTrace}"); } }; } public float Evaluate(float input) { if (!_isValid) { LogDebug( $"Expression is invalid, using last valid expression '{_lastValidExpression}' for input {input}"); return input; // Return original value if expression is invalid } LogDebug($"Evaluating expression '{_configEntry.Value}' with input {input}"); var result = _compiledExpression(input); LogDebug($"Expression '{_configEntry.Value}' evaluated: {input} -> {result}"); return result; } private void LogDebug(string message) { if (_debug?.Value == true) { Console.WriteLine($"[ExpressionConfigEntry] {message}"); } } // Copy of the expression parser from Main private static Func ParseExpression(string expr) { try { // Remove all whitespace expr = expr.Replace(" ", ""); var tokens = new List(); var current = ""; // Tokenize the expression for (int i = 0; i < expr.Length; i++) { char c = expr[i]; if (c == 'v') { if (current != "") { tokens.Add(current); current = ""; } tokens.Add("v"); } else if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')') { if (current != "") { tokens.Add(current); current = ""; } tokens.Add(c.ToString()); } else { current += c; } } if (current != "") { tokens.Add(current); } // Convert to postfix notation using shunting yard var output = new List(); var operators = new Stack(); foreach (var token in tokens) { if (token == "v" || float.TryParse(token, out _)) { output.Add(token); } else if (token == "(") { operators.Push(token); } else if (token == ")") { while (operators.Count > 0 && operators.Peek() != "(") { output.Add(operators.Pop()); } if (operators.Count > 0) operators.Pop(); // Remove "(" } else { while (operators.Count > 0 && operators.Peek() != "(" && GetPrecedence(operators.Peek()) >= GetPrecedence(token)) { output.Add(operators.Pop()); } operators.Push(token); } } while (operators.Count > 0) { output.Add(operators.Pop()); } // Build the expression tree var stack = new Stack>(); foreach (var token in output) { if (token == "v") { stack.Push(v => v); } else if (float.TryParse(token, out float num)) { stack.Push(v => num); } else { var right = stack.Pop(); var left = stack.Pop(); switch (token) { case "+": stack.Push(v => left(v) + right(v)); break; case "-": stack.Push(v => left(v) - right(v)); break; case "*": stack.Push(v => left(v) * right(v)); break; case "/": stack.Push(v => { var r = right(v); return r != 0 ? left(v) / r : left(v); }); break; } } } return stack.Pop(); } catch (Exception e) { throw new FormatException($"Failed to parse expression '{expr}': {e.Message}"); } } private static int GetPrecedence(string op) { switch (op) { case "+": return 1; case "-": return 1; case "*": return 2; case "/": return 2; default: return 0; } } } }