plugins/Purr.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
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.Unity.IL2CPP;
using BepInEx.Unity.IL2CPP.Configuration;
using CatLib.Config;
using CatLib.Core;
using CatLib.Game;
using CatLib.Game.Events;
using CatLib.Localization;
using CatLib.Logging;
using CatLib.Net;
using CatLib.UI;
using CatLib.Voice;
using Il2CppInterop.Runtime.InteropTypes;
using Microsoft.CodeAnalysis;
using Purr.Logic;
using Purr.Scene;
using Purr.Settings;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("CatLib contributors")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+9f1341b20f5574c5b5eaec0f57678eb20f1c8dea")]
[assembly: AssemblyProduct("CatLib")]
[assembly: AssemblyTitle("Purr")]
[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;
		}
	}
}
namespace Purr
{
	public sealed class PurrController
	{
		public const string IconResource = "Purr.Icons.mic.png";

		private readonly CatLogger _log;

		private readonly TextCatalog _texts;

		private readonly SpeakersView _speakers = new SpeakersView();

		private readonly NameTags _tags;

		private Sprite _icon;

		private bool _iconTried;

		private bool _wasTransmitting;

		public VoiceSettings Settings { get; }

		public PurrController(CatLogger log, TextCatalog texts, VoiceSettings settings)
		{
			_log = log;
			_texts = texts;
			Settings = settings;
			_tags = new NameTags(log);
		}

		public void Update()
		{
			try
			{
				ReadKeys();
				Apply();
				Draw();
			}
			catch (Exception ex)
			{
				_log.Error("Voice chat update failed", ex);
			}
		}

		public void Forget()
		{
			_speakers.Destroy();
			_tags.Clear();
		}

