Implement ExpressionConfigEntry that implements shunting yard and postfix notation to compute values based on strings

This commit is contained in:
2025-05-21 16:15:47 +02:00
parent dea5256339
commit 66ca370ac2
3 changed files with 187 additions and 5 deletions

View File

@@ -38,6 +38,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Class1.cs" /> <Compile Include="Class1.cs" />
<Compile Include="ExpressionConfigEntry.cs" />
<!-- <Compile Include="ModuleShieldGeneratorManager.cs" /> <!-- <Compile Include="ModuleShieldGeneratorManager.cs" />
<Compile Include="ObjectFieldMultiplier.cs" /> <Compile Include="ObjectFieldMultiplier.cs" />
<Compile Include="Patches.cs" /> <Compile Include="Patches.cs" />
@@ -82,6 +83,7 @@
<Reference Include="ConfigurationManager"> <Reference Include="ConfigurationManager">
<HintPath>$(GAME_BEPINEX)/plugins/ConfigurationManager/ConfigurationManager.dll</HintPath> <HintPath>$(GAME_BEPINEX)/plugins/ConfigurationManager/ConfigurationManager.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Collections.Generic;
using BepInEx; using BepInEx;
using BepInEx.Configuration; using BepInEx.Configuration;
using HarmonyLib; using HarmonyLib;
@@ -14,16 +15,15 @@ namespace BanquetForCyka {
public static ConfigEntry<bool> debug; public static ConfigEntry<bool> debug;
public static ConfigEntry<bool> debugXp; public static ConfigEntry<bool> debugXp;
public static ExpressionConfigEntry xpMultiplier;
public static ConfigEntry<float> xpMultiplier;
public void Awake() { public void Awake() {
debug = Config.Bind("Debug", "Global Debug", false); debug = Config.Bind("Debug", "Global Debug", false);
debugXp = Config.Bind("Debug", "XP Debug", false); debugXp = Config.Bind("Debug", "XP Debug", false);
xpMultiplier = xpMultiplier =
Config.Bind("General", "XP Multiplier", 1f, new ExpressionConfigEntry(Config, "General", "XP Multiplier", "v*1",
new ConfigDescription("XP Multiplier", new AcceptableValueRange<float>(0.01f, 1024f))); "XP Multiplier expression. Use 'v' to represent the original value.");
Logger.LogInfo("BanquetForCyka loaded"); Logger.LogInfo("BanquetForCyka loaded");
HarmonyFileLog.Enabled = true; HarmonyFileLog.Enabled = true;
@@ -43,7 +43,8 @@ namespace BanquetForCyka {
public class Stats_AddXP { public class Stats_AddXP {
public static void Prefix(ref int amt) { public static void Prefix(ref int amt) {
Main.LogDebug("Original XP amount: " + amt, Main.debugXp); 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); Main.LogDebug("Modified XP amount: " + amt, Main.debugXp);
} }
} }

View 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;
}
}
}
}