using System; using System.Collections.Generic; using BepInEx.Configuration; using UnityEngine; namespace BanquetForCyka { public class ExpressionConfigEntry { private readonly ConfigEntry _configEntry; 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 { var newExpr = ParseExpression(value); _compiledExpression = newExpr; _lastValidExpression = value; _isValid = true; _configEntry.Value = value; } catch (Exception) { _isValid = false; // 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 = 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 _compiledExpression = ParseExpression(_configEntry.Value); _lastValidExpression = _configEntry.Value; // Recompile when config changes _configEntry.SettingChanged += (sender, args) => { try { _compiledExpression = ParseExpression(_configEntry.Value); _lastValidExpression = _configEntry.Value; _isValid = true; } catch (Exception) { _isValid = false; } }; } public float Evaluate(float input) { if (!_isValid) { return input; // Return original value if expression is invalid } return _compiledExpression(input); } // 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; } } } }