7d6bdc592f
• Quick Swap Feature:
• Users can now bind keyboard keys (both single keys and multi-key combinations, e.g., Alt+1 ) to switch to
specific operator sets.
• Auto-toggle capability: Pressing the hotkey switches to the target operator set (automatically changing side
if the operator is on standard Attackers/Defenders side). Pressing it again toggles back to the baseline
operator set.
• Added specificity filtering: Resolves overlapping hotkeys (e.g., pressing Alt+1 will only trigger the Alt+1
binding, successfully ignoring the individual 1 key binding).
• Added a dedicated Quick Swap Bindings editor inside the Settings panel to configure, save, and reload these
bindings visually.
## ⚙️ Updated (Refactoring & Architecture)
• Graceful Thread Shutdown: Updated RecoilEngine.cs to run with a CancellationToken linked to the main window
lifespan, matching HotkeyHandler.cs and preventing background thread leaks when exiting.
• State Management: Updated OperatorSets methods to protect the quick swap transition state, ensuring manual
adjustments (clicks or next/prev hotkeys) reset the quick-swap toggle baseline.
• Config Serialization: Updated MainWindow configuration generator to save new quick-swap parameters cleanly back
to init.config .
## 🛠️ Fixed (Bugs resolved)
• TabControl/Menu Text Colors: Fixed the styling on MainWindow.xaml tab headers. Unselected and hover states now
render in clear light greys, and active tabs in solid white, resolving dark-on-dark invisible text bugs.
• Dropdown Selection Visibility: Changed the text foreground color of TextColorComboBox in MainWindow.xaml from
black to white to make choices readable in dark mode.
• UI Console Text Color: Bound the console setting ( textColor ) to the actual WPF GUI log ( ConsoleOutput.
Foreground ), making the chosen color reflect in the application window.
──────
### 📂 File-Level Changes Summary
• helper.cs: Added helper.cs to safely parse combinations.
• ConfigLoader.cs: Added ConfigLoader.cs class and loading logic.
• OperatorSets.cs: Added OperatorSets.cs and state-tracking flags.
• HotkeyHandler.cs: Registered hotkey monitoring loops for multi-key patterns.
• RecoilEngine.cs: Integrated thread lifecycle cancellation.
• MainWindow.xaml: Styled menus, fixed dropdown contrast, and added Quick Swap bindings textbox.
• MainWindow.xaml.cs: Connected UI controls to backend quick-swap actions and applied dynamic log coloring.
181 lines
5.9 KiB
C#
181 lines
5.9 KiB
C#
using System;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Lua
|
|
{
|
|
static class Helper
|
|
{
|
|
/// <summary>
|
|
// AA0030
|
|
/// </summary>
|
|
public static string[] ReadLinesSafe(string path)
|
|
{
|
|
try
|
|
{
|
|
if (!File.Exists(path)) return Array.Empty<string>();
|
|
return File.ReadAllLines(path);
|
|
}
|
|
catch
|
|
{
|
|
// AA0031
|
|
return Array.Empty<string>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
// AA0032
|
|
/// </summary>
|
|
public static bool ParseBoolFromAssignment(string line, string key, out bool result)
|
|
{
|
|
result = false;
|
|
var idx = line.IndexOf('=');
|
|
if (idx < 0) return false;
|
|
|
|
var lhs = line.Substring(0, idx).Trim();
|
|
if (!string.Equals(lhs, key, StringComparison.OrdinalIgnoreCase)) return false;
|
|
|
|
var rhs = line.Substring(idx + 1).Trim();
|
|
if (rhs.Equals("true", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
result = true;
|
|
return true;
|
|
}
|
|
if (rhs.Equals("false", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
result = false;
|
|
return true;
|
|
}
|
|
|
|
// AA0033
|
|
return bool.TryParse(rhs, out result);
|
|
}
|
|
|
|
// AA0034
|
|
public static bool ParseIntFromAssignment(string line, string key, out int result)
|
|
{
|
|
result = 0;
|
|
var idx = line.IndexOf('=');
|
|
if (idx < 0) return false;
|
|
|
|
var lhs = line.Substring(0, idx).Trim();
|
|
if (!string.Equals(lhs, key, StringComparison.OrdinalIgnoreCase)) return false;
|
|
|
|
var rhs = line.Substring(idx + 1).Trim();
|
|
return int.TryParse(rhs, NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
|
|
}
|
|
|
|
// AA0035
|
|
public static bool ParseStringFromAssignment(string line, string key, out string result)
|
|
{
|
|
result = null;
|
|
var idx = line.IndexOf('=');
|
|
if (idx < 0) return false;
|
|
|
|
var lhs = line.Substring(0, idx).Trim();
|
|
if (!string.Equals(lhs, key, StringComparison.OrdinalIgnoreCase)) return false;
|
|
|
|
result = line.Substring(idx + 1).Trim();
|
|
|
|
// AA0036 - Fixed: Check length before substring to avoid negative index
|
|
if (result.Length >= 2 && ((result.StartsWith("\"") && result.EndsWith("\"")) ||
|
|
(result.StartsWith("'") && result.EndsWith("'"))))
|
|
{
|
|
result = result.Substring(1, result.Length - 2);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// AA0037
|
|
public static bool ParseConsoleColorFromAssignment(string line, string key, out ConsoleColor color)
|
|
{
|
|
color = ConsoleColor.White;
|
|
if (!ParseStringFromAssignment(line, key, out var s)) return false;
|
|
return Enum.TryParse(s, true, out color);
|
|
}
|
|
|
|
/// <summary>
|
|
// AA0038
|
|
/// </summary>
|
|
public static bool TryParseRecoilLine(string line, out string name, out double recoilX, out double recoilY)
|
|
{
|
|
name = null;
|
|
recoilX = 0;
|
|
recoilY = 0;
|
|
|
|
// AA0039
|
|
var pattern = @"\[\s*[""'](.+?)[""']\s*\]\s*=\s*\{\s*recoilX\s*=\s*([-\d\.eE]+)\s*,\s*recoilY\s*=\s*([-\d\.eE]+)\s*\}";
|
|
var match = Regex.Match(line, pattern, RegexOptions.IgnoreCase);
|
|
if (!match.Success) return false;
|
|
|
|
name = match.Groups[1].Value.Trim();
|
|
return double.TryParse(match.Groups[2].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out recoilX) &&
|
|
double.TryParse(match.Groups[3].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out recoilY);
|
|
}
|
|
|
|
/// <summary>
|
|
// AA0040
|
|
/// </summary>
|
|
public static int SafeClampIndex(int idx, int size)
|
|
{
|
|
if (size <= 0) return 0;
|
|
if (idx < 0) return 0;
|
|
if (idx >= size) return size - 1;
|
|
return idx;
|
|
}
|
|
|
|
public static bool ParseQuickSwap(string line, out int[] keys, out string targetOperator)
|
|
{
|
|
keys = null;
|
|
targetOperator = null;
|
|
|
|
var idx = line.IndexOf('=');
|
|
if (idx < 0) return false;
|
|
|
|
var lhs = line.Substring(0, idx).Trim();
|
|
if (!string.Equals(lhs, "quickSwap", StringComparison.OrdinalIgnoreCase)) return false;
|
|
|
|
var rhs = line.Substring(idx + 1).Trim();
|
|
var parts = rhs.Split(new[] { "=>" }, StringSplitOptions.None);
|
|
if (parts.Length != 2) return false;
|
|
|
|
var keyPart = parts[0].Trim();
|
|
var opPart = parts[1].Trim();
|
|
|
|
if (opPart.Length >= 2 && ((opPart.StartsWith("\"") && opPart.EndsWith("\"")) ||
|
|
(opPart.StartsWith("'") && opPart.EndsWith("'"))))
|
|
{
|
|
opPart = opPart.Substring(1, opPart.Length - 2);
|
|
}
|
|
|
|
targetOperator = opPart;
|
|
|
|
var keyStrings = keyPart.Split('+');
|
|
var keyList = new System.Collections.Generic.List<int>();
|
|
foreach (var kStr in keyStrings)
|
|
{
|
|
var trimmed = kStr.Trim();
|
|
int vk = 0;
|
|
if (trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
try { vk = Convert.ToInt32(trimmed, 16); } catch { return false; }
|
|
}
|
|
else if (int.TryParse(trimmed, out int val))
|
|
{
|
|
vk = val;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
keyList.Add(vk);
|
|
}
|
|
|
|
if (keyList.Count == 0) return false;
|
|
|
|
keys = keyList.ToArray();
|
|
return true;
|
|
}
|
|
}
|
|
} |