using System; using System.Diagnostics; using System.IO; 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 HarmonyLib; using Microsoft.CodeAnalysis; using Pigeon.Movement; using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Sparroh")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1")] [assembly: AssemblyProduct("ThirdPersonMode")] [assembly: AssemblyTitle("ThirdPersonMode")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.1.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; } } } public static class ConfigManager { private const float DebounceSeconds = 0.25f; private static ConfigFile config; private static ManualLogSource logger; private static FileSystemWatcher configWatcher; private static volatile bool reloadPending; private static float lastReloadTime; public static ConfigEntry ToggleKey { get; private set; } public static ConfigEntry OrbitDistance { get; private set; } public static ConfigEntry ShoulderOffset { get; private set; } public static ConfigEntry ShoulderHeightOffset { get; private set; } public static ConfigEntry HideHudInThirdPerson { get; private set; } public static ConfigEntry AdsReturnsToFirstPerson { get; private set; } public static ConfigEntry StartInThirdPerson { get; private set; } public static ConfigEntry ScrollToZoom { get; private set; } public static ConfigEntry MinOrbitDistance { get; private set; } public static ConfigEntry MaxOrbitDistance { get; private set; } public static ConfigEntry CrouchBodyOffset { get; private set; } public static void Initialize(ConfigFile configFile, ManualLogSource log) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Expected O, but got Unknown //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Expected O, but got Unknown //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Expected O, but got Unknown //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Expected O, but got Unknown config = configFile; logger = log; ToggleKey = config.Bind("General", "ToggleKey", (Key)50, "Key used to toggle gameplay third-person mode. Uses Unity Input System key names (e.g. Digit0, V, F5)."); OrbitDistance = config.Bind("Camera", "OrbitDistance", 3.25f, new ConfigDescription("Default camera distance behind the player.", (AcceptableValueBase)(object)new AcceptableValueRange(1.5f, 8f), Array.Empty())); MinOrbitDistance = config.Bind("Camera", "MinOrbitDistance", 1.5f, new ConfigDescription("Minimum scroll-zoom distance.", (AcceptableValueBase)(object)new AcceptableValueRange(0.75f, 5f), Array.Empty())); MaxOrbitDistance = config.Bind("Camera", "MaxOrbitDistance", 5.5f, new ConfigDescription("Maximum scroll-zoom distance.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 12f), Array.Empty())); ShoulderOffset = config.Bind("Camera", "ShoulderOffset", 0.9f, new ConfigDescription("Over-the-shoulder horizontal offset (positive = right shoulder). Higher values clear the body from the crosshair.", (AcceptableValueBase)(object)new AcceptableValueRange(-2f, 2f), Array.Empty())); ShoulderHeightOffset = config.Bind("Camera", "ShoulderHeightOffset", 1f, new ConfigDescription("Extra camera height while in third person.", (AcceptableValueBase)(object)new AcceptableValueRange(-1f, 2f), Array.Empty())); ScrollToZoom = config.Bind("Camera", "ScrollToZoom", true, "Allow mouse wheel to change orbit distance while in third person."); HideHudInThirdPerson = config.Bind("Gameplay", "HideHudInThirdPerson", false, "Hide the main HUD while gameplay third-person is active."); AdsReturnsToFirstPerson = config.Bind("Gameplay", "AdsReturnsToFirstPerson", true, "Temporarily switch back to first person while aiming (ADS), then restore third person when you stop aiming."); StartInThirdPerson = config.Bind("Gameplay", "StartInThirdPerson", false, "Automatically enter third person when the local player spawns."); CrouchBodyOffset = config.Bind("Camera", "CrouchBodyOffset", -0.55f, new ConfigDescription("Extra world-space Y applied to the third-person body while crouching (negative lowers the model so feet stay planted).", (AcceptableValueBase)(object)new AcceptableValueRange(-1.5f, 0.5f), Array.Empty())); config.SettingChanged += OnSettingChanged; try { SetupFileWatcher(); } catch (Exception ex) { logger.LogError((object)("Error setting up config file watcher: " + ex.Message)); } } public static void Tick() { if (!reloadPending || Time.unscaledTime - lastReloadTime < 0.25f) { return; } reloadPending = false; lastReloadTime = Time.unscaledTime; try { config.Reload(); logger.LogInfo((object)"Config reloaded from disk."); NotifyConfigReloaded(); } catch (Exception ex) { logger.LogError((object)("Error reloading config: " + ex.Message)); } } public static void Dispose() { if (config != null) { config.SettingChanged -= OnSettingChanged; } if (configWatcher != null) { configWatcher.EnableRaisingEvents = false; configWatcher.Changed -= OnConfigFileChanged; configWatcher.Created -= OnConfigFileChanged; configWatcher.Renamed -= OnConfigFileChanged; configWatcher.Dispose(); configWatcher = null; } } private static void SetupFileWatcher() { configWatcher = new FileSystemWatcher(Paths.ConfigPath, "sparroh.thirdpersonmode.cfg"); configWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite; configWatcher.Changed += OnConfigFileChanged; configWatcher.Created += OnConfigFileChanged; configWatcher.Renamed += OnConfigFileChanged; configWatcher.EnableRaisingEvents = true; } private static void OnConfigFileChanged(object sender, FileSystemEventArgs e) { reloadPending = true; } private static void OnSettingChanged(object sender, EventArgs e) { NotifyConfigReloaded(); } private static void NotifyConfigReloaded() { ThirdPersonController.Instance?.OnConfigReloaded(); } } [BepInPlugin("sparroh.thirdpersonmode", "ThirdPersonMode", "1.0.1")] [MycoMod(/*Could not decode attribute arguments.*/)] public class ThirdPersonModePlugin : BaseUnityPlugin { public const string PluginGUID = "sparroh.thirdpersonmode"; public const string PluginName = "ThirdPersonMode"; public const string PluginVersion = "1.0.1"; internal static ManualLogSource Log; internal static Harmony Harmony; private void Awake() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ConfigManager.Initialize(((BaseUnityPlugin)this).Config, Log); Harmony = new Harmony("sparroh.thirdpersonmode"); try { Harmony.PatchAll(typeof(ThirdPersonPatches)); Log.LogInfo((object)"ThirdPersonMode v1.0.1 loaded."); } catch (Exception arg) { Log.LogError((object)$"Failed to apply Harmony patches: {arg}"); } ((Component)this).gameObject.AddComponent(); } private void Update() { ConfigManager.Tick(); } private void OnDestroy() { ConfigManager.Dispose(); Harmony harmony = Harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal sealed class ThirdPersonActions { private enum ActionKind { None, Equip, Reload, Fire, Melee, Throw, Fly } private static FieldInfo throwableThrowTpField; private static FieldInfo wingsuitFlyAnimField; private static FieldInfo tpAnimancerField; private static bool fieldsResolved; private float actionUntil; private Gun boundGun; private ActionKind currentAction; private Key equipKey; private Key fireKey; private int lastSelectedSlot = int.MinValue; private Key reloadKey; private bool slotInitialized; private Key stowKey; private bool suppressStow; private bool wasFiring; private bool wasFlying; private bool wasMeleeActive; private bool wasReloading; private bool wasThrowableActive; public bool BlocksLocomotion { get { if (currentAction == ActionKind.Fly) { return true; } ActionKind actionKind = currentAction; if ((uint)(actionKind - 4) <= 1u) { return Time.time < actionUntil; } return false; } } public void Reset() { boundGun = null; equipKey = (reloadKey = (fireKey = (stowKey = null))); wasReloading = false; wasFiring = false; wasMeleeActive = false; wasThrowableActive = false; wasFlying = false; currentAction = ActionKind.None; actionUntil = 0f; lastSelectedSlot = int.MinValue; slotInitialized = false; suppressStow = true; } public void Tick(Player player, PlayerAnimation playerAnim, ThirdPersonAnimator tpAnim, bool tpActive) { if (!tpActive || (Object)(object)player == (Object)null || (Object)(object)playerAnim == (Object)null || (Object)(object)tpAnim == (Object)null || !((Behaviour)tpAnim).isActiveAndEnabled) { Reset(); return; } ResolveFields(); EnsureAdditiveLayer(tpAnim); Wingsuit val = FindWingsuit(player); if ((Object)(object)val != (Object)null && val.IsFlying) { if (!wasFlying || currentAction != ActionKind.Fly) { Key wingsuitFlyKey = GetWingsuitFlyKey(val); if ((Object)(object)wingsuitFlyKey?.clip != (Object)null) { PlayBaseLoop(tpAnim, wingsuitFlyKey, ActionKind.Fly); } } wasFlying = true; wasMeleeActive = false; wasThrowableActive = false; return; } if (wasFlying) { wasFlying = false; currentAction = ActionKind.None; actionUntil = 0f; } int selectedGearSlot = player.SelectedGearSlot; if (!slotInitialized) { lastSelectedSlot = selectedGearSlot; slotInitialized = true; suppressStow = false; IGear selectedGear = player.SelectedGear; Gun val2 = (Gun)(object)((selectedGear is Gun) ? selectedGear : null); if (val2 != null && val2.Active) { BindGunSet(val2, player.Character); PlayGunKey(tpAnim, equipKey, ActionKind.Equip); wasReloading = val2.Reloading; wasFiring = val2.IsFiring; } } else if (selectedGearSlot != lastSelectedSlot) { lastSelectedSlot = selectedGearSlot; IGear selectedGear2 = player.SelectedGear; Gun val3 = (Gun)(object)((selectedGear2 is Gun) ? selectedGear2 : null); Gun val4 = boundGun; if ((Object)(object)val4 != (Object)null && ((Object)(object)val3 == (Object)null || (Object)(object)val3 != (Object)(object)val4)) { bool num = (Object)(object)player.Melee != (Object)null && ((Throwable)player.Melee).Active; IGear selectedGear3 = player.SelectedGear; Throwable val5 = (Throwable)(object)((selectedGear3 is Throwable) ? selectedGear3 : null); bool flag = val5 != null && !(val5 is MeleeGear) && val5.Active; if (!num && !flag && !suppressStow) { PlayGunKey(tpAnim, stowKey, ActionKind.None); } if ((Object)(object)val3 == (Object)null || !val3.Active) { boundGun = null; equipKey = (reloadKey = (fireKey = (stowKey = null))); } } if ((Object)(object)val3 != (Object)null && val3.Active) { BindGunSet(val3, player.Character); PlayGunKey(tpAnim, equipKey, ActionKind.Equip); wasReloading = val3.Reloading; wasFiring = val3.IsFiring; } } suppressStow = false; MeleeGear melee = player.Melee; bool flag2 = (Object)(object)melee != (Object)null && ((Throwable)melee).Active; if (flag2 && !wasMeleeActive) { Key meleeTpKey = GetMeleeTpKey(melee, player.Character); PlayOneShot(tpAnim, meleeTpKey, ActionKind.Melee); } wasMeleeActive = flag2; IGear selectedGear4 = player.SelectedGear; Throwable val6 = (Throwable)(object)((selectedGear4 is Throwable) ? selectedGear4 : null); if (val6 != null && !(val6 is MeleeGear)) { bool active = val6.Active; if (active && !wasThrowableActive) { Key throwableTpKey = GetThrowableTpKey(val6); PlayOneShot(tpAnim, throwableTpKey, ActionKind.Throw); } wasThrowableActive = active; } else { wasThrowableActive = false; } bool flag3 = BlocksLocomotion; if (flag3) { ActionKind actionKind = currentAction; bool flag4 = (uint)(actionKind - 4) <= 1u; flag3 = flag4; } if (flag3) { return; } IGear selectedGear5 = player.SelectedGear; Gun val7 = (Gun)(object)((selectedGear5 is Gun) ? selectedGear5 : null); if ((Object)(object)val7 == (Object)null || !val7.Active) { wasReloading = false; wasFiring = false; return; } if ((Object)(object)boundGun != (Object)(object)val7) { BindGunSet(val7, player.Character); if (currentAction != ActionKind.Equip || Time.time >= actionUntil) { PlayGunKey(tpAnim, equipKey, ActionKind.Equip); } wasReloading = val7.Reloading; wasFiring = val7.IsFiring; return; } bool reloading = val7.Reloading; if (reloading && !wasReloading) { PlayGunKey(tpAnim, reloadKey, ActionKind.Reload); } wasReloading = reloading; bool isFiring = val7.IsFiring; if (isFiring && !wasFiring && !reloading) { PlayGunKey(tpAnim, fireKey, ActionKind.Fire); } else if (isFiring && !reloading && currentAction == ActionKind.Fire && Time.time >= actionUntil) { PlayGunKey(tpAnim, fireKey, ActionKind.Fire); } wasFiring = isFiring; } public void OnGunAttached(Gun gun, Character character, ThirdPersonAnimator tpAnim) { if (!((Object)(object)gun == (Object)null) && !((Object)(object)tpAnim == (Object)null)) { BindGunSet(gun, character); EnsureAdditiveLayer(tpAnim); if (slotInitialized) { PlayGunKey(tpAnim, equipKey, ActionKind.Equip); wasReloading = gun.Reloading; wasFiring = gun.IsFiring; } } } private void BindGunSet(Gun gun, Character character) { boundGun = gun; equipKey = (reloadKey = (fireKey = (stowKey = null))); try { ThirdPersonGearAnimationSet animationSet = ThirdPersonReflection.GetAnimationSet(gun, character); if (!((Object)(object)animationSet == (Object)null)) { equipKey = CloneKey(animationSet.equip); reloadKey = CloneKey(animationSet.reload); fireKey = CloneKey(animationSet.fire); stowKey = CloneKey(((PlayerAnimationSet)animationSet).stow); } } catch (Exception ex) { ThirdPersonModePlugin.Log.LogWarning((object)("BindGunSet failed: " + ex.Message)); } } private void PlayGunKey(ThirdPersonAnimator tpAnim, Key key, ActionKind kind) { if ((Object)(object)key?.clip == (Object)null || (Object)(object)tpAnim == (Object)null) { return; } try { EnsureAdditiveLayer(tpAnim); tpAnim.PlayAdditive(key); currentAction = kind; float num = key.clip.length / Mathf.Max(key.speed, 0.01f); actionUntil = Time.time + Mathf.Clamp(num * 0.9f, 0.05f, 2.5f); } catch (Exception ex) { ThirdPersonModePlugin.Log.LogWarning((object)$"PlayGunKey({kind}) failed: {ex.Message}"); } } private void PlayOneShot(ThirdPersonAnimator tpAnim, Key key, ActionKind kind) { if ((Object)(object)key?.clip == (Object)null || (Object)(object)tpAnim == (Object)null) { return; } try { tpAnim.SetState(key, -1f, 0f); currentAction = kind; float num = key.clip.length / Mathf.Max(key.speed, 0.01f); actionUntil = Time.time + Mathf.Clamp(num * 0.95f, 0.1f, 3f); } catch (Exception ex) { ThirdPersonModePlugin.Log.LogWarning((object)$"PlayOneShot({kind}) failed: {ex.Message}"); } } private void PlayBaseLoop(ThirdPersonAnimator tpAnim, Key key, ActionKind kind) { if ((Object)(object)key?.clip == (Object)null || (Object)(object)tpAnim == (Object)null) { return; } try { tpAnim.SetState(key, -1f, 0f); currentAction = kind; actionUntil = float.MaxValue; } catch (Exception ex) { ThirdPersonModePlugin.Log.LogWarning((object)$"PlayBaseLoop({kind}) failed: {ex.Message}"); } } private static Wingsuit FindWingsuit(Player player) { try { IGear selectedGear = player.SelectedGear; Wingsuit val = (Wingsuit)(object)((selectedGear is Wingsuit) ? selectedGear : null); if (val != null) { return val; } IGear[] gear = player.Gear; if (gear == null) { return null; } foreach (IGear obj in gear) { Wingsuit val2 = (Wingsuit)(object)((obj is Wingsuit) ? obj : null); if (val2 != null) { return val2; } } } catch { } return null; } private static Key GetWingsuitFlyKey(Wingsuit wingsuit) { ResolveFields(); try { if (wingsuitFlyAnimField != null) { object? value = wingsuitFlyAnimField.GetValue(wingsuit); Key val = (Key)((value is Key) ? value : null); if ((Object)(object)val?.clip != (Object)null) { return CloneKey(val); } } } catch { } return null; } private static Key GetMeleeTpKey(MeleeGear melee, Character character) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown ResolveFields(); try { if (throwableThrowTpField != null) { object? value = throwableThrowTpField.GetValue(melee); Key val = (Key)((value is Key) ? value : null); if ((Object)(object)val?.clip != (Object)null) { return CloneKey(val); } } } catch { } if ((Object)(object)character?.MeleeAnimationThirdPerson != (Object)null) { return new Key(character.MeleeAnimationThirdPerson) { fadeDuration = 0.1f, speed = 1f }; } return null; } private static Key GetThrowableTpKey(Throwable throwable) { ResolveFields(); try { if (throwableThrowTpField != null) { object? value = throwableThrowTpField.GetValue(throwable); Key val = (Key)((value is Key) ? value : null); if ((Object)(object)val?.clip != (Object)null) { return CloneKey(val); } } } catch { } return null; } private static Key CloneKey(Key source) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown if (source == null || (Object)(object)source.clip == (Object)null) { return null; } return new Key(source); } private static void EnsureAdditiveLayer(ThirdPersonAnimator tpAnim) { try { if (tpAnimancerField == null) { tpAnimancerField = AccessTools.Field(typeof(ThirdPersonAnimator), "animator"); } object obj = tpAnimancerField?.GetValue(tpAnim); if (obj == null) { return; } object obj2 = AccessTools.Property(obj.GetType(), "Layers")?.GetValue(obj); if (obj2 == null) { return; } PropertyInfo propertyInfo = AccessTools.Property(obj2.GetType(), "Count"); if (((propertyInfo != null) ? ((int)propertyInfo.GetValue(obj2)) : 0) <= 1) { return; } PropertyInfo propertyInfo2 = AccessTools.Property(obj2.GetType(), "Item"); object obj3 = ((propertyInfo2 != null) ? propertyInfo2.GetValue(obj2, new object[1] { 1 }) : null); if (obj3 != null) { PropertyInfo propertyInfo3 = AccessTools.Property(obj3.GetType(), "Weight"); if (!(propertyInfo3 == null) && (float)propertyInfo3.GetValue(obj3) < 0.99f) { propertyInfo3.SetValue(obj3, 1f); } } } catch { } } private static void ResolveFields() { if (!fieldsResolved) { fieldsResolved = true; throwableThrowTpField = AccessTools.Field(typeof(Throwable), "throwAnimationThirdPerson"); wingsuitFlyAnimField = AccessTools.Field(typeof(Wingsuit), "thirdPersonFlyAnimation"); tpAnimancerField = AccessTools.Field(typeof(ThirdPersonAnimator), "animator"); } } } internal static class ThirdPersonCamera { private static FieldInfo thirdPersonModeField; private static FieldInfo orbitDistanceField; private static FieldInfo enableThirdPersonTimeField; private static FieldInfo minOrbitDistanceField; private static FieldInfo maxOrbitDistanceField; private static FieldInfo wasThirdPersonEnabledWithHudField; private static FieldInfo isAimingField; private static PropertyInfo allowFirstPersonRenderingProp; private static bool allowFirstPersonRenderingResolved; private static bool fieldsResolved; public static float AppliedOrbitDistance { get; set; } = 3.25f; public static void OnConfigReloaded() { AppliedOrbitDistance = Mathf.Clamp(ConfigManager.OrbitDistance.Value, ConfigManager.MinOrbitDistance.Value, ConfigManager.MaxOrbitDistance.Value); if (ThirdPersonController.IsGameplayThirdPersonActive) { ApplyOrbitSettings(PlayerLook.Instance); } } public static void ResetOrbitDistance() { AppliedOrbitDistance = ConfigManager.OrbitDistance.Value; } public static void ApplyOrbitSettings(PlayerLook look) { if ((Object)(object)look == (Object)null) { return; } ResolveFields(); if (orbitDistanceField != null) { orbitDistanceField.SetValue(look, AppliedOrbitDistance); } if (minOrbitDistanceField != null) { minOrbitDistanceField.SetValue(look, ConfigManager.MinOrbitDistance.Value); } if (maxOrbitDistanceField != null) { maxOrbitDistanceField.SetValue(look, ConfigManager.MaxOrbitDistance.Value); } if (ConfigManager.ScrollToZoom.Value && orbitDistanceField != null) { try { AppliedOrbitDistance = (float)orbitDistanceField.GetValue(look); return; } catch { return; } } if (orbitDistanceField != null) { orbitDistanceField.SetValue(look, AppliedOrbitDistance); } } public static void ApplyShoulderOffset(PlayerLook look) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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) //IL_0057: 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) float value = ConfigManager.ShoulderOffset.Value; float value2 = ConfigManager.ShoulderHeightOffset.Value; if (!Mathf.Approximately(value, 0f) || !Mathf.Approximately(value2, 0f)) { Transform transform = ((Component)look).transform; transform.position += transform.right * value + transform.up * value2; } } public static void MarkThirdPersonEnabled(PlayerLook look) { ResolveFields(); if (enableThirdPersonTimeField != null) { enableThirdPersonTimeField.SetValue(look, Time.time); } if (wasThirdPersonEnabledWithHudField != null) { wasThirdPersonEnabledWithHudField.SetValue(look, ConfigManager.HideHudInThirdPerson.Value); } } public static bool IsPlayerAiming(PlayerLook look) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) ResolveFields(); if (isAimingField != null) { try { if ((bool)isAimingField.GetValue(look)) { return true; } } catch { } } try { PlayerActions player = PlayerInput.Controls.Player; return ((PlayerActions)(ref player)).Aim.IsPressed(); } catch { return false; } } public static bool GetThirdPersonMode(PlayerLook look) { if ((Object)(object)look == (Object)null) { return false; } ResolveFields(); if (thirdPersonModeField == null) { thirdPersonModeField = AccessTools.Field(typeof(PlayerLook), "thirdPersonMode"); } try { return thirdPersonModeField != null && (bool)thirdPersonModeField.GetValue(look); } catch { return look.IsInThirdPerson; } } public static void SetThirdPersonMode(PlayerLook look, bool value) { ResolveFields(); if (thirdPersonModeField == null) { thirdPersonModeField = AccessTools.Field(typeof(PlayerLook), "thirdPersonMode"); } thirdPersonModeField?.SetValue(look, value); } public static void SetAllowFirstPersonRendering(bool allow) { if (!allowFirstPersonRenderingResolved) { allowFirstPersonRenderingResolved = true; try { Type type = AccessTools.TypeByName("UnityEngine.Rendering.Universal.UniversalRenderer") ?? AccessTools.TypeByName("UniversalRenderer"); if (type != null) { allowFirstPersonRenderingProp = AccessTools.Property(type, "AllowFirstPersonRendering"); } if (allowFirstPersonRenderingProp == null) { ThirdPersonModePlugin.Log.LogWarning((object)"UniversalRenderer.AllowFirstPersonRendering not found; FP arms may still render in TP."); } } catch (Exception ex) { ThirdPersonModePlugin.Log.LogWarning((object)("Failed resolving AllowFirstPersonRendering: " + ex.Message)); } } try { allowFirstPersonRenderingProp?.SetValue(null, allow); } catch (Exception ex2) { ThirdPersonModePlugin.Log.LogWarning((object)("Could not set AllowFirstPersonRendering: " + ex2.Message)); } } private static void ResolveFields() { if (!fieldsResolved) { Type? typeFromHandle = typeof(PlayerLook); thirdPersonModeField = AccessTools.Field(typeFromHandle, "thirdPersonMode"); orbitDistanceField = AccessTools.Field(typeFromHandle, "orbitDistance"); enableThirdPersonTimeField = AccessTools.Field(typeFromHandle, "enableThirdPersonTime"); minOrbitDistanceField = AccessTools.Field(typeFromHandle, "minOrbitDistance"); maxOrbitDistanceField = AccessTools.Field(typeFromHandle, "maxOrbitDistance"); wasThirdPersonEnabledWithHudField = AccessTools.Field(typeFromHandle, "wasThirdPersonEnabledWithHUD"); isAimingField = AccessTools.Field(typeFromHandle, "isAiming"); fieldsResolved = thirdPersonModeField != null && orbitDistanceField != null; if (!fieldsResolved) { ThirdPersonModePlugin.Log.LogError((object)"Failed to resolve PlayerLook third-person fields. TP mode will not work."); } } } } public sealed class ThirdPersonController : MonoBehaviour { internal static bool AllowVanillaDisable; private readonly ThirdPersonActions actions = new ThirdPersonActions(); private readonly ThirdPersonGearAttachment gearAttachment = new ThirdPersonGearAttachment(); private readonly ThirdPersonLocomotion locomotion = new ThirdPersonLocomotion(); private bool adsForcedFirstPerson; private bool startInTpPending; public static ThirdPersonController Instance { get; private set; } public static bool WantGameplayThirdPerson { get; private set; } public static bool IsGameplayThirdPersonActive { get; private set; } private void Awake() { Instance = this; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); gearAttachment.Actions = actions; locomotion.ShouldBlockLocomotion = () => actions.BlocksLocomotion; startInTpPending = ConfigManager.StartInThirdPerson.Value; ThirdPersonCamera.ResetOrbitDistance(); } private void Update() { if (!TryGetLocalContext(out var player, out var look)) { if (IsGameplayThirdPersonActive || WantGameplayThirdPerson) { ForceExit(); } return; } if (!player.IsAlive || look.IsSpectating) { if (WantGameplayThirdPerson || IsGameplayThirdPersonActive) { ExitGameplayThirdPerson(look, player, clearWant: false); } return; } if (startInTpPending) { startInTpPending = false; EnterGameplayThirdPerson(look, player); } if (WasTogglePressed()) { if (WantGameplayThirdPerson) { WantGameplayThirdPerson = false; ExitGameplayThirdPerson(look, player, clearWant: true); } else { EnterGameplayThirdPerson(look, player); } } if (!WantGameplayThirdPerson) { return; } if (ConfigManager.AdsReturnsToFirstPerson.Value && ThirdPersonCamera.IsPlayerAiming(look)) { if (!adsForcedFirstPerson) { adsForcedFirstPerson = true; SuspendForAds(look, player); } return; } if (adsForcedFirstPerson) { adsForcedFirstPerson = false; ResumeFromAds(look, player); } if (!IsGameplayThirdPersonActive || !ThirdPersonCamera.GetThirdPersonMode(look)) { if (ThirdPersonCamera.GetThirdPersonMode(look) && !IsGameplayThirdPersonActive) { return; } ApplyGameplayThirdPerson(look, player); } else { ThirdPersonCamera.ApplyOrbitSettings(look); } TickLocomotion(player); } private void LateUpdate() { if (IsGameplayThirdPersonActive && !adsForcedFirstPerson && TryGetLocalContext(out var player, out var look)) { ThirdPersonCamera.ApplyShoulderOffset(look); TickLocomotion(player); } } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { ForceExit(); Instance = null; } } internal void OnConfigReloaded() { ThirdPersonCamera.OnConfigReloaded(); } private void TickLocomotion(Player player) { if (!IsGameplayThirdPersonActive || adsForcedFirstPerson || (Object)(object)player == (Object)null) { gearAttachment.Tick(player, (player != null) ? player.Animator : null, tpActive: false); actions.Tick(player, (player != null) ? player.Animator : null, null, tpActive: false); return; } PlayerAnimation animator = player.Animator; ThirdPersonAnimator tpAnim = (((Object)(object)animator != (Object)null) ? animator.SelfThirdPersonAnimator : null); actions.Tick(player, animator, tpAnim, tpActive: true); locomotion.Tick(player, animator, tpAnim); gearAttachment.Tick(player, animator, tpActive: true); } private static bool TryGetLocalContext(out Player player, out PlayerLook look) { player = Player.LocalPlayer; look = PlayerLook.Instance; if ((Object)(object)player != (Object)null && (Object)(object)look != (Object)null) { return ((NetworkBehaviour)player).IsLocalPlayer; } return false; } private static bool WasTogglePressed() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) Keyboard current = Keyboard.current; if (current == null) { return false; } Key value = ConfigManager.ToggleKey.Value; try { return ((ButtonControl)current[value]).wasPressedThisFrame; } catch { return false; } } private void EnterGameplayThirdPerson(PlayerLook look, Player player) { WantGameplayThirdPerson = true; adsForcedFirstPerson = false; ThirdPersonCamera.ResetOrbitDistance(); locomotion.Reset(); actions.Reset(); gearAttachment.Reset(); ApplyGameplayThirdPerson(look, player); TickLocomotion(player); ThirdPersonModePlugin.Log.LogInfo((object)"Gameplay third-person enabled."); } private void ExitGameplayThirdPerson(PlayerLook look, Player player, bool clearWant) { if (clearWant) { WantGameplayThirdPerson = false; } adsForcedFirstPerson = false; gearAttachment.Reset(); actions.Reset(); locomotion.Reset(); RestoreFirstPerson(look, player); ThirdPersonModePlugin.Log.LogInfo((object)"Gameplay third-person disabled."); } private void ForceExit() { WantGameplayThirdPerson = false; adsForcedFirstPerson = false; IsGameplayThirdPersonActive = false; gearAttachment.Reset(); actions.Reset(); locomotion.Reset(); PlayerLook instance = PlayerLook.Instance; Player localPlayer = Player.LocalPlayer; if ((Object)(object)instance != (Object)null && (Object)(object)localPlayer != (Object)null) { RestoreFirstPerson(instance, localPlayer); } } private void SuspendForAds(PlayerLook look, Player player) { gearAttachment.Reset(); actions.Reset(); locomotion.Reset(); RestoreFirstPerson(look, player, keepWantFlag: true); } private void ResumeFromAds(PlayerLook look, Player player) { if (WantGameplayThirdPerson && player.IsAlive && !look.IsSpectating) { locomotion.Reset(); actions.Reset(); gearAttachment.Reset(); ApplyGameplayThirdPerson(look, player); TickLocomotion(player); } } private void ApplyGameplayThirdPerson(PlayerLook look, Player player) { bool thirdPersonMode = ThirdPersonCamera.GetThirdPersonMode(look); ThirdPersonCamera.SetThirdPersonMode(look, value: true); ThirdPersonCamera.ApplyOrbitSettings(look); ThirdPersonCamera.MarkThirdPersonEnabled(look); ThirdPersonCamera.SetAllowFirstPersonRendering(allow: false); try { PlayerAnimation animator = player.Animator; if (animator != null) { animator.EnableThirdPerson(); } } catch (Exception ex) { ThirdPersonModePlugin.Log.LogWarning((object)("EnableThirdPerson body failed: " + ex.Message)); } if ((Object)(object)look.ArmsModelParent != (Object)null) { look.ArmsModelParent.SetActive(false); } if (ConfigManager.HideHudInThirdPerson.Value && !thirdPersonMode) { try { look.DisableMainHUD = true; } catch { } } IsGameplayThirdPersonActive = true; } private void RestoreFirstPerson(PlayerLook look, Player player, bool keepWantFlag = false) { bool isGameplayThirdPersonActive = IsGameplayThirdPersonActive; IsGameplayThirdPersonActive = false; if (ThirdPersonCamera.GetThirdPersonMode(look)) { ThirdPersonCamera.SetThirdPersonMode(look, value: false); } ThirdPersonCamera.SetAllowFirstPersonRendering(allow: true); try { PlayerAnimation animator = player.Animator; if (animator != null) { animator.DisableThirdPerson(); } } catch { } if ((Object)(object)look.ArmsModelParent != (Object)null) { look.ArmsModelParent.SetActive(true); } if (isGameplayThirdPersonActive && ConfigManager.HideHudInThirdPerson.Value) { try { look.DisableMainHUD = false; } catch { } } if (!keepWantFlag) { WantGameplayThirdPerson = false; } } } internal sealed class ThirdPersonGearAttachment { private Gun attachedGun; private bool attachedIsThrowable; private Transform fpWeaponRoot; private Transform gunModel; private bool isAttached; private Transform mag1Model; private bool mag1WasActive; private Transform magModel; private bool magWasActive; private Vector3 savedLocalPos; private Quaternion savedLocalRot; private Vector3 savedLocalScale; public ThirdPersonActions Actions { get; set; } public void Reset() { if (isAttached) { try { DetachInternal(restoreFp: true); return; } catch (Exception ex) { ThirdPersonModePlugin.Log.LogWarning((object)("Gear attachment Reset detach failed: " + ex.Message)); ClearState(); return; } } ClearState(); } public void Tick(Player player, PlayerAnimation playerAnim, bool tpActive) { if (!tpActive || (Object)(object)player == (Object)null || (Object)(object)playerAnim == (Object)null) { if (isAttached) { DetachInternal(restoreFp: true); } return; } IGear selectedGear = player.SelectedGear; Gun val = (Gun)(object)((selectedGear is Gun) ? selectedGear : null); Throwable val2 = null; if ((Object)(object)val == (Object)null || !val.Active) { IGear selectedGear2 = player.SelectedGear; Throwable val3 = (Throwable)(object)((selectedGear2 is Throwable) ? selectedGear2 : null); if (val3 != null && val3.Active) { val2 = val3; } else if ((Object)(object)player.Melee != (Object)null && ((Throwable)player.Melee).Active) { val2 = (Throwable)(object)player.Melee; } } if (((Object)(object)val == (Object)null || !val.Active) && (Object)(object)val2 == (Object)null) { if (isAttached) { DetachInternal(restoreFp: true); } } else if ((Object)(object)val != (Object)null && val.Active) { if (isAttached && ((Object)(object)attachedGun != (Object)(object)val || attachedIsThrowable)) { DetachInternal(restoreFp: true); } if (!isAttached) { TryAttachGun(player, playerAnim, val); } else if ((Object)(object)gunModel != (Object)null && !((Component)gunModel).gameObject.activeSelf) { ((Component)gunModel).gameObject.SetActive(true); } } else if (isAttached && attachedIsThrowable) { if ((Object)(object)gunModel != (Object)null && !((Component)gunModel).gameObject.activeSelf) { ((Component)gunModel).gameObject.SetActive(true); } } else { if (isAttached) { DetachInternal(restoreFp: true); } TryAttachThrowable(player, playerAnim, val2); } } private void TryAttachGun(Player player, PlayerAnimation playerAnim, Gun gun) { //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0153: 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) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) Transform val = ThirdPersonReflection.GetGunModel(gun); if ((Object)(object)val == (Object)null || !ThirdPersonReflection.IsGunHeld(gun)) { return; } Transform tpWeaponRoot = GetTpWeaponRoot(playerAnim); if ((Object)(object)tpWeaponRoot == (Object)null) { ThirdPersonModePlugin.Log.LogWarning((object)"TP WeaponRootThirdPerson not found; cannot attach gun."); return; } fpWeaponRoot = playerAnim.WeaponRoot; gunModel = val; magModel = ThirdPersonReflection.GetMagModel(gun); mag1Model = ThirdPersonReflection.GetMag1Model(gun); attachedGun = gun; attachedIsThrowable = false; savedLocalPos = gunModel.localPosition; savedLocalRot = gunModel.localRotation; savedLocalScale = gunModel.localScale; if ((Object)(object)magModel != (Object)null) { magWasActive = ((Component)magModel).gameObject.activeSelf; ((Component)magModel).gameObject.SetActive(false); } if ((Object)(object)mag1Model != (Object)null) { mag1WasActive = ((Component)mag1Model).gameObject.activeSelf; ((Component)mag1Model).gameObject.SetActive(false); } ThirdPersonReflection.GetThirdPersonPose(gun, player.Character, out var pos, out var rot, out var scaleMul); try { try { IGear.SetGunRenderingLayerRecursive(gunModel, 1u); } catch { SetLayerRecursive(gunModel, 0); } gunModel.SetParent(tpWeaponRoot, false); gunModel.SetLocalPositionAndRotation(pos, rot); Transform val2 = null; try { IUpgradable prefab = gun.Prefab; Gun val3 = (Gun)(object)((prefab is Gun) ? prefab : null); if (val3 != null) { val2 = ThirdPersonReflection.GetGunModel(val3); } } catch { } Vector3 val4 = (((Object)(object)val2 != (Object)null) ? val2.localScale : savedLocalScale); gunModel.localScale = val4 * scaleMul; ((Component)gunModel).gameObject.SetActive(true); isAttached = true; ThirdPersonAnimator selfThirdPersonAnimator = playerAnim.SelfThirdPersonAnimator; Actions?.OnGunAttached(gun, player.Character, selfThirdPersonAnimator); } catch (Exception ex) { ThirdPersonModePlugin.Log.LogWarning((object)("Failed to attach gun to TP root: " + ex.Message)); ClearState(); } } private void TryAttachThrowable(Player player, PlayerAnimation playerAnim, Throwable throwable) { //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_0062: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)throwable == (Object)null) { return; } Transform throwableModel = ThirdPersonReflection.GetThrowableModel(throwable); if ((Object)(object)throwableModel == (Object)null) { return; } Transform tpWeaponRoot = GetTpWeaponRoot(playerAnim); if ((Object)(object)tpWeaponRoot == (Object)null) { return; } fpWeaponRoot = playerAnim.WeaponRoot; gunModel = throwableModel; magModel = null; mag1Model = null; attachedGun = null; attachedIsThrowable = true; savedLocalPos = gunModel.localPosition; savedLocalRot = gunModel.localRotation; savedLocalScale = gunModel.localScale; try { try { IGear.SetGunRenderingLayerRecursive(gunModel, 1u); } catch { SetLayerRecursive(gunModel, 0); } gunModel.SetParent(tpWeaponRoot, false); gunModel.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); gunModel.localScale = savedLocalScale; ((Component)gunModel).gameObject.SetActive(true); isAttached = true; } catch (Exception ex) { ThirdPersonModePlugin.Log.LogWarning((object)("Failed to attach throwable to TP root: " + ex.Message)); ClearState(); } } private void DetachInternal(bool restoreFp) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) if (!isAttached || (Object)(object)gunModel == (Object)null) { ClearState(); return; } try { if (restoreFp && (Object)(object)fpWeaponRoot != (Object)null) { gunModel.SetParent(fpWeaponRoot, false); gunModel.SetLocalPositionAndRotation(savedLocalPos, savedLocalRot); gunModel.localScale = savedLocalScale; try { IGear.SetGunRenderingLayerRecursive(gunModel, 2u); } catch { } if ((Object)(object)attachedGun != (Object)null && attachedGun.Active) { ((Component)gunModel).gameObject.SetActive(true); } } if ((Object)(object)magModel != (Object)null) { ((Component)magModel).gameObject.SetActive(magWasActive && (Object)(object)attachedGun != (Object)null && attachedGun.Active); } if ((Object)(object)mag1Model != (Object)null) { ((Component)mag1Model).gameObject.SetActive(mag1WasActive && (Object)(object)attachedGun != (Object)null && attachedGun.Active); } } catch (Exception ex) { ThirdPersonModePlugin.Log.LogWarning((object)("Failed to restore FP gun parenting: " + ex.Message)); } ClearState(); } private void ClearState() { attachedGun = null; gunModel = null; magModel = null; mag1Model = null; fpWeaponRoot = null; isAttached = false; attachedIsThrowable = false; } private static Transform GetTpWeaponRoot(PlayerAnimation playerAnim) { try { ThirdPersonAnimator selfThirdPersonAnimator = playerAnim.SelfThirdPersonAnimator; if ((Object)(object)selfThirdPersonAnimator == (Object)null) { return null; } ThirdPersonRig rig = selfThirdPersonAnimator.Rig; if ((Object)(object)rig != (Object)null && (Object)(object)rig.WeaponRootThirdPerson != (Object)null) { return rig.WeaponRootThirdPerson; } } catch { } try { if ((Object)(object)playerAnim.WeaponRootThirdPerson != (Object)null) { return playerAnim.WeaponRootThirdPerson; } } catch { } return null; } private static void SetLayerRecursive(Transform t, int layer) { if (!((Object)(object)t == (Object)null)) { ((Component)t).gameObject.layer = layer; for (int i = 0; i < t.childCount; i++) { SetLayerRecursive(t.GetChild(i), layer); } } } } internal sealed class ThirdPersonLocomotion { private enum LocoState { None, Idle, Walk, Run, Jump, AirJump, JumpLoop, Land, Clamber, Slide } private Key airJumpKey; private Character boundCharacter; private Key clamberKey; private LocoState current; private Key idleKey; private Key jumpKey; private Key jumpLoopKey; private Key landKey; private float landUntil; private Key runKey; private Key slideKey; private bool usingCrouchSet; private Key walkKey; private bool wasGrounded = true; public Func ShouldBlockLocomotion { get; set; } public void Reset() { current = LocoState.None; boundCharacter = null; usingCrouchSet = false; idleKey = (runKey = (walkKey = (jumpKey = (airJumpKey = (jumpLoopKey = (landKey = (clamberKey = (slideKey = null)))))))); landUntil = 0f; wasGrounded = true; } public void Tick(Player player, PlayerAnimation playerAnim, ThirdPersonAnimator tpAnim) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: 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) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)playerAnim == (Object)null || (Object)(object)tpAnim == (Object)null || !((Behaviour)tpAnim).isActiveAndEnabled) { return; } Character character = player.Character; if ((Object)(object)character == (Object)null) { return; } bool flag = player.Crouching && !player.Sliding; if ((Object)(object)boundCharacter != (Object)(object)character || usingCrouchSet != flag) { BindAnimationSet(character, flag); } if (idleKey == null || (Object)(object)idleKey.clip == (Object)null) { return; } try { Vector3 localPosition = ((Component)player).transform.localPosition; if (flag) { localPosition.y += ConfigManager.CrouchBodyOffset.Value; } tpAnim.SetTransform(localPosition, ((Component)player).transform.localRotation); } catch { } if (ShouldBlockLocomotion != null && ShouldBlockLocomotion()) { return; } LocoState locoState = ResolveDesiredState(player, playerAnim); if ((locoState == current && IsLoopingState(locoState) && !AnimatorDriftedFrom(tpAnim, GetKey(locoState), idleKey)) || (current == LocoState.Land && Time.time < landUntil && locoState != LocoState.Jump && locoState != LocoState.Slide && locoState != LocoState.Clamber)) { return; } Key key = GetKey(locoState); if (key == null || (Object)(object)key.clip == (Object)null) { key = idleKey; locoState = LocoState.Idle; } try { tpAnim.SetState(key, -1f, 0f); current = locoState; if (locoState == LocoState.Land) { float num = (((Object)(object)key.clip != (Object)null) ? (key.clip.length / Mathf.Max(key.speed, 0.01f)) : 0.25f); landUntil = Time.time + Mathf.Clamp(num * 0.85f, 0.1f, 0.6f); } } catch (Exception ex) { ThirdPersonModePlugin.Log.LogWarning((object)$"TP loco SetState failed ({locoState}): {ex.Message}"); } } private void BindAnimationSet(Character character, bool crouch) { //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Expected O, but got Unknown boundCharacter = character; usingCrouchSet = crouch; PlayerAnimationSet val = (crouch ? (character.ThirdPersonCrouchAnimationSet ?? character.ThirdPersonAnimationSet) : character.ThirdPersonAnimationSet); if ((Object)(object)val == (Object)null) { ThirdPersonModePlugin.Log.LogWarning((object)("No ThirdPersonAnimationSet on character " + ((Object)character).name)); idleKey = null; return; } idleKey = CloneKey(val.idle); runKey = CloneKey(val.run); jumpKey = CloneKey(val.jump); airJumpKey = CloneKey(val.airJump); jumpLoopKey = CloneKey(val.jumpLoop); landKey = CloneKey(val.land); clamberKey = CloneKey(val.clamber); slideKey = CloneKey(val.slide); walkKey = null; if (runKey != null && (Object)(object)runKey.walkingClip != (Object)null) { walkKey = new Key(runKey) { clip = runKey.walkingClip, walkingClip = runKey.walkingClip, isWalkingKey = true, speed = runKey.speed * ((runKey.walkSpeedMultiplier > 0f) ? runKey.walkSpeedMultiplier : 1f) }; } current = LocoState.None; ThirdPersonModePlugin.Log.LogInfo((object)string.Format("Bound TP loco set for {0} (crouch={1}, idle={2})", ((Object)character).name, crouch, ((Object)(object)idleKey?.clip != (Object)null) ? ((Object)idleKey.clip).name : "null")); } private static Key CloneKey(Key source) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: 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_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown if (source == null) { return null; } return new Key(source) { autoTransitionTo = null, autoTransitionToKey = null, canBeInterruptedBy = null, canBeInterruptedByRunState = false }; } private static bool AnimatorDriftedFrom(ThirdPersonAnimator tpAnim, Key key, Key fallbackIdle) { Key val = key ?? fallbackIdle; if (val == null || (Object)(object)val.clip == (Object)null) { return false; } try { Key currentStateKey = tpAnim.CurrentStateKey; if (currentStateKey == null) { return true; } if (currentStateKey == val) { return false; } if ((Object)(object)currentStateKey.clip != (Object)null && (Object)(object)val.clip != (Object)null && currentStateKey.clip == val.clip) { return false; } return true; } catch { return false; } } private LocoState ResolveDesiredState(Player player, PlayerAnimation playerAnim) { bool flag = player.Grounded || playerAnim.Grounded > 0; if (flag && !wasGrounded) { wasGrounded = true; if ((Object)(object)landKey?.clip != (Object)null && !player.Sliding) { return LocoState.Land; } } else if (!flag) { wasGrounded = false; } if (player.Sliding || playerAnim.Sliding) { return LocoState.Slide; } if (playerAnim.Clambering) { return LocoState.Clamber; } if (!flag) { if (player.AirJumpCount > 0 && (Object)(object)airJumpKey?.clip != (Object)null && Time.time - player.LastJumpTime < 0.35f) { return LocoState.AirJump; } if (Time.time - player.LastJumpTime < 0.4f && (Object)(object)jumpKey?.clip != (Object)null) { return LocoState.Jump; } if (!((Object)(object)jumpLoopKey?.clip != (Object)null)) { return LocoState.Jump; } return LocoState.JumpLoop; } if (player.IsRunning || playerAnim.Running > 0 || player.Speed > 0.35f) { if (!player.IsSprinting && playerAnim.Running < 2 && (Object)(object)walkKey?.clip != (Object)null) { return LocoState.Walk; } return LocoState.Run; } return LocoState.Idle; } private Key GetKey(LocoState state) { return (Key)(state switch { LocoState.Walk => walkKey ?? runKey, LocoState.Run => runKey, LocoState.Jump => jumpKey, LocoState.AirJump => airJumpKey ?? jumpKey, LocoState.JumpLoop => jumpLoopKey ?? jumpKey, LocoState.Land => landKey, LocoState.Clamber => clamberKey, LocoState.Slide => slideKey, _ => idleKey, }); } private static bool IsLoopingState(LocoState state) { switch (state) { case LocoState.Idle: case LocoState.Walk: case LocoState.Run: case LocoState.JumpLoop: case LocoState.Slide: return true; default: return false; } } } [HarmonyPatch] internal static class ThirdPersonPatches { [HarmonyPrefix] [HarmonyPatch(typeof(PlayerLook), "DisableThirdPerson")] private static bool DisableThirdPerson_Prefix(PlayerLook __instance) { if (ThirdPersonController.AllowVanillaDisable) { return true; } if ((ThirdPersonController.IsGameplayThirdPersonActive || ThirdPersonController.WantGameplayThirdPerson) && ThirdPersonCamera.GetThirdPersonMode(__instance)) { return false; } return true; } [HarmonyPostfix] [HarmonyPatch(typeof(PlayerLook), "EnableThirdPerson")] private static void EnableThirdPerson_Postfix(PlayerLook __instance, bool enableThirdPerson, bool disableHUD) { if (!ThirdPersonController.WantGameplayThirdPerson) { return; } Player localPlayer = Player.LocalPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } try { if (localPlayer.MovementControlLocks > 0) { int movementControlLocks = localPlayer.MovementControlLocks; localPlayer.MovementControlLocks = movementControlLocks - 1; } if (localPlayer.GearLocks > 0) { int movementControlLocks = localPlayer.GearLocks; localPlayer.GearLocks = movementControlLocks - 1; } if (localPlayer.InteractionLocks > 0) { int movementControlLocks = localPlayer.InteractionLocks; localPlayer.InteractionLocks = movementControlLocks - 1; } localPlayer.LockFiring(false); } catch { } ThirdPersonController.Instance?.OnConfigReloaded(); } [HarmonyPostfix] [HarmonyPatch(typeof(PlayerLook), "LateUpdate")] private static void PlayerLook_LateUpdate_Postfix(PlayerLook __instance) { if (ThirdPersonController.IsGameplayThirdPersonActive) { Traverse val = Traverse.Create((object)__instance); val.Field("minOrbitDistance").SetValue((object)ConfigManager.MinOrbitDistance.Value); val.Field("maxOrbitDistance").SetValue((object)ConfigManager.MaxOrbitDistance.Value); if (!ConfigManager.ScrollToZoom.Value) { val.Field("orbitDistance").SetValue((object)ConfigManager.OrbitDistance.Value); } } } } internal static class ThirdPersonReflection { private static FieldInfo gunModelField; private static FieldInfo magModelField; private static FieldInfo mag1ModelField; private static FieldInfo isGunHeldField; private static FieldInfo thirdPersonDataField; private static FieldInfo thirdPersonScaleField; private static FieldInfo throwableModelField; private static bool fieldsResolved; public static void Resolve() { if (!fieldsResolved) { fieldsResolved = true; Type? typeFromHandle = typeof(Gun); gunModelField = AccessTools.Field(typeFromHandle, "gunModel"); magModelField = AccessTools.Field(typeFromHandle, "magModel"); mag1ModelField = AccessTools.Field(typeFromHandle, "mag1Model"); isGunHeldField = AccessTools.Field(typeFromHandle, "isGunHeld"); thirdPersonDataField = AccessTools.Field(typeFromHandle, "thirdPersonData"); thirdPersonScaleField = AccessTools.Field(typeFromHandle, "thirdPersonScaleMultiplier"); throwableModelField = AccessTools.Field(typeof(Throwable), "gunModel"); if (gunModelField == null) { ThirdPersonModePlugin.Log.LogWarning((object)"Gun.gunModel field not found — TP gear attachment disabled."); } } } public static Transform GetGunModel(Gun gun) { Resolve(); try { object? obj = gunModelField?.GetValue(gun); return (Transform)((obj is Transform) ? obj : null); } catch { return null; } } public static Transform GetMagModel(Gun gun) { Resolve(); try { object? obj = magModelField?.GetValue(gun); return (Transform)((obj is Transform) ? obj : null); } catch { return null; } } public static Transform GetMag1Model(Gun gun) { Resolve(); try { object? obj = mag1ModelField?.GetValue(gun); return (Transform)((obj is Transform) ? obj : null); } catch { return null; } } public static bool IsGunHeld(Gun gun) { Resolve(); try { if (isGunHeldField != null) { return (bool)isGunHeldField.GetValue(gun); } } catch { } return true; } public static Transform GetThrowableModel(Throwable throwable) { Resolve(); try { object? obj = throwableModelField?.GetValue(throwable); return (Transform)((obj is Transform) ? obj : null); } catch { return null; } } public static void GetThirdPersonPose(Gun gun, Character character, out Vector3 pos, out Quaternion rot, out float scaleMul) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) pos = Vector3.zero; rot = Quaternion.identity; scaleMul = 1f; Resolve(); try { if (thirdPersonScaleField != null) { scaleMul = (float)thirdPersonScaleField.GetValue(gun); } } catch { scaleMul = 1f; } try { if (thirdPersonDataField == null || (Object)(object)character == (Object)null || !(thirdPersonDataField.GetValue(gun) is Array { Length: not 0 } array)) { return; } int num = character.Index; if (num < 0 || num >= array.Length) { num = 0; } object value = array.GetValue(num); if (value == null) { return; } Type type = value.GetType(); FieldInfo fieldInfo = AccessTools.Field(type, "position"); FieldInfo fieldInfo2 = AccessTools.Field(type, "rotation"); if (fieldInfo != null) { pos = (Vector3)fieldInfo.GetValue(value); } if (fieldInfo2 != null) { rot = (Quaternion)fieldInfo2.GetValue(value); if (rot.x == 0f && rot.y == 0f && rot.z == 0f && rot.w == 0f) { rot = Quaternion.identity; } } } catch (Exception ex) { ThirdPersonModePlugin.Log.LogWarning((object)("Could not read gun thirdPersonData: " + ex.Message)); } } public static object GetThirdPersonDataEntry(Gun gun, Character character) { Resolve(); try { if (!(thirdPersonDataField?.GetValue(gun) is Array { Length: not 0 } array)) { return null; } int num = (((Object)(object)character != (Object)null) ? character.Index : 0); if (num < 0 || num >= array.Length) { num = 0; } return array.GetValue(num); } catch { return null; } } public static ThirdPersonGearAnimationSet GetAnimationSet(Gun gun, Character character) { try { object thirdPersonDataEntry = GetThirdPersonDataEntry(gun, character); if (thirdPersonDataEntry == null) { return null; } object? obj = AccessTools.Field(thirdPersonDataEntry.GetType(), "animationSet")?.GetValue(thirdPersonDataEntry); return (ThirdPersonGearAnimationSet)((obj is ThirdPersonGearAnimationSet) ? obj : null); } catch { return null; } } } namespace ThirdPersonMode { public static class MyPluginInfo { public const string PLUGIN_GUID = "ThirdPersonMode"; public const string PLUGIN_NAME = "ThirdPersonMode"; public const string PLUGIN_VERSION = "1.0.1"; } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }