Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Earshot v0.1.0
plugins/Earshot/Earshot.dll
Decompiled 4 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.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) { } } }