Decompiled source of Earshot v0.1.0

plugins/Earshot/Earshot.dll

Decompiled 4 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Earshot.Core;
using Earshot.Core.Model;
using Earshot.UI;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Valheim.SettingsGui;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Earshot")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Closed captions for Valheim's important sounds, with a live direction arrow.")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+fdd049e1daee087f81721e57cdd4e79a25f54800")]
[assembly: AssemblyProduct("Earshot")]
[assembly: AssemblyTitle("Earshot")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.0.0")]
[module: UnverifiableCode]
[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;
		}
	}
}
internal sealed class ConfigurationManagerAttributes
{
	public int? Order;

	public bool? IsAdvanced;

	public bool? Browsable;

	public bool? ReadOnly;
}
namespace Earshot
{
	[BepInPlugin("com.jumpingmushroom.earshot", "Earshot", "0.1.0")]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim.x86_64")]
	public sealed class EarshotPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.jumpingmushroom.earshot";

		public const string PluginName = "Earshot";

		public const string PluginVersion = "0.1.0";

		internal static ManualLogSource Log;

		private static readonly HashSet<string> Warned = new HashSet<string>();

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			PluginConfig.Bind(((BaseUnityPlugin)this).Config);
			EarshotConsole.Register();
			_harmony = new Harmony("com.jumpingmushroom.earshot");
			_harmony.PatchAll(typeof(EarshotPlugin).Assembly);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Earshot 0.1.0 loaded.");
		}

		private void Update()
		{
			try
			{
				Runtime.Tick();
			}
			catch (Exception e)
			{
				WarnOnce("Runtime.Tick", e);
			}
		}

		private void OnDestroy()
		{
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}

		internal static void WarnOnce(string key, Exception e)
		{
			if (Warned.Add(key))
			{
				Log.LogWarning((object)(key + ": " + e));
			}
		}
	}
	public static class PluginConfig
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<float> MinimumVolume;

		public static ConfigEntry<int> MaxLines;

		public static ConfigEntry<float> Linger;

		public static ConfigEntry<float> IdleCooldown;

		public static ConfigEntry<float> OffsetX;

		public static ConfigEntry<float> OffsetY;

		public static ConfigEntry<float> Scale;

		public static ConfigEntry<float> BackgroundOpacity;

		public static ConfigEntry<bool> DimOnScreen;

		public static ConfigEntry<bool> SnapArrows;

		public static ConfigEntry<float> NearDistance;

		public static ConfigEntry<bool> LogUnlabelled;

		public static ConfigEntry<bool> Verbose;

		private static readonly Dictionary<Category, ConfigEntry<bool>> CategoryEntries = new Dictionary<Category, ConfigEntry<bool>>();

		private static readonly Dictionary<Category, ConfigEntry<Color>> ColorEntries = new Dictionary<Category, ConfigEntry<Color>>();

		private static ConfigurationManagerAttributes Attr(int order, bool advanced = false)
		{
			return new ConfigurationManagerAttributes
			{
				Order = order,
				IsAdvanced = advanced
			};
		}

		private static Color Hex(string hex)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			Color result = default(Color);
			ColorUtility.TryParseHtmlString(hex, ref result);
			return result;
		}

		public static void Bind(ConfigFile cfg)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Expected O, but got Unknown
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Expected O, but got Unknown
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Expected O, but got Unknown
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Expected O, but got Unknown
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Expected O, but got Unknown
			//IL_020a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0214: Expected O, but got Unknown
			//IL_024e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0258: Expected O, but got Unknown
			//IL_0292: Unknown result type (might be due to invalid IL or missing references)
			//IL_029c: Expected O, but got Unknown
			//IL_02c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ce: Expected O, but got Unknown
			//IL_02f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0300: Expected O, but got Unknown
			//IL_033a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0344: Expected O, but got Unknown
			//IL_03c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ca: Expected O, but got Unknown
			//IL_03f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fc: Expected O, but got Unknown
			Enabled = cfg.Bind<bool>("General", "Enabled", true, new ConfigDescription("Master switch. The same switch as 'Closed captions' in Settings > Accessibility.", (AcceptableValueBase)null, new object[1] { Attr(100) }));
			MinimumVolume = cfg.Bind<float>("General", "MinimumVolume", 0.05f, new ConfigDescription("How loud a sound must be where you stand to get a caption (0 to 1). 0.05 covers most of a sound's range. Ignores your volume sliders, so captions work with sound off.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[1] { Attr(90) }));
			BindCategory(cfg, Category.Boss, on: true, "Bosses: their alerts, attacks and summons.", 80);
			BindCategory(cfg, Category.Raid, on: true, "A raid starting and running near you.", 79);
			BindCategory(cfg, Category.Enemy, on: true, "Hostile creatures: alerted, attacking, footsteps.", 78);
			BindCategory(cfg, Category.Wildlife, on: true, "Idle creature sounds, throttled so chatter doesn't flood the list.", 77);
			BindCategory(cfg, Category.World, on: true, "Things that need attention: a smelter finishing, food done or burning, doors you didn't open, a shield generator low on fuel.", 76);
			BindCategory(cfg, Category.Ambient, on: false, "Constant background: fires, torches, distant thunder.", 75);
			MaxLines = cfg.Bind<int>("Display", "MaxLines", 5, new ConfigDescription("Caption lines shown at once.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), new object[1] { Attr(70) }));
			Linger = cfg.Bind<float>("Display", "Linger", 3f, new ConfigDescription("Seconds a line stays after its sound was last heard.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), new object[1] { Attr(69) }));
			IdleCooldown = cfg.Bind<float>("Display", "IdleCooldown", 20f, new ConfigDescription("Seconds before the same creature's idle chatter can start a new line.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 120f), new object[1] { Attr(68) }));
			OffsetX = cfg.Bind<float>("Display", "OffsetX", 0f, new ConfigDescription("Horizontal nudge from bottom centre, in HUD pixels (positive is right).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1500f, 1500f), new object[1] { Attr(67) }));
			OffsetY = cfg.Bind<float>("Display", "OffsetY", 0f, new ConfigDescription("Vertical nudge in HUD pixels (positive is up).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-200f, 1000f), new object[1] { Attr(66) }));
			Scale = cfg.Bind<float>("Display", "Scale", 1f, new ConfigDescription("Size of the caption list.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2.5f), new object[1] { Attr(65) }));
			BackgroundOpacity = cfg.Bind<float>("Display", "BackgroundOpacity", 0.6f, new ConfigDescription("Opacity of the dark plate behind the captions.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[1] { Attr(64) }));
			DimOnScreen = cfg.Bind<bool>("Display", "DimOnScreen", true, new ConfigDescription("Dim lines whose source is close and on screen, and rank them lowest within their category.", (AcceptableValueBase)null, new object[1] { Attr(63) }));
			SnapArrows = cfg.Bind<bool>("Display", "SnapArrows", true, new ConfigDescription("Arrows point in 8 directions. Off: they rotate smoothly.", (AcceptableValueBase)null, new object[1] { Attr(62) }));
			NearDistance = cfg.Bind<float>("Display", "NearDistance", 10f, new ConfigDescription("Metres within which threats get the 'near' tag.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), new object[1] { Attr(61) }));
			BindColor(cfg, Category.Boss, "#E056FF", 50);
			BindColor(cfg, Category.Raid, "#FFD84A", 49);
			BindColor(cfg, Category.Enemy, "#FF8A3D", 48);
			BindColor(cfg, Category.Wildlife, "#7EC8FF", 47);
			BindColor(cfg, Category.World, "#E8E4DA", 46);
			BindColor(cfg, Category.Ambient, "#B5B0A6", 45);
			LogUnlabelled = cfg.Bind<bool>("Debug", "LogUnlabelled", false, new ConfigDescription("Log each audible sound that got no caption, once, so gaps can be reported.", (AcceptableValueBase)null, new object[1] { Attr(10, advanced: true) }));
			Verbose = cfg.Bind<bool>("Debug", "Verbose", false, new ConfigDescription("Log every caption decision. Noisy.", (AcceptableValueBase)null, new object[1] { Attr(9, advanced: true) }));
		}

		private static void BindCategory(ConfigFile cfg, Category c, bool on, string description, int order)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			CategoryEntries[c] = cfg.Bind<bool>("Categories", c.ToString(), on, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { Attr(order) }));
		}

		private static void BindColor(ConfigFile cfg, Category c, string hex, int order)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			ColorEntries[c] = cfg.Bind<Color>("Colors", c.ToString(), Hex(hex), new ConfigDescription("Caption colour for " + c.ToString() + ".", (AcceptableValueBase)null, new object[1] { Attr(order) }));
		}

		public static bool CategoryOn(Category c)
		{
			if (c != Category.Self && CategoryEntries.TryGetValue(c, out var value))
			{
				return value.Value;
			}
			return false;
		}

		public static Color ColorFor(Category c)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (!ColorEntries.TryGetValue(c, out var value))
			{
				return Color.white;
			}
			return value.Value;
		}

		public static void CopyTo(BoardSettings s)
		{
			s.MaxLines = MaxLines.Value;
			s.Linger = Linger.Value;
			s.IdleCooldown = IdleCooldown.Value;
		}
	}
}
namespace Earshot.UI
{
	internal sealed class CaptionHud : MonoBehaviour
	{
		private sealed class Row
		{
			public RectTransform Rect;

			public Image Warn;

			public Image Arrow;

			public TextMeshProUGUI Text;

			public TextMeshProUGUI Near;

			public string Source;

			public int Count;

			public string Action;

			public bool Bold;

			public float TextWidth;

			public string NearText;

			public float NearWidth;
		}

		private const int PoolSize = 10;

		private const float LineHeight = 30f;

		private const float Pad = 10f;

		private const float Icon = 20f;

		private const float TextX = 52f;

		private const float BaseY = 150f;

		private static CaptionHud _instance;

		private readonly List<Row> _rows = new List<Row>();

		private RectTransform _root;

		private Image _plate;

		private CanvasGroup _group;

		public static void Ensure()
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_instance != (Object)null))
			{
				Hud instance = Hud.instance;
				if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.m_rootObject == (Object)null) && !((Object)(object)UiUtil.Font == (Object)null))
				{
					RectTransform val = UiUtil.Rect("EarshotCaptions", instance.m_rootObject.transform);
					val.anchorMin = new Vector2(0.5f, 0f);
					val.anchorMax = new Vector2(0.5f, 0f);
					val.pivot = new Vector2(0.5f, 0f);
					_instance = ((Component)val).gameObject.AddComponent<CaptionHud>();
					_instance.Build(val);
				}
			}
		}

		private void Build(RectTransform root)
		{
			_root = root;
			_group = ((Component)root).gameObject.AddComponent<CanvasGroup>();
			_group.interactable = false;
			_group.blocksRaycasts = false;
			_group.alpha = 0f;
			_plate = ((Component)root).gameObject.AddComponent<Image>();
			_plate.sprite = Sprites.Plate;
			_plate.type = (Type)1;
			((Graphic)_plate).raycastTarget = false;
			for (int i = 0; i < 10; i++)
			{
				_rows.Add(MakeRow(i));
			}
		}

		private Row MakeRow(int i)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: 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_0039: 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)
			RectTransform val = UiUtil.Rect("Line" + i, (Transform)(object)_root);
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(0f, 0f);
			val.pivot = val2;
			Vector2 anchorMin = (val.anchorMax = val2);
			val.anchorMin = anchorMin;
			Row obj = new Row
			{
				Rect = val,
				Warn = MakeIcon(val, "Warn", Sprites.Warning, 10f),
				Arrow = MakeIcon(val, "Arrow", Sprites.Arrow, 34f),
				Text = UiUtil.Text((Transform)(object)val, "Text", 22f)
			};
			Place(((TMP_Text)obj.Text).rectTransform, 52f);
			obj.Near = UiUtil.Text((Transform)(object)val, "Near", 16f);
			Place(((TMP_Text)obj.Near).rectTransform, 0f);
			((Component)val).gameObject.SetActive(false);
			return obj;
		}

		private static Image MakeIcon(RectTransform parent, string name, Sprite sprite, float centreX)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: 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_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			RectTransform obj = UiUtil.Rect(name, (Transform)(object)parent);
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(0f, 0.5f);
			obj.anchorMax = val;
			obj.anchorMin = val;
			obj.pivot = new Vector2(0.5f, 0.5f);
			obj.sizeDelta = new Vector2(20f, 20f);
			obj.anchoredPosition = new Vector2(centreX, 0f);
			Image obj2 = ((Component)obj).gameObject.AddComponent<Image>();
			obj2.sprite = sprite;
			((Graphic)obj2).raycastTarget = false;
			return obj2;
		}

		private static void Place(RectTransform rt, float x)
		{
			//IL_0013: 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_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(0f, 0.5f);
			rt.anchorMax = val;
			rt.anchorMin = val;
			rt.pivot = new Vector2(0f, 0.5f);
			rt.anchoredPosition = new Vector2(x, 0f);
			rt.sizeDelta = new Vector2(10f, 30f);
		}

		private void LateUpdate()
		{
			try
			{
				Draw();
			}
			catch (Exception e)
			{
				EarshotPlugin.WarnOnce("CaptionHud", e);
				_group.alpha = 0f;
			}
		}

		private void Draw()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0473: Unknown result type (might be due to invalid IL or missing references)
			//IL_049d: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_04bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_04e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: 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_024b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_027c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0282: Unknown result type (might be due to invalid IL or missing references)
			//IL_0288: Unknown result type (might be due to invalid IL or missing references)
			//IL_028e: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0429: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_038b: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b2: Unknown result type (might be due to invalid IL or missing references)
			CaptionBoard board = Runtime.Board;
			if (!PluginConfig.Enabled.Value || board.Lines.Count == 0 || Hud.IsUserHidden())
			{
				_group.alpha = 0f;
				return;
			}
			float time = Time.time;
			Vector3 val = WorldQuery.CameraForward();
			Vector3 val2 = WorldQuery.ListenerPosition();
			int num = Math.Min(board.Lines.Count, _rows.Count);
			float num2 = 0f;
			for (int i = 0; i < _rows.Count; i++)
			{
				Row row = _rows[i];
				if (i >= num)
				{
					if (((Component)row.Rect).gameObject.activeSelf)
					{
						((Component)row.Rect).gameObject.SetActive(false);
					}
					continue;
				}
				CaptionLine captionLine = board.Lines[i];
				if (!((Component)row.Rect).gameObject.activeSelf)
				{
					((Component)row.Rect).gameObject.SetActive(true);
				}
				bool flag = Categories.IsThreat(captionLine.Category);
				if (row.Source != captionLine.Source || row.Count != captionLine.Count || row.Action != captionLine.Action || row.Bold != flag)
				{
					string text = Runtime.Tr.Format(LineStyle.SourceWithCount(captionLine.Source, captionLine.Count), captionLine.Action);
					((TMP_Text)row.Text).text = text;
					((TMP_Text)row.Text).fontStyle = (FontStyles)(flag ? 1 : 0);
					row.TextWidth = ((TMP_Text)row.Text).GetPreferredValues(text).x;
					((TMP_Text)row.Text).rectTransform.sizeDelta = new Vector2(row.TextWidth, 30f);
					row.Source = captionLine.Source;
					row.Count = captionLine.Count;
					row.Action = captionLine.Action;
					row.Bold = flag;
				}
				Color color = PluginConfig.ColorFor(captionLine.Category);
				color.a = LineStyle.Opacity(captionLine.Distance, captionLine.MaxDistance, captionLine.OnScreen) * captionLine.Fade(time, board.Settings);
				((Graphic)row.Text).color = color;
				((Graphic)row.Arrow).color = color;
				((Graphic)row.Warn).color = color;
				((Graphic)row.Near).color = color;
				((Behaviour)row.Warn).enabled = flag;
				float degrees = Bearing.Degrees(val.x, val.z, val2.x, val2.z, captionLine.X, captionLine.Z);
				((Transform)((Graphic)row.Arrow).rectTransform).localEulerAngles = new Vector3(0f, 0f, 0f - Bearing.ArrowDegrees(degrees, PluginConfig.SnapArrows.Value));
				float num3 = 52f + row.TextWidth;
				bool flag2 = LineStyle.ShowNear(captionLine.Category, captionLine.Distance, PluginConfig.NearDistance.Value, captionLine.NearFlag);
				if (((Component)row.Near).gameObject.activeSelf != flag2)
				{
					((Component)row.Near).gameObject.SetActive(flag2);
				}
				if (flag2)
				{
					string text2 = Runtime.Tr.Get("near") ?? "near";
					if (row.NearText != text2)
					{
						((TMP_Text)row.Near).text = text2;
						row.NearText = text2;
						row.NearWidth = ((TMP_Text)row.Near).GetPreferredValues(text2).x;
						((TMP_Text)row.Near).rectTransform.sizeDelta = new Vector2(row.NearWidth, 30f);
					}
					((TMP_Text)row.Near).rectTransform.anchoredPosition = new Vector2(num3 + 8f, 0f);
					num3 += 8f + row.NearWidth;
				}
				row.Rect.sizeDelta = new Vector2(num3, 30f);
				row.Rect.anchoredPosition = new Vector2(10f, 10f + (float)(num - 1 - i) * 30f);
				num2 = Math.Max(num2, num3);
			}
			_root.sizeDelta = new Vector2(num2 + 20f, (float)num * 30f + 20f);
			_root.anchoredPosition = new Vector2(PluginConfig.OffsetX.Value, 150f + PluginConfig.OffsetY.Value);
			((Transform)_root).localScale = Vector3.one * PluginConfig.Scale.Value;
			((Graphic)_plate).color = new Color(0f, 0f, 0f, PluginConfig.BackgroundOpacity.Value);
			_group.alpha = 1f;
		}
	}
	internal static class Sprites
	{
		private static Sprite _arrow;

		private static Sprite _warning;

		private static Sprite _plate;

		private static readonly Vector2[] ArrowHead = (Vector2[])(object)new Vector2[3]
		{
			new Vector2(16f, 31f),
			new Vector2(28f, 17f),
			new Vector2(4f, 17f)
		};

		private static readonly Vector2[] TriangleShape = (Vector2[])(object)new Vector2[3]
		{
			new Vector2(16f, 30f),
			new Vector2(31f, 2f),
			new Vector2(1f, 2f)
		};

		public static Sprite Arrow => _arrow ?? (_arrow = Make(32, ArrowPixel, Vector4.zero));

		public static Sprite Warning => _warning ?? (_warning = Make(32, WarningPixel, Vector4.zero));

		public static Sprite Plate => _plate ?? (_plate = Make(32, PlatePixel, new Vector4(12f, 12f, 12f, 12f)));

		private static bool WarningPixel(float x, float y)
		{
			if (!Inside(x, y, TriangleShape))
			{
				return false;
			}
			bool num = x >= 14.5f && x <= 17.5f && y >= 12f && y <= 23f;
			bool flag = x >= 14.5f && x <= 17.5f && y >= 6f && y <= 9f;
			return !(num || flag);
		}

		private static bool ArrowPixel(float x, float y)
		{
			if (Inside(x, y, ArrowHead))
			{
				return true;
			}
			if (x >= 12.5f && x <= 19.5f && y >= 2f)
			{
				return y <= 18f;
			}
			return false;
		}

		private static bool PlatePixel(float x, float y)
		{
			float num = Math.Max(Math.Max(10f - x, 0f), x - 22f);
			float num2 = Math.Max(Math.Max(10f - y, 0f), y - 22f);
			return num * num + num2 * num2 <= 100f;
		}

		private static bool Inside(float x, float y, Vector2[] poly)
		{
			bool flag = false;
			int num = 0;
			int num2 = poly.Length - 1;
			while (num < poly.Length)
			{
				if (poly[num].y > y != poly[num2].y > y && x < (poly[num2].x - poly[num].x) * (y - poly[num].y) / (poly[num2].y - poly[num].y) + poly[num].x)
				{
					flag = !flag;
				}
				num2 = num++;
			}
			return flag;
		}

		private static Sprite Make(int size, Func<float, float, bool> inside, Vector4 border)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: 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_00ad: 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)
			Texture2D val = new Texture2D(size, size, (TextureFormat)4, false)
			{
				wrapMode = (TextureWrapMode)1,
				filterMode = (FilterMode)1,
				hideFlags = (HideFlags)61
			};
			Color32[] array = (Color32[])(object)new Color32[size * size];
			for (int i = 0; i < size; i++)
			{
				for (int j = 0; j < size; j++)
				{
					int num = 0;
					for (int k = 0; k < 4; k++)
					{
						for (int l = 0; l < 4; l++)
						{
							if (inside((float)j + ((float)l + 0.5f) / 4f, (float)i + ((float)k + 0.5f) / 4f))
							{
								num++;
							}
						}
					}
					array[i * size + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)(255 * num / 16));
				}
			}
			val.SetPixels32(array);
			val.Apply();
			Sprite obj = Sprite.Create(val, new Rect(0f, 0f, (float)size, (float)size), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0, border);
			((Object)obj).hideFlags = (HideFlags)61;
			return obj;
		}
	}
	internal static class UiUtil
	{
		private static Material _outlined;

		private static TMP_FontAsset _outlinedFor;

		public static TMP_FontAsset Font
		{
			get
			{
				Hud instance = Hud.instance;
				if (!((Object)(object)instance != (Object)null) || !((Object)(object)instance.m_healthText != (Object)null))
				{
					return null;
				}
				return instance.m_healthText.font;
			}
		}

		public static RectTransform Rect(string name, Transform parent)
		{
			//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_002f: 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_003d: Expected O, but got Unknown
			RectTransform val = (RectTransform)new GameObject(name, new Type[1] { typeof(RectTransform) })
			{
				layer = ((Component)parent).gameObject.layer
			}.transform;
			((Transform)val).SetParent(parent, false);
			return val;
		}

		private static Material Outlined(TMP_FontAsset font)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_outlined != (Object)null && (Object)(object)_outlinedFor == (Object)(object)font)
			{
				return _outlined;
			}
			_outlined = new Material(((TMP_Asset)font).material);
			_outlined.EnableKeyword(ShaderUtilities.Keyword_Outline);
			_outlined.SetFloat(ShaderUtilities.ID_OutlineWidth, 0.25f);
			_outlined.SetColor(ShaderUtilities.ID_OutlineColor, new Color(0f, 0f, 0f, 1f));
			((Object)_outlined).hideFlags = (HideFlags)61;
			_outlinedFor = font;
			return _outlined;
		}

		public static TextMeshProUGUI Text(Transform parent, string name, float size)
		{
			RectTransform obj = Rect(name, parent);
			((Component)obj).gameObject.SetActive(false);
			TextMeshProUGUI val = ((Component)obj).gameObject.AddComponent<TextMeshProUGUI>();
			TMP_FontAsset font = Font;
			if ((Object)(object)font != (Object)null)
			{
				((TMP_Text)val).font = font;
				((TMP_Text)val).fontSharedMaterial = Outlined(font);
			}
			((Component)obj).gameObject.SetActive(true);
			((TMP_Text)val).fontSize = size;
			((TMP_Text)val).alignment = (TextAlignmentOptions)4097;
			((TMP_Text)val).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)val).overflowMode = (TextOverflowModes)0;
			((Graphic)val).raycastTarget = false;
			return val;
		}
	}
}
namespace Earshot.Patches
{
	[HarmonyPatch(typeof(AccessibilitySettings))]
	internal static class AccessibilitySettingsPatch
	{
		[HarmonyPostfix]
		[HarmonyPatch("Initialize")]
		private static void AfterInitialize(AccessibilitySettings __instance)
		{
			try
			{
				Toggle closedCaptionsToggle = __instance.m_closedCaptionsToggle;
				if ((Object)(object)closedCaptionsToggle == (Object)null)
				{
					return;
				}
				((Component)closedCaptionsToggle).gameObject.SetActive(true);
				closedCaptionsToggle.isOn = PluginConfig.Enabled.Value;
				TMP_Text[] componentsInChildren = ((Component)closedCaptionsToggle).GetComponentsInChildren<TMP_Text>(true);
				foreach (TMP_Text val in componentsInChildren)
				{
					if (!TextCheck.IsClean(val.text))
					{
						val.text = Runtime.Tr.Get("settings_toggle") ?? "Closed captions (Earshot)";
					}
				}
				List<SettingsTooltip> list = new List<SettingsTooltip>();
				Transform parent = ((Component)closedCaptionsToggle).transform.parent;
				if ((Object)(object)parent != (Object)null)
				{
					list.AddRange(((Component)parent).GetComponentsInChildren<SettingsTooltip>(true));
				}
				SettingsTooltip componentInParent = ((Component)closedCaptionsToggle).GetComponentInParent<SettingsTooltip>();
				if ((Object)(object)componentInParent != (Object)null && !list.Contains(componentInParent))
				{
					list.Add(componentInParent);
				}
				List<SettingsTooltip> list2 = new List<SettingsTooltip>();
				foreach (SettingsTooltip item in list)
				{
					if (!((Object)(object)item == (Object)null) && !list2.Contains(item) && (((Component)item).transform.IsChildOf(((Component)closedCaptionsToggle).transform) || ((Component)closedCaptionsToggle).transform.IsChildOf(((Component)item).transform) || (Object)(object)item.m_selectableOverride == (Object)(object)closedCaptionsToggle))
					{
						list2.Add(item);
					}
				}
				int num = 0;
				foreach (SettingsTooltip item2 in list2)
				{
					bool flag = false;
					bool flag2 = TextCheck.IsClean(Localization.instance.Localize(item2.m_topicId ?? ""));
					bool flag3 = TextCheck.IsClean(Localization.instance.Localize(item2.m_textId ?? ""));
					if (!flag2 || !flag3)
					{
						string text = (flag2 ? item2.m_topicId : (Runtime.Tr.Get("settings_toggle") ?? "Closed captions (Earshot)"));
						string text2 = (flag3 ? item2.m_textId : (Runtime.Tr.Get("settings_toggle_descr") ?? "Show a caption for important sounds, with an arrow pointing where each one came from. More options in the mod settings (F1)."));
						item2.SetTexts(text, text2);
						flag = true;
					}
					if (!string.IsNullOrEmpty(item2.m_textIdSwitch) && !TextCheck.IsClean(Localization.instance.Localize(item2.m_textIdSwitch)))
					{
						item2.m_textIdSwitch = "";
						flag = true;
					}
					if (!string.IsNullOrEmpty(item2.m_textIdPlayStation) && !TextCheck.IsClean(Localization.instance.Localize(item2.m_textIdPlayStation)))
					{
						item2.m_textIdPlayStation = "";
						flag = true;
					}
					if (!string.IsNullOrEmpty(item2.m_textIdXbox) && !TextCheck.IsClean(Localization.instance.Localize(item2.m_textIdXbox)))
					{
						item2.m_textIdXbox = "";
						flag = true;
					}
					if (flag)
					{
						num++;
					}
				}
				if (list2.Count == 0)
				{
					EarshotPlugin.Log.LogInfo((object)"Earshot: no settings tooltip found for the Closed captions toggle");
				}
				else
				{
					EarshotPlugin.Log.LogInfo((object)$"Earshot: fixed {num} settings tooltip(s) for the Closed captions toggle");
				}
			}
			catch (Exception e)
			{
				EarshotPlugin.WarnOnce("AccessibilitySettings.Initialize", e);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch("OnOkAsync")]
		private static void AfterOk(AccessibilitySettings __instance)
		{
			try
			{
				if ((Object)(object)__instance.m_closedCaptionsToggle != (Object)null)
				{
					PluginConfig.Enabled.Value = __instance.m_closedCaptionsToggle.isOn;
				}
			}
			catch (Exception e)
			{
				EarshotPlugin.WarnOnce("AccessibilitySettings.OnOkAsync", e);
			}
		}
	}
	[HarmonyPatch(typeof(ZSFX), "Play")]
	internal static class ZsfxPatch
	{
		private static void Postfix(ZSFX __instance)
		{
			try
			{
				SoundCapture.OnPlay(__instance);
			}
			catch (Exception e)
			{
				EarshotPlugin.WarnOnce("ZSFX.Play", e);
			}
		}
	}
}
namespace Earshot.Core
{
	internal static class DemoCaptions
	{
		private struct Sample
		{
			public readonly string SourceKey;

