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 RunicAwareness v1.0.0
RunicAwareness.dll
Decompiled 14 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using RunicAwareness.Core; using RunicAwareness.Integration; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Runic Awareness")] [assembly: AssemblyDescription("Bounded, display-only explanations of already-known Valheim state.")] [assembly: AssemblyCompany("Chazman")] [assembly: AssemblyProduct("Runic Awareness")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: InternalsVisibleTo("RunicAwareness.Tests")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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 RunicAwareness { public enum AwarenessTimerPrecision { WholeSecond = 1, FiveSeconds = 5, TenSeconds = 10, WholeMinute = 60 } public enum AwarenessOverlayAnchor { TopRight, TopLeft, BottomRight, BottomLeft, MiddleLeft } internal static class AwarenessConfig { internal const int HardMaximumFoodScan = 8; internal const int HardMaximumEffectScan = 32; internal const int HardMaximumComfortPieces = 64; internal const int HardMaximumPanelLines = 40; internal const int HardMaximumPanelCharacters = 4096; internal static ConfigEntry<bool> Enabled { get; private set; } internal static ConfigEntry<bool> ShowFoodTimers { get; private set; } internal static ConfigEntry<bool> ShowEffectTimers { get; private set; } internal static ConfigEntry<bool> ShowComfort { get; private set; } internal static ConfigEntry<bool> ShowItemComparison { get; private set; } internal static ConfigEntry<bool> ShowProduction { get; private set; } internal static ConfigEntry<bool> ShowAgriculture { get; private set; } internal static ConfigEntry<bool> ShowBuilding { get; private set; } internal static ConfigEntry<bool> ShowTamedAnimals { get; private set; } internal static ConfigEntry<AwarenessTimerPrecision> TimerPrecision { get; private set; } internal static ConfigEntry<float> RefreshInterval { get; private set; } internal static ConfigEntry<int> MaximumEffectRows { get; private set; } internal static ConfigEntry<int> MaximumComfortRows { get; private set; } internal static ConfigEntry<int> MaximumContextLines { get; private set; } internal static ConfigEntry<float> UiScale { get; private set; } internal static ConfigEntry<float> ControllerScaleMultiplier { get; private set; } internal static ConfigEntry<AwarenessOverlayAnchor> Anchor { get; private set; } internal static ConfigEntry<bool> MiddleLeftAnchorMigrationApplied { get; private set; } internal static ConfigEntry<bool> VerboseLogging { get; private set; } internal static void Bind(ConfigFile config) { //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Expected O, but got Unknown //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Expected O, but got Unknown //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Expected O, but got Unknown //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Expected O, but got Unknown //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Expected O, but got Unknown //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Expected O, but got Unknown Enabled = config.Bind<bool>("General", "Enabled", true, "Enable Runic Awareness. This module only observes and renders already-known local state."); ShowFoodTimers = Panel(config, "FoodTimers", defaultValue: true, "Show remaining time and the local food item that supplied each active food slot."); ShowEffectTimers = Panel(config, "EffectTimers", defaultValue: true, "Show locally active HUD status effects and their remaining time."); ShowComfort = Panel(config, "ComfortBreakdown", defaultValue: true, "Show current comfort, Rested time, and bounded category results from vanilla's own comfort pass."); ShowItemComparison = Panel(config, "ItemComparison", defaultValue: true, "Retain bounded item-comparison capture for compatibility; inventory UI suppression takes precedence, so this capture is not rendered as an Awareness panel while inventory is open."); ShowProduction = Panel(config, "ProductionContext", defaultValue: true, "Show the currently hovered station's visible state and optional Runic Production status when its runtime contract is available."); ShowAgriculture = Panel(config, "AgricultureContext", defaultValue: true, "Show bounded text already exposed by the currently hovered crop or beehive."); ShowBuilding = Panel(config, "BuildingContext", defaultValue: true, "Show the currently selected or hovered build piece's known transform, health, station level, and vanilla support legend."); ShowTamedAnimals = Panel(config, "TamedAnimalContext", defaultValue: true, "Show bounded text already exposed by the currently hovered tameable animal."); TimerPrecision = config.Bind<AwarenessTimerPrecision>("Display", "TimerPrecision", AwarenessTimerPrecision.WholeSecond, "Round remaining timers upward to whole seconds, five seconds, ten seconds, or whole minutes."); RefreshInterval = config.Bind<float>("Display", "RefreshIntervalSeconds", 0.35f, new ConfigDescription("Low-frequency state sampling interval. Text is rebuilt only when a bounded state signature changes.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 2f), Array.Empty<object>())); MaximumEffectRows = config.Bind<int>("Display", "MaximumEffectRows", 5, new ConfigDescription("Maximum visible HUD-effect rows. The scan itself is hard-capped at 32 local effects.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>())); MaximumComfortRows = config.Bind<int>("Display", "MaximumComfortWinnerRows", 6, new ConfigDescription("Maximum named comfort winners. Oversized vanilla comfort sets fail closed to a level-only explanation.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>())); MaximumContextLines = config.Bind<int>("Display", "MaximumContextLines", 5, new ConfigDescription("Maximum lines copied from already-visible current-hover text.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 6), Array.Empty<object>())); UiScale = config.Bind<float>("Display", "UiScale", 1f, new ConfigDescription("Overlay text and panel scale.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.75f, 2f), Array.Empty<object>())); ControllerScaleMultiplier = config.Bind<float>("Display", "ControllerScaleMultiplier", 1.15f, new ConfigDescription("Additional readability scale while Valheim reports controller navigation as active.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 1.5f), Array.Empty<object>())); Anchor = config.Bind<AwarenessOverlayAnchor>("Display", "Anchor", AwarenessOverlayAnchor.MiddleLeft, "Place the non-interactive overlay at a safe-area anchor. The default is left-middle."); MiddleLeftAnchorMigrationApplied = config.Bind<bool>("Migrations", "DefaultAnchorMovedToMiddleLeft", false, "Internal one-time migration marker. Once true, later Anchor choices are never rewritten by this migration."); if (!MiddleLeftAnchorMigrationApplied.Value) { AwarenessOverlayAnchor awarenessOverlayAnchor = MigrateLegacyAnchor(Anchor.Value, migrationApplied: false); if (awarenessOverlayAnchor != Anchor.Value) { Anchor.Value = awarenessOverlayAnchor; } MiddleLeftAnchorMigrationApplied.Value = true; } VerboseLogging = config.Bind<bool>("Diagnostics", "VerboseLogging", false, "Log Awareness configuration refreshes. Never logs hidden world state."); } internal static AwarenessOverlayAnchor MigrateLegacyAnchor(AwarenessOverlayAnchor anchor, bool migrationApplied) { if (migrationApplied || anchor != AwarenessOverlayAnchor.TopRight) { return anchor; } return AwarenessOverlayAnchor.MiddleLeft; } private static ConfigEntry<bool> Panel(ConfigFile config, string key, bool defaultValue, string description) { return config.Bind<bool>("Panels", key, defaultValue, description); } } [BepInPlugin("chazman.RunicAwareness", "Runic Awareness", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "chazman.RunicAwareness"; public const string Name = "Runic Awareness"; public const string Version = "1.0.0"; private Harmony _harmony; private AwarenessRuntime _runtime; private bool _languageSubscribed; internal static Plugin Instance { get; private set; } internal AwarenessRuntime Runtime => _runtime; private void Awake() { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown Instance = this; AwarenessConfig.Bind(((BaseUnityPlugin)this).Config); ((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged; try { ValheimContracts.VerifyInstalledSignatures(); _runtime = new AwarenessRuntime(((BaseUnityPlugin)this).Logger, Application.isBatchMode); Localization.OnLanguageChange = (Action)Delegate.Combine(Localization.OnLanguageChange, new Action(OnLanguageChange)); _languageSubscribed = true; if (!Application.isBatchMode) { _harmony = new Harmony("chazman.RunicAwareness"); _harmony.PatchAll(typeof(Plugin).Assembly); } ((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Awareness v1.0.0 ready: bounded local timers, comfort explanation, equipment comparison, and current-context display panels; gameplay state is never changed."); if (Application.isBatchMode) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Dedicated/batch process detected; the display runtime is intentionally inert."); } } catch (Exception ex) { Cleanup(); ((BaseUnityPlugin)this).Logger.LogError((object)("Runic Awareness failed closed during startup; vanilla UI and gameplay remain unchanged. " + ex.GetType().Name + ": " + ex.Message)); } } private void Update() { try { _runtime?.Update(); } catch (Exception exception) { _runtime?.FailClosed("update", exception); } } private void OnGUI() { try { _runtime?.Draw(); } catch (Exception exception) { _runtime?.FailClosed("draw", exception); } } private void OnSettingChanged(object sender, SettingChangedEventArgs arguments) { try { AwarenessRuntime runtime = _runtime; if (runtime != null) { object definition; if (arguments == null) { definition = null; } else { ConfigEntryBase changedSetting = arguments.ChangedSetting; definition = ((changedSetting != null) ? changedSetting.Definition : null); } runtime.OnConfigurationChanged((ConfigDefinition)definition); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Awareness configuration refresh failed closed: " + ex.Message)); } } private void OnLanguageChange() { try { ComfortCapture.Reset(); ContextCapture.Reset(); HoverItemCapture.Reset(); _runtime?.OnLocalizationChanged(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Awareness language refresh failed closed: " + ex.Message)); } } private void OnDestroy() { ((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged; Cleanup(); Instance = null; } private void Cleanup() { if (_languageSubscribed) { Localization.OnLanguageChange = (Action)Delegate.Remove(Localization.OnLanguageChange, new Action(OnLanguageChange)); _languageSubscribed = false; } _runtime?.Dispose(); _runtime = null; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; ContextCapture.Reset(); HoverItemCapture.Reset(); ComfortCapture.Reset(); } } } namespace RunicAwareness.Integration { internal sealed class AwarenessRuntime : IDisposable { private const int MaximumFoodRows = 3; private const int MaximumContextCharacters = 768; private readonly ManualLogSource _log; private readonly bool _inert; private readonly PanelCache _panels = new PanelCache(); private readonly StringBuilder _builder = new StringBuilder(1024); private readonly OptionalPortalPanelAdapter _portalPanels = new OptionalPortalPanelAdapter(); private readonly GUIContent _content = new GUIContent(); private GUIStyle _textStyle; private GUIStyle _boxStyle; private float _nextRefresh; private float _nextStationLevelRefresh; private bool _failed; private bool _layoutDirty = true; private int _styleFontSize; private float _cachedHeight; private float _lastInnerWidth; private float _lastMaximumHeight; private int _hookFailureCount; private ulong _foodSignature; private ulong _effectSignature; private ulong _comfortSignature; private ulong _itemSignature; private ulong _contextSignature; private Piece _lastBuildingPiece; private int _cachedStationLevel; private bool _hadFoodSignature; private bool _hadEffectSignature; private bool _hadComfortSignature; private bool _hadItemSignature; private bool _hadContextSignature; internal AwarenessRuntime(ManualLogSource log, bool inert) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown _log = log ?? throw new ArgumentNullException("log"); _inert = inert; } internal void Update() { if (_inert || _failed) { return; } float unscaledTime = Time.unscaledTime; if (unscaledTime < _nextRefresh && unscaledTime >= 0f) { return; } _nextRefresh = unscaledTime + FiniteClamp(AwarenessConfig.RefreshInterval.Value, 0.2f, 2f, 0.35f); if (!AwarenessConfig.Enabled.Value) { ClearAll(); return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { ClearAll(); return; } if (InventoryGui.IsVisible()) { UpdateItemComparison(localPlayer, unscaledTime); return; } _panels.Clear(AwarenessPanel.ItemComparison); UpdateFood(localPlayer); UpdateEffects(localPlayer); UpdateComfort(localPlayer); UpdateContext(localPlayer, unscaledTime); } internal void Draw() { //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) if (_inert || _failed || !AwarenessConfig.Enabled.Value || Event.current == null || (int)Event.current.type != 7 || ShouldSuppressOverlay()) { return; } bool inventoryOnly = InventoryGui.IsVisible(); if (_panels.Compose(inventoryOnly)) { _content.text = _panels.ComposedText; _layoutDirty = true; } if (!string.IsNullOrEmpty(_content.text)) { bool flag = false; try { flag = ZInput.IsGamepadActive(); } catch (Exception) { } float num = Mathf.Clamp(FiniteClamp(AwarenessConfig.UiScale.Value, 0.75f, 2f, 1f) * (flag ? FiniteClamp(AwarenessConfig.ControllerScaleMultiplier.Value, 1f, 1.5f, 1.15f) : 1f), 0.75f, 3f); int num2 = Mathf.Clamp(Mathf.RoundToInt(14f * num), 11, 42); EnsureStyles(num2); Rect safeArea = Screen.safeArea; float num3 = Mathf.Max(10f, 14f * num); float num4 = Mathf.Max(120f, ((Rect)(ref safeArea)).width - num3 * 2f); float num5 = Mathf.Min(280f, num4); float num6 = Mathf.Clamp(430f * num, num5, num4); float num7 = Mathf.Max(120f, num6 - num3 * 1.6f); float num8 = Mathf.Max((float)num2 + num3, ((Rect)(ref safeArea)).height - num3 * 2f); if (Mathf.Abs(num7 - _lastInnerWidth) > 0.5f) { _lastInnerWidth = num7; _layoutDirty = true; } if (Mathf.Abs(num8 - _lastMaximumHeight) > 0.5f) { _lastMaximumHeight = num8; _layoutDirty = true; } if (_layoutDirty) { _cachedHeight = Mathf.Min(num8, _textStyle.CalcHeight(_content, num7) + num3 * 1.4f); _layoutDirty = false; } float height = Mathf.Min(num8, Mathf.Max((float)num2 + num3, _cachedHeight)); Rect val = AnchoredRect(safeArea, Screen.height, num6, height, num3, AwarenessConfig.Anchor.Value); Rect val2 = new Rect(((Rect)(ref val)).x + num3 * 0.8f, ((Rect)(ref val)).y + num3 * 0.65f, ((Rect)(ref val)).width - num3 * 1.6f, ((Rect)(ref val)).height - num3 * 1.3f); GUI.Box(val, GUIContent.none, _boxStyle); GUI.Label(val2, _content, _textStyle); } } internal void OnConfigurationChanged(ConfigDefinition definition) { _nextRefresh = 0f; ResetSignatures(); if (definition == (ConfigDefinition)null || (definition.Section == "Display" && definition.Key == "MaximumComfortWinnerRows")) { ComfortCapture.Reset(); } _styleFontSize = 0; _layoutDirty = true; if (!AwarenessConfig.ShowFoodTimers.Value) { _panels.Clear(AwarenessPanel.Food); } if (!AwarenessConfig.ShowEffectTimers.Value) { _panels.Clear(AwarenessPanel.Effects); } if (!AwarenessConfig.ShowComfort.Value) { _panels.Clear(AwarenessPanel.Comfort); } if (!AwarenessConfig.ShowComfort.Value) { ComfortCapture.Reset(); } if (!AwarenessConfig.ShowItemComparison.Value) { _panels.Clear(AwarenessPanel.ItemComparison); HoverItemCapture.Reset(); } if (!AwarenessConfig.ShowProduction.Value) { _panels.Clear(AwarenessPanel.Production); } if (!AwarenessConfig.ShowAgriculture.Value) { _panels.Clear(AwarenessPanel.Agriculture); } if (!AwarenessConfig.ShowBuilding.Value) { _panels.Clear(AwarenessPanel.Building); } if (!AwarenessConfig.ShowTamedAnimals.Value) { _panels.Clear(AwarenessPanel.TamedAnimal); } if (!AwarenessConfig.ShowProduction.Value && !AwarenessConfig.ShowAgriculture.Value && !AwarenessConfig.ShowBuilding.Value && !AwarenessConfig.ShowTamedAnimals.Value) { ContextCapture.Reset(); } if (!AwarenessConfig.Enabled.Value) { ComfortCapture.Reset(); HoverItemCapture.Reset(); ContextCapture.Reset(); ClearAll(); } if (AwarenessConfig.VerboseLogging.Value) { _log.LogInfo((object)("Awareness configuration refreshed: " + ((definition == (ConfigDefinition)null) ? "unknown" : (definition.Section + "/" + definition.Key)))); } } internal void OnLocalizationChanged() { _nextRefresh = 0f; ResetSignatures(); ClearAll(); if (AwarenessConfig.VerboseLogging.Value) { _log.LogInfo((object)"Awareness display cache cleared for a language change."); } } internal void FailClosed(string operation, Exception exception) { if (!_failed) { _failed = true; ClearAll(); _log.LogError((object)("Runic Awareness display runtime stopped after " + operation + "; gameplay and vanilla UI remain unchanged. " + exception.GetType().Name + ": " + exception.Message)); } } internal void FailHook(string hook, Exception exception) { if (_hookFailureCount < 3) { _hookFailureCount++; _log.LogWarning((object)("Awareness ignored a display capture failure in " + hook + ": " + exception.GetType().Name + ".")); } } public void Dispose() { ClearAll(); _textStyle = null; _boxStyle = null; } private void UpdateFood(Player player) { if (!AwarenessConfig.ShowFoodTimers.Value) { _panels.Clear(AwarenessPanel.Food); return; } List<Food> foods = player.GetFoods(); int num = foods?.Count ?? 0; ulong num2 = HashStart(num, (int)AwarenessConfig.TimerPrecision.Value); if (num > 8) { num2 = HashMix(num2, num); if (!SameSignature(ref _foodSignature, ref _hadFoodSignature, num2)) { _panels.Set(AwarenessPanel.Food, "Details withheld: local food list exceeds the hard limit of " + 8 + "."); } return; } for (int i = 0; i < num; i++) { Food val = foods[i]; num2 = HashMix(num2, TimerFormatter.Bucket(val?.m_time ?? 0f, AwarenessConfig.TimerPrecision.Value)); num2 = HashMix(num2, BoundedText.HashBounded(FoodName(val), 96)); } if (SameSignature(ref _foodSignature, ref _hadFoodSignature, num2)) { return; } if (num == 0) { _panels.Clear(AwarenessPanel.Food); return; } _builder.Clear(); int num3 = Math.Min(num, 3); for (int j = 0; j < num3; j++) { if (j > 0) { _builder.Append('\n'); } Food val2 = foods[j]; _builder.Append(BoundedLocalization.Label(FoodName(val2))).Append(": "); TimerFormatter.Append(_builder, TimerFormatter.Bucket(val2?.m_time ?? 0f, AwarenessConfig.TimerPrecision.Value)); } if (num > num3) { _builder.Append("\n+").Append(num - num3).Append(" more local food slots"); } _panels.Set(AwarenessPanel.Food, BoundedText.Sanitize(_builder.ToString(), 512, 4)); } private void UpdateEffects(Player player) { if (!AwarenessConfig.ShowEffectTimers.Value) { _panels.Clear(AwarenessPanel.Effects); return; } SEMan sEMan = ((Character)player).GetSEMan(); List<StatusEffect> list = ((sEMan != null) ? sEMan.GetStatusEffects() : null); int num = list?.Count ?? 0; ulong hash = HashStart(num, (int)AwarenessConfig.TimerPrecision.Value); if (num > 32) { hash = HashMix(hash, num); if (!SameSignature(ref _effectSignature, ref _hadEffectSignature, hash)) { _panels.Set(AwarenessPanel.Effects, "Details withheld: local effect list exceeds the hard limit of " + 32 + "."); } return; } int num2 = 0; for (int i = 0; i < num; i++) { StatusEffect val = list[i]; if (!((Object)(object)val == (Object)null) && !((Object)(object)val.m_icon == (Object)null)) { num2++; int value = ((val.m_ttl > 0f) ? TimerFormatter.Bucket(val.GetRemaningTime(), AwarenessConfig.TimerPrecision.Value) : (-1)); hash = HashMix(hash, value); hash = HashMix(hash, BoundedText.HashBounded(val.m_name, 96)); } } hash = HashMix(hash, num2); if (SameSignature(ref _effectSignature, ref _hadEffectSignature, hash)) { return; } if (num2 == 0) { _panels.Clear(AwarenessPanel.Effects); return; } _builder.Clear(); int num3 = Math.Max(1, Math.Min(8, AwarenessConfig.MaximumEffectRows.Value)); int num4 = 0; for (int j = 0; j < num; j++) { if (num4 >= num3) { break; } StatusEffect val2 = list[j]; if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.m_icon == (Object)null)) { if (num4 > 0) { _builder.Append('\n'); } _builder.Append(BoundedLocalization.Label(val2.m_name)).Append(": "); if (val2.m_ttl > 0f) { TimerFormatter.Append(_builder, TimerFormatter.Bucket(val2.GetRemaningTime(), AwarenessConfig.TimerPrecision.Value)); } else { _builder.Append("active"); } num4++; } } if (num2 > num4) { _builder.Append("\n+").Append(num2 - num4).Append(" more HUD effects"); } _panels.Set(AwarenessPanel.Effects, BoundedText.Sanitize(_builder.ToString(), 768, num3 + 1)); } private void UpdateComfort(Player player) { if (!AwarenessConfig.ShowComfort.Value) { _panels.Clear(AwarenessPanel.Comfort); return; } int num = Math.Max(0, player.GetComfortLevel()); bool flag = player.InShelter(); ComfortCapture.TryGet(player, num, flag, Time.unscaledTime, out var captured); SEMan sEMan = ((Character)player).GetSEMan(); StatusEffect val = ((sEMan != null) ? sEMan.GetStatusEffect(SEMan.s_statusEffectRested) : null); int num2 = (((Object)(object)val != (Object)null && val.m_ttl > 0f) ? TimerFormatter.Bucket(val.GetRemaningTime(), AwarenessConfig.TimerPrecision.Value) : (-1)); ulong hash = HashStart(num, flag ? 1 : 0); hash = HashMix(hash, captured.Version); hash = HashMix(hash, num2); if (!SameSignature(ref _comfortSignature, ref _hadComfortSignature, hash)) { _builder.Clear(); _builder.Append("Level ").Append(num).Append(flag ? " (sheltered)" : " (not sheltered)"); _builder.Append("\nRested: "); if (num2 >= 0) { TimerFormatter.Append(_builder, num2); } else { _builder.Append("inactive"); } if (!string.IsNullOrEmpty(captured.Detail)) { _builder.Append('\n').Append(captured.Detail); } else { _builder.Append("\nCategory winners appear after vanilla's next comfort check."); } _panels.Set(AwarenessPanel.Comfort, BoundedText.Sanitize(_builder.ToString(), 1024, 12)); } } private void UpdateItemComparison(Player player, float now) { if (!AwarenessConfig.ShowItemComparison.Value || !HoverItemCapture.TryGet(player, now, out var item) || item?.m_shared == null) { _panels.Clear(AwarenessPanel.ItemComparison); _hadItemSignature = false; return; } string text = ((object)Unsafe.As<ItemType, ItemType>(ref item.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString(); ItemComparisonDisposition itemComparisonDisposition = ItemTypePolicy.Classify(text); if (itemComparisonDisposition == ItemComparisonDisposition.HideKnownNonEquipment) { _panels.Clear(AwarenessPanel.ItemComparison); _hadItemSignature = false; return; } try { ItemData val = ((itemComparisonDisposition == ItemComparisonDisposition.CompareKnownEquipment) ? ValheimContracts.EquippedFor(player, item) : null); ItemMetrics itemMetrics = ReadMetrics(player, item, text); ItemMetrics? itemMetrics2 = ((val?.m_shared == null) ? ((ItemMetrics?)null) : new ItemMetrics?(ReadMetrics(player, val, ((object)Unsafe.As<ItemType, ItemType>(ref val.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString()))); ulong current = ItemSignature(item, val, itemMetrics, itemMetrics2); if (!SameSignature(ref _itemSignature, ref _hadItemSignature, current)) { _panels.Set(AwarenessPanel.ItemComparison, ItemComparisonFormatter.Format(itemMetrics, itemMetrics2, item == val)); } } catch (Exception exception) { _panels.Clear(AwarenessPanel.ItemComparison); _hadItemSignature = false; FailHook("item comparison", exception); } } private void UpdateContext(Player player, float now) { Piece val = (AwarenessConfig.ShowBuilding.Value ? player.GetHoveringPiece() : null); if ((Object)(object)val != (Object)null) { ClearContextExcept(AwarenessPanel.Building); if (AllowsDetailedDisclosure(player, (Component)(object)val)) { UpdateBuilding(val, now); } else { UpdateBuildingUnavailable(); } return; } if (!ContextCapture.TryGet(((Humanoid)player).GetHoverObject(), now, out var context) || !ContextEnabled(context.Kind)) { ClearContextPanels(); if (_hadContextSignature) { _hadContextSignature = false; _contextSignature = 0uL; } return; } AwarenessPanel awarenessPanel = PanelFor(context.Kind); ClearContextExcept(awarenessPanel); ulong hash = HashStart((int)context.Kind, (!((Object)(object)context.Subject == (Object)null)) ? ((Object)context.Subject).GetInstanceID() : 0); hash = HashMix(hash, BoundedText.HashBounded(context.VisibleText, 768)); if (!SameSignature(ref _contextSignature, ref _hadContextSignature, hash)) { string text = BoundedText.Sanitize(context.VisibleText, 768, Math.Max(1, Math.Min(6, AwarenessConfig.MaximumContextLines.Value))); if (string.IsNullOrEmpty(text)) { _panels.Clear(awarenessPanel); } else { _panels.Set(awarenessPanel, text); } } } private void UpdateBuilding(Piece piece, float now) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) WearNTear componentInParent = ((Component)piece).GetComponentInParent<WearNTear>(); float num = (((Object)(object)componentInParent == (Object)null) ? (-1f) : (componentInParent.GetHealthPercentage() * 100f)); CraftingStation craftingStation = piece.m_craftingStation; if (piece != _lastBuildingPiece || now >= _nextStationLevelRefresh) { _lastBuildingPiece = piece; _cachedStationLevel = ((!((Object)(object)craftingStation == (Object)null)) ? Math.Max(0, craftingStation.GetLevel(false)) : 0); _nextStationLevelRefresh = now + 2f; } Vector3 position = ((Component)piece).transform.position; Vector3 eulerAngles = ((Component)piece).transform.eulerAngles; ulong hash = HashStart(((Object)piece).GetInstanceID(), Quantize(num)); hash = HashMix(hash, Quantize(position.x)); hash = HashMix(hash, Quantize(position.y)); hash = HashMix(hash, Quantize(position.z)); hash = HashMix(hash, Quantize(eulerAngles.x)); hash = HashMix(hash, Quantize(eulerAngles.y)); hash = HashMix(hash, Quantize(eulerAngles.z)); hash = HashMix(hash, _cachedStationLevel); if (!SameSignature(ref _contextSignature, ref _hadContextSignature, hash)) { _builder.Clear(); _builder.Append(BoundedLocalization.Label(piece.m_name)); if (num >= 0f) { _builder.Append(" | health ").Append(num.ToString("0", CultureInfo.InvariantCulture)).Append('%'); } _builder.Append("\nPosition: ").Append(VectorText(position)); _builder.Append("\nRotation: ").Append(VectorText(eulerAngles)); if (_cachedStationLevel > 0) { _builder.Append("\nStation level: ").Append(_cachedStationLevel); } _builder.Append("\nSupport colors: blue grounded; green/yellow/orange supported; red weakest."); _panels.Set(AwarenessPanel.Building, BoundedText.Sanitize(_builder.ToString(), 768, 6)); } } private void UpdateBuildingUnavailable() { ulong current = HashStart(6, 1431191894); if (!SameSignature(ref _contextSignature, ref _hadContextSignature, current)) { _lastBuildingPiece = null; _cachedStationLevel = 0; _nextStationLevelRefresh = 0f; _panels.Set(AwarenessPanel.Building, "Building details unavailable outside avatar interaction reach or without current ward access."); } } private unsafe static ItemMetrics ReadMetrics(Player player, ItemData item, string itemType) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) SkillType skillType = item.m_shared.m_skillType; float skillFactor = ((Character)player).GetSkillFactor(skillType); float skillLevel = ((Character)player).GetSkillLevel(skillType); string name = BoundedLocalization.Label(item.m_shared.m_name); int quality = item.m_quality; DamageTypes damage = item.GetDamage(); return new ItemMetrics(name, itemType, quality, ((DamageTypes)(ref damage)).GetTotalDamage(), item.GetArmor(), item.GetBlockPower(skillFactor), item.m_shared.m_movementModifier * 100f, item.GetNonStackedWeight(), item.m_durability, item.GetMaxDurability(), ((object)(*(SkillType*)(&skillType))/*cast due to .constrained prefix*/).ToString(), skillLevel); } private static ulong ItemSignature(ItemData selected, ItemData equipped, ItemMetrics selectedMetrics, ItemMetrics? equippedMetrics) { ulong signature = HashStart(RuntimeHelpers.GetHashCode(selected), (equipped != null) ? RuntimeHelpers.GetHashCode(equipped) : 0); signature = HashMetrics(signature, selectedMetrics); if (equippedMetrics.HasValue) { signature = HashMix(signature, 1); signature = HashMetrics(signature, equippedMetrics.Value); } return signature; } private static ulong HashMetrics(ulong signature, ItemMetrics metrics) { signature = HashMix(signature, BoundedText.HashBounded(metrics.Name, 96)); signature = HashMix(signature, BoundedText.HashBounded(metrics.ItemType, 96)); signature = HashMix(signature, metrics.Quality); signature = HashMix(signature, Quantize(metrics.Damage)); signature = HashMix(signature, Quantize(metrics.Armor)); signature = HashMix(signature, Quantize(metrics.Block)); signature = HashMix(signature, Quantize(metrics.MovementPercent)); signature = HashMix(signature, Quantize(metrics.Weight)); signature = HashMix(signature, Quantize(metrics.Durability)); signature = HashMix(signature, Quantize(metrics.MaximumDurability)); signature = HashMix(signature, BoundedText.HashBounded(metrics.SkillName, 96)); return HashMix(signature, Quantize(metrics.SkillLevel)); } private static string FoodName(Food food) { return food?.m_item?.m_shared?.m_name ?? food?.m_name ?? string.Empty; } private static bool AllowsDetailedDisclosure(Player player, Component subject) { //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_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)subject == (Object)null) { return false; } Transform transform = subject.transform; Transform transform2 = ((Component)player).transform; if ((Object)(object)transform == (Object)null || (Object)(object)transform2 == (Object)null) { return false; } Vector3 position = transform.position; Vector3 val = position - transform2.position; if (ContextDisclosurePolicy.AllowsDetailedDisclosure(((Vector3)(ref val)).sqrMagnitude, player.m_maxInteractDistance)) { return StrictWardDisclosure.Allows(position); } return false; } private bool ContextEnabled(AwarenessContextKind kind) { return kind switch { AwarenessContextKind.Production => AwarenessConfig.ShowProduction.Value, AwarenessContextKind.Agriculture => AwarenessConfig.ShowAgriculture.Value, AwarenessContextKind.Building => AwarenessConfig.ShowBuilding.Value, AwarenessContextKind.TamedAnimal => AwarenessConfig.ShowTamedAnimals.Value, _ => false, }; } private static AwarenessPanel PanelFor(AwarenessContextKind kind) { return kind switch { AwarenessContextKind.Production => AwarenessPanel.Production, AwarenessContextKind.Agriculture => AwarenessPanel.Agriculture, AwarenessContextKind.Building => AwarenessPanel.Building, AwarenessContextKind.TamedAnimal => AwarenessPanel.TamedAnimal, _ => throw new ArgumentOutOfRangeException("kind"), }; } private void ClearContextPanels() { _panels.Clear(AwarenessPanel.Production); _panels.Clear(AwarenessPanel.Agriculture); _panels.Clear(AwarenessPanel.Building); _panels.Clear(AwarenessPanel.TamedAnimal); } private void ClearContextExcept(AwarenessPanel retained) { if (retained != AwarenessPanel.Production) { _panels.Clear(AwarenessPanel.Production); } if (retained != AwarenessPanel.Agriculture) { _panels.Clear(AwarenessPanel.Agriculture); } if (retained != AwarenessPanel.Building) { _panels.Clear(AwarenessPanel.Building); } if (retained != AwarenessPanel.TamedAnimal) { _panels.Clear(AwarenessPanel.TamedAnimal); } } private void ClearAll() { for (int i = 0; i <= 7; i++) { _panels.Clear((AwarenessPanel)i); } _content.text = string.Empty; _layoutDirty = true; ResetSignatures(); _lastBuildingPiece = null; _cachedStationLevel = 0; _nextStationLevelRefresh = 0f; } private void ResetSignatures() { _foodSignature = (_effectSignature = (_comfortSignature = (_itemSignature = (_contextSignature = 0uL)))); _hadFoodSignature = (_hadEffectSignature = (_hadComfortSignature = (_hadItemSignature = (_hadContextSignature = false)))); } private bool ShouldSuppressOverlay() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || Menu.IsVisible() || Console.IsVisible() || TextInput.IsVisible() || StoreGui.IsVisible() || InventoryGui.IsVisible() || Minimap.IsOpen() || Hud.IsPieceSelectionVisible() || PlayerCustomizaton.IsBarberGuiVisible() || Feedback.IsVisible() || UnifiedPopup.IsVisible() || ConnectPanel.IsVisible() || ZInput.VirtualKeyboardOpen) { return true; } TextViewer instance = TextViewer.instance; if ((Object)(object)instance != (Object)null && instance.IsVisible()) { return true; } Chat instance2 = Chat.instance; if ((Object)(object)instance2 != (Object)null && (instance2.HasFocus() || instance2.IsChatDialogWindowVisible())) { return true; } if (Game.IsPaused() || (Object)(object)Hud.instance == (Object)null || Hud.instance.m_userHidden) { return true; } return _portalPanels.SetupPanelVisible(localPlayer); } private void EnsureStyles(int fontSize) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown //IL_005a: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) if (_textStyle == null || _boxStyle == null || _styleFontSize != fontSize) { _textStyle = new GUIStyle(GUI.skin.label) { richText = true, wordWrap = true, fontSize = fontSize, alignment = (TextAnchor)0, padding = new RectOffset(0, 0, 0, 0) }; _textStyle.normal.textColor = new Color(0.92f, 0.95f, 1f, 1f); _boxStyle = new GUIStyle(GUI.skin.box); _boxStyle.normal.textColor = Color.clear; _styleFontSize = fontSize; _lastInnerWidth = 0f; _lastMaximumHeight = 0f; _layoutDirty = true; } } internal static Rect AnchoredRect(Rect safe, float screenHeight, float width, float height, float margin, AwarenessOverlayAnchor anchor) { //IL_0079: Unknown result type (might be due to invalid IL or missing references) bool flag = anchor == AwarenessOverlayAnchor.MiddleLeft; bool num = anchor == AwarenessOverlayAnchor.TopRight || anchor == AwarenessOverlayAnchor.BottomRight; bool flag2 = anchor == AwarenessOverlayAnchor.BottomRight || anchor == AwarenessOverlayAnchor.BottomLeft; float num2 = (num ? (((Rect)(ref safe)).xMax - width - margin) : (((Rect)(ref safe)).xMin + margin)); float num3 = screenHeight - ((Rect)(ref safe)).yMax; float num4 = screenHeight - ((Rect)(ref safe)).yMin; float num5 = (flag ? (num3 + (((Rect)(ref safe)).height - height) * 0.5f) : (flag2 ? (num4 - height - margin) : (num3 + margin))); return new Rect(num2, num5, width, height); } private static bool SameSignature(ref ulong stored, ref bool hasStored, ulong current) { if (hasStored && stored == current) { return true; } stored = current; hasStored = true; return false; } private static ulong HashStart(int left, int right) { return HashMix(HashMix(1469598103934665603uL, left), right); } private static ulong HashMix(ulong hash, int value) { hash ^= (uint)value; return hash * 1099511628211L; } private static ulong HashMix(ulong hash, ulong value) { hash ^= value; return hash * 1099511628211L; } private static int Quantize(float value) { if (float.IsNaN(value) || float.IsInfinity(value)) { return 0; } double num = Math.Round((double)value * 10.0, MidpointRounding.AwayFromZero); if (!(num > 2147483647.0)) { if (!(num < -2147483648.0)) { return (int)num; } return int.MinValue; } return int.MaxValue; } private static float FiniteClamp(float value, float minimum, float maximum, float fallback) { if (float.IsNaN(value) || float.IsInfinity(value)) { value = fallback; } return Math.Max(minimum, Math.Min(maximum, value)); } private static string VectorText(Vector3 value) { return value.x.ToString("0.0", CultureInfo.InvariantCulture) + ", " + value.y.ToString("0.0", CultureInfo.InvariantCulture) + ", " + value.z.ToString("0.0", CultureInfo.InvariantCulture); } } internal static class BoundedLocalization { internal const int MaximumTokenCharacters = 128; private static readonly FieldInfo Translations = ResolveTranslations(); internal static bool IsSupported => Translations != null; internal static string Label(string raw) { if (string.IsNullOrEmpty(raw)) { return string.Empty; } if (!TryGetTranslationKey(raw, out var key)) { return BoundedText.Label(raw); } try { Localization instance = Localization.instance; string value = key; if (instance != null && Translations != null && Translations.GetValue(instance) is Dictionary<string, string> dictionary && dictionary.TryGetValue(key, out var value2) && value2 != null) { value = value2; } return BoundedText.Label(value); } catch { return BoundedText.Label(key); } } internal static bool TryGetTranslationKey(string raw, out string key) { key = null; if (string.IsNullOrEmpty(raw) || raw.Length < 2 || raw.Length > 128 || raw[0] != '$') { return false; } int i; for (i = 1; i < raw.Length && raw[i] == '$'; i++) { } if (i == raw.Length) { return false; } for (int j = i; j < raw.Length; j++) { char c = raw[j]; if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != '_' && c != '-' && c != '.') { return false; } } key = raw.Substring(i); if (key.StartsWith("KEY_", StringComparison.Ordinal)) { key = null; return false; } return true; } private static FieldInfo ResolveTranslations() { try { FieldInfo field = typeof(Localization).GetField("m_translations", BindingFlags.Instance | BindingFlags.NonPublic); return (field != null && field.FieldType == typeof(Dictionary<string, string>) && !field.IsStatic) ? field : null; } catch { return null; } } } internal readonly struct CapturedComfort { internal int Level { get; } internal bool Sheltered { get; } internal string Detail { get; } internal ulong Signature { get; } internal int Version { get; } internal int SourceInstanceId { get; } internal float CapturedAt { get; } internal CapturedComfort(int level, bool sheltered, string detail, ulong signature, int version) : this(level, sheltered, detail, signature, version, 0, 0f) { } internal CapturedComfort(int level, bool sheltered, string detail, ulong signature, int version, int sourceInstanceId, float capturedAt) { Level = level; Sheltered = sheltered; Detail = detail ?? string.Empty; Signature = signature; Version = version; SourceInstanceId = sourceInstanceId; CapturedAt = capturedAt; } } internal static class ComfortCapture { internal const float MaximumAgeSeconds = 5f; private static readonly StringBuilder Builder = new StringBuilder(512); private static CapturedComfort _current; internal static CapturedComfort Current => _current; internal static bool TryGet(Player player, int level, bool sheltered, float now, out CapturedComfort captured) { captured = _current; if ((Object)(object)player != (Object)null && captured.Version > 0 && captured.SourceInstanceId == ((Object)player).GetInstanceID() && captured.Level == level && captured.Sheltered == sheltered && now >= captured.CapturedAt && now - captured.CapturedAt <= 5f) { return true; } captured = default(CapturedComfort); return false; } internal static void Record(Player player, int level) { if (Plugin.Instance?.Runtime == null || !AwarenessConfig.Enabled.Value || !AwarenessConfig.ShowComfort.Value || (Object)(object)player == (Object)null || player != Player.m_localPlayer) { return; } bool flag = player.InShelter(); FieldInfo comfortPiecesField = ValheimContracts.ComfortPiecesField; List<Piece> list = (flag ? (comfortPiecesField.GetValue(null) as List<Piece>) : null); int num = list?.Count ?? 0; int instanceID = ((Object)player).GetInstanceID(); ulong hash = Hash(level, flag, list, num); hash = Mix(hash, instanceID); if (_current.Version > 0 && _current.Signature == hash) { _current = new CapturedComfort(_current.Level, _current.Sheltered, _current.Detail, _current.Signature, _current.Version, _current.SourceInstanceId, Time.unscaledTime); return; } Builder.Clear(); if (num > 64) { Builder.Append("Nearby category details withheld: vanilla returned ").Append(num).Append(" comfort pieces (hard limit ") .Append(64) .Append(")."); } else { AppendWinners(Builder, list, num, flag); if (!flag) { if (Builder.Length > 0) { Builder.Append('\n'); } Builder.Append("Known opportunity: shelter is not currently counted."); } } _current = new CapturedComfort(Math.Max(0, level), flag, BoundedText.Sanitize(Builder.ToString(), 768, 10), hash, _current.Version + 1, instanceID, Time.unscaledTime); } internal static void Reset() { _current = default(CapturedComfort); Builder.Clear(); } private static void AppendWinners(StringBuilder builder, List<Piece> pieces, int count, bool sheltered) { //IL_0086: Unknown result type (might be due to invalid IL or missing references) int num = Math.Max(1, Math.Min(8, AwarenessConfig.MaximumComfortRows.Value)); int num2 = 0; int num3 = 0; Piece val = null; builder.Append("Counted: base +1"); if (sheltered) { builder.Append(", shelter +1"); } for (int i = 0; i < count; i++) { Piece val2 = pieces[i]; if ((Object)(object)val2 == (Object)null) { continue; } int num4; if ((Object)(object)val != (Object)null) { if (((object)Unsafe.As<ComfortGroup, ComfortGroup>(ref val2.m_comfortGroup)/*cast due to .constrained prefix*/).ToString() != "None") { ref ComfortGroup comfortGroup = ref val2.m_comfortGroup; object obj = val.m_comfortGroup; if (((object)Unsafe.As<ComfortGroup, ComfortGroup>(ref comfortGroup)/*cast due to .constrained prefix*/).Equals(obj)) { num4 = 1; goto IL_00b6; } } num4 = (string.Equals(val2.m_name, val.m_name, StringComparison.Ordinal) ? 1 : 0); } else { num4 = 0; } goto IL_00b6; IL_00b6: int comfort = val2.GetComfort(); if (num4 != 0 || comfort <= 0) { num3++; } else { num2++; if (num2 <= num) { builder.Append(", ").Append(BoundedLocalization.Label(val2.m_name)).Append(" +") .Append(comfort); } } val = val2; } if (num2 > num) { builder.Append(" (+").Append(num2 - num).Append(" more winners)"); } if (num3 > 0) { builder.Append("\nNearby alternatives not counted: ").Append(num3).Append(" (same category/name or zero comfort)."); } } private static ulong Hash(int level, bool sheltered, List<Piece> pieces, int count) { ulong hash = 1469598103934665603uL; hash = Mix(hash, level); hash = Mix(hash, sheltered ? 1 : 0); hash = Mix(hash, count); if (pieces == null || count > 64) { return hash; } for (int i = 0; i < count; i++) { Piece val = pieces[i]; if ((Object)(object)val == (Object)null) { hash = Mix(hash, -1); continue; } hash = Mix(hash, ((object)Unsafe.As<ComfortGroup, ComfortGroup>(ref val.m_comfortGroup)/*cast due to .constrained prefix*/).GetHashCode()); hash = Mix(hash, val.GetComfort()); hash ^= BoundedText.HashBounded(val.m_name, 96); hash *= 1099511628211L; } return hash; } private static ulong Mix(ulong hash, int value) { hash ^= (uint)value; return hash * 1099511628211L; } } internal enum AwarenessContextKind { None, Production, Agriculture, Building, TamedAnimal } internal readonly struct CapturedContext { internal AwarenessContextKind Kind { get; } internal Component Source { get; } internal Component Subject { get; } internal string VisibleText { get; } internal float CapturedAt { get; } internal CapturedContext(AwarenessContextKind kind, Component source, Component subject, string visibleText, float capturedAt) { Kind = kind; Source = source; Subject = subject; VisibleText = visibleText ?? string.Empty; CapturedAt = capturedAt; } } internal static class ContextCapture { internal const float MaximumAgeSeconds = 0.8f; internal const int MaximumVisibleTextCharacters = 8192; private static CapturedContext _current; internal static void Record(AwarenessContextKind kind, Component source, Component subject, string visibleText) { if (Plugin.Instance?.Runtime != null && AwarenessConfig.Enabled.Value && IsEnabled(kind) && !((Object)(object)source == (Object)null) && !((Object)(object)subject == (Object)null) && kind != AwarenessContextKind.None) { if (visibleText != null && visibleText.Length > 8192) { _current = default(CapturedContext); } else { _current = new CapturedContext(kind, source, subject, visibleText, Time.unscaledTime); } } } internal static bool TryGet(GameObject hoverObject, float now, out CapturedContext context) { context = _current; if ((Object)(object)hoverObject == (Object)null || (Object)(object)context.Source == (Object)null || (Object)(object)context.Subject == (Object)null || now < context.CapturedAt || now - context.CapturedAt > 0.8f) { _current = default(CapturedContext); return false; } Transform transform = hoverObject.transform; if (!SameHierarchy(transform, context.Source.transform)) { return SameHierarchy(transform, context.Subject.transform); } return true; } internal static void Reset() { _current = default(CapturedContext); } private static bool IsEnabled(AwarenessContextKind kind) { return kind switch { AwarenessContextKind.Production => AwarenessConfig.ShowProduction.Value, AwarenessContextKind.Agriculture => AwarenessConfig.ShowAgriculture.Value, AwarenessContextKind.Building => AwarenessConfig.ShowBuilding.Value, AwarenessContextKind.TamedAnimal => AwarenessConfig.ShowTamedAnimals.Value, _ => false, }; } private static bool SameHierarchy(Transform left, Transform right) { if ((Object)(object)left == (Object)null || (Object)(object)right == (Object)null) { return false; } Transform val = left; int num = 0; while ((Object)(object)val != (Object)null && num < 16) { if ((Object)(object)val == (Object)(object)right) { return true; } num++; val = val.parent; } val = right; int num2 = 0; while ((Object)(object)val != (Object)null && num2 < 16) { if ((Object)(object)val == (Object)(object)left) { return true; } num2++; val = val.parent; } return false; } } internal static class HoverItemCapture { internal const float MaximumAgeSeconds = 0.8f; private static ItemData _item; private static float _capturedAt; private static int _sourcePlayerInstanceId; internal static void Record(ItemData item) { Player localPlayer = Player.m_localPlayer; if (Plugin.Instance?.Runtime != null && AwarenessConfig.Enabled.Value && AwarenessConfig.ShowItemComparison.Value && item != null && !((Object)(object)localPlayer == (Object)null)) { _item = item; _capturedAt = Time.unscaledTime; _sourcePlayerInstanceId = ((Object)localPlayer).GetInstanceID(); } } internal static bool TryGet(Player player, float now, out ItemData item) { item = _item; if ((Object)(object)player != (Object)null && item != null && _sourcePlayerInstanceId == ((Object)player).GetInstanceID() && now >= _capturedAt && now - _capturedAt <= 0.8f) { return true; } Reset(); item = null; return false; } internal static void Reset() { _item = null; _capturedAt = 0f; _sourcePlayerInstanceId = 0; } } [HarmonyPatch(typeof(InventoryGrid), "CreateItemTooltip", new Type[] { typeof(ItemData), typeof(UITooltip) })] internal static class InventoryGridCreateItemTooltipPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(ItemData __0) { try { HoverItemCapture.Record(__0); } catch (Exception exception) { Plugin.Instance?.Runtime?.FailHook("item tooltip", exception); } } } [HarmonyPatch(typeof(SE_Rested), "CalculateComfortLevel", new Type[] { typeof(Player) })] internal static class RestedCalculateComfortPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(Player __0, int __result) { try { ComfortCapture.Record(__0, __result); } catch (Exception exception) { Plugin.Instance?.Runtime?.FailHook("comfort capture", exception); } } } [HarmonyPatch(typeof(CraftingStation), "GetHoverText", new Type[] { })] internal static class CraftingStationHoverPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(CraftingStation __instance, string __result) { SafeContext.Record(AwarenessContextKind.Building, (Component)(object)__instance, (Component)(object)__instance, __result, "crafting station hover"); } } [HarmonyPatch(typeof(CookingStation), "GetHoverText", new Type[] { })] internal static class CookingStationHoverPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(CookingStation __instance, string __result) { SafeContext.Record(AwarenessContextKind.Production, (Component)(object)__instance, (Component)(object)__instance, __result, "cooking station hover"); } } [HarmonyPatch(typeof(Fermenter), "GetHoverText", new Type[] { })] internal static class FermenterHoverPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(Fermenter __instance, string __result) { SafeContext.Record(AwarenessContextKind.Production, (Component)(object)__instance, (Component)(object)__instance, __result, "fermenter hover"); } } [HarmonyPatch(typeof(Plant), "GetHoverText", new Type[] { })] internal static class PlantHoverPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(Plant __instance, string __result) { SafeContext.Record(AwarenessContextKind.Agriculture, (Component)(object)__instance, (Component)(object)__instance, __result, "plant hover"); } } [HarmonyPatch(typeof(Beehive), "GetHoverText", new Type[] { })] internal static class BeehiveHoverPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(Beehive __instance, string __result) { SafeContext.Record(AwarenessContextKind.Agriculture, (Component)(object)__instance, (Component)(object)__instance, __result, "beehive hover"); } } [HarmonyPatch(typeof(Tameable), "GetHoverText", new Type[] { })] internal static class TameableHoverPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(Tameable __instance, string __result) { SafeContext.Record(AwarenessContextKind.TamedAnimal, (Component)(object)__instance, (Component)(object)__instance, __result, "tameable hover"); } } [HarmonyPatch(typeof(Switch), "GetHoverText", new Type[] { })] internal static class ProductionSwitchHoverPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(Switch __instance, string __result) { try { if ((Object)(object)__instance == (Object)null) { return; } Smelter componentInParent = ((Component)__instance).GetComponentInParent<Smelter>(); if ((Object)(object)componentInParent != (Object)null) { ContextCapture.Record(AwarenessContextKind.Production, (Component)(object)__instance, (Component)(object)componentInParent, __result); return; } CookingStation componentInParent2 = ((Component)__instance).GetComponentInParent<CookingStation>(); if ((Object)(object)componentInParent2 != (Object)null) { ContextCapture.Record(AwarenessContextKind.Production, (Component)(object)__instance, (Component)(object)componentInParent2, __result); return; } Fermenter componentInParent3 = ((Component)__instance).GetComponentInParent<Fermenter>(); if ((Object)(object)componentInParent3 != (Object)null) { ContextCapture.Record(AwarenessContextKind.Production, (Component)(object)__instance, (Component)(object)componentInParent3, __result); } } catch (Exception exception) { Plugin.Instance?.Runtime?.FailHook("production switch hover", exception); } } } internal static class SafeContext { internal static void Record(AwarenessContextKind kind, Component source, Component subject, string text, string hook) { try { ContextCapture.Record(kind, source, subject, text); } catch (Exception exception) { Plugin.Instance?.Runtime?.FailHook(hook, exception); } } } internal sealed class OptionalPortalPanelAdapter { internal const string PortalPluginGuid = "chazman.RunicPortals"; private BaseUnityPlugin _plugin; private ConfigEntry<bool> _enabled; private ConfigEntry<bool> _universalRouting; private ConfigEntry<bool> _showSetupPanel; private int _lastPortalHoverFrame = -10; internal bool SetupPanelVisible(Player player) { if ((Object)(object)player == (Object)null) { _lastPortalHoverFrame = -10; return false; } if (!TryBind()) { return false; } if (!_enabled.Value || !_universalRouting.Value || !_showSetupPanel.Value) { _lastPortalHoverFrame = -10; return false; } GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if ((Object)(object)hoverObject != (Object)null && (Object)(object)hoverObject.GetComponentInParent<TeleportWorld>() != (Object)null) { _lastPortalHoverFrame = Time.frameCount; } int num = Time.frameCount - _lastPortalHoverFrame; if (num >= 0) { return num <= 1; } return false; } private bool TryBind() { try { if (!Chainloader.PluginInfos.TryGetValue("chazman.RunicPortals", out var value) || (Object)(object)((value != null) ? value.Instance : null) == (Object)null) { Clear(); return false; } BaseUnityPlugin instance = value.Instance; if (_plugin == instance && _enabled != null && _universalRouting != null && _showSetupPanel != null) { return true; } ConfigFile config = instance.Config; ConfigEntry<bool> enabled = default(ConfigEntry<bool>); ConfigEntry<bool> universalRouting = default(ConfigEntry<bool>); ConfigEntry<bool> showSetupPanel = default(ConfigEntry<bool>); if (config == null || !config.TryGetEntry<bool>("General", "Enabled", ref enabled) || !config.TryGetEntry<bool>("Features", "UniversalRouting", ref universalRouting) || !config.TryGetEntry<bool>("Display", "ShowSetupPanel", ref showSetupPanel)) { Clear(); return false; } _plugin = instance; _enabled = enabled; _universalRouting = universalRouting; _showSetupPanel = showSetupPanel; return true; } catch (Exception) { Clear(); return false; } } private void Clear() { _plugin = null; _enabled = null; _universalRouting = null; _showSetupPanel = null; _lastPortalHoverFrame = -10; } } internal static class StrictWardDisclosure { private delegate bool WardStateDelegate(PrivateArea area); private delegate bool WardContainsDelegate(PrivateArea area, Vector3 point, float radius); internal const int MaximumWardAreasExamined = 4096; private static readonly List<PrivateArea> WardAreas = ResolveWardAreas(); private static readonly WardStateDelegate WardEnabled = ResolveWardState("IsEnabled"); private static readonly WardStateDelegate WardLocalAccess = ResolveWardState("HaveLocalAccess"); private static readonly WardContainsDelegate WardContains = ResolveWardContains(); internal static bool IsSupported { get { if (WardAreas != null && WardEnabled != null && WardLocalAccess != null) { return WardContains != null; } return false; } } internal static bool Allows(Vector3 position) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) if (!IsSupported || !IsFinite(position)) { return false; } try { int count = WardAreas.Count; if (count > 4096) { return false; } for (int i = 0; i < count; i++) { PrivateArea val = WardAreas[i]; if (!((Object)(object)val == (Object)null)) { bool num = WardEnabled(val); bool flag = num && WardContains(val, position, 0f); bool localAccess = !flag || WardLocalAccess(val); if (IsHostileOverlap(num, flag, localAccess)) { return false; } } } return true; } catch { return false; } } internal static bool IsHostileOverlap(bool enabled, bool containsTarget, bool localAccess) { if (enabled && containsTarget) { return !localAccess; } return false; } private static bool IsFinite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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) if (!float.IsNaN(value.x) && !float.IsInfinity(value.x) && !float.IsNaN(value.y) && !float.IsInfinity(value.y) && !float.IsNaN(value.z)) { return !float.IsInfinity(value.z); } return false; } private static List<PrivateArea> ResolveWardAreas() { try { FieldInfo field = typeof(PrivateArea).GetField("m_allAreas", BindingFlags.Static | BindingFlags.NonPublic); return (field != null && field.FieldType == typeof(List<PrivateArea>)) ? (field.GetValue(null) as List<PrivateArea>) : null; } catch { return null; } } private static WardStateDelegate ResolveWardState(string name) { try { MethodInfo method = typeof(PrivateArea).GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); return (method != null && method.ReturnType == typeof(bool) && !method.IsStatic) ? ((WardStateDelegate)method.CreateDelegate(typeof(WardStateDelegate))) : null; } catch { return null; } } private static WardContainsDelegate ResolveWardContains() { try { MethodInfo method = typeof(PrivateArea).GetMethod("IsInside", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(Vector3), typeof(float) }, null); return (method != null && method.ReturnType == typeof(bool) && !method.IsStatic) ? ((WardContainsDelegate)method.CreateDelegate(typeof(WardContainsDelegate))) : null; } catch { return null; } } } internal static class ValheimContracts { private const BindingFlags InstanceFields = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static FieldInfo _rightItem; private static FieldInfo _leftItem; private static FieldInfo _helmetItem; private static FieldInfo _chestItem; private static FieldInfo _legItem; private static FieldInfo _shoulderItem; private static FieldInfo _utilityItem; private static FieldInfo _trinketItem; private static FieldInfo _ammoItem; private static FieldInfo _comfortPieces; private static bool _verified; internal static FieldInfo ComfortPiecesField => _comfortPieces ?? throw new InvalidOperationException("Valheim contracts are not initialized."); internal static void VerifyInstalledSignatures() { if (!_verified) { RequireMethod(typeof(Player), "GetFoods", typeof(List<Food>), false); RequireMethod(typeof(Player), "GetComfortLevel", typeof(int), false); RequireMethod(typeof(Player), "GetHoverObject", typeof(GameObject), false); RequireMethod(typeof(Player), "GetHoveringPiece", typeof(Piece), false); RequireMethod(typeof(Player), "GetSEMan", typeof(SEMan), false); RequireMethod(typeof(Player), "InShelter", typeof(bool), false); RequireMethod(typeof(Minimap), "IsOpen", typeof(bool), true); RequireMethod(typeof(Hud), "IsPieceSelectionVisible", typeof(bool), true); RequireMethod(typeof(Chat), "HasFocus", typeof(bool), false); RequireMethod(typeof(Chat), "IsChatDialogWindowVisible", typeof(bool), false); RequireMethod(typeof(PlayerCustomizaton), "IsBarberGuiVisible", typeof(bool), true); RequireMethod(typeof(Feedback), "IsVisible", typeof(bool), true); RequireMethod(typeof(UnifiedPopup), "IsVisible", typeof(bool), true); RequireMethod(typeof(ConnectPanel), "IsVisible", typeof(bool), true); RequireMethod(typeof(TextViewer), "IsVisible", typeof(bool), false); RequireMethod(typeof(SEMan), "GetStatusEffects", typeof(List<StatusEffect>), false); RequireMethod(typeof(SEMan), "GetStatusEffect", typeof(StatusEffect), false, typeof(int)); RequireMethod(typeof(StatusEffect), "GetRemaningTime", typeof(float), false); RequireField(typeof(Localization), "m_translations", typeof(Dictionary<string, string>)); if (!BoundedLocalization.IsSupported) { throw new MissingFieldException(typeof(Localization).FullName, "m_translations"); } RequireMethod(typeof(InventoryGrid), "CreateItemTooltip", typeof(void), false, typeof(ItemData), typeof(UITooltip)); RequireMethod(typeof(SE_Rested), "CalculateComfortLevel", typeof(int), true, typeof(Player)); RequireMethod(typeof(CraftingStation), "GetHoverText", typeof(string), false); RequireMethod(typeof(CookingStation), "GetHoverText", typeof(string), false); RequireMethod(typeof(Fermenter), "GetHoverText", typeof(string), false); RequireMethod(typeof(Plant), "GetHoverText", typeof(string), false); RequireMethod(typeof(Beehive), "GetHoverText", typeof(string), false); RequireMethod(typeof(Tameable), "GetHoverText", typeof(string), false); RequireMethod(typeof(Switch), "GetHoverText", typeof(string), false); RequireMethod(typeof(WearNTear), "GetHealthPercentage", typeof(float), false); RequireMethod(typeof(CraftingStation), "GetLevel", typeof(int), false, typeof(bool)); RequireField(typeof(PrivateArea), "m_allAreas", typeof(List<PrivateArea>), BindingFlags.Static | BindingFlags.NonPublic); RequireMethod(typeof(PrivateArea), "IsEnabled", typeof(bool), false); RequireMethod(typeof(PrivateArea), "HaveLocalAccess", typeof(bool), false); RequireMethod(typeof(PrivateArea), "IsInside", typeof(bool), false, typeof(Vector3), typeof(float)); if (!StrictWardDisclosure.IsSupported) { throw new MissingMethodException(typeof(PrivateArea).FullName, "strict disclosure delegates"); } _rightItem = RequireField(typeof(Humanoid), "m_rightItem", typeof(ItemData)); _leftItem = RequireField(typeof(Humanoid), "m_leftItem", typeof(ItemData)); _helmetItem = RequireField(typeof(Humanoid), "m_helmetItem", typeof(ItemData)); _chestItem = RequireField(typeof(Humanoid), "m_chestItem", typeof(ItemData)); _legItem = RequireField(typeof(Humanoid), "m_legItem", typeof(ItemData)); _shoulderItem = RequireField(typeof(Humanoid), "m_shoulderItem", typeof(ItemData)); _utilityItem = RequireField(typeof(Humanoid), "m_utilityItem", typeof(ItemData)); _trinketItem = RequireField(typeof(Humanoid), "m_trinketItem", typeof(ItemData)); _ammoItem = RequireField(typeof(Humanoid), "m_ammoItem", typeof(ItemData)); _comfortPieces = RequireField(typeof(SE_Rested), "s_tempPieces", typeof(List<Piece>), BindingFlags.Static | BindingFlags.NonPublic); _verified = true; } } internal static ItemData EquippedFor(Player player, ItemData selected) { if (!_verified || (Object)(object)player == (Object)null || selected?.m_shared == null) { return null; } string text = ((object)Unsafe.As<ItemType, ItemType>(ref selected.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString(); switch (text) { case "Helmet": return ReadItem(_helmetItem, (Humanoid)(object)player); case "Chest": return ReadItem(_chestItem, (Humanoid)(object)player); case "Legs": return ReadItem(_legItem, (Humanoid)(object)player); case "Shoulder": return ReadItem(_shoulderItem, (Humanoid)(object)player); case "Utility": return ReadItem(_utilityItem, (Humanoid)(object)player); case "Trinket": return ReadItem(_trinketItem, (Humanoid)(object)player); case "Ammo": return ReadItem(_ammoItem, (Humanoid)(object)player); case "Shield": return ReadItem(_leftItem, (Humanoid)(object)player); case "TwoHandedWeaponLeft": return ReadItem(_leftItem, (Humanoid)(object)player); default: { ItemData val = ReadItem(_rightItem, (Humanoid)(object)player); if (val != null && val.m_shared != null && string.Equals(((object)Unsafe.As<ItemType, ItemType>(ref val.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString(), text, StringComparison.Ordinal)) { return val; } ItemData val2 = ReadItem(_leftItem, (Humanoid)(object)player); if (val2 == null || val2.m_shared == null || !string.Equals(((object)Unsafe.As<ItemType, ItemType>(ref val2.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString(), text, StringComparison.Ordinal)) { return null; } return val2; } } } private static ItemData ReadItem(FieldInfo field, Humanoid player) { object? obj = field?.GetValue(player); return (ItemData)((obj is ItemData) ? obj : null); } private static MethodInfo RequireMethod(Type type, string name, Type returnType, bool isStatic, params Type[] parameters) { MethodInfo method = type.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, parameters ?? Type.EmptyTypes, null); if (method == null || method.ReturnType != returnType || method.IsStatic != isStatic) { throw new MissingMethodException(type.FullName, name); } return method; } private static FieldInfo RequireField(Type type, string name, Type fieldType) { return RequireField(type, name, fieldType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } private static FieldInfo RequireField(Type type, string name, Type fieldType, BindingFlags flags) { FieldInfo field = type.GetField(name, flags); if (field == null || field.FieldType != fieldType) { throw new MissingFieldException(type.FullName, name); } return field; } } } namespace RunicAwareness.Core { internal static class BoundedText { internal const int MaximumLabelCharacters = 96; internal static string Sanitize(string value, int maximumCharacters, int maximumLines) { maximumCharacters = Math.Max(1, maximumCharacters); maximumLines = Math.Max(1, maximumLines); if (string.IsNullOrEmpty(value)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(Math.Min(maximumCharacters, value.Length)); int num = 1; bool flag = false; int num2 = Math.Min(value.Length, Math.Max(256, maximumCharacters * 4)); for (int i = 0; i < num2 && stringBuilder.Length < maximumCharacters; i++) { char c = value[i]; switch (c) { case '<': { int num3 = FindTagEnd(value, i, 64); if (num3 >= 0) { i = num3; continue; } c = '['; goto default; } case '>': c = ']'; goto default; default: switch (c) { case '\n': break; default: { UnicodeCategory unicodeCategory = char.GetUnicodeCategory(c); if (unicodeCategory == UnicodeCategory.Control || unicodeCategory == UnicodeCategory.Format || IsBidiControl(c)) { continue; } if (c == '\t' || char.IsWhiteSpace(c)) { if (!flag && stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != '\n') { stringBuilder.Append(' '); flag = true; } } else { stringBuilder.Append(c); flag = false; } continue; } case '\r': continue; } if (num < maximumLines) { TrimTrailingSpace(stringBuilder); if (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != '\n') { stringBuilder.Append('\n'); num++; } flag = false; continue; } break; } break; } TrimTrailingSpace(stringBuilder); while (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] == '\n') { stringBuilder.Length--; } if ((stringBuilder.Length == maximumCharacters && value.Length > maximumCharacters) || num2 < value.Length) { AddEllipsis(stringBuilder, maximumCharacters); } return stringBuilder.ToString(); } internal static string Label(string value) { return Sanitize(value, 96, 1); } internal static ulong HashBounded(string value, int maximumCharacters) { ulong num = 1469598103934665603uL; if (value == null) { return num; } int num2 = Math.Min(Math.Max(0, maximumCharacters), value.Length); for (int i = 0; i < num2; i++) { num ^= value[i]; num *= 1099511628211L; } num ^= (ulong)Math.Min(value.Length, maximumCharacters + 1); return num * 1099511628211L; } internal static int CountLines(string value) { if (string.IsNullOrEmpty(value)) { return 0; } int num = 1; for (int i = 0; i < value.Length; i++) { if (value[i] == '\n') { num++; } } return num; } private static int FindTagEnd(string value, int start, int maximumDistance) { int num = Math.Min(value.Length, start + maximumDistance + 1); for (int i = start + 1; i < num; i++) { if (value[i] == '>') { return i; } } return -1; } private static bool IsBidiControl(char value) { if ((value < '\u202a' || value > '\u202e') && (value < '\u2066' || value > '\u2069') && (value < '\u200b' || value > '\u200f') && value != '\u061c') { return value == '\ufeff'; } return true; } private static void TrimTrailingSpace(StringBuilder builder) { while (builder.Length > 0 && builder[builder.Length - 1] == ' ') { builder.Length--; } } private static void AddEllipsis(StringBuilder builder, int maximumCharacters) { if (maximumCharacters == 1) { builder[0] = '…'; return; } builder.Length = Math.Min(builder.Length, maximumCharacters - 1); builder.Append('…'); } } internal static class ContextDisclosurePolicy { internal const float HardMaximumDetailReachMeters = 10f; internal static bool AllowsDetailedDisclosure(float squaredAvatarDistance, float vanillaInteractDistance) { if (float.IsNaN(squaredAvatarDistance) || float.IsInfinity(squaredAvatarDistance) || float.IsNaN(vanillaInteractDistance) || float.IsInfinity(vanillaInteractDistance) || squaredAvatarDistance < 0f || vanillaInteractDistance <= 0f) { return false; } float num = Math.Min(vanillaInteractDistance, 10f); return squaredAvatarDistance <= num * num; } } internal readonly struct ItemMetrics { internal string Name { get; } internal string ItemType { get; } internal int Quality { get; } internal float Damage { get; } internal float Armor { get; } internal float Block { get; } internal float MovementPercent { get; } internal float Weight { get; } internal float Durability { get; } internal float MaximumDurability { get; } internal string SkillName { get; } internal float SkillLevel { get; } internal ItemMetrics(string name, string itemType, int quality, float damage, float armor, float block, float movementPercent, float weight, float durability, float maximumDurability, string skillName, float skillLevel) { Name = name ?? string.Empty; ItemType = itemType ?? string.Empty; Quality = Math.Max(0, quality); Damage = FiniteOrZero(damage); Armor = FiniteOrZero(armor); Block = FiniteOrZero(block); MovementPercent = FiniteOrZero(movementPercent); Weight = FiniteOrZero(weight); Durability = FiniteOrZero(durability); MaximumDurability = FiniteOrZero(maximumDurability); SkillName = skillName ?? string.Empty; SkillLevel = FiniteOrZero(skillLevel); } private static float FiniteOrZero(float value) { if (!float.IsNaN(value) && !float.IsInfinity(value)) { return value; } return 0f; } } internal static class ItemComparisonFormatter { internal const int MaximumLines = 8; internal static string Format(ItemMetrics selected, ItemMetrics? equipped, bool isEquipped) { StringBuilder stringBuilder = new StringBuilder(384); stringBuilder.Append(BoundedText.Label(selected.Name)); if (isEquipped) { stringBuilder.Append(" (equipped)"); } else if (equipped.HasValue) { stringBuilder.Append(" vs ").Append(BoundedText.Label(equipped.Value.Name)); } else { stringBuilder.Append(" vs empty slot"); } int lines = 1; AppendText(stringBuilder, ref lines, "Type", BoundedText.Label(selected.ItemType)); AppendInteger(stringBuilder, ref lines, "Quality", selected.Quality, equipped?.Quality); AppendNumber(stringBuilder, ref lines, "Damage (item)", selected.Damage, equipped?.Damage, selected.Damage > 0f || (equipped?.Damage ?? 0f) > 0f); AppendNumber(stringBuilder, ref lines, "Armor", selected.Armor, equipped?.Armor, selected.Armor > 0f || (equipped?.Armor ?? 0f) > 0f); AppendNumber(stringBuilder, ref lines, "Block (skill)", selected.Block, equipped?.Block, selected.Block > 0f || (equipped?.Block ?? 0f) > 0f); AppendNumber(stringBuilder, ref lines, "Move", selected.MovementPercent, equipped?.MovementPercent, include: true, "%"); if (lines < 8 && !string.IsNullOrEmpty(selected.SkillName)) { stringBuilder.Append("\nSkill: ").Append(BoundedText.Label(selected.SkillName)).Append(' ') .Append(FormatNumber(selected.SkillLevel)); lines++; } AppendNumber(stringBuilder, ref lines, "Weight", selected.Weight, equipped?.Weight, include: true); if (lines < 8 && selected.MaximumDurability > 0f) { stringBuilder.Append("\nDurability: ").Append(FormatNumber(selected.Durability)).Append('/') .Append(FormatNumber(selected.MaximumDurability)); lines++; } return BoundedText.Sanitize(stringBuilder.ToString(), 640, 8); } private static void AppendText(StringBuilder builder, ref int lines, string label, string value) { if (lines < 8 && !string.IsNullOrEmpty(value)) { builder.Append('\n').Append(label).Append(": ") .Append(value); lines++; } } private static void AppendInteger(StringBuilder builder, ref int lines, string label, int current, int? comparison) { if (lines < 8) { builder.Append('\n').Append(label).Append(": ") .Append(current); if (comparison.HasValue) { AppendDelta(builder, current - comparison.Value, string.Empty); } lines++; } } private static void AppendNumber(StringBuilder builder, ref int lines, string label, float current, float? comparison, bool include, string suffix = "") { if (include && lines < 8) { builder.Append('\n').Append(label).Append(": ") .Append(FormatNumber(current)) .Append(suffix); if (comparison.HasValue) { AppendDelta(builder, current - comparison.Value, suffix); } lines++; } } private static void AppendDelta(StringBuilder builder, float delta, string suffix) { if (!(Math.Abs(delta) < 0.05f)) { builder.Append(" ("); if (delta > 0f) { builder.Append('+'); } builder.Append(FormatNumber(delta)).Append(suffix).Append(')'); } } private static string FormatNumber(float value) { return value.ToString("0.#", CultureInfo.InvariantCulture); } } internal enum ItemComparisonDisposition { HideKnownNonEquipment, CompareKnownEquipment, ShowRawUnsupported } internal static class ItemTypePolicy { internal static ItemComparisonDisposition Classify(string type) { switch (type) { case "Helmet": case "Shield": case "Chest": case "Hands": case "Torch": case "Legs": case "Ammo": case "Tool": case "Shoulder": case "Trinket": case "Utility": case "OneHandedWeapon": case "TwoHandedWeapon": case "Attach_Atgeir": case "TwoHandedWeaponLeft": case "Bow": return ItemComparisonDisposition.CompareKnownEquipment; case "Trophy": case "None": case "Misc": case "Fish": case "Material": case "Customization": case "Consumable": case "AmmoNonEquipable": return ItemComparisonDisposition.HideKnownNonEquipment; default: if (!string.IsNullOrEmpty(type)) { return ItemComparisonDisposition.ShowRawUnsupported; } return ItemComparisonDisposition.HideKnownNonEquipment; } } } internal enum AwarenessPanel { Food, Effects, Comfort, ItemComparison, Production, Agriculture, Building, TamedAnimal } internal sealed class PanelCache { private static readonly string[] Titles = new string[8] { "Food", "Effects", "Comfort", "Item comparison", "Production", "Agriculture", "Building", "Tamed animal" }; private readonly string[] _bodies = new string[Titles.Length]; private readonly int[] _bodyLines = new int[Titles.Length]; private readonly StringBuilder _builder = new StringBuilder(4096); private int _version; private int _composedVersion = -1; private bool _composedInventoryOnly; private string _composed = string.Empty; private int _composedLines; internal int Version => _version; internal string ComposedText => _composed; internal int ComposedLines => _composedLines; internal bool Set(AwarenessPanel panel, string body) { body = body ?? string.Empty; if (string.Equals(_bodies[(int)panel], body, StringComparison.Ordinal)) { return false; } _bodies[(int)panel] = body; _bodyLines[(int)panel] = BoundedText.CountLines(body); _version++; return true; } internal bool Clear(AwarenessPanel panel) { return Set(panel, string.Empty); } internal bool Compose(bool inventoryOnly) { if (_composedVersion == _version && _composedInventoryOnly == inventoryOnly) { return false; } _builder.Clear(); int num = 0; for (int i = 0; i < _bodies.Length; i++) { bool flag = i == 3; if (inventoryOnly == flag && !string.IsNullOrEmpty(_bodies[i])) { int num2 = _bodyLines[i] + 1 + ((_builder.Length != 0) ? 1 : 0); if (num + num2 > 40) { break; } int num3 = ((_builder.Length != 0) ? 2 : 0) + 3 + Titles[i].Length + 5 + _bodies[i].Length; if (_builder.Length + num3 > 4096) { break; } if (_builder.Length > 0) { _builder.Append("\n\n"); num++; } _builder.Append("<b>").Append(Titles[i]).Append("</b>\n"); _builder.Append(_bodies[i]); num += _bodyLines[i] + 1; } } _composed = _builder.ToString(); _composedLines = num; _composedVersion = _version; _composedInventoryOnly = inventoryOnly; return true; } } internal static class TimerFormatter { internal const int MaximumDisplaySeconds = 359999; internal static int Bucket(float remainingSeconds, AwarenessTimerPrecision precision) { if (float.IsNaN(remainingSeconds) || float.IsInfinity(remainingSeconds) || remainingSeconds <= 0f) { return 0; } int num = Math.Max(1, (int)precision); double num2 = Math.Min(359999f, remainingSeconds); return Math.Min(359999, checked((int)Math.Ceiling(num2 / (double)num) * num)); } internal static void Append(StringBuilder builder, int bucketSeconds) { int num = Math.Max(0, Math.Min(359999, bucketSeconds)); int num2 = num / 3600; int value = num / 60 % 60; int value2 = num % 60; if (num2 > 0) { builder.Append(num2).Append(':'); AppendTwoDigits(builder, value); builder.Append(':'); AppendTwoDigits(builder, value2); } else { builder.Append(value).Append(':'); AppendTwoDigits(builder, value2); } } internal static string Format(int bucketSeconds) { StringBuilder stringBuilder = new StringBuilder(10); Append(stringBuilder, bucketSeconds); return stringBuilder.ToString(); } private static void AppendTwoDigits(StringBuilder builder, int value) { if (value < 10) { builder.Append('0'); } builder.Append(value); } } }