using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using BetterSpectate.Compatibility;
using BetterSpectate.Patches;
using GameNetcodeStuff;
using HarmonyLib;
using SpectateEnemy;
using UnityEngine;
using UnityEngine.InputSystem;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("BetterSpectate")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("BetterSpectate")]
[assembly: AssemblyCopyright("Copyright © 2024")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("89d76a0b-65c7-4246-8645-3d523e10258e")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace BetterSpectate
{
public class BetterSpectateAPI
{
public bool isModLoaded => (Object)(object)BetterSpectateBase.instance != (Object)null;
public bool isFirstPersonSpectateEnabled => PlayerControllerB_Patch.GetFirstPersonEnabled();
public bool isZoomEnabled => PlayerControllerB_Patch.GetZoomEnabled();
public bool isFirstPersonSpectating => PlayerControllerB_Patch.IsPlayerInFirstPerson();
public float zoomDistance => PlayerControllerB_Patch.GetZoomDistance();
public float zoomSpeed => PlayerControllerB_Patch.GetZoomSpeed();
public float maxZoom => SpectateUtils.GetMaxZoom();
public float minZoom => SpectateUtils.GetMinZoom();
}
public class ConfigSetup
{
private const string CONFIG_FILE_NAME = "Fusition.BetterSpectate";
private static ConfigFile config;
private ConfigEntry<bool> isZoomEnabled;
private ConfigEntry<float> maxZoomDistance;
private ConfigEntry<float> minZoomDistance;
private ConfigEntry<float> defaultZoomDistance;
private ConfigEntry<float> zoomSpeed;
private ConfigEntry<bool> isFirstPersonEnabled;
private ConfigEntry<bool> isFirstPersonDefault;
private ConfigEntry<string> firstPersonKeybind;
public static void Initialize()
{
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: Expected O, but got Unknown
string text = Path.Combine(Paths.ConfigPath, "Fusition.BetterSpectate.cfg");
config = new ConfigFile(text, true);
ConfigEntry<bool> val = config.Bind<bool>("Zoom Settings", "Third Person Zoom Enabled", true, "Allows spectators to zoom in and out.");
ConfigEntry<float> val2 = config.Bind<float>("Zoom Settings", "Max Zoom Distance", 15f, "Furthest distance a spectator can zoom out.");
ConfigEntry<float> val3 = config.Bind<float>("Zoom Settings", "Min Zoom Distance", 1f, "Closest distance a spectator can zoom in.");
ConfigEntry<float> val4 = config.Bind<float>("Zoom Settings", "Default Zoom Distance", 1.4f, "Distance a spectator is set upon death.");
ConfigEntry<float> val5 = config.Bind<float>("Zoom Settings", "Zoom Speed", 0.4f, "Speed that scrolling will zoom in or out.");
ConfigEntry<bool> val6 = config.Bind<bool>("General Settings", "First Person Spectate Enabled", true, "Allows spectators to enter a first person view.");
ConfigEntry<bool> val7 = config.Bind<bool>("General Settings", "Default to First Person", true, "Defaults players to a first person view on death.");
ConfigEntry<string> val8 = config.Bind<string>("General Settings", "First Person Keybind", "P", "Sets the keybind to switch between perspectives.");
PlayerControllerB_Patch.SetFirstPersonEnabled(val6.Value);
PlayerControllerB_Patch.SetFirstPersonToggle(val7.Value);
PlayerControllerB_Patch.InitializeFirstPersonSpectateInputAction("<Keyboard>/" + val8.Value);
PlayerControllerB_Patch.SetZoomEnabled(val.Value);
PlayerControllerB_Patch.SetZoomDistance(val4.Value);
PlayerControllerB_Patch.SetDefaultZoomDistance(val4.Value);
PlayerControllerB_Patch.SetZoomSpeed(val5.Value);
SpectateUtils.SetMaxZoom(val2.Value);
SpectateUtils.SetMinZoom(val3.Value);
}
}
[BepInPlugin("Fusition.BetterSpectate", "BetterSpectate", "1.0.0.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class BetterSpectateBase : BaseUnityPlugin
{
public const string MOD_GUID = "Fusition.BetterSpectate";
private const string MOD_NAME = "BetterSpectate";
private const string MOD_VERS = "1.0.0.0";
private readonly Harmony harmony = new Harmony("Fusition.BetterSpectate");
public static BetterSpectateBase instance;
internal static ManualLogSource fusLogSource;
private void Awake()
{
if ((Object)(object)instance == (Object)null)
{
instance = this;
}
((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
fusLogSource = Logger.CreateLogSource("Fusition.BetterSpectate");
fusLogSource.LogInfo((object)SpectateEnemyCompat.enabled);
ConfigSetup.Initialize();
harmony.PatchAll(typeof(PlayerControllerB_Patch));
harmony.PatchAll(typeof(StartOfRound_Patch));
}
}
}
namespace BetterSpectate.Patches
{
[HarmonyPatch(typeof(PlayerControllerB))]
public class PlayerControllerB_Patch
{
private static bool isZoomEnabled;
private static float zoomDistance = 1.4f;
private static float zoomSpeed = 0.4f;
private static float defaultZoomDistance;
private static bool isFirstPersonEnabled;
private static InputAction firstPersonSpectateAction;
private static bool firstPersonSpectateToggle = false;
private static bool isInFirstPerson = false;
private static bool inputDisabledForCompat = false;
[HarmonyPatch(typeof(PlayerControllerB), "LateUpdate")]
[HarmonyPrefix]
public static void LateUpdate_Patch(PlayerControllerB __instance)
{
//IL_0077: Unknown result type (might be due to invalid IL or missing references)
//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)__instance == (Object)(object)GameNetworkManager.Instance.localPlayerController && (Object)(object)__instance.spectatedPlayerScript != (Object)null)
{
if (__instance.spectatedPlayerScript.isHoldingObject)
{
__instance.spectatedPlayerScript.currentlyHeldObjectServer.parentObject = (firstPersonSpectateToggle ? ((Component)__instance.spectatedPlayerScript.localItemHolder).transform : ((Component)__instance.spectatedPlayerScript.serverItemHolder).transform);
}
if (UnityInput.Current.mouseScrollDelta.y != 0f && isZoomEnabled)
{
((Renderer)__instance.thisPlayerModel).enabled = false;
SetZoomDistance((zoomDistance + UnityInput.Current.mouseScrollDelta.y / -120f * zoomSpeed).ZoomClamp());
}
if (SpectateEnemyCompat.enabled)
{
SpectateEnemyCompat.CheckIfSpectatingEnemies();
}
if (!inputDisabledForCompat && firstPersonSpectateAction.WasPressedThisFrame())
{
SwitchPerspective(__instance);
}
}
}
[HarmonyPatch(typeof(PlayerControllerB), "RaycastSpectateCameraAroundPivot")]
[HarmonyPrefix]
public static bool RaycastSpectateCameraAroundPivot_Patch(PlayerControllerB __instance, RaycastHit ___hit, int ___walkableSurfacesNoPlayersMask)
{
//IL_004b: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: 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)
if ((Object)(object)__instance.spectatedPlayerScript != (Object)null)
{
Transform transform = ((Component)__instance.spectatedPlayerScript.visorCamera).transform;
if (isFirstPersonEnabled && firstPersonSpectateToggle)
{
((Component)__instance.playersManager.spectateCamera).transform.position = transform.position;
((Component)__instance.playersManager.spectateCamera).transform.rotation = transform.rotation;
isInFirstPerson = true;
}
else if (isZoomEnabled)
{
RaycastCameraToZoomDistance(__instance, ___hit, ___walkableSurfacesNoPlayersMask);
}
return false;
}
return true;
}
[HarmonyPatch(typeof(PlayerControllerB), "SpectateNextPlayer")]
[HarmonyPrefix]
public static bool SpectateNextPlayer_Patch(PlayerControllerB __instance, RaycastHit ___hit, int ___walkableSurfacesNoPlayersMask)
{
//IL_0183: Unknown result type (might be due to invalid IL or missing references)
//IL_018f: Unknown result type (might be due to invalid IL or missing references)
int num = 0;
if ((Object)(object)__instance.spectatedPlayerScript != (Object)null)
{
num = (int)__instance.spectatedPlayerScript.playerClientId;
if ((Object)(object)__instance == (Object)(object)GameNetworkManager.Instance.localPlayerController)
{
SetModelVisibilityForThirdPerson(__instance.spectatedPlayerScript);
}
}
for (int i = 0; i < __instance.playersManager.allPlayerScripts.Length; i++)
{
num = (num + 1) % __instance.playersManager.allPlayerScripts.Length;
if (__instance.playersManager.allPlayerScripts[num].isPlayerDead || !__instance.playersManager.allPlayerScripts[num].isPlayerControlled || !((Object)(object)__instance.playersManager.allPlayerScripts[num] != (Object)(object)__instance))
{
continue;
}
__instance.spectatedPlayerScript = __instance.playersManager.allPlayerScripts[num];
if ((Object)(object)__instance == (Object)(object)GameNetworkManager.Instance.localPlayerController)
{
if (!firstPersonSpectateToggle)
{
BetterSpectateBase.fusLogSource.LogInfo((object)"Model visibility adjusted for dead player in third person");
SetModelVisibilityForThirdPerson(__instance.spectatedPlayerScript);
}
else
{
BetterSpectateBase.fusLogSource.LogInfo((object)"Model visibility adjusted for dead player in first person");
SetModelVisibilityForFirstPerson(__instance.spectatedPlayerScript);
}
}
__instance.SetSpectatedPlayerEffects(false);
return false;
}
if ((Object)(object)__instance.deadBody != (Object)null && ((Component)__instance.deadBody).gameObject.activeSelf)
{
__instance.spectateCameraPivot.position = __instance.deadBody.bodyParts[0].position;
RaycastSpectateCameraAroundPivot_Patch(__instance, ___hit, ___walkableSurfacesNoPlayersMask);
}
StartOfRound.Instance.SetPlayerSafeInShip();
return false;
}
public static void InitializeFirstPersonSpectateInputAction(string binding)
{
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Expected O, but got Unknown
firstPersonSpectateAction = new InputAction("FirstPersonSpectatePressed", (InputActionType)0, binding, (string)null, (string)null, (string)null);
firstPersonSpectateAction.Enable();
}
public static bool IsPlayerInFirstPerson()
{
return isInFirstPerson;
}
public static bool GetZoomEnabled()
{
return isZoomEnabled;
}
public static void SetZoomEnabled(bool enabled)
{
isZoomEnabled = true;
}
public static bool GetFirstPersonEnabled()
{
return isFirstPersonEnabled;
}
public static void SetFirstPersonEnabled(bool enabled)
{
isFirstPersonEnabled = true;
}
public static void SwitchPerspective(PlayerControllerB controller)
{
//IL_0052: Unknown result type (might be due to invalid IL or missing references)
firstPersonSpectateToggle = !firstPersonSpectateToggle;
if (!firstPersonSpectateToggle)
{
BetterSpectateBase.fusLogSource.LogInfo((object)"Player Toggled to Third Person");
SetModelVisibilityForThirdPerson(controller.spectatedPlayerScript);
((Component)controller.spectateCameraPivot).transform.rotation = ((Component)controller.spectatedPlayerScript.visorCamera).transform.rotation;
zoomDistance = defaultZoomDistance;
}
else
{
BetterSpectateBase.fusLogSource.LogInfo((object)"Player Toggled to First Person");
SetModelVisibilityForFirstPerson(controller.spectatedPlayerScript);
}
}
public static void SetFirstPersonToggle(bool value)
{
firstPersonSpectateToggle = value;
}
public static void SetModelVisibilityForFirstPerson(PlayerControllerB controller)
{
((Renderer)controller.thisPlayerModelArms).enabled = true;
((Renderer)controller.thisPlayerModel).enabled = false;
((Renderer)controller.thisPlayerModelLOD1).enabled = false;
((Renderer)controller.thisPlayerModelLOD2).enabled = false;
}
public static void SetModelVisibilityForThirdPerson(PlayerControllerB controller)
{
((Renderer)controller.thisPlayerModelArms).enabled = false;
((Renderer)controller.thisPlayerModel).enabled = true;
((Renderer)controller.thisPlayerModelLOD1).enabled = true;
((Renderer)controller.thisPlayerModelLOD2).enabled = true;
}
public static float GetZoomDistance()
{
return zoomDistance;
}
public static void SetZoomDistance(float value)
{
zoomDistance = value;
}
public static void SetDefaultZoomDistance(float value)
{
defaultZoomDistance = value;
}
public static float GetZoomSpeed()
{
return zoomSpeed;
}
public static void SetZoomSpeed(float value)
{
zoomSpeed = value;
}
public static void SetInputDisabled(bool value)
{
inputDisabledForCompat = value;
}
private static void RaycastCameraToZoomDistance(PlayerControllerB controller, RaycastHit hit, int walkableSurfacesNoPlayersMask)
{
//IL_0009: Unknown result type (might be due to invalid IL or missing references)
//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_0023: 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_0056: Unknown result type (might be due to invalid IL or missing references)
Ray val = default(Ray);
((Ray)(ref val))..ctor(controller.spectateCameraPivot.position, -controller.spectateCameraPivot.forward);
if (Physics.Raycast(val, ref hit, zoomDistance, walkableSurfacesNoPlayersMask, (QueryTriggerInteraction)1))
{
((Component)controller.playersManager.spectateCamera).transform.position = ((Ray)(ref val)).GetPoint(((RaycastHit)(ref hit)).distance - 0.25f);
}
else
{
((Component)controller.playersManager.spectateCamera).transform.position = ((Ray)(ref val)).GetPoint(zoomDistance - 0.1f);
}
((Component)controller.playersManager.spectateCamera).transform.LookAt(controller.spectateCameraPivot);
}
}
internal static class SpectateUtils
{
private static float maxZoomDistance;
private static float minZoomDistance;
public static float ZoomClamp(this float value)
{
if (maxZoomDistance > minZoomDistance)
{
if (value > maxZoomDistance)
{
return maxZoomDistance;
}
if (value < minZoomDistance)
{
return minZoomDistance;
}
return value;
}
return PlayerControllerB_Patch.GetZoomDistance();
}
public static float GetMaxZoom()
{
return maxZoomDistance;
}
public static void SetMaxZoom(float value)
{
maxZoomDistance = value;
}
public static float GetMinZoom()
{
return minZoomDistance;
}
public static void SetMinZoom(float value)
{
minZoomDistance = value;
}
}
[HarmonyPatch(typeof(StartOfRound))]
public class StartOfRound_Patch
{
[HarmonyPatch(typeof(StartOfRound), "ReviveDeadPlayers")]
[HarmonyPostfix]
public static void ReviveDeadPlayers_Patch(PlayerControllerB[] ___allPlayerScripts)
{
for (int i = 0; i < ___allPlayerScripts.Length; i++)
{
if ((Object)(object)___allPlayerScripts[i] != (Object)(object)GameNetworkManager.Instance.localPlayerController)
{
((Renderer)___allPlayerScripts[i].thisPlayerModelArms).enabled = false;
((Renderer)___allPlayerScripts[i].thisPlayerModel).enabled = true;
((Renderer)___allPlayerScripts[i].thisPlayerModelLOD1).enabled = true;
((Renderer)___allPlayerScripts[i].thisPlayerModelLOD2).enabled = true;
if (___allPlayerScripts[i].isHoldingObject)
{
___allPlayerScripts[i].currentlyHeldObjectServer.parentObject = ((Component)___allPlayerScripts[i].serverItemHolder).transform;
}
}
}
}
}
}
namespace BetterSpectate.Compatibility
{
public static class SpectateEnemyCompat
{
private static bool? _enabled;
public static bool enabled
{
get
{
if (!_enabled.HasValue)
{
_enabled = Chainloader.PluginInfos.ContainsKey("SpectateEnemy");
}
return _enabled.Value;
}
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static void CheckIfSpectatingEnemies()
{
if (SpectateEnemiesAPI.IsLoaded && SpectateEnemiesAPI.IsSpectatingEnemies)
{
PlayerControllerB_Patch.SetFirstPersonToggle(value: false);
PlayerControllerB_Patch.SetInputDisabled(value: true);
}
}
}
}