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.Logging;
using BetterSpectator;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("BetterSpectator")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("Extended spectator controls and information")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyInformationalVersion("1.2.0")]
[assembly: AssemblyProduct("BetterSpectator")]
[assembly: AssemblyTitle("BetterSpectator")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.2.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.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;
}
}
}
[HarmonyPatch(typeof(HUDManager))]
internal class HUDManager_Patch
{
private static GameObject canvasObj;
private static Text clockText;
private static bool onLocalPlayerDeadTriggered;
private static bool onLocalPlayerAliveTriggered;
[HarmonyPatch("Update")]
[HarmonyPostfix]
public static void HarmonyPatch_Update()
{
if (IsLocalPlayerDeadAndSpectating() && !onLocalPlayerDeadTriggered)
{
onLocalPlayerDeadTriggered = true;
onLocalPlayerAliveTriggered = false;
OnLocalPlayerDead();
}
else if (!IsLocalPlayerDeadAndSpectating() && !onLocalPlayerAliveTriggered)
{
onLocalPlayerAliveTriggered = true;
onLocalPlayerDeadTriggered = false;
OnLocalPlayerAlive();
}
if (IsLocalPlayerDeadAndSpectating())
{
UpdateExtraInput();
UpdateClock();
}
}
private static void OnLocalPlayerDead()
{
SetupClock();
SetupChat();
}
private static void OnLocalPlayerAlive()
{
RemoveClock();
RemoveChat();
}
private static void SetupClock()
{
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0022: Expected O, but got Unknown
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
if (Settings.isClockEnabled.Value)
{
canvasObj = new GameObject("Canvas");
Canvas val = canvasObj.AddComponent<Canvas>();
val.renderMode = (RenderMode)0;
val.worldCamera = Camera.main;
CanvasScaler val2 = canvasObj.AddComponent<CanvasScaler>();
val2.uiScaleMode = (ScaleMode)1;
val2.screenMatchMode = (ScreenMatchMode)1;
clockText = CreateText(((TMP_Text)HUDManager.Instance.clockNumber).text, ((TMP_Text)HUDManager.Instance.clockNumber).font.sourceFontFile, 30, canvasObj.transform, new Vector2(20f, -20f), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f));
}
}
private static void RemoveClock()
{
if ((Object)(object)clockText != (Object)null)
{
Object.Destroy((Object)(object)((Component)clockText).gameObject);
clockText = null;
}
if ((Object)(object)canvasObj != (Object)null)
{
Object.Destroy((Object)(object)canvasObj);
canvasObj = null;
}
}
private static void UpdateExtraInput()
{
if (Settings.isExtraInputEnabled.Value)
{
float num = IngamePlayerSettings.Instance.playerInput.actions.FindAction("SwitchItem", false).ReadValue<float>();
if (((ButtonControl)Keyboard.current[(Key)62]).wasPressedThisFrame || num > 0f)
{
SpectateNextPlayer(forward: true);
}
else if (((ButtonControl)Keyboard.current[(Key)61]).wasPressedThisFrame || num < 0f)
{
SpectateNextPlayer(forward: false);
}
}
}
private static void UpdateClock()
{
if (Settings.isClockEnabled.Value)
{
clockText.text = ((TMP_Text)HUDManager.Instance.clockNumber).text;
}
}
private static void SetupChat()
{
if (Settings.isChatEnabled.Value)
{
HUDManager.Instance.HideHUD(false);
HUDManager.Instance.HUDElements[0].targetAlpha = 0f;
HUDManager.Instance.HUDElements[2].targetAlpha = 0f;
HUDManager.Instance.HUDElements[5].targetAlpha = 0f;
((Component)HUDManager.Instance.HUDElements[5].canvasGroup).gameObject.SetActive(false);
}
}
private static void RemoveChat()
{
if (Settings.isChatEnabled.Value)
{
HUDManager.Instance.HUDElements[0].targetAlpha = 1f;
HUDManager.Instance.HUDElements[2].targetAlpha = 1f;
HUDManager.Instance.HUDElements[5].targetAlpha = 1f;
((Component)HUDManager.Instance.HUDElements[5].canvasGroup).gameObject.SetActive(true);
}
}
[HarmonyPatch("UpdateBoxesSpectateUI")]
[HarmonyPostfix]
public static void HarmonyPatch_UpdateBoxesSpectateUI()
{
//IL_013e: Unknown result type (might be due to invalid IL or missing references)
if (!Settings.isCauseOfDeathEnabled.Value || !IsLocalPlayerDeadAndSpectating())
{
return;
}
Animator val = null;
PlayerControllerB currentPlayer;
for (int i = 0; i < StartOfRound.Instance.allPlayerScripts.Length; i++)
{
currentPlayer = StartOfRound.Instance.allPlayerScripts[i];
if (!currentPlayer.isPlayerDead || !HUDManager.Instance.spectatingPlayerBoxes.Values.Contains(currentPlayer))
{
continue;
}
val = HUDManager.Instance.spectatingPlayerBoxes.FirstOrDefault((KeyValuePair<Animator, PlayerControllerB> x) => (Object)(object)x.Value == (Object)(object)currentPlayer).Key;
if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeSelf)
{
TextMeshProUGUI[] componentsInChildren = ((Component)val).gameObject.GetComponentsInChildren<TextMeshProUGUI>();
if ((Object)(object)((Component)componentsInChildren[1]).gameObject.GetComponent<ContentSizeFitter>() == (Object)null)
{
ContentSizeFitter val2 = ((Component)componentsInChildren[1]).gameObject.AddComponent<ContentSizeFitter>();
val2.horizontalFit = (FitMode)2;
val2.verticalFit = (FitMode)2;
}
((TMP_Text)componentsInChildren[1]).alignment = (TextAlignmentOptions)2049;
((TMP_Text)componentsInChildren[1]).text = "\nDead: " + Enum.GetName(typeof(CauseOfDeath), currentPlayer.causeOfDeath);
}
}
}
[HarmonyPatch("AddPlayerChatMessageClientRpc")]
[HarmonyPostfix]
[ClientRpc]
public static void HarmonyPatch_AddPlayerChatMessageClientRpc(ref string chatMessage, ref int playerId)
{
if (Settings.isChatEnabled.Value && IsLocalPlayerDeadAndSpectating())
{
NetworkManager networkManager = ((NetworkBehaviour)HUDManager.Instance).NetworkManager;
PlayerControllerB localPlayerController = GameNetworkManager.Instance.localPlayerController;
StartOfRound playersManager = HUDManager.Instance.playersManager;
if (networkManager != null && networkManager.IsListening && localPlayerController.isPlayerDead && (networkManager.IsClient || networkManager.IsHost) && (playersManager.allPlayerScripts[playerId].isPlayerDead || IsSpectatedPlayer(playerId) || IsInRangeOfSpectatedPlayer(playerId)))
{
HUDManager.Instance.AddChatMessage(chatMessage, playersManager.allPlayerScripts[playerId].playerUsername);
}
}
}
[HarmonyPatch("AddChatMessage")]
[HarmonyPostfix]
private static void AddChatMessage(ref string chatMessage, string nameOfUserWhoTyped = "")
{
if (!Settings.isChatEnabled.Value)
{
return;
}
PlayerControllerB localPlayerController = GameNetworkManager.Instance.localPlayerController;
int playerIndex = GetPlayerIndex(nameOfUserWhoTyped);
string text = nameOfUserWhoTyped + "(Dead)";
if (playerIndex >= 0 && localPlayerController.playersManager.allPlayerScripts[playerIndex].isPlayerDead && !HUDManager.Instance.ChatMessageHistory[HUDManager.Instance.ChatMessageHistory.Count - 1].Contains(text))
{
HUDManager.Instance.ChatMessageHistory[HUDManager.Instance.ChatMessageHistory.Count - 1] = HUDManager.Instance.ChatMessageHistory[HUDManager.Instance.ChatMessageHistory.Count - 1].Replace(nameOfUserWhoTyped, text);
((TMP_Text)HUDManager.Instance.chatText).text = "";
for (int i = 0; i < HUDManager.Instance.ChatMessageHistory.Count; i++)
{
TextMeshProUGUI chatText = HUDManager.Instance.chatText;
((TMP_Text)chatText).text = ((TMP_Text)chatText).text + "\n" + HUDManager.Instance.ChatMessageHistory[i];
}
}
}
private static int GetPlayerIndex(string playerName)
{
int result = -1;
PlayerControllerB localPlayerController = GameNetworkManager.Instance.localPlayerController;
for (int i = 0; i < localPlayerController.playersManager.allPlayerScripts.Length; i++)
{
if (localPlayerController.playersManager.allPlayerScripts[i].playerUsername.Equals(playerName))
{
result = i;
break;
}
}
return result;
}
private static bool IsSpectatedPlayer(int playerId)
{
PlayerControllerB localPlayerController = GameNetworkManager.Instance.localPlayerController;
if ((Object)(object)localPlayerController.spectatedPlayerScript != (Object)null && (int)localPlayerController.spectatedPlayerScript.playerClientId == playerId)
{
return true;
}
return false;
}
private static bool IsInRangeOfSpectatedPlayer(int playerId)
{
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_004b: Unknown result type (might be due to invalid IL or missing references)
PlayerControllerB spectatedPlayerScript = GameNetworkManager.Instance.localPlayerController.spectatedPlayerScript;
StartOfRound playersManager = HUDManager.Instance.playersManager;
if ((spectatedPlayerScript.holdingWalkieTalkie && playersManager.allPlayerScripts[playerId].holdingWalkieTalkie) || Vector3.Distance(((Component)spectatedPlayerScript).transform.position, ((Component)playersManager.allPlayerScripts[playerId]).transform.position) <= 25f)
{
return true;
}
return false;
}
[HarmonyPatch("EnableChat_performed")]
[HarmonyPostfix]
private static void HarmonyPatch_EnableChat_performed(ref CallbackContext context)
{
if (Settings.isChatEnabled.Value && IsLocalPlayerDeadAndSpectating())
{
PlayerControllerB localPlayerController = GameNetworkManager.Instance.localPlayerController;
if (((CallbackContext)(ref context)).performed && !((Object)(object)localPlayerController == (Object)null) && ((((NetworkBehaviour)localPlayerController).IsOwner && (!((NetworkBehaviour)HUDManager.Instance).IsServer || localPlayerController.isHostPlayerObject)) || localPlayerController.isTestingPlayer) && localPlayerController.isPlayerDead && !localPlayerController.inTerminalMenu)
{
ShipBuildModeManager.Instance.CancelBuildMode(true);
localPlayerController.isTypingChat = true;
((Selectable)HUDManager.Instance.chatTextField).Select();
HUDManager.Instance.PingHUDElement(HUDManager.Instance.Chat, 0.1f, 1f, 1f);
((Behaviour)HUDManager.Instance.typingIndicator).enabled = true;
}
}
}
[HarmonyPatch("SubmitChat_performed")]
[HarmonyPostfix]
public static void HarmonyPatch_SubmitChat_performed(ref CallbackContext context)
{
if (!Settings.isChatEnabled.Value || !IsLocalPlayerDeadAndSpectating())
{
return;
}
PlayerControllerB localPlayerController = GameNetworkManager.Instance.localPlayerController;
if (((CallbackContext)(ref context)).performed && !((Object)(object)localPlayerController == (Object)null) && localPlayerController.isTypingChat && ((((NetworkBehaviour)localPlayerController).IsOwner && (!((NetworkBehaviour)HUDManager.Instance).IsServer || localPlayerController.isHostPlayerObject)) || localPlayerController.isTestingPlayer))
{
if (!string.IsNullOrEmpty(HUDManager.Instance.chatTextField.text) && HUDManager.Instance.chatTextField.text.Length < 50)
{
HUDManager.Instance.AddTextToChatOnServer(HUDManager.Instance.chatTextField.text, (int)localPlayerController.playerClientId);
}
localPlayerController.isTypingChat = false;
HUDManager.Instance.chatTextField.text = "";
EventSystem.current.SetSelectedGameObject((GameObject)null);
HUDManager.Instance.PingHUDElement(HUDManager.Instance.Chat, 2f, 1f, 0.2f);
((Behaviour)HUDManager.Instance.typingIndicator).enabled = false;
}
}
private static void SpectateNextPlayer(bool forward)
{
//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
PlayerControllerB localPlayerController = GameNetworkManager.Instance.localPlayerController;
int num = 0;
bool flag = false;
if ((Object)(object)localPlayerController.spectatedPlayerScript != (Object)null)
{
num = (int)localPlayerController.spectatedPlayerScript.playerClientId;
}
if (forward)
{
for (int i = 0; i < localPlayerController.playersManager.allPlayerScripts.Length; i++)
{
num = (num + 1) % localPlayerController.playersManager.allPlayerScripts.Length;
if (!localPlayerController.playersManager.allPlayerScripts[num].isPlayerDead && localPlayerController.playersManager.allPlayerScripts[num].isPlayerControlled && (Object)(object)localPlayerController.playersManager.allPlayerScripts[num] != (Object)(object)localPlayerController)
{
localPlayerController.spectatedPlayerScript = localPlayerController.playersManager.allPlayerScripts[num];
localPlayerController.SetSpectatedPlayerEffects(false);
flag = true;
break;
}
}
}
else
{
for (int num2 = localPlayerController.playersManager.allPlayerScripts.Length - 1; num2 >= 0; num2--)
{
if (num == 0)
{
num = (int)localPlayerController.playersManager.allPlayerScripts[localPlayerController.playersManager.allPlayerScripts.Length - 1].playerClientId;
}
num = (num - 1) % localPlayerController.playersManager.allPlayerScripts.Length;
if (!localPlayerController.playersManager.allPlayerScripts[num].isPlayerDead && localPlayerController.playersManager.allPlayerScripts[num].isPlayerControlled && (Object)(object)localPlayerController.playersManager.allPlayerScripts[num] != (Object)(object)localPlayerController)
{
localPlayerController.spectatedPlayerScript = localPlayerController.playersManager.allPlayerScripts[num];
localPlayerController.SetSpectatedPlayerEffects(false);
flag = true;
break;
}
}
}
if (!flag)
{
if ((Object)(object)localPlayerController.deadBody != (Object)null && ((Component)localPlayerController.deadBody).gameObject.activeSelf)
{
localPlayerController.spectateCameraPivot.position = localPlayerController.deadBody.bodyParts[0].position;
localPlayerController.RaycastSpectateCameraAroundPivot();
}
StartOfRound.Instance.SetPlayerSafeInShip();
}
}
private static Text CreateText(string text, Font font, int fontSize, Transform parent, Vector2 localPosition, Vector2 anchorMin, Vector2 anchorMax, Vector2 pivot)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Expected O, but got Unknown
//IL_004e: 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)
//IL_005c: Unknown result type (might be due to invalid IL or missing references)
//IL_0065: Unknown result type (might be due to invalid IL or missing references)
//IL_006e: Unknown result type (might be due to invalid IL or missing references)
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
GameObject val = new GameObject();
Text val2 = val.AddComponent<Text>();
val2.font = font;
val2.fontSize = fontSize;
val2.text = text;
ContentSizeFitter val3 = val.AddComponent<ContentSizeFitter>();
val3.horizontalFit = (FitMode)2;
val3.verticalFit = (FitMode)2;
RectTransform component = val.GetComponent<RectTransform>();
((Transform)component).SetParent(parent, false);
((Transform)component).localPosition = Vector2.op_Implicit(localPosition);
component.anchorMin = anchorMin;
component.anchorMax = anchorMax;
component.pivot = pivot;
((Transform)component).localScale = new Vector3(1f, 1f, 1f);
return val2;
}
private static bool IsLocalPlayerDeadAndSpectating()
{
return (Object)(object)GameNetworkManager.Instance != (Object)null && (Object)(object)GameNetworkManager.Instance.localPlayerController != (Object)null && GameNetworkManager.Instance.localPlayerController.isPlayerDead && HUDManager.Instance.hasLoadedSpectateUI;
}
}
namespace BetterSpectator
{
[BepInPlugin("BetterSpectator", "BetterSpectator", "1.2.0")]
public class Plugin : BaseUnityPlugin
{
private readonly Harmony harmony = new Harmony("Aelstraz.BetterSpectator");
private static ManualLogSource logger;
private void Awake()
{
logger = ((BaseUnityPlugin)this).Logger;
Settings.Load(((BaseUnityPlugin)this).Config);
harmony.PatchAll(typeof(HUDManager_Patch));
Log("Plugin BetterSpectator is loaded!");
}
public static void Log(string info)
{
logger.LogInfo((object)info);
}
}
internal static class Settings
{
public static ConfigEntry<bool> isClockEnabled;
public static ConfigEntry<bool> isCauseOfDeathEnabled;
public static ConfigEntry<bool> isChatEnabled;
public static ConfigEntry<bool> isExtraInputEnabled;
private static bool isLoaded;
public static void Load(ConfigFile config)
{
if (!isLoaded)
{
isClockEnabled = config.Bind<bool>("General", "Show Clock", true, "Shows the time of day when spectating");
isCauseOfDeathEnabled = config.Bind<bool>("General", "Show Cause Of Death", true, "Shows each players cause of death when spectating");
isChatEnabled = config.Bind<bool>("General", "Show Chat", true, "Enable text chat when spectating, but only with other dead players(and see alive players text chat if you are spectating them, or if they are in range of the spectated player/using a walkie talkie)");
isExtraInputEnabled = config.Bind<bool>("General", "Enable Extra Input", true, "Enables using the scroll wheel/arrow keys to scroll forwards/back between alive players when spectating");
isLoaded = true;
}
}
}
public static class PluginInfo
{
public const string PLUGIN_GUID = "BetterSpectator";
public const string PLUGIN_NAME = "BetterSpectator";
public const string PLUGIN_VERSION = "1.2.0";
}
}
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
internal sealed class IgnoresAccessChecksToAttribute : Attribute
{
public IgnoresAccessChecksToAttribute(string assemblyName)
{
}
}
}