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<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;
}
}
}