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.5
plugins/RunicAwareness/RunicAwareness.dll
Decompiled a week agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization.Json; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Runic.Localization; 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.4.0")] [assembly: AssemblyInformationalVersion("1.0.4")] [assembly: InternalsVisibleTo("RunicAwareness.Tests")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("1.0.4.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 Runic.Localization { internal sealed class LanguageCatalog { private readonly Dictionary<string, string> _english; private Dictionary<string, string> _selected = new Dictionary<string, string>(); private readonly Func<string, string> _read; private string _language; internal LanguageCatalog(string english, Func<string, string> read) { _english = Parse(english); _read = read; } internal static Dictionary<string, string> Parse(string json) { if (string.IsNullOrWhiteSpace(json)) { return new Dictionary<string, string>(); } if (json.Length > 2097152) { throw new InvalidDataException("Language file exceeds 2 MiB."); } DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(typeof(Dictionary<string, string>), new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true }); using MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json.TrimStart('\ufeff'))); return ((Dictionary<string, string>)dataContractJsonSerializer.ReadObject(stream)) ?? new Dictionary<string, string>(); } internal void Select(string language) { language = (string.IsNullOrWhiteSpace(language) ? "English" : language); if (string.Equals(language, _language, StringComparison.Ordinal)) { return; } _language = language; _selected = new Dictionary<string, string>(); if (language.IndexOfAny(new char[4] { '/', '\\', ':', '\0' }) >= 0 || language == "." || language == "..") { return; } try { _selected = Parse(_read(language)); } catch (Exception) { } } internal string Get(string key) { _english.TryGetValue(key, out var value); if (_selected.TryGetValue(key, out var value2) && !string.IsNullOrWhiteSpace(value2) && (value == null || SameFields(value, value2))) { return value2; } return value ?? key; } internal string English(string key) { if (!_english.TryGetValue(key, out var value)) { return key; } return value; } internal string TranslateEnglish(string text) { foreach (KeyValuePair<string, string> item in _english) { if (item.Value == text) { return Get(item.Key); } } return text; } internal string Format(string key, params object[] args) { _english.TryGetValue(key, out var value); string text = Get(key); if (value != null && !SameFields(value, text)) { text = value; } try { return string.Format(CultureInfo.CurrentCulture, text, args); } catch (FormatException) { try { return string.Format(CultureInfo.CurrentCulture, value ?? key, args); } catch (FormatException) { return value ?? key; } } } private static bool SameFields(string a, string b) { try { return Fields(a).SetEquals(Fields(b)); } catch (FormatException) { return false; } } private static HashSet<int> Fields(string text) { HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < text.Length; i++) { if (text[i] == '}') { if (i + 1 >= text.Length || text[i + 1] != '}') { throw new FormatException(); } i++; } else { if (text[i] != '{') { continue; } if (i + 1 < text.Length && text[i + 1] == '{') { i++; continue; } int num = ++i; for (; i < text.Length && char.IsDigit(text[i]); i++) { } if (num == i || !int.TryParse(text.Substring(num, i - num), out var result)) { throw new FormatException(); } hashSet.Add(result); for (; i < text.Length && text[i] != '}'; i++) { if (text[i] == '{') { throw new FormatException(); } } if (i == text.Length) { throw new FormatException(); } } } return hashSet; } } internal static class RunicText { private static readonly Assembly Owner = typeof(RunicText).Assembly; private static readonly string Module = Owner.GetName().Name; private static readonly LanguageCatalog Catalog = Create(); private static LanguageCatalog Create() { using Stream stream = Owner.GetManifestResourceStream("Runic.Localization.English.json"); using StreamReader streamReader = ((stream == null) ? null : new StreamReader(stream)); return new LanguageCatalog(streamReader?.ReadToEnd() ?? "{}", ReadLanguage); } private static string ReadLanguage(string language) { string path = Path.Combine(Path.Combine(Path.GetDirectoryName(Owner.Location), "Translations", Module), language + ".json"); if (!File.Exists(path)) { return null; } return File.ReadAllText(path); } private static void SelectLanguage() { string language = "English"; try { Localization instance = Localization.instance; language = ((instance != null) ? instance.GetSelectedLanguage() : null) ?? "English"; } catch { } Catalog.Select(language); } internal static string Get(string key) { SelectLanguage(); return Catalog.Get(key); } internal static string English(string key) { return Catalog.English(key); } internal static string TranslateEnglish(string text) { SelectLanguage(); return Catalog.TranslateEnglish(text); } internal static string Format(string key, params object[] args) { SelectLanguage(); return Catalog.Format(key, args); } } } 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_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Expected O, but got Unknown //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Expected O, but got Unknown //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Expected O, but got Unknown //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Expected O, but got Unknown //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Expected O, but got Unknown Enabled = config.Bind<bool>("General", "Enabled", true, RunicText.Get("text_ec3a59079845")); ShowFoodTimers = Panel(config, "FoodTimers", defaultValue: true, RunicText.Get("text_a4bb65e67fea")); ShowEffectTimers = Panel(config, "EffectTimers", defaultValue: true, RunicText.Get("text_8c10f579fab2")); ShowComfort = Panel(config, "ComfortBreakdown", defaultValue: true, RunicText.Get("text_0a62886999a3")); ShowItemComparison = Panel(config, "ItemComparison", defaultValue: true, RunicText.Get("text_d8c348bb2521")); ShowProduction = Panel(config, "ProductionContext", defaultValue: true, RunicText.Get("text_c15b51f64b09")); ShowAgriculture = Panel(config, "AgricultureContext", defaultValue: true, RunicText.Get("text_f0ca691afa6f")); ShowBuilding = Panel(config, "BuildingContext", defaultValue: true, RunicText.Get("text_dc6303811cd6")); ShowTamedAnimals = Panel(config, "TamedAnimalContext", defaultValue: true, RunicText.Get("text_cbe9e30cc686")); TimerPrecision = config.Bind<AwarenessTimerPrecision>("Display", "TimerPrecision", AwarenessTimerPrecision.WholeSecond, RunicText.Get("text_8286cca9c032")); RefreshInterval = config.Bind<float>("Display", "RefreshIntervalSeconds", 0.35f, new ConfigDescription(RunicText.Get("text_352dbacb99be"), (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 2f), Array.Empty<object>())); MaximumEffectRows = config.Bind<int>("Display", "MaximumEffectRows", 5, new ConfigDescription(RunicText.Get("text_e5d72a41f73c"), (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>())); MaximumComfortRows = config.Bind<int>("Display", "MaximumComfortWinnerRows", 6, new ConfigDescription(RunicText.Get("text_65e1027cf017"), (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>())); MaximumContextLines = config.Bind<int>("Display", "MaximumContextLines", 5, new ConfigDescription(RunicText.Get("text_943e318572fe"), (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 6), Array.Empty<object>())); UiScale = config.Bind<float>("Display", "UiScale", 1f, new ConfigDescription(RunicText.Get("text_bddd88ea038a"), (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.75f, 2f), Array.Empty<object>())); ControllerScaleMultiplier = config.Bind<float>("Display", "ControllerScaleMultiplier", 1.15f, new ConfigDescription(RunicText.Get("text_256227e437ea"), (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 1.5f), Array.Empty<object>())); Anchor = config.Bind<AwarenessOverlayAnchor>("Display", "Anchor", AwarenessOverlayAnchor.MiddleLeft, RunicText.Get("text_161e710f61d3")); MiddleLeftAnchorMigrationApplied = config.Bind<bool>("Migrations", "DefaultAnchorMovedToMiddleLeft", false, RunicText.Get("text_65bb41d59a9b")); 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, RunicText.Get("text_4a49a608f002")); } 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.4")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "chazman.RunicAwareness"; public const string Name = "Runic Awareness"; public const string Version = "1.0.4"; 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.4 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, RunicText.Get("text_1d2daebe64a5") + 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(RunicText.Get("text_41f530ff8d65")); } _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, RunicText.Get("text_82ca4ff07363") + 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(RunicText.Get("text_f9d361b571a5")); } _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(RunicText.Get("text_596663b106bf")).Append(num).Append(flag ? RunicText.Get("text_be78596135a9") : RunicText.Get("text_5447300d55f0")); _builder.Append(RunicText.Get("text_5fe3267bdb18")); 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(RunicText.Get("text_5913e42517da")); } _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_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: 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(RunicText.Get("text_1fb7ebe0bcf4")).Append(num.ToString("0", CultureInfo.InvariantCulture)).Append('%'); } _builder.Append(RunicText.Get("text_b903a0442568")).Append(VectorText(position)); _builder.Append(RunicText.Get("text_c86233fec08e")).Append(VectorText(eulerAngles)); if (_cachedStationLevel > 0) { _builder.Append(RunicText.Get("text_4ece3e556586")).Append(_cachedStationLevel); } _builder.Append(RunicText.Get("text_724018b29f5f")); _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; } if (!ValheimContracts.GameplayInputBlocked(localPlayer)) { return _portalPanels.SetupPanelVisible(localPlayer); } return true; } 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(RunicText.Get("text_60c5ff286468")).Append(num).Append(RunicText.Get("text_10267c60bf55")) .Append(64) .Append(")."); } else { AppendWinners(Builder, list, num, flag); if (!flag) { if (Builder.Length > 0) { Builder.Append('\n'); } Builder.Append(RunicText.Get("text_9fa12d3448ee")); } } _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_0090: 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(RunicText.Get("text_426565458d7a")); if (sheltered) { builder.Append(RunicText.Get("text_2bcb760d3170")); } 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_00c0; } } num4 = (string.Equals(val2.m_name, val.m_name, StringComparison.Ordinal) ? 1 : 0); } else { num4 = 0; } goto IL_00c0; IL_00c0: 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(RunicText.Get("text_ed3bc035c87c")); } if (num3 > 0) { builder.Append(RunicText.Get("text_888cbc0d1cb6")).Append(num3).Append(RunicText.Get("text_ad7cefa833f0")); } } 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>(RunicText.Get("text_c910d474dcd7"), RunicText.Get("text_92c1cdfdf4cb"), ref enabled) || !config.TryGetEntry<bool>(RunicText.Get("text_5697d03daef4"), "UniversalRouting", ref universalRouting) || !config.TryGetEntry<bool>(RunicText.Get("text_34e108c0896d"), "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; private static Func<Player, bool> _takeInput; internal static FieldInfo ComfortPiecesField => _comfortPieces ?? throw new InvalidOperationException("Valheim contracts are not initialized."); internal static bool GameplayInputBlocked(Player player) { if (!((Object)(object)player == (Object)null) && _takeInput != null) { return !_takeInput(player); } return true; } internal static void VerifyInstalledSignatures() { if (!_verified) { _takeInput = AccessTools.MethodDelegate<Func<Player, bool>>(RequireMethod(typeof(Player), "TakeInput", typeof(bool), false), (object)null, true); 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(RunicText.Get("text_e16d658cb550")); } else if (equipped.HasValue) { stringBuilder.Append(RunicText.Get("text_8e292bb1d717")).Append(BoundedText.Label(equipped.Value.Name)); } else { stringBuilder.Append(RunicText.Get("text_4ed6c4f52683")); } int lines = 1; AppendText(stringBuilder, ref lines, RunicText.Get("text_baaddf70fb5d"), BoundedText.Label(selected.ItemType)); AppendInteger(stringBuilder, ref lines, RunicText.Get("text_1b2c08a8733d"), selected.Quality, equipped?.Quality); AppendNumber(stringBuilder, ref lines, RunicText.Get("text_7fb3931ac7f8"), selected.Damage, equipped?.Damage, selected.Damage > 0f || (equipped?.Damage ?? 0f) > 0f); AppendNumber(stringBuilder, ref lines, RunicText.Get("text_a592855d0508"), selected.Armor, equipped?.Armor, selected.Armor > 0f || (equipped?.Armor ?? 0f) > 0f); AppendNumber(stringBuilder, ref lines, RunicText.Get("text_14630e234541"), selected.Block, equipped?.Block, selected.Block > 0f || (equipped?.Block ?? 0f) > 0f); AppendNumber(stringBuilder, ref lines, RunicText.Get("text_6ecc3df6bffd"), selected.MovementPercent, equipped?.MovementPercent, include: true, "%"); if (lines < 8 && !string.IsNullOrEmpty(selected.SkillName)) { stringBuilder.Append(RunicText.Get("text_ccbca08e91b3")).Append(BoundedText.Label(selected.SkillName)).Append(' ') .Append(FormatNumber(selected.SkillLevel)); lines++; } AppendNumber(stringBuilder, ref lines, RunicText.Get("text_81d27ef6d503"), selected.Weight, equipped?.Weight, include: true); if (lines < 8 && selected.MaximumDurability > 0f) { stringBuilder.Append(RunicText.Get("text_4bcbae487b47")).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 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; private static string[] Titles => new string[8] { RunicText.Get("text_e4eb18069d65"), RunicText.Get("text_358511c8c098"), RunicText.Get("text_040d4645676f"), RunicText.Get("text_3bdbe9d40080"), RunicText.Get("text_13a7c655a7d4"), RunicText.Get("text_f0b1b07aa8f1"), RunicText.Get("text_87c5912f2cef"), RunicText.Get("text_0a7cc2687346") }; 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); } } }