		private void ReadKeys()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			if (Settings.Enabled.Value && !IsTyping())
			{
				KeyboardShortcut value = Settings.MuteKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					bool flag = !Settings.Muted.Value;
					Settings.Muted.LocalValue = flag;
					Notice(MicLogic.MuteNotice(flag));
				}
				value = Settings.DeafenKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					bool flag2 = !Settings.Deafened.Value;
					Settings.Deafened.LocalValue = flag2;
					Notice(MicLogic.DeafenNotice(flag2));
				}
			}
		}

		private void Apply()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			bool value = Settings.Enabled.Value;
			bool flag = MicLogic.Transmits(pushToTalkHeld: !IsTyping() && Held(Settings.PushToTalkKey.Value), enabled: value, mode: Settings.Mode.Value, muted: Settings.Muted.Value, deafened: Settings.Deafened.Value);
			if (flag != _wasTransmitting)
			{
				_wasTransmitting = flag;
				_log.Debug(flag ? "The microphone is on" : "The microphone is off");
			}
			CatVoice.Transmitting = flag;
			CatVoice.Deafened = !MicLogic.Hears(value, Settings.Deafened.Value);
			CatVoice.Volume = Settings.Volume.Value;
			CatVoice.Sound = Settings.Sound.Value;
			CatVoice.HearDistance = Settings.HearDistance.Value;
			CatVoice.FullVolumeDistance = Settings.FullVolumeDistance.Value;
		}

		private void Draw()
		{
			IReadOnlyList<ulong> speakers = CatVoice.Speakers;
			if (Settings.Enabled.Value && Settings.ShowSpeakers.Value && !HudLayer.IsGameMenuOpen && speakers.Count > 0)
			{
				IReadOnlyList<string> names = MicLogic.SpeakerNames((IEnumerable<ulong>)speakers, CatVoice.LocalId, (Func<ulong, string>)CatNetwork.PlayerName, _texts.Get("speakers.you"));
				_speakers.Show(names, Icon(), _texts.Get("speakers.title"), Settings.Corner.Value);
			}
			else
			{
				_speakers.Hide();
			}
			if (Settings.Enabled.Value && Settings.NameTags.Value)
			{
				_tags.Show(speakers.Where((ulong id) => id != CatVoice.LocalId).ToList());
			}
			else
			{
				_tags.Clear();
			}
		}

		private void Notice(MicNotice notice)
		{
			if (notice != MicNotice.None && Settings.Notices.Value)
			{
				Notifications.Show(_texts.Get("notice." + notice), (string)null);
			}
		}

		private Sprite Icon()
		{
			if (!_iconTried)
			{
				_iconTried = true;
				_icon = UiSprites.FromResource(typeof(PurrController).Assembly, "Purr.Icons.mic.png");
			}
			return _icon;
		}

		public static bool Held(KeyboardShortcut shortcut)
		{
			//IL_0002: 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)
			if ((int)((KeyboardShortcut)(ref shortcut)).MainKey != 0 && UnityInput.Current.GetKey(((KeyboardShortcut)(ref shortcut)).MainKey))
			{
				return ((KeyboardShortcut)(ref shortcut)).Modifiers.All((Func<KeyCode, bool>)UnityInput.Current.GetKey);
			}
			return false;
		}

		private static bool IsTyping()
		{
			try
			{
				EventSystem current = EventSystem.current;
				GameObject val = (((Object)(object)current == (Object)null) ? null : current.currentSelectedGameObject);
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				TMP_InputField component = val.GetComponent<TMP_InputField>();
				return (Object)(object)component != (Object)null && component.isFocused;
			}
			catch (Exception)
			{
				return false;
			}
		}
	}
	[BepInPlugin("catlib.purr", "Purr!", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class PurrPlugin : BasePlugin
	{
		public const string LanguageResourcePrefix = "Purr.Lang.";

		public static PurrController Controller { get; private set; }

		public override void Load()
		{
			CatLogger obj = CatLogger.From(((BasePlugin)this).Log);
			CatSettings val = CatSettings.For((BasePlugin)(object)this);
			int count = val.Texts.Count;
			CatNetwork.Declare((BasePlugin)(object)this, (SessionPolicy)3, (VersionRule)2);
			CatVoice.Enable((BasePlugin)(object)this);
			Controller = new PurrController(obj, val.Texts, new VoiceSettings(val));
			FrameLoop.Update += Controller.Update;
			BootstrapEvents.LevelLoadStarted += Controller.Forget;
			BootstrapEvents.GameRestartStarted += Controller.Forget;
			BootstrapEvents.MainMenuLoaded += Controller.Forget;
			obj.Info($"Purr! {"0.1.0"} loaded with {count} translated text(s) in {string.Join(", ", val.Texts.Languages)}");
		}
	}
	public static class PluginMeta
	{
		public const string Guid = "catlib.purr";

		public const string Name = "Purr!";

		public const string Version = "0.1.0";
	}
}
namespace Purr.Settings
{
	public sealed class VoiceSettings
	{
		public Setting<bool> Enabled { get; }

		public Setting<float> Volume { get; }

		public Setting<MicMode> Mode { get; }

		public Setting<KeyboardShortcut> PushToTalkKey { get; }

		public Setting<bool> Muted { get; }

		public Setting<KeyboardShortcut> MuteKey { get; }

		public Setting<bool> Deafened { get; }

		public Setting<KeyboardShortcut> DeafenKey { get; }

		public Setting<VoiceSound> Sound { get; }

		public Setting<float> HearDistance { get; }

		public Setting<float> FullVolumeDistance { get; }

		public Setting<bool> ShowSpeakers { get; }

		public Setting<SpeakersCorner> Corner { get; }

		public Setting<bool> NameTags { get; }

		public Setting<bool> Notices { get; }

		public VoiceSettings(CatSettings settings)
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			Enabled = settings.Local<bool>("Voice", "Enabled", true, "Voice chat with the players who have this mod too. Off, you neither talk nor hear anyone.", (AcceptableValueBase)null);
			Volume = settings.Local<float>("Voice", "Volume", 1f, "How loud the other players are: 1 is as Steam gives them, 2 is twice as loud.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f));
			Mode = settings.Local<MicMode>("Microphone", "Mode", MicMode.PushToTalk, "Push to talk sends your voice only while you hold the key below. Open microphone sends it whenever Steam hears you. The microphone itself is the one chosen in Steam: Settings, Voice.", (AcceptableValueBase)null);
			PushToTalkKey = settings.Local<KeyboardShortcut>("Microphone", "PushToTalkKey", new KeyboardShortcut((KeyCode)118, Array.Empty<KeyCode>()), "Hold it to talk in push to talk mode.", (AcceptableValueBase)null);
			Muted = settings.Local<bool>("Microphone", "Muted", false, "Turns your microphone off: nobody hears you, you still hear everyone.", (AcceptableValueBase)null);
			MuteKey = settings.Local<KeyboardShortcut>("Microphone", "MuteKey", new KeyboardShortcut((KeyCode)109, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Turns the microphone off or on.", (AcceptableValueBase)null);
			Deafened = settings.Local<bool>("Headphones", "Deafened", false, "Turns the other players off: you hear nobody, and your microphone is off too until you turn them back on.", (AcceptableValueBase)null);
			DeafenKey = settings.Local<KeyboardShortcut>("Headphones", "DeafenKey", new KeyboardShortcut((KeyCode)104, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Turns the other players off or on.", (AcceptableValueBase)null);
			Sound = settings.Local<VoiceSound>("Sound", "Sound", (VoiceSound)0, "In space, a voice comes from the player's cat and gets quieter with distance. Flat, everyone sounds the same wherever they are. In the lobby voices are always flat.", (AcceptableValueBase)null);
			HearDistance = settings.Local<float>("Sound", "HearDistance", 25f, "In space, how far away a voice is still heard, in metres.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 80f));
			FullVolumeDistance = settings.Local<float>("Sound", "FullVolumeDistance", 3f, "In space, up to this distance a voice is at full volume, in metres.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f));
			ShowSpeakers = settings.Local<bool>("Screen", "ShowSpeakers", true, "Lists who is talking right now in a corner of the screen during a level.", (AcceptableValueBase)null);
			Corner = settings.Local<SpeakersCorner>("Screen", "Corner", SpeakersCorner.TopRight, "The corner of the screen for the list of who is talking.", (AcceptableValueBase)null);
			NameTags = settings.Local<bool>("Screen", "NameTags", true, "Colours the name over a cat while that player is talking.", (AcceptableValueBase)null);
			Notices = settings.Local<bool>("Screen", "Notices", true, "Says on the screen when the microphone or the headphones are turned off or on with the keys.", (AcceptableValueBase)null);
		}
	}
}
namespace Purr.Scene
{
	public sealed class NameTags
	{
		public static readonly Color Talking = new Color(0.55f, 0.95f, 0.55f, 1f);

		private readonly Dictionary<ulong, (TMP_Text Text, Color Original)> _colored = new Dictionary<ulong, (TMP_Text, Color)>();

		private readonly CatLogger _log;

		private bool _reported;

		public NameTags(CatLogger log)
		{
			_log = log;
		}

		public void Show(IReadOnlyCollection<ulong> speaking)
		{
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			foreach (ulong item in _colored.Keys.Where((ulong id) => !speaking.Contains(id)).ToList())
			{
				Restore(item);
			}
			foreach (ulong item2 in speaking)
			{
				if (!_colored.TryGetValue(item2, out (TMP_Text, Color) value) || !((Object)(object)value.Item1 != (Object)null) || ((Il2CppObjectBase)value.Item1).WasCollected)
				{
					TMP_Text val = NameOf(item2);
					if (!((Object)(object)val == (Object)null))
					{
						_colored[item2] = (val, ((Graphic)val).color);
						((Graphic)val).color = Talking;
					}
				}
			}
		}

		public void Clear()
		{
			foreach (ulong item in _colored.Keys.ToList())
			{
				Restore(item);
			}
		}

		private void Restore(ulong id)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			if (!_colored.Remove(id, out (TMP_Text, Color) value))
			{
				return;
			}
			try
			{
				if ((Object)(object)value.Item1 != (Object)null && !((Il2CppObjectBase)value.Item1).WasCollected)
				{
					((Graphic)value.Item1).color = value.Item2;
				}
			}
			catch (Exception)
			{
			}
		}

		private TMP_Text NameOf(ulong id)
		{
			try
			{
				PlayerGhostEntity val = default(PlayerGhostEntity);
				if (!GamePlayers.TryGetGhost(id, ref val))
				{
					return null;
				}
				return val.NameText;
			}
			catch (Exception ex)
			{
				if (!_reported)
				{
					_reported = true;
					_log.Warning("Reading the name over a cat failed: " + ex.Message);
				}
				return null;
			}
		}
	}
	public sealed class SpeakersView
	{
		public const string ObjectName = "Purr_Speakers";

		public const float Margin = 24f;

		private CountList _list;

		private IntPtr _hudPointer;

		public bool IsShown
		{
			get
			{
				if (_list != null && _list.IsAlive)
				{
					return ((Component)_list.Rect).gameObject.activeSelf;
				}
				return false;
			}
		}

		public void Show(IReadOnlyList<string> names, Sprite icon, string title, SpeakersCorner corner)
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Expected O, but got Unknown
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Expected O, but got Unknown
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			RectTransform root = HudLayer.Root;
			if ((Object)(object)root == (Object)null || names == null || names.Count == 0)
			{
				Hide();
				return;
			}
			if (_list == null || !_list.IsAlive || ((Il2CppObjectBase)root).Pointer != _hudPointer)
			{
				CountList list = _list;
				if (list != null)
				{
					list.Destroy();
				}
				_list = CountList.Create((Transform)(object)root, "Purr_Speakers", new CountListStyle
				{
					PlateAlpha = 0.35f,
					Scale = 0.85f
				});
				_hudPointer = ((Il2CppObjectBase)root).Pointer;
			}
			bool flag = (uint)(corner - 2) <= 1u;
			bool flag2 = flag;
			flag = ((corner == SpeakersCorner.TopRight || corner == SpeakersCorner.BottomRight) ? true : false);
			bool flag3 = flag;
			((Component)_list.Rect).gameObject.SetActive(true);
			_list.Show(new CountListContent(new CountRow(icon, title, string.Empty, false, false), (IReadOnlyList<CountRow>)((IEnumerable<string>)names).Select((Func<string, CountRow>)((string name) => new CountRow((Sprite)null, name, string.Empty, false, false))).ToList(), true), flag2);
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(flag3 ? 1f : 0f, flag2 ? 0f : 1f);
			RectTransform rect = _list.Rect;
			rect.anchorMin = val;
			rect.anchorMax = val;
			rect.pivot = val;
			rect.anchoredPosition = new Vector2(flag3 ? (-24f) : 24f, flag2 ? 24f : (-24f));
		}

		public void Hide()
		{
			if (_list != null && _list.IsAlive)
			{
				((Component)_list.Rect).gameObject.SetActive(false);
			}
		}

		public void Destroy()
		{
			CountList list = _list;
			if (list != null)
			{
				list.Destroy();
			}
			_list = null;
		}
	}
}
namespace Purr.Logic
{
	public enum MicMode
	{
		PushToTalk,
		OpenMic
	}
	public enum SpeakersCorner
	{
		TopLeft,
		TopRight,
		BottomLeft,
		BottomRight
	}
	public enum MicNotice
	{
		None,
		MicrophoneOff,
		MicrophoneOn,
		HeadphonesOff,
		HeadphonesOn
	}
	public static class MicLogic
	{
		public static bool Transmits(bool enabled, MicMode mode, bool pushToTalkHeld, bool muted, bool deafened)
		{
			if (enabled && !muted && !deafened)
			{
				return mode == MicMode.OpenMic || pushToTalkHeld;
			}
			return false;
		}

		public static bool Hears(bool enabled, bool deafened)
		{
			if (enabled)
			{
				return !deafened;
			}
			return false;
		}

		public static MicNotice MuteNotice(bool muted)
		{
			if (!muted)
			{
				return MicNotice.MicrophoneOn;
			}
			return MicNotice.MicrophoneOff;
		}

		public static MicNotice DeafenNotice(bool deafened)
		{
			if (!deafened)
			{
				return MicNotice.HeadphonesOn;
			}
			return MicNotice.HeadphonesOff;
		}

		public static IReadOnlyList<string> SpeakerNames(IEnumerable<ulong> speakers, ulong local, Func<ulong, string> name, string you)
		{
			List<ulong> list = speakers?.Distinct().ToList() ?? new List<ulong>();
			List<string> list2 = new List<string>();
			if (list.Contains(local))
			{
				list2.Add(you);
			}
			list2.AddRange((from id in list
				where id != local
				select name(id) into text
				select (!string.IsNullOrWhiteSpace(text)) ? text : "?").OrderBy<string, string>((string text) => text, StringComparer.CurrentCultureIgnoreCase));
			return list2;
		}
	}
}