Implement ExpressionConfigEntry that implements shunting yard and postfix notation to compute values based on strings
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Class1.cs" />
|
||||
<Compile Include="ExpressionConfigEntry.cs" />
|
||||
<!-- <Compile Include="ModuleShieldGeneratorManager.cs" />
|
||||
<Compile Include="ObjectFieldMultiplier.cs" />
|
||||
<Compile Include="Patches.cs" />
|
||||
@@ -82,6 +83,7 @@
|
||||
<Reference Include="ConfigurationManager">
|
||||
<HintPath>$(GAME_BEPINEX)/plugins/ConfigurationManager/ConfigurationManager.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using BepInEx;
|
||||
using BepInEx.Configuration;
|
||||
using HarmonyLib;
|
||||
@@ -14,16 +15,15 @@ namespace BanquetForCyka {
|
||||
|
||||
public static ConfigEntry<bool> debug;
|
||||
public static ConfigEntry<bool> debugXp;
|
||||
|
||||
public static ConfigEntry<float> xpMultiplier;
|
||||
public static ExpressionConfigEntry xpMultiplier;
|
||||
|
||||
public void Awake() {
|
||||
debug = Config.Bind("Debug", "Global Debug", false);
|
||||
debugXp = Config.Bind("Debug", "XP Debug", false);
|
||||
|
||||
xpMultiplier =
|
||||
Config.Bind("General", "XP Multiplier", 1f,
|
||||
new ConfigDescription("XP Multiplier", new AcceptableValueRange<float>(0.01f, 1024f)));
|
||||
new ExpressionConfigEntry(Config, "General", "XP Multiplier", "v*1",
|
||||
"XP Multiplier expression. Use 'v' to represent the original value.");
|
||||
|
||||
Logger.LogInfo("BanquetForCyka loaded");
|
||||
HarmonyFileLog.Enabled = true;
|
||||
@@ -43,7 +43,8 @@ namespace BanquetForCyka {
|
||||
public class Stats_AddXP {
|
||||
public static void Prefix(ref int amt) {
|
||||
Main.LogDebug("Original XP amount: " + amt, Main.debugXp);
|
||||
amt = (int)((float)amt * Main.xpMultiplier.Value);
|
||||
float result = Main.xpMultiplier.Evaluate(amt);
|
||||
amt = (int)result;
|
||||
Main.LogDebug("Modified XP amount: " + amt, Main.debugXp);
|
||||
}
|
||||
}
|
||||
|
179
Projects/BanquetForFools/BanquetForCyka/ExpressionConfigEntry.cs
Normal file
179
Projects/BanquetForFools/BanquetForCyka/ExpressionConfigEntry.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BepInEx.Configuration;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BanquetForCyka {
|
||||
public class ExpressionConfigEntry {
|
||||
private readonly ConfigEntry<string> _configEntry;
|
||||
private Func<float, float> _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<float, float> ParseExpression(string expr) {
|
||||
try {
|
||||
// Remove all whitespace
|
||||
expr = expr.Replace(" ", "");
|
||||
|
||||
var tokens = new List<string>();
|
||||
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<string>();
|
||||
var operators = new Stack<string>();
|
||||
|
||||
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<Func<float, float>>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user