			public readonly string ActionKey;

			public readonly Category Category;

			public readonly float Angle;

			public readonly float Distance;

			public readonly float MaxDistance;

			public readonly int[] SourceIds;

			public Sample(string sourceKey, string actionKey, Category category, float angle, float distance, float maxDistance, params int[] sourceIds)
			{
				SourceKey = sourceKey;
				ActionKey = actionKey;
				Category = category;
				Angle = angle;
				Distance = distance;
				MaxDistance = maxDistance;
				SourceIds = sourceIds;
			}
		}

		private const float TickInterval = 0.5f;

		private static readonly Sample[] Samples = new Sample[5]
		{
			new Sample("$enemy_eikthyr", "alerted", Category.Boss, 0f, 60f, 100f, 9001),
			new Sample("$enemy_greydwarf", "attacking", Category.Enemy, -45f, 6f, 50f, 9002, 9003, 9004),
			new Sample("$enemy_troll", "stomping", Category.Enemy, 90f, 40f, 100f, 9005),
			new Sample("$enemy_deer", "alerted", Category.Wildlife, 180f, 25f, 50f, 9006),
			new Sample("$piece_smelter", "done", Category.World, -100f, 15f, 50f, 9007)
		};

		private static float _until;

		private static float _next;

		private static float _cameraYaw;

