Decompiled source of Odins Nudge v0.1.1

plugins/OdinsNudge.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("OdinsNudge")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1+21f5148171e5c66ec0f3299e1ba04eae9b5c8cef")]
[assembly: AssemblyProduct("OdinsNudge")]
[assembly: AssemblyTitle("OdinsNudge")]
[assembly: AssemblyVersion("0.1.1.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 OdinsNudge
{
	internal static class HudText
	{
		private sealed class Scale : MonoBehaviour
		{
			private const float ReferenceWidth = 1920f;

			private const float ReferenceHeight = 1080f;

			private static readonly FieldRef<float> GameGuiScale = TryFindGuiScale();

			internal CanvasScaler Scaler;

			private float _logged = -1f;

			private void Update()
			{
				if (!((Object)(object)Scaler == (Object)null))
				{
					float num = ((GameGuiScale != null) ? GameGuiScale.Invoke() : 1f);
					if (num <= 0f)
					{
						num = 1f;
					}
					float num2 = Mathf.Min((float)Screen.width / 1920f, (float)Screen.height / 1080f) * num * Plugin.TextScale.Value;
					if (Mathf.Abs(Scaler.scaleFactor - num2) > 0.0001f)
					{
						Scaler.scaleFactor = num2;
					}
					if (Plugin.DebugLog.Value && Mathf.Abs(_logged - num2) > 0.0001f)
					{
						_logged = num2;
						Plugin.Log.LogInfo((object)$"Text scale {num2:0.###}: screen {Screen.width}x{Screen.height}, game GUI scale {num:0.##}, TextScale {Plugin.TextScale.Value:0.##}.");
					}
				}
			}

			private static FieldRef<float> TryFindGuiScale()
			{
				FieldInfo fieldInfo = AccessTools.Field(typeof(GuiScaler), "m_largeGuiScale");
				if (fieldInfo == null || fieldInfo.FieldType != typeof(float) || !fieldInfo.IsStatic)
				{
					Plugin.Log.LogWarning((object)"Couldn't find the game's GUI scale; text follows the screen size only.");
					return null;
				}
				return AccessTools.StaticFieldRefAccess<float>(fieldInfo);
			}
		}

		private static Canvas _canvas;

		public static TMP_Text Create(string name, Transform owner)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			TMP_Text val = (((Object)(object)MessageHud.instance != (Object)null) ? MessageHud.instance.m_messageCenterText : null);
			if ((Object)(object)val == (Object)null || (Object)(object)val.font == (Object)null || !EnsureCanvas(owner))
			{
				return null;
			}
			GameObject val2 = new GameObject(name, new Type[1] { typeof(RectTransform) });
			val2.transform.SetParent(((Component)_canvas).transform, false);
			TextMeshProUGUI obj = val2.AddComponent<TextMeshProUGUI>();
			((TMP_Text)obj).font = val.font;
			((TMP_Text)obj).fontSharedMaterial = val.fontSharedMaterial;
			((TMP_Text)obj).alignment = (TextAlignmentOptions)514;
			((TMP_Text)obj).textWrappingMode = (TextWrappingModes)1;
			((Graphic)obj).raycastTarget = false;
			((TMP_Text)obj).outlineWidth = 0.15f;
			((TMP_Text)obj).outlineColor = new Color32((byte)0, (byte)0, (byte)0, (byte)200);
			RectTransform val3 = (RectTransform)val2.transform;
			val3.pivot = new Vector2(0.5f, 0.5f);
			val3.anchoredPosition = Vector2.zero;
			return (TMP_Text)(object)obj;
		}

		private static bool EnsureCanvas(Transform owner)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: 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)
			if ((Object)(object)_canvas != (Object)null)
			{
				return true;
			}
			GameObject val = new GameObject("OdinsNudge_Canvas");
			val.transform.SetParent(owner, false);
			_canvas = val.AddComponent<Canvas>();
			_canvas.renderMode = (RenderMode)0;
			_canvas.sortingOrder = 5000;
			CanvasScaler val2 = val.AddComponent<CanvasScaler>();
			val2.uiScaleMode = (ScaleMode)0;
			val.AddComponent<Scale>().Scaler = val2;
			Plugin.Log.LogInfo((object)"On-screen text layer created.");
			return true;
		}
	}
	internal sealed class Nudge : MonoBehaviour
	{
		internal enum Kind
		{
			Death,
			Raid,
			Damage,
			Enemy,
			Name
		}

		private const float ScanInterval = 0.5f;

		private const float WelcomeDelay = 0.6f;

		private static readonly Dictionary<Kind, string[]> SignOffs = new Dictionary<Kind, string[]>
		{
			[Kind.Death] = new string[3] { "Odin did knock.", "Odin knocked. You were busy. Being dead, mostly.", "Odin would rather you heard it from him." },
			[Kind.Raid] = new string[2] { "Odin knocked. The army didn't wait.", "Odin did knock. Loudly." },
			[Kind.Damage] = new string[3] { "Odin counted.", "Odin knocked. Something else was knocking harder.", "Odin kept score. You lost." },
			[Kind.Enemy] = new string[3] { "Odin knocked first.", "Odin thought you'd want to know.", "They didn't knock. Odin did." },
			[Kind.Name] = new string[2] { "Odin isn't your secretary, but there it is.", "Odin passed the message on." }
		};

		private readonly float[] _lastKnock = new float[5];

		private readonly HashSet<int> _threats = new HashSet<int>();

		private readonly HashSet<int> _threatsNow = new HashSet<int>();

		private OdinLine _line;

		private AudioSource _audio;

		private bool _focused = true;

		private float _nextScan;

		private float _welcomeAt = -1f;

		private string _raid;

		private string _lastSignOff;

		private int _hits;

		private float _damage;

		private int _deaths;

		private int _enemies;

		private int _raids;

		private string _raidText;

		private int _names;

		private string _namedBy;

		private bool _knocked;

		internal static Nudge Instance { get; private set; }

		private void Awake()
		{
			Instance = this;
			_line = ((Component)this).gameObject.AddComponent<OdinLine>();
			_audio = ((Component)this).gameObject.AddComponent<AudioSource>();
			_audio.playOnAwake = false;
			_audio.spatialBlend = 0f;
			_audio.ignoreListenerPause = true;
			_audio.bypassReverbZones = true;
			for (int i = 0; i < _lastKnock.Length; i++)
			{
				_lastKnock[i] = float.MinValue;
			}
		}

		private void OnDestroy()
		{
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		private void Update()
		{
			bool isFocused = Application.isFocused;
			if (isFocused != _focused)
			{
				_focused = isFocused;
				if (isFocused)
				{
					Back();
				}
				else
				{
					Away();
				}
			}
			if (_welcomeAt >= 0f && Time.unscaledTime >= _welcomeAt)
			{
				_welcomeAt = -1f;
				Welcome();
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				_raid = null;
				return;
			}
			WatchRaid(isFocused);
			if (!isFocused && Plugin.Enabled.Value && !(Time.unscaledTime < _nextScan))
			{
				_nextScan = Time.unscaledTime + 0.5f;
				if (Plugin.NudgeOnEnemy.Value && !((Character)localPlayer).IsDead())
				{
					WatchEnemies(localPlayer);
				}
				if (Plugin.NudgeOnRaid.Value && _raid != null && !((Character)localPlayer).IsDead())
				{
					Raise(Kind.Raid, "raid " + _raid + " still on around you", quietly: true);
				}
			}
		}

		private void Away()
		{
			Clear();
			_threats.Clear();
			for (int i = 0; i < _lastKnock.Length; i++)
			{
				_lastKnock[i] = float.MinValue;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null && Plugin.NudgeOnEnemy.Value)
			{
				Threats(localPlayer, _threats, null, out var _);
			}
			if (_threats.Count > 0)
			{
				_lastKnock[3] = Time.unscaledTime;
			}
			if (_raid != null)
			{
				_lastKnock[1] = Time.unscaledTime;
			}
			Note($"Valheim is in the background. Odin is watching ({_threats.Count} awake enemies about already).");
		}

		private void Back()
		{
			Taskbar.Stop();
			if (!_knocked && Total() == 0)
			{
				Note("Back in Valheim. Nothing happened.");
				return;
			}
			Note($"Back in Valheim: {_hits} hits ({_damage:0} damage), {_deaths} deaths, {_enemies} enemies, {_raids} raids, {_names} mentions.");
			if (Plugin.WelcomeBack.Value && Total() > 0)
			{
				_welcomeAt = Time.unscaledTime + 0.6f;
			}
			else
			{
				Clear();
			}
		}

		private int Total()
		{
			return _hits + _deaths + _enemies + _raids + _names;
		}

		private void Clear()
		{
			_hits = (_deaths = (_enemies = (_raids = (_names = 0))));
			_damage = 0f;
			_raidText = (_namedBy = null);
			_knocked = false;
		}

		internal void OnHurt(float damage, Character attacker)
		{
			if (!_focused && Plugin.Enabled.Value && Plugin.NudgeOnDamage.Value && !(damage < Plugin.MinDamage.Value))
			{
				_hits++;
				_damage += damage;
				Raise(Kind.Damage, string.Format("hit for {0:0.#} by {1}", damage, ((Object)(object)attacker != (Object)null) ? attacker.m_name : "the world"));
			}
		}

		internal void OnDied()
		{
			if (!_focused && Plugin.Enabled.Value && Plugin.NudgeOnDeath.Value)
			{
				_deaths++;
				Raise(Kind.Death, "you died");
			}
		}

		internal void OnChat(Type type, UserInfo sender, string text)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			if (!_focused && Plugin.Enabled.Value && Plugin.NudgeOnName.Value && !string.IsNullOrEmpty(text))
			{
				string text2 = NameIn(text);
				if (text2 != null)
				{
					_names++;
					_namedBy = Who(sender);
					Raise(Kind.Name, string.Format("{0} said \"{1}\" ({2})", _namedBy ?? "someone", text2, type));
				}
			}
		}

		private void WatchRaid(bool focused)
		{
			RandomEvent val = (((Object)(object)RandEventSystem.instance != (Object)null) ? RandEventSystem.instance.GetActiveEvent() : null);
			string text = val?.m_name;
			if (!(text == _raid))
			{
				_raid = text;
				if (!(text == null || focused) && Plugin.Enabled.Value && Plugin.NudgeOnRaid.Value)
				{
					_raids++;
					_raidText = Localize(val.m_startMessage);
					Raise(Kind.Raid, "raid " + text + " started around you");
				}
			}
		}

		private void WatchEnemies(Player player)
		{
			//IL_0077: 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)
			_threatsNow.Clear();
			Character nearest;
			Character val = Threats(player, _threatsNow, _threats, out nearest);
			_threats.Clear();
			_threats.UnionWith(_threatsNow);
			if (!((Object)(object)nearest == (Object)null))
			{
				if ((Object)(object)val != (Object)null)
				{
					_enemies++;
				}
				Character val2 = val ?? nearest;
				Raise(Kind.Enemy, string.Format("{0} {1:0} m away{2}", val2.m_name, Vector3.Distance(((Component)val2).transform.position, ((Component)player).transform.position), ((Object)(object)val != (Object)null) ? "" : ", still"), (Object)(object)val == (Object)null);
			}
		}

		private static Character Threats(Player player, HashSet<int> into, HashSet<int> before, out Character nearest)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			Character val = null;
			nearest = null;
			float num = float.MaxValue;
			float value = Plugin.EnemyRadius.Value;
			Vector3 position = ((Component)player).transform.position;
			foreach (Character allCharacter in Character.GetAllCharacters())
			{
				if ((Object)(object)allCharacter == (Object)null || (Object)(object)allCharacter == (Object)(object)player || allCharacter.IsPlayer() || allCharacter.IsDead() || allCharacter.IsTamed())
				{
					continue;
				}
				float num2 = Vector3.Distance(((Component)allCharacter).transform.position, position) - allCharacter.GetRadius();
				if (!(num2 > value) && IsThreat(player, allCharacter))
				{
					if (num2 < num)
					{
						num = num2;
						nearest = allCharacter;
					}
					int instanceID = ((Object)allCharacter).GetInstanceID();
					into.Add(instanceID);
					if (before != null && (Object)(object)val == (Object)null && !before.Contains(instanceID))
					{
						val = allCharacter;
					}
				}
			}
			return val;
		}

		private static bool IsThreat(Player player, Character other)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Invalid comparison between Unknown and I4
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Invalid comparison between Unknown and I4
			if (!BaseAI.IsEnemy((Character)(object)player, other))
			{
				return false;
			}
			BaseAI baseAI = other.GetBaseAI();
			if ((Object)(object)baseAI == (Object)null)
			{
				return false;
			}
			Faction faction = other.GetFaction();
			if ((int)faction != 1)
			{
				if ((int)faction == 12)
				{
					return false;
				}
				if (!baseAI.IsAlerted() && !baseAI.HaveTarget())
				{
					return baseAI.HuntPlayer();
				}
				return true;
			}
			if (baseAI is MonsterAI)
			{
				return baseAI.HaveTarget();
			}
			return false;
		}

		private void Raise(Kind kind, string what, bool quietly = false)
		{
			float unscaledTime = Time.unscaledTime;
			float num = Plugin.Cooldown.Value - (unscaledTime - _lastKnock[(int)kind]);
			if (num > 0f)
			{
				if (!quietly)
				{
					Note($"{kind}: {what}. Knocked for that {unscaledTime - _lastKnock[(int)kind]:0} s ago; next knock in {num:0} s if it keeps up.");
				}
				return;
			}
			_lastKnock[(int)kind] = unscaledTime;
			bool flag = Plugin.FlashTaskbar.Value && Taskbar.Flash();
			AudioClip val = Sounds.Current();
			if ((Object)(object)val != (Object)null && Plugin.Volume.Value > 0f)
			{
				_audio.PlayOneShot(val, Plugin.Volume.Value);
			}
			_knocked = true;
			Note(string.Format("Nudge! {0}: {1}. Flash {2}, sound {3}.", kind, what, flag ? "on" : "off", ((Object)(object)val != (Object)null) ? ((Object)val).name : "none"));
		}

		private void Welcome()
		{
			string text = Summary();
			Clear();
			if (text != null)
			{
				_line.Show(text);
			}
		}

		private string Summary()
		{
			List<string> parts = new List<string>();
			Kind top = Kind.Name;
			bool any = false;
			if (_deaths > 0)
			{
				Add(Kind.Death, (_deaths == 1) ? "you died" : $"you died {_deaths} times");
			}
			if (_raids > 0)
			{
				Add(Kind.Raid, (!string.IsNullOrEmpty(_raidText)) ? ("a raid started (\"" + _raidText.TrimEnd('.', '!') + "\")") : "a raid started");
			}
			if (_hits > 0)
			{
				Add(Kind.Damage, (_hits == 1) ? $"you were hit once ({_damage:0} damage)" : $"you were hit {_hits} times ({_damage:0} damage)");
			}
			if (_enemies > 0)
			{
				Add(Kind.Enemy, (_enemies == 1) ? "something came looking for you" : $"{_enemies} things came looking for you");
			}
			if (_names > 0)
			{
				Add(Kind.Name, (_names == 1) ? ((_namedBy ?? "someone") + " said your name in chat") : $"chat said your name {_names} times");
			}
			if (!any)
			{
				return null;
			}
			string text = ((parts.Count == 1) ? parts[0] : (string.Join(", ", parts.GetRange(0, parts.Count - 1).ToArray()) + " and " + parts[parts.Count - 1]));
			return string.Concat(char.ToUpperInvariant(text[0]) + text.Substring(1) + " while you were away.", " ", SignOff(top));
			void Add(Kind kind, string part)
			{
				parts.Add(part);
				if (!any || kind < top)
				{
					top = kind;
				}
				any = true;
			}
		}

		private string SignOff(Kind kind)
		{
			string[] array = SignOffs[kind];
			string text = array[Random.Range(0, array.Length)];
			if (text == _lastSignOff && array.Length > 1)
			{
				text = array[(Array.IndexOf(array, text) + 1) % array.Length];
			}
			_lastSignOff = text;
			return text;
		}

		private static string NameIn(string text)
		{
			foreach (string item in Names())
			{
				if (item.Length >= 2)
				{
					string pattern = "(?<![\\p{L}\\p{N}_])" + Regex.Escape(item) + "(?![\\p{L}\\p{N}_])";
					if (Regex.IsMatch(text, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
					{
						return item;
					}
				}
			}
			return null;
		}

		private static IEnumerable<string> Names()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				yield return localPlayer.GetPlayerName().Trim();
			}
			string[] array = (Plugin.ExtraNames.Value ?? "").Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					yield return text;
				}
			}
		}

		private static string Who(UserInfo sender)
		{
			if (sender == null)
			{
				return null;
			}
			try
			{
				string displayName = sender.GetDisplayName();
				return string.IsNullOrEmpty(displayName) ? sender.Name : displayName;
			}
			catch (Exception)
			{
				return sender.Name;
			}
		}

		private static string Localize(string token)
		{
			if (!string.IsNullOrEmpty(token))
			{
				if (Localization.instance == null)
				{
					return token.TrimStart(new char[1] { '$' });
				}
				return Localization.instance.Localize(token);
			}
			return null;
		}

		private static void Note(string message)
		{
			if (Plugin.DebugLog.Value)
			{
				Plugin.Log.LogInfo((object)message);
			}
		}
	}
	internal class OdinLine : MonoBehaviour
	{
		private const float ScreenY = 0.72f;

		private const float FadeIn = 0.2f;

		private const float Hold = 4.5f;

		private const float FadeOut = 0.8f;

		private const float TextSize = 21f;

		private static readonly Color Tint = new Color(1f, 0.86f, 0.62f);

		private TMP_Text _text;

		private float _elapsed = -1f;

		public void Show(string line)
		{
			if (EnsureText())
			{
				_text.text = line;
				_elapsed = 0f;
				SetAlpha(0f);
				((Component)_text).gameObject.SetActive(true);
			}
		}

		private void Update()
		{
			if (_elapsed < 0f || (Object)(object)_text == (Object)null)
			{
				return;
			}
			_elapsed += Time.deltaTime;
			float num = 5.5f;
			if (_elapsed >= num)
			{
				_elapsed = -1f;
				((Component)_text).gameObject.SetActive(false);
				return;
			}
			float num2 = Mathf.Min(Mathf.Clamp01(_elapsed / 0.2f), Mathf.Clamp01((num - _elapsed) / 0.8f));
			if (!HudVisible())
			{
				num2 = 0f;
			}
			SetAlpha(Mathf.SmoothStep(0f, 1f, num2));
		}

		private void SetAlpha(float alpha)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			Color tint = Tint;
			tint.a = alpha;
			((Graphic)_text).color = tint;
		}

		private bool EnsureText()
		{
			//IL_0041: 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_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_text != (Object)null)
			{
				return true;
			}
			_text = HudText.Create("OdinsNudge_Line", ((Component)this).transform);
			if ((Object)(object)_text == (Object)null)
			{
				return false;
			}
			RectTransform val = (RectTransform)_text.transform;
			val.anchorMin = new Vector2(0.5f, 0.72f);
			val.anchorMax = new Vector2(0.5f, 0.72f);
			val.sizeDelta = new Vector2(1400f, 160f);
			_text.fontSize = 21f;
			return true;
		}

		private static bool HudVisible()
		{
			if ((Object)(object)Hud.instance != (Object)null && !Hud.IsUserHidden())
			{
				return Hud.instance.IsVisible();
			}
			return false;
		}

		private void OnDestroy()
		{
			if ((Object)(object)_text != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)_text).gameObject);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnDamaged")]
	internal static class PlayerDamagedPatch
	{
		private static void Postfix(Player __instance, HitData hit)
		{
			if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && hit != null && !((Object)(object)Nudge.Instance == (Object)null))
			{
				Nudge.Instance.OnHurt(hit.GetTotalDamage(), hit.GetAttacker());
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnDeath")]
	internal static class PlayerDeathPatch
	{
		private static void Postfix(Player __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !((Object)(object)Nudge.Instance == (Object)null))
			{
				Nudge.Instance.OnDied();
			}
		}
	}
	[HarmonyPatch(typeof(Chat), "OnNewChatMessage")]
	internal static class ChatMessagePatch
	{
		private static void Prefix(long senderID, Type type, UserInfo sender, string text)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)Nudge.Instance == (Object)null) && !((Object)(object)ZNet.instance == (Object)null))
			{
				ZDOID localPlayerCharacterID = ZNet.instance.LocalPlayerCharacterID;
				if (senderID != ((ZDOID)(ref localPlayerCharacterID)).UserID)
				{
					Nudge.Instance.OnChat(type, sender, text);
				}
			}
		}
	}
	[BepInPlugin("hevyh2o.valheim.odinsnudge", "Odin's Nudge", "0.1.1")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "hevyh2o.valheim.odinsnudge";

		public const string Name = "Odin's Nudge";

		public const string Version = "0.1.1";

		internal static ManualLogSource Log;

		internal static Harmony Patcher;

		internal static string ConfigFolder;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<float> Cooldown;

		internal static ConfigEntry<bool> NudgeOnDamage;

		internal static ConfigEntry<float> MinDamage;

		internal static ConfigEntry<bool> NudgeOnEnemy;

		internal static ConfigEntry<float> EnemyRadius;

		internal static ConfigEntry<bool> NudgeOnRaid;

		internal static ConfigEntry<bool> NudgeOnDeath;

		internal static ConfigEntry<bool> NudgeOnName;

		internal static ConfigEntry<string> ExtraNames;

		internal static ConfigEntry<bool> FlashTaskbar;

		internal static ConfigEntry<string> Sound;

		internal static ConfigEntry<float> Volume;

		internal static ConfigEntry<bool> WelcomeBack;

		internal static ConfigEntry<float> TextScale;

		internal static ConfigEntry<bool> DebugLog;

		private Nudge _nudge;

		private void Awake()
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Expected O, but got Unknown
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Expected O, but got Unknown
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Expected O, but got Unknown
			//IL_027f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0289: Expected O, but got Unknown
			//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bd: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			ConfigFolder = Paths.ConfigPath;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master switch.");
			Cooldown = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Cooldown", 20f, new ConfigDescription("Seconds between knocks of the same kind, 5 to 300. While the trouble lasts (hits still landing, an awake enemy still close, a raid still on), Odin knocks again every Cooldown seconds.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 300f), Array.Empty<object>()));
			NudgeOnDamage = ((BaseUnityPlugin)this).Config.Bind<bool>("Nudges", "NudgeOnDamage", true, "Nudge when you take damage while Valheim is in the background.");
			MinDamage = ((BaseUnityPlugin)this).Config.Bind<float>("Nudges", "MinDamage", 1f, new ConfigDescription("Smallest hit, after armour, that counts, 0 to 100.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			NudgeOnEnemy = ((BaseUnityPlugin)this).Config.Bind<bool>("Nudges", "NudgeOnEnemy", true, "Nudge when an enemy within EnemyRadius is awake: alerted, hunting or after something. Animals only count while they're attacking. Tamed creatures never do.");
			EnemyRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Nudges", "EnemyRadius", 20f, new ConfigDescription("How close to you, in metres, an enemy has to be, 5 to 60.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 60f), Array.Empty<object>()));
			NudgeOnRaid = ((BaseUnityPlugin)this).Config.Bind<bool>("Nudges", "NudgeOnRaid", true, "Nudge when a raid or other event starts around you.");
			NudgeOnDeath = ((BaseUnityPlugin)this).Config.Bind<bool>("Nudges", "NudgeOnDeath", true, "Nudge when you die.");
			NudgeOnName = ((BaseUnityPlugin)this).Config.Bind<bool>("Nudges", "NudgeOnName", true, "Nudge when someone else's chat line has your character's name in it, as a whole word. Your own lines never count.");
			ExtraNames = ((BaseUnityPlugin)this).Config.Bind<string>("Nudges", "ExtraNames", "", "Other names to listen for in chat, separated by commas (a nickname, your Steam name). Empty = just your character's name.");
			FlashTaskbar = ((BaseUnityPlugin)this).Config.Bind<bool>("Sound", "FlashTaskbar", true, "Flash Valheim's taskbar button until you click back in. Windows only.");
			Sound = ((BaseUnityPlugin)this).Config.Bind<string>("Sound", "Sound", "knock", "What plays with a nudge: knock, horn, none, or the name of a .wav file in the BepInEx config folder (16-bit or float PCM).");
			Volume = ((BaseUnityPlugin)this).Config.Bind<float>("Sound", "Volume", 0.8f, new ConfigDescription("How loud the sound is, 0 to 1. The game's master volume still applies.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			WelcomeBack = ((BaseUnityPlugin)this).Config.Bind<bool>("Messages", "WelcomeBack", true, "When you click back into Valheim, Odin says in one line what happened while you were away. Nothing, if nothing did.");
			TextScale = ((BaseUnityPlugin)this).Config.Bind<float>("Messages", "TextScale", 1f, new ConfigDescription("Size of Odin's lines, 0.5 to 2. 1 = sized to match the game's own HUD, which already follows your screen height and the game's GUI scale setting.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2f), Array.Empty<object>()));
			DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugLog", false, "Write each thing Odin notices while you're away, and each nudge or why there wasn't one, to the BepInEx log.");
			Patcher = new Harmony("hevyh2o.valheim.odinsnudge");
			Patcher.PatchAll(typeof(Plugin).Assembly);
			_nudge = ((Component)this).gameObject.AddComponent<Nudge>();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Odin's Nudge 0.1.1 loaded. Odin will tap your shoulder.");
		}

		private void OnDestroy()
		{
			Harmony patcher = Patcher;
			if (patcher != null)
			{
				patcher.UnpatchSelf();
			}
			if ((Object)(object)_nudge != (Object)null)
			{
				Object.Destroy((Object)(object)_nudge);
			}
		}
	}
	internal static class Sounds
	{
		private const int Rate = 44100;

		private static string _loadedFor;

		private static AudioClip _clip;

		public static AudioClip Current()
		{
			string text = (Plugin.Sound.Value ?? "").Trim();
			if (text == _loadedFor)
			{
				return _clip;
			}
			_loadedFor = text;
			_clip = null;
			if (text.Length == 0 || text.Equals("none", StringComparison.OrdinalIgnoreCase))
			{
				return null;
			}
			if (text.Equals("knock", StringComparison.OrdinalIgnoreCase))
			{
				_clip = Make("OdinsNudge_Knock", Knock());
			}
			else if (text.Equals("horn", StringComparison.OrdinalIgnoreCase))
			{
				_clip = Make("OdinsNudge_Horn", Horn());
			}
			else
			{
				_clip = FromFile(text);
			}
			return _clip;
		}

		private static float[] Knock()
		{
			float[] array = new float[27342];
			Random random = new Random(9);
			float[] array2 = new float[3] { 0f, 0.16f, 0.32f };
			float[] array3 = new float[3] { 1f, 0.85f, 0.95f };
			int num = 4851;
			for (int i = 0; i < array2.Length; i++)
			{
				int num2 = (int)(array2[i] * 44100f);
				for (int j = 0; j < num && num2 + j < array.Length; j++)
				{
					float num3 = (float)j / 44100f;
					float num4 = Mathf.Sin((float)Math.PI * 280f * num3) * 0.75f + Mathf.Sin((float)Math.PI * 580f * num3) * 0.3f;
					float num5 = (float)(random.NextDouble() * 2.0 - 1.0) * Mathf.Exp((0f - num3) * 450f) * 0.5f;
					array[num2 + j] += (num4 * Mathf.Exp((0f - num3) * 45f) + num5) * array3[i];
				}
			}
			return Normalise(array);
		}

		private static float[] Horn()
		{
			float num = 1.4f;
			float[] array = new float[(int)(44100f * num)];
			float[] array2 = new float[5] { 1f, 0.5f, 0.32f, 0.18f, 0.08f };
			for (int i = 0; i < array.Length; i++)
			{
				float num2 = (float)i / 44100f;
				float num3 = 98f * (1f + 0.004f * Mathf.Sin((float)Math.PI * 10f * num2));
				float num4 = 0f;
				for (int j = 0; j < array2.Length; j++)
				{
					num4 += Mathf.Sin((float)Math.PI * 2f * num3 * (float)(j + 1) * num2) * array2[j];
				}
				float num5 = Mathf.Clamp01(num2 / 0.18f) * Mathf.Clamp01((num - num2) / 0.55f);
				array[i] = num4 * num5;
			}
			return Normalise(array);
		}

		private static float[] Normalise(float[] data)
		{
			float num = 0f;
			foreach (float num2 in data)
			{
				num = Mathf.Max(num, Mathf.Abs(num2));
			}
			if (num > 0f)
			{
				for (int j = 0; j < data.Length; j++)
				{
					data[j] = data[j] / num * 0.9f;
				}
			}
			return data;
		}

		private static AudioClip Make(string name, float[] data, int channels = 1, int rate = 44100)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			int at = 0;
			return AudioClip.Create(name, data.Length / channels, channels, rate, false, (PCMReaderCallback)delegate(float[] buffer)
			{
				int num = Math.Max(0, Math.Min(buffer.Length, data.Length - at));
				Array.Copy(data, at, buffer, 0, num);
				Array.Clear(buffer, num, buffer.Length - num);
				at += num;
			});
		}

		private static AudioClip FromFile(string name)
		{
			string text = Path.Combine(Plugin.ConfigFolder ?? "", name);
			if (!text.EndsWith(".wav", StringComparison.OrdinalIgnoreCase))
			{
				text += ".wav";
			}
			try
			{
				if (!File.Exists(text))
				{
					Plugin.Log.LogWarning((object)("Sound \"" + name + "\" isn't knock, horn or none, and there's no " + text + ". Odin will flash without a sound."));
					return null;
				}
				byte[] array = File.ReadAllBytes(text);
				if (array.Length < 12 || Tag(array, 0) != "RIFF" || Tag(array, 8) != "WAVE")
				{
					throw new InvalidDataException("not a WAV file");
				}
				int num = 0;
				int num2 = 0;
				int num3 = 0;
				int num4 = 0;
				int num5 = -1;
				int num6 = 0;
				int num7 = 12;
				while (num7 + 8 <= array.Length)
				{
					string text2 = Tag(array, num7);
					int num8 = BitConverter.ToInt32(array, num7 + 4);
					int num9 = num7 + 8;
					if (text2 == "fmt " && num8 >= 16)
					{
						num = BitConverter.ToUInt16(array, num9);
						num2 = BitConverter.ToUInt16(array, num9 + 2);
						num3 = BitConverter.ToInt32(array, num9 + 4);
						num4 = BitConverter.ToUInt16(array, num9 + 14);
						if (num == 65534 && num8 >= 26)
						{
							num = BitConverter.ToUInt16(array, num9 + 24);
						}
					}
					else if (text2 == "data")
					{
						num5 = num9;
						num6 = Math.Min(num8, array.Length - num9);
						break;
					}
					num7 = num9 + num8 + (num8 & 1);
				}
				if (num5 < 0 || num2 < 1 || num2 > 2 || num3 <= 0)
				{
					throw new InvalidDataException("no audio in it");
				}
				int num10 = num4 / 8;
				if (num10 < 1 || ((num != 1 || (num4 != 8 && num4 != 16 && num4 != 24)) && (num != 3 || num4 != 32)))
				{
					throw new InvalidDataException($"format {num}, {num4}-bit isn't supported");
				}
				float[] array2 = new float[num6 / num10 / num2 * num2];
				for (int i = 0; i < array2.Length; i++)
				{
					int num11 = num5 + i * num10;
					switch (num4)
					{
					case 8:
						array2[i] = (float)(array[num11] - 128) / 128f;
						break;
					case 16:
						array2[i] = (float)BitConverter.ToInt16(array, num11) / 32768f;
						break;
					case 24:
						array2[i] = (float)(array[num11] | (array[num11 + 1] << 8) | ((sbyte)array[num11 + 2] << 16)) / 8388608f;
						break;
					default:
						array2[i] = BitConverter.ToSingle(array, num11);
						break;
					}
				}
				Plugin.Log.LogInfo((object)("Nudge sound: " + Path.GetFileName(text) + "."));
				return Make("OdinsNudge_" + Path.GetFileNameWithoutExtension(text), array2, num2, num3);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Couldn't read " + text + " (" + ex.Message + "). Odin will flash without a sound."));
				return null;
			}
		}

		private static string Tag(byte[] b, int at)
		{
			if (at + 4 > b.Length)
			{
				return "";
			}
			return Encoding.ASCII.GetString(b, at, 4);
		}
	}
	internal static class Taskbar
	{
		private struct FlashInfo
		{
			public uint cbSize;

			public IntPtr hwnd;

			public uint dwFlags;

			public uint uCount;

			public uint dwTimeout;
		}

		private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

		private const uint FlashStop = 0u;

		private const uint FlashAll = 3u;

		private const uint FlashUntilFront = 12u;

		private const string UnityClass = "UnityWndClass";

		private static IntPtr _window = IntPtr.Zero;

		private static bool _broken;

		private static uint _pid;

		private static IntPtr _anyVisible;

		private static readonly EnumWindowsProc Visit = OnWindow;

		private static bool Supported
		{
			get
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				//IL_000d: Invalid comparison between Unknown and I4
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0015: Invalid comparison between Unknown and I4
				if (!_broken)
				{
					if ((int)Application.platform != 2)
					{
						return (int)Application.platform == 7;
					}
					return true;
				}
				return false;
			}
		}

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool FlashWindowEx(ref FlashInfo info);

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool EnumWindows(EnumWindowsProc callback, IntPtr lParam);

		[DllImport("user32.dll")]
		private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool IsWindowVisible(IntPtr hWnd);

		[DllImport("user32.dll", CharSet = CharSet.Unicode)]
		private static extern int GetClassName(IntPtr hWnd, StringBuilder className, int maxCount);

		[DllImport("kernel32.dll")]
		private static extern uint GetCurrentProcessId();

		public static bool Flash()
		{
			if (!Supported || !FindWindow())
			{
				return false;
			}
			try
			{
				FlashInfo info = Info(15u);
				FlashWindowEx(ref info);
				return true;
			}
			catch (Exception e)
			{
				Give(e);
				return false;
			}
		}

		public static void Stop()
		{
			if (!Supported || _window == IntPtr.Zero)
			{
				return;
			}
			try
			{
				FlashInfo info = Info(0u);
				FlashWindowEx(ref info);
			}
			catch (Exception e)
			{
				Give(e);
			}
		}

		private static FlashInfo Info(uint flags)
		{
			return new FlashInfo
			{
				cbSize = (uint)Marshal.SizeOf(typeof(FlashInfo)),
				hwnd = _window,
				dwFlags = flags,
				uCount = uint.MaxValue,
				dwTimeout = 0u
			};
		}

		private static bool FindWindow()
		{
			if (_window != IntPtr.Zero)
			{
				return true;
			}
			try
			{
				_pid = GetCurrentProcessId();
				_anyVisible = IntPtr.Zero;
				EnumWindows(Visit, IntPtr.Zero);
				if (_window == IntPtr.Zero)
				{
					_window = _anyVisible;
				}
				if (_window == IntPtr.Zero)
				{
					Plugin.Log.LogInfo((object)"Odin couldn't find Valheim's window. No taskbar flash; the sound still plays.");
					_broken = true;
					return false;
				}
				return true;
			}
			catch (Exception e)
			{
				Give(e);
				return false;
			}
		}

		private static bool OnWindow(IntPtr hWnd, IntPtr lParam)
		{
			GetWindowThreadProcessId(hWnd, out var processId);
			if (processId != _pid || !IsWindowVisible(hWnd))
			{
				return true;
			}
			StringBuilder stringBuilder = new StringBuilder(64);
			GetClassName(hWnd, stringBuilder, stringBuilder.Capacity);
			if (stringBuilder.ToString() == "UnityWndClass")
			{
				_window = hWnd;
				return false;
			}
			if (_anyVisible == IntPtr.Zero)
			{
				_anyVisible = hWnd;
			}
			return true;
		}

		private static void Give(Exception e)
		{
			_broken = true;
			Plugin.Log.LogInfo((object)("Taskbar flash unavailable (" + e.GetType().Name + "). Odin will knock instead."));
		}
	}
	internal static class BuildInfo
	{
		public const string Version = "0.1.1";
	}
}