Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PvPOverhaul v1.0.1
PvpOverhaul.dll
Decompiled 5 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.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.Configuration; using HarmonyLib; using Jotunn; using Jotunn.Entities; using Jotunn.Extensions; using Jotunn.Managers; using Jotunn.Utils; 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.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] 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); 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); } } [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.0")] [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.0"; 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; 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(); 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); } 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.OnDeathPatches"); if (type != null) { break; } } catch { } } if (type == null) { Logger.LogWarning((object)"[PvPOverhaul] FortifySkillsRedux OnDeathPatches type not found."); return; } MethodInfo methodInfo = AccessTools.DeclaredMethod(type, "OnDeathFinalizer", (Type[])null, (Type[])null); if (methodInfo == null) { Logger.LogWarning((object)"[PvPOverhaul] FortifySkillsRedux OnDeathFinalizer method not found."); return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PvpSkillLossPatch), "FortifyOnDeathFinalizerPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Logger.LogInfo((object)"[PvPOverhaul] FortifySkillsRedux death finalizer patched."); } private static bool FortifyOnDeathFinalizerPrefix(Player __instance) { if (!ShouldProtectSkills(__instance)) { return true; } ForceFortifyLevelsToCurrentSkills(__instance); RestoreSnapshot(__instance); StartDelayedRestore(__instance); Logger.LogInfo((object)("[PvPOverhaul] FortifySkillsRedux death finalizer BLOCKED for PvP death: " + __instance.GetPlayerName())); return false; } private static void FortifyOnDeathFinalizerPostfix(Player __instance) { if (ShouldProtectSkills(__instance)) { RestoreSnapshot(__instance); StartDelayedRestore(__instance); } } private static void ForceFortifyLevelsToCurrentSkills(Player player) { //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0155: 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; } try { Type type = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { type = assembly.GetTypes().FirstOrDefault((Type t) => t.FullName == "FortifySkillsRedux.FortifySkillData"); if (type != null) { break; } } catch { } } if (type == null) { return; } object obj2 = AccessTools.Field(type, "s_FortifySkills")?.GetValue(null); if (obj2 == null) { return; } foreach (object item in (IEnumerable)obj2) { object obj3 = item.GetType().GetProperty("Key")?.GetValue(item, null); object obj4 = item.GetType().GetProperty("Value")?.GetValue(item, null); if (obj3 != null && obj4 != null) { SkillType val = (SkillType)obj3; Skill skill = player.m_skills.GetSkill(val); if (skill != null) { FieldInfo fieldInfo = AccessTools.Field(obj4.GetType(), "FortifyLevel"); FieldInfo fieldInfo2 = AccessTools.Field(obj4.GetType(), "FortifyAccumulator"); fieldInfo?.SetValue(obj4, skill.m_level); fieldInfo2?.SetValue(obj4, skill.m_accumulator); } } } Logger.LogInfo((object)("[PvPOverhaul] Fortify levels forced to current skill levels for " + player.GetPlayerName())); } catch (Exception ex) { Logger.LogWarning((object)("[PvPOverhaul] Failed to force Fortify levels: " + ex.Message)); } } } [HarmonyPatch(typeof(Character), "Damage")] internal static class PvpSkillSnapshotOnDamagePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(Character __instance, HitData hit) { if (!PvPOverhaul.DisableSkillLossOnPvpDeath.Value) { return; } 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)) { PvpSkillLossPatch.ForceSaveSnapshot(val); } } } } [HarmonyPatch(typeof(Skills), "OnDeath")] internal static class PvpSkillsOnDeathPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(Skills __instance) { Player player = __instance.m_player; if (!PvpSkillLossPatch.ShouldProtectSkills(player)) { return true; } Logger.LogInfo((object)("[PvPOverhaul] Blocked vanilla Skills.OnDeath for PvP death: " + player.GetPlayerName())); return false; } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class PvpPlayerOnDeathSkillRestorePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(Player __instance) { if (PvpSkillLossPatch.ShouldProtectSkills(__instance)) { PvpSkillLossPatch.Fo