		public static void Start(float seconds)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			_until = Time.time + seconds;
			_next = 0f;
			Camera mainCamera = Utils.GetMainCamera();
			_cameraYaw = (((Object)(object)mainCamera != (Object)null) ? ((Component)mainCamera).transform.eulerAngles.y : 0f);
		}

		public static void Tick(float now)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: 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_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			if (now >= _until || now < _next)
			{
				return;
			}
			_next = now + 0.5f;
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			Sample[] samples = Samples;
			for (int i = 0; i < samples.Length; i++)
			{
				Sample sample = samples[i];
				Vector3 val = ((Component)localPlayer).transform.position + Quaternion.Euler(0f, _cameraYaw + sample.Angle, 0f) * Vector3.forward * sample.Distance;
				Label label = new Label
				{
					Source = Localization.instance.Localize(sample.SourceKey),
					Action = Runtime.Tr.Get(sample.ActionKey),
					Category = sample.Category
				};
				float distance = Vector3.Distance(WorldQuery.ListenerPosition(), val);
				int[] sourceIds = sample.SourceIds;
				foreach (int sourceId in sourceIds)
				{
					SoundEvent e = new SoundEvent
					{
						X = val.x,
						Z = val.z,
						Distance = distance,
						MaxDistance = sample.MaxDistance,
						Loudness = 1f,
						OnScreen = false,
						SourceId = sourceId
					};
					Runtime.Board.Offer(e, label, now);
				}
			}
		}
	}
	internal static class EarshotConsole
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__0_0;

			internal void <Register>b__0_0(ConsoleEventArgs args)
			{
				try
				{
					switch ((args.Length > 1) ? args[1].ToLowerInvariant() : "")
					{
					case "unlabelled":
					case "unlabeled":
						Unlabelled(args.Context);
						break;
					case "demo":
						Demo(args.Context);
						break;
					default:
						Recent(args.Context);
						break;
					}
				}
				catch (Exception e)
				{
					EarshotPlugin.WarnOnce("earshot console", e);
				}
			}
		}

		public static void Register()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			object obj = <>c.<>9__0_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					try
					{
						switch ((args.Length > 1) ? args[1].ToLowerInvariant() : "")
						{
						case "unlabelled":
						case "unlabeled":
							Unlabelled(args.Context);
							break;
						case "demo":
							Demo(args.Context);
							break;
						default:
							Recent(args.Context);
							break;
						}
					}
					catch (Exception e)
					{
						EarshotPlugin.WarnOnce("earshot console", e);
					}
				};
				<>c.<>9__0_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("earshot", "Earshot: recent sounds and their captions (unlabelled: sounds with no caption; demo: sample captions for 20 s)", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		private static void Say(Terminal ctx, string line)
		{
			if ((Object)(object)ctx != (Object)null)
			{
				ctx.AddString(line);
			}
			EarshotPlugin.Log.LogInfo((object)line);
		}

		private static void Recent(Terminal ctx)
		{
			ShowingNow(ctx);
			Say(ctx, "Earshot: last sounds, newest first (enabled=" + PluginConfig.Enabled.Value + ", min volume " + PluginConfig.MinimumVolume.Value.ToString("0.00", CultureInfo.InvariantCulture) + ")");
			foreach (RecentEntry item in Runtime.Recent.Newest(20))
			{
				Say(ctx, "  " + item.Time.ToString("0.0", CultureInfo.InvariantCulture) + "s  " + item.Prefab + "  " + item.Distance.ToString("0", CultureInfo.InvariantCulture) + "m  vol " + item.Loudness.ToString("0.00", CultureInfo.InvariantCulture) + "  -> " + item.Outcome + (string.IsNullOrEmpty(item.Text) ? "" : ("  " + item.Text)));
			}
		}

		private static void ShowingNow(Terminal ctx)
		{
			//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_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_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = WorldQuery.CameraForward();
			Vector3 val2 = WorldQuery.ListenerPosition();
			Camera mainCamera = Utils.GetMainCamera();
			float num = (((Object)(object)mainCamera != (Object)null) ? ((Component)mainCamera).transform.eulerAngles.y : 0f);
			Player localPlayer = Player.m_localPlayer;
			string text = (((Object)(object)localPlayer != (Object)null) ? ("player " + ((Component)localPlayer).transform.position.x.ToString("0.0", CultureInfo.InvariantCulture) + "," + ((Component)localPlayer).transform.position.z.ToString("0.0", CultureInfo.InvariantCulture)) : "no player");
			Say(ctx, "Earshot: showing now (camera yaw " + num.ToString("0", CultureInfo.InvariantCulture) + "°, listener " + val2.x.ToString("0.0", CultureInfo.InvariantCulture) + "," + val2.z.ToString("0.0", CultureInfo.InvariantCulture) + ", " + text + ", snap " + PluginConfig.SnapArrows.Value + ")");
			foreach (CaptionLine line in Runtime.Board.Lines)
			{
				float degrees = Bearing.Degrees(val.x, val.z, val2.x, val2.z, line.X, line.Z);
				float num2 = Bearing.ArrowDegrees(degrees, PluginConfig.SnapArrows.Value);
				Say(ctx, "  " + LineStyle.SourceWithCount(line.Source, line.Count) + " " + line.Action + " [" + line.Category.ToString() + "]  dist " + line.Distance.ToString("0.0", CultureInfo.InvariantCulture) + "m  pos " + line.X.ToString("0.0", CultureInfo.InvariantCulture) + "," + line.Z.ToString("0.0", CultureInfo.InvariantCulture) + "  bearing " + degrees.ToString("0", CultureInfo.InvariantCulture) + "°  arrow " + num2.ToString("0", CultureInfo.InvariantCulture) + "°  fade " + line.Fade(Time.time, Runtime.Board.Settings).ToString("0.00", CultureInfo.InvariantCulture) + "  onscreen " + line.OnScreen);
			}
		}

		private static void Unlabelled(Terminal ctx)
		{
			List<string> unlabelled = Runtime.Recent.Unlabelled;
			Say(ctx, "Earshot: " + unlabelled.Count + " audible sounds with no caption this session:");
			foreach (string item in unlabelled)
			{
				Say(ctx, "  " + item);
			}
		}

		private static void Demo(Terminal ctx)
		{
			if (!PluginConfig.Enabled.Value)
			{
				Say(ctx, "Earshot: captions are off (Settings > Accessibility or F1).");
				return;
			}
			if ((Object)(object)Player.m_localPlayer == (Object)null)
			{
				Say(ctx, "Earshot: load into a world first.");
				return;
			}
			DemoCaptions.Start(20f);
			Say(ctx, "Earshot: showing sample captions for 20 seconds.");
		}
	}
	internal static class LoopTracker
	{
		private sealed class Tracked
		{
			public ZSFX Sfx;

			public SoundEvent Event;

			public Label Label;
		}

		private const float Interval = 0.25f;

		private const int MaxTracked = 64;

		private const float CreatureRange = 60f;

		private static readonly List<Tracked> Loops = new List<Tracked>();

		private static float _next;

		public static void Init()
		{
			SoundCapture.LoopSeen += OnLoopSeen;
		}

		private static void OnLoopSeen(ZSFX z, SoundEvent e, Label label)
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			if (label.Category == Category.Ambient && !PluginConfig.CategoryOn(Category.Ambient))
			{
				return;
			}
			foreach (Tracked loop in Loops)
			{
				if ((Object)(object)loop.Sfx == (Object)(object)z)
				{
					return;
				}
			}
			if (Loops.Count >= 64)
			{
				Loops.RemoveAt(Farthest(WorldQuery.ListenerPosition()));
			}
			Loops.Add(new Tracked
			{
				Sfx = z,
				Event = e,
				Label = label
			});
		}

		private static int Farthest(Vector3 listener)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			int result = 0;
			float num = -1f;
			for (int i = 0; i < Loops.Count; i++)
			{
				ZSFX sfx = Loops[i].Sfx;
				if ((Object)(object)sfx == (Object)null)
				{
					return i;
				}
				float num2 = Vector3.Distance(listener, ((Component)sfx).transform.position);
				if (num2 > num)
				{
					num = num2;
					result = i;
				}
			}
			return result;
		}

		public static void Tick(float now)
		{
			//IL_0015: 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)
			if (!(now < _next))
			{
				_next = now + 0.25f;
				Vector3 listener = WorldQuery.ListenerPosition();
				RecheckZsfxLoops(listener);
				ScanCreatures(listener);
			}
		}

		private static void RecheckZsfxLoops(Vector3 listener)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			for (int num = Loops.Count - 1; num >= 0; num--)
			{
				Tracked tracked = Loops[num];
				if ((Object)(object)tracked.Sfx == (Object)null || !((Behaviour)tracked.Sfx).isActiveAndEnabled || !tracked.Sfx.IsPlaying())
				{
					Loops.RemoveAt(num);
				}
				else
				{
					AudioSource audioSource = tracked.Sfx.m_audioSource;
					Vector3 position = ((Component)tracked.Sfx).transform.position;
					SoundEvent soundEvent = tracked.Event;
					soundEvent.X = position.x;
					soundEvent.Z = position.z;
					soundEvent.Distance = Vector3.Distance(listener, position);
					soundEvent.Loudness = WorldQuery.Loudness(audioSource, audioSource.volume, soundEvent.Distance);
					if (!(soundEvent.Loudness < PluginConfig.MinimumVolume.Value) && PluginConfig.CategoryOn(tracked.Label.Category) && (tracked.Label.Category != Category.World || !Runtime.Settling))
					{
						soundEvent.OnScreen = PluginConfig.DimOnScreen.Value && WorldQuery.OnScreen(position, 15f);
						Runtime.Board.Offer(soundEvent, tracked.Label, Time.time);
					}
				}
			}
		}

		private static void ScanCreatures(Vector3 listener)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: 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_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			LabelTable table = Runtime.Resolver.Table;
			foreach (Character allCharacter in Character.GetAllCharacters())
			{
				if ((Object)(object)allCharacter == (Object)null || allCharacter is Player)
				{
					continue;
				}
				Vector3 position = ((Component)allCharacter).transform.position;
				float num = Vector3.Distance(listener, position);
				if (num > 60f)
				{
					continue;
				}
				string prefabName = "creature:" + WorldQuery.CleanName(((Object)((Component)allCharacter).gameObject).name);
				LabelRow labelRow = table.Find(prefabName);
				if (labelRow == null || labelRow.Mute)
				{
					continue;
				}
				float num2 = 0f;
				float maxDistance = 0f;
				AudioSource[] componentsInChildren = ((Component)allCharacter).GetComponentsInChildren<AudioSource>();
				foreach (AudioSource val in componentsInChildren)
				{
					if (val.loop && val.isPlaying)
					{
						float num3 = WorldQuery.Loudness(val, val.volume, num);
						if (num3 > num2)
						{
							num2 = num3;
							maxDistance = val.maxDistance;
						}
					}
				}
				if (!(num2 < PluginConfig.MinimumVolume.Value))
				{
					SoundEvent e = new SoundEvent
					{
						PrefabName = prefabName,
						CreatureToken = allCharacter.m_name,
						SourceId = ((Object)allCharacter).GetInstanceID(),
						IsLoop = true,
						X = position.x,
						Z = position.z,
						Distance = num,
						MaxDistance = maxDistance,
						Loudness = num2,
						OnScreen = (PluginConfig.DimOnScreen.Value && WorldQuery.OnScreen(position, 15f))
					};
					SkipReason skip;
					Label label = Runtime.Resolver.Resolve(e, out skip);
					if (label != null && PluginConfig.CategoryOn(label.Category))
					{
						Runtime.Board.Offer(e, label, Time.time);
					}
				}
			}
		}
	}
	internal static class RaidWatch
	{
		private const float Interval = 0.5f;

		private static float _next;

		public static void Tick(float now)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			if (now < _next)
			{
				return;
			}
			_next = now + 0.5f;
			if (PluginConfig.CategoryOn(Category.Raid))
			{
				RandEventSystem instance = RandEventSystem.instance;
				RandomEvent val = (((Object)(object)instance != (Object)null) ? instance.GetActiveEvent() : null);
				if (val != null)
				{
					Vector3 val2 = WorldQuery.ListenerPosition();
					SoundEvent e = new SoundEvent
					{
						PrefabName = "raid:" + val.m_name,
						X = val.m_pos.x,
						Z = val.m_pos.z,
						Distance = Vector3.Distance(val2, val.m_pos),
						MaxDistance = val.m_eventRange,
						Loudness = 1f
					};
					Label label = new Label
					{
						Source = (Runtime.Tr.Get("raid") ?? "Raid"),
						Category = Category.Raid,
						Origin = LabelOrigin.Special
					};
					Runtime.Board.Offer(e, label, now);
				}
			}
		}
	}
	internal static class Runtime
	{
		public static readonly BoardSettings Settings = new BoardSettings();

		public static readonly CaptionBoard Board = new CaptionBoard(Settings);

		public static readonly RecentLog Recent = new RecentLog(40);

		public static LabelResolver Resolver;

		public static Translations Tr = new Translations();

		private const float SettleSeconds = 5f;

		public static float SettleUntil;

		private static Player _lastPlayer;

		public static bool Ready => Resolver != null;

		public static bool Settling => Time.time < SettleUntil;

		public static void Tick()
		{
			if (!Ready)
			{
				TryInit();
			}
			PluginConfig.CopyTo(Settings);
			if ((Object)(object)Player.m_localPlayer == (Object)null)
			{
				Board.Clear();
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)(object)_lastPlayer)
			{
				_lastPlayer = localPlayer;
				SettleUntil = Time.time + 5f;
			}
			if (((Character)localPlayer).IsTeleporting())
			{
				SettleUntil = Time.time + 5f;
			}
			float time = Time.time;
			if (PluginConfig.Enabled.Value && Ready)
			{
				LoopTracker.Tick(time);
				RaidWatch.Tick(time);
				VanillaCaptions.Tick(time);
				DemoCaptions.Tick(time);
			}
			Board.Tick(time);
			CaptionHud.Ensure();
		}

		private static void TryInit()
		{
			if (Localization.instance != null)
			{
				Translations translations = Translations.Parse(ReadResource("Earshot.English.txt"));
				string selectedLanguage = Localization.instance.GetSelectedLanguage();
				string text = Path.Combine(Path.Combine(Path.Combine(Paths.ConfigPath, "Earshot"), "translations"), selectedLanguage + ".txt");
				Tr = (File.Exists(text) ? Translations.Parse(File.ReadAllText(text)).WithFallback(translations) : translations);
				LabelTable labelTable = LabelTable.Parse(ReadResource("Earshot.labels.tsv"), delegate(string w)
				{
					EarshotPlugin.Log.LogWarning((object)w);
				});
				Resolver = new LabelResolver(labelTable, Tr, (string token) => Localization.instance.Localize(token));
				EarshotPlugin.Log.LogInfo((object)("Earshot ready: " + labelTable.Count + " label rows, language " + selectedLanguage + (File.Exists(text) ? (" (custom strings from " + text + ")") : "")));
				LoopTracker.Init();
			}
		}

		private static string ReadResource(string name)
		{
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name);
			if (stream == null)
			{
				EarshotPlugin.Log.LogError((object)("Missing embedded resource " + name));
				return "";
			}
			using StreamReader streamReader = new StreamReader(stream);
			return streamReader.ReadToEnd();
		}

		public static OfferResult Offer(SoundEvent e, Label label)
		{
			OfferResult result = Board.Offer(e, label, Time.time);
			Record(e, result.ToString().ToLowerInvariant(), label);
			return result;
		}

		public static void Record(SoundEvent e, string outcome, Label label)
		{
			string text = ((label != null) ? (Tr.Format(label.Source, label.Action) + " [" + label.Category.ToString() + ", " + label.Origin.ToString().ToLowerInvariant() + "]") : "");
			Recent.Add(new RecentEntry
			{
				Time = Time.time,
				Prefab = e.PrefabName,
				Distance = e.Distance,
				Loudness = e.Loudness,
				Outcome = outcome,
				Text = text
			});
			if (outcome == "unlabelled" && Recent.NoteUnlabelled(e.PrefabName) && PluginConfig.LogUnlabelled.Value)
			{
				EarshotPlugin.Log.LogInfo((object)("Earshot unlabelled: " + e.PrefabName + " tokens='" + e.PrimaryToken + "' '" + e.SecondaryToken + "' creature='" + e.CreatureToken + "' " + e.Distance.ToString("0") + "m"));
			}
			if (PluginConfig.Verbose.Value)
			{
				EarshotPlugin.Log.LogInfo((object)("Earshot " + outcome + ": " + e.PrefabName + " " + e.Distance.ToString("0") + "m vol " + e.Loudness.ToString("0.00") + " " + text));
			}
		}
	}
	internal static class SoundCapture
	{
		private const float OwnActionRadius = 5f;

		public static event Action<ZSFX, SoundEvent, Label> LoopSeen;

		public static void OnPlay(ZSFX z)
		{
			//IL_007d: 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_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: 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_00d1: Expected I4, but got Unknown
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0254: Unknown result type (might be due to invalid IL or missing references)
			if (!PluginConfig.Enabled.Value || !Runtime.Ready || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				return;
			}
			AudioSource audioSource = z.m_audioSource;
			if ((Object)(object)audioSource == (Object)null || !audioSource.isPlaying || audioSource.spatialBlend < 0.5f)
			{
				return;
			}
			string text = WorldQuery.CleanName(((Object)((Component)z).gameObject).name);
			if (text.IndexOf("vibration_only", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("vibrateonly", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return;
			}
			Vector3 position = ((Component)z).transform.position;
			float distance = Vector3.Distance(WorldQuery.ListenerPosition(), position);
			SoundEvent soundEvent = new SoundEvent
			{
				PrefabName = text,
				PrimaryToken = (z.m_closedCaptionToken ?? ""),
				SecondaryToken = (z.m_secondaryCaptionToken ?? ""),
				VanillaType = (VanillaType)z.m_captionType,
				IsLoop = audioSource.loop,
				X = position.x,
				Z = position.z,
				Distance = distance,
				MaxDistance = audioSource.maxDistance,
				Loudness = WorldQuery.Loudness(audioSource, z.m_vol, distance)
			};
			if (!soundEvent.IsLoop && soundEvent.Loudness == 0f)
			{
				return;
			}
			if (!soundEvent.IsLoop && soundEvent.Loudness < PluginConfig.MinimumVolume.Value)
			{
				Runtime.Record(soundEvent, "quiet", null);
				return;
			}
			if (IsSelf(z, soundEvent, position))
			{
				Runtime.Record(soundEvent, "self", null);
				return;
			}
			SkipReason skip;
			Label label = Runtime.Resolver.Resolve(soundEvent, out skip);
			if (label == null)
			{
				if (soundEvent.Loudness >= PluginConfig.MinimumVolume.Value)
				{
					Runtime.Record(soundEvent, (skip == SkipReason.Muted) ? "muted" : "unlabelled", null);
				}
				return;
			}
			if (soundEvent.IsLoop && SoundCapture.LoopSeen != null)
			{
				SoundCapture.LoopSeen(z, soundEvent, label);
			}
			if (soundEvent.Loudness != 0f)
			{
				if (soundEvent.Loudness < PluginConfig.MinimumVolume.Value)
				{
					Runtime.Record(soundEvent, "quiet", label);
					return;
				}
				if (label.Category == Category.World && Runtime.Settling)
				{
					Runtime.Record(soundEvent, "settling", label);
					return;
				}
				if (!PluginConfig.CategoryOn(label.Category))
				{
					Runtime.Record(soundEvent, "category off", label);
					return;
				}
				soundEvent.OnScreen = PluginConfig.DimOnScreen.Value && WorldQuery.OnScreen(position, 15f);
				Runtime.Offer(soundEvent, label);
			}
		}

		private static bool IsSelf(ZSFX z, SoundEvent e, Vector3 pos)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			bool flag = e.PrimaryToken.StartsWith("$enemy_", StringComparison.Ordinal);
			Character val = WorldQuery.NearestCharacter(pos, 2f);
			if ((Object)(object)val != (Object)null && !(val is Player))
			{
				e.CreatureToken = val.m_name;
				e.SourceId = ((Object)val).GetInstanceID();
				return false;
			}
			if (flag)
			{
				return false;
			}
			if (z.m_sfxCreator != ZDOID.None)
			{
				return CreatedByPlayer(z.m_sfxCreator);
			}
			if (val is Player && !e.IsLoop)
			{
				return true;
			}
			if ((Object)(object)((Component)z).GetComponentInParent<Player>() != (Object)null)
			{
				return true;
			}
			if (!e.IsLoop && WorldQuery.AnyPlayerWithin(pos, 5f))
			{
				return true;
			}
			Piece componentInParent = ((Component)z).GetComponentInParent<Piece>();
			if ((Object)(object)componentInParent != (Object)null)
			{
				e.ObjectToken = componentInParent.m_name;
				e.SourceId = ((Object)componentInParent).GetInstanceID();
			}
			return false;
		}

		private static bool CreatedByPlayer(ZDOID creator)
		{
			//IL_001f: 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)
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				if ((Object)(object)allPlayer != (Object)null && ((Character)allPlayer).GetZDOID() == creator)
				{
					return true;
				}
			}
			return false;
		}
	}
	internal static class VanillaCaptions
	{
		private static float _next;

		private static ClosedCaptions _hidden;

		public static void Tick(float now)
		{
			if (!(now < _next) && PluginConfig.Enabled.Value)
			{
				_next = now + 1f;
				ClosedCaptions instance = ClosedCaptions.Instance;
				if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance == (Object)(object)_hidden) && ((Component)instance).gameObject.activeSelf && ((Component)instance).transform.childCount != 0)
				{
					((Component)instance).gameObject.SetActive(false);
					_hidden = instance;
					EarshotPlugin.Log.LogInfo((object)"Vanilla closed captions detected; hiding them in favour of Earshot.");
				}
			}
		}
	}
	internal static class WorldQuery
	{
		public static string CleanName(string name)
		{
			if (name == null)
			{
				return "";
			}
			int num = name.IndexOf("(Clone)", StringComparison.Ordinal);
			return ((num >= 0) ? name.Substring(0, num) : name).Trim();
		}

		public static Vector3 ListenerPosition()
		{
			//IL_0028: 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_003d: Unknown result type (might be due to invalid IL or missing references)
			AudioMan instance = AudioMan.instance;
			AudioListener val = (((Object)(object)instance != (Object)null) ? instance.GetActiveAudioListener() : null);
			if ((Object)(object)val != (Object)null)
			{
				return ((Component)val).transform.position;
			}
			Camera mainCamera = Utils.GetMainCamera();
			if (!((Object)(object)mainCamera != (Object)null))
			{
				return Vector3.zero;
			}
			return ((Component)mainCamera).transform.position;
		}

		public static Vector3 CameraForward()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			Camera mainCamera = Utils.GetMainCamera();
			if (!((Object)(object)mainCamera != (Object)null))
			{
				return Vector3.forward;
			}
			return ((Component)mainCamera).transform.forward;
		}

		public static float Loudness(AudioSource a, float baseVolume, float distance)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Invalid comparison between Unknown and I4
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected I4, but got Unknown
			Func<float, float> customCurve = null;
			if ((int)a.rolloffMode == 2)
			{
				AnimationCurve c = a.GetCustomCurve((AudioSourceCurveType)0);
				if (c != null)
				{
					customCurve = (float x) => c.Evaluate(x);
				}
			}
			float attenuation = Audibility.Attenuation((Rolloff)a.rolloffMode, distance, a.minDistance, a.maxDistance, customCurve);
			return Audibility.Loudness(baseVolume, attenuation);
		}

		public static Character NearestCharacter(Vector3 pos, float radius)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			Character result = null;
			float num = radius * radius;
			foreach (Character allCharacter in Character.GetAllCharacters())
			{
				if (!((Object)(object)allCharacter == (Object)null))
				{
					Vector3 val = ((Component)allCharacter).transform.position - pos;
					float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
					if (sqrMagnitude <= num)
					{
						num = sqrMagnitude;
						result = allCharacter;
					}
				}
			}
			return result;
		}

		public static bool AnyPlayerWithin(Vector3 pos, float radius)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			float num = radius * radius;
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				if ((Object)(object)allPlayer != (Object)null)
				{
					Vector3 val = ((Component)allPlayer).transform.position - pos;
					if (((Vector3)(ref val)).sqrMagnitude <= num)
					{
						return true;
					}
				}
			}
			return false;
		}

		public static bool OnScreen(Vector3 pos, float within)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: 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_002f: 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_0049: 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)
			Camera mainCamera = Utils.GetMainCamera();
			if ((Object)(object)mainCamera == (Object)null)
			{
				return false;
			}
			Vector3 val = mainCamera.WorldToViewportPoint(pos);
			if (val.z > 0f && val.z <= within && val.x >= 0f && val.x <= 1f && val.y >= 0f)
			{
				return val.y <= 1f;
			}
			return false;
		}
	}
}
namespace Earshot.Core.Model
{
	public enum Rolloff
	{
		Logarithmic,
		Linear,
		Custom
	}
	public static class Audibility
	{
		public static float Attenuation(Rolloff mode, float distance, float minDistance, float maxDistance, Func<float, float> customCurve)
		{
			if (maxDistance <= 0f || distance > maxDistance)
			{
				return 0f;
			}
			if (distance < 0f)
			{
				distance = 0f;
			}
			switch (mode)
			{
			case Rolloff.Linear:
				if (distance <= minDistance || maxDistance <= minDistance)
				{
					return 1f;
				}
				return Clamp01(1f - (distance - minDistance) / (maxDistance - minDistance));
			case Rolloff.Custom:
				if (customCurve != null)
				{
					return Clamp01(customCurve(distance / maxDistance));
				}
				return 1f;
			default:
				if (distance <= minDistance)
				{
					return 1f;
				}
				return Clamp01(minDistance / distance);
			}
		}

