Skip to content

Instantly share code, notes, and snippets.

@nikitasius
Last active May 25, 2026 10:00
Show Gist options
  • Select an option

  • Save nikitasius/01d1312c880b0ae795ed78b4f36187ea to your computer and use it in GitHub Desktop.

Select an option

Save nikitasius/01d1312c880b0ae795ed78b4f36187ea to your computer and use it in GitHub Desktop.

There are uboat mos working locally and working perfectly after passing via Claude Opus 4.7 .

All modes are working fine on my Uboat from GOG.

@nikitasius

Copy link
Copy Markdown
Author

Dynamic Distance Labels
https://steamcommunity.com/sharedfiles/filedetails/?id=3405289645

Opus 4.7 recap

Details

Recap of Improvements

🧹 Code Quality

# Improvement Impact
1 Collapsed 4 duplicated Alt/Unit branches into one XOR expression ((units == Nautical) ^ altPressed) ~30 lines removed, single source of truth
2 Unified format-selection in helpers via ternary cascade instead of repeated if/else blocks Less duplication, easier to tweak thresholds
3 Split Constants into its own file (Constants.cs) Separation of concerns
4 Renamed file KilometersToPreferredUnitsPatch.cs β†’ UnitsUtilityPatch.cs Matches the class it patches
5 const instead of static readonly for true constants Slightly faster, signals intent
6 Added HARMONY_ID constant No magic strings

⚑ Performance

# Improvement Impact
7 Cached FieldInfo for UnitsUtility.locale once, instead of Traverse.Create(...).Field(...) on every call Removes per-frame reflection allocations
8 Removed redundant Traverse...SetValue write-backs for cache_* fields They were no-ops β€” Harmony's ref ___field already writes back automatically
9 Cache-on-miss pattern cacheM ?? (cacheM = locale["m"]) Locale lookup happens once, not every call

πŸ›‘οΈ Correctness & Safety

# Improvement Impact
10 ConditionalWeakTable<MapLineUI, …> replaces Dictionary in MapLineUIPatches Fixes a memory leak β€” destroyed MapLineUI instances are now GC'd automatically
11 sqrMagnitude > 1e-6f instead of diff != Vector2.zero Avoids float-equality pitfalls
12 Re-enabled error logging in the patch catch block Silent failures previously hid bugs
13 Locale type alias (using Locale = UBOAT.Game.Core.Data.Locale;) Disambiguates from other Locale types in the namespace tree

πŸ“ Final File Layout

Source/
β”œβ”€β”€ Constants.cs                 (new)
β”œβ”€β”€ ModLoader.cs                 (cleaned)
β”œβ”€β”€ UnitsUtilityPatch.cs         (renamed + rewritten)
β”œβ”€β”€ DynamicDistanceHelper.cs     (deduplicated)
└── MapLineUIPatches.cs          (leak fixed)

πŸ” Notable items left alone (intentionally)

  • SetCursorPos Y coordinate β€” Win32 and Unity use different Y-origins; flagged it but didn't change it since your original worked. Switch to Screen.height - y if cursor placement drifts.
  • Public API of helpers β€” kept static methods, just renamed ProcessMetricUnits β†’ AppendMetric to reflect what they actually do (append to a StringBuilder, not "process").
  • Threshold values (1 km, 5 km, 20 km, 1 nmi, 20 nmi) β€” preserved exactly. Could be promoted to Constants if you want them user-configurable later.

πŸš€ Possible future improvements (not done)

  • Make Alt-toggle key configurable instead of hardcoding LeftAlt/RightAlt.
  • Make distance thresholds & precisions configurable via a JSON config file.
  • Replace Camera.main lookup in MapLineUIPatches with a cached reference (Camera.main is a FindObjectWithTag call internally).
  • Add a [HarmonyPatch] guard via Prepare() that logs and skips if UnitsUtility.locale field is missing (future-proofing against game updates).

/Bundles - no changes

Constants.cs

Details
namespace DynamicDistanceLabels
{
    public static class Constants
    {
        public const string MOD_VERSION = "1.1.0";
        public const string MOD_TAG     = "[DynamicLabels]";
        public const string HARMONY_ID  = "com.drexack.uboat.DynamicDistanceLabels";

        // 1 km == 1/1.852 nmi
        public const float KM_TO_NMI = 1f / 1.852f;
    }
}

DynamicDistanceHelper.cs

Details
using Cysharp.Text;
using UBOAT.Game.Core.Data;                    // <-- this was missing
using Locale = UBOAT.Game.Core.Data.Locale;    // <-- disambiguate

namespace DynamicDistanceLabels
{
    public static class DynamicDistanceHelper
    {
        public static void AppendMetric(
            float kilometers,
            ref Utf16ValueStringBuilder sb,
            Locale locale,
            ref string cacheM,
            ref string cacheKm)
        {
            if (kilometers < 1f)
            {
                sb.AppendFormat("{0:0}", kilometers * 1000f); // meters
                sb.Append(' ');
                sb.Append(cacheM ?? (cacheM = locale["m"]));
                return;
            }

            string format =
                kilometers <  5f ? "{0:0.00}" :
                kilometers < 20f ? "{0:0.0}"  :
                                   "{0:0}";

            sb.AppendFormat(format, kilometers);
            sb.Append(' ');
            sb.Append(cacheKm ?? (cacheKm = locale["km"]));
        }

