diff --git a/.gitignore b/.gitignore
index d7be2f5..8a45785 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,7 @@ __pycache__/
gb4dump/
*.bin
*.bak-bg4uw
+
+# .NET plugin build output
+**/plugin/bin/
+**/plugin/obj/
diff --git a/games/BurglinGnomes/plugin/UltraWideFix.cs b/games/BurglinGnomes/plugin/UltraWideFix.cs
new file mode 100644
index 0000000..6f1100a
--- /dev/null
+++ b/games/BurglinGnomes/plugin/UltraWideFix.cs
@@ -0,0 +1,142 @@
+using BepInEx;
+using BepInEx.Configuration;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.SceneManagement;
+
+namespace BurglinGnomes.UltraWideFix
+{
+ ///
+ /// Ultrawide UI fix for Burglin' Gnomes (Unity 6, Mono).
+ ///
+ /// The camera needs no patching: it is orthographic with a constant
+ /// orthographicSize (5.0), so a wider display already shows proportionally
+ /// more world (visible width 17.8 -> 35.6 going 16:9 -> 32:9). That is
+ /// correct Hor+ behaviour.
+ ///
+ /// The actual problem is UI scaling:
+ /// * HUD canvases (ScreenSpaceOverlay + ScaleWithScreenSize) ship
+ /// matchWidthOrHeight = 0.453, which is width-biased. At 5120px wide that
+ /// yields scaleFactor 1.948 and a ballooned HUD. Matching HEIGHT (1.0)
+ /// gives 1.333, and raising referenceResolution.y shrinks it further
+ /// while giving the layout MORE logical room (cures overlapping text).
+ /// * World-space label canvases (NamePlate, ...) are NOT affected by
+ /// CanvasScaler.scaleFactor - CanvasScaler.HandleWorldCanvas() overwrites
+ /// it with dynamicPixelsPerUnit every frame. Their size comes from the
+ /// canvas transform's localScale, so that is what we scale.
+ ///
+ [BepInPlugin(PluginGuid, PluginName, PluginVersion)]
+ public class UltraWideFixPlugin : BaseUnityPlugin
+ {
+ public const string PluginGuid = "com.programmingpug.burglingnomes.ultrawidefix";
+ public const string PluginName = "Burglin' Gnomes UltraWide Fix";
+ public const string PluginVersion = "1.0.0";
+
+ private ConfigEntry _enabled;
+ private ConfigEntry _match;
+ private ConfigEntry _hudScale;
+ private ConfigEntry _worldScale;
+ private ConfigEntry _designHeight;
+ private ConfigEntry _verbose;
+
+ // Baselines captured once per canvas so repeated applies never compound.
+ private readonly System.Collections.Generic.Dictionary _refBaseline
+ = new System.Collections.Generic.Dictionary();
+ private readonly System.Collections.Generic.Dictionary _scaleBaseline
+ = new System.Collections.Generic.Dictionary();
+
+ private float _timer;
+
+ private void Awake()
+ {
+ _enabled = Config.Bind("General", "Enabled", true,
+ "Master switch for the ultrawide UI fix.");
+ _match = Config.Bind("HUD", "MatchWidthOrHeight", 1.0f,
+ new ConfigDescription(
+ "CanvasScaler match for HUD canvases. 0 = scale by width, 1 = by height. " +
+ "Keep at 1 for ultrawide; lower values re-introduce width scaling and " +
+ "cramp the layout vertically.",
+ new AcceptableValueRange(0f, 1f)));
+ _hudScale = Config.Bind("HUD", "HudScale", 0.85f,
+ new ConfigDescription(
+ "HUD size multiplier. Below 1 shrinks the HUD AND gives the layout more " +
+ "logical room, which is what cures overlapping text. 1 = stock size.",
+ new AcceptableValueRange(0.4f, 1.5f)));
+ _designHeight = Config.Bind("HUD", "DesignHeight", 1080f,
+ "The game's design reference height. Do not change unless you know why.");
+ _worldScale = Config.Bind("World", "WorldLabelScale", 1.0f,
+ new ConfigDescription(
+ "Size multiplier for in-world labels (nameplates). At 32:9 you see twice " +
+ "as much world, so more labels are on screen and can collide; lower this " +
+ "to shrink them. 1 = stock size.",
+ new AcceptableValueRange(0.3f, 1.5f)));
+ _verbose = Config.Bind("Debug", "Verbose", false,
+ "Log every canvas adjustment.");
+
+ SceneManager.sceneLoaded += (_, __) => Apply();
+ Logger.LogInfo($"{PluginName} {PluginVersion} loaded.");
+ }
+
+ private void Start() => Apply();
+
+ // Canvases are created lazily (menus, popups), so re-apply periodically.
+ private void Update()
+ {
+ _timer += Time.unscaledDeltaTime;
+ if (_timer < 2f) return;
+ _timer = 0f;
+ Apply();
+ }
+
+ private void Apply()
+ {
+ if (!_enabled.Value) return;
+
+ foreach (var cs in FindObjectsOfType())
+ {
+ var canvas = cs.GetComponent