		public static float Loudness(float baseVolume, float attenuation)
		{
			return Clamp01(baseVolume) * Clamp01(attenuation);
		}

		private static float Clamp01(float v)
		{
			if (!(v < 0f))
			{
				if (!(v > 1f))
				{
					return v;
				}
				return 1f;
			}
			return 0f;
		}
	}
	public static class Bearing
	{
		public static float Degrees(float forwardX, float forwardZ, float listenerX, float listenerZ, float soundX, float soundZ)
		{
			double num = Math.Atan2(forwardX, forwardZ);
			double num2;
			for (num2 = (Math.Atan2(soundX - listenerX, soundZ - listenerZ) - num) * 180.0 / Math.PI; num2 > 180.0; num2 -= 360.0)
			{
			}
			for (; num2 <= -180.0; num2 += 360.0)
			{
			}
			return (float)num2;
		}

		public static int Sector8(float degrees)
		{
			double num = (double)degrees % 360.0;
			if (num < 0.0)
			{
				num += 360.0;
			}
			return (int)Math.Floor((num + 22.5) / 45.0) % 8;
		}

		public static float ArrowDegrees(float degrees, bool snap)
		{
			if (!snap)
			{
				return degrees;
			}
			return (float)Sector8(degrees) * 45f;
		}
	}
	public sealed class BoardSettings
	{
		public int MaxLines = 5;

