using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("RespawnAtDeath.cs")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RespawnAtDeath.cs")]
[assembly: AssemblyCopyright("Copyright © 2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("aa85ea7d-d04a-4cc3-bfd3-47a13618fd5c")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
[BepInPlugin("com.arathorv.RespawnAtDeath", "RespawnAtDeath", "2.1.2")]
public class RespawnAtDeath : BaseUnityPlugin
{
[HarmonyPatch(typeof(Player), "Awake")]
private static class Player_Awake_Patch
{
private static void Postfix(Player __instance)
{
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_006b: Unknown result type (might be due to invalid IL or missing references)
//IL_006c: Unknown result type (might be due to invalid IL or missing references)
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
//IL_008b: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)__instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null) && __instance == Player.m_localPlayer)
{
if (!customSpawnInitialized)
{
CustomSpawn = Vector3.zero;
customSpawnInitialized = true;
}
if (TryParseVector3(CustomSpawnConfig.Value, out var v))
{
CustomSpawn = v;
}
else if (UseHomePointEveryLogin.Value)
{
CustomSpawn = GetHomePointSafe(__instance);
}
ApplyRecoveryTimers();
}
}
}
[HarmonyPatch(typeof(Player), "OnSpawned")]
private static class Player_OnSpawned_Patch
{
private static void Postfix(Player __instance)
{
//IL_0046: 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)
if (!((Object)(object)__instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null) && __instance == Player.m_localPlayer && forceSpawnNextSpawned)
{
forceSpawnNextSpawned = false;
((Component)__instance).transform.position = forcedSpawnPos;
((Component)__instance).transform.rotation = Quaternion.identity;
}
}
}
[HarmonyPatch(typeof(Player), "OnDeath")]
private static class Player_OnDeath_Patch
{
private static bool Prefix(Player __instance)
{
//IL_004c: Unknown result type (might be due to invalid IL or missing references)
//IL_0051: Unknown result type (might be due to invalid IL or missing references)
//IL_0061: Unknown result type (might be due to invalid IL or missing references)
//IL_0062: Unknown result type (might be due to invalid IL or missing references)
//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
//IL_00d3: 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_00fb: Unknown result type (might be due to invalid IL or missing references)
//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
//IL_00e0: 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_00ec: 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_0109: Unknown result type (might be due to invalid IL or missing references)
//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)__instance == (Object)null)
{
return true;
}
if ((Object)(object)Player.m_localPlayer == (Object)null)
{
return true;
}
if (__instance != Player.m_localPlayer)
{
return true;
}
Vector3 position = ((Component)__instance).transform.position;
if (!deathRestarts)
{
lastDeathPoint = position;
lastDeathInitialized = true;
}
if (deathRestarts)
{
ClearBedSpawnIfPossible();
string text = PickRandomDeathMessage();
if (!string.IsNullOrWhiteSpace(text))
{
pendingDeathPinOverride = true;
pendingDeathPinOverridePos = position;
pendingDeathPinOverrideLabel = text.Trim();
}
}
AdvanceProgression();
lastDeathTimeUtc = DateTime.UtcNow;
Vector3 forcedSpawnPos = ((!deathRestarts) ? (lastDeathInitialized ? lastDeathPoint : position) : ((CustomSpawn != Vector3.zero) ? CustomSpawn : GetHomePointSafe(__instance)));
forceSpawnNextSpawned = true;
RespawnAtDeath.forcedSpawnPos = forcedSpawnPos;
ZNetView nView = GetNView((Character)(object)__instance);
if ((Object)(object)nView != (Object)null && nView.GetZDO() != null)
{
nView.GetZDO().Set("dead", true);
nView.InvokeRPC(ZNetView.Everybody, "OnDeath", Array.Empty<object>());
}
InvokeNoArgs(m_CreateDeathEffects, __instance);
if (deathRestarts)
{
DamageArmorDurability(__instance, DeathRestartArmorDamage.Value);
ClearFoodsIfPossible(__instance);
InvokeNoArgs(m_CreateTombStone, __instance);
if (!string.IsNullOrWhiteSpace(pendingDeathPinOverrideLabel))
{
AddDeathPinSafe(position, pendingDeathPinOverrideLabel);
}
}
RemoveAllStatusEffectsSafe(__instance);
if ((Object)(object)Game.instance != (Object)null)
{
Game.instance.RequestRespawn(RespawnDelaySeconds.Value, false);
}
if (deathRestarts)
{
ResetProgression();
}
return false;
}
}
[HarmonyPatch(typeof(Minimap), "AddPin")]
private static class Minimap_AddPin_Patch
{
private static void Postfix(object __result)
{
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: 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)
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
if (!pendingDeathPinOverride)
{
return;
}
PinData val = (PinData)((__result is PinData) ? __result : null);
if (val != null && val.m_type == deathPinType && !(Vector3.Distance(val.m_pos, pendingDeathPinOverridePos) > 2f))
{
if (!string.IsNullOrWhiteSpace(pendingDeathPinOverrideLabel))
{
val.m_name = pendingDeathPinOverrideLabel;
}
pendingDeathPinOverride = false;
pendingDeathPinOverrideLabel = null;
}
}
}
private Harmony _harmony;
private static Vector3 CustomSpawn = Vector3.zero;
private static bool customSpawnInitialized;
private static Vector3 lastDeathPoint = Vector3.zero;
private static bool lastDeathInitialized;
private static bool isHurt;
private static bool isInjured;
private static bool isNearDeath;
private static bool deathRestarts;
private static DateTime lastDeathTimeUtc = DateTime.MinValue;
private static bool forceSpawnNextSpawned;
private static Vector3 forcedSpawnPos;
private static ConfigEntry<bool> UseHomePointEveryLogin;
private static ConfigEntry<string> CustomSpawnConfig;
private static ConfigEntry<int> RecoveryTimeMinutes;
private static ConfigEntry<int> NearDeathRecoveryMinutes;
private static ConfigEntry<string> DeathRestartMessages;
private static ConfigEntry<float> RespawnDelaySeconds;
private static ConfigEntry<float> DeathRestartArmorDamage;
private static FieldInfo f_m_nview;
private static FieldInfo f_m_seman;
private static MethodInfo m_CreateDeathEffects;
private static MethodInfo m_CreateTombStone;
private static MethodInfo m_RemoveAllStatusEffects;
private static MethodInfo m_ClearFoods;
private static FieldInfo f_humanoidChest;
private static FieldInfo f_humanoidLeg;
private static FieldInfo f_humanoidHelmet;
private static FieldInfo f_humanoidShoulder;
private static PinType deathPinType;
private static MethodInfo cachedMinimapAddPin;
private static bool pendingDeathPinOverride;
private static Vector3 pendingDeathPinOverridePos;
private static string pendingDeathPinOverrideLabel;
private static readonly Random rng = new Random();
private void Awake()
{
//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
//IL_00f4: Expected O, but got Unknown
UseHomePointEveryLogin = ((BaseUnityPlugin)this).Config.Bind<bool>("Spawn", "UseHomePointEveryLogin", true, (ConfigDescription)null);
CustomSpawnConfig = ((BaseUnityPlugin)this).Config.Bind<string>("Spawn", "CustomSpawn", "", "Format: x,y,z. Leave empty to not override.");
RespawnDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Spawn", "RespawnDelaySeconds", 20f, (ConfigDescription)null);
RecoveryTimeMinutes = ((BaseUnityPlugin)this).Config.Bind<int>("Recovery", "RecoveryTimeMinutes", 5, (ConfigDescription)null);
NearDeathRecoveryMinutes = ((BaseUnityPlugin)this).Config.Bind<int>("Recovery", "NearDeathRecoveryMinutes", 15, (ConfigDescription)null);
DeathRestartArmorDamage = ((BaseUnityPlugin)this).Config.Bind<float>("DeathRestart", "ArmorDurabilityDamage", 100f, (ConfigDescription)null);
DeathRestartMessages = ((BaseUnityPlugin)this).Config.Bind<string>("Pins", "DeathRestartMessages", DefaultMessages(), "One message per line.");
CacheReflection();
_harmony = new Harmony("com.arathorv.RespawnAtDeath");
_harmony.PatchAll();
}
private static string DefaultMessages()
{
return "Your story ends here.\r\nThe world moves on without you.\r\nThis is where you fell.\r\nYour strength was not enough.\r\nSilence follows your final breath.\r\nYou misjudged this moment.\r\nThe ground claims you.\r\nThis place remembers your failure.\r\nYour body grows still.\r\nYou pushed too far.\r\nThat could have gone better.\r\nYou chose poorly.\r\nConfidence outpaced preparation.\r\nThis was avoidable.\r\nA mistake was made.\r\nYou underestimated the danger.\r\nYour plan ends here.\r\nYou were not ready.\r\nOverconfidence proved fatal.\r\nThis outcome was predictable.\r\nYour blood darkens the ground.\r\nThe land absorbs what remains.\r\nNo one comes to help.\r\nYour presence fades.\r\nOnly echoes remain.\r\nThe world does not pause for death.\r\nYou are reduced to memory.\r\nYour final stand was brief.\r\nThe silence is complete.\r\nNothing answers you.\r\nYou fought. You failed.\r\nThe threat proved greater than you.\r\nYou leave nothing behind.\r\nThis death teaches nothing.\r\nYour struggle ends unnoticed.\r\nYou fall without witness.\r\nYour resolve breaks.\r\nThe danger remains. You do not.\r\nThis ground has claimed many.\r\nYou are not the first.\r\nThat was reckless.\r\nYou ignored the warning signs.\r\nYou paid the price.\r\nSurvival was an option.\r\nThis ends badly.\r\nBugman Does not approve.\r\nReturn to the in-between.\r\nThe Malediction calls.\r\nAnother falls to the blight.\r\nThe last of the world dwindle.\r\nYou wasted your second chance.\r\nBorn again... and again.\r\nBorn again... and wasted it.\r\nPersevere.\r\nYour story is not over.\r\nOnce more.\r\nDo not waste his sacrifice.\r\nStand up. You're not done.\r\nDo not let them win.\r\nWas his life worth yours?\r\nThe call of the void beckons.\r\nThe aether draws in another.\r\nYou were not fit for this world.\r\nThe Stranger sought you out.";
}
private static void CacheReflection()
{
BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
f_m_nview = typeof(Character).GetField("m_nview", bindingAttr);
f_m_seman = typeof(Character).GetField("m_seman", bindingAttr);
m_CreateDeathEffects = typeof(Player).GetMethod("CreateDeathEffects", bindingAttr);
m_CreateTombStone = typeof(Player).GetMethod("CreateTombStone", bindingAttr);
m_RemoveAllStatusEffects = typeof(SEMan).GetMethod("RemoveAllStatusEffects", bindingAttr);
m_ClearFoods = typeof(Player).GetMethod("ClearFoods", bindingAttr);
f_humanoidChest = typeof(Humanoid).GetField("m_chestItem", bindingAttr);
f_humanoidLeg = typeof(Humanoid).GetField("m_legItem", bindingAttr);
f_humanoidHelmet = typeof(Humanoid).GetField("m_helmetItem", bindingAttr);
f_humanoidShoulder = typeof(Humanoid).GetField("m_shoulderItem", bindingAttr);
CacheDeathPinType();
}
private static void CacheDeathPinType()
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
deathPinType = (PinType)4;
try
{
string[] names = Enum.GetNames(typeof(PinType));
for (int i = 0; i < names.Length; i++)
{
if (string.Equals(names[i], "Death", StringComparison.OrdinalIgnoreCase))
{
deathPinType = (PinType)Enum.Parse(typeof(PinType), names[i]);
break;
}
}
}
catch
{
}
}
private static void ClearBedSpawnIfPossible()
{
if (!((Object)(object)Game.instance == (Object)null))
{
PlayerProfile playerProfile = Game.instance.GetPlayerProfile();
if (playerProfile != null)
{
playerProfile.ClearCustomSpawnPoint();
}
}
}
private static string PickRandomDeathMessage()
{
string value = DeathRestartMessages.Value;
if (string.IsNullOrWhiteSpace(value))
{
return "";
}
string[] array = value.Split(new string[3] { "\r\n", "\n", "\r" }, StringSplitOptions.None);
List<string> list = new List<string>();
foreach (string text in array)
{
if (!string.IsNullOrWhiteSpace(text))
{
list.Add(text.Trim());
}
}
if (list.Count == 0)
{
return "";
}
return list[rng.Next(0, list.Count)];
}
private static bool AddDeathPinSafe(Vector3 pos, string label)
{
//IL_0062: Unknown result type (might be due to invalid IL or missing references)
//IL_006c: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)Minimap.instance == (Object)null)
{
return false;
}
if (cachedMinimapAddPin == null)
{
cachedMinimapAddPin = FindBestAddPinMethod();
}
if (cachedMinimapAddPin == null)
{
return false;
}
try
{
ParameterInfo[] parameters = cachedMinimapAddPin.GetParameters();
object[] array = new object[parameters.Length];
array[0] = pos;
array[1] = deathPinType;
array[2] = label;
for (int i = 3; i < parameters.Length; i++)
{
Type parameterType = parameters[i].ParameterType;
if (parameterType == typeof(bool))
{
if (i == 3)
{
array[i] = true;
}
else
{
array[i] = false;
}
}
else if (parameterType == typeof(long))
{
array[i] = 0L;
}
else if (parameterType == typeof(int))
{
array[i] = 0;
}
else if (parameterType == typeof(float))
{
array[i] = 0f;
}
else if (parameterType == typeof(string))
{
array[i] = "";
}
else if (parameterType.IsEnum)
{
array[i] = Activator.CreateInstance(parameterType);
}
else
{
array[i] = (parameterType.IsValueType ? Activator.CreateInstance(parameterType) : null);
}
}
cachedMinimapAddPin.Invoke(Minimap.instance, array);
return true;
}
catch
{
cachedMinimapAddPin = null;
return false;
}
}
private static MethodInfo FindBestAddPinMethod()
{
try
{
MethodInfo[] methods = typeof(Minimap).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
MethodInfo result = null;
int num = -1;
foreach (MethodInfo methodInfo in methods)
{
if (!(methodInfo.Name != "AddPin"))
{
ParameterInfo[] parameters = methodInfo.GetParameters();
if (parameters.Length >= 3 && !(parameters[0].ParameterType != typeof(Vector3)) && !(parameters[1].ParameterType != typeof(PinType)) && !(parameters[2].ParameterType != typeof(string)) && parameters.Length > num)
{
result = methodInfo;
num = parameters.Length;
}
}
}
return result;
}
catch
{
return null;
}
}
private static bool TryParseVector3(string s, out Vector3 v)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
v = Vector3.zero;
if (string.IsNullOrWhiteSpace(s))
{
return false;
}
string[] array = s.Split(new char[1] { ',' });
if (array.Length != 3)
{
return false;
}
if (!float.TryParse(array[0].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
{
return false;
}
if (!float.TryParse(array[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
{
return false;
}
if (!float.TryParse(array[2].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result3))
{
return false;
}
v = new Vector3(result, result2, result3);
return true;
}
private static Vector3 GetHomePointSafe(Player player)
{
//IL_0044: 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)
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: 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)
//IL_004c: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)Game.instance != (Object)null)
{
PlayerProfile playerProfile = Game.instance.GetPlayerProfile();
if (playerProfile != null)
{
return playerProfile.GetHomePoint();
}
}
return ((Object)(object)player != (Object)null) ? ((Component)player).transform.position : Vector3.zero;
}
private static ZNetView GetNView(Character c)
{
if ((Object)(object)c == (Object)null || f_m_nview == null)
{
return null;
}
try
{
object? value = f_m_nview.GetValue(c);
return (ZNetView)((value is ZNetView) ? value : null);
}
catch
{
return null;
}
}
private static object GetSEMan(Character c)
{
if ((Object)(object)c == (Object)null || f_m_seman == null)
{
return null;
}
try
{
return f_m_seman.GetValue(c);
}
catch
{
return null;
}
}
private static void RemoveAllStatusEffectsSafe(Player player)
{
object sEMan = GetSEMan((Character)(object)player);
if (sEMan == null || m_RemoveAllStatusEffects == null)
{
return;
}
try
{
m_RemoveAllStatusEffects.Invoke(sEMan, new object[1] { false });
}
catch
{
}
}
private static void InvokeNoArgs(MethodInfo m, object inst)
{
if (m == null || inst == null)
{
return;
}
try
{
m.Invoke(inst, null);
}
catch
{
}
}
private static void AdvanceProgression()
{
if (!deathRestarts)
{
if (!isHurt && !isInjured && !isNearDeath)
{
isHurt = true;
}
else if (isHurt && !isInjured)
{
isHurt = false;
isInjured = true;
}
else if (isInjured && !isNearDeath)
{
isInjured = false;
isNearDeath = true;
}
else if (isNearDeath && !deathRestarts)
{
isNearDeath = false;
deathRestarts = true;
}
}
}
private static void ResetProgression()
{
isHurt = false;
isInjured = false;
isNearDeath = false;
deathRestarts = false;
lastDeathTimeUtc = DateTime.MinValue;
}
private static void ApplyRecoveryTimers()
{
if (lastDeathTimeUtc == DateTime.MinValue || deathRestarts)
{
return;
}
double totalMinutes = (DateTime.UtcNow - lastDeathTimeUtc).TotalMinutes;
if (isNearDeath)
{
int value = NearDeathRecoveryMinutes.Value;
if (totalMinutes >= (double)value)
{
isNearDeath = false;
isInjured = true;
lastDeathTimeUtc = DateTime.UtcNow;
}
}
else if (isInjured)
{
int value2 = RecoveryTimeMinutes.Value;
if (totalMinutes >= (double)value2)
{
isInjured = false;
isHurt = true;
lastDeathTimeUtc = DateTime.UtcNow;
}
}
else if (isHurt)
{
int value3 = RecoveryTimeMinutes.Value;
if (totalMinutes >= (double)value3)
{
isHurt = false;
lastDeathTimeUtc = DateTime.MinValue;
}
}
}
private static void DamageArmorDurability(Player player, float amount)
{
if (!((Object)(object)player == (Object)null))
{
TryDamageItem((Humanoid)(object)player, f_humanoidChest, amount);
TryDamageItem((Humanoid)(object)player, f_humanoidLeg, amount);
TryDamageItem((Humanoid)(object)player, f_humanoidHelmet, amount);
TryDamageItem((Humanoid)(object)player, f_humanoidShoulder, amount);
}
}
private static void TryDamageItem(Humanoid h, FieldInfo f, float amount)
{
if (!((Object)(object)h == (Object)null) && !(f == null))
{
ItemData val = null;
try
{
object? value = f.GetValue(h);
val = (ItemData)((value is ItemData) ? value : null);
}
catch
{
val = null;
}
if (val != null)
{
val.m_durability = Mathf.Max(0f, val.m_durability - amount);
}
}
}
private static void ClearFoodsIfPossible(Player player)
{
if ((Object)(object)player == (Object)null || m_ClearFoods == null)
{
return;
}
try
{
m_ClearFoods.Invoke(player, null);
}
catch
{
}
}
}