Decompiled source of SoloChat v0.1.1

SoloChat.dll

Decompiled 8 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("SoloChat")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1+11e95d11e76704deee643ca1c94491a02f3e0905")]
[assembly: AssemblyProduct("SoloChat")]
[assembly: AssemblyTitle("SoloChat")]
[assembly: AssemblyVersion("0.1.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SoloChat
{
	[HarmonyPatch(typeof(ChatManager), "Update")]
	[HarmonyPriority(0)]
	internal static class ChatManagerUpdatePatch
	{
		private const int ExpectedGuardCount = 2;

		private static readonly MethodInfo VanillaMultiplayerCheck = AccessTools.Method(typeof(SemiFunc), "IsMultiplayer", Type.EmptyTypes, (Type[])null);

		private static readonly MethodInfo SoloChatEnabledCheck = AccessTools.Method(typeof(ChatManagerUpdatePatch), "ChatEnabledForThisUpdate", Type.EmptyTypes, (Type[])null);

		internal static bool InstalledSuccessfully { get; private set; }

		internal static string FailureReason { get; private set; } = "The ChatManager.Update transpiler did not run.";

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			List<int> list2 = new List<int>();
			if (VanillaMultiplayerCheck == null || SoloChatEnabledCheck == null)
			{
				FailureReason = "Required chat compatibility methods could not be resolved.";
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)FailureReason);
				}
				return list;
			}
			for (int i = 0; i < list.Count; i++)
			{
				if (CodeInstructionExtensions.Calls(list[i], VanillaMultiplayerCheck))
				{
					list2.Add(i);
				}
			}
			if (list2.Count != 2)
			{
				FailureReason = $"Expected {2} SemiFunc.IsMultiplayer guards in ChatManager.Update, " + $"but found {list2.Count}.";
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogError((object)FailureReason);
				}
				return list;
			}
			foreach (int item in list2)
			{
				list[item].opcode = OpCodes.Call;
				list[item].operand = SoloChatEnabledCheck;
			}
			InstalledSuccessfully = true;
			FailureReason = null;
			ManualLogSource log3 = Plugin.Log;
			if (log3 != null)
			{
				log3.LogInfo((object)$"Replaced {list2.Count} singleplayer chat guards in ChatManager.Update.");
			}
			return list;
		}

		private static bool ChatEnabledForThisUpdate()
		{
			return true;
		}
	}
	[BepInPlugin("mathe.solochat", "Solo Chat", "0.1.1")]
	[BepInProcess("REPO.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "mathe.solochat";

		public const string PluginName = "Solo Chat";

		public const string PluginVersion = "0.1.1";

		private Harmony _harmony;

		private SoloTtsController _ttsController;

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			try
			{
				_harmony = new Harmony("mathe.solochat");
				_harmony.PatchAll();
				if (!ChatManagerUpdatePatch.InstalledSuccessfully)
				{
					throw new InvalidOperationException(ChatManagerUpdatePatch.FailureReason);
				}
				_ttsController = ((Component)this).gameObject.AddComponent<SoloTtsController>();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Vanilla ChatManager and native TTS are enabled in singleplayer. The configured base-game chat key and vanilla mod chat calls are active.");
			}
			catch (Exception ex)
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
				_harmony = null;
				((BaseUnityPlugin)this).Logger.LogError((object)("Solo Chat could not safely recognize the current ChatManager.Update implementation. No chat behavior was changed. " + ex));
			}
		}

		private void OnDestroy()
		{
			if ((Object)(object)_ttsController != (Object)null)
			{
				_ttsController.Shutdown();
				_ttsController = null;
			}
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_harmony = null;
		}
	}
	internal sealed class SoloTtsController : MonoBehaviour
	{
		private static readonly FieldInfo TtsPlayerAvatarField = AccessTools.Field(typeof(TTSVoice), "playerAvatar");

		private static readonly FieldInfo PlayerAvatarVoiceChatField = AccessTools.Field(typeof(PlayerAvatar), "voiceChat");

		private TTSVoice _voice;

		private AudioSource _audioSource;

		private PlayerAvatar _boundAvatar;

		private bool _creating;

		private bool _ready;

		private bool _resourceFailureLogged;

		private string _pendingMessage;

		private bool _pendingCrouching;

		internal static SoloTtsController Instance { get; private set; }

		internal static TTSVoice Voice
		{
			get
			{
				if (!((Object)(object)Instance != (Object)null))
				{
					return null;
				}
				return Instance._voice;
			}
		}

		internal static bool IsSpeaking
		{
			get
			{
				if ((Object)(object)Instance != (Object)null && Instance._ready && (Object)(object)Instance._audioSource != (Object)null)
				{
					return Instance._audioSource.isPlaying;
				}
				return false;
			}
		}

		private void Awake()
		{
			Instance = this;
		}

		private void Update()
		{
			if (SemiFunc.IsMultiplayer())
			{
				DestroySoloVoice();
			}
			else if (!((Object)(object)LevelGenerator.Instance == (Object)null) && LevelGenerator.Instance.Generated && Object.op_Implicit((Object)(object)PlayerAvatar.instance))
			{
				if (Object.op_Implicit((Object)(object)_voice) && (Object)(object)_boundAvatar != (Object)(object)PlayerAvatar.instance)
				{
					BindAvatar(PlayerAvatar.instance);
				}
				if (!Object.op_Implicit((Object)(object)_voice) && !_creating && !_resourceFailureLogged)
				{
					((MonoBehaviour)this).StartCoroutine(CreateNativeVoice(PlayerAvatar.instance));
				}
			}
		}

		internal static bool HasExistingVanillaVoice(PlayerAvatar avatar)
		{
			if (!Object.op_Implicit((Object)(object)avatar) || PlayerAvatarVoiceChatField == null)
			{
				return false;
			}
			object? value = PlayerAvatarVoiceChatField.GetValue(avatar);
			PlayerVoiceChat val = (PlayerVoiceChat)((value is PlayerVoiceChat) ? value : null);
			if (Object.op_Implicit((Object)(object)val))
			{
				return Object.op_Implicit((Object)(object)val.ttsVoice);
			}
			return false;
		}

		internal static bool TrySpeak(PlayerAvatar avatar, string message, bool crouching)
		{
			if ((Object)(object)Instance == (Object)null || !Object.op_Implicit((Object)(object)avatar) || string.IsNullOrEmpty(message))
			{
				return false;
			}
			return Instance.QueueOrSpeak(avatar, message, crouching);
		}

		internal static bool Owns(TTSVoice voice)
		{
			if ((Object)(object)Instance != (Object)null)
			{
				return (Object)(object)Instance._voice == (Object)(object)voice;
			}
			return false;
		}

		internal void Shutdown()
		{
			((MonoBehaviour)this).StopAllCoroutines();
			DestroySoloVoice();
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		private bool QueueOrSpeak(PlayerAvatar avatar, string message, bool crouching)
		{
			if (SemiFunc.IsMultiplayer())
			{
				return false;
			}
			if (_resourceFailureLogged)
			{
				return false;
			}
			if (Object.op_Implicit((Object)(object)_voice) && (Object)(object)_boundAvatar != (Object)(object)avatar)
			{
				BindAvatar(avatar);
			}
			if (_ready && Object.op_Implicit((Object)(object)_voice))
			{
				_voice.TTSSpeakNow(message, crouching);
				return true;
			}
			_pendingMessage = message;
			_pendingCrouching = crouching;
			if (!_creating)
			{
				((MonoBehaviour)this).StartCoroutine(CreateNativeVoice(avatar));
			}
			return true;
		}

		private IEnumerator CreateNativeVoice(PlayerAvatar avatar)
		{
			_creating = true;
			if (!TryCreateNativeVoice(avatar))
			{
				_creating = false;
				yield break;
			}
			yield return null;
			_creating = false;
			if (Object.op_Implicit((Object)(object)_voice))
			{
				_ready = true;
				Plugin.Log.LogInfo((object)"Native singleplayer TTS voice is ready.");
				if (!string.IsNullOrEmpty(_pendingMessage))
				{
					string pendingMessage = _pendingMessage;
					bool pendingCrouching = _pendingCrouching;
					_pendingMessage = null;
					_voice.TTSSpeakNow(pendingMessage, pendingCrouching);
				}
			}
		}

		private bool TryCreateNativeVoice(PlayerAvatar avatar)
		{
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			if (TtsPlayerAvatarField == null)
			{
				LogResourceFailure("TTSVoice.playerAvatar could not be resolved.");
				return false;
			}
			PlayerVoiceChat val = null;
			GameObject val2 = Resources.Load<GameObject>("Voice");
			if (Object.op_Implicit((Object)(object)val2))
			{
				val = val2.GetComponent<PlayerVoiceChat>();
			}
			if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val.TTSprefab))
			{
				PlayerVoiceChat[] array = Resources.FindObjectsOfTypeAll<PlayerVoiceChat>();
				foreach (PlayerVoiceChat val3 in array)
				{
					if (Object.op_Implicit((Object)(object)val3) && Object.op_Implicit((Object)(object)val3.TTSprefab))
					{
						val = val3;
						break;
					}
				}
			}
			if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val.TTSprefab))
			{
				LogResourceFailure("The vanilla Voice prefab or its TTS prefab could not be found.");
				return false;
			}
			GameObject val4 = Object.Instantiate<GameObject>(val.TTSprefab, ((Component)this).transform);
			((Object)val4).name = "Solo Chat Vanilla TTS";
			val4.transform.localPosition = Vector3.zero;
			val4.transform.localRotation = Quaternion.identity;
			if (!val4.activeSelf)
			{
				val4.SetActive(true);
			}
			_voice = val4.GetComponent<TTSVoice>();
			_audioSource = val4.GetComponent<AudioSource>();
			if (!Object.op_Implicit((Object)(object)_voice) || !Object.op_Implicit((Object)(object)_audioSource))
			{
				Object.Destroy((Object)(object)val4);
				_voice = null;
				_audioSource = null;
				LogResourceFailure("The vanilla TTS prefab is missing TTSVoice or AudioSource.");
				return false;
			}
			_audioSource.outputAudioMixerGroup = val.mixerTTSSound;
			_audioSource.spatialBlend = 0f;
			_audioSource.volume = 1f;
			BindAvatar(avatar);
			return true;
		}

		private void BindAvatar(PlayerAvatar avatar)
		{
			_boundAvatar = avatar;
			if (Object.op_Implicit((Object)(object)_voice) && TtsPlayerAvatarField != null)
			{
				TtsPlayerAvatarField.SetValue(_voice, avatar);
			}
		}

		private void DestroySoloVoice()
		{
			_pendingMessage = null;
			_creating = false;
			_ready = false;
			_boundAvatar = null;
			_audioSource = null;
			if (Object.op_Implicit((Object)(object)_voice))
			{
				Object.Destroy((Object)(object)((Component)_voice).gameObject);
				_voice = null;
			}
		}

		private void LogResourceFailure(string reason)
		{
			if (!_resourceFailureLogged)
			{
				_resourceFailureLogged = true;
				Plugin.Log.LogError((object)("Native singleplayer TTS could not initialize. " + reason));
			}
		}
	}
	[HarmonyPatch(typeof(PlayerAvatar), "ChatMessageSpeak")]
	internal static class PlayerAvatarChatMessageSpeakPatch
	{
		private static bool Prefix(PlayerAvatar __instance, string _message, bool crouching)
		{
			if (SemiFunc.IsMultiplayer() || SoloTtsController.HasExistingVanillaVoice(__instance))
			{
				return true;
			}
			return !SoloTtsController.TrySpeak(__instance, _message, crouching);
		}
	}
	[HarmonyPatch(typeof(TTSVoice), "Update")]
	internal static class SoloTtsVoiceUpdatePatch
	{
		private static bool Prefix(TTSVoice __instance, ref bool ___isSpeaking, AudioSource ___audioSource)
		{
			if (!SoloTtsController.Owns(__instance))
			{
				return true;
			}
			___isSpeaking = Object.op_Implicit((Object)(object)___audioSource) && ___audioSource.isPlaying;
			return false;
		}
	}
	[HarmonyPatch(typeof(LobbyChatUI), "Update")]
	internal static class LobbyChatUiUpdatePatch
	{
		private static void Prefix(LobbyChatUI __instance, ref TTSVoice ___ttsVoice)
		{
			if (!SemiFunc.IsMultiplayer() && __instance.isGameplay && Object.op_Implicit((Object)(object)SoloTtsController.Voice))
			{
				___ttsVoice = SoloTtsController.Voice;
			}
		}
	}
	[HarmonyPatch(typeof(ChatManager), "PossessChatCustomLogic")]
	internal static class ChatManagerPossessVoicePatch
	{
		private static void Postfix(ref float ___isSpeakingTimer)
		{
			if (!SemiFunc.IsMultiplayer() && SoloTtsController.IsSpeaking && ___isSpeakingTimer < 0.2f)
			{
				___isSpeakingTimer = 0.2f;
			}
		}
	}
}