		public float Linger = 3f;

		public float FadeOut = 0.5f;

		public float IdleCooldown = 20f;
	}
	public enum OfferResult
	{
		Added,
		Merged,
		Throttled,
		Outranked
	}
	public sealed class CaptionLine
	{
		public string Key;

		public Category Category;

		public string Source;

		public string Action;

		public bool NearFlag;

		public float X;

		public float Z;

		public float Distance;

		public float MaxDistance;

		public bool OnScreen;

		public float Born;

		public float LastHeard;

		public int Count = 1;

		internal float PositionTime = float.NegativeInfinity;

		internal readonly Dictionary<int, float> Sources = new Dictionary<int, float>();

		public float Fade(float now, BoardSettings s)
		{
			float num = now - LastHeard - s.Linger;
			if (num <= 0f)
			{
				return 1f;
			}
			if (s.FadeOut <= 0f)
			{
				return 0f;
			}
			return Math.Max(0f, 1f - num / s.FadeOut);
		}
	}
	public sealed class CaptionBoard
	{
		private readonly List<CaptionLine> _lines = new List<CaptionLine>();

		private readonly Dictionary<string, float> _lastCreated = new Dictionary<string, float>();

		private readonly List<int> _stale = new List<int>();

		public BoardSettings Settings { get; }

