From c9e0f7c7814c4d23b8de8ec697669eb0d872341a Mon Sep 17 00:00:00 2001 From: MuelNova Date: Mon, 27 Apr 2026 01:26:53 +0800 Subject: [PATCH] feat: add ModConfig shortcuts --- ChatMod.cs | 2 + ChatPanel.cs | 7 +- Scripts/ModConfigBridge.cs | 174 +++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 Scripts/ModConfigBridge.cs diff --git a/ChatMod.cs b/ChatMod.cs index 7d8e705..3bbeebe 100644 --- a/ChatMod.cs +++ b/ChatMod.cs @@ -1,6 +1,7 @@ using Godot; using MegaCrit.Sts2.Core.Modding; using MegaCrit.Sts2.Core.Nodes; +using Typing.Scripts; namespace Typing; @@ -10,5 +11,6 @@ public static class ChatMod public static void Initialize() { NGame.Instance?.CallDeferred(Node.MethodName.AddChild, new ChatPanel()); + ModConfigBridge.DeferredRegister(); } } diff --git a/ChatPanel.cs b/ChatPanel.cs index 5d593e1..245ed4f 100644 --- a/ChatPanel.cs +++ b/ChatPanel.cs @@ -13,6 +13,7 @@ using MegaCrit.Sts2.Core.Nodes.Cards; using MegaCrit.Sts2.Core.Nodes.Cards.Holders; using MegaCrit.Sts2.Core.Nodes.Combat; +using Typing.Scripts; using MegaCrit.Sts2.Core.Nodes.Potions; using MegaCrit.Sts2.Core.Nodes.Relics; using MegaCrit.Sts2.Core.Nodes.Screens.CharacterSelect; @@ -211,8 +212,10 @@ public override void _Input(InputEvent evt) return; } - if ((key.Keycode == Key.Enter || key.Keycode == Key.KpEnter) - && !key.AltPressed && !key.CtrlPressed) + var toggleKey = ModConfigBridge.ChatToggleKey; + bool toggleMatched = key.Keycode == toggleKey + || (toggleKey == Key.Enter && key.Keycode == Key.KpEnter); + if (toggleMatched && !key.AltPressed && !key.CtrlPressed) { var focused = GetViewport().GuiGetFocusOwner(); if (focused is TextEdit or LineEdit) diff --git a/Scripts/ModConfigBridge.cs b/Scripts/ModConfigBridge.cs new file mode 100644 index 0000000..693080b --- /dev/null +++ b/Scripts/ModConfigBridge.cs @@ -0,0 +1,174 @@ +// ModConfig integration for the typing mod. +// Adapted from https://github.com/xhyrzldf/ModConfig-STS2 (examples/ModConfigBridge.cs). +// Zero DLL reference: everything is done via reflection. If ModConfig is not +// installed, GetValue() returns the supplied fallback and the mod still works. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Godot; + +namespace Typing.Scripts; + +internal static class ModConfigBridge +{ + internal const string ModId = "typing"; + + internal const string KeyChatToggle = "chatToggleKey"; + internal static readonly Key ChatToggleKeyDefault = Key.Enter; + + internal static Key ChatToggleKey { get; private set; } = ChatToggleKeyDefault; + + private static bool _available; + private static bool _registered; + private static Type? _apiType; + private static Type? _entryType; + private static Type? _configTypeEnum; + + internal static void DeferredRegister() + { + var tree = (SceneTree)Engine.GetMainLoop(); + tree.ProcessFrame += OnNextFrame; + } + + private static void OnNextFrame() + { + var tree = (SceneTree)Engine.GetMainLoop(); + tree.ProcessFrame -= OnNextFrame; + Detect(); + if (_available) + { + Register(); + ChatToggleKey = (Key)GetValue(KeyChatToggle, (long)ChatToggleKeyDefault); + } + } + + private static void Detect() + { + try + { + var allTypes = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => + { + try { return a.GetTypes(); } + catch { return Type.EmptyTypes; } + }) + .ToArray(); + + _apiType = allTypes.FirstOrDefault(t => t.FullName == "ModConfig.ModConfigApi"); + _entryType = allTypes.FirstOrDefault(t => t.FullName == "ModConfig.ConfigEntry"); + _configTypeEnum = allTypes.FirstOrDefault(t => t.FullName == "ModConfig.ConfigType"); + _available = _apiType != null && _entryType != null && _configTypeEnum != null; + } + catch + { + _available = false; + } + } + + private static void Register() + { + if (_registered) return; + _registered = true; + + try + { + var entries = BuildEntries(); + + var displayNames = new Dictionary + { + ["en"] = "Typing Chat", + ["zhs"] = "聊天 Typing", + ["zht"] = "聊天 Typing", + ["ja"] = "Typing チャット", + ["ko"] = "Typing 채팅", + }; + + var registerMethod = _apiType!.GetMethods(BindingFlags.Public | BindingFlags.Static) + .Where(m => m.Name == "Register") + .OrderByDescending(m => m.GetParameters().Length) + .First(); + + if (registerMethod.GetParameters().Length == 4) + { + registerMethod.Invoke(null, new object[] + { + ModId, + displayNames["en"], + displayNames, + entries, + }); + } + else + { + registerMethod.Invoke(null, new object[] + { + ModId, + displayNames["en"], + entries, + }); + } + } + catch (Exception e) + { + GD.PrintErr($"[typing] ModConfig registration failed: {e}"); + } + } + + internal static T GetValue(string key, T fallback) + { + if (!_available) return fallback; + try + { + var result = _apiType!.GetMethod("GetValue", BindingFlags.Public | BindingFlags.Static) + ?.MakeGenericMethod(typeof(T)) + ?.Invoke(null, new object[] { ModId, key }); + return result != null ? (T)result : fallback; + } + catch { return fallback; } + } + + private static Array BuildEntries() + { + var list = new List(); + + list.Add(Entry(cfg => + { + Set(cfg, "Key", KeyChatToggle); + Set(cfg, "Label", "Open Chat Hotkey"); + Set(cfg, "Labels", L("Open Chat Hotkey", "打开聊天快捷键")); + Set(cfg, "Type", EnumVal("KeyBind")); + Set(cfg, "DefaultValue", (object)(long)ChatToggleKeyDefault); + Set(cfg, "Description", "Press this key to open the chat input."); + Set(cfg, "Descriptions", L( + "Press this key to open the chat input.", + "按下此键打开聊天输入框。")); + Set(cfg, "OnChanged", new Action(v => + { + ChatToggleKey = (Key)Convert.ToInt64(v); + })); + })); + + var result = Array.CreateInstance(_entryType!, list.Count); + for (int i = 0; i < list.Count; i++) + result.SetValue(list[i], i); + return result; + } + + private static object Entry(Action configure) + { + var inst = Activator.CreateInstance(_entryType!)!; + configure(inst); + return inst; + } + + private static void Set(object obj, string name, object value) + => obj.GetType().GetProperty(name)?.SetValue(obj, value); + + private static Dictionary L(string en, string zhs) + => new() { ["en"] = en, ["zhs"] = zhs }; + + private static object EnumVal(string name) + => Enum.Parse(_configTypeEnum!, name); +}