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 PvPModes v1.0.0
PvpModes.dll
Decompiled 2 days agousing 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<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<long, float> _reportedDeaths = 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, "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<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)("[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<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)"[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<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)"[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<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(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<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; 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<int>("Bounty", "TriggerKillCount", 3, "Number of player kills required to activate a bounty."); BountyKillWindowMinutes = ((BaseUnityPlugin)this).Config.Bind<int>("Bounty", "KillWindowMinutes", 180, "Time window in minutes during which kills are counted for bounty activation."); BountyDurationMinutes = ((BaseUnityPlugin)this).Config.Bind<int>("Bounty", "DurationMinutes", 60, "How long the bounty stays active, in minutes."); BountyRewardItem = ((BaseUnityPlugin)this).Config.Bind<string>("Bounty", "RewardItem", "Coins", "Prefab name of the item rewarded when killing a bounty player."); BountyRewardAmountPerTier = ((BaseUnityPlugin)this).Config.Bind<int>("Bounty", "RewardAmountPerTier", 100, "Reward amount per bounty tier."); MsgKillBroadcast = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "KillBroadcast", "{killer} a tué {victim}", "Broadcast when a player kills another player."); MsgBountyActivated = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "BountyActivated", "{player} est maintenant recherché ! Tier {tier}", "Broadcast when a bounty starts."); MsgBountyRefreshed = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "BountyRefreshed", "{player} reste recherché ! Tier {tier} | Timer remis à {duration} min.", "Broadcast when bounty timer refreshes."); MsgBountyCleared = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "BountyCleared", "{player} n'est plus recherché.", "Broadcast when a bounty is cleared."); MsgBountyExpired = ((BaseUnityPlugin)this).Config.Bind<string>("Messages", "BountyExpired", "La prime de {player} a expiré.", "Broadcast when a bounty expires."); MsgBountyReward = ((BaseUnityPlugin)this).Config.Bind<string>("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; } } }