		public IReadOnlyList<CaptionLine> Lines => _lines;

		public CaptionBoard(BoardSettings settings)
		{
			Settings = settings;
		}

		public static string KeyOf(Label label)
		{
			int category = (int)label.Category;
			return category + "|" + label.Source;
		}

		public OfferResult Offer(SoundEvent e, Label label, float now)
		{
			string text = KeyOf(label);
			for (int i = 0; i < _lines.Count; i++)
			{
				if (_lines[i].Key == text)
				{
					Refresh(_lines[i], e, label, now);
					return OfferResult.Merged;
				}
			}
			if (label.Idle && _lastCreated.TryGetValue(text, out var value) && now - value < Settings.IdleCooldown)
			{
				return OfferResult.Throttled;
			}
			CaptionLine captionLine = new CaptionLine
			{
				Key = text,
				Category = label.Category,
				Source = label.Source,
				Born = now
			};
			Refresh(captionLine, e, label, now);
			int num = Math.Max(1, Settings.MaxLines);
			while (_lines.Count >= num)
			{
				CaptionLine captionLine2 = Weakest(now);
				if (Compare(captionLine, captionLine2, now) < 0)
				{
					return OfferResult.Outranked;
				}
				_lines.Remove(captionLine2);
			}
			_lines.Add(captionLine);
			_lastCreated[text] = now;
			return OfferResult.Added;
		}

