Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of StructureHealth v1.5.1
plugins/StructureHealth/StructureHealth.dll
Decompiled 6 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("lubertmiliutin")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("MIT")] [assembly: AssemblyFileVersion("1.5.1.0")] [assembly: AssemblyInformationalVersion("1.5.1+d821256bef6e454729b94226289dcb5106b687cc")] [assembly: AssemblyProduct("StructureHealth")] [assembly: AssemblyTitle("StructureHealth")] [assembly: AssemblyVersion("1.5.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace StructureHealth { internal static class TextStyle { public static string ForFraction(float p) { if (p > 0.66f) { return "#7dd87d"; } if (p > 0.33f) { return "#e9d36b"; } if (p > 0f) { return "#e07070"; } return "#a33a3a"; } public static string WrapHoverSize(string text) { int value = StructureHealthPlugin.HoverFontSize.Value; if (value <= 0) { return text; } return $"<size={value}>{text}</size>"; } } internal static class PieceNames { public static string Get(GameObject pieceObj) { if ((Object)(object)pieceObj == (Object)null) { return string.Empty; } return Resolve(pieceObj.GetComponent<Piece>(), ((Object)pieceObj).name); } public static string Get(WearNTear wnt) { if ((Object)(object)wnt == (Object)null) { return string.Empty; } return Resolve(((Component)wnt).GetComponent<Piece>(), ((Object)wnt).name); } private static string Resolve(Piece piece, string fallback) { if ((Object)(object)piece == (Object)null) { return fallback; } if (Localization.instance == null) { return piece.m_name; } return Localization.instance.Localize(piece.m_name); } } internal static class ValheimRefs { public static readonly MethodInfo GetSupport = AccessTools.Method(typeof(WearNTear), "GetSupport", (Type[])null, (Type[])null); public static readonly MethodInfo GetMaxSupport = AccessTools.Method(typeof(WearNTear), "GetMaxSupport", (Type[])null, (Type[])null); public static readonly MethodInfo UpdateSupport = AccessTools.Method(typeof(WearNTear), "UpdateSupport", (Type[])null, (Type[])null); public static readonly MethodInfo SetupColliders = AccessTools.Method(typeof(WearNTear), "SetupColliders", (Type[])null, (Type[])null); public static readonly MethodInfo ClearCachedSupport = AccessTools.Method(typeof(WearNTear), "ClearCachedSupport", (Type[])null, (Type[])null); public static readonly FieldInfo SupportField = AccessTools.Field(typeof(WearNTear), "m_support"); public static readonly FieldInfo RayMaskField = AccessTools.Field(typeof(WearNTear), "s_rayMask"); public static readonly FieldInfo PlacementGhostField = AccessTools.Field(typeof(Player), "m_placementGhost"); public static readonly FieldInfo RightItemField = AccessTools.Field(typeof(Humanoid), "m_rightItem"); } internal static class SafeNView { public static bool IsOwner(ZNetView nv) { if ((Object)(object)nv != (Object)null) { return nv.IsOwner(); } return false; } public static bool IsValid(ZNetView nv) { if ((Object)(object)nv != (Object)null) { return nv.IsValid(); } return false; } } internal struct PieceStats { public int CurHealth; public int MaxHealth; public float CurSupport; public float MaxSupport; } internal static class WearNTearInfo { public static bool TryRead(WearNTear wnt, out PieceStats stats) { stats = default(PieceStats); if ((Object)(object)wnt == (Object)null) { return false; } stats.MaxHealth = Mathf.RoundToInt(wnt.m_health); stats.CurHealth = Mathf.RoundToInt(Mathf.Clamp01(wnt.GetHealthPercentage()) * wnt.m_health); stats.CurSupport = SupportOf(wnt); stats.MaxSupport = MaxSupportOf(wnt); return true; } public static float SupportOf(WearNTear wnt) { return InvokeFloat(ValheimRefs.GetSupport, wnt); } public static float MaxSupportOf(WearNTear wnt) { return InvokeFloat(ValheimRefs.GetMaxSupport, wnt); } private static float InvokeFloat(MethodInfo m, WearNTear wnt) { if (m == null || (Object)(object)wnt == (Object)null) { return 0f; } try { return (float)m.Invoke(wnt, null); } catch { return 0f; } } } internal static class GhostSupport { private const float ScanHalfExtent = 1f; private static readonly Collider[] _hitBuffer = (Collider[])(object)new Collider[64]; private static int _groundMask = -1; private static bool _useHeuristic; public static GameObject GetPlacementGhost(Player player) { object? obj = ValheimRefs.PlacementGhostField?.GetValue(player); return (GameObject)((obj is GameObject) ? obj : null); } public static bool Estimate(WearNTear ghostWnt, out float curSupport, out float maxSupport) { curSupport = 0f; maxSupport = WearNTearInfo.MaxSupportOf(ghostWnt); if ((Object)(object)ghostWnt == (Object)null) { return false; } if (!_useHeuristic && TryNative(ghostWnt, out curSupport)) { return true; } return Heuristic(ghostWnt, maxSupport, out curSupport); } private static bool TryNative(WearNTear ghostWnt, out float curSupport) { curSupport = 0f; try { ValheimRefs.ClearCachedSupport?.Invoke(ghostWnt, null); ValheimRefs.SetupColliders?.Invoke(ghostWnt, null); } catch (Exception e) { DisableNative("SetupColliders", e); return false; } try { ValheimRefs.UpdateSupport?.Invoke(ghostWnt, null); } catch (TargetInvocationException ex) when (ex.InnerException is NullReferenceException) { } catch (Exception e2) { DisableNative("UpdateSupport", e2); return false; } if (ValheimRefs.SupportField == null) { return false; } curSupport = (float)ValheimRefs.SupportField.GetValue(ghostWnt); return true; } private static bool Heuristic(WearNTear ghostWnt, float maxSupport, out float curSupport) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) curSupport = 0f; if ((Object)(object)ghostWnt == (Object)null) { return false; } Vector3 position = ((Component)ghostWnt).transform.position; Quaternion rotation = ((Component)ghostWnt).transform.rotation; Vector3 half = default(Vector3); ((Vector3)(ref half))..ctor(1f, 1f, 1f); if (IsGrounded(position, rotation, half)) { curSupport = maxSupport; return true; } curSupport = StrongestNeighbourSupport(ghostWnt, position, rotation, half, maxSupport); return true; } private static bool IsGrounded(Vector3 pos, Quaternion rot, Vector3 half) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) int num = GroundMask(); if (num == 0) { return false; } return Physics.OverlapBoxNonAlloc(pos, half, _hitBuffer, rot, num, (QueryTriggerInteraction)1) > 0; } private static float StrongestNeighbourSupport(WearNTear self, Vector3 pos, Quaternion rot, Vector3 half, float cap) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) int num = Physics.OverlapBoxNonAlloc(pos, half, _hitBuffer, rot, PieceMask(), (QueryTriggerInteraction)1); float num2 = 0f; for (int i = 0; i < num; i++) { WearNTear val = NeighbourFrom(_hitBuffer[i], self); if (!((Object)(object)val == (Object)null)) { float num3 = WearNTearInfo.SupportOf(val); float num4 = DirectionalFactor(pos - ((Component)val).transform.position); float num5 = num3 * num4; if (num5 > num2) { num2 = num5; } } } return Mathf.Min(num2, cap); } private static float DirectionalFactor(Vector3 toGhost) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) float sqrMagnitude = ((Vector3)(ref toGhost)).sqrMagnitude; float num = ((sqrMagnitude > 0.0001f) ? Vector3.Dot(toGhost / Mathf.Sqrt(sqrMagnitude), Vector3.up) : 0f); return Mathf.Lerp(0.55f, 0.9f, (num + 1f) * 0.5f); } private static WearNTear NeighbourFrom(Collider col, WearNTear self) { if ((Object)(object)col == (Object)null) { return null; } WearNTear componentInParent = ((Component)col).GetComponentInParent<WearNTear>(); if ((Object)(object)componentInParent == (Object)null || (Object)(object)componentInParent == (Object)(object)self) { return null; } if ((Object)(object)((Component)componentInParent).gameObject == (Object)(object)((Component)self).gameObject) { return null; } if (!componentInParent.m_supports) { return null; } return componentInParent; } private static int GroundMask() { if (_groundMask != -1) { return _groundMask; } _groundMask = LayerMask.GetMask(new string[3] { "terrain", "static_solid", "Default" }); return _groundMask; } private static int PieceMask() { if (ValheimRefs.RayMaskField != null && ValheimRefs.RayMaskField.GetValue(null) is int num && num != 0) { return num; } return -5; } private static void DisableNative(string where, Exception e) { Exception ex = (e as TargetInvocationException)?.InnerException ?? e; StructureHealthPlugin.Log.LogWarning((object)(where + " threw " + ex.GetType().Name + ": " + ex.Message + ". Using heuristic from now on.")); _useHeuristic = true; } } internal static class HoverFormatter { public static string ForHover(PieceStats s) { Strings.Translation current = Strings.Current; string text3; if (StructureHealthPlugin.UseRichText.Value) { string text = TextStyle.ForFraction((s.MaxHealth > 0) ? ((float)s.CurHealth / (float)s.MaxHealth) : 0f); string text2 = TextStyle.ForFraction((s.MaxSupport > 0f) ? (s.CurSupport / s.MaxSupport) : 0f); text3 = $"<color=#dddddd>{current.Health}:</color> <color={text}>{s.CurHealth} / {s.MaxHealth}</color>\n" + $"<color=#dddddd>{current.Stability}:</color> <color={text2}>{s.CurSupport:0.#} / {s.MaxSupport:0.#}</color>"; } else { text3 = $"{current.Health}: {s.CurHealth} / {s.MaxHealth}\n{current.Stability}: {s.CurSupport:0.#} / {s.MaxSupport:0.#}"; } return TextStyle.WrapHoverSize(text3); } public static string ForGhost(string ghostName, int maxHealth, float curSupport, float maxSupport) { Strings.Translation current = Strings.Current; string text2; string text3; string text4; if (StructureHealthPlugin.UseRichText.Value) { string text = TextStyle.ForFraction((maxSupport > 0f) ? (curSupport / maxSupport) : 0f); text2 = "<color=#9fc0e0>" + current.Placing + ":</color> <b>" + ghostName + "</b>"; text3 = $"<color=#dddddd>{current.MaxHealth}:</color> {maxHealth}"; text4 = $"<color=#dddddd>{current.Stability}:</color> <color={text}>{curSupport:0.#} / {maxSupport:0.#}</color>"; } else { text2 = current.Placing + ": " + ghostName; text3 = $"{current.MaxHealth}: {maxHealth}"; text4 = $"{current.Stability}: {curSupport:0.#} / {maxSupport:0.#}"; } return TextStyle.WrapHoverSize(text2 + "\n" + text3 + "\n" + text4); } } internal class OverlayRenderer : MonoBehaviour { public static WearNTear CurrentTarget; public static float LastSeenTime; private const float TargetTimeoutSeconds = 0.2f; private const string HammerItemKey = "$item_hammer"; private GUIStyle _style; private int _cachedFontSize = -1; private void Update() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value = StructureHealthPlugin.ToggleOverlayKey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { bool flag = !StructureHealthPlugin.ShowOverlay.Value; StructureHealthPlugin.ShowOverlay.Value = flag; StructureHealthPlugin.Log.LogInfo((object)$"Overlay panel: {flag}"); } } private void OnGUI() { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) if (StructureHealthPlugin.ShowOverlay.Value && IsTargetFresh() && IsHammerEquipped(Player.m_localPlayer) && WearNTearInfo.TryRead(CurrentTarget, out var stats)) { Strings.Translation current = Strings.Current; string text = PieceNames.Get(CurrentTarget) + "\n" + $"{current.Health}: {stats.CurHealth} / {stats.MaxHealth}\n" + $"{current.Stability}: {stats.CurSupport:0.#} / {stats.MaxSupport:0.#}"; EnsureStyle(); DrawShadowed(new Rect(20f, 20f, 480f, 120f), text); } } private static bool IsTargetFresh() { if ((Object)(object)CurrentTarget != (Object)null) { return Time.unscaledTime - LastSeenTime <= 0.2f; } return false; } private static bool IsHammerEquipped(Player player) { if ((Object)(object)player == (Object)null) { return false; } object? obj = ValheimRefs.RightItemField?.GetValue(player); ItemData val = (ItemData)((obj is ItemData) ? obj : null); if (val != null && val.m_shared != null) { return val.m_shared.m_name == "$item_hammer"; } return false; } private void DrawShadowed(Rect rect, string text) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) Color textColor = _style.normal.textColor; _style.normal.textColor = new Color(0f, 0f, 0f, 0.85f); GUI.Label(new Rect(((Rect)(ref rect)).x + 2f, ((Rect)(ref rect)).y + 2f, ((Rect)(ref rect)).width, ((Rect)(ref rect)).height), text, _style); _style.normal.textColor = textColor; GUI.Label(rect, text, _style); } private void EnsureStyle() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.Clamp(StructureHealthPlugin.OverlayFontSize.Value, 8, 64); if (_style == null || _cachedFontSize != num) { _cachedFontSize = num; _style = new GUIStyle(GUI.skin.label) { fontSize = num, fontStyle = (FontStyle)1, alignment = (TextAnchor)0, richText = false }; _style.normal.textColor = Color.white; } } } [HarmonyPatch(typeof(WearNTear), "Highlight")] internal static class WearNTear_Highlight_Patch { private static void Postfix(WearNTear __instance) { OverlayRenderer.CurrentTarget = __instance; OverlayRenderer.LastSeenTime = Time.unscaledTime; } } [HarmonyPatch(typeof(Hud), "UpdateCrosshair")] internal static class Hud_UpdateCrosshair_Patch { private static void Postfix(Hud __instance, Player player) { if ((Object)(object)player == (Object)null || (Object)(object)__instance == (Object)null || !((Character)player).InPlaceMode()) { return; } TextMeshProUGUI hoverName = __instance.m_hoverName; if (!((Object)(object)hoverName == (Object)null)) { if (player.InRepairMode()) { AppendRepairInfo(player, hoverName); } else { OverrideWithGhostInfo(player, hoverName); } } } private static void AppendRepairInfo(Player player, TextMeshProUGUI label) { if (!StructureHealthPlugin.EnableRepairInfo.Value) { return; } Piece hoveringPiece = player.GetHoveringPiece(); if ((Object)(object)hoveringPiece == (Object)null) { return; } WearNTear component = ((Component)hoveringPiece).GetComponent<WearNTear>(); if (!((Object)(object)component == (Object)null) && WearNTearInfo.TryRead(component, out var stats)) { string text = HoverFormatter.ForHover(stats); if (!string.IsNullOrEmpty(text)) { ((TMP_Text)label).richText = true; string text2 = ((TMP_Text)label).text ?? string.Empty; ((TMP_Text)label).text = ((text2.Length > 0) ? (text2 + "\n" + text) : text); } } } private static void OverrideWithGhostInfo(Player player, TextMeshProUGUI label) { if (!StructureHealthPlugin.EnableBuildInfo.Value) { return; } GameObject placementGhost = GhostSupport.GetPlacementGhost(player); if (!((Object)(object)placementGhost == (Object)null)) { WearNTear component = placementGhost.GetComponent<WearNTear>(); if (!((Object)(object)component == (Object)null) && GhostSupport.Estimate(component, out var curSupport, out var maxSupport)) { int maxHealth = Mathf.RoundToInt(component.m_health); string text = HoverFormatter.ForGhost(PieceNames.Get(placementGhost), maxHealth, curSupport, maxSupport); ((TMP_Text)label).richText = true; ((TMP_Text)label).text = text; } } } } [HarmonyPatch(typeof(WearNTear), "UpdateSupport")] internal static class WearNTear_UpdateSupport_Transpiler { private static readonly MethodInfo NViewIsOwner = AccessTools.Method(typeof(ZNetView), "IsOwner", (Type[])null, (Type[])null); private static readonly MethodInfo NViewIsValid = AccessTools.Method(typeof(ZNetView), "IsValid", (Type[])null, (Type[])null); private static readonly MethodInfo SafeIsOwner = AccessTools.Method(typeof(SafeNView), "IsOwner", (Type[])null, (Type[])null); private static readonly MethodInfo SafeIsValid = AccessTools.Method(typeof(SafeNView), "IsValid", (Type[])null, (Type[])null); private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); int earlyExitGuard = FindEarlyExitGuard(list); Label? continueLabel = ((earlyExitGuard > 0) ? PriorLoopContinueLabel(list, earlyExitGuard) : null); for (int i = 0; i < list.Count; i++) { CodeInstruction val = list[i]; if (val.opcode == OpCodes.Callvirt && val.operand is MethodInfo methodInfo) { if (methodInfo == NViewIsOwner) { yield return new CodeInstruction(OpCodes.Call, (object)SafeIsOwner); continue; } if (methodInfo == NViewIsValid) { yield return new CodeInstruction(OpCodes.Call, (object)SafeIsValid); continue; } } yield return val; if (i == earlyExitGuard && continueLabel.HasValue) { yield return new CodeInstruction(OpCodes.Br, (object)continueLabel.Value); } } } private static int FindEarlyExitGuard(List<CodeInstruction> list) { for (int i = 1; i < list.Count - 3; i++) { if ((!(list[i].opcode != OpCodes.Brfalse_S) || !(list[i].opcode != OpCodes.Brfalse)) && !(list[i - 1].opcode != OpCodes.Call) && !((list[i - 1].operand as MethodInfo)?.Name != "op_Equality") && !(list[i + 1].opcode != OpCodes.Ldarg_0) && !(list[i + 3].opcode != OpCodes.Stfld) && !((list[i + 3].operand as FieldInfo)?.Name != "m_support")) { return i; } } return -1; } private static Label? PriorLoopContinueLabel(List<CodeInstruction> list, int beforeIdx) { for (int num = beforeIdx - 1; num >= 0; num--) { if (list[num].opcode == OpCodes.Br) { object operand = list[num].operand; if (operand is Label) { return (Label)operand; } } } return null; } } [BepInPlugin("com.lubert.valheim.structurehealth", "StructureHealth", "1.5.1")] public class StructureHealthPlugin : BaseUnityPlugin { public const string PluginGuid = "com.lubert.valheim.structurehealth"; public const string PluginName = "StructureHealth"; public const string PluginVersion = "1.5.1"; internal static ManualLogSource Log; internal static ConfigEntry<bool> EnableRepairInfo; internal static ConfigEntry<bool> EnableBuildInfo; internal static ConfigEntry<bool> ShowOverlay; internal static ConfigEntry<KeyboardShortcut> ToggleOverlayKey; internal static ConfigEntry<bool> UseRichText; internal static ConfigEntry<int> HoverFontSize; internal static ConfigEntry<int> OverlayFontSize; private Harmony _harmony; private GameObject _runner; private void Awake() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; BindConfig(); _harmony = new Harmony("com.lubert.valheim.structurehealth"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); _runner = new GameObject("StructureHealthRunner"); _runner.AddComponent<OverlayRenderer>(); Object.DontDestroyOnLoad((Object)(object)_runner); Log.LogInfo((object)"StructureHealth v1.5.1 loaded."); } private void OnDestroy() { if ((Object)(object)_runner != (Object)null) { Object.Destroy((Object)(object)_runner); } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void BindConfig() { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Expected O, but got Unknown //IL_0100: Unknown result type (might be due to invalid IL or missing references) EnableRepairInfo = ((BaseUnityPlugin)this).Config.Bind<bool>("1. Modes", "EnableRepairInfo", true, "Show current health/stability of the hovered structure while the Repair tool is selected."); EnableBuildInfo = ((BaseUnityPlugin)this).Config.Bind<bool>("1. Modes", "EnableBuildInfo", true, "Show max health and predicted stability for the placement ghost while building a new piece."); ShowOverlay = ((BaseUnityPlugin)this).Config.Bind<bool>("1. Modes", "ShowOverlayPanel", false, "Draw a parallel info panel via IMGUI in the top-left corner of the screen."); UseRichText = ((BaseUnityPlugin)this).Config.Bind<bool>("2. Display", "UseRichText", true, "Use TMP rich-text formatting (bold labels, coloured values) in the cursor hover label."); HoverFontSize = ((BaseUnityPlugin)this).Config.Bind<int>("2. Display", "HoverFontSize", 18, new ConfigDescription("Font size (TMP units) for the info text appended to the cursor hover label. 0 = inherit Valheim's default.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 48), Array.Empty<object>())); OverlayFontSize = ((BaseUnityPlugin)this).Config.Bind<int>("2. Display", "OverlayFontSize", 16, new ConfigDescription("Font size (pixels) for the IMGUI overlay in the top-left corner.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(8, 64), Array.Empty<object>())); ToggleOverlayKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("3. Input", "ToggleOverlayKey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Toggle the IMGUI overlay panel on/off."); } } internal static class Strings { public class Translation { public string Health; public string Stability; public string Placing; public string MaxHealth; } private static readonly Translation _fallback = new Translation { Health = "Health", Stability = "Stability", Placing = "Placing", MaxHealth = "Max Health" }; private static readonly Dictionary<string, Translation> _byLanguage = new Dictionary<string, Translation> { ["English"] = _fallback, ["Ukrainian"] = new Translation { Health = "Здоров'я", Stability = "Стабільність", Placing = "Розміщення", MaxHealth = "Макс. здоров'я" }, ["German"] = new Translation { Health = "Leben", Stability = "Stabilität", Placing = "Platzierung", MaxHealth = "Max. Leben" }, ["French"] = new Translation { Health = "Santé", Stability = "Stabilité", Placing = "Placement", MaxHealth = "Santé max." }, ["Spanish"] = new Translation { Health = "Salud", Stability = "Estabilidad", Placing = "Colocación", MaxHealth = "Salud máx." } }; public static Translation Current { get { string text = ((Localization.instance != null) ? Localization.instance.GetSelectedLanguage() : null); if (string.IsNullOrEmpty(text) || !_byLanguage.TryGetValue(text, out var value)) { return _fallback; } return value; } } } }