Decompiled source of GhostWalk v1.4.3

GhostWalk.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Splatform;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Networking;

[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: AssemblyCompany("GhostWalk")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("WoW-style spirit walk back to your corpse for Valheim.")]
[assembly: AssemblyFileVersion("1.4.3.0")]
[assembly: AssemblyInformationalVersion("1.4.3")]
[assembly: AssemblyProduct("GhostWalk")]
[assembly: AssemblyTitle("GhostWalk")]
[assembly: AssemblyVersion("1.4.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace GhostWalk
{
	internal static class DeathSnapshot
	{
		private const int MaxPending = 16;

		private static readonly List<ItemData> Items = new List<ItemData>();

		private static readonly HashSet<int> TombstonesBeforeDeath = new HashSet<int>();

		private static readonly List<ZDOID> PendingReap = new List<ZDOID>();

		private static ZDOID _tombstoneId = ZDOID.None;

		public static void BeginDeath(Player player)
		{
			//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)
			Items.Clear();
			_tombstoneId = ZDOID.None;
			TombstonesBeforeDeath.Clear();
			TombStone[] array = Object.FindObjectsByType<TombStone>((FindObjectsSortMode)0);
			foreach (TombStone val in array)
			{
				TombstonesBeforeDeath.Add(((Object)val).GetInstanceID());
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory == null)
			{
				return;
			}
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem != null && allItem.m_shared != null && !allItem.m_shared.m_questItem)
				{
					Items.Add(allItem.Clone());
				}
			}
		}

		public static void EndDeath(Player player)
		{
			//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_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_00dc: Unknown result type (might be due to invalid IL or missing references)
			_tombstoneId = ZDOID.None;
			TombStone val = null;
			TombStone[] array = Object.FindObjectsByType<TombStone>((FindObjectsSortMode)0);
			foreach (TombStone val2 in array)
			{
				if (!TombstonesBeforeDeath.Contains(((Object)val2).GetInstanceID()))
				{
					val = val2;
					break;
				}
			}
			TombstonesBeforeDeath.Clear();
			if ((Object)(object)val == (Object)null)
			{
				Debug.LogWarning((object)"[GhostWalk] Death: no new tombstone appeared, so there is nothing for the Spirit to restore (empty inventory, or another mod handles death items).");
				Items.Clear();
				return;
			}
			ZNetView component = ((Component)val).GetComponent<ZNetView>();
			ZDO val3 = (((Object)(object)component != (Object)null) ? component.GetZDO() : null);
			if (val3 == null)
			{
				Debug.LogWarning((object)"[GhostWalk] Death: tombstone has no ZDO - can't track it, so the Spirit will not restore gear this life (prevents duplication).");
				Items.Clear();
			}
			else
			{
				_tombstoneId = val3.m_uid;
				Debug.Log((object)$"[GhostWalk] Death: snapshotted {Items.Count} item stack(s), tracking tombstone {_tombstoneId}.");
			}
		}

		public static List<ItemData> TakeItems()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: 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)
			List<ItemData> list = new List<ItemData>(Items);
			Items.Clear();
			if (list.Count > 0)
			{
				ZDOID none = ZDOID.None;
				if (!((ZDOID)(ref none)).Equals(_tombstoneId))
				{
					if (PendingReap.Count >= 16)
					{
						PendingReap.RemoveAt(0);
					}
					PendingReap.Add(_tombstoneId);
				}
			}
			_tombstoneId = ZDOID.None;
			ReapPending();
			return list;
		}

		public static void Discard()
		{
			//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)
			Items.Clear();
			_tombstoneId = ZDOID.None;
		}

		public static void ReapPending()
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			if (PendingReap.Count == 0 || (Object)(object)ZNetScene.instance == (Object)null)
			{
				return;
			}
			for (int num = PendingReap.Count - 1; num >= 0; num--)
			{
				ZDOID val = PendingReap[num];
				try
				{
					GameObject val2 = ZNetScene.instance.FindInstance(val);
					if ((Object)(object)val2 != (Object)null)
					{
						ZNetView component = val2.GetComponent<ZNetView>();
						if ((Object)(object)component != (Object)null && component.GetZDO() != null)
						{
							component.ClaimOwnership();
							component.Destroy();
						}
						else
						{
							Object.Destroy((Object)(object)val2);
						}
						PendingReap.RemoveAt(num);
						Debug.Log((object)"[GhostWalk] Removed the tombstone whose items the Spirit already returned.");
					}
					else
					{
						ZDO val3 = ((ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(val) : null);
						if (val3 != null)
						{
							val3.SetOwner(ZDOMan.GetSessionID());
							ZDOMan.instance.DestroyZDO(val3);
							PendingReap.RemoveAt(num);
							Debug.Log((object)"[GhostWalk] Removed the (unloaded) tombstone whose items the Spirit already returned.");
						}
					}
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)("[GhostWalk] Could not remove returned tombstone yet (" + ex.Message + "); will retry."));
				}
			}
		}
	}
	public class GhostAmbience : MonoBehaviour
	{
		private struct MutedSource
		{
			public AudioSource Source;

			public bool WasMuted;
		}

		private const string FileBaseName = "ghostwalk_music";

		private const float FadeInPerSecond = 0.33f;

		private const float FadeOutPerSecond = 0.5f;

		private static AudioClip _cachedClip;

		private static string _cachedPath;

		private static bool _loggedMissing;

		private AudioSource _source;

		private bool _fadingOut;

		private readonly List<MutedSource> _muted = new List<MutedSource>();

		private float _muteTimer;

		private bool _loggedMute;

		public static GhostAmbience Begin(Transform parent)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			GameObject val = new GameObject("GhostWalk_Music");
			val.transform.SetParent(parent, false);
			return val.AddComponent<GhostAmbience>();
		}

		public void FadeOutAndDestroy()
		{
			_fadingOut = true;
		}

		private void Awake()
		{
			_source = ((Component)this).gameObject.AddComponent<AudioSource>();
			_source.loop = true;
			_source.playOnAwake = false;
			_source.spatialBlend = 0f;
			_source.volume = 0f;
			_source.outputAudioMixerGroup = FindGameMixerGroup();
			((MonoBehaviour)this).StartCoroutine(LoadAndPlay());
		}

		private void Update()
		{
			float num = ((!_fadingOut && GhostWalkPlugin.EnableGhostMusic.Value) ? Mathf.Clamp01(GhostWalkPlugin.GhostMusicVolume.Value) : 0f);
			float num2 = ((num > _source.volume) ? 0.33f : 0.5f);
			_source.volume = Mathf.MoveTowards(_source.volume, num, Time.unscaledDeltaTime * num2);
			if (!_fadingOut && _source.isPlaying && GhostWalkPlugin.MuteGameMusicWhileGhost.Value)
			{
				_muteTimer -= Time.unscaledDeltaTime;
				if (_muteTimer <= 0f)
				{
					_muteTimer = 0.5f;
					MuteGameMusic();
				}
			}
			if (_fadingOut && _source.volume <= 0.001f)
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
			}
		}

		private void OnDestroy()
		{
			RestoreGameMusic();
		}

		private void MuteGameMusic()
		{
			if ((Object)(object)MusicMan.instance == (Object)null)
			{
				return;
			}
			foreach (AudioSource gameMusicSource in GetGameMusicSources())
			{
				if ((Object)(object)gameMusicSource == (Object)null || (Object)(object)gameMusicSource == (Object)(object)_source)
				{
					continue;
				}
				bool flag = false;
				foreach (MutedSource item in _muted)
				{
					if ((Object)(object)item.Source == (Object)(object)gameMusicSource)
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					_muted.Add(new MutedSource
					{
						Source = gameMusicSource,
						WasMuted = gameMusicSource.mute
					});
				}
				gameMusicSource.mute = true;
			}
			if (!_loggedMute)
			{
				_loggedMute = true;
				Debug.Log((object)((_muted.Count > 0) ? $"[GhostWalk] Muting {_muted.Count} game music source(s) while ghost music plays." : "[GhostWalk] Could not find the game's music source to mute - the game music will keep playing under the ghost music."));
			}
		}

		private void RestoreGameMusic()
		{
			foreach (MutedSource item in _muted)
			{
				if ((Object)(object)item.Source != (Object)null)
				{
					item.Source.mute = item.WasMuted;
				}
			}
			_muted.Clear();
		}

		private static List<AudioSource> GetGameMusicSources()
		{
			List<AudioSource> list = new List<AudioSource>();
			try
			{
				FieldInfo fieldInfo = AccessTools.Field(typeof(MusicMan), "m_musicSource");
				AudioSource val = (AudioSource)((fieldInfo != null) ? /*isinst with value type is only supported in some contexts*/: null);
				if ((Object)(object)val != (Object)null)
				{
					list.Add(val);
					return list;
				}
			}
			catch
			{
			}
			list.AddRange(((Component)MusicMan.instance).GetComponents<AudioSource>());
			return list;
		}

		private static string PluginFolder()
		{
			return Path.GetDirectoryName(typeof(GhostWalkPlugin).Assembly.Location);
		}

		private static string FindMusicFile()
		{
			string path = PluginFolder();
			string[] array = new string[3] { ".ogg", ".mp3", ".wav" };
			foreach (string text in array)
			{
				string text2 = Path.Combine(path, "ghostwalk_music" + text);
				if (File.Exists(text2))
				{
					return text2;
				}
			}
			return null;
		}

		private static AudioType TypeFor(string path)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			string text = Path.GetExtension(path).ToLowerInvariant();
			string text2 = text;
			if (!(text2 == ".mp3"))
			{
				if (text2 == ".wav")
				{
					return (AudioType)20;
				}
				return (AudioType)14;
			}
			return (AudioType)13;
		}

		private IEnumerator LoadAndPlay()
		{
			string path = FindMusicFile();
			if (path == null)
			{
				if (!_loggedMissing)
				{
					_loggedMissing = true;
					Debug.Log((object)("[GhostWalk] No ghost music file found. To use one, put 'ghostwalk_music.ogg' (or .mp3/.wav) in: " + PluginFolder()));
				}
				yield break;
			}
			if ((Object)(object)_cachedClip == (Object)null || _cachedPath != path)
			{
				UnityWebRequest req = UnityWebRequestMultimedia.GetAudioClip(new Uri(path).AbsoluteUri, TypeFor(path));
				try
				{
					yield return req.SendWebRequest();
					if ((int)req.result != 1)
					{
						Debug.LogWarning((object)("[GhostWalk] Could not load ghost music '" + path + "': " + req.error + ". Try converting it to .ogg or .wav."));
						yield break;
					}
					_cachedClip = DownloadHandlerAudioClip.GetContent(req);
					_cachedPath = path;
					if ((Object)(object)_cachedClip != (Object)null)
					{
						((Object)_cachedClip).hideFlags = (HideFlags)61;
					}
				}
				finally
				{
					((IDisposable)req)?.Dispose();
				}
			}
			if (!((Object)(object)_cachedClip == (Object)null) && !_fadingOut)
			{
				_source.clip = _cachedClip;
				_source.Play();
				Debug.Log((object)("[GhostWalk] Playing ghost music: " + Path.GetFileName(path)));
			}
		}

		private static AudioMixerGroup FindGameMixerGroup()
		{
			if ((Object)(object)AudioMan.instance == (Object)null)
			{
				return null;
			}
			string[] array = new string[2] { "m_ambientMixer", "m_ambientLoopMixer" };
			foreach (string text in array)
			{
				try
				{
					FieldInfo fieldInfo = AccessTools.Field(typeof(AudioMan), text);
					AudioMixerGroup val = (AudioMixerGroup)((fieldInfo != null) ? /*isinst with value type is only supported in some contexts*/: null);
					if ((Object)(object)val != (Object)null)
					{
						return val;
					}
				}
				catch
				{
				}
			}
			return null;
		}
	}
	public class GhostModeWatcher : MonoBehaviour
	{
		private static GhostModeWatcher _instance;

		private static Action _onPlayerBackInWorld;

		private static bool _deathPending;

		private float _aliveSince = -1f;

		private const float SettleSeconds = 0.5f;

		public static void Install(Action onPlayerBackInWorld)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			_onPlayerBackInWorld = onPlayerBackInWorld;
			if (!((Object)(object)_instance != (Object)null))
			{
				GameObject val = new GameObject("GhostWalk_Watcher");
				Object.DontDestroyOnLoad((Object)(object)val);
				_instance = val.AddComponent<GhostModeWatcher>();
				Debug.Log((object)"[GhostWalk] Watcher installed.");
			}
		}

		public static void NotifyDeath()
		{
			_deathPending = true;
			Debug.Log((object)"[GhostWalk] Watcher armed by OnDeath - waiting for player to return.");
		}

		private void Update()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				_aliveSince = -1f;
			}
			else if (((Character)localPlayer).IsDead())
			{
				_deathPending = true;
				_aliveSince = -1f;
			}
			else
			{
				if (!_deathPending)
				{
					return;
				}
				if (_aliveSince < 0f)
				{
					_aliveSince = Time.time;
				}
				else if (!(Time.time - _aliveSince < 0.5f))
				{
					_deathPending = false;
					_aliveSince = -1f;
					Debug.Log((object)"[GhostWalk] Watcher: player is back in the world, entering ghost mode.");
					try
					{
						_onPlayerBackInWorld?.Invoke();
					}
					catch (Exception ex)
					{
						Debug.LogError((object)("[GhostWalk] Watcher callback threw: " + ex));
					}
				}
			}
		}
	}
	public class GhostRemoteHider : MonoBehaviour
	{
		public const string ZdoKey = "GhostWalk_Ghost";

		private Player _player;

		private ZNetView _nview;

		private float _timer;

		private bool _ghostLook;

		private GameObject _wisp;

		private readonly ModelHide _modelHide = new ModelHide();

		private void Awake()
		{
			_player = ((Component)this).GetComponent<Player>();
			_nview = ((Component)this).GetComponent<ZNetView>();
		}

		private void Update()
		{
			if ((Object)(object)_player == (Object)null || (Object)(object)_player == (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			_timer -= Time.deltaTime;
			if (_timer > 0f)
			{
				return;
			}
			_timer = 0.25f;
			bool flag = false;
			if ((Object)(object)_nview != (Object)null && _nview.IsValid())
			{
				ZDO zDO = _nview.GetZDO();
				if (zDO != null)
				{
					flag = zDO.GetBool("GhostWalk_Ghost", false);
				}
			}
			if (flag)
			{
				ShowGhostLook();
			}
			else if (_ghostLook)
			{
				ClearGhostLook();
			}
		}

		private void ShowGhostLook()
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			if (!_ghostLook)
			{
				Debug.Log((object)("[GhostWalk] Another player is a ghost - replacing " + _player.GetPlayerName() + " with a wisp on this screen."));
				if (GhostWalkPlugin.WispModelWhileGhost.Value)
				{
					_wisp = WispModel.Build(((Component)this).transform, GhostWalkPlugin.GhostTintColor.Value);
				}
				_ghostLook = true;
			}
			_modelHide.Apply(((Component)this).gameObject);
		}

		private void ClearGhostLook()
		{
			Debug.Log((object)("[GhostWalk] " + _player.GetPlayerName() + " is no longer a ghost - showing them again."));
			if ((Object)(object)_wisp != (Object)null)
			{
				Object.Destroy((Object)(object)_wisp);
			}
			_wisp = null;
			_modelHide.Restore();
			_ghostLook = false;
		}

		private void OnDestroy()
		{
			_modelHide.Restore();
		}
	}
	public class GhostWalkController : MonoBehaviour
	{
		private Player _player;

		private static Vector3 _corpsePosition;

		private static bool _hasCorpse;

		private GameObject _visualRig;

		private GameObject _spiritNpcInstance;

		private PinData _corpsePin;

		private Vector3 _spiritAnchor;

		private bool _hasSpiritAnchor;

		private Vector3 _respawnPos;

		private Quaternion _respawnRot;

		private bool _hasRespawnPos;

		private GhostAmbience _ambience;

		private bool _gameGhostModeOn;

		private float _aggroTimer;

		private float _lastTeleportTime = -99f;

		private const float TeleportCooldownSeconds = 1f;

		private const string CorpsePinName = "Your body";

		private float _resurrectionSicknessTimer;

		public const string SicknessEffectName = "Resurrection Sickness";

		private static StatusEffect _resurrectionSicknessEffect;

		private float _tintAlpha;

		private readonly List<ItemData> _restoredEquippedGear = new List<ItemData>();

		private float _reapTimer;

		private GameObject _wispRoot;

		private readonly ModelHide _modelHide = new ModelHide();

		private float _modelRefreshTimer;

		private bool _flightWeEnabled;

		private static MethodInfo _toggleDebugFly;

		private static MethodInfo _isDebugFlying;

		private bool _flightAwaitingJump;

		private const float AirborneFlightFailsafeSeconds = 1f;

		private float _airborneTimer;

		private float _flightActivatedTime;

		private const float FlightActivationDebounceSeconds = 0.2f;

		private bool _hasBeenAirborneThisFlight;

		public static bool SuppressFlyMessage;

		private bool _teleportRequested;

		private Vector3 _teleportDestination;

		private float _teleportGiveUpTime;

		private static Texture2D _softCircle;

		private static MethodInfo _takeInputMethod;

		private static bool _takeInputLookedUp;

		public static GhostWalkController Instance { get; private set; }

		public bool IsGhostActive { get; private set; }

		public bool HasResurrectionSickness => _resurrectionSicknessTimer > 0f;

		public float SicknessHealthCap
		{
			get
			{
				if (!HasResurrectionSickness)
				{
					return 0f;
				}
				return Mathf.Max(0f, GhostWalkPlugin.ResurrectionSicknessMaxHealth.Value);
			}
		}

		private void Update()
		{
			_reapTimer -= Time.deltaTime;
			if (_reapTimer <= 0f)
			{
				_reapTimer = 2f;
				DeathSnapshot.ReapPending();
			}
			if (_teleportRequested)
			{
				ProcessTeleportRequest();
			}
			if (IsGhostActive)
			{
				if (GhostWalkPlugin.GhostNoStaminaDrain.Value && (Object)(object)_player != (Object)null && _player.GetStamina() < ((Character)_player).GetMaxStamina())
				{
					((Character)_player).UseStamina(0f - (((Character)_player).GetMaxStamina() - _player.GetStamina()));
				}
				if (_flightAwaitingJump)
				{
					UpdateFlightArming();
				}
				else if (_flightWeEnabled)
				{
					UpdateFlightLanding();
				}
				if (TeleportHotkeyPressed())
				{
					Debug.Log((object)"[GhostWalk] Teleport hotkey pressed.");
					TeleportToSpirit();
				}
				if (_modelHide.IsHidden)
				{
					_modelRefreshTimer -= Time.deltaTime;
					if (_modelRefreshTimer <= 0f)
					{
						_modelRefreshTimer = 0.5f;
						_modelHide.Apply(((Component)this).gameObject);
					}
				}
			}
			float sicknessHealthCap = SicknessHealthCap;
			if (sicknessHealthCap > 0f && (Object)(object)_player != (Object)null && ((Character)_player).GetHealth() > sicknessHealthCap)
			{
				((Character)_player).SetHealth(sicknessHealthCap);
			}
			if (IsGhostActive && GhostWalkPlugin.PreventEnemyTargeting.Value)
			{
				_aggroTimer -= Time.deltaTime;
				if (_aggroTimer <= 0f)
				{
					_aggroTimer = 1f;
					ClearExistingAggro();
				}
			}
			if (!(_resurrectionSicknessTimer > 0f))
			{
				return;
			}
			_resurrectionSicknessTimer -= Time.deltaTime;
			if (_resurrectionSicknessTimer <= 0f)
			{
				_resurrectionSicknessTimer = 0f;
				if ((Object)(object)MessageHud.instance != (Object)null)
				{
					MessageHud.instance.ShowMessage((MessageType)2, "Resurrection Sickness has faded. Your strength returns.", 0, (Sprite)null, false, true);
				}
			}
		}

		private void OnGUI()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Invalid comparison between Unknown and I4
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Invalid comparison between Unknown and I4
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: 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_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			if ((int)Event.current.type == 8)
			{
				float num = ((IsGhostActive && GhostWalkPlugin.EnableScreenTint.Value) ? GhostWalkPlugin.ScreenTintStrength.Value : 0f);
				_tintAlpha = Mathf.MoveTowards(_tintAlpha, num, Time.unscaledDeltaTime * 0.5f);
			}
			if (!(_tintAlpha <= 0.001f) && (int)Event.current.type == 7)
			{
				Color color = GUI.color;
				Color value = GhostWalkPlugin.GhostTintColor.Value;
				GUI.color = new Color(value.r, value.g, value.b, _tintAlpha);
				GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Texture2D.whiteTexture, (ScaleMode)0);
				GUI.color = color;
			}
		}

		private void Awake()
		{
			Instance = this;
			_player = ((Component)this).GetComponent<Player>();
		}

		private void OnDestroy()
		{
			_modelHide.Restore();
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		public void RecordCorpsePosition(Vector3 pos)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			_corpsePosition = pos;
			_hasCorpse = true;
		}

		public void EnterGhostMode()
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
			if (IsGhostActive)
			{
				return;
			}
			if (!GhostWalkPlugin.Enabled.Value || !_hasCorpse)
			{
				Debug.Log((object)("[GhostWalk] EnterGhostMode called but skipped " + $"(Enabled={GhostWalkPlugin.Enabled.Value}, hasCorpse={_hasCorpse})."));
				return;
			}
			float num = Vector3.Distance(((Component)_player).transform.position, _corpsePosition);
			if (num <= GhostWalkPlugin.SkipGhostModeRadius.Value)
			{
				Debug.Log((object)($"[GhostWalk] Respawned {num:0.#}m from death point - within " + $"SkipGhostModeRadius ({GhostWalkPlugin.SkipGhostModeRadius.Value}m), " + "skipping ghost mode."));
				_hasCorpse = false;
				return;
			}
			Debug.Log((object)$"[GhostWalk] Entering ghost mode ({num:0.#}m from death point).");
			IsGhostActive = true;
			_spiritAnchor = ((Component)_player).transform.position;
			_hasSpiritAnchor = true;
			_respawnPos = ((Component)_player).transform.position;
			_respawnRot = ((Component)_player).transform.rotation;
			_hasRespawnPos = true;
			_lastTeleportTime = -99f;
			((Character)_player).SetHealth(1f);
			_player.SetMaxStamina(((Character)_player).GetMaxStamina(), true);
			if (GhostWalkPlugin.ShowCorpsePin.Value && (Object)(object)Minimap.instance != (Object)null)
			{
				_corpsePin = Minimap.instance.AddPin(_corpsePosition, (PinType)4, "Your body", false, false, 0L, default(PlatformUserID));
			}
			if (GhostWalkPlugin.EnableGhostVisual.Value)
			{
				try
				{
					SpawnGhostVisuals();
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)("[GhostWalk] Ghost visuals failed to spawn (" + ex.Message + ") - continuing without them. Ghost mode itself is unaffected."));
				}
			}
			if (GhostWalkPlugin.WispModelWhileGhost.Value)
			{
				try
				{
					ApplyWispModel();
				}
				catch (Exception ex2)
				{
					Debug.LogWarning((object)("[GhostWalk] Wisp model failed (" + ex2.Message + ") - continuing with the normal character model. Ghost mode itself is unaffected."));
					RemoveWispModel();
				}
			}
			_airborneTimer = 0f;
			if (GhostWalkPlugin.GhostFlight.Value)
			{
				if (GhostWalkPlugin.GhostFlightOnJumpOnly.Value)
				{
					_flightAwaitingJump = true;
					Debug.Log((object)"[GhostWalk] Ghost flight armed - press Jump to take off.");
				}
				else
				{
					SetFlight(on: true);
				}
			}
			SpawnSpiritOfResurrection();
			if (GhostWalkPlugin.EnableGhostMusic.Value)
			{
				try
				{
					_ambience = GhostAmbience.Begin(((Component)this).transform);
				}
				catch (Exception ex3)
				{
					Debug.LogWarning((object)("[GhostWalk] Ghost music failed to start (" + ex3.Message + ") - continuing without it. Ghost mode itself is unaffected."));
				}
			}
			if (GhostWalkPlugin.UseGameGhostMode.Value)
			{
				SetGameGhostMode(on: true);
			}
			if (GhostWalkPlugin.HideFromOtherPlayers.Value)
			{
				SetGhostFlag(on: true);
			}
			if (GhostWalkPlugin.PreventEnemyTargeting.Value)
			{
				ClearExistingAggro();
			}
			if ((Object)(object)MessageHud.instance != (Object)null)
			{
				MessageHud.instance.ShowMessage((MessageType)2, "Your spirit rises. Recover your body, or seek the Spirit of Resurrection.", 0, (Sprite)null, false, true);
			}
		}

		private void UpdateFlightArming()
		{
			if ((Object)(object)_player == (Object)null)
			{
				return;
			}
			if (((Character)_player).IsOnGround())
			{
				_airborneTimer = 0f;
				if (ZInput.GetButtonDown("Jump"))
				{
					Debug.Log((object)"[GhostWalk] Jump pressed - taking off.");
					ActivateArmedFlight();
				}
			}
			else
			{
				_airborneTimer += Time.deltaTime;
				if (_airborneTimer >= 1f)
				{
					Debug.Log((object)"[GhostWalk] Ghost has been airborne without flying - taking off (failsafe).");
					ActivateArmedFlight();
				}
			}
		}

		private void ActivateArmedFlight()
		{
			_flightAwaitingJump = false;
			_airborneTimer = 0f;
			_flightActivatedTime = Time.time;
			_hasBeenAirborneThisFlight = false;
			SetFlight(on: true);
		}

		private void UpdateFlightLanding()
		{
			if (!((Object)(object)_player == (Object)null) && _flightWeEnabled)
			{
				if (!_hasBeenAirborneThisFlight && !((Character)_player).IsOnGround())
				{
					_hasBeenAirborneThisFlight = true;
					Debug.Log((object)"[GhostWalk] Player is airborne - will check for landing.");
				}
				float num = Time.time - _flightActivatedTime;
				if (!(num < 0.2f) && _hasBeenAirborneThisFlight && ((Character)_player).IsOnGround())
				{
					Debug.Log((object)"[GhostWalk] Player landed - disabling flight.");
					SetFlight(on: false);
					_flightAwaitingJump = true;
				}
			}
		}

		private void SetFlight(bool on)
		{
			try
			{
				if (_toggleDebugFly == null || _isDebugFlying == null)
				{
					_toggleDebugFly = AccessTools.Method(typeof(Player), "ToggleDebugFly", (Type[])null, (Type[])null);
					_isDebugFlying = AccessTools.Method(typeof(Player), "IsDebugFlying", (Type[])null, (Type[])null);
				}
				if (_toggleDebugFly == null || _isDebugFlying == null || (Object)(object)_player == (Object)null)
				{
					if (on)
					{
						Debug.LogWarning((object)"[GhostWalk] Player.ToggleDebugFly / IsDebugFlying not found on this game version - ghost flight is unavailable.");
					}
					return;
				}
				bool flag = (bool)_isDebugFlying.Invoke(_player, null);
				if (on)
				{
					if (!flag)
					{
						ToggleFlyQuietly();
						_flightWeEnabled = (bool)_isDebugFlying.Invoke(_player, null);
						Debug.Log((object)(_flightWeEnabled ? "[GhostWalk] Ghost flight ON." : "[GhostWalk] Asked the game to start flying, but it did not switch on (the game may restrict fly mode)."));
					}
				}
				else if (_flightWeEnabled)
				{
					_flightWeEnabled = false;
					if (flag)
					{
						ToggleFlyQuietly();
						Debug.Log((object)"[GhostWalk] Ghost flight OFF.");
					}
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[GhostWalk] Could not change flight (" + ex.Message + ")."));
			}
		}

		private void ToggleFlyQuietly()
		{
			SuppressFlyMessage = true;
			try
			{
				_toggleDebugFly.Invoke(_player, null);
			}
			finally
			{
				SuppressFlyMessage = false;
			}
		}

		private void ApplyWispModel()
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			_wispRoot = WispModel.Build(((Component)this).transform, GhostWalkPlugin.GhostTintColor.Value);
			_modelHide.Apply(((Component)this).gameObject);
			_modelRefreshTimer = 0.5f;
			Debug.Log((object)"[GhostWalk] Wisp model ON (character meshes hidden).");
		}

		private void RemoveWispModel()
		{
			if ((Object)(object)_wispRoot != (Object)null)
			{
				Object.Destroy((Object)(object)_wispRoot);
			}
			_wispRoot = null;
			bool isHidden = _modelHide.IsHidden;
			_modelHide.Restore();
			if (isHidden)
			{
				Debug.Log((object)"[GhostWalk] Wisp model OFF (character model restored).");
			}
		}

		private void SpawnGhostVisuals()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Expected O, but got Unknown
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Expected O, but got Unknown
			//IL_01b1: 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)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01db: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			_visualRig = new GameObject("GhostWalk_Visuals");
			_visualRig.transform.SetParent(((Component)this).transform, false);
			_visualRig.transform.localPosition = new Vector3(0f, 1f, 0f);
			Light val = _visualRig.AddComponent<Light>();
			val.type = (LightType)2;
			val.color = GhostWalkPlugin.GhostTintColor.Value;
			val.intensity = 1.5f;
			val.range = 4f;
			AttachFrostAura(_visualRig.transform);
			Shader val2 = FindFirstAvailableShader("Universal Render Pipeline/Particles/Unlit", "Particles/Standard Unlit", "Sprites/Default", "Legacy Shaders/Particles/Alpha Blended", "Unlit/Transparent");
			if ((Object)(object)val2 == (Object)null)
			{
				Debug.LogWarning((object)"[GhostWalk] No usable particle/unlit shader found on this game version - skipping the spirit trail visual. The light glow still works.");
				return;
			}
			Color value = GhostWalkPlugin.GhostTintColor.Value;
			Color val3 = Color.Lerp(value, Color.white, 0.35f);
			for (int i = 0; i < 2; i++)
			{
				GameObject val4 = new GameObject($"GhostWalk_Trail_{i}");
				val4.transform.SetParent(_visualRig.transform, false);
				val4.transform.localPosition = new Vector3((i == 0) ? 0.2f : (-0.2f), 0f, 0f);
				TrailRenderer val5 = val4.AddComponent<TrailRenderer>();
				val5.time = 1.1f;
				val5.startWidth = 0.08f;
				val5.endWidth = 0f;
				((Renderer)val5).material = new Material(val2);
				val5.startColor = new Color(val3.r, val3.g, val3.b, 0.4f);
				val5.endColor = new Color(val3.r, val3.g, val3.b, 0f);
			}
		}

		private void AttachFrostAura(Transform parent)
		{
			//IL_0045: 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_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			if (!GhostWalkPlugin.EnableFrostAura.Value)
			{
				return;
			}
			int num = 0;
			try
			{
				foreach (GameObject item in FindColdEffectPrefabs())
				{
					GameObject val = Object.Instantiate<GameObject>(item, parent);
					val.transform.localPosition = Vector3.zero;
					val.transform.localRotation = Quaternion.identity;
					MonoBehaviour[] componentsInChildren = val.GetComponentsInChildren<MonoBehaviour>(true);
					foreach (MonoBehaviour val2 in componentsInChildren)
					{
						if ((Object)(object)val2 != (Object)null && ((object)val2).GetType().Name == "TimedDestruction")
						{
							Object.Destroy((Object)(object)val2);
						}
					}
					ParticleSystem[] componentsInChildren2 = val.GetComponentsInChildren<ParticleSystem>(true);
					foreach (ParticleSystem val3 in componentsInChildren2)
					{
						MainModule main = val3.main;
						((MainModule)(ref main)).loop = true;
						val3.Play();
					}
					num++;
					Debug.Log((object)("[GhostWalk] Frost aura: using the game's '" + ((Object)item).name + "' effect."));
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[GhostWalk] Could not reuse the game's cold effect (" + ex.Message + ")."));
			}
			if (num != 0)
			{
				return;
			}
			try
			{
				BuildFallbackMist(parent);
				Debug.Log((object)"[GhostWalk] Frost aura: game cold effect not found, using the built-in mist.");
			}
			catch (Exception ex2)
			{
				Debug.LogWarning((object)("[GhostWalk] Frost aura failed (" + ex2.Message + ") - continuing without it."));
			}
		}

		private static List<GameObject> FindColdEffectPrefabs()
		{
			List<GameObject> list = new List<GameObject>();
			if ((Object)(object)ObjectDB.instance == (Object)null)
			{
				return list;
			}
			foreach (StatusEffect statusEffect in ObjectDB.instance.m_StatusEffects)
			{
				if ((Object)(object)statusEffect == (Object)null || !string.Equals(((Object)statusEffect).name, "Cold", StringComparison.OrdinalIgnoreCase) || statusEffect.m_startEffects == null || statusEffect.m_startEffects.m_effectPrefabs == null)
				{
					continue;
				}
				EffectData[] effectPrefabs = statusEffect.m_startEffects.m_effectPrefabs;
				for (int i = 0; i < effectPrefabs.Length; i++)
				{
					GameObject val = effectPrefabs[i]?.m_prefab;
					if (!((Object)(object)val == (Object)null) && !((Object)val).name.StartsWith("sfx", StringComparison.OrdinalIgnoreCase) && !((Object)(object)val.GetComponent<ZNetView>() != (Object)null))
					{
						list.Add(val);
					}
				}
			}
			return list;
		}

		private static Texture2D SoftCircle()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_softCircle != (Object)null)
			{
				return _softCircle;
			}
			Texture2D val = new Texture2D(64, 64, (TextureFormat)4, false);
			Color32[] array = (Color32[])(object)new Color32[4096];
			for (int i = 0; i < 64; i++)
			{
				for (int j = 0; j < 64; j++)
				{
					float num = ((float)j + 0.5f) / 64f * 2f - 1f;
					float num2 = ((float)i + 0.5f) / 64f * 2f - 1f;
					float num3 = Mathf.Clamp01(1f - Mathf.Sqrt(num * num + num2 * num2));
					num3 *= num3;
					array[i * 64 + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)(num3 * 255f));
				}
			}
			val.SetPixels32(array);
			val.Apply();
			((Object)val).hideFlags = (HideFlags)61;
			_softCircle = val;
			return val;
		}

		private static void BuildFallbackMist(Transform parent)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: 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_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Expected O, but got Unknown
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0202: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0224: Expected O, but got Unknown
			Shader val = FindFirstAvailableShader("Sprites/Default", "Legacy Shaders/Particles/Alpha Blended", "Particles/Standard Unlit", "Universal Render Pipeline/Particles/Unlit", "Unlit/Transparent");
			if ((Object)(object)val == (Object)null)
			{
				Debug.LogWarning((object)"[GhostWalk] No usable particle shader - skipping the frost mist.");
				return;
			}
			Color value = GhostWalkPlugin.GhostTintColor.Value;
			GameObject val2 = new GameObject("GhostWalk_FrostMist");
			val2.transform.SetParent(parent, false);
			val2.transform.localPosition = Vector3.zero;
			ParticleSystem val3 = val2.AddComponent<ParticleSystem>();
			val3.Stop(true, (ParticleSystemStopBehavior)0);
			MainModule main = val3.main;
			((MainModule)(ref main)).loop = true;
			((MainModule)(ref main)).playOnAwake = false;
			((MainModule)(ref main)).startLifetime = MinMaxCurve.op_Implicit(2.5f);
			((MainModule)(ref main)).startSpeed = MinMaxCurve.op_Implicit(0.15f);
			((MainModule)(ref main)).startSize = new MinMaxCurve(0.6f, 1.2f);
			((MainModule)(ref main)).startColor = MinMaxGradient.op_Implicit(new Color(value.r, value.g, value.b, 0.35f));
			((MainModule)(ref main)).gravityModifier = MinMaxCurve.op_Implicit(-0.03f);
			((MainModule)(ref main)).simulationSpace = (ParticleSystemSimulationSpace)1;
			((MainModule)(ref main)).maxParticles = 80;
			EmissionModule emission = val3.emission;
			((EmissionModule)(ref emission)).rateOverTime = MinMaxCurve.op_Implicit(20f);
			ShapeModule shape = val3.shape;
			((ShapeModule)(ref shape)).shapeType = (ParticleSystemShapeType)0;
			((ShapeModule)(ref shape)).radius = 0.45f;
			ColorOverLifetimeModule colorOverLifetime = val3.colorOverLifetime;
			((ColorOverLifetimeModule)(ref colorOverLifetime)).enabled = true;
			Gradient val4 = new Gradient();
			val4.SetKeys((GradientColorKey[])(object)new GradientColorKey[2]
			{
				new GradientColorKey(Color.white, 0f),
				new GradientColorKey(Color.white, 1f)
			}, (GradientAlphaKey[])(object)new GradientAlphaKey[3]
			{
				new GradientAlphaKey(0f, 0f),
				new GradientAlphaKey(1f, 0.3f),
				new GradientAlphaKey(0f, 1f)
			});
			((ColorOverLifetimeModule)(ref colorOverLifetime)).color = MinMaxGradient.op_Implicit(val4);
			Material val5 = new Material(val);
			val5.mainTexture = (Texture)(object)SoftCircle();
			ParticleSystemRenderer component = val2.GetComponent<ParticleSystemRenderer>();
			((Renderer)component).material = val5;
			component.renderMode = (ParticleSystemRenderMode)0;
			val3.Play();
		}

		private static Shader FindFirstAvailableShader(params string[] names)
		{
			foreach (string text in names)
			{
				Shader val = Shader.Find(text);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			return null;
		}

		private void SpawnSpiritOfResurrection()
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: 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_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: 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_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: 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)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: 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_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			if (!GhostWalkPlugin.EnableSpiritNpc.Value)
			{
				Debug.Log((object)"[GhostWalk] Spirit of Resurrection is disabled in config - not spawning.");
				return;
			}
			GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab("Haldor") : null);
			if ((Object)(object)val == (Object)null)
			{
				Debug.LogWarning((object)"[GhostWalk] Could not find the 'Haldor' prefab to base the Spirit of Resurrection on - skipping NPC spawn this life. Corpse pickup still works.");
				return;
			}
			Vector3 val2 = (_hasRespawnPos ? _respawnPos : ((Component)_player).transform.position);
			Quaternion val3 = (_hasRespawnPos ? _respawnRot : ((Component)_player).transform.rotation);
			Vector3 val4 = FindSpiritSpot(val2, val3);
			Vector3 val5 = val2 - val4;
			val5.y = 0f;
			Quaternion val6 = ((((Vector3)(ref val5)).sqrMagnitude > 0.01f) ? Quaternion.LookRotation(((Vector3)(ref val5)).normalized) : val3);
			_spiritAnchor = val4;
			_hasSpiritAnchor = true;
			bool activeSelf = val.activeSelf;
			val.SetActive(false);
			_spiritNpcInstance = Object.Instantiate<GameObject>(val, val4, val6);
			val.SetActive(activeSelf);
			((Object)_spiritNpcInstance).name = "GhostWalk_SpiritOfResurrection";
			StripToVisualsOnly(_spiritNpcInstance);
			TintGhostly(_spiritNpcInstance);
			SpiritOfResurrectionInteract spiritOfResurrectionInteract = _spiritNpcInstance.AddComponent<SpiritOfResurrectionInteract>();
			spiritOfResurrectionInteract.Setup(this, val4, val6);
			_spiritNpcInstance.SetActive(true);
			Debug.Log((object)($"[GhostWalk] Spirit of Resurrection spawned at {val4} " + $"({Vector3.Distance(val4, val2):0.#}m from respawn point)."));
		}

		private static Vector3 FindSpiritSpot(Vector3 center, Quaternion facing)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: 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_006a: 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_0269: Unknown result type (might be due to invalid IL or missing references)
			//IL_026a: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0270: Unknown result type (might be due to invalid IL or missing references)
			//IL_027a: Unknown result type (might be due to invalid IL or missing references)
			//IL_027f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0284: Unknown result type (might be due to invalid IL or missing references)
			//IL_0288: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			//IL_022d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0232: Unknown result type (might be due to invalid IL or missing references)
			//IL_0237: Unknown result type (might be due to invalid IL or missing references)
			int mask = LayerMask.GetMask(new string[6] { "Default", "static_solid", "Default_small", "piece", "terrain", "vehicle" });
			Vector3 val = facing * Vector3.forward;
			val.y = 0f;
			if (((Vector3)(ref val)).sqrMagnitude < 0.001f)
			{
				val = Vector3.forward;
			}
			((Vector3)(ref val)).Normalize();
			float[] array = new float[4] { 2.5f, 2f, 1.5f, 3f };
			float[] array2 = new float[10] { 0f, 40f, -40f, 80f, -80f, 120f, -120f, 160f, -160f, 180f };
			Vector3 val2 = center + Vector3.up * 1.2f;
			float[] array3 = array;
			RaycastHit val5 = default(RaycastHit);
			foreach (float num in array3)
			{
				float[] array4 = array2;
				foreach (float num2 in array4)
				{
					Vector3 val3 = Quaternion.Euler(0f, num2, 0f) * val;
					Vector3 val4 = center + val3 * num;
					if (!Physics.Linecast(val2, val4 + Vector3.up * 1.2f, mask) && Physics.Raycast(val4 + Vector3.up * 1.5f, Vector3.down, ref val5, 3f, mask) && !(Mathf.Abs(((RaycastHit)(ref val5)).point.y - center.y) > 0.5f) && !(((RaycastHit)(ref val5)).normal.y < 0.7f))
					{
						Vector3 point = ((RaycastHit)(ref val5)).point;
						if (!Physics.Raycast(point + Vector3.up * 0.1f, Vector3.up, 2.1f, mask) && !Physics.CheckCapsule(point + Vector3.up * 0.6f, point + Vector3.up * 1.4f, 0.4f, mask))
						{
							return point + Vector3.up * 0.02f;
						}
					}
				}
			}
			Debug.LogWarning((object)"[GhostWalk] No clear floor spot found for the Spirit of Resurrection - placing it right beside the respawn point instead.");
			return center + facing * Vector3.right * 1.2f;
		}

		private static void StripToVisualsOnly(GameObject root)
		{
			try
			{
				MonoBehaviour[] componentsInChildren = root.GetComponentsInChildren<MonoBehaviour>(true);
				foreach (MonoBehaviour val in componentsInChildren)
				{
					if ((Object)(object)val != (Object)null && !(val is Character) && !(val is ZNetView))
					{
						Object.DestroyImmediate((Object)(object)val);
					}
				}
				MonoBehaviour[] componentsInChildren2 = root.GetComponentsInChildren<MonoBehaviour>(true);
				foreach (MonoBehaviour val2 in componentsInChildren2)
				{
					if (val2 is Character)
					{
						Object.DestroyImmediate((Object)(object)val2);
					}
				}
				MonoBehaviour[] componentsInChildren3 = root.GetComponentsInChildren<MonoBehaviour>(true);
				foreach (MonoBehaviour val3 in componentsInChildren3)
				{
					if ((Object)(object)val3 != (Object)null)
					{
						Object.DestroyImmediate((Object)(object)val3);
					}
				}
				Rigidbody[] componentsInChildren4 = root.GetComponentsInChildren<Rigidbody>(true);
				foreach (Rigidbody val4 in componentsInChildren4)
				{
					Object.DestroyImmediate((Object)(object)val4);
				}
				Animator[] componentsInChildren5 = root.GetComponentsInChildren<Animator>(true);
				foreach (Animator val5 in componentsInChildren5)
				{
					val5.applyRootMotion = false;
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[GhostWalk] Non-fatal issue stripping the Spirit clone: " + ex.Message));
			}
		}

		private static void SafeDestroyComponentImmediate<T>(GameObject go) where T : Component
		{
			try
			{
				T component = go.GetComponent<T>();
				if ((Object)(object)component != (Object)null)
				{
					Object.DestroyImmediate((Object)(object)component);
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[GhostWalk] Non-fatal cleanup issue removing " + typeof(T).Name + " from Spirit of Resurrection clone: " + ex.Message));
			}
		}

		private void SetGameGhostMode(bool on)
		{
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(Player), "SetGhostMode", new Type[1] { typeof(bool) }, (Type[])null);
				if (methodInfo != null)
				{
					methodInfo.Invoke(_player, new object[1] { on });
					_gameGhostModeOn = on;
					Debug.Log((object)("[GhostWalk] Game ghost mode " + (on ? "ON" : "OFF") + "."));
					return;
				}
				FieldInfo fieldInfo = AccessTools.Field(typeof(Player), "m_ghostMode");
				if (fieldInfo != null)
				{
					fieldInfo.SetValue(_player, on);
					_gameGhostModeOn = on;
					Debug.Log((object)("[GhostWalk] Game ghost mode " + (on ? "ON" : "OFF") + " (via field)."));
				}
				else
				{
					Debug.LogWarning((object)"[GhostWalk] Player.SetGhostMode / m_ghostMode not found on this game version - relying on the enemy-targeting patches alone.");
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[GhostWalk] Could not change game ghost mode (" + ex.Message + ")."));
			}
		}

		private void SetGhostFlag(bool on)
		{
			try
			{
				ZNetView val = (((Object)(object)_player != (Object)null) ? ((Component)_player).GetComponent<ZNetView>() : null);
				ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
				if (val2 != null)
				{
					val2.Set("GhostWalk_Ghost", on);
					Debug.Log((object)("[GhostWalk] Ghost flag for other players: " + (on ? "ON" : "OFF") + "."));
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[GhostWalk] Could not set the ghost flag (" + ex.Message + ")."));
			}
		}

		private void ClearExistingAggro()
		{
			foreach (Character item in GetAllCharactersSafe())
			{
				if ((Object)(object)item == (Object)null || item.IsPlayer())
				{
					continue;
				}
				BaseAI component = ((Component)item).GetComponent<BaseAI>();
				if ((Object)(object)component == (Object)null)
				{
					continue;
				}
				try
				{
					Traverse val = Traverse.Create((object)component);
					Traverse val2 = val.Field("m_targetCreature");
					if (val2.FieldExists() && (Object)(object)val2.GetValue<Character>() == (Object)(object)_player)
					{
						val2.SetValue((object)null);
					}
					Traverse val3 = val.Field("m_alerted");
					if (val3.FieldExists())
					{
						val3.SetValue((object)false);
					}
				}
				catch
				{
				}
			}
		}

		private static List<Character> GetAllCharactersSafe()
		{
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(Character), "GetAllCharacters", Type.EmptyTypes, (Type[])null);
				if (methodInfo != null && typeof(IEnumerable<Character>).IsAssignableFrom(methodInfo.ReturnType) && methodInfo.Invoke(null, null) is IEnumerable<Character> collection)
				{
					return new List<Character>(collection);
				}
				MethodInfo methodInfo2 = AccessTools.Method(typeof(Character), "GetAllCharacters", new Type[1] { typeof(List<Character>) }, (Type[])null);
				if (methodInfo2 != null)
				{
					List<Character> list = new List<Character>();
					methodInfo2.Invoke(null, new object[1] { list });
					return list;
				}
				Debug.LogWarning((object)"[GhostWalk] Character.GetAllCharacters not found in an expected shape on this game version - skipping the stale-aggro sweep this life. The IsValidTarget patch still stops future targeting.");
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[GhostWalk] Could not enumerate characters to clear stale aggro: " + ex.Message));
			}
			return new List<Character>();
		}

		private static void SafeDestroyComponent<T>(GameObject go) where T : Component
		{
			try
			{
				T component = go.GetComponent<T>();
				if ((Object)(object)component != (Object)null)
				{
					Object.Destroy((Object)(object)component);
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[GhostWalk] Non-fatal cleanup issue removing " + typeof(T).Name + " from Spirit of Resurrection clone: " + ex.Message));
			}
		}

		private static void TintGhostly(GameObject go)
		{
			//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_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			Color value = GhostWalkPlugin.GhostTintColor.Value;
			Renderer[] componentsInChildren = go.GetComponentsInChildren<Renderer>();
			foreach (Renderer val in componentsInChildren)
			{
				Material[] materials = val.materials;
				foreach (Material val2 in materials)
				{
					try
					{
						if (val2.HasProperty("_Color"))
						{
							val2.color = Color.Lerp(val2.color, value, 0.6f);
						}
						if (val2.HasProperty("_EmissionColor"))
						{
							val2.EnableKeyword("_EMISSION");
							val2.SetColor("_EmissionColor", value * 0.5f);
						}
					}
					catch
					{
					}
				}
			}
		}

		public bool TeleportToSpirit()
		{
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			if (!IsGhostActive || (Object)(object)_player == (Object)null)
			{
				return false;
			}
			if (Time.time - _lastTeleportTime < 1f)
			{
				return true;
			}
			_lastTeleportTime = Time.time;
			Debug.Log((object)"[GhostWalk] Teleport to Spirit requested.");
			if (!_hasSpiritAnchor)
			{
				ShowMessage("There is nowhere for your spirit to return to.");
				Debug.LogWarning((object)"[GhostWalk] Teleport refused: no Spirit anchor / respawn position recorded.");
				return true;
			}
			if (!GhostWalkPlugin.AllowTeleportWithRestrictedItems.Value && !((Humanoid)_player).IsTeleportable(false))
			{
				ShowMessage("The spirits refuse to carry what you are holding.");
				return true;
			}
			GameObject val = EnsureSpiritExists();
			_teleportDestination = (_hasRespawnPos ? _respawnPos : _spiritAnchor);
			_teleportGiveUpTime = Time.time + 5f;
			_teleportRequested = true;
			Debug.Log((object)$"[GhostWalk] Teleporting from {((Component)_player).transform.position} to {_teleportDestination}.");
			ShowMessage(((Object)(object)val != (Object)null) ? "Your spirit is drawn back to the Spirit of Resurrection." : "Your spirit returns to where you awoke.");
			ProcessTeleportRequest();
			return true;
		}

		private void ProcessTeleportRequest()
		{
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			if (!_teleportRequested)
			{
				return;
			}
			if (!IsGhostActive || (Object)(object)_player == (Object)null)
			{
				_teleportRequested = false;
			}
			else
			{
				if (((Character)_player).IsTeleporting())
				{
					return;
				}
				bool flag = false;
				try
				{
					flag = ((Character)_player).TeleportTo(_teleportDestination, ((Component)_player).transform.rotation, true);
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)("[GhostWalk] Player.TeleportTo threw (" + ex.Message + ")."));
				}
				if (flag)
				{
					_teleportRequested = false;
					Debug.Log((object)"[GhostWalk] Player.TeleportTo accepted - teleport under way.");
				}
				else if (!(Time.time < _teleportGiveUpTime))
				{
					_teleportRequested = false;
					Debug.LogWarning((object)"[GhostWalk] Player.TeleportTo kept refusing for 5s - moving the player directly as a last resort.");
					((Component)_player).transform.position = _teleportDestination;
					Rigidbody component = ((Component)_player).GetComponent<Rigidbody>();
					if ((Object)(object)component != (Object)null)
					{
						component.position = _teleportDestination;
						component.linearVelocity = Vector3.zero;
					}
				}
			}
		}

		private bool PlayerHasInputFocus()
		{
			if (!_takeInputLookedUp)
			{
				_takeInputLookedUp = true;
				_takeInputMethod = AccessTools.Method(typeof(Player), "TakeInput", Type.EmptyTypes, (Type[])null);
			}
			if (_takeInputMethod == null)
			{
				return true;
			}
			try
			{
				return (bool)_takeInputMethod.Invoke(_player, null);
			}
			catch
			{
				return true;
			}
		}

		private bool TeleportHotkeyPressed()
		{
			//IL_0007: 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_000f: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				KeyboardShortcut value = GhostWalkPlugin.TeleportHotkey.Value;
				if ((int)((KeyboardShortcut)(ref value)).MainKey == 0 || (Object)(object)_player == (Object)null)
				{
					return false;
				}
				return ((KeyboardShortcut)(ref value)).IsDown() && PlayerHasInputFocus();
			}
			catch
			{
				return false;
			}
		}

		private GameObject EnsureSpiritExists()
		{
			if ((Object)(object)_spiritNpcInstance != (Object)null)
			{
				return _spiritNpcInstance;
			}
			SpawnSpiritOfResurrection();
			return _spiritNpcInstance;
		}

		private static void ShowMessage(string text)
		{
			if ((Object)(object)MessageHud.instance != (Object)null)
			{
				MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true);
			}
		}

		public void Revive(bool viaSpiritNpc)
		{
			if (!IsGhostActive)
			{
				return;
			}
			IsGhostActive = false;
			_hasCorpse = false;
			_hasSpiritAnchor = false;
			_flightAwaitingJump = false;
			_airborneTimer = 0f;
			SetFlight(on: false);
			if (viaSpiritNpc)
			{
				try
				{
					ReturnGearFromTombstone();
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)("[GhostWalk] Failed to return gear from tombstone: " + ex.Message));
				}
				ApplySpiritReviveCosts();
			}
			else
			{
				DeathSnapshot.Discard();
			}
			((Character)_player).SetHealth(((Character)_player).GetMaxHealth() * GhostWalkPlugin.ReviveHealthFraction.Value);
			_player.SetMaxStamina(((Character)_player).GetMaxStamina(), true);
			((Character)_player).UseStamina((0f - ((Character)_player).GetMaxStamina()) * GhostWalkPlugin.ReviveStaminaFraction.Value);
			if (_corpsePin != null && (Object)(object)Minimap.instance != (Object)null)
			{
				Minimap.instance.RemovePin(_corpsePin);
				_corpsePin = null;
			}
			if ((Object)(object)_visualRig != (Object)null)
			{
				Object.Destroy((Object)(object)_visualRig);
				_visualRig = null;
			}
			RemoveWispModel();
			_teleportRequested = false;
			if ((Object)(object)_spiritNpcInstance != (Object)null)
			{
				Object.Destroy((Object)(object)_spiritNpcInstance);
				_spiritNpcInstance = null;
			}
			if ((Object)(object)_ambience != (Object)null)
			{
				_ambience.FadeOutAndDestroy();
				_ambience = null;
			}
			_hasRespawnPos = false;
			if (_gameGhostModeOn)
			{
				SetGameGhostMode(on: false);
			}
			SetGhostFlag(on: false);
			if ((Object)(object)MessageHud.instance != (Object)null)
			{
				string text = (viaSpiritNpc ? "The Spirit of Resurrection calls you home. Your skills and gear feel the toll." : "You have returned to your body.");
				MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true);
			}
		}

		private void ReturnGearFromTombstone()
		{
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			_restoredEquippedGear.Clear();
			if ((Object)(object)_player == (Object)null)
			{
				return;
			}
			List<ItemData> list = DeathSnapshot.TakeItems();
			if (list.Count == 0)
			{
				Debug.LogWarning((object)"[GhostWalk] Spirit revive: no death snapshot to restore. Either the death was not tracked (see the 'Death:' line in the log) or the gear was already returned. Your tombstone, if it exists, was left alone.");
				return;
			}
			Inventory inventory = ((Humanoid)_player).GetInventory();
			if (inventory == null)
			{
				Debug.LogWarning((object)"[GhostWalk] Could not access the player inventory for gear transfer.");
				return;
			}
			List<ItemData> list2 = new List<ItemData>();
			int num = 0;
			int num2 = 0;
			foreach (ItemData item in list)
			{
				bool equipped = item.m_equipped;
				item.m_equipped = false;
				if (inventory.AddItem(item))
				{
					num++;
					if (equipped)
					{
						list2.Add(item);
					}
				}
				else
				{
					ItemDrop.DropItem(item, item.m_stack, ((Component)_player).transform.position + Vector3.up, ((Component)_player).transform.rotation);
					num2++;
				}
			}
			foreach (ItemData item2 in list2)
			{
				if (inventory.ContainsItem(item2))
				{
					_restoredEquippedGear.Add(item2);
					try
					{
						((Humanoid)_player).EquipItem(item2, true);
					}
					catch (Exception ex)
					{
						Debug.LogWarning((object)("[GhostWalk] Could not re-equip " + item2.m_shared.m_name + ": " + ex.Message));
					}
				}
			}
			Debug.Log((object)($"[GhostWalk] Spirit revive: returned {num} item stack(s), dropped {num2} " + $"(bag full), re-equipped {_restoredEquippedGear.Count}."));
			ShowMessage((num2 > 0) ? $"Retrieved {num} items. {num2} did not fit and were dropped at your feet." : $"Retrieved {num} items from your tombstone.");
		}

		private void ApplySpiritReviveCosts()
		{
			try
			{
				float value = GhostWalkPlugin.SpiritNpcSkillLossPercent.Value;
				Skills skills = ((Character)_player).GetSkills();
				if ((Object)(object)skills != (Object)null && value > 0f)
				{
					skills.LowerAllSkills(value / 100f);
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[GhostWalk] Skill loss failed: " + ex.Message));
			}
			try
			{
				float value2 = GhostWalkPlugin.SpiritNpcDurabilityLossPercent.Value;
				Inventory inventory = ((Humanoid)_player).GetInventory();
				if (value2 > 0f && inventory != null)
				{
					List<ItemData> list = new List<ItemData>(_restoredEquippedGear);
					if (list.Count == 0)
					{
						foreach (ItemData allItem in inventory.GetAllItems())
						{
							if (allItem != null && allItem.m_equipped)
							{
								list.Add(allItem);
							}
						}
					}
					foreach (ItemData item in list)
					{
						if (item != null && item.m_shared != null && item.m_shared.m_useDurability)
						{
							float maxDurability = item.GetMaxDurability();
							if (!(maxDurability <= 0f))
							{
								float num = maxDurability * (value2 / 100f);
								item.m_durability = Mathf.Max(1f, item.m_durability - num);
							}
						}
					}
					NotifyInventoryChangedSafe(inventory);
				}
			}
			catch (Exception ex2)
			{
				Debug.LogWarning((object)("[GhostWalk] Durability loss failed: " + ex2.Message));
			}
			try
			{
				ApplyResurrectionSickness();
			}
			catch (Exception arg)
			{
				Debug.LogWarning((object)$"[GhostWalk] Resurrection Sickness failed: {arg}");
			}
		}

		private static void NotifyInventoryChangedSafe(Inventory inv)
		{
			if (inv == null)
			{
				return;
			}
			string[] array = new string[3] { "Changed", "SetDirty", "OnItemsChanged" };
			foreach (string text in array)
			{
				try
				{
					MethodInfo methodInfo = AccessTools.Method(typeof(Inventory), text, Type.EmptyTypes, (Type[])null);
					if (methodInfo != null)
					{
						methodInfo.Invoke(inv, null);
						break;
					}
				}
				catch
				{
				}
			}
		}

		private void ApplyResurrectionSickness()
		{
			float num = GhostWalkPlugin.ResurrectionSicknessDurationMinutes.Value * 60f;
			if (num <= 0f)
			{
				Debug.Log((object)"[GhostWalk] Resurrection Sickness duration is 0 - skipping debuff.");
				return;
			}
			_resurrectionSicknessTimer = num;
			EnsureResurrectionSicknessEffectTemplate(num);
			SEMan sEMan = ((Character)_player).GetSEMan();
			if (sEMan == null || (Object)(object)_resurrectionSicknessEffect == (Object)null)
			{
				Debug.LogWarning((object)"[GhostWalk] Could not apply Resurrection Sickness - seman or effect is null.");
				return;
			}
			sEMan.AddStatusEffect(_resurrectionSicknessEffect, true, 0, 0f, (short)(-1));
			bool flag = false;
			foreach (StatusEffect statusEffect in sEMan.GetStatusEffects())
			{
				if ((Object)(object)statusEffect != (Object)null && statusEffect.m_name == _resurrectionSicknessEffect.m_name)
				{
					flag = true;
					break;
				}
			}
			if (flag)
			{
				ShowMessage($"Resurrection Sickness applied ({GhostWalkPlugin.ResurrectionSicknessDurationMinutes.Value:0.#} min)");
				Debug.Log((object)"[GhostWalk] Resurrection Sickness status effect is active on the player.");
			}
			else
			{
				Debug.LogWarning((object)"[GhostWalk] AddStatusEffect ran but Resurrection Sickness is NOT in the player's status effect list - the game refused it.");
			}
		}

		private static void EnsureResurrectionSicknessEffectTemplate(float durationSeconds)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			if ((Object)(object)_resurrectionSicknessEffect == (Object)null)
			{
				SE_ResurrectionSickness sE_ResurrectionSickness = ScriptableObject.CreateInstance<SE_ResurrectionSickness>();
				((Object)sE_ResurrectionSickness).name = "GhostWalk_ResurrectionSickness";
				((StatusEffect)sE_ResurrectionSickness).m_name = "Resurrection Sickness";
				_resurrectionSicknessEffect = (StatusEffect)(object)sE_ResurrectionSickness;
			}
			SE_Stats val = (SE_Stats)_resurrectionSicknessEffect;
			((StatusEffect)val).m_icon = SicknessIcon.Get();
			((StatusEffect)val).m_tooltip = BuildSicknessTooltip();
			((StatusEffect)val).m_ttl = durationSeconds;
			val.m_staminaRegenMultiplier = 1f - GhostWalkPlugin.ResurrectionSicknessStaminaRegenReductionPercent.Value / 100f;
		}

		private static string BuildSicknessTooltip()
		{
			string text = "Recently pulled back from death by a Spirit of Resurrection.\nStamina regeneration is reduced and food buffs fade faster.";
			float value = GhostWalkPlugin.ResurrectionSicknessMaxHealth.Value;
			if (value > 0f)
			{
				text += $"\nMaximum health is limited to {value:0.#} until it fades.";
			}
			return text;
		}

		public void ScaleMostRecentFoodDuration(float scale)
		{
			try
			{
				Traverse val = Traverse.Create((object)_player).Field("m_foods");
				if (!val.FieldExists())
				{
					Debug.LogWarning((object)"[GhostWalk] Player.m_foods field not found - skipping food duration penalty for Resurrection Sickness this time.");
				}
				else if (val.GetValue() is IList { Count: not 0 } list)
				{
					int index = list.Count - 1;
					object obj = list[index];
					Traverse val2 = Traverse.Create(obj).Field("m_time");
					if (!val2.FieldExists())
					{
						Debug.LogWarning((object)"[GhostWalk] Player.Food.m_time field not found - skipping food duration penalty for Resurrection Sickness this time.");
						return;
					}
					float value = val2.GetValue<float>();
					val2.SetValue((object)(value * scale));
					list[index] = obj;
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[GhostWalk] Could not adjust food duration for Resurrection Sickness (internal Player.Food layout may differ on this game version): " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnDeath")]
	internal static class Patch_Player_OnDeath
	{
		private static void Prefix(Player __instance)
		{
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			try
			{
				DeathSnapshot.BeginDeath(__instance);
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[GhostWalk] Could not snapshot inventory at death: " + ex.Message));
			}
		}

		private static void Postfix(Player __instance)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			GhostWalkController ghostWalkController = ((Component)__instance).GetComponent<GhostWalkController>();
			if ((Object)(object)ghostWalkController == (Object)null)
			{
				ghostWalkController = ((Component)__instance).gameObject.AddComponent<GhostWalkController>();
			}
			ghostWalkController.RecordCorpsePosition(((Component)__instance).transform.position);
			Debug.Log((object)("[GhostWalk] Player.OnDeath fired - recorded corpse at " + $"{((Component)__instance).transform.position}."));
			try
			{
				DeathSnapshot.EndDeath(__instance);
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[GhostWalk] Could not track the tombstone after death: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnRespawn")]
	internal static class Patch_Player_OnRespawn
	{
		private static void Postfix(Player __instance)
		{
			if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
			{
				Debug.Log((object)"[GhostWalk] Player.OnRespawn fired.");
				GhostWalkController component = ((Component)__instance).GetComponent<GhostWalkController>();
				if ((Object)(object)component != (Object)null)
				{
					component.EnterGhostMode();
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnSpawned")]
	internal static class Patch_Player_OnSpawned
	{
		private static void Postfix(Player __instance)
		{
			if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
			{
				Debug.Log((object)"[GhostWalk] Player.OnSpawned fired.");
				GhostWalkController component = ((Component)__instance).GetComponent<GhostWalkController>();
				if ((Object)(object)component != (Object)null)
				{
					component.EnterGhostMode();
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "GetJogSpeedFactor")]
	internal static class Patch_Player_GetJogSpeedFactor
	{
		private static void Postfix(Player __instance, ref float __result)
		{
			if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && (Object)(object)GhostWalkController.Instance != (Object)null && GhostWalkController.Instance.IsGhostActive)
			{
				__result *= GhostWalkPlugin.GhostSpeedMultiplier.Value;
			}
		}
	}
	[HarmonyPatch(typeof(Player), "GetRunSpeedFactor")]
	internal static class Patch_Player_GetRunSpeedFactor
	{
		private static void Postfix(Player __instance, ref float __result)
		{
			if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && (Object)(object)GhostWalkController.Instance != (Object)null && GhostWalkController.Instance.IsGhostActive)
			{
				__result *= GhostWalkPlugin.GhostSpeedMultiplier.Value;
			}
		}
	}
	[HarmonyPatch(typeof(Player), "UseStamina")]
	internal static class Patch_Player_UseStamina_Ghost
	{
		private static bool Prefix(Player __instance, float __0)
		{
			if (__0 > 0f && (Object)(object)__instance == (Object)(object)Player.m_localPlayer && GhostWalkPlugin.GhostNoStaminaDrain.Value && (Object)(object)GhostWalkController.Instance != (Object)null && GhostWalkController.Instance.IsGhostActive)
			{
				return false;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Player), "Message")]
	internal static class Patch_Player_Message_FlyToggle
	{
		private static bool Prefix()
		{
			return !GhostWalkController.SuppressFlyMessage;
		}
	}
	[HarmonyPatch]
	internal static class Patch_Minimap_Explore_Ghost
	{
		private static float _lastLogTime = -99f;

		private static IEnumerable<MethodBase> TargetMethods()
		{
			List<MethodBase> list = new List<MethodBase>();
			MethodInfo[] methods = typeof(Minimap).GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (!(methodInfo.Name != "Explore"))
				{
					ParameterInfo[] parameters = methodInfo.GetParameters();
					if (parameters.Length >= 2 && parameters[0].ParameterType == typeof(Vector3) && parameters[1].ParameterType == typeof(float))
					{
						list.Add(methodInfo);
					}
				}
			}
			if (list.Count == 0)
			{
				List<string> list2 = new List<string>();
				MethodInfo[] methods2 = typeof(Minimap).GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo2 in methods2)
				{
					if (methodInfo2.Name == "Explore")
					{
						list2.Add(string.Join(", ", Array.ConvertAll(methodInfo2.GetParameters(), (ParameterInfo pi) => pi.ParameterType.Name)));
					}
				}
				Debug.LogError((object)("[GhostWalk] No Minimap.Explore(Vector3, float, ...) overload found on this game version - map-exploration blocking while ghosted will NOT work. " + ((list2.Count > 0) ? ("Explore overloads that DO exist: (" + string.Join(") | (", list2) + ")") : "No method named 'Explore' exists on Minimap at all.")));
				throw new Exception("Minimap.Explore(Vector3, float, ...) not found");
			}
			Debug.Log((object)$"[GhostWalk] Map-exploration block: patching {list.Count} Minimap.Explore overload(s).");
			return list;
		}

		private static bool Prefix()
		{
			bool flag = GhostWalkPlugin.BlockMapExplorationWhileGhost.Value && (Object)(object)GhostWalkController.Instance != (Object)null && GhostWalkController.Instance.IsGhostActive;
			if (flag && Time.unscaledTime - _lastLogTime > 2f)
			{
				_lastLogTime = Time.unscaledTime;
				Debug.Log((object)"[GhostWalk] Blocked a Minimap.Explore(Vector3, float) call while ghosted.");
			}
			return !flag;
		}
	}
	[HarmonyPatch(typeof(Character), "Damage")]
	internal static class Patch_Character_Damage
	{
		private static bool Prefix(Character __instance)
		{
			if (!GhostWalkPlugin.Invulnerable.Value)
			{
				return true;
			}
			if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && (Object)(object)GhostWalkController.Instance != (Object)null && GhostWalkController.Instance.IsGhostActive)
			{
				return false;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Humanoid), "StartAttack")]
	internal static class Patch_Humanoid_StartAttack
	{
		private static bool Prefix(Humanoid __instance, ref bool __result)
		{
			if (!GhostWalkPlugin.BlockAttacksWhileGhost.Value)
			{
				return true;
			}
			if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && (Object)(object)GhostWalkController.Instance != (Object)null && GhostWalkController.Instance.IsGhostActive)
			{
				__result = false;
				return false;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(BaseAI), "IsValidTarget")]
	internal static class Patch_BaseAI_IsValidTarget
	{
		private static void Postfix(Character target, ref bool __result)
		{
			if (GhostWalkPlugin.PreventEnemyTargeting.Value && __result && (Object)(object)target == (Object)(object)Player.m_localPlayer && (Object)(object)GhostWalkController.Instance != (Object)null && GhostWalkController.Instance.IsGhostActive)
			{
				__result = false;
			}
		}
	}
	[HarmonyPatch(typeof(Player), "EatFood")]
	internal static class Patch_Player_EatFood_ResurrectionSickness
	{
		private static void Postfix(Player __instance, bool __result)
		{
			if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && __result)
			{
				GhostWalkController instance = GhostWalkController.Instance;
				if (!((Object)(object)instance == (Object)null) && instance.HasResurrectionSickness)
				{
					float value = GhostWalkPlugin.ResurrectionSicknessFoodDurationReductionPercent.Value;
					instance.ScaleMostRecentFoodDuration(1f - value / 100f);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Character), "GetMaxHealth")]
	internal static class Patch_Character_GetMaxHealth_Sickness
	{
		private static void Postfix(Character __instance, ref float __result)
		{
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			GhostWalkController instance = GhostWalkController.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				float sicknessHealthCap = instance.SicknessHealthCap;
				if (sicknessHealthCap > 0f && __result > sicknessHealthCap)
				{
					__result = sicknessHealthCap;
				}
			}
		}
	}
	[HarmonyPatch(typeof(Character), "SetHealth")]
	internal static class Patch_Character_SetHealth_Sickness
	{
		private static void Prefix(Character __instance, ref float __0)
		{
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			GhostWalkController instance = GhostWalkController.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				float sicknessHealthCap = instance.SicknessHealthCap;
				if (sicknessHealthCap > 0f && __0 > sicknessHealthCap)
				{
					__0 = sicknessHealthCap;
				}
			}
		}
	}
	[HarmonyPatch(typeof(Container), "Interact")]
	internal static class Patch_Container_Interact_Tombstone
	{
		private static bool Prefix(Container __instance, Humanoid character, bool hold, bool alt, ref bool __result)
		{
			if (!TombstoneUtil.IsTeleportTrigger(hold, alt))
			{
				return true;
			}
			if (TombstoneUtil.TryTeleportToSpirit(character, __instance))
			{
				__result = true;
				return false;
			}
			return true;
		}

		private static void Postfix(Container __instance, Humanoid character, bool __result)
		{
			if (__result && !GhostWalkPlugin.ReviveRequiresEmptyTombstone.Value)
			{
				TryReviveFromTombstone(__instance, character);
			}
		}

		internal static void TryReviveFromTombstone(Container container, Humanoid character)
		{
			if ((Object)(object)character != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			TombStone tombStone = TombstoneUtil.GetTombStone(container);
			if (!((Object)(object)tombStone == (Object)null) && TombstoneUtil.MayReviveAt(tombStone))
			{
				GhostWalkController component = ((Component)Player.m_localPlayer).GetComponent<GhostWalkController>();
				if ((Object)(object)component != (Object)null && component.IsGhostActive)
				{
					component.Revive(viaSpiritNpc: false);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Container), "OnClose")]
	internal static class Patch_Container_OnClose_Tombstone
	{
		private static void Postfix(Container __instance)
		{
			if (GhostWalkPlugin.ReviveRequiresEmptyTombstone.Value && !((Object)(object)TombstoneUtil.GetTombStone(__instance) == (Object)null))
			{
				Inventory inventory = __instance.GetInventory();
				if (inventory == null || inventory.NrOfItems() <= 0)
				{
					Patch_Container_Interact_Tombstone.TryReviveFromTombstone(__instance, (Humanoid)(object)Player.m_localPlayer);
				}
			}
		}
	}
	internal static class ManualPatches
	{
		private static float _lastGhostInteractLog = -99f;

		public static void Apply(Harmony harmony)
		{
			TryPatch(harmony, AccessTools.Method(typeof(TombStone), "Interact", (Type[])null, (Type[])null), AccessTools.Method(typeof(ManualPatches), "TombStone_Interact_Prefix", (Type[])null, (Type[])null), AccessTools.Method(typeof(ManualPatches), "TombStone_Interact_Postfix", (Type[])null, (Type[])null), "TombStone.Interact (alt-interact teleport)");
			TryPatch(harmony, AccessTools.Method(typeof(TombStone), "GetHoverText", (Type[])null, (Type[])null), null, AccessTools.Method(typeof(ManualPatches), "TombStone_GetHoverText_Postfix", (Type[])null, (Type[])null), "TombStone.GetHoverText (teleport hint)");
		}

		private static void TryPatch(Harmony harmony, MethodBase target, MethodInfo prefix, MethodInfo postfix, string label)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			if (target == null)
			{
				Debug.LogWarning((object)("[GhostWalk] " + label + ": method not found on this game version - skipping. The Container-level patch should still cover it."));
				return;
			}
			try
			{
				harmony.Patch(target, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(prefix), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(postfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Debug.Log((object)("[GhostWalk] Patched " + label + "."));
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[GhostWalk] " + label + ": patch failed (" + ex.Message + ") - continuing without it."));
			}
		}

		private static bool TombStone_Interact_Prefix(TombStone __instance, Humanoid character, bool hold, bool alt, ref bool __result)
		{
			if ((Object)(object)character == (Object)(object)Player.m_localPlayer && TombstoneUtil.LocalPlayerIsGhost() && Time.time - _lastGhostInteractLog > 0.5f)
			{
				_lastGhostInteractLog = Time.time;
				Debug.Log((object)($"[GhostWalk] Ghost used a grave: hold={hold}, alt={alt}, " + $"trigger={GhostWalkPlugin.TombstoneTeleportTrigger.Value}, " + $"teleportEnabled={GhostWalkPlugin.TombstoneTeleportEnabled.Value}, " + $"matches={TombstoneUtil.IsTeleportTrigger(hold, alt)}."));
			}
			if (!TombstoneUtil.IsTeleportTrigger(hold, alt))
			{
				return true;
			}
			if (TombstoneUtil.TryTeleportToSpirit(character, __instance))
			{
				__result = true;
				return false;
			}
			return true;
		}

		private static void TombStone_Interact_Postfix(TombStone __instance, Humanoid character, bool hold, bool alt, bool __result)
		{
			if (__result && !TombstoneUtil.IsTeleportTrigger(hold, alt) && !GhostWalkPlugin.ReviveRequiresEmptyTombstone.Value)
			{
				Container component = ((Component)__instance).GetComponent<Container>();
				if ((Object)(object)component != (Object)null)
				{
					Patch_Container_Interact_Tombstone.TryReviveFromTombstone(component, character);
				}
			}
		}

		private static void TombStone_GetHoverText_Postfix(TombStone __instance, ref string __result)
		{
			string text = TombstoneUtil.BuildGhostHoverHint(__instance);
			if (!string.IsNullOrEmpty(text))
			{
				__result += text;
			}
		}
	}
	[HarmonyPatch(typeof(FootStep))]
	internal static class Patch_FootStep_Silent
	{
		private static readonly string[] EffectMethods = new string[3] { "OnFoot", "OnLand", "OnJump" };

		private static IEnumerable<MethodBase> TargetMethods()
		{
			List<MethodBase> list = new List<MethodBase>();
			MethodInfo[] methods = typeof(FootStep).GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (Array.IndexOf(EffectMethods, methodInfo.Name) >= 0)
				{
					list.Add(methodInfo);
				}
			}
			if (list.Count == 0)
			{
				throw new Exception("no OnFoot/OnLand/OnJump methods found on FootStep");
			}
			return list;
		}

		private static bool Prefix(FootStep __instance)
		{
			if (!GhostWalkPlugin.SilentGhostFootsteps.Value)
			{
				return true;
			}
			if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).GetComponentInParent<Player>() != (Object)(object)Player.m_localPlayer)
			{
				return true;
			}
			if ((Object)(object)GhostWalkController.Instance != (Object)null && GhostWalkController.Instance.IsGhostActive)
			{
				return false;
			}
			return true;
		}
	}
	internal static class GhostTargetingHelper
	{
		public static bool InvolvesGhostPlayer(object[] args)
		{
			if (!GhostWalkPlugin.PreventEnemyTargeting.Value || (Object)(object)GhostWalkController.Instance == (Object)null || !GhostWalkController.Instance.IsGhostActive || (Object)(object)Player.m_localPlayer == (Object)null || args == null)
			{
				return false;
			}
			foreach (object obj in args)
			{
				Player val = (Player)((obj is Player) ? obj : null);
				if (val != null && (Object)(object)val == (Object)(object)Player.m_localPlayer)
				{
					return true;
				}
			}
			return false;
		}

		public static bool GhostActive()
		{
			return GhostWalkPlugin.PreventEnemyTargeting.Value && (Object)(object)GhostWalkController.Instance != (Object)null && GhostWalkController.Instance.IsGhostActive && (Object)(object)Player.m_localPlayer != (Object)null;
		}

		public static IEnumerable<MethodBase> BoolMethodsNamed(Type type, params string[] names)
		{
			List<MethodBase> list = new List<MethodBase>();
			MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.ReturnType == typeof(bool) && Array.IndexOf(names, methodInfo.Name) >= 0)
				{
					list.Add(methodInfo);
				}
			}
			if (list.Count == 0)
			{
				throw new Exception("none of " + string.Join("/", names) + " found on " + type.Name);
			}
			return list;
		}
	}
	[HarmonyPatch(typeof(BaseAI))]
	internal static class Patch_BaseAI_SetTarget_Ghost
	{
		private static float _lastLogTime = -99f;

		private static IEnumerable<MethodBase> TargetMethods()
		{
			List<MethodBase> list = new List<MethodBase>();
			Type[] array = new Type[2]
			{
				typeof(MonsterAI),
				typeof(BaseAI)
			};
			foreach (Type type in array)
			{
				MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo in methods)
				{
					if (methodInfo.Name == "SetTarget")
					{
						list.Add(methodInfo);
					}
				}
			}
			if (list.Count == 0)
			{
				throw new Exception("SetTarget not found on MonsterAI/BaseAI");
			}
			return list;
		}

		private static bool Prefix(BaseAI __instance, object[] __args)
		{
			if (!GhostTargetingHelper.InvolvesGhostPlayer(__args))
			{
				return true;
			}
			if (Time.unscaledTime - _lastLogTime > 2f)
			{
				_lastLogTime = Time.unscaledTime;
				string text = (((Object)(object)__instance != (Object)null) ? ((object)__instance).GetType().Name : "unknown");
				Debug.Log((object)("[GhostWalk] Vetoed SetTarget(ghost) on a " + text + "."));
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(BaseAI))]
	internal static class Patch_BaseAI_IsEnemy_Ghost
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			return GhostTargetingHelper.BoolMethodsNamed(typeof(BaseAI), "IsEnemy");
		}

		private static void Postfix(ref bool __result, object[] __args)
		{
			if (__result && GhostTargetingHelper.InvolvesGhostPlayer(__args))
			{
				__result = false;
			}
		}
	}
	[HarmonyPatch(typeof(BaseAI))]
	internal static class Patch_BaseAI_Sense_Ghost
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			return GhostTargetingHelper.BoolMethodsNamed(typeof(BaseAI), "CanSenseTarget", "CanSeeTarget", "CanHearTarget");
		}

		private static void Postfix(ref bool __result, object[] __args)
		{
			if (__result && GhostTargetingHelper.InvolvesGhostPlayer(__args))
			{
				__result = false;
			}
		}
	}
	[HarmonyPatch(typeof(Player), "Awake")]
	internal static class Patch_Player_Awake_RemoteHider
	{
		private static void Postfix(Player __instance)
		{
			if ((Object)(object)__instance != (Object)null && (Object)(object)((Component)__instance).GetComponent<GhostRemoteHider>() == (Object)null)
			{
				((Component)__instance).gameObject.AddComponent<GhostRemoteHider>();
			}
		}
	}
	[HarmonyPatch(typeof(BaseAI))]
	internal static class Patch_BaseAI_FindEnemy_Ghost
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			List<MethodBase> list = new List<MethodBase>();
			MethodInfo[] methods = typeof(BaseAI).GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.Name == "FindEnemy" && typeof(Character).IsAssignableFrom(methodInfo.ReturnType))
				{
					list.Add(methodInfo);
				}
			}
			if (list.Count == 0)
			{
				throw new Exception("BaseAI.FindEnemy not found");
			}
			return list;
		}

		private static void Postfix(ref Character __result)
		{
			if ((Object)(object)__result != (Object)null && GhostTargetingHelper.GhostActive() && (Object)(object)__result == (Object)(object)Player.m_localPlayer)
			{
				__result = null;
			}
		}
	}
	[HarmonyPatch(typeof(BaseAI))]
	internal static class Patch_AI_Update_Ghost
	{
		private static readonly Dictionary<Type, FieldInfo[]> FieldCache = new Dictionary<Type, FieldInfo[]>();

		private static float _lastCatchLogTime = -99f;

		private static IEnumerable<MethodBase> TargetMethods()
		{
			List<MethodBase> list = new List<MethodBase>();
			Type[] array = new Type[2]
			{
				typeof(MonsterAI),
				typeof(BaseAI)
			};
			foreach (Type type in array)
			{
				MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo in methods)
				{
					if (methodInfo.Name == "UpdateAI" && !methodInfo.IsAbstract)
					{
						list.Add(methodInfo);
					}
				}
			}
			if (list.Count == 0)
			{
				throw new Exception("UpdateAI not found on MonsterAI/BaseAI");
			}
			return list;
		}

		private static void Prefix(BaseAI __instance)
		{
			if ((Object)(object)__instance == (Object)null || !GhostTargetingHelper.GhostActive())
			{
				return;
			}
			Type type = ((object)__instance).GetType();
			if (!FieldCache.TryGetValue(type, out var value))
			{
				value = new FieldInfo[2]
				{
					AccessTools.Field(type, "m_targetCreature"),
					AccessTools.Field(type, "m_alerted")
				};
				FieldCache[type] = value;
			}
			try
			{
				if (!(value[0] != null))
				{
					return;
				}
				object? value2 = value[0].GetValue(__instance);
				Character val = (Character)((value2 is Character) ? value2 : null);
				if (val != null && (Object)(object)val == (Object)(object)Player.m_localPlayer)
				{
					value[0].SetValue(__instance, null);
					if (value[1] != null)
					{
						value[1].SetValue(__instance, false);
					}
					if (Time.unscaledTime - _lastCatchLogTime > 2f)
					{
						_lastCatchLogTime = Time.unscaledTime;
						Debug.Log((object)("[GhostWalk] UpdateAI caught a live ghost target on a " + type.Name + " that SetTarget's veto didn't stop."));
					}
				}
			}
			catch
			{
			}
		}
	}
	public enum TeleportTrigger
	{
		AltInteract,
		HoldInteract,
		Both
	}
	[BepInPlugin("cranflakes.ghostwalk", "GhostWalk", "1.4.3")]
	public class GhostWalkPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "cranflakes.ghostwalk";

		public const string PluginName = "GhostWalk";

		public const string PluginVersion = "1.4.3";

		private Harmony _harmony;

		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<float> GhostSpeedMultiplier;

		public static ConfigEntry<float> SkipGhostModeRadius;

		public static ConfigEntry<float> ReviveHealthFraction;

		public static ConfigEntry<float> ReviveStaminaFraction;

		public static ConfigEntry<bool> Invulnerable;

		public static ConfigEntry<bool> BlockAttacksWhileGhost;

		public static ConfigEntry<bool> GhostFlight;

		public static ConfigEntry<bool> GhostFlightOnJumpOnly;

		public static ConfigEntry<bool> GhostNoStaminaDrain;

		public static ConfigEntry<bool> BlockMapExplorationWhileGhost;

		public static ConfigEntry<bool> PreventEnemyTargeting;

		public static ConfigEntry<bool> ShowCorpsePin;

		public static ConfigEntry<bool> EnableGhostVisual;

		public static ConfigEntry<bool> WispModelWhileGhost;

		public static ConfigEntry<Color> GhostTintColor;

		public static Con