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;
}
}
}