        public static void AppendNautical(
            float nauticalMiles,
            ref Utf16ValueStringBuilder sb,
            Locale locale,
            ref string cacheCables,
            ref string cacheNmi)
        {
            if (nauticalMiles < 1f)
            {
                // 1 nmi == 10 cables
                sb.AppendFormat("{0:0.0}", nauticalMiles * 10f);
                sb.Append(' ');
                sb.Append(cacheCables ?? (cacheCables = locale["cables"]));
                return;
            }

            string format = nauticalMiles < 20f ? "{0:0.0}" : "{0:0}";

            sb.AppendFormat(format, nauticalMiles);
            sb.Append(' ');
            sb.Append(cacheNmi ?? (cacheNmi = locale["nmi"]));
        }
    }
}

MapLineUIPatches.cs

Details
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using HarmonyLib;
using UBOAT.Game.UI.Map;
using UnityEngine;

namespace DynamicDistanceLabels
{
    [HarmonyPatch(typeof(MapLineUI), "PlaceLine")]
    public static class MapLineUI_PlaceLine_Patch
    {
        private sealed class LockedLength { public float Value; }

        // ConditionalWeakTable -> entries are GC'd automatically when the
        // MapLineUI instance is destroyed. No manual cleanup, no leaks.
        private static readonly ConditionalWeakTable<MapLineUI, LockedLength> lockedLengths =
            new ConditionalWeakTable<MapLineUI, LockedLength>();

#if UNITY_STANDALONE_WIN
        [DllImport("user32.dll")]
        private static extern bool SetCursorPos(int X, int Y);
#endif

        static void Prefix(MapLineUI __instance, ref Vector2 from, ref Vector2 targetPoint)
        {
            if (Input.GetKey(KeyCode.R))
            {
                if (!lockedLengths.TryGetValue(__instance, out var locked))
                {
                    locked = new LockedLength { Value = Vector2.Distance(from, targetPoint) };
                    lockedLengths.Add(__instance, locked);
                }

                Vector2 diff = targetPoint - from;
                if (diff.sqrMagnitude > 1e-6f)
                {
                    targetPoint = from + diff.normalized * locked.Value;
                }

#if UNITY_STANDALONE_WIN
                Camera cam = Camera.main;
                if (cam != null)
                {
                    Vector3 worldEndpoint = new Vector3(targetPoint.x, 0f, targetPoint.y);
                    Vector3 screenPoint   = cam.WorldToScreenPoint(worldEndpoint);
                    SetCursorPos((int)screenPoint.x, (int)screenPoint.y);
                }
#endif
            }
            else
            {
                lockedLengths.Remove(__instance);
            }
        }
    }
}

ModLoader.cs

Details
using System;
using HarmonyLib;
using UBOAT.Game;                          // <-- IUserMod
using UBOAT.Game.Core.Serialization;
using UnityEngine;

namespace DynamicDistanceLabels
{
    [NonSerializedInGameState]
    public class ModLoader : IUserMod
    {
        public void OnLoaded()
        {
            try
            {
                var harmony = new Harmony(Constants.HARMONY_ID);
                harmony.PatchAll();
                Debug.Log($"{Constants.MOD_TAG} v{Constants.MOD_VERSION} loaded. All patches applied.");
            }
            catch (Exception e)
            {
                Debug.LogError($"{Constants.MOD_TAG} Error during patching:");
                Debug.LogException(e);
            }
        }
    }
}

UnitsUtilityPatch.cs

Details
using System;
using System.Reflection;
using Cysharp.Text;
using HarmonyLib;
using UBOAT.Game.Core;
using UBOAT.Game.Core.Data;
using UnityEngine;
using Locale = UBOAT.Game.Core.Data.Locale;   // <-- disambiguate

namespace DynamicDistanceLabels
{
    [HarmonyPatch(typeof(UnitsUtility), "KilometersToPreferredUnits",
        new[] { typeof(Utf16ValueStringBuilder), typeof(float), typeof(bool) },
        new[] { ArgumentType.Ref, ArgumentType.Normal, ArgumentType.Normal })]
    public static class UnitsUtilityPatch
    {
        // Cache the FieldInfo once; avoids per-call Traverse allocations.
        private static readonly FieldInfo LocaleField =
            AccessTools.Field(typeof(UnitsUtility), "locale");

        static bool Prefix(
            ref Utf16ValueStringBuilder stringBuilder,
            float value,
            ref UserSettings ___userSettings,
            ref string ___cache_nmi,
            ref string ___cache_cables,
            ref string ___cache_km,
            ref string ___cache_m)
        {
            try
            {
                var locale = (Locale)LocaleField.GetValue(null);
                if (locale == null) return true; // fall through to original

                bool altPressed = Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt);
                Units units = ___userSettings.GameplaySettings.units;

                // Alt toggles to the opposite unit system.
                bool useNautical = (units == Units.Nautical) ^ altPressed;

                if (useNautical)
                {
                    DynamicDistanceHelper.AppendNautical(
                        value * Constants.KM_TO_NMI, ref stringBuilder, locale,
                        ref ___cache_cables, ref ___cache_nmi);
                }
                else
                {
                    DynamicDistanceHelper.AppendMetric(
                        value, ref stringBuilder, locale,
                        ref ___cache_m, ref ___cache_km);
                }
                return false;
            }
            catch (Exception ex)
            {
                Debug.LogError($"{Constants.MOD_TAG} Exception in UnitsUtility patch: {ex}");
                return true; // safe fallback to original
            }
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment