Decompiled source of PvPOverhaul v1.0.7

PvpOverhaul.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Jotunn;
using Jotunn.Entities;
using Jotunn.Extensions;
using Jotunn.Managers;
using Jotunn.Utils;
using PvpOverhaul.API;
using PvpOverhaul.Compatibility;
using Splatform;
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("PvpOverhaul")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("PvpOverhaul")]
[assembly: AssemblyCopyright("Copyright ©  2021")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")]
[assembly: AssemblyFileVersion("1.0.6")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.6.0")]
namespace PvPOverhaul
{
	[HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[]
	{
		typeof(ItemData),
		typeof(int),
		typeof(bool),
		typeof(float),
		typeof(int)
	})]
	public static class PvPResilienceTooltipPatch
	{
		private static void Postfix(ItemData item, ref string __result)
		{
			if (item?.m_customData != null)
			{
				float resilience = PvpResilienceAPI.GetResilience(item);
				if (!(resilience <= 0f))
				{
					__result += $"\n<color=#66ccff>Résilience : {resilience:0}</color>";
				}
			}
		}
	}
}
namespace PvpOverhaul
{
	internal class BountySystem
	{
		private class KillRecord
		{
			public string KillerId;

			public string VictimId;

			public DateTime TimestampUtc;
		}

		public class BountyState
		{
			public string PlayerId;

			public DateTime StartUtc;

			public DateTime EndUtc;

			public int Tier;

			public Vector3 LastKnownPosition;
		}

		public class LastHit
		{
			public string VictimName;

			public string AttackerName;

			public float Damage;

			public float Time;
		}

		[CompilerGenerated]
		private sealed class <DelayedDeathCheck>d__51 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public Player victim;

			public BountySystem <>4__this;

			private string <victimName>5__1;

			private long <victimId>5__2;

			private int <i>5__3;

			private LastHit <lastHit>5__4;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <DelayedDeathCheck>d__51(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<victimName>5__1 = null;
				<lastHit>5__4 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0067: Unknown result type (might be due to invalid IL or missing references)
				//IL_0071: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					if ((Object)(object)victim == (Object)null)
					{
						return false;
					}
					<victimName>5__1 = victim.GetPlayerName();
					<victimId>5__2 = victim.GetPlayerID();
					<i>5__3 = 0;
					break;
				case 1:
					<>1__state = -1;
					if ((Object)(object)victim == (Object)null)
					{
						return false;
					}
					<lastHit>5__4 = <>4__this.ResolveLastHit(<victimId>5__2);
					if (<i>5__3 == 0 || ((Character)victim).IsDead() || ((Character)victim).GetHealth() <= 0f)
					{
						Logger.LogInfo((object)($"[PvPOverhaul BOUNTY] DelayedDeathCheck {<i>5__3} victim={<victimName>5__1} " + $"health={((Character)victim).GetHealth()} dead={((Character)victim).IsDead()} " + "lastHit=" + ((<lastHit>5__4 != null) ? <lastHit>5__4.AttackerName : "NULL")));
					}
					if (((Character)victim).GetHealth() <= 0f || ((Character)victim).IsDead())
					{
						<>4__this.TryReportDeath(victim);
						return false;
					}
					<lastHit>5__4 = null;
					<i>5__3++;
					break;
				}
				if (<i>5__3 < 30)
				{
					<>2__current = (object)new WaitForSeconds(0.1f);
					<>1__state = 1;
					return true;
				}
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private readonly List<KillRecord> _kills = new List<KillRecord>();

		private readonly Dictionary<string, BountyState> _activeBounties = new Dictionary<string, BountyState>();

		private readonly Dictionary<long, LastHit> _lastHits = new Dictionary<long, LastHit>();

		private readonly Dictionary<string, LastHit> _lastHitsByName = new Dictionary<string, LastHit>();

		private readonly Dictionary<long, float> _reportedDeaths = new Dictionary<long, float>();

		private readonly Dictionary<long, float> _ignoredDeaths = new Dictionary<long, float>();

		private bool _loadedPersistence;

		private float _nextTimerTick;

		private float _nextPositionSync;

		private float _nextFullSync;

		public static BountySystem Instance { get; } = new BountySystem();


		private string SaveDir => Path.Combine(Paths.ConfigPath, "PvPOverhaul");

		private string SaveFile => Path.Combine(SaveDir, "bounties.txt");

		private string LogDir => Path.Combine(Paths.ConfigPath, "PvPOverhaul", "Logs");

		private TimeSpan KillWindow => TimeSpan.FromMinutes(PvPOverhaul.BountyKillWindowMinutes.Value);

		private TimeSpan BountyDuration => TimeSpan.FromMinutes(PvPOverhaul.BountyDurationMinutes.Value);

		public void Update()
		{
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				if (!_loadedPersistence)
				{
					_loadedPersistence = true;
					LoadBounties();
				}
				if (Time.time >= _nextTimerTick)
				{
					_nextTimerTick = Time.time + 1f;
					UpdateTimers();
				}
				if (Time.time >= _nextPositionSync)
				{
					_nextPositionSync = Time.time + 10f;
					SyncBountyPositions();
				}
				if (Time.time >= _nextFullSync)
				{
					_nextFullSync = Time.time + 30f;
					BroadcastAllBounties();
				}
			}
		}

		private void WriteServerLog(string message)
		{
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				Directory.CreateDirectory(LogDir);
				string path = Path.Combine(LogDir, $"{DateTime.UtcNow:yyyy-MM-dd}.log");
				string text = $"[{DateTime.UtcNow:HH:mm:ss} UTC] {message}";
				File.AppendAllText(path, text + Environment.NewLine);
			}
		}

		private bool IsPlayerOnline(string playerName)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return false;
			}
			foreach (PlayerInfo player in ZNet.instance.GetPlayerList())
			{
				if (player.m_name == playerName)
				{
					return true;
				}
			}
			return false;
		}

		private static string FormatMessage(string template, string killer = "", string victim = "", string player = "", int tier = 0, int duration = 0, int amount = 0, string item = "")
		{
			return template.Replace("{killer}", killer).Replace("{victim}", victim).Replace("{player}", player)
				.Replace("{tier}", tier.ToString())
				.Replace("{duration}", duration.ToString())
				.Replace("{amount}", amount.ToString())
				.Replace("{item}", item);
		}

		public void RegisterKill(string killerName, string victimName)
		{
			if (string.IsNullOrWhiteSpace(killerName) || string.IsNullOrWhiteSpace(victimName) || killerName == victimName)
			{
				return;
			}
			DateTime now = DateTime.UtcNow;
			BountyState bounty = GetBounty(victimName);
			ClearBounty(victimName, killed: true);
			_kills.RemoveAll((KillRecord x) => x.KillerId == victimName);
			SaveBounties();
			if (bounty != null)
			{
				int num = Math.Max(1, bounty.Tier) * PvPOverhaul.BountyRewardAmountPerTier.Value;
				string value = PvPOverhaul.BountyRewardItem.Value;
				BountyRpc.SendRewardToPlayer(killerName, value, num);
				_kills.RemoveAll((KillRecord x) => x.KillerId == killerName);
				string message = FormatMessage(PvPOverhaul.MsgBountyReward.Value, killerName, victimName, "", 0, 0, num, value);
				BountyRpc.BroadcastCenterMessage(message);
				WriteServerLog($"REWARD | {killerName} claimed bounty on {victimName} | tier={bounty.Tier} | {num}x {value}");
			}
			_kills.Add(new KillRecord
			{
				KillerId = killerName,
				VictimId = victimName,
				TimestampUtc = now
			});
			CleanupOldKills(now);
			int num2 = _kills.Count((KillRecord x) => x.KillerId == killerName && now - x.TimestampUtc <= KillWindow);
			Logger.LogInfo((object)$"[PvPOverhaul SERVER] Kill registered: {killerName} -> {victimName} | kills={num2}");
			WriteServerLog($"KILL | {killerName} killed {victimName} | kills={num2}");
			BountyRpc.BroadcastCenterMessage(FormatMessage(PvPOverhaul.MsgKillBroadcast.Value, killerName, victimName));
			if (bounty == null && num2 >= PvPOverhaul.BountyTriggerKillCount.Value)
			{
				ActivateOrRefreshBounty(killerName, num2, now);
			}
		}

		private int GetTierFromKills(int kills)
		{
			return (int)Math.Floor(Math.Sqrt(kills));
		}

		private void ActivateOrRefreshBounty(string playerName, int killCount, DateTime now)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: 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_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			int tierFromKills = GetTierFromKills(killCount);
			DateTime dateTime = now.Add(BountyDuration);
			Vector3 playerPosition = GetPlayerPosition(playerName);
			if (!_activeBounties.TryGetValue(playerName, out var value))
			{
				value = new BountyState
				{
					PlayerId = playerName,
					StartUtc = now,
					EndUtc = dateTime,
					Tier = tierFromKills,
					LastKnownPosition = playerPosition
				};
				_activeBounties[playerName] = value;
				Logger.LogInfo((object)("[PvPOverhaul SERVER] BOUNTY ACTIVATED: " + playerName));
				WriteServerLog($"BOUNTY_ACTIVATED | {playerName} | tier={tierFromKills} | end={dateTime:o}");
				BountyRpc.BroadcastCenterMessage(FormatMessage(PvPOverhaul.MsgBountyActivated.Value, "", "", playerName, tierFromKills));
			}
			else
			{
				value.EndUtc = dateTime;
				value.Tier = tierFromKills;
				if (playerPosition != Vector3.zero)
				{
					value.LastKnownPosition = playerPosition;
				}
				Logger.LogInfo((object)("[PvPOverhaul SERVER] BOUNTY REFRESHED: " + playerName));
				WriteServerLog($"BOUNTY_REFRESHED | {playerName} | tier={tierFromKills} | end={dateTime:o}");
				BountyRpc.BroadcastCenterMessage(FormatMessage(PvPOverhaul.MsgBountyRefreshed.Value, "", "", playerName, tierFromKills, PvPOverhaul.BountyDurationMinutes.Value));
			}
			SaveBounties();
			if (IsPlayerOnline(playerName))
			{
				BountyRpc.BroadcastBountyCircle(playerName, active: true, value.LastKnownPosition, tierFromKills, dateTime);
			}
		}

		public void UpdateBountyPositionFromClient(string playerName, Vector3 position)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			if (_activeBounties.TryGetValue(playerName, out var value) && !(position == Vector3.zero))
			{
				value.LastKnownPosition = position;
				SaveBounties();
				BountyRpc.BroadcastBountyCircle(playerName, active: true, position, value.Tier, value.EndUtc);
			}
		}

		public void ClearBounty(string playerName, bool killed)
		{
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			if (_activeBounties.Remove(playerName))
			{
				Logger.LogInfo((object)("[PvPOverhaul SERVER] Bounty cleared: " + playerName));
				WriteServerLog($"BOUNTY_CLEARED | {playerName} | killed={killed}");
				if (killed)
				{
					BountyRpc.BroadcastCenterMessage(FormatMessage(PvPOverhaul.MsgBountyCleared.Value, "", "", playerName));
				}
				else
				{
					BountyRpc.BroadcastCenterMessage(FormatMessage(PvPOverhaul.MsgBountyExpired.Value, "", "", playerName));
				}
				SaveBounties();
				BountyRpc.BroadcastBountyCircle(playerName, active: false, Vector3.zero, 0, DateTime.UtcNow);
			}
		}

		public void ResetPlayer(string playerName)
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			_activeBounties.Remove(playerName);
			_kills.RemoveAll((KillRecord x) => x.KillerId == playerName || x.VictimId == playerName);
			Logger.LogInfo((object)("[PvPOverhaul SERVER] Full reset: " + playerName));
			WriteServerLog("ADMIN_RESET | " + playerName);
			SaveBounties();
			BountyRpc.BroadcastBountyCircle(playerName, active: false, Vector3.zero, 0, DateTime.UtcNow);
		}

		public BountyState GetBounty(string playerName)
		{
			if (!_activeBounties.TryGetValue(playerName, out var value))
			{
				return null;
			}
			if (DateTime.UtcNow >= value.EndUtc)
			{
				ClearBounty(playerName, killed: false);
				return null;
			}
			return value;
		}

		private void UpdateTimers()
		{
			DateTime now = DateTime.UtcNow;
			foreach (string item in (from x in _activeBounties
				where now >= x.Value.EndUtc
				select x.Key).ToList())
			{
				ClearBounty(item, killed: false);
			}
		}

		private void SyncBountyPositions()
		{
			//IL_0079: 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)
			DateTime utcNow = DateTime.UtcNow;
			foreach (BountyState item in _activeBounties.Values.ToList())
			{
				if (!(utcNow >= item.EndUtc))
				{
					if (!IsPlayerOnline(item.PlayerId))
					{
						BountyRpc.BroadcastBountyCircle(item.PlayerId, active: false, Vector3.zero, item.Tier, item.EndUtc);
					}
					else
					{
						BountyRpc.BroadcastBountyCircle(item.PlayerId, active: true, item.LastKnownPosition, item.Tier, item.EndUtc);
					}
				}
			}
		}

		private void BroadcastAllBounties()
		{
			//IL_0079: 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)
			DateTime utcNow = DateTime.UtcNow;
			foreach (BountyState item in _activeBounties.Values.ToList())
			{
				if (!(utcNow >= item.EndUtc))
				{
					if (!IsPlayerOnline(item.PlayerId))
					{
						BountyRpc.BroadcastBountyCircle(item.PlayerId, active: false, Vector3.zero, item.Tier, item.EndUtc);
					}
					else
					{
						BountyRpc.BroadcastBountyCircle(item.PlayerId, active: true, item.LastKnownPosition, item.Tier, item.EndUtc);
					}
				}
			}
		}

		private Vector3 GetPlayerPosition(string playerName)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: 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_0056: 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)
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				if (allPlayer.GetPlayerName() == playerName)
				{
					return ((Component)allPlayer).transform.position;
				}
			}
			return Vector3.zero;
		}

		private void CleanupOldKills(DateTime now)
		{
			_kills.RemoveAll((KillRecord x) => now - x.TimestampUtc > KillWindow);
		}

		public void SetLastHit(long victimId, string victimName, string attackerName, float damage)
		{
			LastHit value = new LastHit
			{
				VictimName = victimName,
				AttackerName = attackerName,
				Damage = damage,
				Time = Time.time
			};
			_lastHits[victimId] = value;
			if (!string.IsNullOrWhiteSpace(victimName))
			{
				_lastHitsByName[victimName] = value;
			}
			Logger.LogInfo((object)$"[PvPOverhaul BOUNTY] LastHit set: victim={victimName} id={victimId} attacker={attackerName} dmg={damage}");
		}

		public LastHit ResolveLastHit(long victimId)
		{
			if (!_lastHits.TryGetValue(victimId, out var value))
			{
				return null;
			}
			if (Time.time - value.Time > 10f)
			{
				return null;
			}
			return value;
		}

		private LastHit ResolveLastHit(Player victim)
		{
			if ((Object)(object)victim == (Object)null)
			{
				return null;
			}
			long playerID = victim.GetPlayerID();
			LastHit value = ResolveLastHit(playerID);
			if (value != null)
			{
				return value;
			}
			string playerName = victim.GetPlayerName();
			if (string.IsNullOrWhiteSpace(playerName))
			{
				return null;
			}
			if (!_lastHitsByName.TryGetValue(playerName, out value))
			{
				return null;
			}
			if (Time.time - value.Time > 10f)
			{
				return null;
			}
			return value;
		}

		public void TryReportDeath(Player victim)
		{
			if ((Object)(object)victim == (Object)null)
			{
				return;
			}
			if (victim.m_godMode)
			{
				Logger.LogInfo((object)("[PvPOverhaul BOUNTY] Ignore death: godmode victim=" + victim.GetPlayerName()));
				return;
			}
			if (((Character)victim).GetHealth() > 0f && !((Character)victim).IsDead())
			{
				Logger.LogInfo((object)$"[PvPOverhaul BOUNTY] Ignore death report: victim not dead yet {victim.GetPlayerName()} health={((Character)victim).GetHealth()} dead={((Character)victim).IsDead()}");
				return;
			}
			if (RecentlyIgnoredDeath(victim))
			{
				Logger.LogInfo((object)("[PvPOverhaul BOUNTY] Ignore death: recently ignored victim=" + victim.GetPlayerName()));
				return;
			}
			long playerID = victim.GetPlayerID();
			if (!_reportedDeaths.TryGetValue(playerID, out var value) || !(Time.time - value < 10f))
			{
				LastHit lastHit = ResolveLastHit(victim);
				Logger.LogInfo((object)($"[PvPOverhaul BOUNTY] TryReportDeath victim={victim.GetPlayerName()} id={playerID} " + "lastHit=" + ((lastHit != null) ? lastHit.AttackerName : "NULL") + " " + $"health={((Character)victim).GetHealth()} dead={((Character)victim).IsDead()}"));
				if (lastHit != null && !string.IsNullOrWhiteSpace(lastHit.AttackerName) && !(lastHit.AttackerName == victim.GetPlayerName()))
				{
					_reportedDeaths[playerID] = Time.time;
					Logger.LogInfo((object)("[PvPOverhaul CLIENT] Reporting REAL kill to server: " + lastHit.AttackerName + " -> " + victim.GetPlayerName()));
					BountyRpc.SendKillToServer(lastHit.AttackerName, victim.GetPlayerName());
				}
			}
		}

		public void MarkIgnoredDeath(Player victim)
		{
			if (!((Object)(object)victim == (Object)null))
			{
				_ignoredDeaths[victim.GetPlayerID()] = Time.time;
			}
		}

		private bool RecentlyIgnoredDeath(Player victim)
		{
			if ((Object)(object)victim == (Object)null)
			{
				return false;
			}
			if (!_ignoredDeaths.TryGetValue(victim.GetPlayerID(), out var value))
			{
				return false;
			}
			return Time.time - value < 1f;
		}

		private void SaveBounties()
		{
			Directory.CreateDirectory(SaveDir);
			List<string> list = new List<string>();
			foreach (BountyState value in _activeBounties.Values)
			{
				list.Add(string.Join("|", value.PlayerId, value.StartUtc.Ticks, value.EndUtc.Ticks, value.Tier, value.LastKnownPosition.x.ToString(CultureInfo.InvariantCulture), value.LastKnownPosition.y.ToString(CultureInfo.InvariantCulture), value.LastKnownPosition.z.ToString(CultureInfo.InvariantCulture)));
			}
			File.WriteAllLines(SaveFile, list);
		}

		private void LoadBounties()
		{
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			if (!File.Exists(SaveFile))
			{
				return;
			}
			DateTime utcNow = DateTime.UtcNow;
			string[] array = File.ReadAllLines(SaveFile);
			foreach (string text in array)
			{
				string[] array2 = text.Split(new char[1] { '|' });
				if (array2.Length < 7)
				{
					continue;
				}
				string text2 = array2[0];
				if (!long.TryParse(array2[1], out var result) || !long.TryParse(array2[2], out var result2) || !int.TryParse(array2[3], out var result3) || !float.TryParse(array2[4], NumberStyles.Float, CultureInfo.InvariantCulture, out var result4) || !float.TryParse(array2[5], NumberStyles.Float, CultureInfo.InvariantCulture, out var result5) || !float.TryParse(array2[6], NumberStyles.Float, CultureInfo.InvariantCulture, out var result6))
				{
					continue;
				}
				DateTime dateTime = new DateTime(result2, DateTimeKind.Utc);
				if (!(utcNow >= dateTime))
				{
					BountyState bountyState = new BountyState
					{
						PlayerId = text2,
						StartUtc = new DateTime(result, DateTimeKind.Utc),
						EndUtc = dateTime,
						Tier = result3,
						LastKnownPosition = new Vector3(result4, result5, result6)
					};
					_activeBounties[text2] = bountyState;
					Logger.LogInfo((object)("[PvPOverhaul SERVER] Loaded persistent bounty: " + text2));
					WriteServerLog($"BOUNTY_LOADED | {text2} | tier={result3} | end={dateTime:o}");
					if (IsPlayerOnline(text2))
					{
						BountyRpc.BroadcastBountyCircle(text2, active: true, bountyState.LastKnownPosition, result3, dateTime);
					}
				}
			}
			SaveBounties();
		}

		public void ScheduleDeathCheck(Player victim)
		{
			if (!((Object)(object)victim == (Object)null) && !((Object)(object)PvPOverhaul.Instance == (Object)null))
			{
				((MonoBehaviour)PvPOverhaul.Instance).StartCoroutine(DelayedDeathCheck(victim));
			}
		}

		[IteratorStateMachine(typeof(<DelayedDeathCheck>d__51))]
		private IEnumerator DelayedDeathCheck(Player victim)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <DelayedDeathCheck>d__51(0)
			{
				<>4__this = this,
				victim = victim
			};
		}
	}
	internal static class BountyRpc
	{
		private const string RpcRegisterKill = "PvpModes_RegisterKill";

		private const string RpcBroadcastMessage = "PvpModes_BroadcastMessage";

		private const string RpcBountyCircle = "PvpModes_BountyCircle";

		private const string RpcBountyPosition = "PvpModes_BountyPosition";

		private const string RpcBountyStatusRequest = "PvpModes_BountyStatusRequest";

		private const string RpcBountyStatusResponse = "PvpModes_BountyStatusResponse";

		private const string RpcBountyResetRequest = "PvpModes_BountyResetRequest";

		private const string RpcBountyResetResponse = "PvpModes_BountyResetResponse";

		private const string RpcBountyReward = "PvpModes_BountyReward";

		private static bool _registered;

		private static ZRoutedRpc _registeredInstance;

		private static string _pendingRewardItem;

		private static int _pendingRewardAmount;

		public static void Update()
		{
			EnsureRegistered();
		}

		public static bool EnsureRegistered()
		{
			if (ZRoutedRpc.instance == null)
			{
				_registered = false;
				_registeredInstance = null;
				return false;
			}
			if (_registered && _registeredInstance == ZRoutedRpc.instance)
			{
				return true;
			}
			_registered = false;
			_registeredInstance = ZRoutedRpc.instance;
			ZRoutedRpc.instance.Register<ZPackage>("PvpModes_RegisterKill", (Action<long, ZPackage>)RPC_RegisterKill);
			ZRoutedRpc.instance.Register<ZPackage>("PvpModes_BroadcastMessage", (Action<long, ZPackage>)RPC_BroadcastMessage);
			ZRoutedRpc.instance.Register<ZPackage>("PvpModes_BountyCircle", (Action<long, ZPackage>)RPC_BountyCircle);
			ZRoutedRpc.instance.Register<ZPackage>("PvpModes_BountyPosition", (Action<long, ZPackage>)RPC_BountyPosition);
			ZRoutedRpc.instance.Register<ZPackage>("PvpModes_BountyStatusRequest", (Action<long, ZPackage>)RPC_BountyStatusRequest);
			ZRoutedRpc.instance.Register<ZPackage>("PvpModes_BountyStatusResponse", (Action<long, ZPackage>)RPC_BountyStatusResponse);
			ZRoutedRpc.instance.Register<ZPackage>("PvpModes_BountyResetRequest", (Action<long, ZPackage>)RPC_BountyResetRequest);
			ZRoutedRpc.instance.Register<ZPackage>("PvpModes_BountyResetResponse", (Action<long, ZPackage>)RPC_BountyResetResponse);
			ZRoutedRpc.instance.Register<ZPackage>("PvpModes_BountyReward", (Action<long, ZPackage>)RPC_BountyReward);
			_registered = true;
			Logger.LogInfo((object)"[PvPOverhaul] RPC registered successfully");
			return true;
		}

		public static void RequestBountyStatus(string playerName)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			if (!EnsureRegistered())
			{
				Console.instance.Print("[PvPOverhaul] RPC not ready");
				return;
			}
			ZPackage val = new ZPackage();
			val.Write(playerName);
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				SendBountyStatusResponse(ZRoutedRpc.instance.GetServerPeerID(), playerName);
				return;
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "PvpModes_BountyStatusRequest", new object[1] { val });
		}

		public static void RequestBountyReset(string playerName)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			if (!EnsureRegistered())
			{
				Console.instance.Print("[PvPOverhaul] RPC not ready");
				return;
			}
			ZPackage val = new ZPackage();
			val.Write(playerName);
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				BountySystem.Instance.ResetPlayer(playerName);
				Console.instance.Print("Reset bounty for " + playerName);
			}
			else
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "PvpModes_BountyResetRequest", new object[1] { val });
			}
		}

		private static void RPC_BountyStatusRequest(long sender, ZPackage pkg)
		{
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				string text = pkg.ReadString();
				ZPackage val = BuildBountyStatusResponse(text);
				Logger.LogInfo((object)$"[PvPOverhaul SERVER] Bounty status request: {text} sender={sender}");
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "PvpModes_BountyStatusResponse", new object[1] { val });
			}
		}

		private static void RPC_BountyStatusResponse(long sender, ZPackage pkg)
		{
			string text = pkg.ReadString();
			string text2;
			if (!pkg.ReadBool())
			{
				text2 = "No bounty for " + text;
			}
			else
			{
				int num = pkg.ReadInt();
				long ticks = pkg.ReadLong();
				TimeSpan timeSpan = new DateTime(ticks, DateTimeKind.Utc) - DateTime.UtcNow;
				if (timeSpan < TimeSpan.Zero)
				{
					timeSpan = TimeSpan.Zero;
				}
				text2 = $"{text} → Tier {num} | Time left: {timeSpan:hh\\:mm\\:ss}";
			}
			Console.instance.Print(text2);
			if ((Object)(object)MessageHud.instance != (Object)null)
			{
				MessageHud.instance.ShowMessage((MessageType)2, text2, 0, (Sprite)null, false);
			}
		}

		private static void RPC_BountyResetRequest(long sender, ZPackage pkg)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				string text = pkg.ReadString();
				BountySystem.Instance.ResetPlayer(text);
				ZPackage val = new ZPackage();
				val.Write(text);
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "PvpModes_BountyResetResponse", new object[1] { val });
			}
		}

		private static void RPC_BountyResetResponse(long sender, ZPackage pkg)
		{
			string text = pkg.ReadString();
			Console.instance.Print("Reset bounty for " + text);
		}

		private static ZPackage BuildBountyStatusResponse(string playerName)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			BountySystem.BountyState bounty = BountySystem.Instance.GetBounty(playerName);
			val.Write(playerName);
			val.Write(bounty != null);
			if (bounty != null)
			{
				val.Write(bounty.Tier);
				val.Write(bounty.EndUtc.Ticks);
			}
			return val;
		}

		private static void SendBountyStatusResponse(long targetPeerId, string playerName)
		{
			ZPackage val = BuildBountyStatusResponse(playerName);
			ZRoutedRpc.instance.InvokeRoutedRPC(targetPeerId, "PvpModes_BountyStatusResponse", new object[1] { val });
		}

		public static void SendKillToServer(string killerName, string victimName)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			if (EnsureRegistered())
			{
				ZPackage val = new ZPackage();
				val.Write(killerName);
				val.Write(victimName);
				if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
				{
					BountySystem.Instance.RegisterKill(killerName, victimName);
					return;
				}
				ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "PvpModes_RegisterKill", new object[1] { val });
			}
		}

		public static void SendBountyPositionToServer(string playerName, Vector3 position)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			if (EnsureRegistered() && (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer()))
			{
				ZPackage val = new ZPackage();
				val.Write(playerName);
				val.Write(position);
				ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "PvpModes_BountyPosition", new object[1] { val });
			}
		}

		public static void BroadcastCenterMessage(string message)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			if (EnsureRegistered())
			{
				ZPackage val = new ZPackage();
				val.Write(message);
				Logger.LogInfo((object)("[PvPOverhaul SERVER] Broadcast message: " + message));
				ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "PvpModes_BroadcastMessage", new object[1] { val });
			}
		}

		public static void BroadcastBountyCircle(string playerName, bool active, Vector3 position, int tier, DateTime endUtc)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			if (EnsureRegistered())
			{
				ZPackage val = new ZPackage();
				val.Write(playerName);
				val.Write(active);
				val.Write(position);
				val.Write(tier);
				val.Write(endUtc.Ticks);
				ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "PvpModes_BountyCircle", new object[1] { val });
			}
		}

		private static void RPC_RegisterKill(long sender, ZPackage pkg)
		{
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				string text = pkg.ReadString();
				string text2 = pkg.ReadString();
				Logger.LogInfo((object)("[PvPOverhaul SERVER] Kill RPC received: " + text + " -> " + text2));
				BountySystem.Instance.RegisterKill(text, text2);
			}
		}

		private static void RPC_BountyPosition(long sender, ZPackage pkg)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				string playerName = pkg.ReadString();
				Vector3 position = pkg.ReadVector3();
				BountySystem.Instance.UpdateBountyPositionFromClient(playerName, position);
			}
		}

		private static void RPC_BroadcastMessage(long sender, ZPackage pkg)
		{
			string text = pkg.ReadString();
			if ((Object)(object)MessageHud.instance != (Object)null)
			{
				MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false);
			}
		}

		private static void RPC_BountyCircle(long sender, ZPackage pkg)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			string playerName = pkg.ReadString();
			bool active = pkg.ReadBool();
			Vector3 position = pkg.ReadVector3();
			int tier = pkg.ReadInt();
			long ticks = pkg.ReadLong();
			DateTime endUtc = new DateTime(ticks, DateTimeKind.Utc);
			BountyStatusEffectController.SetLocalBountyStatus(playerName, active, endUtc);
			BountyMapCircle.Set(playerName, active, position, tier, endUtc);
		}

		public static void SendRewardToPlayer(string playerName, string itemPrefabName, int amount)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			if (EnsureRegistered())
			{
				ZPackage val = new ZPackage();
				val.Write(playerName);
				val.Write(itemPrefabName);
				val.Write(amount);
				Logger.LogInfo((object)$"[PvPOverhaul SERVER] Sending reward RPC to {playerName}: {amount}x {itemPrefabName}");
				ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "PvpModes_BountyReward", new object[1] { val });
			}
		}

		private static void RPC_BountyReward(long sender, ZPackage pkg)
		{
			string text = pkg.ReadString();
			string text2 = pkg.ReadString();
			int num = pkg.ReadInt();
			Logger.LogInfo((object)$"[PvPOverhaul CLIENT] Reward RPC received for {text}: {num}x {text2}");
			if (!((Object)(object)Player.m_localPlayer == (Object)null) && !(Player.m_localPlayer.GetPlayerName() != text))
			{
				Logger.LogInfo((object)("[PvPOverhaul CLIENT] Reward accepted by local player " + Player.m_localPlayer.GetPlayerName()));
				_pendingRewardItem = text2;
				_pendingRewardAmount += num;
				TryGivePendingReward();
			}
		}

		public static void UpdateReward()
		{
			TryGivePendingReward();
		}

		private static void TryGivePendingReward()
		{
			if (string.IsNullOrWhiteSpace(_pendingRewardItem) || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ObjectDB.instance == (Object)null)
			{
				return;
			}
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(_pendingRewardItem);
			if ((Object)(object)itemPrefab == (Object)null)
			{
				Logger.LogWarning((object)("[PvPOverhaul] Reward prefab not found yet: " + _pendingRewardItem));
				return;
			}
			((Humanoid)Player.m_localPlayer).GetInventory().AddItem(itemPrefab, _pendingRewardAmount);
			if ((Object)(object)MessageHud.instance != (Object)null)
			{
				MessageHud.instance.ShowMessage((MessageType)2, $"Récompense de prime : {_pendingRewardAmount}x {_pendingRewardItem}", 0, (Sprite)null, false);
			}
			_pendingRewardItem = null;
			_pendingRewardAmount = 0;
		}
	}
	internal static class BountyMapCircle
	{
		private class CircleState
		{
			public PinData Pin;

			public bool Active;

			public int Tier;

			public DateTime EndUtc;

			public float NextPositionSend;
		}

		private static readonly Dictionary<string, CircleState> _circles = new Dictionary<string, CircleState>();

		private const float CircleWorldSize = 300f;

		public static void Update()
		{
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			DateTime utcNow = DateTime.UtcNow;
			foreach (KeyValuePair<string, CircleState> item in _circles.ToList())
			{
				if (item.Value.EndUtc <= utcNow)
				{
					Remove(item.Key);
				}
			}
			if ((Object)(object)Player.m_localPlayer == (Object)null)
			{
				return;
			}
			string playerName = Player.m_localPlayer.GetPlayerName();
			if (_circles.TryGetValue(playerName, out var value) && value.Active && !(Time.time < value.NextPositionSend))
			{
				value.NextPositionSend = Time.time + 10f;
				Vector3 position = ((Component)Player.m_localPlayer).transform.position;
				if (value.Pin != null)
				{
					value.Pin.m_pos = position;
					value.Pin.m_name = "RECHERCHÉ: " + playerName;
					SetPinWorldSize(value.Pin, 300f);
				}
				BountyRpc.SendBountyPositionToServer(playerName, position);
			}
		}

		public static void Set(string playerName, bool active, Vector3 position, int tier, DateTime endUtc)
		{
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Minimap.instance == (Object)null)
			{
				return;
			}
			bool flag = (Object)(object)Player.m_localPlayer != (Object)null && Player.m_localPlayer.GetPlayerName() == playerName;
			if (!active)
			{
				Remove(playerName);
				return;
			}
			if (flag)
			{
				if (_circles.TryGetValue(playerName, out var value))
				{
					if (value.Pin != null)
					{
						Minimap.instance.RemovePin(value.Pin);
					}
					value.Pin = null;
					value.Active = true;
					value.Tier = tier;
					value.EndUtc = endUtc;
				}
				else
				{
					_circles[playerName] = new CircleState
					{
						Pin = null,
						Active = true,
						Tier = tier,
						EndUtc = endUtc,
						NextPositionSend = 0f
					};
				}
				return;
			}
			string text = "RECHERCHÉ: " + playerName;
			if (_circles.TryGetValue(playerName, out var value2))
			{
				value2.Active = true;
				value2.Tier = tier;
				value2.EndUtc = endUtc;
				if (value2.Pin != null)
				{
					Minimap.instance.RemovePin(value2.Pin);
				}
				PinData pin = Minimap.instance.AddPin(position, (PinType)13, text, false, false, 0L, default(PlatformUserID));
				SetPinWorldSize(pin, 300f);
				value2.Pin = pin;
			}
			else
			{
				PinData pin2 = Minimap.instance.AddPin(position, (PinType)13, text, false, false, 0L, default(PlatformUserID));
				SetPinWorldSize(pin2, 300f);
				_circles[playerName] = new CircleState
				{
					Pin = pin2,
					Active = true,
					Tier = tier,
					EndUtc = endUtc,
					NextPositionSend = 0f
				};
			}
		}

		private static void Remove(string playerName)
		{
			if (_circles.TryGetValue(playerName, out var value))
			{
				if ((Object)(object)Minimap.instance != (Object)null && value.Pin != null)
				{
					Minimap.instance.RemovePin(value.Pin);
				}
				_circles.Remove(playerName);
			}
		}

		private static void SetPinWorldSize(PinData pin, float size)
		{
			FieldInfo field = typeof(PinData).GetField("m_worldSize", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				field.SetValue(pin, size);
			}
		}
	}
	internal static class BountyStatusEffectController
	{
		private const string EffectName = "SE_PvpModes_Bounty";

		private static readonly int EffectHash = StringExtensionMethods.GetStableHashCode("SE_PvpModes_Bounty");

		private static DateTime _currentEndUtc;

		private static SE_Stats _effectPrefab;

		private static bool _registered;

		private static bool _pendingActive;

		private static DateTime _pendingEndUtc;

		private static string _pendingPlayerName;

		public static void UpdateRegistration()
		{
			if (_registered || (Object)(object)ObjectDB.instance == (Object)null)
			{
				return;
			}
			StatusEffect statusEffect = ObjectDB.instance.GetStatusEffect(EffectHash);
			if ((Object)(object)statusEffect != (Object)null)
			{
				_effectPrefab = (SE_Stats)(object)((statusEffect is SE_Stats) ? statusEffect : null);
				_registered = true;
				return;
			}
			SE_Stats val = (_effectPrefab = ScriptableObject.CreateInstance<SE_Stats>());
			((Object)val).name = "SE_PvpModes_Bounty";
			((StatusEffect)val).m_name = "Recherché";
			((StatusEffect)val).m_tooltip = "Votre position approximative est révélée aux autres joueurs.";
			((StatusEffect)val).m_ttl = 60f;
			Sprite sprite = GetSprite("SoftDeath");
			if ((Object)(object)sprite != (Object)null)
			{
				((StatusEffect)val).m_icon = sprite;
			}
			else
			{
				Logger.LogWarning((object)"[PvPOverhaul] SoftDeath icon not found");
			}
			ObjectDB.instance.m_StatusEffects.Add((StatusEffect)(object)val);
			_registered = true;
			Logger.LogInfo((object)"[PvPOverhaul] Bounty StatusEffect registered");
			if (!string.IsNullOrEmpty(_pendingPlayerName))
			{
				string pendingPlayerName = _pendingPlayerName;
				bool pendingActive = _pendingActive;
				DateTime pendingEndUtc = _pendingEndUtc;
				_pendingPlayerName = null;
				SetLocalBountyStatus(pendingPlayerName, pendingActive, pendingEndUtc);
			}
		}

		public static void SetLocalBountyStatus(string playerName, bool active, DateTime endUtc)
		{
			if (!_registered || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				_pendingPlayerName = playerName;
				_pendingActive = active;
				_pendingEndUtc = endUtc;
			}
			UpdateRegistration();
			if (!_registered || (Object)(object)Player.m_localPlayer == (Object)null || Player.m_localPlayer.GetPlayerName() != playerName)
			{
				return;
			}
			SEMan sEMan = ((Character)Player.m_localPlayer).GetSEMan();
			if (!active)
			{
				sEMan.RemoveStatusEffect(EffectHash, false);
				return;
			}
			float num = (float)(endUtc - DateTime.UtcNow).TotalSeconds;
			if (num <= 0f)
			{
				sEMan.RemoveStatusEffect(EffectHash, false);
				return;
			}
			bool flag = sEMan.HaveStatusEffect(EffectHash);
			if (!(_currentEndUtc == endUtc && flag))
			{
				_currentEndUtc = endUtc;
				if (flag)
				{
					sEMan.RemoveStatusEffect(EffectHash, false);
				}
				if ((Object)(object)_effectPrefab != (Object)null)
				{
					((StatusEffect)_effectPrefab).m_ttl = num;
				}
				StatusEffect val = (((Object)(object)_effectPrefab != (Object)null) ? sEMan.AddStatusEffect((StatusEffect)(object)_effectPrefab, true, 0, 0f) : sEMan.AddStatusEffect(EffectHash, true, 0, 0f));
				if ((Object)(object)val != (Object)null)
				{
					val.m_ttl = num;
					val.m_time = 0f;
				}
			}
		}

		private static Sprite GetSprite(string name)
		{
			Sprite[] array = Resources.FindObjectsOfTypeAll<Sprite>();
			foreach (Sprite val in array)
			{
				if (((Object)val).name == name)
				{
					return val;
				}
			}
			return null;
		}
	}
	internal class BountyStatusCommand : ConsoleCommand
	{
		public override string Name => "pvp_bounty";

		public override string Help => "Affiche la bounty d'un joueur. Usage: pvp_bounty nom";

		public override void Run(string[] args)
		{
			if (args.Length < 1)
			{
				Console.instance.Print("Usage: pvp_bounty nom");
			}
			else if (!SynchronizationManager.Instance.PlayerIsAdmin)
			{
				Console.instance.Print("Only server admins can use this command.");
			}
			else
			{
				BountyRpc.RequestBountyStatus(args[0]);
			}
		}
	}
	internal class BountyResetCommand : ConsoleCommand
	{
		public override string Name => "pvp_resetbounty";

		public override string Help => "Reset la bounty et les kills d'un joueur. Usage: pvp_resetbounty nom";

		public override void Run(string[] args)
		{
			if (args.Length < 1)
			{
				Console.instance.Print("Usage: pvp_resetbounty nom");
			}
			else if (!SynchronizationManager.Instance.PlayerIsAdmin)
			{
				Console.instance.Print("Only server admins can use this command.");
			}
			else
			{
				BountyRpc.RequestBountyReset(args[0]);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnDeath")]
	internal static class PlayerDeathBountyPatch
	{
		private static void Postfix(Player __instance)
		{
			if (!((Object)(object)__instance == (Object)null))
			{
				Logger.LogInfo((object)("[PvPOverhaul BOUNTY] Player.OnDeath detected: " + __instance.GetPlayerName()));
				BountySystem.Instance.TryReportDeath(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Character), "OnDeath")]
	internal static class CharacterDeathBountyPatch
	{
		private static void Postfix(Character __instance)
		{
			Player val = (Player)(object)((__instance is Player) ? __instance : null);
			if (val != null)
			{
				Logger.LogInfo((object)("[PvPOverhaul BOUNTY] Character.OnDeath detected: " + val.GetPlayerName()));
				BountySystem.Instance.TryReportDeath(val);
			}
		}
	}
	[HarmonyPatch]
	internal static class PvPTweaksCombatFilterPatch
	{
		private static readonly int CombatHash = StringExtensionMethods.GetStableHashCode("SE_Combat");

		private static readonly Dictionary<long, float> RecentRealPvpHit = new Dictionary<long, float>();

		[HarmonyPatch(typeof(Character), "Damage")]
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static void CharacterDamagePrefix(Character __instance, HitData hit)
		{
			Player val = (Player)(object)((__instance is Player) ? __instance : null);
			if (val != null && hit != null)
			{
				Character attacker = hit.GetAttacker();
				Player val2 = (Player)(object)((attacker is Player) ? attacker : null);
				if (val2 != null && !((Object)(object)val2 == (Object)(object)val))
				{
					float time = Time.time;
					RecentRealPvpHit[val.GetPlayerID()] = time;
					RecentRealPvpHit[val2.GetPlayerID()] = time;
				}
			}
		}

		[HarmonyPatch(typeof(SEMan), "AddStatusEffect", new Type[]
		{
			typeof(int),
			typeof(bool),
			typeof(int),
			typeof(float)
		})]
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static bool AddStatusEffectHashPrefix(SEMan __instance, int nameHash)
		{
			if (nameHash != CombatHash)
			{
				return true;
			}
			return AllowCombatStatus(__instance);
		}

		[HarmonyPatch(typeof(SEMan), "AddStatusEffect", new Type[]
		{
			typeof(StatusEffect),
			typeof(bool),
			typeof(int),
			typeof(float)
		})]
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static bool AddStatusEffectObjectPrefix(SEMan __instance, StatusEffect statusEffect)
		{
			if ((Object)(object)statusEffect == (Object)null)
			{
				return true;
			}
			if (statusEffect.NameHash() != CombatHash)
			{
				return true;
			}
			return AllowCombatStatus(__instance);
		}

		private static bool AllowCombatStatus(SEMan seMan)
		{
			if (seMan == null)
			{
				return true;
			}
			Character character = seMan.m_character;
			Player val = (Player)(object)((character is Player) ? character : null);
			if (val == null)
			{
				return true;
			}
			long playerID = val.GetPlayerID();
			if (RecentRealPvpHit.TryGetValue(playerID, out var value) && Time.time - value <= 2f)
			{
				return true;
			}
			if (BountySystem.Instance.ResolveLastHit(playerID) != null)
			{
				return true;
			}
			Logger.LogInfo((object)("[PvPOverhaul Compat] Blocked PvPTweaks SE_Combat on " + val.GetPlayerName() + " | no real PvP hit"));
			return false;
		}
	}
	[HarmonyPatch(typeof(Character), "Damage")]
	internal static class PvPDamagePatch
	{
		private class MagicBurstWindow
		{
			public float StartTime;

			public float Damage;
		}

		internal class PendingBlockLeak
		{
			public float ExpectedDamage;

			public float HealthBefore;

			public float Time;

			public DamageTypes LeakDamage;

			public float BlockLeakPercent;
		}

		internal static readonly Dictionary<long, PendingBlockLeak> PendingLeaks = new Dictionary<long, PendingBlockLeak>();

		private static readonly Dictionary<string, MagicBurstWindow> MagicBurstWindows = new Dictionary<string, MagicBurstWindow>();

		private static readonly Dictionary<long, string> PendingBountyAttackers = new Dictionary<long, string>();

		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static void Prefix(Character __instance, HitData hit)
		{
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			if (!PvPOverhaul.EnablePvpDamagePatch.Value)
			{
				return;
			}
			Player val = (Player)(object)((__instance is Player) ? __instance : null);
			if (val == null || hit == null)
			{
				return;
			}
			if (IsTherzieBleedTick(val, hit))
			{
				ScaleDamage(hit, PvPOverhaul.TherzieBleedNerf.Value);
				Logger.LogInfo((object)$"[PvPOverhaul PvPDMG] Therzie bleed nerfed on {val.GetPlayerName()} | dmg={hit.GetTotalDamage()}");
				return;
			}
			Character attacker = hit.GetAttacker();
			Player val2 = (Player)(object)((attacker is Player) ? attacker : null);
			if ((Object)(object)val2 == (Object)null)
			{
				val2 = TryResolveRecentPvpAttacker(val);
			}
			if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2 == (Object)(object)val))
			{
				PendingBountyAttackers[val.GetPlayerID()] = val2.GetPlayerName();
				float totalDamage = hit.GetTotalDamage();
				if (totalDamage > 0f && !val.m_godMode)
				{
					BountySystem.Instance.SetLastHit(val.GetPlayerID(), val.GetPlayerName(), val2.GetPlayerName(), totalDamage);
				}
				float health = ((Character)val).GetHealth();
				NormalizePvpDamage(val, val2, hit);
				float totalDamage2 = hit.GetTotalDamage();
				if (hit.m_blockable && totalDamage2 > 0f && ((Character)val).IsBlocking())
				{
					DamageTypes damage = hit.m_damage;
					float num = (IsTwoHandedMeleeWeapon(((Humanoid)val2).GetCurrentWeapon()) ? PvPOverhaul.PvpTwoHandedBlockedDamageLeakPercent.Value : PvPOverhaul.PvpBlockedDamageLeakPercent.Value);
					ScaleDamageTypes(ref damage, num);
					PendingLeaks[val.GetPlayerID()] = new PendingBlockLeak
					{
						ExpectedDamage = totalDamage2,
						HealthBefore = health,
						LeakDamage = damage,
						Time = Time.time,
						BlockLeakPercent = num
					};
				}
			}
		}

		private static Player TryResolveRecentPvpAttacker(Player victim)
		{
			if ((Object)(object)victim == (Object)null)
			{
				return null;
			}
			BountySystem.LastHit lastHit = BountySystem.Instance.ResolveLastHit(victim.GetPlayerID());
			if (lastHit == null)
			{
				return null;
			}
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				if ((Object)(object)allPlayer != (Object)null && allPlayer.GetPlayerName() == lastHit.AttackerName)
				{
					return allPlayer;
				}
			}
			return null;
		}

		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(Character __instance, HitData hit)
		{
			Player val = (Player)(object)((__instance is Player) ? __instance : null);
			if (val == null)
			{
				return;
			}
			long playerID = val.GetPlayerID();
			if (PendingBountyAttackers.TryGetValue(playerID, out var value))
			{
				PendingBountyAttackers.Remove(playerID);
				if (!string.IsNullOrWhiteSpace(value) && !val.m_godMode && !(value == val.GetPlayerName()))
				{
					Logger.LogInfo((object)$"[PvPOverhaul BOUNTY] PvP hit completed, scheduling death check: {value} -> {val.GetPlayerName()} health={((Character)val).GetHealth()} dead={((Character)val).IsDead()}");
					BountySystem.Instance.ScheduleDeathCheck(val);
				}
			}
		}

		private static void NormalizePvpDamage(Player victim, Player attacker, HitData hit)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Invalid comparison between Unknown and I4
			float maxHealth = ((Character)victim).GetMaxHealth();
			if (maxHealth <= 0f)
			{
				return;
			}
			float totalDamage = hit.GetTotalDamage();
			if (totalDamage <= 0f)
			{
				return;
			}
			bool flag = IsLikelyDotTick(hit);
			bool flag2 = IsLikelyMagicBurstFragment(hit);
			bool flag3 = (int)hit.m_skill == 9 && IsLikelyMagicSecondary(hit);
			float value = PvPOverhaul.PvpMaxDirectHitPercentHp.Value;
			value *= GetWeaponPvpMultiplier(attacker);
			float num = (flag ? (maxHealth * PvPOverhaul.PvpMaxDotTickPercentHp.Value) : (maxHealth * value));
			if (flag2)
			{
				num = Mathf.Min(num, maxHealth * PvPOverhaul.PvpMaxMagicMultiHitPercentHp.Value);
			}
			if (flag3)
			{
				num = Mathf.Min(num, maxHealth * PvPOverhaul.PvpMaxMagicSecondaryPercentHp.Value);
			}
			if (flag2)
			{
				string key = $"{victim.GetPlayerID()}:{attacker.GetPlayerID()}";
				float num2 = Mathf.Max(0.05f, PvPOverhaul.PvpMagicBurstWindowSeconds.Value);
				float num3 = maxHealth * PvPOverhaul.PvpMaxMagicBurstPercentHp.Value;
				if (!MagicBurstWindows.TryGetValue(key, out var value2) || Time.time - value2.StartTime > num2)
				{
					value2 = new MagicBurstWindow
					{
						StartTime = Time.time,
						Damage = 0f
					};
					MagicBurstWindows[key] = value2;
				}
				float num4 = num3 - value2.Damage;
				if (num4 <= 0f)
				{
					ZeroDamage(hit);
					return;
				}
				num = Mathf.Min(num, num4);
			}
			if (num <= 0f)
			{
				ZeroDamage(hit);
				return;
			}
			if (totalDamage > num)
			{
				float scale = num / totalDamage;
				ScaleDamage(hit, scale);
			}
			ApplyElementalPvpMultipliers(hit);
			ApplyTherzieSpecialNerfs(victim, attacker, hit);
			ApplyPvPResilience(victim, hit);
			if (!flag && (!hit.m_blockable || !((Character)victim).IsBlocking()))
			{
				ConvertPvpDamageToTrueDamage(hit);
			}
			if (flag2)
			{
				string key2 = $"{victim.GetPlayerID()}:{attacker.GetPlayerID()}";
				if (MagicBurstWindows.TryGetValue(key2, out var value3))
				{
					value3.Damage += hit.GetTotalDamage();
				}
			}
		}

		private static void ConvertPvpDamageToTrueDamage(HitData hit)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if (hit != null)
			{
				float totalDamage = hit.GetTotalDamage();
				if (!(totalDamage <= 0f))
				{
					hit.m_damage = new DamageTypes
					{
						m_damage = totalDamage
					};
				}
			}
		}

		private static void ApplyElementalPvpMultipliers(HitData hit)
		{
			if (hit != null)
			{
				hit.m_damage.m_lightning *= PvPOverhaul.PvpLightningDamageMultiplier.Value;
			}
		}

		private static void ApplyTherzieSpecialNerfs(Player victim, Player attacker, HitData hit)
		{
			if (hit != null)
			{
				if (IsTherzieBleedTick(victim, hit))
				{
					ScaleDamage(hit, PvPOverhaul.TherzieBleedNerf.Value);
				}
				else if (IsTherzieDualWeapon(attacker))
				{
					ScaleDamage(hit, PvPOverhaul.TherzieDualWeapNerf.Value);
				}
			}
		}

		private static bool IsTherzieDualWeapon(Player attacker)
		{
			if ((Object)(object)attacker == (Object)null)
			{
				return false;
			}
			ItemData currentWeapon = ((Humanoid)attacker).GetCurrentWeapon();
			if (currentWeapon == null || currentWeapon.m_shared == null)
			{
				return false;
			}
			string text = "";
			if ((Object)(object)currentWeapon.m_dropPrefab != (Object)null)
			{
				text = text + ((Object)currentWeapon.m_dropPrefab).name + " ";
			}
			text += currentWeapon.m_shared.m_name;
			return text.IndexOf("Dual", StringComparison.OrdinalIgnoreCase) >= 0;
		}

		private static bool IsTherzieBleedTick(Player victim, HitData hit)
		{
			if ((Object)(object)victim == (Object)null || hit == null)
			{
				return false;
			}
			if ((Object)(object)hit.GetAttacker() != (Object)null)
			{
				return false;
			}
			if (hit.GetTotalDamage() <= 0f)
			{
				return false;
			}
			SEMan sEMan = ((Character)victim).GetSEMan();
			if (sEMan == null)
			{
				return false;
			}
			foreach (StatusEffect statusEffect in sEMan.GetStatusEffects())
			{
				if (!((Object)(object)statusEffect == (Object)null))
				{
					string text = ((Object)statusEffect).name ?? "";
					string text2 = statusEffect.m_name ?? "";
					string text3 = ((object)statusEffect).GetType().FullName ?? "";
					if (text.StartsWith("SE_Warfare", StringComparison.OrdinalIgnoreCase))
					{
						return true;
					}
					if (text.StartsWith("SE_WarfareFireAndIce", StringComparison.OrdinalIgnoreCase))
					{
						return true;
					}
					if (text3.IndexOf("SE_Warfare", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return true;
					}
					if (text3.IndexOf("SE_WarfareFireAndIce", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return true;
					}
					if (text.IndexOf("Warfare_Bleeding", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return true;
					}
					if (text2.IndexOf("Bleeding", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return true;
					}
				}
			}
			return false;
		}

		private static float GetWeaponPvpMultiplier(Player attacker)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: 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_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Expected I4, but got Unknown
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			ItemData currentWeapon = ((Humanoid)attacker).GetCurrentWeapon();
			if (currentWeapon == null || currentWeapon.m_shared == null)
			{
				return 1f;
			}
			SkillType skillType = currentWeapon.m_shared.m_skillType;
			SkillType val = skillType;
			float num = (val - 1) switch
			{
				0 => PvPOverhaul.PvpSwordDamageMultiplier.Value, 
				6 => PvPOverhaul.PvpAxeDamageMultiplier.Value, 
				2 => PvPOverhaul.PvpClubDamageMultiplier.Value, 
				1 => PvPOverhaul.PvpKnifeDamageMultiplier.Value, 
				4 => PvPOverhaul.PvpSpearDamageMultiplier.Value, 
				3 => PvPOverhaul.PvpPolearmDamageMultiplier.Value, 
				10 => PvPOverhaul.PvpUnarmedDamageMultiplier.Value, 
				7 => PvPOverhaul.PvpBowDamageMultiplier.Value, 
				13 => PvPOverhaul.PvpCrossbowDamageMultiplier.Value, 
				_ => 1f, 
			};
			if (IsMeleeSkill(currentWeapon.m_shared.m_skillType) && IsSecondaryAttack(attacker, currentWeapon))
			{
				num *= PvPOverhaul.PvpMeleeSecondaryDamageMultiplier.Value;
			}
			if (IsTwoHandedMeleeWeapon(currentWeapon))
			{
				num *= PvPOverhaul.PvpTwoHandedDamageMultiplier.Value;
			}
			return num;
		}

		private static bool IsMeleeSkill(SkillType skill)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Invalid comparison between Unknown and I4
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Invalid comparison between Unknown and I4
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Invalid comparison between Unknown and I4
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Invalid comparison between Unknown and I4
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			return (int)skill == 1 || (int)skill == 7 || (int)skill == 3 || (int)skill == 2 || (int)skill == 5 || (int)skill == 4 || (int)skill == 11;
		}

		private static bool IsTwoHandedMeleeWeapon(ItemData weapon)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Invalid comparison between Unknown and I4
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Invalid comparison between Unknown and I4
			if (weapon == null || weapon.m_shared == null)
			{
				return false;
			}
			if (!IsMeleeSkill(weapon.m_shared.m_skillType))
			{
				return false;
			}
			return (int)weapon.m_shared.m_itemType == 14 || (int)weapon.m_shared.m_itemType == 22;
		}

		private static bool IsSecondaryAttack(Player attacker, ItemData weapon)
		{
			if ((Object)(object)attacker == (Object)null || weapon?.m_shared == null)
			{
				return false;
			}
			Attack currentAttack = ((Humanoid)attacker).m_currentAttack;
			Attack secondaryAttack = weapon.m_shared.m_secondaryAttack;
			if (currentAttack == null || secondaryAttack == null)
			{
				return false;
			}
			if (currentAttack == secondaryAttack)
			{
				return true;
			}
			return currentAttack.m_attackAnimation == secondaryAttack.m_attackAnimation || currentAttack.m_attackChainLevels == secondaryAttack.m_attackChainLevels || Math.Abs(currentAttack.m_damageMultiplier - secondaryAttack.m_damageMultiplier) < 0.01f;
		}

		private static bool IsLikelyMagicBurstFragment(HitData hit)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Invalid comparison between Unknown and I4
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Invalid comparison between Unknown and I4
			if (hit == null)
			{
				return false;
			}
			float totalDamage = hit.GetTotalDamage();
			if (totalDamage <= 0f || totalDamage > 100f)
			{
				return false;
			}
			if ((int)hit.m_skill != 9 && (int)hit.m_skill != 10)
			{
				return false;
			}
			int num = 0;
			if (hit.m_damage.m_damage > 0f)
			{
				num++;
			}
			if (hit.m_damage.m_blunt > 0f)
			{
				num++;
			}
			if (hit.m_damage.m_slash > 0f)
			{
				num++;
			}
			if (hit.m_damage.m_pierce > 0f)
			{
				num++;
			}
			if (hit.m_damage.m_chop > 0f)
			{
				num++;
			}
			if (hit.m_damage.m_pickaxe > 0f)
			{
				num++;
			}
			if (hit.m_damage.m_fire > 0f)
			{
				num++;
			}
			if (hit.m_damage.m_frost > 0f)
			{
				num++;
			}
			if (hit.m_damage.m_lightning > 0f)
			{
				num++;
			}
			if (hit.m_damage.m_poison > 0f)
			{
				num++;
			}
			if (hit.m_damage.m_spirit > 0f)
			{
				num++;
			}
			return num <= 3;
		}

		private static bool IsLikelyMagicSecondary(HitData hit)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Invalid comparison between Unknown and I4
			if ((int)hit.m_skill != 9)
			{
				return false;
			}
			float totalDamage = hit.GetTotalDamage();
			if (totalDamage <= 0f)
			{
				return false;
			}
			return hit.m_damage.m_fire >= 50f || hit.m_damage.m_frost >= 50f || hit.m_damage.m_lightning >= 50f || hit.m_damage.m_poison >= 50f || hit.m_damage.m_spirit >= 50f || totalDamage >= 100f;
		}

		private static bool IsLikelyDotTick(HitData hit)
		{
			if (!(hit.m_damage.m_fire > 0f) && !(hit.m_damage.m_poison > 0f) && !(hit.m_damage.m_spirit > 0f))
			{
				return false;
			}
			bool flag = hit.m_damage.m_damage > 0f || hit.m_damage.m_blunt > 0f || hit.m_damage.m_slash > 0f || hit.m_damage.m_pierce > 0f || hit.m_damage.m_chop > 0f || hit.m_damage.m_pickaxe > 0f || hit.m_damage.m_frost > 0f || hit.m_damage.m_lightning > 0f;
			return !flag;
		}

		private static void ScaleDamage(HitData hit, float scale)
		{
			hit.m_damage.m_damage *= scale;
			hit.m_damage.m_blunt *= scale;
			hit.m_damage.m_slash *= scale;
			hit.m_damage.m_pierce *= scale;
			hit.m_damage.m_chop *= scale;
			hit.m_damage.m_pickaxe *= scale;
			hit.m_damage.m_fire *= scale;
			hit.m_damage.m_frost *= scale;
			hit.m_damage.m_lightning *= scale;
			hit.m_damage.m_poison *= scale;
			hit.m_damage.m_spirit *= scale;
		}

		private static void ScaleDamageTypes(ref DamageTypes damage, float scale)
		{
			damage.m_damage *= scale;
			damage.m_blunt *= scale;
			damage.m_slash *= scale;
			damage.m_pierce *= scale;
			damage.m_chop *= scale;
			damage.m_pickaxe *= scale;
			damage.m_fire *= scale;
			damage.m_frost *= scale;
			damage.m_lightning *= scale;
			damage.m_poison *= scale;
			damage.m_spirit *= scale;
		}

		private static void ZeroDamage(HitData hit)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			hit.m_damage = default(DamageTypes);
		}

		private static void ApplyPvPResilience(Player victim, HitData hit)
		{
			if ((Object)(object)victim == (Object)null || hit == null)
			{
				return;
			}
			float totalDamage = hit.GetTotalDamage();
			float resilience = PvpResilienceAPI.GetResilience(victim);
			Logger.LogInfo((object)$"[PvPOverhaul Resilience] victim={victim.GetPlayerName()} rating={resilience} before={totalDamage}");
			if (!(resilience <= 0f))
			{
				float num = resilience / PvPOverhaul.ResilienceRatingPerPercent.Value / 100f;
				num = Mathf.Clamp(num, 0f, PvPOverhaul.MaxResilienceReduction.Value);
				if (!(num <= 0f))
				{
					((DamageTypes)(ref hit.m_damage)).Modify(1f - num);
					Logger.LogInfo((object)$"[PvPOverhaul Resilience] reduction={num:P1} after={hit.GetTotalDamage()}");
				}
			}
		}
	}
	[HarmonyPatch(typeof(Character), "Heal", new Type[]
	{
		typeof(float),
		typeof(bool)
	})]
	internal static class PvpCombatNoHealingPatch
	{
		private static bool Prefix(Character __instance, ref float hp)
		{
			if (!PvPOverhaul.DisableHealthRegenInPvpBattle.Value)
			{
				return true;
			}
			Player val = (Player)(object)((__instance is Player) ? __instance : null);
			if (val == null)
			{
				return true;
			}
			if (!IsInPvpCombat(val))
			{
				return true;
			}
			hp = 0f;
			return false;
		}

		private static bool IsInPvpCombat(Player player)
		{
			SEMan sEMan = ((Character)player).GetSEMan();
			if (sEMan != null && sEMan.HaveStatusEffect(StringExtensionMethods.GetStableHashCode("SE_Combat")))
			{
				return true;
			}
			if ((Object)(object)((Character)player).m_nview != (Object)null && ((Character)player).m_nview.IsValid())
			{
				ZDO zDO = ((Character)player).m_nview.GetZDO();
				if (zDO != null && zDO.GetBool("VPT_PlayerInCombat", false))
				{
					return true;
				}
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(SE_Shield), "OnDamaged")]
	internal static class PvPShieldBreakOnDamagedPatch
	{
		private static bool Prefix(SE_Shield __instance, HitData hit, Character attacker)
		{
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			if (!PvPOverhaul.BreakShieldOnPvpHit.Value)
			{
				return true;
			}
			Character obj = ((StatusEffect)(__instance?)).m_character;
			Player val = (Player)(object)((obj is Player) ? obj : null);
			if (val == null)
			{
				return true;
			}
			if (hit == null || hit.GetTotalDamage() <= 0f)
			{
				return true;
			}
			Character val2 = (((Object)(object)attacker != (Object)null) ? attacker : hit.GetAttacker());
			Player val3 = (Player)(object)((val2 is Player) ? val2 : null);
			if (val3 == null)
			{
				return true;
			}
			if ((Object)(object)val3 == (Object)(object)val)
			{
				return true;
			}
			__instance.m_absorbDamage = 0f;
			__instance.m_absorbDamagePerSkillLevel = 0f;
			SEMan sEMan = ((Character)val).GetSEMan();
			if (sEMan != null)
			{
				sEMan.RemoveStatusEffect(((StatusEffect)__instance).NameHash(), true);
			}
			BountySystem.Instance.MarkIgnoredDeath(val);
			if (PvPOverhaul.ShieldBreakConsumesHit.Value)
			{
				hit.m_damage = default(DamageTypes);
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Character), "Damage")]
	internal static class PvPBlockLeakPatch
	{
		private static bool _applyingLeak;

		private static void Postfix(Character __instance, HitData hit)
		{
			//IL_00f1: 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_00f9: 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_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: 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_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: 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_0140: 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_014e: Expected O, but got Unknown
			if (_applyingLeak)
			{
				return;
			}
			Player val = (Player)(object)((__instance is Player) ? __instance : null);
			if (val == null)
			{
				return;
			}
			long playerID = val.GetPlayerID();
			if (!PvPDamagePatch.PendingLeaks.TryGetValue(playerID, out var value))
			{
				return;
			}
			PvPDamagePatch.PendingLeaks.Remove(playerID);
			if (Time.time - value.Time > 1f)
			{
				return;
			}
			float health = ((Character)val).GetHealth();
			float num = Mathf.Max(0f, value.HealthBefore - health);
			if (num >= value.ExpectedDamage * 0.8f)
			{
				return;
			}
			float blockLeakPercent = value.BlockLeakPercent;
			float num2 = value.ExpectedDamage * blockLeakPercent;
			if (!(num >= num2))
			{
				float num3 = num2 - num;
				if (!(num3 <= 0f))
				{
					HitData val2 = new HitData
					{
						m_damage = new DamageTypes
						{
							m_damage = num3
						},
						m_point = ((Character)val).GetCenterPoint(),
						m_dir = Vector3.zero,
						m_blockable = false,
						m_dodgeable = false,
						m_skill = hit.m_skill,
						m_hitType = hit.m_hitType
					};
					_applyingLeak = true;
					((Character)val).Damage(val2);
					_applyingLeak = false;
				}
			}
		}
	}
	[HarmonyPatch(typeof(Aoe), "OnHit")]
	internal static class DisablePvpAoePatch
	{
		private static bool Prefix(Aoe __instance, Collider collider)
		{
			if (!PvPOverhaul.DisablePvpAoeSplash.Value)
			{
				return true;
			}
			if ((Object)(object)__instance == (Object)null || (Object)(object)collider == (Object)null)
			{
				return true;
			}
			if (((DamageTypes)(ref __instance.m_damage)).GetTotalDamage() <= 0f)
			{
				return true;
			}
			Player componentInParent = ((Component)collider).GetComponentInParent<Player>();
			if ((Object)(object)componentInParent == (Object)null)
			{
				return true;
			}
			Character owner = __instance.m_owner;
			Player val = (Player)(object)((owner is Player) ? owner : null);
			if (val == null)
			{
				return true;
			}
			if ((Object)(object)val == (Object)(object)componentInParent)
			{
				return true;
			}
			return false;
		}
	}
	[BepInPlugin("dzk.pvpoverhaul", "PvpOverhaul", "1.0.6")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	internal class PvPOverhaul : BaseUnityPlugin
	{
		public const string PluginGUID = "dzk.pvpoverhaul";

		public const string PluginName = "PvpOverhaul";

		public const string PluginVersion = "1.0.6";

		public static PvPOverhaul Instance;

		public static CustomLocalization Localization;

		public static ConfigEntry<int> BountyTriggerKillCount;

		public static ConfigEntry<int> BountyKillWindowMinutes;

		public static ConfigEntry<int> BountyDurationMinutes;

		public static ConfigEntry<string> BountyRewardItem;

		public static ConfigEntry<int> BountyRewardAmountPerTier;

		public static ConfigEntry<string> MsgKillBroadcast;

		public static ConfigEntry<string> MsgBountyActivated;

		public static ConfigEntry<string> MsgBountyRefreshed;

		public static ConfigEntry<string> MsgBountyCleared;

		public static ConfigEntry<string> MsgBountyExpired;

		public static ConfigEntry<string> MsgBountyReward;

		public static ConfigEntry<bool> EnablePvpDamagePatch;

		public static ConfigEntry<float> PvpMaxDirectHitPercentHp;

		public static ConfigEntry<float> PvpMaxDotTickPercentHp;

		public static ConfigEntry<bool> BreakShieldOnPvpHit;

		public static ConfigEntry<bool> ShieldBreakConsumesHit;

		public static ConfigEntry<string> ShieldStatusEffectNames;

		public static ConfigEntry<bool> DisableHealthRegenInPvpBattle;

		public static ConfigEntry<string> PvpBattleStatusEffectNames;

		public static ConfigEntry<float> PvpBlockedDamageLeakPercent;

		public static ConfigEntry<float> PvpMaxMagicMultiHitPercentHp;

		public static ConfigEntry<bool> DisablePvpAoeSplash;

		public static ConfigEntry<float> PvpMaxMagicBurstPercentHp;

		public static ConfigEntry<float> PvpMagicBurstWindowSeconds;

		public static ConfigEntry<float> PvpMaxMagicSecondaryPercentHp;

		public static ConfigEntry<float> PvpMeleeDamageMultiplier;

		public static ConfigEntry<float> PvpMeleeSecondaryDamageMultiplier;

		public static ConfigEntry<float> PvpSwordDamageMultiplier;

		public static ConfigEntry<float> PvpAxeDamageMultiplier;

		public static ConfigEntry<float> PvpClubDamageMultiplier;

		public static ConfigEntry<float> PvpKnifeDamageMultiplier;

		public static ConfigEntry<float> PvpSpearDamageMultiplier;

		public static ConfigEntry<float> PvpPolearmDamageMultiplier;

		public static ConfigEntry<float> PvpUnarmedDamageMultiplier;

		public static ConfigEntry<float> PvpBowDamageMultiplier;

		public static ConfigEntry<float> PvpCrossbowDamageMultiplier;

		public static ConfigEntry<float> PvpLightningDamageMultiplier;

		public static ConfigEntry<float> PvpTwoHandedDamageMultiplier;

		public static ConfigEntry<float> PvpTwoHandedBlockedDamageLeakPercent;

		public static ConfigEntry<float> TherzieDualWeapNerf;

		public static ConfigEntry<float> TherzieBleedNerf;

		public static ConfigEntry<bool> DisableSkillLossOnPvpDeath;

		public static ConfigEntry<float> ResilienceRatingPerPercent;

		public static ConfigEntry<float> MaxResilienceReduction;

		private FileSystemWatcher _configWatcher;

		private DateTime _lastConfigReloadUtc;

		private void Awake()
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			Instance = this;
			Localization = LocalizationManager.Instance.GetLocalization();
			SetupConfig();
			SetupConfigWatcher();
			Logger.LogInfo((object)"PvpOverhaul is loading...");
			Harmony val = new Harmony("dzk.pvpoverhaul");
			val.PatchAll();
			Compat_ShudnalInfinityHammer.TryInstall(val);
			PvpSkillLossPatch.PatchFortifySkillsRedux(val);
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new BountyStatusCommand());
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new BountyResetCommand());
			SynchronizationManager.OnConfigurationSynchronized += delegate(object _, ConfigurationSynchronizationEventArgs args)
			{
				Logger.LogInfo((object)(args.InitialSynchronization ? "[PvPOverhaul] Initial server config sync received" : "[PvPOverhaul] Server config sync received"));
			};
			Logger.LogInfo((object)"PvpOverhaul loaded successfully.");
		}

		private void Update()
		{
			BountyRpc.Update();
			BountySystem.Instance.Update();
			BountyMapCircle.Update();
			BountyStatusEffectController.UpdateRegistration();
			BountyRpc.UpdateReward();
		}

		private void SetupConfig()
		{
			BountyTriggerKillCount = ConfigFileExtensions.BindConfig<int>(((BaseUnityPlugin)this).Config, "Bounty", "TriggerKillCount", 3, "Number of player kills required to activate a bounty.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			BountyKillWindowMinutes = ConfigFileExtensions.BindConfig<int>(((BaseUnityPlugin)this).Config, "Bounty", "KillWindowMinutes", 180, "Time window in minutes during which kills are counted for bounty activation.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			BountyDurationMinutes = ConfigFileExtensions.BindConfig<int>(((BaseUnityPlugin)this).Config, "Bounty", "DurationMinutes", 60, "How long the bounty stays active, in minutes.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			BountyRewardItem = ConfigFileExtensions.BindConfig<string>(((BaseUnityPlugin)this).Config, "Bounty", "RewardItem", "Coins", "Prefab name of the item rewarded when killing a bounty player.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			BountyRewardAmountPerTier = ConfigFileExtensions.BindConfig<int>(((BaseUnityPlugin)this).Config, "Bounty", "RewardAmountPerTier", 100, "Reward amount per bounty tier.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			MsgKillBroadcast = ConfigFileExtensions.BindConfig<string>(((BaseUnityPlugin)this).Config, "Messages", "KillBroadcast", "{killer} a tué {victim}", "Broadcast when a player kills another player.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			MsgBountyActivated = ConfigFileExtensions.BindConfig<string>(((BaseUnityPlugin)this).Config, "Messages", "BountyActivated", "{player} est maintenant recherché ! Tier {tier}", "Broadcast when a bounty starts.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			MsgBountyRefreshed = ConfigFileExtensions.BindConfig<string>(((BaseUnityPlugin)this).Config, "Messages", "BountyRefreshed", "{player} reste recherché ! Tier {tier} | Timer remis à {duration} min.", "Broadcast when bounty timer refreshes.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			MsgBountyCleared = ConfigFileExtensions.BindConfig<string>(((BaseUnityPlugin)this).Config, "Messages", "BountyCleared", "{player} n'est plus recherché.", "Broadcast when a bounty is cleared.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			MsgBountyExpired = ConfigFileExtensions.BindConfig<string>(((BaseUnityPlugin)this).Config, "Messages", "BountyExpired", "La prime de {player} a expiré.", "Broadcast when a bounty expires.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			MsgBountyReward = ConfigFileExtensions.BindConfig<string>(((BaseUnityPlugin)this).Config, "Messages", "BountyReward", "{killer} a éliminé le joueur recherché {victim} et gagne {amount}x {item} !", "Broadcast when a bounty is claimed.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			EnablePvpDamagePatch = ConfigFileExtensions.BindConfig<bool>(((BaseUnityPlugin)this).Config, "PvP Damage", "EnablePvpDamagePatch", true, "Enable global PvP damage normalization.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpMaxDirectHitPercentHp = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Damage", "MaxDirectHitPercentHp", 0.08f, "Maximum PvP direct hit damage as percent of victim max HP.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpMaxDotTickPercentHp = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Damage", "MaxDotTickPercentHp", 0.015f, "Maximum PvP fire/poison/spirit tick damage as percent of victim max HP.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			BreakShieldOnPvpHit = ConfigFileExtensions.BindConfig<bool>(((BaseUnityPlugin)this).Config, "PvP Shields", "BreakShieldOnPvpHit", true, "If true, PvP hits instantly remove configured shield status effects.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			ShieldBreakConsumesHit = ConfigFileExtensions.BindConfig<bool>(((BaseUnityPlugin)this).Config, "PvP Shields", "ShieldBreakConsumesHit", true, "If true, the hit that breaks a shield deals 0 damage.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			ShieldStatusEffectNames = ConfigFileExtensions.BindConfig<string>(((BaseUnityPlugin)this).Config, "PvP Shields", "ShieldStatusEffectNames", "Staff_shield,SE_StaffShield,SE_Shield,Shield,Bubble,Barrier,Protection", "Comma-separated status effect names/hashes to remove on PvP hit.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			DisableHealthRegenInPvpBattle = ConfigFileExtensions.BindConfig<bool>(((BaseUnityPlugin)this).Config, "PvP Battle", "DisableHealthRegenInPvpBattle", true, "If true, health regen is set to 0 while the player has the PvP battle status.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpBattleStatusEffectNames = ConfigFileExtensions.BindConfig<string>(((BaseUnityPlugin)this).Config, "PvP Battle", "BattleStatusEffectNames", "SE_Combat", "Comma-separated status effect names used by Valheim PvP Tweaks.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpBlockedDamageLeakPercent = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Damage", "BlockedDamageLeakPercent", 0.25f, "Minimum percent of normalized PvP damage that still goes through block.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpMaxMagicMultiHitPercentHp = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Damage", "MaxMagicMultiHitPercentHp", 0.015f, "Maximum damage per hit for magic multi-hit projectiles, as percent of victim max HP.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			DisablePvpAoeSplash = ConfigFileExtensions.BindConfig<bool>(((BaseUnityPlugin)this).Config, "PvP Damage", "DisablePvpAoeSplash", true, "If true, damaging AoE effects owned by players do not damage other players.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpMaxMagicBurstPercentHp = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Damage", "MaxMagicBurstPercentHp", 0.08f, "Maximum total PvP damage from magic multi-hit burst per attacker/victim over a short window.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpMagicBurstWindowSeconds = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Damage", "MagicBurstWindowSeconds", 0.2f, "Short time window used to group instant magic fragment hits from the same attacker to the same victim.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpMaxMagicSecondaryPercentHp = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Damage", "MaxMagicSecondaryPercentHp", 0.02f, "Maximum PvP damage for magic secondary attacks, as percent of victim max HP.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpMeleeDamageMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Damage", "MeleeDamageMultiplier", 1.25f, "Multiplier applied to melee PvP direct hit cap.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpMeleeSecondaryDamageMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Damage", "MeleeSecondaryDamageMultiplier", 1.5f, "Multiplier applied to melee secondary attack PvP damage cap.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpSwordDamageMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Weapon Multipliers", "Swords", 1.25f, "PvP damage cap multiplier for swords.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpAxeDamageMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Weapon Multipliers", "Axes", 1.25f, "PvP damage cap multiplier for axes.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpClubDamageMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Weapon Multipliers", "Clubs", 1.25f, "PvP damage cap multiplier for clubs.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpKnifeDamageMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Weapon Multipliers", "Knives", 0.9f, "PvP damage cap multiplier for knives.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpSpearDamageMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Weapon Multipliers", "Spears", 1f, "PvP damage cap multiplier for spears.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpPolearmDamageMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Weapon Multipliers", "Polearms", 1.15f, "PvP damage cap multiplier for polearms.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpUnarmedDamageMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Weapon Multipliers", "Unarmed", 1f, "PvP damage cap multiplier for unarmed.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpBowDamageMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Weapon Multipliers", "Bows", 1f, "PvP damage cap multiplier for bows.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpCrossbowDamageMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Weapon Multipliers", "Crossbows", 1.25f, "PvP damage cap multiplier for crossbows.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpLightningDamageMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Damage", "LightningDamageMultiplier", 0.25f, "Multiplier applied to PvP lightning damage after normalization. 0.25 = -75%.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpTwoHandedDamageMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Damage", "TwoHandedDamageMultiplier", 1.25f, "Extra PvP damage cap multiplier for two-handed melee weapons.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			PvpTwoHandedBlockedDamageLeakPercent = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Damage", "TwoHandedBlockedDamageLeakPercent", 0.75f, "Minimum percent of normalized two-handed PvP damage that still goes through block.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			TherzieDualWeapNerf = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Therzie Balance", "TherzieDualWeapNerf", 0.5f, "Multiplier applied to PvP damage from Therzie dual weapons detected by prefab/name containing 'Dual'. 0.50 = -50%.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			TherzieBleedNerf = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, "PvP Therzie Balance", "TherzieSENerf", 0.25f, "Multiplier applied to Therzie SE that proc on 4th or 5th. 0.25 = -75%.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			DisableSkillLossOnPvpDeath = ConfigFileExtensions.BindConfig<bool>(((BaseUnityPlugin)this).Config, "PvP Death", "DisableSkillLossOnPvpDeath", true, "If true, PvP deaths restore all skill levels after death, overriding vanilla and other mods.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			ResilienceRatingPerPercent = ((BaseUnityPlugin)this).Config.Bind<float>("PvP Resilience", "ResilienceRatingPerPercent", 10f, "How much resilience rating equals 1% PvP damage reduction.");
			MaxResilienceReduction = ((BaseUnityPlugin)this).Config.Bind<float>("PvP Resilience", "MaxResilienceReduction", 0.25f, "Maximum PvP damage reduction from resilience. 0.25 = 25%.");
		}

		private void SetupConfigWatcher()
		{
			string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
			string directoryName = Path.GetDirectoryName(configFilePath);
			string fileName = Path.GetFileName(configFilePath);
			if (!string.IsNullOrWhiteSpace(directoryName) && !string.IsNullOrWhiteSpace(fileName))
			{
				_configWatcher = new FileSystemWatcher(directoryName, fileName)
				{
					NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite),
					EnableRaisingEvents = true
				};
				_configWatcher.Changed += OnConfigFileChanged;
				_configWatcher.Created += OnConfigFileChanged;
				_configWatcher.Renamed += OnConfigFileChanged;
				Logger.LogInfo((object)"[PvPOverhaul] Config watcher enabled");
			}
		}

		private void OnConfigFileChanged(object sender, FileSystemEventArgs e)
		{
			DateTime utcNow = DateTime.UtcNow;
			if ((utcNow - _lastConfigReloadUtc).TotalMilliseconds < 500.0)
			{
				return;
			}
			_lastConfigReloadUtc = utcNow;
			try
			{
				((BaseUnityPlugin)this).Config.Reload();
				Logger.LogInfo((object)"[PvPOverhaul] Config reloaded");
			}
			catch (Exception arg)
			{
				Logger.LogWarning((object)$"[PvPOverhaul] Failed to reload config: {arg}");
			}
		}

		private void OnDestroy()
		{
			if (_configWatcher != null)
			{
				_configWatcher.Changed -= OnConfigFileChanged;
				_configWatcher.Created -= OnConfigFileChanged;
				_configWatcher.Renamed -= OnConfigFileChanged;
				_configWatcher.Dispose();
				_configWatcher = null;
			}
		}
	}
	internal static class PvpSkillLossPatch
	{
		private class SkillSnapshot
		{
			public float LastPvpHitTime;

			public readonly Dictionary<SkillType, SkillData> Skills = new Dictionary<SkillType, SkillData>();

			public readonly Dictionary<object, FortifyData> FortifySkills = new Dictionary<object, FortifyData>();
		}

		private class SkillData
		{
			public float Level;

			public float Accumulator;
		}

		private class FortifyData
		{
			public float Level;

			public float Accumulator;
		}

		[CompilerGenerated]
		private sealed class <DelayedRestore>d__11 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public Player player;

			private long <playerId>5__1;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <DelayedRestore>d__11(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0054: Unknown result type (might be due to invalid IL or missing references)
				//IL_005e: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = null;
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					RestoreSnapshot(player);
					<>2__current = (object)new WaitForSeconds(0.5f);
					<>1__state = 2;
					return true;
				case 2:
					<>1__state = -1;
					RestoreSnapshot(player);
					if ((Object)(object)player != (Object)null)
					{
						<playerId>5__1 = player.GetPlayerID();
						Snapshots.Remove(<playerId>5__1);
						DelayedRestoreRunning.Remove(<playerId>5__1);
					}
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private const float SnapshotDuration = 30f;

		private static readonly Dictionary<long, SkillSnapshot> Snapshots = new Dictionary<long, SkillSnapshot>();

		private static readonly HashSet<long> DelayedRestoreRunning = new HashSet<long>();

		internal static bool ShouldProtectSkills(Player player)
		{
			if (!PvPOverhaul.DisableSkillLossOnPvpDeath.Value)
			{
				return false;
			}
			if ((Object)(object)player == (Object)null || (Object)(object)player.m_skills == (Object)null)
			{
				return false;
			}
			if (player.m_godMode)
			{
				return false;
			}
			if (Snapshots.TryGetValue(player.GetPlayerID(), out var value) && Time.time - value.LastPvpHitTime <= 30f)
			{
				return true;
			}
			if (IsInPvpCombat(player))
			{
				return true;
			}
			if (BountySystem.Instance.ResolveLastHit(player.GetPlayerID()) != null)
			{
				return true;
			}
			return false;
		}

		private static bool IsInPvpCombat(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			SEMan sEMan = ((Character)player).GetSEMan();
			if (sEMan != null && sEMan.HaveStatusEffect(StringExtensionMethods.GetStableHashCode("SE_Combat")))
			{
				return true;
			}
			if ((Object)(object)((Character)player).m_nview != (Object)null && ((Character)player).m_nview.IsValid())
			{
				ZDO zDO = ((Character)player).m_nview.GetZDO();
				if (zDO != null && zDO.GetBool("VPT_PlayerInCombat", false))
				{
					return true;
				}
			}
			return false;
		}

		internal static void ForceSaveSnapshot(Player player)
		{
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || (Object)(object)player.m_skills == (Object)null)
			{
				return;
			}
			long playerID = player.GetPlayerID();
			if (Snapshots.TryGetValue(playerID, out var value))
			{
				value.LastPvpHitTime = Time.time;
				return;
			}
			SkillSnapshot skillSnapshot = new SkillSnapshot
			{
				LastPvpHitTime = Time.time
			};
			foreach (KeyValuePair<SkillType, Skill> skillDatum in player.m_skills.m_skillData)
			{
				if (skillDatum.Value != null)
				{
					skillSnapshot.Skills[skillDatum.Key] = new SkillData
					{
						Level = skillDatum.Value.m_level,
						Accumulator = skillDatum.Value.m_accumulator
					};
				}
			}
			SnapshotFortifySkills(skillSnapshot);
			Snapshots[playerID] = skillSnapshot;
			Logger.LogInfo((object)("[PvPOverhaul] PvP skill snapshot saved for " + player.GetPlayerName()));
		}

		internal static void RestoreSnapshot(Player player)
		{
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || (Object)(object)player.m_skills == (Object)null)
			{
				return;
			}
			long playerID = player.GetPlayerID();
			if (!Snapshots.TryGetValue(playerID, out var value))
			{
				return;
			}
			if (Time.time - value.LastPvpHitTime > 30f)
			{
				Snapshots.Remove(playerID);
				return;
			}
			foreach (KeyValuePair<SkillType, SkillData> skill2 in value.Skills)
			{
				Skill skill = player.m_skills.GetSkill(skill2.Key);
				if (skill != null)
				{
					skill.m_level = skill2.Value.Level;
					skill.m_accumulator = skill2.Value.Accumulator;
				}
			}
			RestoreFortifySkills(value);
			Logger.LogInfo((object)("[PvPOverhaul] PvP death skills restored for " + player.GetPlayerName()));
		}

		internal static void StartDelayedRestore(Player player)
		{
			if (!((Object)(object)PvPOverhaul.Instance == (Object)null) && !((Object)(object)player == (Object)null))
			{
				long playerID = player.GetPlayerID();
				if (Snapshots.ContainsKey(playerID) && !DelayedRestoreRunning.Contains(playerID))
				{
					DelayedRestoreRunning.Add(playerID);
					((MonoBehaviour)PvPOverhaul.Instance).StartCoroutine(DelayedRestore(player));
				}
			}
		}

		[IteratorStateMachine(typeof(<DelayedRestore>d__11))]
		private static IEnumerator DelayedRestore(Player player)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <DelayedRestore>d__11(0)
			{
				player = player
			};
		}

		private static void SnapshotFortifySkills(SkillSnapshot snapshot)
		{
			try
			{
				Type type = AccessTools.TypeByName("FortifySkillsRedux.FortifySkillData");
				if (type == null)
				{
					return;
				}
				object obj = AccessTools.Field(type, "s_FortifySkills")?.GetValue(null);
				if (obj == null)
				{
					return;
				}
				foreach (object item in (IEnumerable)obj)
				{
					object obj2 = item.GetType().GetProperty("Key")?.GetValue(item, null);
					object obj3 = item.GetType().GetProperty("Value")?.GetValue(item, null);
					if (obj2 != null && obj3 != null)
					{
						FieldInfo fieldInfo = AccessTools.Field(obj3.GetType(), "FortifyLevel");
						FieldInfo fieldInfo2 = AccessTools.Field(obj3.GetType(), "FortifyAccumulator");
						if (!(fieldInfo == null) && !(fieldInfo2 == null))
						{
							snapshot.FortifySkills[obj2] = new FortifyData
							{
								Level = Convert.ToSingle(fieldInfo.GetValue(obj3)),
								Accumulator = Convert.ToSingle(fieldInfo2.GetValue(obj3))
							};
						}
					}
				}
			}
			catch (Exception ex)
			{
				Logger.LogWarning((object)("[PvPOverhaul] Fortify snapshot failed: " + ex.Message));
			}
		}

		private static void RestoreFortifySkills(SkillSnapshot snapshot)
		{
			try
			{
				Type type = AccessTools.TypeByName("FortifySkillsRedux.FortifySkillData");
				if (type == null)
				{
					return;
				}
				object obj = AccessTools.Field(type, "s_FortifySkills")?.GetValue(null);
				if (obj == null)
				{
					return;
				}
				foreach (object item in (IEnumerable)obj)
				{
					object obj2 = item.GetType().GetProperty("Key")?.GetValue(item, null);
					object obj3 = item.GetType().GetProperty("Value")?.GetValue(item, null);
					if (obj2 != null && obj3 != null && snapshot.FortifySkills.TryGetValue(obj2, out var value))
					{
						FieldInfo fieldInfo = AccessTools.Field(obj3.GetType(), "FortifyLevel");
						FieldInfo fieldInfo2 = AccessTools.Field(obj3.GetType(), "FortifyAccumulator");
						fieldInfo?.SetValue(obj3, value.Level);
						fieldInfo2?.SetValue(obj3, value.Accumulator);
					}
				}
			}
			catch (Exception ex)
			{
				Logger.LogWarning((object)("[PvPOverhaul] Fortify restore failed: " + ex.Message));
			}
		}

		internal static void PatchFortifySkillsRedux(Harmony harmony)
		{
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Expected O, but got Unknown
			Type type = null;
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				try
				{
					type = assembly.GetTypes().FirstOrDefault((Type t) => t.FullName == "FortifySkillsRedux.Patches.OnDeathPatche