		public void Tick(float now)
		{
			for (int num = _lines.Count - 1; num >= 0; num--)
			{
				if (_lines[num].Fade(now, Settings) <= 0f)
				{
					_lines.RemoveAt(num);
				}
				else
				{
					Prune(_lines[num], now);
				}
			}
			int num2 = Math.Max(1, Settings.MaxLines);
			while (_lines.Count > num2)
			{
				_lines.Remove(Weakest(now));
			}
			if (_lastCreated.Count <= 256)
			{
				return;
			}
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, float> item in _lastCreated)
			{
				if (now - item.Value >= Settings.IdleCooldown)
				{
					list.Add(item.Key);
				}
			}
			foreach (string item2 in list)
			{
				_lastCreated.Remove(item2);
			}
		}

		public void Clear()
		{
			_lines.Clear();
			_lastCreated.Clear();
		}

		public int Compare(CaptionLine a, CaptionLine b, float now)
		{
			bool flag = a.Fade(now, Settings) < 1f;
			bool flag2 = b.Fade(now, Settings) < 1f;
			if (flag != flag2)
			{
				if (!flag)
				{
					return 1;
				}
				return -1;
			}
			if (a.Category != b.Category)
			{
				return a.Category.CompareTo(b.Category);
			}
			if (a.OnScreen != b.OnScreen)
			{
				if (!a.OnScreen)
				{
					return 1;
				}
				return -1;
			}
			if (a.Distance != b.Distance)
			{
				return b.Distance.CompareTo(a.Distance);
			}
			return a.LastHeard.CompareTo(b.LastHeard);
		}

		private CaptionLine Weakest(float now)
		{
			CaptionLine captionLine = _lines[0];
			for (int i = 1; i < _lines.Count; i++)
			{
				if (Compare(_lines[i], captionLine, now) < 0)
				{
					captionLine = _lines[i];
				}
			}
			return captionLine;
		}

		private void Refresh(CaptionLine line, SoundEvent e, Label label, float now)
		{
			if (label.Action != null)
			{
				line.Action = label.Action;
			}
			if (label.Near)
			{
				line.NearFlag = true;
			}
			line.LastHeard = now;
			line.Sources[e.SourceId] = now;
			Prune(line, now);
			if (e.Distance <= line.Distance || now - line.PositionTime > 1f)
			{
				line.X = e.X;
				line.Z = e.Z;
				line.Distance = e.Distance;
				line.MaxDistance = e.MaxDistance;
				line.OnScreen = e.OnScreen;
				line.PositionTime = now;
			}
		}

