Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of GhostWalk v1.4.3
GhostWalk.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
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