using System; 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.Managers; 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("PvpModes")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("PvpModes")] [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 PvpModes; 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; } private readonly List _kills = new List(); private readonly Dictionary _activeBounties = new Dictionary(); private readonly Dictionary _lastHits = new Dictionary(); private readonly Dictionary _reportedDeaths = new Dictionary(); 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, "PvpModes"); private string SaveFile => Path.Combine(SaveDir, "bounties.txt"); private string LogDir => Path.Combine(Paths.ConfigPath, "PvpModes", "Logs"); private TimeSpan KillWindow => TimeSpan.FromMinutes(PvpModes.BountyKillWindowMinutes.Value); private TimeSpan BountyDuration => TimeSpan.FromMinutes(PvpModes.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) { Logger.LogInfo((object)$"[PvpModes SERVER] Config check: trigger={PvpModes.BountyTriggerKillCount.Value}, window={PvpModes.BountyKillWindowMinutes.Value}, duration={PvpModes.BountyDurationMinutes.Value}"); 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) * PvpModes.BountyRewardAmountPerTier.Value; string value = PvpModes.BountyRewardItem.Value; BountyRpc.SendRewardToPlayer(killerName, value, num); _kills.RemoveAll((KillRecord x) => x.KillerId == killerName); string message = FormatMessage(PvpModes.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)$"[PvpModes SERVER] Kill registered: {killerName} -> {victimName} | kills={num2}"); WriteServerLog($"KILL | {killerName} killed {victimName} | kills={num2}"); BountyRpc.BroadcastCenterMessage(FormatMessage(PvpModes.MsgKillBroadcast.Value, killerName, victimName)); if (bounty == null && num2 >= PvpModes.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)("[PvpModes SERVER] BOUNTY ACTIVATED: " + playerName)); WriteServerLog($"BOUNTY_ACTIVATED | {playerName} | tier={tierFromKills} | end={dateTime:o}"); BountyRpc.BroadcastCenterMessage(FormatMessage(PvpModes.MsgBountyActivated.Value, "", "", playerName, tierFromKills)); } else { value.EndUtc = dateTime; value.Tier = tierFromKills; if (playerPosition != Vector3.zero) { value.LastKnownPosition = playerPosition; } Logger.LogInfo((object)("[PvpModes SERVER] BOUNTY REFRESHED: " + playerName)); WriteServerLog($"BOUNTY_REFRESHED | {playerName} | tier={tierFromKills} | end={dateTime:o}"); BountyRpc.BroadcastCenterMessage(FormatMessage(PvpModes.MsgBountyRefreshed.Value, "", "", playerName, tierFromKills, PvpModes.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)("[PvpModes SERVER] Bounty cleared: " + playerName)); WriteServerLog($"BOUNTY_CLEARED | {playerName} | killed={killed}"); if (killed) { BountyRpc.BroadcastCenterMessage(FormatMessage(PvpModes.MsgBountyCleared.Value, "", "", playerName)); } else { BountyRpc.BroadcastCenterMessage(FormatMessage(PvpModes.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)("[PvpModes 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) { _lastHits[victimId] = new LastHit { VictimName = victimName, AttackerName = attackerName, Damage = damage, Time = Time.time }; } public LastHit ResolveLastHit(long victimId) { if (!_lastHits.TryGetValue(victimId, out var value)) { return null; } if (Time.time - value.Time > 5f) { return null; } return value; } public void TryReportDeath(Player victim) { long playerID = victim.GetPlayerID(); if (!_reportedDeaths.TryGetValue(playerID, out var value) || !(Time.time - value < 10f)) { LastHit lastHit = ResolveLastHit(playerID); if (lastHit != null) { _reportedDeaths[playerID] = Time.time; Logger.LogInfo((object)("[PvpModes CLIENT] Reporting kill to server: " + lastHit.AttackerName + " -> " + victim.GetPlayerName())); BountyRpc.SendKillToServer(lastHit.AttackerName, victim.GetPlayerName()); } } } private void SaveBounties() { Directory.CreateDirectory(SaveDir); List list = new List(); 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)("[PvpModes 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(); } } 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("PvpModes_RegisterKill", (Action)RPC_RegisterKill); ZRoutedRpc.instance.Register("PvpModes_BroadcastMessage", (Action)RPC_BroadcastMessage); ZRoutedRpc.instance.Register("PvpModes_BountyCircle", (Action)RPC_BountyCircle); ZRoutedRpc.instance.Register("PvpModes_BountyPosition", (Action)RPC_BountyPosition); ZRoutedRpc.instance.Register("PvpModes_BountyStatusRequest", (Action)RPC_BountyStatusRequest); ZRoutedRpc.instance.Register("PvpModes_BountyStatusResponse", (Action)RPC_BountyStatusResponse); ZRoutedRpc.instance.Register("PvpModes_BountyResetRequest", (Action)RPC_BountyResetRequest); ZRoutedRpc.instance.Register("PvpModes_BountyResetResponse", (Action)RPC_BountyResetResponse); ZRoutedRpc.instance.Register("PvpModes_BountyReward", (Action)RPC_BountyReward); _registered = true; Logger.LogInfo((object)"[PvpModes] 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("[PvpModes] 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("[PvpModes] 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)$"[PvpModes 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)("[PvpModes 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)("[PvpModes 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)$"[PvpModes 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)$"[PvpModes 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)("[PvpModes 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)("[PvpModes] 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 _circles = new Dictionary(); 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 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()); ((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)"[PvpModes] SoftDeath icon not found"); } ObjectDB.instance.m_StatusEffects.Add((StatusEffect)(object)val); _registered = true; Logger.LogInfo((object)"[PvpModes] 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(); 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(Character), "Damage")] internal static class TrackLastHit { private static void Prefix(Character __instance, HitData hit) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val == null) { return; } Character attacker = hit.GetAttacker(); Player val2 = (Player)(object)((attacker is Player) ? attacker : null); if (val2 != null && !((Object)(object)val2 == (Object)(object)val)) { float health = ((Character)val).GetHealth(); float totalDamage = hit.GetTotalDamage(); BountySystem.Instance.SetLastHit(val.GetPlayerID(), val.GetPlayerName(), val2.GetPlayerName(), totalDamage); if (totalDamage >= health) { BountySystem.Instance.TryReportDeath(val); } } } } [BepInPlugin("dzk.pvpmodes", "PvpModes", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] internal class PvpModes : BaseUnityPlugin { public const string PluginGUID = "dzk.pvpmodes"; public const string PluginName = "PvpModes"; public const string PluginVersion = "1.0.0"; public static PvpModes Instance; public static CustomLocalization Localization; public static ConfigEntry BountyTriggerKillCount; public static ConfigEntry BountyKillWindowMinutes; public static ConfigEntry BountyDurationMinutes; public static ConfigEntry BountyRewardItem; public static ConfigEntry BountyRewardAmountPerTier; public static ConfigEntry MsgKillBroadcast; public static ConfigEntry MsgBountyActivated; public static ConfigEntry MsgBountyRefreshed; public static ConfigEntry MsgBountyCleared; public static ConfigEntry MsgBountyExpired; public static ConfigEntry MsgBountyReward; 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)"PvpModes is loading..."); Harmony val = new Harmony("dzk.pvpmodes"); val.PatchAll(); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new BountyStatusCommand()); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new BountyResetCommand()); Logger.LogInfo((object)"PvpModes loaded successfully."); } private void Update() { BountyRpc.Update(); BountySystem.Instance.Update(); BountyMapCircle.Update(); BountyStatusEffectController.UpdateRegistration(); BountyRpc.UpdateReward(); } private void SetupConfig() { BountyTriggerKillCount = ((BaseUnityPlugin)this).Config.Bind("Bounty", "TriggerKillCount", 3, "Number of player kills required to activate a bounty."); BountyKillWindowMinutes = ((BaseUnityPlugin)this).Config.Bind("Bounty", "KillWindowMinutes", 180, "Time window in minutes during which kills are counted for bounty activation."); BountyDurationMinutes = ((BaseUnityPlugin)this).Config.Bind("Bounty", "DurationMinutes", 60, "How long the bounty stays active, in minutes."); BountyRewardItem = ((BaseUnityPlugin)this).Config.Bind("Bounty", "RewardItem", "Coins", "Prefab name of the item rewarded when killing a bounty player."); BountyRewardAmountPerTier = ((BaseUnityPlugin)this).Config.Bind("Bounty", "RewardAmountPerTier", 100, "Reward amount per bounty tier."); MsgKillBroadcast = ((BaseUnityPlugin)this).Config.Bind("Messages", "KillBroadcast", "{killer} a tué {victim}", "Broadcast when a player kills another player."); MsgBountyActivated = ((BaseUnityPlugin)this).Config.Bind("Messages", "BountyActivated", "{player} est maintenant recherché ! Tier {tier}", "Broadcast when a bounty starts."); MsgBountyRefreshed = ((BaseUnityPlugin)this).Config.Bind("Messages", "BountyRefreshed", "{player} reste recherché ! Tier {tier} | Timer remis à {duration} min.", "Broadcast when bounty timer refreshes."); MsgBountyCleared = ((BaseUnityPlugin)this).Config.Bind("Messages", "BountyCleared", "{player} n'est plus recherché.", "Broadcast when a bounty is cleared."); MsgBountyExpired = ((BaseUnityPlugin)this).Config.Bind("Messages", "BountyExpired", "La prime de {player} a expiré.", "Broadcast when a bounty expires."); MsgBountyReward = ((BaseUnityPlugin)this).Config.Bind("Messages", "BountyReward", "{killer} a éliminé le joueur recherché {victim} et gagne {amount}x {item} !", "Broadcast when a bounty is claimed."); } 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)"[PvpModes] 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)"[PvpModes] Config reloaded"); Logger.LogInfo((object)("[PvpModes] Bounty config: " + $"trigger={BountyTriggerKillCount.Value}, " + $"window={BountyKillWindowMinutes.Value}, " + $"duration={BountyDurationMinutes.Value}, " + $"reward={BountyRewardAmountPerTier.Value}x {BountyRewardItem.Value}")); } catch (Exception arg) { Logger.LogWarning((object)$"[PvpModes] Failed to reload config: {arg}"); } } private void OnDestroy() { if (_configWatcher != null) { _configWatcher.Changed -= OnConfigFileChanged; _configWatcher.Created -= OnConfigFileChanged; _configWatcher.Renamed -= OnConfigFileChanged; _configWatcher.Dispose(); _configWatcher = null; } } }