		private void Prune(CaptionLine line, float now)
		{
			_stale.Clear();
			foreach (KeyValuePair<int, float> source in line.Sources)
			{
				if (now - source.Value > Settings.Linger)
				{
					_stale.Add(source.Key);
				}
			}
			foreach (int item in _stale)
			{
				line.Sources.Remove(item);
			}
			line.Count = Math.Max(1, line.Sources.Count);
		}
	}
	public enum Category
	{
		Self,
		Ambient,
		World,
		Wildlife,
		Enemy,
		Raid,
		Boss
	}
	public enum VanillaType
	{
		Default,
		Wildlife,
		Enemy,
		Boss
	}
	public static class Categories
	{
		public static bool IsThreat(Category c)
		{
			if (c != Category.Enemy && c != Category.Raid)
			{
				return c == Category.Boss;
			}
			return true;
		}

		public static Category FromVanilla(VanillaType t)
		{
			return t switch
			{
				VanillaType.Boss => Category.Boss, 
				VanillaType.Enemy => Category.Enemy, 
				VanillaType.Wildlife => Category.Wildlife, 
				_ => Category.World, 
			};
		}

		public static bool TryParse(string text, out Category c)
		{
			if (string.IsNullOrEmpty(text) || int.TryParse(text, out var _))
			{
				c = Category.Self;
				return false;
			}
			if (Enum.TryParse<Category>(text, ignoreCase: true, out c))
			{
				return Enum.IsDefined(typeof(Category), c);
			}
			return false;
		}
	}
	public enum LabelOrigin
	{
		Table,
		Creature,
		Vanilla,
		Special
	}
	public enum SkipReason
	{
		None,
		Muted,
		Unlabelled
	}
	public sealed class Label
	{
		public string Source;

		public string Action;

		public Category Category;

		public bool Idle;

		public bool Near;

		public LabelOrigin Origin;
	}
	public sealed class LabelResolver
	{
		private readonly LabelTable _table;

		private readonly Translations _tr;

		private readonly Func<string, string> _localize;

		public LabelTable Table => _table;

		public LabelResolver(LabelTable table, Translations translations, Func<string, string> localize)
		{
			_table = table;
			_tr = translations;
			_localize = localize;
		}

		public Label Resolve(SoundEvent e, out SkipReason skip)
		{
			skip = SkipReason.None;
			LabelRow labelRow = _table.Find(e.PrefabName);
			if (labelRow != null)
			{
				return FromRow(labelRow, e, out skip);
			}
			if (string.IsNullOrEmpty(e.PrimaryToken))
			{
				skip = SkipReason.Unlabelled;
				return null;
			}
			string action = GameText(e.SecondaryToken);
			Category category = Categories.FromVanilla(e.VanillaType);
			bool idle = IsVanillaIdle(e);
			string text = (e.PrimaryToken.StartsWith("$enemy_", StringComparison.Ordinal) ? GameText(e.CreatureToken) : null);
			if (text != null)
			{
				return new Label
				{
					Source = text,
					Action = action,
					Category = category,
					Idle = idle,
					Origin = LabelOrigin.Creature
				};
			}
			string text2 = GameText(e.PrimaryToken);
			if (text2 != null)
			{
				return new Label
				{
					Source = text2,
					Action = action,
					Category = category,
					Idle = idle,
					Origin = LabelOrigin.Vanilla
				};
			}
			skip = SkipReason.Unlabelled;
			return null;
		}

		private Label FromRow(LabelRow row, SoundEvent e, out SkipReason skip)
		{
			skip = SkipReason.None;
			if (row.Mute)
			{
				skip = SkipReason.Muted;
				return null;
			}
			string text = SourceText(row.Source, e);
			if (text == null)
			{
				skip = SkipReason.Unlabelled;
				return null;
			}
			return new Label
			{
				Source = text,
				Action = ActionText(row.Action, e),
				Category = (row.HasCategory ? row.Category : Categories.FromVanilla(e.VanillaType)),
				Idle = (row.Idle || (!row.HasCategory && IsVanillaIdle(e))),
				Near = row.Near,
				Origin = LabelOrigin.Table
			};
		}

		private static bool IsVanillaIdle(SoundEvent e)
		{
			if (e.VanillaType == VanillaType.Wildlife && e.PrefabName != null)
			{
				return e.PrefabName.IndexOf("_idle", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}

		private string SourceText(string spec, SoundEvent e)
		{
			if (spec == "@creature")
			{
				return GameText(e.CreatureToken) ?? GameText(e.PrimaryToken);
			}
			if (spec == "@object")
			{
				return GameText(e.ObjectToken) ?? GameText(e.PrimaryToken);
			}
			return Word(spec);
		}

		private string ActionText(string spec, SoundEvent e)
		{
			if (spec == null)
			{
				return null;
			}
			if (spec == "@vanilla")
			{
				return GameText(e.SecondaryToken);
			}
			return Word(spec);
		}

		private string Word(string spec)
		{
			string text = (spec.StartsWith("$earshot_", StringComparison.Ordinal) ? _tr.Get(spec.Substring(1)) : ((!spec.StartsWith("$", StringComparison.Ordinal)) ? _tr.Get(spec) : _localize(spec)));
			if (!TextCheck.IsClean(text))
			{
				return null;
			}
			return text;
		}

		private string GameText(string token)
		{
			if (string.IsNullOrEmpty(token))
			{
				return null;
			}
			string text = _localize(token);
			if (!TextCheck.IsClean(text))
			{
				return null;
			}
			return text;
		}
	}
	public sealed class LabelRow
	{
		public string Pattern;

		public bool Prefix;

		public bool HasCategory;

		public Category Category;

		public string Source;

		public string Action;

		public bool Idle;

		public bool Mute;

		public bool Near;
	}
	public sealed class LabelTable
	{
		private readonly Dictionary<string, LabelRow> _exact = new Dictionary<string, LabelRow>(StringComparer.OrdinalIgnoreCase);

		private readonly List<LabelRow> _prefixes = new List<LabelRow>();

		public int Count => _exact.Count + _prefixes.Count;

		public IEnumerable<LabelRow> Rows
		{
			get
			{
				foreach (LabelRow value in _exact.Values)
				{
					yield return value;
				}
				foreach (LabelRow prefix in _prefixes)
				{
					yield return prefix;
				}
			}
		}

		public static LabelTable Parse(string text, Action<string> warn)
		{
			LabelTable labelTable = new LabelTable();
			string[] array = (text ?? "").Split(new char[1] { '\n' });
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i];
				int num = text2.IndexOf('#');
				if (num >= 0)
				{
					text2 = text2.Substring(0, num);
				}
				text2 = text2.Trim();
				if (text2.Length == 0)
				{
					continue;
				}
				string text3 = "labels.tsv line " + (i + 1) + ": ";
				string[] array2 = text2.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
				if (array2.Length < 2)
				{
					warn?.Invoke(text3 + "needs at least a prefab and a category");
					continue;
				}
				LabelRow labelRow = new LabelRow();
				string text4 = Normalise(array2[0]);
				if (text4.EndsWith("*", StringComparison.Ordinal))
				{
					labelRow.Prefix = true;
					text4 = text4.Substring(0, text4.Length - 1);
				}
				labelRow.Pattern = text4;
				if (array2[1] != "-")
				{
					if (!Categories.TryParse(array2[1], out labelRow.Category))
					{
						warn?.Invoke(text3 + "unknown category '" + array2[1] + "'");
						continue;
					}
					labelRow.HasCategory = true;
				}
				labelRow.Source = ((array2.Length > 2 && array2[2] != "-") ? array2[2] : null);
				labelRow.Action = ((array2.Length > 3 && array2[3] != "-") ? array2[3] : null);
				if (array2.Length > 4)
				{
					string[] array3 = array2[4].Split(new char[1] { ',' });
					foreach (string text5 in array3)
					{
						string text6 = text5.Trim().ToLowerInvariant();
						switch (text6)
						{
						case "idle":
							labelRow.Idle = true;
							continue;
						case "mute":
							labelRow.Mute = true;
							continue;
						case "near":
							labelRow.Near = true;
							continue;
						}
						if (text6.Length > 0)
						{
							warn?.Invoke(text3 + "unknown flag '" + text5 + "'");
						}
					}
				}
				if (!labelRow.Mute && labelRow.Source == null)
				{
					warn?.Invoke(text3 + "needs a source unless it is muted");
				}
				else if (labelRow.Prefix)
				{
					labelTable._prefixes.Add(labelRow);
				}
				else if (labelTable._exact.ContainsKey(text4))
				{
					warn?.Invoke(text3 + "duplicate row for '" + text4 + "', keeping the first");
				}
				else
				{
					labelTable._exact.Add(text4, labelRow);
				}
			}
			labelTable._prefixes.Sort((LabelRow a, LabelRow b) => b.Pattern.Length.CompareTo(a.Pattern.Length));
			return labelTable;
		}

		public LabelRow Find(string prefabName)
		{
			if (string.IsNullOrEmpty(prefabName))
			{
				return null;
			}
			string text = Normalise(prefabName);
			if (_exact.TryGetValue(text, out var value))
			{
				return value;
			}
			foreach (LabelRow prefix in _prefixes)
			{
				if (text.StartsWith(prefix.Pattern, StringComparison.OrdinalIgnoreCase))
				{
					return prefix;
				}
			}
			return null;
		}

		private static string Normalise(string name)
		{
			return name.Replace(' ', '_');
		}
	}
	public static class LineStyle
	{
		public const float FarOpacity = 0.55f;

		public const float OnScreenOpacity = 0.5f;

		public static float Opacity(float distance, float maxDistance, bool onScreen)
		{
			float num = ((maxDistance > 0f) ? Math.Min(1f, Math.Max(0f, distance / maxDistance)) : 0f);
			float num2 = 1f - 0.45f * num;
			if (!onScreen)
			{
				return num2;
			}
			return Math.Min(num2, 0.5f);
		}

		public static bool ShowNear(Category category, float distance, float nearDistance, bool nearFlag)
		{
			if (nearFlag || Categories.IsThreat(category))
			{
				return distance <= nearDistance;
			}
			return false;
		}

		public static string SourceWithCount(string source, int count)
		{
			if (count < 2)
			{
				return source;
			}
			return source + " ×" + count;
		}
	}
	public sealed class RecentEntry
	{
		public float Time;

		public string Prefab;

		public float Distance;

		public float Loudness;

		public string Outcome;

		public string Text;
	}
	public sealed class RecentLog
	{
		private readonly RecentEntry[] _buffer;

		private readonly HashSet<string> _unlabelled = new HashSet<string>(StringComparer.Ordinal);

		private int _next;

		private int _count;

		public List<string> Unlabelled
		{
			get
			{
				List<string> list = new List<string>(_unlabelled);
				list.Sort(StringComparer.Ordinal);
				return list;
			}
		}

		public RecentLog(int capacity)
		{
			_buffer = new RecentEntry[Math.Max(1, capacity)];
		}

		public void Add(RecentEntry entry)
		{
			_buffer[_next] = entry;
			_next = (_next + 1) % _buffer.Length;
			if (_count < _buffer.Length)
			{
				_count++;
			}
		}

		public List<RecentEntry> Newest(int n)
		{
			List<RecentEntry> list = new List<RecentEntry>();
			for (int i = 0; i < Math.Min(n, _count); i++)
			{
				list.Add(_buffer[(_next - 1 - i + _buffer.Length) % _buffer.Length]);
			}
			return list;
		}

		public bool NoteUnlabelled(string prefab)
		{
			return _unlabelled.Add(prefab);
		}
	}
	public sealed class SoundEvent
	{
		public string PrefabName;

		public string PrimaryToken = "";

		public string SecondaryToken = "";

		public VanillaType VanillaType;

		public string CreatureToken;

		public string ObjectToken;

		public int SourceId;

		public bool IsLoop;

		public float X;

		public float Z;

		public float Distance;

		public float MaxDistance;

		public float Loudness;

		public bool OnScreen;
	}
	public static class TextCheck
	{
		public static bool IsClean(string text)
		{
			if (string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			if (text.IndexOf('[') < 0 && text.IndexOf(']') < 0)
			{
				return text.IndexOf('$') < 0;
			}
			return false;
		}
	}
	public sealed class Translations
	{
		public const string DefaultFormat = "{source} {action}";

		private readonly Dictionary<string, string> _map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private Translations _fallback;

		public static Translations Parse(string text)
		{
			Translations translations = new Translations();
			string[] array = (text ?? "").Split(new char[1] { '\n' });
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length == 0 || text2.StartsWith("#", StringComparison.Ordinal))
				{
					continue;
				}
				int num = text2.IndexOf('=');
				if (num > 0)
				{
					string text3 = text2.Substring(0, num).Trim();
					string text4 = text2.Substring(num + 1).Trim();
					if (text3.Length > 0 && text4.Length > 0)
					{
						translations._map[text3] = text4;
					}
				}
			}
			return translations;
		}

		public Translations WithFallback(Translations fallback)
		{
			_fallback = fallback;
			return this;
		}

		public string Get(string key)
		{
			if (key == null)
			{
				return null;
			}
			if (_map.TryGetValue(key, out var value))
			{
				return value;
			}
			if (_fallback == null)
			{
				return null;
			}
			return _fallback.Get(key);
		}

		public string Format(string source, string action)
		{
			if (string.IsNullOrEmpty(action))
			{
				return source;
			}
			return (Get("format") ?? "{source} {action}").Replace("{source}", source).Replace("{action}", action);
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}