using 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 $"{text}"; } } internal static class PieceNames { public static string Get(GameObject pieceObj) { if ((Object)(object)pieceObj == (Object)null) { return string.Empty; } return Resolve(pieceObj.GetComponent(), ((Object)pieceObj).name); } public static string Get(WearNTear wnt) { if ((Object)(object)wnt == (Object)null) { return string.Empty; } return Resolve(((Component)wnt).GetComponent(), ((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(); 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 = $"{current.Health}: {s.CurHealth} / {s.MaxHealth}\n" + $"{current.Stability}: {s.CurSupport:0.#} / {s.MaxSupport:0.#}"; } 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 = "" + current.Placing + ": " + ghostName + ""; text3 = $"{current.MaxHealth}: {maxHealth}"; text4 = $"{current.Stability}: {curSupport:0.#} / {maxSupport:0.#}"; } 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(); 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(); 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 Transpiler(IEnumerable instructions) { List list = new List(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 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 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 EnableRepairInfo; internal static ConfigEntry EnableBuildInfo; internal static ConfigEntry ShowOverlay; internal static ConfigEntry ToggleOverlayKey; internal static ConfigEntry UseRichText; internal static ConfigEntry HoverFontSize; internal static ConfigEntry 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(); 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("1. Modes", "EnableRepairInfo", true, "Show current health/stability of the hovered structure while the Repair tool is selected."); EnableBuildInfo = ((BaseUnityPlugin)this).Config.Bind("1. Modes", "EnableBuildInfo", true, "Show max health and predicted stability for the placement ghost while building a new piece."); ShowOverlay = ((BaseUnityPlugin)this).Config.Bind("1. Modes", "ShowOverlayPanel", false, "Draw a parallel info panel via IMGUI in the top-left corner of the screen."); UseRichText = ((BaseUnityPlugin)this).Config.Bind("2. Display", "UseRichText", true, "Use TMP rich-text formatting (bold labels, coloured values) in the cursor hover label."); HoverFontSize = ((BaseUnityPlugin)this).Config.Bind("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(0, 48), Array.Empty())); OverlayFontSize = ((BaseUnityPlugin)this).Config.Bind("2. Display", "OverlayFontSize", 16, new ConfigDescription("Font size (pixels) for the IMGUI overlay in the top-left corner.", (AcceptableValueBase)(object)new AcceptableValueRange(8, 64), Array.Empty())); ToggleOverlayKey = ((BaseUnityPlugin)this).Config.Bind("3. Input", "ToggleOverlayKey", new KeyboardShortcut((KeyCode)289, Array.Empty()), "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 _byLanguage = new Dictionary { ["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; } } } }