Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BetterSpectate ForkForMe v1.2.1
BetterSpectate.dll
Decompiled 3 weeks agousing System; 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.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("BetterSpectate")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("BetterSpectate")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("89d76a0b-65c7-4246-8645-3d523e10258e")] [assembly: AssemblyFileVersion("1.2.1.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")] [assembly: AssemblyVersion("1.2.1.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(); } [BepInPlugin("Fusition.BetterSpectate", "BetterSpectate", "1.2.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class BetterSpectateBase : BaseUnityPlugin { public const string MOD_GUID = "Fusition.BetterSpectate"; internal const string MOD_NAME = "BetterSpectate"; internal const string MOD_VERS = "1.2.1.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)); } } public class ConfigSetup { private const string CONFIG_FILE_NAME = "Fusition.BetterSpectate"; private static ConfigFile config; public static void Initialize() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown config = new ConfigFile(Path.Combine(Paths.ConfigPath, "Fusition.BetterSpectate.cfg"), 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> obj = config.Bind<float>("Zoom Settings", "Min Zoom Distance", 1f, "Closest distance a spectator can zoom in."); ConfigEntry<float> obj2 = config.Bind<float>("Zoom Settings", "Default Zoom Distance", 1.4f, "Distance a spectator is set upon death."); ConfigEntry<float> val3 = config.Bind<float>("Zoom Settings", "Zoom Speed", 0.4f, "Speed that scrolling will zoom in or out."); ConfigEntry<bool> val4 = config.Bind<bool>("General Settings", "First Person Spectate Enabled", true, "Allows spectators to enter a first person view."); ConfigEntry<bool> obj3 = config.Bind<bool>("General Settings", "Default to First Person", true, "Defaults players to a first person view on death."); ConfigEntry<string> val5 = config.Bind<string>("General Settings", "First Person Keybind", "P", "Sets the keybind to switch between perspectives."); PlayerControllerB_Patch.SetFirstPersonEnabled(val4.Value); PlayerControllerB_Patch.SetFirstPersonToggle(obj3.Value); PlayerControllerB_Patch.InitializeFirstPersonSpectateInputAction("<Keyboard>/" + val5.Value); PlayerControllerB_Patch.SetZoomEnabled(val.Value); PlayerControllerB_Patch.SetZoomDistance(obj2.Value); PlayerControllerB_Patch.SetDefaultZoomDistance(obj2.Value); PlayerControllerB_Patch.SetZoomSpeed(val3.Value); SpectateUtils.SetMaxZoom(val2.Value); SpectateUtils.SetMinZoom(obj.Value); } } } 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_0071: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: 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() && isFirstPersonEnabled) { SwitchPerspective(__instance); } } } [HarmonyPatch(typeof(PlayerControllerB), "RaycastSpectateCameraAroundPivot")] [HarmonyPrefix] public static bool RaycastSpectateCameraAroundPivot_Patch(PlayerControllerB __instance, RaycastHit ___hit, int ___walkableSurfacesNoPlayersMask) { //IL_0041: 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_007c: 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; return false; } isInFirstPerson = false; if (isZoomEnabled) { RaycastCameraToZoomDistance(__instance, ___hit, ___walkableSurfacesNoPlayersMask); return false; } return true; } return true; } [HarmonyPatch(typeof(PlayerControllerB), "SpectateNextPlayer")] [HarmonyPrefix] public static bool SpectateNextPlayer_Patch(PlayerControllerB __instance, RaycastHit ___hit, int ___walkableSurfacesNoPlayersMask) { //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)GameNetworkManager.Instance == (Object)null) { return true; } int num = 0; if ((Object)(object)__instance.spectatedPlayerScript != (Object)null) { num = (int)__instance.spectatedPlayerScript.playerClientId; if ((Object)(object)GameNetworkManager.Instance.localPlayerController != (Object)null && (Object)(object)__instance == (Object)(object)GameNetworkManager.Instance.localPlayerController) { SetModelVisibilityForThirdPerson(__instance.spectatedPlayerScript); } } if ((Object)(object)__instance.playersManager == (Object)null || __instance.playersManager.allPlayerScripts == null) { return true; } for (int i = 0; i < __instance.playersManager.allPlayerScripts.Length; i++) { num = (num + 1) % __instance.playersManager.allPlayerScripts.Length; PlayerControllerB val = __instance.playersManager.allPlayerScripts[num]; if ((Object)(object)val == (Object)null || val.isPlayerDead || !val.isPlayerControlled || !((Object)(object)val != (Object)(object)__instance)) { continue; } __instance.spectatedPlayerScript = val; if ((Object)(object)GameNetworkManager.Instance.localPlayerController != (Object)null && (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); } } try { __instance.SetSpectatedPlayerEffects(false); } catch (NullReferenceException) { ManualLogSource fusLogSource = BetterSpectateBase.fusLogSource; if (fusLogSource != null) { fusLogSource.LogWarning((object)"NullReferenceException in SetSpectatedPlayerEffects caught and suppressed"); } } 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); } if ((Object)(object)StartOfRound.Instance != (Object)null) { StartOfRound.Instance.SetPlayerSafeInShip(); } return false; } public static void InitializeFirstPersonSpectateInputAction(string binding) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0014: 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 = enabled; } public static bool GetFirstPersonEnabled() { return isFirstPersonEnabled; } public static void SetFirstPersonEnabled(bool enabled) { isFirstPersonEnabled = enabled; } public static void SwitchPerspective(PlayerControllerB controller) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (isFirstPersonEnabled) { 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) { if (isFirstPersonEnabled) { firstPersonSpectateToggle = value; } else { firstPersonSpectateToggle = false; } } public static void SetModelVisibilityForFirstPerson(PlayerControllerB controller) { if (!((Object)(object)controller == (Object)null)) { if ((Object)(object)controller.thisPlayerModelArms != (Object)null) { ((Renderer)controller.thisPlayerModelArms).enabled = true; } if ((Object)(object)controller.thisPlayerModel != (Object)null) { ((Renderer)controller.thisPlayerModel).enabled = false; } if ((Object)(object)controller.thisPlayerModelLOD1 != (Object)null) { ((Renderer)controller.thisPlayerModelLOD1).enabled = false; } if ((Object)(object)controller.thisPlayerModelLOD2 != (Object)null) { ((Renderer)controller.thisPlayerModelLOD2).enabled = false; } } } public static void SetModelVisibilityForThirdPerson(PlayerControllerB controller) { if (!((Object)(object)controller == (Object)null)) { if ((Object)(object)controller.thisPlayerModelArms != (Object)null) { ((Renderer)controller.thisPlayerModelArms).enabled = false; } if ((Object)(object)controller.thisPlayerModel != (Object)null) { ((Renderer)controller.thisPlayerModel).enabled = true; } if ((Object)(object)controller.thisPlayerModelLOD1 != (Object)null) { ((Renderer)controller.thisPlayerModelLOD1).enabled = true; } if ((Object)(object)controller.thisPlayerModelLOD2 != (Object)null) { ((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_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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; private static bool _wasSpectating; 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) { if (!_wasSpectating) { PlayerControllerB_Patch.SetFirstPersonToggle(value: false); PlayerControllerB_Patch.SetInputDisabled(value: true); } _wasSpectating = true; } else { if (_wasSpectating) { PlayerControllerB_Patch.SetInputDisabled(value: false); } _wasSpectating = false; } } } }