using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using DooDesch.FullHouse; using FishNet; using FishNet.Managing; using FishNet.Object; using FishySteamworks; using HarmonyLib; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using MultiplayerPVP; using MultiplayerPVP.Addons; using MultiplayerPVP.Compat; using MultiplayerPVP.Config; using MultiplayerPVP.Core; using MultiplayerPVP.Modes; using MultiplayerPVP.Net; using MultiplayerPVP.UI; using ScheduleOne; using ScheduleOne.Cartel; using ScheduleOne.Combat; using ScheduleOne.Core.Items.Framework; using ScheduleOne.DevUtilities; using ScheduleOne.Doors; using ScheduleOne.Economy; using ScheduleOne.Equipping; using ScheduleOne.FX; using ScheduleOne.GameTime; using ScheduleOne.ItemFramework; using ScheduleOne.Law; using ScheduleOne.Map; using ScheduleOne.Money; using ScheduleOne.NPCs; using ScheduleOne.NPCs.Behaviour; using ScheduleOne.Networking; using ScheduleOne.PlayerScripts; using ScheduleOne.PlayerScripts.Health; using ScheduleOne.Police; using ScheduleOne.Product; using ScheduleOne.Product.Packaging; using ScheduleOne.Storage; using ScheduleOne.UI; using ScheduleOne.UI.Multiplayer; using ScheduleOne.Vision; using SteamNetworkLib; using SteamNetworkLib.Events; using SteamNetworkLib.Models; using SteamNetworkLib.Sync; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.AI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("MultiplayerPVP")] [assembly: AssemblyDescription("Schedule I MelonLoader Mod")] [assembly: AssemblyCompany("Virtunerd")] [assembly: AssemblyProduct("MultiplayerPVP")] [assembly: AssemblyCopyright("Copyright (c) Virtunerd 2026")] [assembly: ComVisible(false)] [assembly: Guid("f83b02a6-396b-4b34-b8b7-3d0117d8d960")] [assembly: MelonAdditionalDependencies(new string[] { "SteamNetworkLib" })] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: MelonInfo(typeof(MultiplayerPVPMod), "MultiplayerPVP", "1.0.6", "Virtunerd", null)] [assembly: MelonGame(null, "Schedule I")] [assembly: AssemblyFileVersion("1.0.6")] [assembly: AssemblyInformationalVersion("1.0.6")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.6.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace DooDesch.FullHouse { internal static class Lobbies { internal const int DefaultCapacity = 32; private const int HardMax = 250; private const int SafeMax = 32; private const string GuardName = "___DooDesch_FullHouse___"; private static bool _installed; private static MelonPreferences_Entry _capEntry; private static int _hostCap; internal static int Capacity { get { int num = ((_capEntry != null) ? _capEntry.Value : 32); if (num < 2) { num = 2; } if (num > 250) { num = 250; } return num; } } internal static int EffectiveCap => Math.Min(250, Math.Max(Capacity, _hostCap)); internal static ulong CurrentLobbyId { get { try { SteamLobbyService val = Service(); return (val != null) ? val._lobbyID : 0; } catch { return 0uL; } } } internal static int SeatCount { get { try { CSteamID[] array = Service()?._players; return (array != null) ? array.Length : 0; } catch { return 0; } } } internal static void Install() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Expected O, but got Unknown if (_installed) { return; } _installed = true; try { if ((Object)(object)GameObject.Find("___DooDesch_FullHouse___") != (Object)null) { MelonLogger.Msg("[FullHouse] another copy is already active - standing down."); return; } GameObject val = new GameObject("___DooDesch_FullHouse___"); Object.DontDestroyOnLoad((Object)(object)val); ((Object)val).hideFlags = (HideFlags)61; } catch { } try { MelonPreferences_Category val2 = MelonPreferences.CreateCategory("FullHouse", "FullHouse"); _capEntry = val2.CreateEntry("Capacity", 32, "Max lobby players", "Maximum co-op players FullHouse seats (2-250). Values above 32 are unsupported and may destabilise the game.", false, false, (ValueValidator)null, (string)null); } catch (Exception ex) { MelonLogger.Warning("[FullHouse] preference registration failed: " + ex.Message); } if (Capacity > 32) { MelonLogger.Warning($"[FullHouse] capacity {Capacity} exceeds the tested maximum of {32} - expect instability."); } try { Harmony h = new Harmony("com.doodesch.fullhouse"); Patch(h, typeof(SteamLobbyService), "Initialize", null, "Service_Initialize_Postfix"); Patch(h, typeof(SteamLobbyService), "UpdateLobbyMembers", "Service_UpdateLobbyMembers_Prefix"); Patch(h, typeof(SteamLobbyService), "CreateLobby", "Service_CreateLobby_Prefix"); Patch(h, typeof(SteamLobbyService), "OnLobbyCreated", null, "Service_OnLobbyCreated_Postfix"); Patch(h, typeof(SteamLobbyService), "OnLobbyEntered", null, "Service_OnLobbyEntered_Postfix"); Patch(h, typeof(SteamLobbyService), "OpenInviteUI", "Service_OpenInviteUI_Prefix"); Patch(h, typeof(SteamLobbyService), "LeaveLobby", null, "Service_LeaveLobby_Postfix"); Patch(h, typeof(LobbyInterface), "Start", null, "LobbyInterface_Start_Postfix"); Patch(h, typeof(LobbyInterface), "UpdateUI", null, "LobbyInterface_UpdateUI_Postfix"); Patch(h, typeof(LobbyInterface), "UpdateButtons", null, "LobbyInterface_UpdateButtons_Postfix"); MelonLogger.Msg($"[FullHouse] active - lobby cap raised to {Capacity}."); } catch (Exception ex2) { MelonLogger.Error("[FullHouse] patch install failed: " + ex2); } } private static void Patch(Harmony h, Type type, string method, string prefix = null, string postfix = null) { MethodInfo methodInfo = AccessTools.Method(type, method, (Type[])null, (Type[])null); if (methodInfo == null) { MelonLogger.Warning("[FullHouse] " + type.Name + "." + method + " not found - skipped."); } else { h.Patch((MethodBase)methodInfo, Hook(prefix), Hook(postfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static HarmonyMethod Hook(string name) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) return (name == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Lobbies).GetMethod(name, AccessTools.all)); } private static SteamLobbyService Service() { try { ILobbyService val = Singleton.Instance?._lobbyService; if (val == null) { return null; } return (SteamLobbyService)(object)((val is SteamLobbyService) ? val : null); } catch { return null; } } private static void EnsurePlayers(SteamLobbyService svc, int target) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) try { if (svc == null || target < 2) { return; } CSteamID[] players = svc._players; int num = ((players != null) ? players.Length : 0); if (num < target) { CSteamID[] array = (CSteamID[])(object)new CSteamID[target]; for (int i = 0; i < num; i++) { array[i] = players[i]; } svc._players = array; } } catch (Exception ex) { MelonLogger.Warning("[FullHouse] resizing the lobby seat array failed: " + ex.Message); } } private static void Service_Initialize_Postfix(SteamLobbyService __instance) { EnsurePlayers(__instance, EffectiveCap); RaiseTransportCap(); } private static void RaiseTransportCap() { try { FishySteamworks val = Object.FindObjectOfType(); if (!((Object)(object)val == (Object)null)) { int effectiveCap = EffectiveCap; ushort maximumClients = val._maximumClients; if (maximumClients < effectiveCap) { val._maximumClients = (ushort)effectiveCap; MelonLogger.Msg($"[FullHouse] transport client limit {maximumClients} -> {effectiveCap}."); } } } catch (Exception ex) { MelonLogger.Warning("[FullHouse] raising the transport client limit failed: " + ex.Message); } } private static void Service_UpdateLobbyMembers_Prefix(SteamLobbyService __instance) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) try { int val = 0; try { ulong num = ((__instance != null) ? __instance._lobbyID : 0); if (num != 0) { val = SteamMatchmaking.GetNumLobbyMembers(new CSteamID(num)); } } catch { } EnsurePlayers(__instance, Math.Max(EffectiveCap, val)); } catch (Exception ex) { MelonLogger.Warning("[FullHouse] seat guard failed: " + ex.Message); } } private static void Service_CreateLobby_Prefix(ref int maxPlayers) { int effectiveCap = EffectiveCap; if (maxPlayers < effectiveCap) { maxPlayers = effectiveCap; } } private static void Service_OnLobbyCreated_Postfix(LobbyCreated_t result) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) try { if ((int)result.m_eResult == 1) { RaiseTransportCap(); CSteamID val = (CSteamID)result.m_ulSteamIDLobby; int effectiveCap = EffectiveCap; if (SteamMatchmaking.GetLobbyMemberLimit(val) < effectiveCap) { SteamMatchmaking.SetLobbyMemberLimit(val, effectiveCap); } int lobbyMemberLimit = SteamMatchmaking.GetLobbyMemberLimit(val); if (lobbyMemberLimit < effectiveCap) { MelonLogger.Warning($"[FullHouse] Steam kept the lobby member limit at {lobbyMemberLimit} (requested {effectiveCap}); advertising {lobbyMemberLimit}."); } SteamMatchmaking.SetLobbyData(val, "max_players", lobbyMemberLimit.ToString()); SteamMatchmaking.SetLobbyData(val, "num_slots", lobbyMemberLimit.ToString()); } } catch (Exception ex) { MelonLogger.Warning("[FullHouse] OnLobbyCreated failed: " + ex.Message); } } private static void Service_OnLobbyEntered_Postfix(SteamLobbyService __instance, LobbyEnter_t result) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_003e: Unknown result type (might be due to invalid IL or missing references) try { if (__instance == null || __instance._lobbyID == 0) { return; } CSteamID val = (CSteamID)result.m_ulSteamIDLobby; int result2 = 0; int.TryParse(SteamMatchmaking.GetLobbyData(val, "max_players"), out result2); int lobbyMemberLimit = SteamMatchmaking.GetLobbyMemberLimit(val); if (lobbyMemberLimit > 0 && result2 > lobbyMemberLimit) { result2 = lobbyMemberLimit; } if (result2 > _hostCap) { if (result2 > 32) { MelonLogger.Warning($"[FullHouse] this lobby seats {result2}, past the tested maximum of {32} - expect instability."); } _hostCap = result2; int effectiveCap = EffectiveCap; EnsurePlayers(__instance, effectiveCap); EnsureSlots(Singleton.Instance, effectiveCap); MelonLogger.Msg($"[FullHouse] adopted host lobby cap {effectiveCap}."); } } catch (Exception ex) { MelonLogger.Warning("[FullHouse] host-cap sync failed: " + ex.Message); } } private static void Service_LeaveLobby_Postfix() { if (_hostCap != 0) { _hostCap = 0; MelonLogger.Msg($"[FullHouse] left the lobby - back to the local cap of {Capacity}."); } } private static bool Service_OpenInviteUI_Prefix(SteamLobbyService __instance) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) try { if (__instance == null) { return true; } if (__instance._lobbyID == 0) { __instance.CreateLobby(EffectiveCap); return false; } CSteamID val = default(CSteamID); ((CSteamID)(ref val))..ctor(__instance._lobbyID); int num = EffectiveCap; int lobbyMemberLimit = SteamMatchmaking.GetLobbyMemberLimit(val); if (lobbyMemberLimit > 0 && lobbyMemberLimit < num) { num = lobbyMemberLimit; } if (SteamMatchmaking.GetNumLobbyMembers(val) >= num) { MelonLogger.Warning($"[FullHouse] lobby already at max capacity ({num})."); return false; } SteamFriends.ActivateGameOverlayInviteDialog(val); return false; } catch (Exception ex) { MelonLogger.Warning("[FullHouse] invite gate failed, falling back to vanilla: " + ex.Message); return true; } } private static void LobbyInterface_Start_Postfix(LobbyInterface __instance) { try { MelonCoroutines.Start(BuildUi(__instance)); } catch (Exception ex) { MelonLogger.Warning("[FullHouse] UI coroutine start failed: " + ex.Message); } } private static void LobbyInterface_UpdateUI_Postfix(LobbyInterface __instance) { try { Lobby instance = Singleton.Instance; int num = (((Object)(object)instance != (Object)null) ? instance.PlayerCount : 0); if ((Object)(object)__instance?.LobbyTitle != (Object)null) { ((TMP_Text)__instance.LobbyTitle).text = "Lobby (" + num + "/" + EffectiveCap + ")"; } } catch (Exception ex) { MelonLogger.Warning("[FullHouse] title sync failed: " + ex.Message); } } [HarmonyPriority(600)] private static void LobbyInterface_UpdateButtons_Postfix(LobbyInterface __instance) { try { Lobby instance = Singleton.Instance; if (!((Object)(object)instance == (Object)null) && !((Object)(object)__instance?.InviteButton == (Object)null)) { ((Component)__instance.InviteButton).gameObject.SetActive(instance.IsHost && instance.PlayerCount < EffectiveCap); } } catch (Exception ex) { MelonLogger.Warning("[FullHouse] invite button sync failed: " + ex.Message); } } private static IEnumerator BuildUi(LobbyInterface ui) { while ((Object)(object)Singleton.Instance == (Object)null) { yield return null; } bool steamReady = true; try { steamReady = SteamManager.Initialized; } catch { } if (steamReady) { EnsurePlayers(Service(), EffectiveCap); EnsureSlots(ui, EffectiveCap); } } private static void EnsureSlots(LobbyInterface ui, int target) { if ((Object)(object)ui == (Object)null) { return; } try { RectTransform[] playerSlots = ui.PlayerSlots; if (playerSlots == null || playerSlots.Length == 0) { MelonLogger.Warning("[FullHouse] lobby PlayerSlots not found."); return; } int num = playerSlots.Length; if (num >= target) { RefreshPlayers(ui); return; } RectTransform val = playerSlots[num - 1]; Transform parent = ((Transform)val).parent; RectTransform[] array = (RectTransform[])(object)new RectTransform[target]; for (int i = 0; i < num; i++) { array[i] = playerSlots[i]; } for (int j = num; j < target; j++) { GameObject val2 = Object.Instantiate(((Component)val).gameObject, parent, false); ((Object)val2).name = ((Object)((Component)val).gameObject).name + " (" + j + ")"; array[j] = val2.GetComponent(); } ui.PlayerSlots = array; RefreshPlayers(ui); } catch (Exception ex) { MelonLogger.Warning("[FullHouse] building lobby UI failed: " + ex.Message); } } private static void RefreshPlayers(LobbyInterface ui) { try { AccessTools.Method(typeof(LobbyInterface), "UpdatePlayers", (Type[])null, (Type[])null)?.Invoke(ui, null); } catch { } } } } namespace MultiplayerPVP { internal static class ImguiCompat { private static GUIStyle _style; internal static void DrawTexture(Rect position, Texture2D image) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Blit(position, image); } internal static void DrawTexture(Rect position, Texture2D image, ScaleMode scaleMode) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) Blit(Fit(position, image, scaleMode), image); } private static Rect Fit(Rect position, Texture2D image, ScaleMode scaleMode) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 //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_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Invalid comparison between Unknown and I4 //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)image == (Object)null || (int)scaleMode == 0) { return position; } float num = ((Texture)image).width; float num2 = ((Texture)image).height; if (num <= 0f || num2 <= 0f) { return position; } float num3 = (((int)scaleMode == 2) ? Mathf.Min(((Rect)(ref position)).width / num, ((Rect)(ref position)).height / num2) : Mathf.Max(((Rect)(ref position)).width / num, ((Rect)(ref position)).height / num2)); float num4 = num * num3; float num5 = num2 * num3; return new Rect(((Rect)(ref position)).x + (((Rect)(ref position)).width - num4) * 0.5f, ((Rect)(ref position)).y + (((Rect)(ref position)).height - num5) * 0.5f, num4, num5); } private static void Blit(Rect position, Texture2D image) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown if ((Object)(object)image == (Object)null) { return; } Event current = Event.current; if (current != null && (int)current.type == 7) { if (_style == null) { _style = new GUIStyle(); } _style.normal.background = image; Color backgroundColor = GUI.backgroundColor; GUI.backgroundColor = Color.white; _style.Draw(position, false, false, false, false); GUI.backgroundColor = backgroundColor; } } } public class MultiplayerPVPMod : MelonMod { private MatchController _controller; private PvpHud _hud; private PvpNet _net; internal static MultiplayerPVPMod Instance { get; private set; } internal MatchController Controller => _controller; internal PvpNet Net => _net; public override void OnInitializeMelon() { Instance = this; Lobbies.Install(); PvpConfig.Load(); _net = new PvpNet(); _net.Init(); _net.OnSighting += SightingRelay.OnReceived; _controller = new MatchController(_net); _hud = new PvpHud(); PvpAddonManager.Init(_net); RiotChaos.Init(); MelonLogger.Msg($"[MultiplayerPVP] v{((MelonBase)this).Info.Version} ready. Press {(object)(KeyCode)PvpConfig.PanelKey.Value} in-game to open the host panel."); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { try { RiotChaos.OnSceneLoaded(); } catch { } try { _net?.Init(); _controller?.Cleanup(); _hud?.Reset(); SightingRelay.Reset(); ViewLock.Release(); } catch (Exception ex) { MelonLogger.Warning("OnSceneWasLoaded: " + ex.Message); } try { PvpAddonManager.SceneLoaded(buildIndex, sceneName); } catch (Exception ex2) { MelonLogger.Warning("OnSceneWasLoaded(addons): " + ex2.Message); } } public override void OnDeinitializeMelon() { try { RiotChaos.Deinit(); } catch { } try { PvpAddonManager.Deinit(); } catch { } try { ViewLock.Release(); _controller?.Cleanup(); _net?.Dispose(); } catch (Exception ex) { MelonLogger.Warning("OnDeinitializeMelon: " + ex.Message); } } public override void OnUpdate() { if (_controller == null) { return; } try { if (Input.GetKeyDown((KeyCode)PvpConfig.PanelKey.Value)) { if (_controller.Phase == MatchPhase.Idle) { _hud.PanelOpen = !_hud.PanelOpen; } else if (_controller.Phase == MatchPhase.Active && !_controller.IsRemote) { _hud.EndPromptOpen = !_hud.EndPromptOpen; } } if (_controller.Phase != MatchPhase.Active) { _hud.EndPromptOpen = false; } try { Player local = Player.Local; if ((Object)(object)local != (Object)null) { _net?.SetLocalIdentity(local.PlayerCode); } } catch { } _net?.Tick(); _controller.Tick(Time.deltaTime); PvpAddonManager.Tick(Time.deltaTime, _controller.Phase, _controller.ModeType, _controller.Day); RiotChaos.Tick(Time.deltaTime, _controller); UnlimitedAmmo.Tick(_controller); DoubleJump.Tick(_controller); bool locked = _hud.PanelOpen || _hud.EndPromptOpen || (_controller.Phase == MatchPhase.EndOfDay && !_controller.IsRemote); ViewLock.Set(locked); } catch (Exception ex) { MelonLogger.Warning("OnUpdate: " + ex.Message); } } public override void OnGUI() { if (_hud == null) { return; } try { _hud.Draw(_controller); } catch (Exception ex) { MelonLogger.Warning("OnGUI: " + ex.Message); } try { PvpAddonManager.DrawGui(); } catch (Exception ex2) { MelonLogger.Warning("OnGUI(addons): " + ex2.Message); } } } } namespace MultiplayerPVP.UI { internal class PvpHud { private struct DispRow { public int ColorIdx; public string Text; public int Kills; public int Downs; public int Arrests; public int BagsCap; public int BagsLost; public int WpnLost; public int WpnWon; public int PlayerIndex; public string Days; } internal bool PanelOpen; internal bool EndPromptOpen; private static Texture2D _white; private readonly List _markers = new List(); private Camera _cam; private int _camRevalidate; private int _hoverMode = -1; private PvpMatchToggle _hoverToggle; private readonly List _rows = new List(); private static Texture2D White { get { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_white == (Object)null) { _white = new Texture2D(1, 1); _white.SetPixel(0, 0, Color.white); _white.Apply(); } return _white; } } private static string FormatDays(string dotted) { return string.IsNullOrEmpty(dotted) ? "" : dotted.Replace(".", ", "); } internal void Reset() { _cam = null; _camRevalidate = 0; _markers.Clear(); PanelOpen = false; EndPromptOpen = false; } internal void Draw(MatchController mc) { //IL_0147: Unknown result type (might be due to invalid IL or missing references) if (mc == null) { return; } Safe(delegate { DrawBanner(mc); }); Safe(delegate { DrawLocalBanner(mc); }); switch (mc.Phase) { case MatchPhase.Countdown: case MatchPhase.Active: { bool flag = true; Safe(delegate { DrawCountdown(mc); }); Safe(delegate { DrawScoreboard(mc, 220f); }); Safe(delegate { DrawMarkers(mc); }); if (mc.IsRemote) { Safe(delegate { DrawClientModeCard(mc); }); } break; } case MatchPhase.EndOfDay: Safe(delegate { DrawEndOfDayMenu(mc); }); break; case MatchPhase.Idle: if (PanelOpen) { Safe(delegate { DrawHostPanel(mc); }); } break; } if (EndPromptOpen && mc.Phase == MatchPhase.Active && !mc.IsRemote) { Safe(delegate { DrawEndMatchPrompt(mc); }); } GUI.color = Color.white; } private static void Safe(Action draw) { try { draw(); } catch (Exception ex) { MelonLogger.Warning("HUD draw: " + ex.Message); } } private void DrawBanner(MatchController mc) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) if (mc.Announcer.HasBanner) { GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 40, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(0f, (float)Screen.height * 0.14f, (float)Screen.width, 60f); GUI.color = Color.black; GUI.Label(new Rect(((Rect)(ref val2)).x + 2f, ((Rect)(ref val2)).y + 2f, ((Rect)(ref val2)).width, ((Rect)(ref val2)).height), mc.Announcer.Banner, val); GUI.color = new Color(1f, 0.85f, 0.2f); GUI.Label(val2, mc.Announcer.Banner, val); GUI.color = Color.white; } } private void DrawLocalBanner(MatchController mc) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) if (mc.Announcer.HasLocalBanner) { GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 26, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(0f, (float)Screen.height * 0.21f, (float)Screen.width, 44f); GUI.color = Color.black; GUI.Label(new Rect(((Rect)(ref val2)).x + 2f, ((Rect)(ref val2)).y + 2f, ((Rect)(ref val2)).width, ((Rect)(ref val2)).height), mc.Announcer.LocalBanner, val); GUI.color = mc.Announcer.LocalBannerColor; GUI.Label(val2, mc.Announcer.LocalBanner, val); GUI.color = Color.white; } } private void DrawCountdown(MatchController mc) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) string text; if (mc.Phase == MatchPhase.Countdown) { text = "Starting…"; } else { int num = Mathf.Max(0, Mathf.CeilToInt(mc.RemainingSeconds)); text = $"{GameModeInfo.Title(mc.ModeType)} · Day {mc.Day} · {num / 60:0}:{num % 60:00}"; } GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 20, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; Rect val2 = default(Rect); ((Rect)(ref val2))..ctor((float)Screen.width * 0.5f - 220f, 70f, 440f, 28f); GUI.color = new Color(0f, 0f, 0f, 0.55f); ImguiCompat.DrawTexture(val2, White); GUI.color = Color.white; GUI.Label(val2, text, val); } private void DrawLoadoutCard(Rect anchor) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Expected O, but got Unknown //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Unknown result type (might be due to invalid IL or missing references) //IL_02bc: Unknown result type (might be due to invalid IL or missing references) List list = StartingLoadout.Buttons(); float num = 210f; float num2 = Mathf.Max(((Rect)(ref anchor)).height, 64f + (float)list.Count * 24f + 60f); Rect position = default(Rect); ((Rect)(ref position))..ctor(((Rect)(ref anchor)).x - num - 10f, ((Rect)(ref anchor)).y, num, num2); GUI.color = new Color(0.05f, 0.05f, 0.08f, 0.92f); ImguiCompat.DrawTexture(position, White); GUI.color = Color.white; GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 15, fontStyle = (FontStyle)1 }; GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 11, wordWrap = true }; GUI.Label(new Rect(((Rect)(ref position)).x + 10f, ((Rect)(ref position)).y + 8f, num - 20f, 20f), "STARTING LOADOUT", val); GUI.Label(new Rect(((Rect)(ref position)).x + 10f, ((Rect)(ref position)).y + 28f, num - 20f, 30f), "Every player gets the checked items at round start.", val2); float num3 = ((Rect)(ref position)).y + 62f; for (int i = 0; i < list.Count; i++) { string id = list[i]; bool flag = StartingLoadout.IsChecked(id); bool flag2 = GUI.Toggle(new Rect(((Rect)(ref position)).x + 12f, num3, num - 24f, 22f), flag, " " + StartingLoadout.LabelOf(id)); if (flag2 != flag) { StartingLoadout.SetChecked(id, flag2); } num3 += 24f; } num3 += 6f; GUI.color = new Color(1f, 1f, 1f, 0.15f); ImguiCompat.DrawTexture(new Rect(((Rect)(ref position)).x + 10f, num3, num - 20f, 1f), White); GUI.color = Color.white; num3 += 6f; bool flag3 = UnlimitedAmmo.Checked; bool flag4 = GUI.Toggle(new Rect(((Rect)(ref position)).x + 12f, num3, num - 24f, 22f), flag3, " unlimited ammo (this round)"); if (flag4 != flag3) { UnlimitedAmmo.SetChecked(flag4); } num3 += 24f; bool flag5 = DoubleJump.Checked; bool flag6 = GUI.Toggle(new Rect(((Rect)(ref position)).x + 12f, num3, num - 24f, 22f), flag5, " double jump (this round)"); if (flag6 != flag5) { DoubleJump.SetChecked(flag6); } } private void DrawModeInfoCard(Rect anchor) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) Rect position = default(Rect); ((Rect)(ref position))..ctor(((Rect)(ref anchor)).xMax + 10f, ((Rect)(ref anchor)).y, 240f, ((Rect)(ref anchor)).height); GUI.color = new Color(0.05f, 0.05f, 0.08f, 0.92f); ImguiCompat.DrawTexture(position, White); GUI.color = Color.white; GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 15, fontStyle = (FontStyle)1, wordWrap = true }; GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 12, wordWrap = true }; if (_hoverToggle != null) { GUI.Label(new Rect(((Rect)(ref position)).x + 10f, ((Rect)(ref position)).y + 10f, 220f, 44f), _hoverToggle.Label + " (modifier)", val); GUI.Label(new Rect(((Rect)(ref position)).x + 10f, ((Rect)(ref position)).y + 48f, 220f, ((Rect)(ref position)).height - 58f), _hoverToggle.Description, val2); } else if (_hoverMode >= 0) { GameModeType hoverMode = (GameModeType)_hoverMode; GUI.Label(new Rect(((Rect)(ref position)).x + 10f, ((Rect)(ref position)).y + 10f, 220f, 44f), GameModeInfo.Title(hoverMode), val); GUI.Label(new Rect(((Rect)(ref position)).x + 10f, ((Rect)(ref position)).y + 48f, 220f, ((Rect)(ref position)).height - 58f), GameModeInfo.Description(hoverMode), val2); } else { GUI.Label(new Rect(((Rect)(ref position)).x + 10f, ((Rect)(ref position)).y + 10f, 220f, ((Rect)(ref position)).height - 20f), "Hover a mode button to see what it does.", val2); } } private void DrawClientModeCard(MatchController mc) { //IL_0030: 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) //IL_0047: 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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) Rect position = default(Rect); ((Rect)(ref position))..ctor(12f, 70f, 290f, 116f); GUI.color = new Color(0f, 0f, 0f, 0.6f); ImguiCompat.DrawTexture(position, White); GUI.color = Color.white; GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 15, fontStyle = (FontStyle)1, wordWrap = true }; GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 12, wordWrap = true }; GUI.Label(new Rect(((Rect)(ref position)).x + 10f, ((Rect)(ref position)).y + 8f, 270f, 22f), GameModeInfo.Title(mc.ModeType), val); GUI.Label(new Rect(((Rect)(ref position)).x + 10f, ((Rect)(ref position)).y + 32f, 270f, 76f), GameModeInfo.Description(mc.ModeType), val2); } private void BuildRows(MatchController mc) { _rows.Clear(); if (mc.IsRemote ? (mc.RemoteTeamRows.Count > 0) : mc.IsTeamScoredMode) { if (mc.IsRemote) { for (int i = 0; i < mc.RemoteTeamRows.Count; i++) { SnapTeamRow snapTeamRow = mc.RemoteTeamRows[i]; _rows.Add(new DispRow { ColorIdx = snapTeamRow.Team, Text = snapTeamRow.Name + " — " + snapTeamRow.Score, Kills = snapTeamRow.Kills, Downs = snapTeamRow.Downs, Arrests = snapTeamRow.Arrests, BagsCap = snapTeamRow.BagsCaptured, BagsLost = snapTeamRow.BagsLost, WpnLost = snapTeamRow.WeaponsLost, WpnWon = snapTeamRow.WeaponsWon, PlayerIndex = -1 }); } return; } int activeTeamCount = mc.ActiveTeamCount; List players = mc.Registry.Players; for (int j = 0; j < activeTeamCount; j++) { int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; for (int k = 0; k < players.Count; k++) { PvpPlayer pvpPlayer = players[k]; if (pvpPlayer.Team == j) { num += pvpPlayer.Kills; num2 += pvpPlayer.Downs; num3 += pvpPlayer.Arrests; num4 += pvpPlayer.BagsCaptured; num5 += pvpPlayer.BagsLost; num6 += pvpPlayer.WeaponsLost; num7 += pvpPlayer.WeaponsWon; } } string text = ""; try { if (mc.CurrentMode != null) { text = mc.CurrentMode.TeamScoreText(mc.Context, j); } } catch { } _rows.Add(new DispRow { ColorIdx = j, Text = mc.Registry.TeamLabel(j) + " — " + text, Kills = num, Downs = num2, Arrests = num3, BagsCap = num4, BagsLost = num5, WpnLost = num6, WpnWon = num7, PlayerIndex = -1 }); } return; } if (mc.IsRemote) { for (int l = 0; l < mc.RemoteRows.Count; l++) { SnapRow snapRow = mc.RemoteRows[l]; _rows.Add(new DispRow { ColorIdx = snapRow.Index, Text = snapRow.Name + " — " + snapRow.Score, Kills = snapRow.Kills, Downs = snapRow.Downs, Arrests = snapRow.Arrests, BagsCap = snapRow.BagsCaptured, BagsLost = snapRow.BagsLost, WpnLost = snapRow.WeaponsLost, WpnWon = snapRow.WeaponsWon, PlayerIndex = snapRow.Index, Days = FormatDays(snapRow.History) }); } return; } List players2 = mc.Registry.Players; for (int m = 0; m < players2.Count; m++) { PvpPlayer pvpPlayer2 = players2[m]; string text2 = ""; string text3 = pvpPlayer2.Name; try { if (mc.CurrentMode != null) { text2 = mc.CurrentMode.ScoreText(pvpPlayer2); text3 = mc.CurrentMode.DisplayName(pvpPlayer2); } } catch { } string days = ""; List list = mc.HistoryOf(pvpPlayer2); if (list != null && list.Count >= 2) { days = string.Join(", ", list); } _rows.Add(new DispRow { ColorIdx = pvpPlayer2.Index, Text = text3 + " — " + text2, Kills = pvpPlayer2.Kills, Downs = pvpPlayer2.Downs, Arrests = pvpPlayer2.Arrests, BagsCap = pvpPlayer2.BagsCaptured, BagsLost = pvpPlayer2.BagsLost, WpnLost = pvpPlayer2.WeaponsLost, WpnWon = pvpPlayer2.WeaponsWon, PlayerIndex = pvpPlayer2.Index, Days = days }); } } private void DrawScoreboard(MatchController mc, float width) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Expected O, but got Unknown //IL_00e7: 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) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Expected O, but got Unknown //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) if (!mc.IsRemote && mc.CurrentMode == null) { return; } BuildRows(mc); int count = _rows.Count; if (count != 0) { float num = 26 + count * 22 + 8; Rect position = default(Rect); ((Rect)(ref position))..ctor((float)Screen.width - width - 12f, 50f, width, num); GUI.color = new Color(0f, 0f, 0f, 0.55f); ImguiCompat.DrawTexture(position, White); GUI.color = Color.white; GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 14, fontStyle = (FontStyle)1 }; GUI.Label(new Rect(((Rect)(ref position)).x + 8f, ((Rect)(ref position)).y + 4f, width - 16f, 20f), "SCOREBOARD", val); GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 13 }; for (int i = 0; i < count; i++) { float num2 = ((Rect)(ref position)).y + 26f + (float)(i * 22); DispRow dispRow = _rows[i]; GUI.color = PvpColors.For(dispRow.ColorIdx); ImguiCompat.DrawTexture(new Rect(((Rect)(ref position)).x + 8f, num2 + 4f, 10f, 10f), White); GUI.color = Color.white; GUI.Label(new Rect(((Rect)(ref position)).x + 24f, num2, width - 32f, 20f), dispRow.Text, val2); } } } private void DrawMarkers(MatchController mc) { //IL_02f4: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: 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) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Expected O, but got Unknown //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02c2: Unknown result type (might be due to invalid IL or missing references) if (_camRevalidate-- <= 0 || (Object)(object)_cam == (Object)null) { _cam = Camera.main; _camRevalidate = 30; } if ((Object)(object)_cam == (Object)null) { return; } _markers.Clear(); if (mc.IsRemote) { List remoteMarkers = mc.RemoteMarkers; for (int i = 0; i < remoteMarkers.Count; i++) { _markers.Add(new WorldMarker(remoteMarkers[i].Pos, remoteMarkers[i].Color, remoteMarkers[i].Label, remoteMarkers[i].Team)); } } else { if (!(mc.CurrentMode is IWorldMarkers worldMarkers)) { return; } try { worldMarkers.CollectMarkers(mc.Context, mc.Registry.Local, _markers); } catch { return; } } if (_markers.Count == 0) { return; } int num = mc.LocalTeam(); GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 12, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; for (int j = 0; j < _markers.Count; j++) { WorldMarker worldMarker = _markers[j]; if (worldMarker.Team < 0 || worldMarker.Team == num) { Vector3 val2; try { val2 = _cam.WorldToScreenPoint(worldMarker.Pos); } catch { continue; } if (!(val2.z <= 0f)) { float x = val2.x; float num2 = (float)Screen.height - val2.y; GUI.color = worldMarker.Color; ImguiCompat.DrawTexture(new Rect(x - 2f, num2 - 80f, 4f, 80f), White); ImguiCompat.DrawTexture(new Rect(x - 7f, num2 - 7f, 14f, 14f), White); GUI.color = Color.black; GUI.Label(new Rect(x - 99f, num2 - 104f, 200f, 18f), worldMarker.Label, val); GUI.color = worldMarker.Color; GUI.Label(new Rect(x - 100f, num2 - 105f, 200f, 18f), worldMarker.Label, val); } } } GUI.color = Color.white; } private void DrawEndOfDayMenu(MatchController mc) { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: 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_00f4: Expected O, but got Unknown //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Expected O, but got Unknown //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Expected O, but got Unknown //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Expected O, but got Unknown //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02ce: Unknown result type (might be due to invalid IL or missing references) //IL_04c6: Unknown result type (might be due to invalid IL or missing references) //IL_04cb: Unknown result type (might be due to invalid IL or missing references) //IL_04d4: Unknown result type (might be due to invalid IL or missing references) //IL_04de: Expected O, but got Unknown //IL_0439: Unknown result type (might be due to invalid IL or missing references) //IL_043e: Unknown result type (might be due to invalid IL or missing references) //IL_0447: Unknown result type (might be due to invalid IL or missing references) //IL_0451: Expected O, but got Unknown //IL_0460: Unknown result type (might be due to invalid IL or missing references) //IL_0490: Unknown result type (might be due to invalid IL or missing references) //IL_04b0: Unknown result type (might be due to invalid IL or missing references) //IL_056d: Unknown result type (might be due to invalid IL or missing references) //IL_0598: Unknown result type (might be due to invalid IL or missing references) //IL_05a5: Unknown result type (might be due to invalid IL or missing references) //IL_05ae: Unknown result type (might be due to invalid IL or missing references) //IL_04fa: Unknown result type (might be due to invalid IL or missing references) //IL_0522: Unknown result type (might be due to invalid IL or missing references) //IL_0534: Unknown result type (might be due to invalid IL or missing references) //IL_03c4: Unknown result type (might be due to invalid IL or missing references) //IL_03f0: Unknown result type (might be due to invalid IL or missing references) //IL_03ff: Unknown result type (might be due to invalid IL or missing references) //IL_05f1: Unknown result type (might be due to invalid IL or missing references) //IL_0654: Unknown result type (might be due to invalid IL or missing references) BuildRows(mc); int count = _rows.Count; float num = ModeGridHeight(); float num2 = Mathf.Max(0f, num - 104f); float num3 = 560f; float num4 = Mathf.Clamp(78f + (float)count * 46f + 200f + num2, 470f + num2, (float)(Screen.height - 40)); Rect val = default(Rect); ((Rect)(ref val))..ctor((float)Screen.width * 0.5f - num3 / 2f, (float)Screen.height * 0.5f - num4 / 2f, num3, num4); GUI.color = new Color(0.05f, 0.05f, 0.08f, 0.92f); ImguiCompat.DrawTexture(val, White); GUI.color = Color.white; GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 24, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; GUI.Label(new Rect(((Rect)(ref val)).x, ((Rect)(ref val)).y + 12f, num3, 32f), $"END OF DAY {mc.Day}", val2); GUIStyle val3 = new GUIStyle(GUI.skin.label) { fontSize = 15, alignment = (TextAnchor)4 }; GUI.Label(new Rect(((Rect)(ref val)).x, ((Rect)(ref val)).y + 46f, num3, 22f), mc.DayResult ?? "", val3); GUIStyle val4 = new GUIStyle(GUI.skin.label) { fontSize = 14 }; GUIStyle val5 = new GUIStyle(GUI.skin.label) { fontSize = 12 }; float num5 = ((Rect)(ref val)).y + 78f; int num6 = Mathf.Max(1, Mathf.FloorToInt((num4 - 268f - num2) / 46f)); if (count > num6) { num6 = Mathf.Max(1, num6 - 1); } int num7 = Mathf.Min(count, num6); for (int i = 0; i < num7; i++) { DispRow dispRow = _rows[i]; string text = dispRow.Text; if (!string.IsNullOrEmpty(dispRow.Days)) { text = text + " (days: " + dispRow.Days + ")"; } float num8 = num5 + (float)i * 46f; GUI.color = PvpColors.For(dispRow.ColorIdx); ImguiCompat.DrawTexture(new Rect(((Rect)(ref val)).x + 30f, num8 + 4f, 10f, 10f), White); GUI.color = Color.white; GUI.Label(new Rect(((Rect)(ref val)).x + 48f, num8, num3 - 80f, 20f), $"{i + 1}. {text}", val4); string text2 = mc.ModeType switch { GameModeType.StealTheBags => $"Bags captured {dispRow.BagsCap} · Bags lost {dispRow.BagsLost}", GameModeType.Teams => $"Kills {dispRow.Kills} · Downs {dispRow.Downs} · Weapons won {dispRow.WpnWon} · Weapons lost {dispRow.WpnLost}", _ => $"Kills {dispRow.Kills} · Downs {dispRow.Downs} · Cuffs {dispRow.Arrests}", }; GUI.color = new Color(0.78f, 0.82f, 0.9f); GUI.Label(new Rect(((Rect)(ref val)).x + 48f, num8 + 22f, num3 - 80f, 18f), text2, val5); GUI.color = Color.white; } if (count > num7) { GUIStyle val6 = new GUIStyle(GUI.skin.label) { fontSize = 12, fontStyle = (FontStyle)2 }; GUI.color = new Color(0.7f, 0.75f, 0.85f); GUI.Label(new Rect(((Rect)(ref val)).x + 48f, num5 + (float)num7 * 46f, num3 - 80f, 18f), $"+ {count - num7} more…", val6); GUI.color = Color.white; } GUIStyle val7 = new GUIStyle(GUI.skin.label) { fontSize = 13, alignment = (TextAnchor)4 }; if (mc.IsRemote) { GUI.color = new Color(1f, 0.85f, 0.3f); GUI.Label(new Rect(((Rect)(ref val)).x, ((Rect)(ref val)).y + num4 - 90f, num3, 22f), "Waiting for the host to choose the next mode…", val7); GUI.color = Color.white; return; } float num9 = ((Rect)(ref val)).y + num4 - 168f - num2; GUI.Label(new Rect(((Rect)(ref val)).x, num9 - 22f, num3, 18f), "Host: choose the next mode to start the morning", val7); DrawModeGrid(mc, new Rect(((Rect)(ref val)).x + 20f, num9, num3 - 40f, num), fresh: true); DrawModeInfoCard(val); DrawLoadoutCard(val); float num10 = ((Rect)(ref val)).y + num4 - 44f; if (mc.HasActiveMode && GUI.Button(new Rect(((Rect)(ref val)).x + 20f, num10, (num3 - 50f) / 2f, 30f), "Continue " + GameModeInfo.Title(mc.ModeType) + " (next day)")) { mc.RequestContinue(); } if (GUI.Button(new Rect(((Rect)(ref val)).x + 30f + (num3 - 50f) / 2f, num10, (num3 - 50f) / 2f, 30f), "Stop match")) { mc.RequestStop(); } } private void DrawHostPanel(MatchController mc) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Expected O, but got Unknown //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Expected O, but got Unknown //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) float num = ModeGridHeight(); float num2 = 540f; float num3 = 250f + Mathf.Max(0f, num - 104f); Rect val = default(Rect); ((Rect)(ref val))..ctor((float)Screen.width * 0.5f - num2 / 2f, (float)Screen.height * 0.5f - num3 / 2f, num2, num3); GUI.color = new Color(0.05f, 0.05f, 0.08f, 0.92f); ImguiCompat.DrawTexture(val, White); GUI.color = Color.white; GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 22, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; GUI.Label(new Rect(((Rect)(ref val)).x, ((Rect)(ref val)).y + 12f, num2, 30f), "MULTIPLAYER PVP", val2); GUIStyle val3 = new GUIStyle(GUI.skin.label) { fontSize = 13, alignment = (TextAnchor)4 }; GUIStyle val4 = new GUIStyle(GUI.skin.label) { fontSize = 15, alignment = (TextAnchor)4, wordWrap = true }; if (!mc.IsMultiplayer) { GUI.color = new Color(1f, 0.7f, 0.3f); GUI.Label(new Rect(((Rect)(ref val)).x + 20f, ((Rect)(ref val)).y + 70f, num2 - 40f, 80f), "This mod is multiplayer-only.\nHost or join a co-op lobby with 2+ players,\nthen press the host key again.", val4); GUI.color = Color.white; } else if (!mc.IsHost) { GUI.color = new Color(1f, 0.7f, 0.3f); GUI.Label(new Rect(((Rect)(ref val)).x + 20f, ((Rect)(ref val)).y + 70f, num2 - 40f, 80f), "Only the host controls the match.\nThe host picks the game mode.", val4); GUI.color = Color.white; } else { GUI.Label(new Rect(((Rect)(ref val)).x, ((Rect)(ref val)).y + 44f, num2, 18f), $"{SessionUtil.PlayerCount()} player(s) · pick a mode to start", val3); Rect area = default(Rect); ((Rect)(ref area))..ctor(((Rect)(ref val)).x + 20f, ((Rect)(ref val)).y + 70f, num2 - 40f, num); DrawModeGrid(mc, area, fresh: true); DrawModeInfoCard(val); DrawLoadoutCard(val); } if (GUI.Button(new Rect(((Rect)(ref val)).x + num2 - 110f, ((Rect)(ref val)).y + num3 - 34f, 90f, 26f), "Close")) { PanelOpen = false; } } private void DrawEndMatchPrompt(MatchController mc) { //IL_0052: 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_0069: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Expected O, but got Unknown //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Expected O, but got Unknown //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) float num = 440f; float num2 = 180f; Rect position = default(Rect); ((Rect)(ref position))..ctor((float)Screen.width * 0.5f - num / 2f, (float)Screen.height * 0.5f - num2 / 2f, num, num2); GUI.color = new Color(0.05f, 0.05f, 0.08f, 0.95f); ImguiCompat.DrawTexture(position, White); GUI.color = Color.white; GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 20, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; GUI.Label(new Rect(((Rect)(ref position)).x, ((Rect)(ref position)).y + 16f, num, 28f), "END MATCH EARLY?", val); GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 14, alignment = (TextAnchor)4, wordWrap = true }; GUI.Label(new Rect(((Rect)(ref position)).x + 24f, ((Rect)(ref position)).y + 52f, num - 48f, 44f), "End the running match now? The countdown drops to 3 seconds and the day is scored as normal.", val2); if (GUI.Button(new Rect(((Rect)(ref position)).x + 40f, ((Rect)(ref position)).y + num2 - 64f, num - 80f, 38f), "End match early")) { EndPromptOpen = false; mc.RequestEndEarly(); } GUIStyle val3 = new GUIStyle(GUI.skin.label) { fontSize = 11, alignment = (TextAnchor)4 }; GUI.color = new Color(0.7f, 0.75f, 0.85f); GUI.Label(new Rect(((Rect)(ref position)).x, ((Rect)(ref position)).y + num2 - 22f, num, 16f), $"Press {(object)(KeyCode)PvpConfig.PanelKey.Value} again to cancel", val3); GUI.color = Color.white; } private static float ModeGridHeight() { int num = 7 + MatchToggles.Snapshot().Length; int num2 = (num + 3) / 4; return (float)num2 * 48f + (float)(num2 - 1) * 8f; } private void DrawModeGrid(MatchController mc, Rect area, bool fresh) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) int num = 7; float num2 = (((Rect)(ref area)).width - 24f) / 4f; float num3 = 48f; _hoverMode = -1; _hoverToggle = null; Vector2 val = (Vector2)((Event.current != null) ? Event.current.mousePosition : new Vector2(-1f, -1f)); Rect val2 = default(Rect); for (int i = 0; i < num; i++) { int num4 = i / 4; int num5 = i % 4; ((Rect)(ref val2))..ctor(((Rect)(ref area)).x + (float)num5 * (num2 + 8f), ((Rect)(ref area)).y + (float)num4 * (num3 + 8f), num2, num3); if (((Rect)(ref val2)).Contains(val)) { _hoverMode = i; } GameModeType gameModeType = (GameModeType)i; if (GUI.Button(val2, $"{i + 1}. {GameModeInfo.Title(gameModeType)}")) { PanelOpen = false; if (fresh) { mc.RequestStart(gameModeType); } } } PvpMatchToggle[] array = MatchToggles.Snapshot(); Rect val3 = default(Rect); for (int j = 0; j < array.Length; j++) { int num6 = num + j; int num7 = num6 / 4; int num8 = num6 % 4; ((Rect)(ref val3))..ctor(((Rect)(ref area)).x + (float)num8 * (num2 + 8f), ((Rect)(ref area)).y + (float)num7 * (num3 + 8f), num2, num3); PvpMatchToggle pvpMatchToggle = array[j]; if (((Rect)(ref val3)).Contains(val)) { _hoverToggle = pvpMatchToggle; } GUI.color = (pvpMatchToggle.Requested ? new Color(0.55f, 1f, 0.55f) : new Color(0.85f, 0.85f, 0.9f)); if (GUI.Button(val3, pvpMatchToggle.Label + "\n" + (pvpMatchToggle.Requested ? "ON (this round)" : "OFF"))) { pvpMatchToggle.Requested = !pvpMatchToggle.Requested; } GUI.color = Color.white; } } } internal static class ViewLock { private static bool _locked; internal static void Set(bool locked) { if (locked != _locked) { _locked = locked; try { PlayerCamera instance = PlayerSingleton.Instance; if ((Object)(object)instance != (Object)null) { instance.SetCanLook(!locked); if (locked) { instance.FreeMouse(true); } else { instance.LockMouse(true); } } } catch { } } if (locked) { try { Cursor.visible = true; Cursor.lockState = (CursorLockMode)0; } catch { } } } internal static void Release() { if (!_locked) { return; } _locked = false; try { PlayerCamera instance = PlayerSingleton.Instance; if ((Object)(object)instance != (Object)null) { instance.SetCanLook(true); instance.LockMouse(true); } } catch { } } } } namespace MultiplayerPVP.Net { internal struct SnapRow { internal string Name; internal int Team; internal int Index; internal string Score; internal bool Bag; internal Vector3 Spawn; internal int Kills; internal int Downs; internal int Arrests; internal int BagsCaptured; internal int BagsLost; internal int WeaponsLost; internal int WeaponsWon; internal string History; } internal struct SnapTeamRow { internal int Team; internal string Name; internal string Score; internal int Kills; internal int Downs; internal int Arrests; internal int BagsCaptured; internal int BagsLost; internal int WeaponsLost; internal int WeaponsWon; } internal struct SnapMarker { internal Vector3 Pos; internal Color Color; internal string Label; internal int Team; } internal class MatchSnapshot { internal int Phase; internal int Mode; internal int Day; internal float Remaining; internal string Banner = ""; internal string Result = ""; internal string Loot = ""; internal bool Chaos; internal bool Ammo; internal bool Jump; internal string Loadout = ""; internal long RoundId; internal readonly List Rows = new List(); internal readonly List TeamRows = new List(); internal readonly List Markers = new List(); internal long Seq; private static string San(string s) { if (string.IsNullOrEmpty(s)) { return ""; } return s.Replace('|', ' ').Replace('~', ' ').Replace(';', ' ') .Replace('=', ' ') .Replace(',', ' ') .Replace('"', ' ') .Replace('\\', ' '); } private static string F(float v) { return v.ToString("0.0", CultureInfo.InvariantCulture); } internal string Serialize() { //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_044b: Unknown result type (might be due to invalid IL or missing references) //IL_0468: Unknown result type (might be due to invalid IL or missing references) //IL_0485: Unknown result type (might be due to invalid IL or missing references) //IL_04a2: Unknown result type (might be due to invalid IL or missing references) //IL_04bf: Unknown result type (might be due to invalid IL or missing references) //IL_04dc: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(192); stringBuilder.Append("v=1|sq=").Append(Seq).Append("|ph=") .Append(Phase) .Append("|md=") .Append(Mode) .Append("|day=") .Append(Day) .Append("|rs=") .Append(Remaining.ToString("0.0", CultureInfo.InvariantCulture)) .Append("|bn=") .Append(San(Banner)) .Append("|rz=") .Append(San(Result)) .Append("|lt=") .Append(Loot ?? "") .Append("|cx=") .Append(Chaos ? 1 : 0) .Append("|ua=") .Append(Ammo ? 1 : 0) .Append("|dj=") .Append(Jump ? 1 : 0) .Append("|gi=") .Append(Loadout ?? "") .Append("|gr=") .Append(RoundId) .Append("|p="); for (int i = 0; i < Rows.Count; i++) { SnapRow snapRow = Rows[i]; if (i > 0) { stringBuilder.Append(';'); } stringBuilder.Append(San(snapRow.Name)).Append('~').Append(snapRow.Team) .Append('~') .Append(snapRow.Index) .Append('~') .Append(San(snapRow.Score)) .Append('~') .Append(snapRow.Bag ? 1 : 0) .Append('~') .Append(F(snapRow.Spawn.x)) .Append('~') .Append(F(snapRow.Spawn.y)) .Append('~') .Append(F(snapRow.Spawn.z)) .Append('~') .Append(snapRow.Kills) .Append('~') .Append(snapRow.Downs) .Append('~') .Append(snapRow.Arrests) .Append('~') .Append(snapRow.BagsCaptured) .Append('~') .Append(snapRow.BagsLost) .Append('~') .Append(snapRow.WeaponsLost) .Append('~') .Append(snapRow.WeaponsWon) .Append('~') .Append(San(snapRow.History)); } stringBuilder.Append("|tr="); for (int j = 0; j < TeamRows.Count; j++) { SnapTeamRow snapTeamRow = TeamRows[j]; if (j > 0) { stringBuilder.Append(';'); } stringBuilder.Append(snapTeamRow.Team).Append('~').Append(San(snapTeamRow.Name)) .Append('~') .Append(San(snapTeamRow.Score)) .Append('~') .Append(snapTeamRow.Kills) .Append('~') .Append(snapTeamRow.Downs) .Append('~') .Append(snapTeamRow.Arrests) .Append('~') .Append(snapTeamRow.BagsCaptured) .Append('~') .Append(snapTeamRow.BagsLost) .Append('~') .Append(snapTeamRow.WeaponsLost) .Append('~') .Append(snapTeamRow.WeaponsWon); } stringBuilder.Append("|m="); for (int k = 0; k < Markers.Count; k++) { SnapMarker snapMarker = Markers[k]; if (k > 0) { stringBuilder.Append(','); } stringBuilder.Append(F(snapMarker.Pos.x)).Append('~').Append(F(snapMarker.Pos.y)) .Append('~') .Append(F(snapMarker.Pos.z)) .Append('~') .Append(F(snapMarker.Color.r)) .Append('~') .Append(F(snapMarker.Color.g)) .Append('~') .Append(F(snapMarker.Color.b)) .Append('~') .Append(San(snapMarker.Label)) .Append('~') .Append(snapMarker.Team); } return stringBuilder.ToString(); } internal static bool TryParse(string s, MatchSnapshot into) { //IL_08ff: Unknown result type (might be due to invalid IL or missing references) //IL_0904: Unknown result type (might be due to invalid IL or missing references) //IL_0916: Unknown result type (might be due to invalid IL or missing references) //IL_091b: Unknown result type (might be due to invalid IL or missing references) //IL_05a0: Unknown result type (might be due to invalid IL or missing references) //IL_05a5: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(s) || into == null) { return false; } try { into.Rows.Clear(); into.TeamRows.Clear(); into.Markers.Clear(); into.Banner = ""; into.Result = ""; into.Loot = ""; into.Chaos = false; into.Ammo = false; into.Jump = false; into.Loadout = ""; into.RoundId = 0L; string[] array = s.Split(new char[1] { '|' }); string[] array2 = array; foreach (string text in array2) { int num = text.IndexOf('='); if (num < 0) { continue; } string text2 = text.Substring(0, num); string text3 = text.Substring(num + 1); switch (text2) { case "sq": long.TryParse(text3, out into.Seq); break; case "ph": int.TryParse(text3, out into.Phase); break; case "md": int.TryParse(text3, out into.Mode); break; case "day": int.TryParse(text3, out into.Day); break; case "rs": float.TryParse(text3, NumberStyles.Float, CultureInfo.InvariantCulture, out into.Remaining); break; case "bn": into.Banner = text3; break; case "rz": into.Result = text3; break; case "lt": into.Loot = text3; break; case "cx": into.Chaos = text3 == "1"; break; case "ua": into.Ammo = text3 == "1"; break; case "dj": into.Jump = text3 == "1"; break; case "gi": into.Loadout = text3; break; case "gr": long.TryParse(text3, out into.RoundId); break; case "p": { if (text3.Length == 0) { break; } string[] array7 = text3.Split(new char[1] { ';' }); foreach (string text6 in array7) { string[] array8 = text6.Split(new char[1] { '~' }); if (array8.Length >= 4) { SnapRow item3 = new SnapRow { Name = array8[0], Score = array8[3] }; int.TryParse(array8[1], out item3.Team); int.TryParse(array8[2], out item3.Index); if (array8.Length >= 5) { item3.Bag = array8[4] == "1"; } if (array8.Length >= 8) { float.TryParse(array8[5], NumberStyles.Float, CultureInfo.InvariantCulture, out var result7); float.TryParse(array8[6], NumberStyles.Float, CultureInfo.InvariantCulture, out var result8); float.TryParse(array8[7], NumberStyles.Float, CultureInfo.InvariantCulture, out var result9); item3.Spawn = new Vector3(result7, result8, result9); } if (array8.Length >= 11) { int.TryParse(array8[8], out item3.Kills); int.TryParse(array8[9], out item3.Downs); int.TryParse(array8[10], out item3.Arrests); } if (array8.Length >= 15) { int.TryParse(array8[11], out item3.BagsCaptured); int.TryParse(array8[12], out item3.BagsLost); int.TryParse(array8[13], out item3.WeaponsLost); int.TryParse(array8[14], out item3.WeaponsWon); } if (array8.Length >= 16) { item3.History = array8[15]; } into.Rows.Add(item3); } } break; } case "tr": { if (text3.Length == 0) { break; } string[] array5 = text3.Split(new char[1] { ';' }); foreach (string text5 in array5) { string[] array6 = text5.Split(new char[1] { '~' }); if (array6.Length >= 3) { SnapTeamRow item2 = new SnapTeamRow { Name = array6[1], Score = array6[2] }; int.TryParse(array6[0], out item2.Team); if (array6.Length >= 10) { int.TryParse(array6[3], out item2.Kills); int.TryParse(array6[4], out item2.Downs); int.TryParse(array6[5], out item2.Arrests); int.TryParse(array6[6], out item2.BagsCaptured); int.TryParse(array6[7], out item2.BagsLost); int.TryParse(array6[8], out item2.WeaponsLost); int.TryParse(array6[9], out item2.WeaponsWon); } into.TeamRows.Add(item2); } } break; } case "m": { if (text3.Length == 0) { break; } string[] array3 = text3.Split(new char[1] { ',' }); foreach (string text4 in array3) { string[] array4 = text4.Split(new char[1] { '~' }); if (array4.Length >= 7) { SnapMarker item = new SnapMarker { Label = array4[6], Team = -1 }; float.TryParse(array4[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result); float.TryParse(array4[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2); float.TryParse(array4[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result3); float.TryParse(array4[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var result4); float.TryParse(array4[4], NumberStyles.Float, CultureInfo.InvariantCulture, out var result5); float.TryParse(array4[5], NumberStyles.Float, CultureInfo.InvariantCulture, out var result6); if (array4.Length >= 8) { int.TryParse(array4[7], out item.Team); } item.Pos = new Vector3(result, result2, result3); item.Color = new Color(result4, result5, result6, 1f); into.Markers.Add(item); } } break; } } } return true; } catch { return false; } } } internal sealed class PvpNet { internal const string KeyPrefix = "mpvp_"; internal const string SnapshotKey = "snap"; internal const string ModTagKey = "mod"; internal const string LootKey = "loot"; internal const string EarnKey = "earn"; internal const string SpillKey = "spill"; internal const string IdTagKey = "idtag"; internal const string SightKey = "sight"; private SteamNetworkClient _client; private HostSyncVar _snap; private ClientSyncVar _modTag; private ClientSyncVar _loot; private ClientSyncVar _earn; private ClientSyncVar _spill; private ClientSyncVar _idTag; private ClientSyncVar _sight; private readonly List> _addonVars = new List>(); private bool _init; private static readonly string ModTag = "MultiplayerPVP/" + ModVersion(); private bool _wasInLobby; private bool _tagSet; private string _idCode; private bool _idSet; private const float WarnInterval = 15f; private float _lastWarn = -999f; internal bool Available => _init && _client != null; internal bool IsInLobby { get { try { return Available && _client.IsInLobby; } catch { return false; } } } internal bool IsHost { get { try { return Available && _client.IsHost; } catch { return false; } } } internal ulong LocalSteamId { get { //IL_0044: Unknown result type (might be due to invalid IL or missing references) if (!Available) { return 0uL; } try { foreach (MemberInfo lobbyMember in _client.GetLobbyMembers()) { if (lobbyMember != null && lobbyMember.IsLocalPlayer) { return lobbyMember.SteamId.m_SteamID; } } } catch { } return 0uL; } } internal int ModdedMemberCount { get { if (_modTag == null) { return 0; } try { int num = 0; foreach (KeyValuePair allValue in _modTag.GetAllValues()) { if (!string.IsNullOrEmpty(allValue.Value)) { num++; } } return num; } catch { return 0; } } } internal int MemberCount { get { try { return Available ? _client.GetLobbyMembers().Count : 0; } catch { return 0; } } } internal bool AllMembersModded { get { int memberCount = MemberCount; return memberCount > 0 && ModdedMemberCount >= memberCount; } } internal event Action OnSnapshot; internal event Action OnLoot; internal event Action OnSpill; internal event Action OnSighting; internal void Init() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (_init) { return; } try { _client = new SteamNetworkClient(); if (!_client.Initialize()) { return; } try { _client.OnVersionMismatch += OnVersionMismatch; } catch { } NetworkSyncOptions val = MakeOpts(); _snap = _client.CreateHostSyncVar("snap", "", val, (ISyncValidator)null); _snap.OnValueChanged += OnSnapValueChanged; _snap.OnSyncError += OnSyncError; _modTag = _client.CreateClientSyncVar("mod", "", val, (ISyncValidator)null); _loot = _client.CreateClientSyncVar("loot", "", val, (ISyncValidator)null); _loot.OnValueChanged += delegate(CSteamID from, string oldV, string newV) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) try { if (!string.IsNullOrEmpty(newV)) { this.OnLoot?.Invoke(from.m_SteamID, newV); } } catch { } }; _earn = _client.CreateClientSyncVar("earn", "", val, (ISyncValidator)null); _spill = _client.CreateClientSyncVar("spill", "", val, (ISyncValidator)null); _spill.OnValueChanged += delegate(CSteamID from, string oldV, string newV) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) try { if (!string.IsNullOrEmpty(newV)) { this.OnSpill?.Invoke(from.m_SteamID, newV); } } catch { } }; _idTag = _client.CreateClientSyncVar("idtag", "", val, (ISyncValidator)null); _sight = _client.CreateClientSyncVar("sight", "", val, (ISyncValidator)null); _sight.OnValueChanged += delegate(CSteamID from, string oldV, string newV) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) try { if (!string.IsNullOrEmpty(newV)) { this.OnSighting?.Invoke(from.m_SteamID, newV); } } catch { } }; _init = true; PvpLog.Debug("[MultiplayerPVP][Net] SteamNetworkClient initialized (HostSyncVar transport)."); } catch { } } private static NetworkSyncOptions MakeOpts() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown return new NetworkSyncOptions { KeyPrefix = "mpvp_", AutoSync = true, SyncOnPlayerJoin = true, MaxSyncsPerSecond = 0 }; } internal ClientSyncVar CreateAddonVar(string keySuffix, Action onMessage) { if (!_init || _client == null || string.IsNullOrEmpty(keySuffix)) { return null; } try { ClientSyncVar val = _client.CreateClientSyncVar(keySuffix, "", MakeOpts(), (ISyncValidator)null); if (onMessage != null) { val.OnValueChanged += delegate(CSteamID from, string oldV, string newV) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) try { if (!string.IsNullOrEmpty(newV)) { onMessage(from.m_SteamID, newV); } } catch { } }; } lock (_addonVars) { _addonVars.Add(val); } return val; } catch (Exception ex) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - _lastWarn >= 15f) { _lastWarn = realtimeSinceStartup; MelonLogger.Warning("[MultiplayerPVP][Net] addon channel '" + keySuffix + "' create failed: " + ex.Message); } return null; } } internal void Tick() { if (!_init) { return; } bool isInLobby = IsInLobby; if (isInLobby && !_wasInLobby) { _tagSet = false; _idSet = false; } _wasInLobby = isInLobby; if (isInLobby && !_tagSet && _modTag != null) { try { _modTag.Value = ModTag; _tagSet = true; } catch { } } if (!isInLobby || _idSet || _idTag == null || string.IsNullOrEmpty(_idCode)) { return; } try { _idTag.Value = _idCode; _idSet = true; } catch { } } internal void SetLocalIdentity(string playerCode) { if (!string.IsNullOrEmpty(playerCode) && playerCode != _idCode) { _idCode = playerCode; _idSet = false; } } private void OnSnapValueChanged(string oldValue, string newValue) { try { if (!IsHost && !string.IsNullOrEmpty(newValue)) { this.OnSnapshot?.Invoke(newValue); } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP][Net] snapshot apply: " + ex.Message); } } internal void PublishSnapshot(string snapshot) { if (!Available || !IsHost || _snap == null) { return; } try { _snap.Value = snapshot; } catch (Exception ex) { OnSyncError(ex); } } internal void PublishLoot(string record) { if (!Available || _loot == null) { return; } try { _loot.Value = record; } catch { } } internal void PublishSpill(string record) { if (!Available || _spill == null) { return; } try { _spill.Value = record; } catch { } } internal void PublishEarnings(string record) { if (!Available || _earn == null) { return; } try { _earn.Value = record; } catch { } } internal List> AllEarnings() { return ReadAllOf(_earn); } internal List> AllLoot() { return ReadAllOf(_loot); } internal List> AllSpill() { return ReadAllOf(_spill); } internal static List> ReadAllOf(ClientSyncVar v) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) List> list = new List>(); if (v == null) { return list; } try { v.Refresh(); } catch { } try { foreach (KeyValuePair allValue in v.GetAllValues()) { if (!string.IsNullOrEmpty(allValue.Value)) { list.Add(new KeyValuePair(allValue.Key.m_SteamID, allValue.Value)); } } } catch { } return list; } internal void PublishSighting(string record) { if (!Available || _sight == null) { return; } try { _sight.Value = record; } catch { } } internal Dictionary AllIdentities() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) Dictionary dictionary = new Dictionary(); if (_idTag == null) { return dictionary; } try { foreach (KeyValuePair allValue in _idTag.GetAllValues()) { if (!string.IsNullOrEmpty(allValue.Value)) { dictionary[allValue.Value] = allValue.Key.m_SteamID; } } } catch { } return dictionary; } internal string PersonaOf(ulong steamId) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) if (!Available || steamId == 0) { return null; } try { foreach (MemberInfo lobbyMember in _client.GetLobbyMembers()) { if (lobbyMember != null && lobbyMember.SteamId.m_SteamID == steamId) { return lobbyMember.DisplayName; } } } catch { } return null; } private void OnVersionMismatch(object sender, VersionMismatchEventArgs e) { MelonLogger.Warning("[MultiplayerPVP][Net] SteamNetworkLib version mismatch between players — networking may be unreliable. Ensure everyone runs the same mod/lib build."); } private void OnSyncError(Exception ex) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup - _lastWarn < 15f)) { _lastWarn = realtimeSinceStartup; MelonLogger.Warning("[MultiplayerPVP][Net] snapshot sync error (throttled): " + ex.Message); } } internal void Dispose() { try { if (_snap != null) { _snap.OnValueChanged -= OnSnapValueChanged; _snap.OnSyncError -= OnSyncError; _snap.Dispose(); } } catch { } try { if (_modTag != null) { _modTag.Dispose(); } } catch { } try { if (_loot != null) { _loot.Dispose(); } } catch { } try { if (_earn != null) { _earn.Dispose(); } } catch { } try { if (_spill != null) { _spill.Dispose(); } } catch { } try { if (_idTag != null) { _idTag.Dispose(); } } catch { } try { if (_sight != null) { _sight.Dispose(); } } catch { } lock (_addonVars) { for (int i = 0; i < _addonVars.Count; i++) { try { _addonVars[i].Dispose(); } catch { } } _addonVars.Clear(); } try { if (_client != null) { _client.OnVersionMismatch -= OnVersionMismatch; _client.Dispose(); } } catch { } _snap = null; _modTag = null; _loot = null; _earn = null; _spill = null; _idTag = null; _sight = null; _client = null; _init = false; _tagSet = false; _idSet = false; _idCode = null; _wasInLobby = false; } private static string ModVersion() { try { Version version = typeof(PvpNet).Assembly.GetName().Version; return (version != null) ? version.ToString() : "1.0"; } catch { return "1.0"; } } } } namespace MultiplayerPVP.Modes { internal class FindersKeepersMode : GameModeBase, IWorldMarkers, IBagSpillHandler { private DeadDrop _pickup; private int _pickupZone = -1; private int _pickupKnown; private PvpPlayer _carrier; private int _carrierHome = -1; private int _homeBaseline; private float _transit; private bool _active; private static string BagId => BagFactory.MatchId; public override GameModeType Type => GameModeType.FindersKeepers; public override void OnMatchStart(MatchContext ctx) { _active = true; SpawnBag(ctx, announce: false); } public override void OnMatchEnd(MatchContext ctx) { _active = false; bool tie; PvpPlayer pvpPlayer = ctx.Registry.Leader(out tie); if (pvpPlayer != null) { ctx.Announcer.Announce(tie ? $"It's a draw at {pvpPlayer.Score} delivery(ies)!" : $"{pvpPlayer.Name} wins with {pvpPlayer.Score} delivery(ies)!", 6f); } } private void SpawnBag(MatchContext ctx, bool announce) { _carrier = null; _carrierHome = -1; _transit = 0f; _pickup = ctx.Drops.RandomNonZoneDrop(); _pickupZone = -1; for (int i = 0; i < ctx.Drops.Count; i++) { if ((Object)(object)ctx.Drops.Zone(i) == (Object)(object)_pickup) { _pickupZone = i; break; } } if (GameCompat.Alive((Object)(object)_pickup)) { DeadDropStash.Insert(_pickup, BagId, 1, BagFactory.Create); _pickupKnown = DeadDropStash.Count(_pickup, BagId); } else { _pickupKnown = 0; } if (announce && GameCompat.Alive((Object)(object)_pickup)) { ctx.Announcer.Announce("A fresh bag is in a dead drop — find it!", 3f); } } public bool OnCarrierSpill(MatchContext ctx, PvpPlayer victim, int count, string reason) { if (count <= 0) { return true; } if (_carrier == null) { return true; } if (victim != null && victim != _carrier) { return true; } string name = _carrier.Name; string text = ((reason == "arrested") ? "was busted" : "went down"); SpawnBag(ctx, announce: false); ctx.Announcer.Announce(name + " " + text + " — the bag dropped and reappears in a dead drop!", 4f, notify: false); return true; } public override void Tick(MatchContext ctx, float dt) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) if (!_active) { return; } if (_carrier == null) { if (!GameCompat.Alive((Object)(object)_pickup)) { SpawnBag(ctx, announce: false); return; } int num = DeadDropStash.Count(_pickup, BagId); if (num < _pickupKnown) { _carrier = NearestPlayer(ctx, PickupPos(ctx)); _pickupKnown = num; _transit = 0f; _carrierHome = ((_carrier != null) ? ctx.Drops.ZoneForIndex(_carrier.Index) : (-1)); DeadDrop val = ((_carrierHome >= 0) ? ctx.Drops.Zone(_carrierHome) : null); _homeBaseline = (GameCompat.Alive((Object)(object)val) ? DeadDropStash.Count(val, BagId) : 0); string text = ((_carrier != null) ? _carrier.Name : "Someone"); ctx.Announcer.Announce(text + " has the bag!"); } } else { _transit += dt; int carrierHome = _carrierHome; DeadDrop val2 = ((carrierHome >= 0) ? ctx.Drops.Zone(carrierHome) : null); if (GameCompat.Alive((Object)(object)val2) && DeadDropStash.Count(val2, BagId) > _homeBaseline) { DeadDropStash.RemoveOne(val2, BagId); ctx.Award(_carrier, 1); ctx.Announcer.Announce($"{_carrier.Name} delivered the bag! ({_carrier.Score})", 5f); SpawnBag(ctx, announce: false); } else if (_transit > 60f) { ctx.Announcer.Announce("The bag was lost — a new one appears."); SpawnBag(ctx, announce: true); } } } private Vector3 PickupPos(MatchContext ctx) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (_pickupZone >= 0) { return ctx.Drops.ZonePosition(_pickupZone); } try { return GameCompat.Alive((Object)(object)_pickup) ? ((Component)_pickup).transform.position : Vector3.zero; } catch { return Vector3.zero; } } private static PvpPlayer NearestPlayer(MatchContext ctx, Vector3 pos) { //IL_0035: 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) List players = ctx.Registry.Players; PvpPlayer result = null; float num = float.MaxValue; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; if (pvpPlayer.Valid) { float num2 = Vector3.Distance(pos, pvpPlayer.Position); if (num2 < num) { num = num2; result = pvpPlayer; } } } return result; } public override bool IsComplete(MatchContext ctx) { int num = Mathf.Max(1, PvpConfig.CtfScoreToWin.Value); List players = ctx.Registry.Players; for (int i = 0; i < players.Count; i++) { if (players[i].Score >= num) { return true; } } return false; } public override string ScoreText(PvpPlayer p) { return p.Score + " caps" + ((_carrier == p) ? " ·BAG" : ""); } public void CollectMarkers(MatchContext ctx, PvpPlayer local, List into) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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) if (_active && _carrier != null) { int carrierHome = _carrierHome; if (carrierHome >= 0) { into.Add(new WorldMarker(ctx.Drops.ZonePosition(carrierHome) + Vector3.up * 1.4f, PvpColors.For(_carrier.Index), "DELIVER HERE (" + _carrier.Name + ")")); } } } } internal class FreeForAllMode : GameModeBase { public override GameModeType Type => GameModeType.FreeForAll; public override void Tick(MatchContext ctx, float dt) { Combat.ProcessEliminations(ctx, teamMode: false); } public override void OnMatchEnd(MatchContext ctx) { bool tie; PvpPlayer pvpPlayer = ctx.Registry.Leader(out tie); if (pvpPlayer != null) { ctx.Announcer.Announce(tie ? $"It's a draw at {pvpPlayer.Score} eliminations!" : $"{pvpPlayer.Name} wins with {pvpPlayer.Score} eliminations!", 6f); } } public override string ScoreText(PvpPlayer p) { return p.Score + " elim"; } } internal class HideAndSeekMode : GameModeBase { private readonly HashSet _originalHiders = new HashSet(); private readonly Dictionary _foundRank = new Dictionary(); private int _foundCounter; private float _lastScan; public override GameModeType Type => GameModeType.HideAndSeek; private static string KeyOf(PvpPlayer p) { return (p != null) ? p.StableKey : ""; } private bool IsOriginalHider(PvpPlayer p) { return _originalHiders.Contains(KeyOf(p)) || (p != null && p.Name != null && _originalHiders.Contains(p.Name)); } private bool TryGetFoundRank(PvpPlayer p, out int rank) { if (_foundRank.TryGetValue(KeyOf(p), out rank)) { return true; } return p != null && p.Name != null && _foundRank.TryGetValue(p.Name, out rank); } public override void OnMatchStart(MatchContext ctx) { _originalHiders.Clear(); _foundRank.Clear(); _foundCounter = 0; List players = ctx.Registry.Players; int count = players.Count; int num = Mathf.Clamp(PvpConfig.SeekerCount.Value, 1, Mathf.Max(1, count - 1)); List list = new List(count); for (int i = 0; i < count; i++) { list.Add(i); } for (int num2 = list.Count - 1; num2 > 0; num2--) { int index = Random.Range(0, num2 + 1); int value = list[num2]; list[num2] = list[index]; list[index] = value; } for (int j = 0; j < list.Count; j++) { PvpPlayer pvpPlayer = players[list[j]]; pvpPlayer.Eliminated = false; pvpPlayer.IsSeeker = j < num; if (!pvpPlayer.IsSeeker) { _originalHiders.Add(KeyOf(pvpPlayer)); } } DamageTracker.Clear(); _lastScan = Time.realtimeSinceStartup; ctx.Announcer.Announce($"{num} random seeker(s) — hiders, hide! Any hit tags you onto the seeker team.", 5f); } public override void Tick(MatchContext ctx, float dt) { List players = ctx.Registry.Players; float lastScan = _lastScan; _lastScan = Time.realtimeSinceStartup; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; pvpPlayer.IsSeeker = !IsOriginalHider(pvpPlayer) || TryGetFoundRank(pvpPlayer, out var _); } for (int j = 0; j < players.Count; j++) { PvpPlayer pvpPlayer2 = players[j]; if (pvpPlayer2.IsSeeker || !pvpPlayer2.Valid) { continue; } bool flag = !pvpPlayer2.IsAlive; if (!flag) { try { flag = DamageTracker.WasHitSince(((Object)pvpPlayer2.Game).GetInstanceID(), lastScan); } catch { } } if (flag) { pvpPlayer2.IsSeeker = true; _foundRank[KeyOf(pvpPlayer2)] = ++_foundCounter; PvpPlayer pvpPlayer3 = NearestSeeker(ctx, pvpPlayer2); if (pvpPlayer3 != null) { ctx.Award(pvpPlayer3, 1); } ctx.Announcer.Announce((pvpPlayer3 != null) ? (pvpPlayer2.Name + " was tagged by " + pvpPlayer3.Name + " — now a seeker!") : (pvpPlayer2.Name + " was hit — now a seeker!")); } } } private static PvpPlayer NearestSeeker(MatchContext ctx, PvpPlayer hider) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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) //IL_005b: Unknown result type (might be due to invalid IL or missing references) List players = ctx.Registry.Players; PvpPlayer result = null; float num = float.MaxValue; Vector3 position = hider.Position; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; if (pvpPlayer != hider && pvpPlayer.IsSeeker && pvpPlayer.Valid && pvpPlayer.IsAlive) { float num2 = Vector3.Distance(position, pvpPlayer.Position); if (num2 < num) { num = num2; result = pvpPlayer; } } } return result; } public override bool IsComplete(MatchContext ctx) { List players = ctx.Registry.Players; for (int i = 0; i < players.Count; i++) { if (IsOriginalHider(players[i]) && !players[i].IsSeeker) { return false; } } return _originalHiders.Count > 0; } public override void OnMatchEnd(MatchContext ctx) { List players = ctx.Registry.Players; PvpPlayer pvpPlayer = null; int num = -1; bool flag = false; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer2 = players[i]; if (!IsOriginalHider(pvpPlayer2)) { continue; } if (!TryGetFoundRank(pvpPlayer2, out var rank)) { flag = true; if (pvpPlayer == null) { pvpPlayer = pvpPlayer2; } } else if (!flag && rank > num) { num = rank; pvpPlayer = pvpPlayer2; } } if (pvpPlayer != null) { ctx.Announcer.Announce(flag ? (pvpPlayer.Name + " survived and wins Hide & Seek!") : (pvpPlayer.Name + " hid the longest and wins!"), 6f); } } public override string ScoreText(PvpPlayer p) { if (!IsOriginalHider(p)) { return "Seeker"; } if (TryGetFoundRank(p, out var rank)) { return "found #" + rank; } return "hiding"; } } internal class HorseMode : GameModeBase { private string Word => string.IsNullOrEmpty(PvpConfig.HorseWord.Value) ? "HORSE" : PvpConfig.HorseWord.Value.ToUpperInvariant(); public override GameModeType Type => GameModeType.Horse; public override bool IsTeamScored => true; public override void Tick(MatchContext ctx, float dt) { } public override void OnDayEnd(MatchContext ctx) { bool tie; int num = LowestTeam(ctx, out tie); if (num < 0) { return; } if (tie) { ctx.Announcer.Announce("No letter today — it's a tie for last place."); return; } string word = Word; string text = TeamLetters(ctx, num); if (text.Length < word.Length) { string text2 = text + word[text.Length]; SetTeamLetters(ctx, num, text2); ctx.Announcer.Announce(ctx.Registry.TeamLabel(num) + " earned the worst street sales today — letter: " + text2, 5f); if (text2.Length >= word.Length) { EliminateTeam(ctx, num); ctx.Announcer.Announce(ctx.Registry.TeamLabel(num) + " spelled " + word + " and is OUT!", 6f); } } } public override bool IsComplete(MatchContext ctx) { return ctx.Registry.ActiveTeamCount > 1 && AliveTeamCount(ctx) <= 1; } public override void OnMatchEnd(MatchContext ctx) { int activeTeamCount = ctx.Registry.ActiveTeamCount; for (int i = 0; i < activeTeamCount; i++) { if (!IsTeamEliminated(ctx, i) && TeamHasMembers(ctx, i)) { ctx.Announcer.Announce(ctx.Registry.TeamLabel(i) + " survives H.O.R.S.E. and wins!", 6f); break; } } } public override string TeamScoreText(MatchContext ctx, int team) { string text = TeamLetters(ctx, team); if (string.IsNullOrEmpty(text)) { text = "—"; } return text + " ($" + ctx.Registry.TeamDayScore(team) + ")"; } public override string DisplayName(PvpPlayer p) { return PvpColors.TeamName(p.Team); } public override string ScoreText(PvpPlayer p) { string text = (string.IsNullOrEmpty(p.HorseLetters) ? "—" : p.HorseLetters); return text + " ($" + p.DayScore + ")"; } private static string TeamLetters(MatchContext ctx, int team) { string text = ""; List players = ctx.Registry.Players; for (int i = 0; i < players.Count; i++) { if (players[i].Team == team && players[i].HorseLetters != null && players[i].HorseLetters.Length > text.Length) { text = players[i].HorseLetters; } } return text; } private static void SetTeamLetters(MatchContext ctx, int team, string letters) { List players = ctx.Registry.Players; for (int i = 0; i < players.Count; i++) { if (players[i].Team == team) { players[i].HorseLetters = letters; } } } private static void EliminateTeam(MatchContext ctx, int team) { List players = ctx.Registry.Players; for (int i = 0; i < players.Count; i++) { if (players[i].Team == team) { players[i].Eliminated = true; } } } private static bool TeamHasMembers(MatchContext ctx, int team) { List players = ctx.Registry.Players; for (int i = 0; i < players.Count; i++) { if (players[i].Team == team) { return true; } } return false; } private static bool IsTeamEliminated(MatchContext ctx, int team) { List players = ctx.Registry.Players; bool result = false; for (int i = 0; i < players.Count; i++) { if (players[i].Team == team) { result = true; if (!players[i].Eliminated) { return false; } } } return result; } private static int AliveTeamCount(MatchContext ctx) { int activeTeamCount = ctx.Registry.ActiveTeamCount; int num = 0; for (int i = 0; i < activeTeamCount; i++) { if (TeamHasMembers(ctx, i) && !IsTeamEliminated(ctx, i)) { num++; } } return num; } private static int LowestTeam(MatchContext ctx, out bool tie) { tie = false; int activeTeamCount = ctx.Registry.ActiveTeamCount; int result = -1; int num = int.MaxValue; int num2 = 0; for (int i = 0; i < activeTeamCount; i++) { if (TeamHasMembers(ctx, i) && !IsTeamEliminated(ctx, i)) { int num3 = ctx.Registry.TeamDayScore(i); if (num3 < num) { num = num3; result = i; num2 = 1; } else if (num3 == num) { num2++; } } } tie = num2 > 1; return result; } } internal interface IGameMode { GameModeType Type { get; } bool IsTeamScored { get; } void OnMatchStart(MatchContext ctx); void OnGo(MatchContext ctx); void Tick(MatchContext ctx, float dt); void OnDayEnd(MatchContext ctx); void OnMatchEnd(MatchContext ctx); bool IsComplete(MatchContext ctx); string ScoreText(PvpPlayer p); string DisplayName(PvpPlayer p); string TeamScoreText(MatchContext ctx, int team); } internal abstract class GameModeBase : IGameMode { public abstract GameModeType Type { get; } public virtual bool IsTeamScored => false; public virtual void OnMatchStart(MatchContext ctx) { } public virtual void OnGo(MatchContext ctx) { } public virtual void Tick(MatchContext ctx, float dt) { } public virtual void OnDayEnd(MatchContext ctx) { } public virtual void OnMatchEnd(MatchContext ctx) { } public virtual bool IsComplete(MatchContext ctx) { return false; } public virtual string ScoreText(PvpPlayer p) { return p.Score.ToString(); } public virtual string DisplayName(PvpPlayer p) { return p.Name; } public virtual string TeamScoreText(MatchContext ctx, int team) { return ""; } } internal struct WorldMarker { internal Vector3 Pos; internal Color Color; internal string Label; internal int Team; internal WorldMarker(Vector3 pos, Color color, string label, int team = -1) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Pos = pos; Color = color; Label = label; Team = team; } } internal interface IWorldMarkers { void CollectMarkers(MatchContext ctx, PvpPlayer local, List into); } internal interface IBagSpillHandler { bool OnCarrierSpill(MatchContext ctx, PvpPlayer victim, int count, string reason); } internal class SpeedRunMode : GameModeBase, IWorldMarkers { private readonly List _route = new List(); private float _startTime; private int _winningTeam = -1; public override GameModeType Type => GameModeType.SpeedRun; public override bool IsTeamScored => true; public override void OnMatchStart(MatchContext ctx) { _route.Clear(); _winningTeam = -1; int count = ctx.Drops.Count; for (int i = 0; i < count; i++) { _route.Add(i); } for (int num = _route.Count - 1; num > 0; num--) { int index = Random.Range(0, num + 1); int value = _route[num]; _route[num] = _route[index]; _route[index] = value; } List players = ctx.Registry.Players; for (int j = 0; j < players.Count; j++) { players[j].NextObjective = 0; players[j].Score = 0; } ctx.Announcer.Announce($"Team race — first team with ALL players through the circuit wins! ({_route.Count} checkpoints)", 5f); } public override void OnGo(MatchContext ctx) { _startTime = Time.realtimeSinceStartup; } public override void Tick(MatchContext ctx, float dt) { //IL_0093: Unknown result type (might be due to invalid IL or missing references) if (_route.Count == 0 || _winningTeam >= 0) { return; } List players = ctx.Registry.Players; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; if (pvpPlayer.Valid && pvpPlayer.IsAlive && pvpPlayer.NextObjective < _route.Count) { int index = _route[pvpPlayer.NextObjective]; if (ctx.Drops.WithinZone(pvpPlayer.Position, index, ctx.PickupRadius)) { pvpPlayer.NextObjective++; pvpPlayer.Score = pvpPlayer.NextObjective; } } } int activeTeamCount = ctx.Registry.ActiveTeamCount; for (int j = 0; j < activeTeamCount; j++) { if (TeamAllFinished(ctx, j)) { _winningTeam = j; Finish(ctx, j); break; } } } private void Finish(MatchContext ctx, int team) { float num = Time.realtimeSinceStartup - _startTime; float value = PvpConfig.SpeedRunBestSeconds.Value; bool flag = value < 0f || num < value; if (flag) { PvpConfig.SpeedRunBestSeconds.Value = num; try { MelonPreferences.Save(); } catch { } } string arg = (flag ? " — NEW HIGH SCORE!" : $" (best {value:0.0}s)"); ctx.Announcer.Announce($"{ctx.Registry.TeamLabel(team)} win the Speed Run! {num:0.0}s{arg}", 8f); } public override bool IsComplete(MatchContext ctx) { return _winningTeam >= 0; } public override void OnMatchEnd(MatchContext ctx) { if (_winningTeam >= 0) { return; } int activeTeamCount = ctx.Registry.ActiveTeamCount; int num = -1; int num2 = -1; int num3 = -1; for (int i = 0; i < activeTeamCount; i++) { int num4 = TeamFinishedCount(ctx, i); int num5 = TeamProgress(ctx, i); if (num4 > num2 || (num4 == num2 && num5 > num3)) { num2 = num4; num3 = num5; num = i; } } if (num >= 0) { ctx.Announcer.Announce($"{ctx.Registry.TeamLabel(num)} lead the circuit ({TeamFinishedCount(ctx, num)}/{TeamMemberCount(ctx, num)} finished)", 6f); } } public override string TeamScoreText(MatchContext ctx, int team) { return $"{TeamFinishedCount(ctx, team)}/{TeamMemberCount(ctx, team)} done"; } public override string DisplayName(PvpPlayer p) { return PvpColors.TeamName(p.Team); } public override string ScoreText(PvpPlayer p) { return p.NextObjective + "/" + Mathf.Max(1, _route.Count); } public void CollectMarkers(MatchContext ctx, PvpPlayer local, List into) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) if (_route.Count == 0) { return; } List players = ctx.Registry.Players; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; if (pvpPlayer.NextObjective < _route.Count) { int index = _route[pvpPlayer.NextObjective]; into.Add(new WorldMarker(ctx.Drops.ZonePosition(index) + Vector3.up * 1.5f, PvpColors.For(pvpPlayer.Team), $"{ctx.Registry.TeamLabel(pvpPlayer.Team)}: {pvpPlayer.Name} ({pvpPlayer.NextObjective + 1}/{_route.Count})", pvpPlayer.Team)); } } } private bool TeamAllFinished(MatchContext ctx, int team) { List players = ctx.Registry.Players; bool result = false; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; if (pvpPlayer.Valid && pvpPlayer.Team == team) { result = true; if (pvpPlayer.NextObjective < _route.Count) { return false; } } } return result; } private int TeamFinishedCount(MatchContext ctx, int team) { List players = ctx.Registry.Players; int num = 0; for (int i = 0; i < players.Count; i++) { if (players[i].Team == team && players[i].NextObjective >= _route.Count) { num++; } } return num; } private static int TeamMemberCount(MatchContext ctx, int team) { List players = ctx.Registry.Players; int num = 0; for (int i = 0; i < players.Count; i++) { if (players[i].Team == team) { num++; } } return num; } private int TeamProgress(MatchContext ctx, int team) { List players = ctx.Registry.Players; int num = 0; for (int i = 0; i < players.Count; i++) { if (players[i].Team == team) { num += players[i].NextObjective; } } return num; } } internal class StealTheBagsMode : GameModeBase, IWorldMarkers, IBagSpillHandler { private struct Flight { public int Team; public int SourceZone; } private readonly List _ownedZones = new List(); private readonly Dictionary _zoneTeam = new Dictionary(); private readonly Dictionary _known = new Dictionary(); private readonly Dictionary _baseline = new Dictionary(); private readonly List _inFlight = new List(); private bool _active; private const float RaidRadius = 5f; private static string BagId => BagFactory.MatchId; private static int Floor => Mathf.Max(1, 1); public override GameModeType Type => GameModeType.StealTheBags; public override bool IsTeamScored => true; public override void OnMatchStart(MatchContext ctx) { _ownedZones.Clear(); _zoneTeam.Clear(); _known.Clear(); _baseline.Clear(); _inFlight.Clear(); _active = true; int floor = Floor; int num = Mathf.Max(1, ctx.Registry.ActiveTeamCount); for (int i = 0; i < num; i++) { int num2 = ctx.Drops.ZoneForIndex(i); if (num2 >= 0 && !_zoneTeam.ContainsKey(num2)) { DeadDrop val = ctx.Drops.Zone(num2); int num3 = (((Object)(object)val != (Object)null) ? DeadDropStash.Count(val, BagId) : 0); if ((Object)(object)val != (Object)null) { DeadDropStash.Insert(val, BagId, floor, BagFactory.Create); } _ownedZones.Add(num2); _zoneTeam[num2] = i; _baseline[num2] = num3; _known[num2] = (((Object)(object)val != (Object)null) ? DeadDropStash.Count(val, BagId) : (num3 + floor)); } } ctx.Announcer.Announce($"Grab bags from ENEMY team stashes and carry them to YOUR stash to capture! ({floor} per stash, most captures wins)", 5f); } public override void OnMatchEnd(MatchContext ctx) { _active = false; bool tie; int num = ctx.Registry.TeamTopScore(out tie); if (num >= 0) { int num2 = ctx.Registry.TeamScore(num); ctx.Announcer.Announce(tie ? $"It's a draw — {num2} capture(s) each!" : $"{ctx.Registry.TeamLabel(num)} win with {num2} capture(s)!", 6f); } } public override void Tick(MatchContext ctx, float dt) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) if (!_active) { return; } for (int i = 0; i < _ownedZones.Count; i++) { int num = _ownedZones[i]; DeadDrop val = ctx.Drops.Zone(num); if ((Object)(object)val == (Object)null) { continue; } int value; int num2 = (_zoneTeam.TryGetValue(num, out value) ? value : (-1)); Vector3 pos = ctx.Drops.ZonePosition(num); int num3 = DeadDropStash.Count(val, BagId); int value2; int num4 = (_known.TryGetValue(num, out value2) ? value2 : num3); if (num3 < num4) { int num5 = num4 - num3; PvpPlayer pvpPlayer = NearestOnTeam(ctx, pos, num2, sameTeam: false); if (pvpPlayer != null) { for (int j = 0; j < num5; j++) { _inFlight.Add(new Flight { Team = pvpPlayer.Team, SourceZone = num }); } ctx.Announcer.Announce(ctx.Registry.TeamLabel(pvpPlayer.Team) + " grabbed a bag from " + ctx.Registry.TeamLabel(num2) + "'s stash!", 3f, notify: false); _known[num] = num3; } else { DeadDropStash.NoteExternalRemoval(num5); RefillToFloor(ctx, num); PvpLog.Debug($"[MultiplayerPVP] StealTheBags: self-take from {ctx.Registry.TeamLabel(num2)}'s stash (zone {num}) — restocked to floor (anti-hoard)."); } } else { if (num3 <= num4) { continue; } int num6 = num3 - num4; PvpPlayer pvpPlayer2 = NearestOnTeam(ctx, pos, num2, sameTeam: true); if (pvpPlayer2 != null && CountForTeam(num2) > 0) { int num7 = Mathf.Min(num6, CountForTeam(num2)); pvpPlayer2.Score += num7; pvpPlayer2.BagsCaptured += num7; for (int k = 0; k < num7; k++) { int num8 = IndexOfTeam(num2); if (num8 < 0) { break; } int sourceZone = _inFlight[num8].SourceZone; _inFlight.RemoveAt(num8); int value3; int team = (_zoneTeam.TryGetValue(sourceZone, out value3) ? value3 : (-1)); PvpPlayer pvpPlayer3 = TeamRep(ctx, team); if (pvpPlayer3 != null) { pvpPlayer3.BagsLost++; } Refill(ctx, sourceZone); } ctx.Announcer.Announce($"{ctx.Registry.TeamLabel(num2)} captured {num7} bag(s)! ({ctx.Registry.TeamScore(num2)})", 4f, notify: false); for (int l = 0; l < num7; l++) { if (!DeadDropStash.RemoveOne(val, BagId)) { break; } } _known[num] = DeadDropStash.Count(val, BagId); continue; } PvpPlayer pvpPlayer4 = NearestOnTeam(ctx, pos, num2, sameTeam: false); if (pvpPlayer4 != null) { for (int m = 0; m < num6; m++) { if (!DeadDropStash.RemoveOne(val, BagId)) { break; } } int num9 = Mathf.Min(num6, CountForTeam(pvpPlayer4.Team)); for (int n = 0; n < num9; n++) { int num10 = IndexOfTeam(pvpPlayer4.Team); if (num10 < 0) { break; } int sourceZone2 = _inFlight[num10].SourceZone; _inFlight.RemoveAt(num10); Refill(ctx, sourceZone2); } _known[num] = DeadDropStash.Count(val, BagId); continue; } if (pvpPlayer2 != null) { ClampToFloor(ctx, num); continue; } for (int num11 = 0; num11 < num6; num11++) { if (!DeadDropStash.RemoveOne(val, BagId)) { break; } } _known[num] = DeadDropStash.Count(val, BagId); } } for (int num12 = _inFlight.Count - 1; num12 >= 0; num12--) { if (!TeamHasValidMembers(ctx, _inFlight[num12].Team)) { Refill(ctx, _inFlight[num12].SourceZone); _inFlight.RemoveAt(num12); } } } public bool OnCarrierSpill(MatchContext ctx, PvpPlayer victim, int count, string reason) { if (victim == null || count <= 0) { return true; } int num = 0; for (int i = 0; i < count; i++) { int num2 = IndexOfTeam(victim.Team); if (num2 < 0) { break; } int sourceZone = _inFlight[num2].SourceZone; _inFlight.RemoveAt(num2); Refill(ctx, sourceZone); num++; } for (int j = num; j < count; j++) { RefillAnyDepleted(ctx); } string arg = ((reason == "arrested") ? "was busted" : "went down"); ctx.Announcer.Announce($"A {ctx.Registry.TeamLabel(victim.Team)} carrier {arg} — {count} bag(s) returned to the stash.", 4f, notify: false); return true; } private void Refill(MatchContext ctx, int zone) { DeadDrop val = ctx.Drops.Zone(zone); if (!((Object)(object)val == (Object)null)) { int value; int num = (_baseline.TryGetValue(zone, out value) ? value : 0); int num2 = num + Floor; if (DeadDropStash.Count(val, BagId) < num2) { DeadDropStash.Insert(val, BagId, 1, BagFactory.Create); } _known[zone] = DeadDropStash.Count(val, BagId); } } private void RefillToFloor(MatchContext ctx, int zone) { DeadDrop val = ctx.Drops.Zone(zone); if ((Object)(object)val == (Object)null) { return; } int value; int num = (_baseline.TryGetValue(zone, out value) ? value : 0) + Floor; int num2 = num - InFlightFromZone(zone); int num3 = DeadDropStash.Count(val, BagId); for (int i = num3; i < num2; i++) { if (DeadDropStash.Insert(val, BagId, 1, BagFactory.Create) <= 0) { break; } } _known[zone] = DeadDropStash.Count(val, BagId); } private void ClampToFloor(MatchContext ctx, int zone) { DeadDrop val = ctx.Drops.Zone(zone); if (!((Object)(object)val == (Object)null)) { int value; int target = (_baseline.TryGetValue(zone, out value) ? value : 0) + Floor; DeadDropStash.RemoveSurplusUntracked(val, BagId, target); _known[zone] = DeadDropStash.Count(val, BagId); } } private int InFlightFromZone(int zone) { int num = 0; for (int i = 0; i < _inFlight.Count; i++) { if (_inFlight[i].SourceZone == zone) { num++; } } return num; } private void RefillAnyDepleted(MatchContext ctx) { for (int i = 0; i < _ownedZones.Count; i++) { int num = _ownedZones[i]; DeadDrop val = ctx.Drops.Zone(num); if (!((Object)(object)val == (Object)null)) { int value; int num2 = (_baseline.TryGetValue(num, out value) ? value : 0) + Floor; if (DeadDropStash.Count(val, BagId) < num2) { Refill(ctx, num); break; } } } } private int CountForTeam(int team) { int num = 0; for (int i = 0; i < _inFlight.Count; i++) { if (_inFlight[i].Team == team) { num++; } } return num; } private int IndexOfTeam(int team) { for (int i = 0; i < _inFlight.Count; i++) { if (_inFlight[i].Team == team) { return i; } } return -1; } private static bool TeamHasValidMembers(MatchContext ctx, int team) { List players = ctx.Registry.Players; for (int i = 0; i < players.Count; i++) { if (players[i].Team == team && players[i].Valid) { return true; } } return false; } private static PvpPlayer NearestOnTeam(MatchContext ctx, Vector3 pos, int ownerTeam, bool sameTeam) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) List players = ctx.Registry.Players; PvpPlayer result = null; float num = 5f; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; if (!pvpPlayer.Valid) { continue; } bool flag = pvpPlayer.Team == ownerTeam; if (flag == sameTeam) { float num2 = Vector3.Distance(pos, pvpPlayer.Position); if (num2 <= num) { num = num2; result = pvpPlayer; } } } return result; } private static PvpPlayer TeamRep(MatchContext ctx, int team) { if (team < 0) { return null; } List players = ctx.Registry.Players; for (int i = 0; i < players.Count; i++) { if (players[i].Valid && players[i].Team == team) { return players[i]; } } return null; } public override string TeamScoreText(MatchContext ctx, int team) { return ctx.Registry.TeamScore(team) + " captured"; } public override string DisplayName(PvpPlayer p) { return PvpColors.TeamName(p.Team); } public override string ScoreText(PvpPlayer p) { return p.Score + " captured"; } public void CollectMarkers(MatchContext ctx, PvpPlayer local, List into) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < _ownedZones.Count; i++) { int num = _ownedZones[i]; int value; int num2 = (_zoneTeam.TryGetValue(num, out value) ? value : num); into.Add(new WorldMarker(ctx.Drops.ZonePosition(num) + Vector3.up * 1.4f, PvpColors.For(num2), ctx.Registry.TeamLabel(num2) + " stash")); } } } internal class TeamsMode : GameModeBase { public override GameModeType Type => GameModeType.Teams; public override bool IsTeamScored => true; internal static string TeamName(int team) { return PvpColors.TeamName(team); } public override void OnMatchStart(MatchContext ctx) { ctx.Announcer.Announce($"{ctx.Registry.ActiveTeamCount} teams — fight for dominance!"); } public override void Tick(MatchContext ctx, float dt) { Combat.ProcessEliminations(ctx, teamMode: true); } public override void OnMatchEnd(MatchContext ctx) { bool tie; int num = ctx.Registry.TeamTopScore(out tie); if (num >= 0) { int num2 = ctx.Registry.TeamScore(num); ctx.Announcer.Announce(tie ? $"It's a draw — {num2} eliminations at the top!" : $"{ctx.Registry.TeamLabel(num)} win with {num2} eliminations!", 6f); } } public override string TeamScoreText(MatchContext ctx, int team) { return $"{ctx.Registry.TeamScore(team)} elim"; } public override string DisplayName(PvpPlayer p) { return TeamName(p.Team); } public override string ScoreText(PvpPlayer p) { return $"{p.Score} elim"; } } } namespace MultiplayerPVP.Core { internal class Announcer { internal string Banner { get; private set; } internal float BannerExpiry { get; private set; } internal bool HasBanner => !string.IsNullOrEmpty(Banner) && Time.time < BannerExpiry; internal string LocalBanner { get; private set; } internal float LocalBannerExpiry { get; private set; } internal Color LocalBannerColor { get; private set; } = Color.white; internal bool HasLocalBanner => !string.IsNullOrEmpty(LocalBanner) && Time.time < LocalBannerExpiry; internal void Announce(string text, float seconds = 4f, bool notify = true) { Banner = text; BannerExpiry = Time.time + seconds; PvpLog.Debug("[MultiplayerPVP][ANNOUNCE] " + text); if (notify) { PushNotification("Multiplayer PVP", text, seconds); } } internal void Notify(string text, float seconds = 4f) { PvpLog.Debug("[MultiplayerPVP][NOTIFY] " + text); PushNotification("Multiplayer PVP", text, seconds); } internal void ShowLocalBanner(string text, float seconds, Color color) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) LocalBanner = text; LocalBannerExpiry = Time.time + seconds; LocalBannerColor = color; PvpLog.Debug("[MultiplayerPVP][LOCALBANNER] " + text); } internal void Clear() { Banner = null; BannerExpiry = 0f; LocalBanner = null; LocalBannerExpiry = 0f; } private static void PushNotification(string title, string subtitle, float duration) { try { NotificationsManager instance = Singleton.Instance; if (GameCompat.Alive((Object)(object)instance)) { instance.SendNotification(title, subtitle, (Sprite)null, Mathf.Clamp(duration, 2f, 8f), true); } } catch { } } } internal static class BagFactory { internal static string MatchId => string.IsNullOrWhiteSpace(PvpConfig.BagProductId.Value) ? "ogkush" : PvpConfig.BagProductId.Value; private static string PackagingId => string.IsNullOrWhiteSpace(PvpConfig.BagPackagingId.Value) ? "baggie" : PvpConfig.BagPackagingId.Value; internal static ItemInstance Create(int quantity = 1) { if (quantity < 1) { quantity = 1; } try { ItemDefinition item = Registry.GetItem(MatchId); if (!GameCompat.Alive((Object)(object)item)) { MelonLogger.Warning("[MultiplayerPVP] Bag product '" + MatchId + "' not found in Registry."); return null; } ItemInstance defaultInstance = item.GetDefaultInstance(quantity); if (defaultInstance == null) { return null; } ProductItemInstance val = GameCompat.As(defaultInstance); if (val != null) { try { ItemDefinition item2 = Registry.GetItem(PackagingId); PackagingDefinition val2 = (GameCompat.Alive((Object)(object)item2) ? GameCompat.As(item2) : null); if ((Object)(object)val2 != (Object)null) { val.SetPackaging(val2); } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] BagFactory packaging: " + ex.Message); } } return defaultInstance; } catch (Exception ex2) { MelonLogger.Warning("[MultiplayerPVP] BagFactory.Create: " + ex2.Message); return null; } } } internal static class BagInventory { internal static int CountLocal() { int num = 0; try { PlayerInventory instance = PlayerSingleton.Instance; if (!GameCompat.Alive((Object)(object)instance)) { return 0; } List list; try { list = GameCompat.ToManaged(instance.GetAllInventorySlots()); } catch { return 0; } if (list == null) { return 0; } string matchId = BagFactory.MatchId; for (int i = 0; i < list.Count; i++) { ItemSlot val = list[i]; if (val == null) { continue; } ItemInstance itemInstance; try { itemInstance = val.ItemInstance; } catch { continue; } if (itemInstance == null) { continue; } string iD; try { iD = ((BaseItemInstance)itemInstance).ID; } catch { continue; } if (string.Equals(iD, matchId, StringComparison.OrdinalIgnoreCase)) { try { num += Math.Max(1, val.Quantity); } catch { num++; } } } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] BagInventory.CountLocal: " + ex.Message); } return num; } internal static int RemoveLocal(int max = int.MaxValue) { if (max <= 0) { return 0; } int num = 0; try { PlayerInventory instance = PlayerSingleton.Instance; if (!GameCompat.Alive((Object)(object)instance)) { return 0; } List list; try { list = GameCompat.ToManaged(instance.GetAllInventorySlots()); } catch { return 0; } if (list == null) { return 0; } string matchId = BagFactory.MatchId; for (int i = 0; i < list.Count && num < max; i++) { ItemSlot val = list[i]; if (val == null) { continue; } ItemInstance itemInstance; try { itemInstance = val.ItemInstance; } catch { continue; } if (itemInstance == null) { continue; } string iD; try { iD = ((BaseItemInstance)itemInstance).ID; } catch { continue; } if (!string.Equals(iD, matchId, StringComparison.OrdinalIgnoreCase)) { continue; } int num2; try { num2 = Math.Max(1, val.Quantity); } catch { num2 = 1; } int num3 = Math.Min(num2, max - num); try { if (num3 >= num2) { val.ClearStoredInstance(true); } else { val.ChangeQuantity(-num3, true); } } catch { continue; } num += num3; } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] BagInventory.RemoveLocal: " + ex.Message); } return num; } } internal static class BagItem { private static bool _equipped; private static string _equippedId; internal static void EquipLocal() { if (_equipped) { return; } string matchId = BagFactory.MatchId; if (string.IsNullOrWhiteSpace(matchId)) { return; } try { PlayerInventory instance = PlayerSingleton.Instance; if (GameCompat.Alive((Object)(object)instance)) { ItemInstance val = BagFactory.Create(); if (val != null) { instance.AddItemToInventory(val); EquipFirstSlotHolding(instance, matchId); _equipped = true; _equippedId = matchId; } } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] BagItem.EquipLocal: " + ex.Message); } } internal static void UnequipLocal() { if (!_equipped) { return; } try { PlayerInventory instance = PlayerSingleton.Instance; if (GameCompat.Alive((Object)(object)instance) && !string.IsNullOrEmpty(_equippedId)) { instance.RemoveAmountOfItem(_equippedId, 1u); } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] BagItem.UnequipLocal: " + ex.Message); } finally { _equipped = false; _equippedId = null; } } internal static void SetLocalCarrier(bool isLocalCarrier) { if (isLocalCarrier) { EquipLocal(); } else { UnequipLocal(); } } internal static void Reset() { _equipped = false; _equippedId = null; } private static void EquipFirstSlotHolding(PlayerInventory inv, string id) { try { List hotbarSlots = inv.hotbarSlots; if (hotbarSlots == null) { return; } int count; try { count = hotbarSlots.Count; } catch { return; } for (int i = 0; i < count; i++) { HotbarSlot val; try { val = hotbarSlots[i]; } catch { continue; } if (val == null) { continue; } ItemInstance itemInstance; try { itemInstance = ((ItemSlot)val).ItemInstance; } catch { continue; } if (itemInstance == null) { continue; } string iD; try { iD = ((BaseItemInstance)itemInstance).ID; } catch { continue; } if (!string.Equals(iD, id, StringComparison.OrdinalIgnoreCase)) { continue; } try { inv.Equip(val); break; } catch { break; } } } catch { } } } internal static class ChaosDirector { private sealed class Hunter { internal NPC Npc; internal Player Target; internal float RefreshAt; internal bool Civilian; } private const float RETARGET_INTERVAL = 1.5f; private const float REFRESH_INTERVAL = 10f; private const float NPC_GIVE_UP = 200f; private static readonly List _hunters = new List(); private static float _cd; private static float _nextWave; internal static bool On { get; private set; } internal static bool IsHunting(Player p) { if (!On || (Object)(object)p == (Object)null) { return false; } for (int i = 0; i < _hunters.Count; i++) { try { if ((Object)(object)_hunters[i].Target == (Object)(object)p) { return true; } } catch { } } return false; } internal static void SetOn(bool on) { if (On != on) { On = on; if (!on) { Teardown(); } else { _cd = 0f; _nextWave = 0f; } PvpLog.Debug("[MultiplayerPVP][Chaos] " + (on ? "ON — the city hunts the players." : "OFF.")); } } internal static void Reset() { _hunters.Clear(); On = false; } internal static void Tick(float dt) { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: 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_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_0397: Unknown result type (might be due to invalid IL or missing references) if (!On) { return; } try { if (!SessionUtil.IsServerAuthority()) { SetOn(on: false); return; } _cd -= dt; if (_cd > 0f) { return; } _cd = 1.5f; float num = Mathf.Clamp(PvpConfig.ChaosRadius.Value, 15f, 200f); float time = Time.time; for (int num2 = _hunters.Count - 1; num2 >= 0; num2--) { Hunter hunter = _hunters[num2]; if (NpcInvalid(hunter.Npc)) { _hunters.RemoveAt(num2); continue; } Vector3 position; try { position = ((Component)hunter.Npc).transform.position; } catch { _hunters.RemoveAt(num2); continue; } if ((Object)(object)NearestAlivePlayer(position, num * 2f) == (Object)null && TargetInvalid(hunter.Target)) { Calm(hunter.Npc); _hunters.RemoveAt(num2); } else if (TargetInvalid(hunter.Target)) { Player val = NearestAlivePlayer(position, num * 2f); if ((Object)(object)val == (Object)null) { Calm(hunter.Npc); _hunters.RemoveAt(num2); } else { Sic(hunter, val, time); } } else if (time >= hunter.RefreshAt) { Sic(hunter, hunter.Target, time); } } int num3 = Mathf.Clamp(PvpConfig.ChaosMaxNpcs.Value, 1, 16); if (time < _nextWave || _hunters.Count >= num3) { return; } _nextWave = time + Mathf.Max(5f, PvpConfig.ChaosWaveSeconds.Value); try { List officers = PoliceOfficer.Officers; if (officers != null) { for (int i = 0; i < officers.Count; i++) { if (_hunters.Count >= num3) { break; } try { PoliceOfficer val2 = officers[i]; if (!NpcInvalid((NPC)(object)val2) && !IsTracked((NPC)(object)val2)) { Player val3 = NearestAlivePlayer(((Component)val2).transform.position, num); if (!((Object)(object)val3 == (Object)null)) { Hunter hunter2 = new Hunter { Npc = (NPC)(object)val2, Civilian = false }; Sic(hunter2, val3, time); _hunters.Add(hunter2); } } } catch { } } } } catch { } if (PvpConfig.ChaosCivilians.Value) { try { List nPCRegistry = NPCManager.NPCRegistry; if (nPCRegistry != null) { for (int j = 0; j < nPCRegistry.Count; j++) { if (_hunters.Count >= num3) { break; } try { NPC val4 = nPCRegistry[j]; if (!NpcInvalid(val4) && !IsTracked(val4) && !((Object)(object)GameCompat.As(val4) != (Object)null) && !((Object)(object)GameCompat.As(val4) != (Object)null) && !IsPoolDummy(val4)) { Player val5 = NearestAlivePlayer(((Component)val4).transform.position, num); if (!((Object)(object)val5 == (Object)null)) { Hunter hunter3 = new Hunter { Npc = val4, Civilian = true }; Sic(hunter3, val5, time); _hunters.Add(hunter3); } } } catch { } } } } catch { } } PvpLog.Debug($"[MultiplayerPVP][Chaos] wave: {_hunters.Count}/{num3} hunters active."); } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP][Chaos] Tick: " + ex.Message); } } private static void Sic(Hunter h, Player target, float now) { try { object obj; if (target == null) { obj = null; } else { GameObject gameObject = ((Component)target).gameObject; obj = ((gameObject != null) ? gameObject.GetComponent() : null); } NetworkObject val = (NetworkObject)obj; NPC npc = h.Npc; CombatBehaviour val2 = ((npc == null) ? null : npc.Behaviour?.CombatBehaviour); if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null)) { try { val2.GiveUpRange = 200f; } catch { } val2.SetTargetAndEnable_Server(val); h.Target = target; h.RefreshAt = now + 10f; } } catch { } } private static void Calm(NPC npc) { try { CombatBehaviour val = ((npc == null) ? null : npc.Behaviour?.CombatBehaviour); if ((Object)(object)val != (Object)null) { try { ((Behaviour)val).Deactivate(); } catch { } try { ((Behaviour)val).Disable(); return; } catch { return; } } } catch { } } private static bool IsTracked(NPC npc) { for (int i = 0; i < _hunters.Count; i++) { try { if ((Object)(object)_hunters[i].Npc == (Object)(object)npc) { return true; } } catch { } } return false; } private static bool IsPoolDummy(NPC npc) { try { if ((Object)(object)((Component)npc).gameObject != (Object)null && !((Component)npc).gameObject.activeInHierarchy) { return true; } string text = ""; try { text = npc.ID ?? ""; } catch { } try { text = text + " " + npc.FullName; } catch { } try { text = text + " " + ((Object)npc).name; } catch { } text = text.ToLowerInvariant(); return text.Contains("goon") || text.Contains("benzi"); } catch { return true; } } private static bool NpcInvalid(NPC npc) { try { if ((Object)(object)npc == (Object)null || (Object)(object)((Component)npc).transform == (Object)null) { return true; } NPCHealth health = npc.Health; return (Object)(object)health == (Object)null || health.IsDead || health.IsKnockedOut; } catch { return true; } } private static bool TargetInvalid(Player p) { try { if ((Object)(object)p == (Object)null || (Object)(object)((Component)p).transform == (Object)null) { return true; } PlayerHealth componentInChildren = ((Component)p).GetComponentInChildren(true); return (Object)(object)componentInChildren != (Object)null && !componentInChildren.IsAlive; } catch { return true; } } internal static Player NearestAlivePlayer(Vector3 pos, float radius) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) Player result = null; float num = radius; try { List playerList = Player.PlayerList; if (playerList == null) { return null; } for (int i = 0; i < playerList.Count; i++) { try { Player val = playerList[i]; if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).transform == (Object)null) { continue; } PlayerHealth componentInChildren = ((Component)val).GetComponentInChildren(true); if (!((Object)(object)componentInChildren != (Object)null) || componentInChildren.IsAlive) { float num2 = Vector3.Distance(((Component)val).transform.position, pos); if (num2 < num) { num = num2; result = val; } } } catch { } } } catch { } return result; } private static void Teardown() { for (int i = 0; i < _hunters.Count; i++) { try { Calm(_hunters[i].Npc); } catch { } } _hunters.Clear(); } } internal static class ChaosQuiet { private sealed class Muted { internal VisionCone Cone; internal bool OrigIcons; internal bool OrigTremolo; } private const float SWEEP_INTERVAL = 3f; private static readonly List _muted = new List(); private static float _cd; private static bool _wasActive; private static int _lastLogged = -1; internal static void Tick(float dt, bool chaosActive) { try { if (!chaosActive) { if (_wasActive) { RestoreAll(); } _wasActive = false; return; } _wasActive = true; _cd -= dt; if (_cd > 0f) { return; } _cd = 3f; try { List nPCRegistry = NPCManager.NPCRegistry; if (nPCRegistry != null) { for (int i = 0; i < nPCRegistry.Count; i++) { try { Mute(ConeOf(nPCRegistry[i])); } catch { } } } } catch { } try { CartelGoon[] array = Object.FindObjectsOfType(); if (array != null) { for (int j = 0; j < array.Length; j++) { try { CartelGoon val = array[j]; if (!((Object)(object)val == (Object)null) && val.IsGoonSpawned) { Mute(ConeOf((NPC)(object)val)); } } catch { } } } } catch { } if (_muted.Count != _lastLogged) { _lastLogged = _muted.Count; PvpLog.Debug($"[MultiplayerPVP][Chaos] quiet: {_muted.Count} vision cone(s) muted."); } } catch { } } private static VisionCone ConeOf(NPC npc) { try { NPCAwareness val = (((Object)(object)npc != (Object)null) ? npc.Awareness : null); return ((Object)(object)val != (Object)null) ? val.VisionCone : null; } catch { return null; } } private static void Mute(VisionCone cone) { if (!GameCompat.Alive((Object)(object)cone)) { return; } bool worldspaceIconsEnabled; bool useTremoloSound; try { worldspaceIconsEnabled = cone.WorldspaceIconsEnabled; useTremoloSound = cone.UseTremoloSound; } catch { return; } if (!worldspaceIconsEnabled && !useTremoloSound) { return; } for (int i = 0; i < _muted.Count; i++) { if ((Object)(object)_muted[i].Cone == (Object)(object)cone) { Apply(cone); return; } } _muted.Add(new Muted { Cone = cone, OrigIcons = worldspaceIconsEnabled, OrigTremolo = useTremoloSound }); Apply(cone); } private static void Apply(VisionCone cone) { try { cone.WorldspaceIconsEnabled = false; cone.UseTremoloSound = false; } catch { } } internal static void RestoreAll() { for (int i = 0; i < _muted.Count; i++) { try { Muted muted = _muted[i]; if (GameCompat.Alive((Object)(object)muted.Cone)) { muted.Cone.WorldspaceIconsEnabled = muted.OrigIcons; muted.Cone.UseTremoloSound = muted.OrigTremolo; } } catch { } } _muted.Clear(); _cd = 0f; _lastLogged = -1; } internal static void Reset() { _muted.Clear(); _wasActive = false; _cd = 0f; _lastLogged = -1; } } internal static class Combat { internal static void ProcessEliminations(MatchContext ctx, bool teamMode) { List players = ctx.Registry.Players; for (int i = 0; i < players.Count; i++) { try { PvpPlayer pvpPlayer = players[i]; if (!pvpPlayer.Valid || !pvpPlayer.PollWentDown()) { continue; } if (RiotChaos.IsDownChaosCaused(pvpPlayer)) { ctx.Announcer.Announce(pvpPlayer.Name + " was taken out by the chaos!", 3f, notify: false); continue; } PvpPlayer pvpPlayer2 = NearestLivingEnemy(ctx, pvpPlayer, teamMode); if (pvpPlayer2 != null) { ctx.Award(pvpPlayer2, 1); ctx.Announcer.Announce(pvpPlayer2.Name + " eliminated " + pvpPlayer.Name + "!", 3f, notify: false); } else { ctx.Announcer.Announce(pvpPlayer.Name + " went down.", 3f, notify: false); } } catch (Exception ex) { MelonLogger.Warning("Combat: " + ex.Message); } } } internal static PvpPlayer NearestLivingEnemy(MatchContext ctx, PvpPlayer victim, bool teamMode) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) List players = ctx.Registry.Players; PvpPlayer result = null; float num = float.MaxValue; Vector3 position = victim.Position; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; if (pvpPlayer != victim && pvpPlayer.Valid && pvpPlayer.IsAlive && (!teamMode || pvpPlayer.Team != victim.Team)) { float num2 = Vector3.Distance(position, pvpPlayer.Position); if (num2 < num) { num = num2; result = pvpPlayer; } } } return result; } } internal static class DamageTracker { private static readonly Dictionary _lastHit = new Dictionary(); internal static void RecordHit(int playerInstanceId) { _lastHit[playerInstanceId] = Time.realtimeSinceStartup; } internal static bool WasHitSince(int playerInstanceId, float since) { float value; return _lastHit.TryGetValue(playerInstanceId, out value) && value >= since; } internal static void Clear() { _lastHit.Clear(); } } internal class DeadDropManager { internal const int MaxZones = 4; internal readonly List Zones = new List(); private static int _ndProbe; internal int Count => Zones.Count; internal bool Ready => Zones.Count > 0; internal void Clear() { Zones.Clear(); } internal static bool ExcludeGazebo() { if (_ndProbe == 0) { _ndProbe = 2; try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { string name = assembly.GetName().Name; if (!string.IsNullOrEmpty(name) && (name.IndexOf("TheMob", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Ndrang", StringComparison.OrdinalIgnoreCase) >= 0)) { _ndProbe = 1; break; } } } catch { } if (_ndProbe == 1) { PvpLog.Debug("[MultiplayerPVP] Ndrangheta detected — excluding the 'Gazebo' dead drop from the PVP pool."); } } return _ndProbe == 1; } private static List FilterGazebo(List drops) { List list = new List(drops.Count); for (int i = 0; i < drops.Count; i++) { DeadDrop val = drops[i]; string text = null; try { if (GameCompat.Alive((Object)(object)val)) { text = val.DeadDropName; } } catch { } if (text == null || text.IndexOf("Gazebo", StringComparison.OrdinalIgnoreCase) < 0) { list.Add(val); } } return list; } internal void Refresh(bool reshuffle, bool spread = false) { if (!reshuffle && Zones.Count > 0) { for (int num = Zones.Count - 1; num >= 0; num--) { if (!GameCompat.Alive((Object)(object)Zones[num])) { Zones.RemoveAt(num); } } if (Zones.Count > 0) { return; } } Zones.Clear(); List list = GameCompat.ToManaged(DeadDrop.DeadDrops); if (ExcludeGazebo()) { list = FilterGazebo(list); } if (list.Count == 0) { MelonLogger.Warning("[MultiplayerPVP] No dead drops found yet."); return; } int num2 = Mathf.Min(4, list.Count); if (spread) { SelectSpread(list, num2); } else { for (int num3 = list.Count - 1; num3 > 0; num3--) { int index = Random.Range(0, num3 + 1); DeadDrop value = list[num3]; list[num3] = list[index]; list[index] = value; } for (int i = 0; i < num2; i++) { Zones.Add(list[i]); } } PvpLog.Debug(string.Format("[MultiplayerPVP] Selected {0} dead-drop zone(s) from {1} available{2}.", Zones.Count, list.Count, spread ? " (max-spread)" : "")); } private void SelectSpread(List all, int take) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) int count = all.Count; Vector3[] array = (Vector3[])(object)new Vector3[count]; for (int i = 0; i < count; i++) { try { array[i] = (GameCompat.Alive((Object)(object)all[i]) ? ((Component)all[i]).transform.position : Vector3.zero); } catch { array[i] = Vector3.zero; } } List list = new List(take); int num = 0; int num2 = 0; float num3 = -1f; Vector3 val; for (int j = 0; j < count; j++) { for (int k = j + 1; k < count; k++) { val = array[j] - array[k]; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude > num3) { num3 = sqrMagnitude; num = j; num2 = k; } } } list.Add(num); if (take > 1 && num2 != num) { list.Add(num2); } while (list.Count < take && list.Count < count) { int num4 = -1; float num5 = -1f; for (int l = 0; l < count; l++) { if (list.Contains(l)) { continue; } float num6 = float.MaxValue; for (int m = 0; m < list.Count; m++) { val = array[l] - array[list[m]]; float sqrMagnitude2 = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude2 < num6) { num6 = sqrMagnitude2; } } if (num6 > num5) { num5 = num6; num4 = l; } } if (num4 < 0) { break; } list.Add(num4); } for (int n = 0; n < list.Count; n++) { Zones.Add(all[list[n]]); } } internal DeadDrop Zone(int index) { if (index < 0 || index >= Zones.Count) { return null; } DeadDrop val = Zones[index]; return GameCompat.Alive((Object)(object)val) ? val : null; } internal Vector3 ZonePosition(int index) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (index < 0 || index >= Zones.Count) { return Vector3.zero; } DeadDrop val = Zones[index]; if (!GameCompat.Alive((Object)(object)val)) { return Vector3.zero; } try { return ((Component)val).transform.position; } catch { return Vector3.zero; } } internal DeadDrop RandomNonZoneDrop() { List list = GameCompat.ToManaged(DeadDrop.DeadDrops); if (ExcludeGazebo()) { list = FilterGazebo(list); } if (list.Count == 0) { return null; } List list2 = new List(list.Count); for (int i = 0; i < list.Count; i++) { DeadDrop val = list[i]; bool flag = false; for (int j = 0; j < Zones.Count; j++) { if ((Object)(object)Zones[j] == (Object)(object)val) { flag = true; break; } } if (!flag) { list2.Add(val); } } if (list2.Count == 0) { list2 = list; } return list2[Random.Range(0, list2.Count)]; } internal int ZoneForIndex(int teamOrPlayerIndex) { if (Zones.Count == 0) { return -1; } return teamOrPlayerIndex % Zones.Count; } internal int NearestZone(Vector3 pos, out float distance) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) distance = float.MaxValue; int result = -1; for (int i = 0; i < Zones.Count; i++) { Vector3 val = ZonePosition(i); float num = Vector3.Distance(pos, val); if (num < distance) { distance = num; result = i; } } return result; } internal bool WithinZone(Vector3 pos, int index, float radius) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (index < 0 || index >= Zones.Count) { return false; } return Vector3.Distance(pos, ZonePosition(index)) <= radius; } } internal static class DeadDropStash { private static readonly List _touched = new List(); private static int _addedTotal; private static string _itemId; private static WorldStorageEntity StoreOf(DeadDrop drop) { if (!GameCompat.Alive((Object)(object)drop)) { return null; } try { WorldStorageEntity storage = drop.Storage; return GameCompat.Alive((Object)(object)storage) ? storage : null; } catch { return null; } } private static void Track(WorldStorageEntity store, string itemId, int delta) { _itemId = itemId; _addedTotal += delta; if (_addedTotal < 0) { _addedTotal = 0; } if (delta <= 0) { return; } bool flag = false; for (int i = 0; i < _touched.Count; i++) { if ((Object)(object)_touched[i] == (Object)(object)store) { flag = true; break; } } if (!flag) { _touched.Add(store); } } internal static int Insert(DeadDrop drop, string itemId, int qty = 1, Func factory = null) { if (string.IsNullOrWhiteSpace(itemId) || qty <= 0) { return 0; } WorldStorageEntity val = StoreOf(drop); if ((Object)(object)val == (Object)null) { return 0; } try { ItemInstance val2; if (factory != null) { val2 = factory(qty); } else { ItemDefinition item = Registry.GetItem(itemId); val2 = (GameCompat.Alive((Object)(object)item) ? item.GetDefaultInstance(qty) : null); } if (val2 == null) { return 0; } int num = Count(drop, itemId); ((StorageEntity)val).InsertItem(val2, true); int num2 = Count(drop, itemId) - num; if (num2 <= 0) { return 0; } Track(val, itemId, num2); return num2; } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] DeadDropStash.Insert: " + ex.Message); return 0; } } internal static int InsertSpread(IEnumerable drops, string itemId, int qty, Func factory = null) { if (drops == null || qty <= 0) { return 0; } int num = qty; foreach (DeadDrop drop in drops) { if (num <= 0) { break; } if (GameCompat.Alive((Object)(object)drop)) { num -= Insert(drop, itemId, num, factory); } } return qty - num; } internal static bool RemoveOne(DeadDrop drop, string itemId) { WorldStorageEntity val = StoreOf(drop); if ((Object)(object)val == (Object)null) { return false; } try { List list = GameCompat.ToManaged(((StorageEntity)val).ItemSlots); for (int i = 0; i < list.Count; i++) { ItemSlot val2 = list[i]; if (val2 == null) { continue; } ItemInstance itemInstance; try { itemInstance = val2.ItemInstance; } catch { continue; } if (itemInstance == null) { continue; } string iD; try { iD = ((BaseItemInstance)itemInstance).ID; } catch { continue; } if (!string.Equals(iD, itemId, StringComparison.OrdinalIgnoreCase)) { continue; } int num; try { num = Math.Max(1, val2.Quantity); } catch { num = 1; } try { if (num > 1) { val2.ChangeQuantity(-1, true); } else { val2.ClearStoredInstance(true); } } catch { return false; } if (_addedTotal > 0) { _addedTotal--; } return true; } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] DeadDropStash.RemoveOne: " + ex.Message); } return false; } internal static void NoteExternalRemoval(int n) { if (n > 0) { _addedTotal -= n; if (_addedTotal < 0) { _addedTotal = 0; } } } internal static int RemoveSurplusUntracked(DeadDrop drop, string itemId, int target) { int num = 0; while (Count(drop, itemId) > target) { int addedTotal = _addedTotal; if (!RemoveOne(drop, itemId)) { break; } _addedTotal = addedTotal; num++; } return num; } internal static int Count(DeadDrop drop, string itemId) { WorldStorageEntity val = StoreOf(drop); if ((Object)(object)val == (Object)null) { return 0; } int num = 0; try { List list = GameCompat.ToManaged(((StorageEntity)val).ItemSlots); for (int i = 0; i < list.Count; i++) { ItemSlot val2 = list[i]; if (val2 == null) { continue; } ItemInstance itemInstance; try { itemInstance = val2.ItemInstance; } catch { continue; } if (itemInstance == null) { continue; } string iD; try { iD = ((BaseItemInstance)itemInstance).ID; } catch { continue; } if (string.Equals(iD, itemId, StringComparison.OrdinalIgnoreCase)) { try { num += Math.Max(1, val2.Quantity); } catch { num++; } } } } catch { } return num; } internal static void ClearAllOurs() { int num = _addedTotal; string itemId = _itemId; if (num > 0 && !string.IsNullOrEmpty(itemId)) { for (int i = 0; i < _touched.Count; i++) { if (num <= 0) { break; } WorldStorageEntity val = _touched[i]; if (!GameCompat.Alive((Object)(object)val)) { continue; } try { List list = GameCompat.ToManaged(((StorageEntity)val).ItemSlots); for (int j = 0; j < list.Count && num > 0; j++) { ItemSlot val2 = list[j]; if (val2 == null) { continue; } ItemInstance itemInstance; try { itemInstance = val2.ItemInstance; } catch { continue; } if (itemInstance == null) { continue; } string iD; try { iD = ((BaseItemInstance)itemInstance).ID; } catch { continue; } if (!string.Equals(iD, itemId, StringComparison.OrdinalIgnoreCase)) { continue; } int num2; try { num2 = Math.Max(1, val2.Quantity); } catch { num2 = 1; } int num3 = Math.Min(num2, num); try { if (num3 >= num2) { val2.ClearStoredInstance(true); } else { val2.ChangeQuantity(-num3, true); } } catch { continue; } num -= num3; } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] DeadDropStash.ClearAllOurs: " + ex.Message); } } } _touched.Clear(); _addedTotal = 0; _itemId = null; } } internal static class DoubleJump { private static bool _checked; private static bool _remote; private static bool _airJumpReady; private static bool _jumpHeldPrev; internal static bool Checked => _checked; internal static void SetChecked(bool on) { _checked = on; } internal static void SetRemote(bool on) { _remote = on; } internal static void RoundEnded() { _checked = false; } internal static void Reset() { _checked = false; _remote = false; _airJumpReady = false; _jumpHeldPrev = false; } internal static void Tick(MatchController c) { if (c == null) { return; } if ((c.Phase != MatchPhase.Countdown && c.Phase != MatchPhase.Active) || !SessionUtil.IsMultiplayer() || !(SessionUtil.IsServerAuthority() ? _checked : _remote)) { _airJumpReady = false; _jumpHeldPrev = false; return; } bool flag; try { bool key = Input.GetKey((KeyCode)32); flag = key && !_jumpHeldPrev; _jumpHeldPrev = key; } catch { flag = false; } try { PlayerMovement instance = PlayerSingleton.Instance; if (!GameCompat.Alive((Object)(object)instance)) { return; } bool isGrounded; try { isGrounded = instance.IsGrounded; } catch { return; } if (isGrounded) { _airJumpReady = true; } else if (_airJumpReady && flag) { _airJumpReady = false; try { instance.Jump(); return; } catch { return; } } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] DoubleJump: " + ex.Message); } } } public enum GameModeType { FreeForAll, Teams, Horse, FindersKeepers, StealTheBags, SpeedRun, HideAndSeek } public static class GameModeInfo { public static string Title(GameModeType t) { return t switch { GameModeType.FreeForAll => "Free-for-all", GameModeType.Teams => "Teams", GameModeType.Horse => "H.O.R.S.E.", GameModeType.FindersKeepers => "Finders Keepers", GameModeType.StealTheBags => "Capture The Bag", GameModeType.SpeedRun => "Speed Run", GameModeType.HideAndSeek => "Hide & Seek", _ => t.ToString(), }; } public static string Description(GameModeType t) { return t switch { GameModeType.FreeForAll => "Everyone for themselves. Most eliminations wins.", GameModeType.Teams => "Up to 4 colour teams (assigned at random). Most summed eliminations wins.", GameModeType.Horse => "The worst team by summed street sales earns a letter each day. Spell the word and the team is out — last team standing wins.", GameModeType.FindersKeepers => "One high-value bag. Grab it and bring it to your own drop to score. Players start at 4 shared dead drops.", GameModeType.StealTheBags => "4 team stashes. Grab a bag from an enemy stash and carry it to YOUR stash to capture it (a captured bag is consumed; the raided stash only refills once the bag is banked or the carrier dies). Most captures wins.", GameModeType.SpeedRun => "Team race from a shared start line. The first team to get ALL its players through the circuit wins.", GameModeType.HideAndSeek => "Seekers hunt hiders; a tagged hider joins the seekers. Last found / never found wins.", _ => string.Empty, }; } } internal static class HiderWhistle { private const int SampleRate = 44100; private const float MinDistance = 5f; private static float _next; private static AudioClip _clip; internal static void Tick(MatchController c) { try { if (c == null || c.ModeType != GameModeType.HideAndSeek || c.Phase != MatchPhase.Active || !PvpConfig.HiderWhistle.Value) { _next = 0f; return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (_next <= 0f) { _next = realtimeSinceStartup + Interval(); } else if (!(realtimeSinceStartup < _next)) { _next = realtimeSinceStartup + Interval(); WhistleAll(); } } catch { } } internal static void Reset() { _next = 0f; } private static float Interval() { return Mathf.Max(3f, (float)PvpConfig.WhistleIntervalSeconds.Value); } private static void WhistleAll() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) Player val = null; try { val = Player.Local; } catch { } if ((Object)(object)val == (Object)null || !PvpApi.TryGetHideSeekRole(val, out var isSeeker) || !isSeeker) { return; } Vector3 position; try { position = ((Component)val).transform.position; } catch { return; } float num = Mathf.Clamp(PvpConfig.WhistleRangeMeters.Value, 20f, 400f); int num2 = 0; int num3 = 0; List list = GameCompat.ToManaged(Player.PlayerList); for (int i = 0; i < list.Count; i++) { Player val2 = list[i]; try { if (!GameCompat.Alive((Object)(object)val2) || (Object)(object)val2 == (Object)(object)val || !PvpApi.TryGetHideSeekRole(val2, out var isSeeker2) || isSeeker2) { continue; } PlayerHealth componentInChildren = ((Component)val2).GetComponentInChildren(true); if (!GameCompat.Alive((Object)(object)componentInChildren) || componentInChildren.IsAlive) { Vector3 position2 = ((Component)val2).transform.position; float num4 = Vector3.Distance(position, position2); if (num4 > num) { num3++; continue; } PlayAt(position2, num); num2++; PvpLog.Debug($"[MultiplayerPVP] Whistle: hider '{SafeName(val2)}' at {num4:0} m (range {num:0})."); } } catch { } } if (num2 > 0 || num3 > 0) { PvpLog.Debug($"[MultiplayerPVP] Whistle wave: {num2} audible, {num3} out of range."); } } private static string SafeName(Player p) { try { return p.PlayerName; } catch { return "?"; } } private static void PlayAt(Vector3 pos, float range) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_0029: 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_0034: 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) try { AudioClip val = Clip(); if (!((Object)(object)val == (Object)null)) { GameObject val2 = new GameObject("MPVP Whistle"); val2.transform.position = pos + Vector3.up * 1.6f; AudioSource val3 = val2.AddComponent(); val3.clip = val; val3.volume = Mathf.Clamp01(PvpConfig.WhistleVolume.Value); val3.spatialBlend = 1f; val3.spread = 0f; val3.rolloffMode = (AudioRolloffMode)1; val3.minDistance = 5f; val3.maxDistance = range; val3.dopplerLevel = 0f; val3.Play(); Object.Destroy((Object)(object)val2, val.length + 0.5f); } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] Whistle play: " + ex.Message); } } private static AudioClip Clip() { if ((Object)(object)_clip != (Object)null) { return _clip; } try { List list = new List(44100); AddSweep(list, 1500f, 2250f, 0.22f, 0.55f); AddSilence(list, 0.05f); AddSweep(list, 2150f, 1150f, 0.32f, 0.55f); float[] array = list.ToArray(); AudioClip val = AudioClip.Create("mpvp_whistle", array.Length, 1, 44100, false); val.SetData(array, 0); _clip = val; } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] Whistle clip: " + ex.Message); return null; } return _clip; } private static void AddSweep(List data, float f0, float f1, float seconds, float gain) { int num = (int)(seconds * 44100f); double num2 = 0.0; for (int i = 0; i < num; i++) { float num3 = (float)i / (float)num; float num4 = Mathf.Lerp(f0, f1, num3) + 18f * Mathf.Sin((float)Math.PI * 60f * (float)i / 44100f); num2 += Math.PI * 2.0 * (double)num4 / 44100.0; float num5 = Mathf.Min(1f, Mathf.Min((float)i / 661.5f, (float)(num - i) / 2205f)); data.Add((float)Math.Sin(num2) * gain * num5); } } private static void AddSilence(List data, float seconds) { int num = (int)(seconds * 44100f); for (int i = 0; i < num; i++) { data.Add(0f); } } } internal static class MapHider { private static bool _hidden; internal static void HideRemotePlayers() { try { Player local = Player.Local; List list = GameCompat.ToManaged(Player.PlayerList); for (int i = 0; i < list.Count; i++) { Player val = list[i]; if (GameCompat.Alive((Object)(object)val) && !((Object)(object)val == (Object)(object)local)) { SetPoiEnabled(val, enabled: false); } } _hidden = true; } catch { } } internal static void RestoreAll() { if (!_hidden) { return; } _hidden = false; try { List list = GameCompat.ToManaged(Player.PlayerList); for (int i = 0; i < list.Count; i++) { SetPoiEnabled(list[i], enabled: true); } } catch { } } private static void SetPoiEnabled(Player p, bool enabled) { try { if (GameCompat.Alive((Object)(object)p)) { POI poI = p.PoI; if (GameCompat.Alive((Object)(object)poI) && ((Behaviour)poI).enabled != enabled) { ((Behaviour)poI).enabled = enabled; } } } catch { } } } internal class MatchContext { internal PlayerRegistry Registry; internal DeadDropManager Drops; internal Announcer Announcer; internal float PickupRadius = 4f; internal int DaysCompleted; internal void Award(PvpPlayer p, int points) { if (p != null) { p.Score += points; p.DayScore += points; } } } internal class MatchController { internal readonly PlayerRegistry Registry = new PlayerRegistry(); internal readonly DeadDropManager Drops = new DeadDropManager(); internal readonly Announcer Announcer = new Announcer(); private readonly MatchContext _ctx; internal readonly Dictionary> ScoreHistory = new Dictionary>(); private readonly PvpNet _net; internal readonly List RemoteRows = new List(); internal readonly List RemoteMarkers = new List(); internal readonly List RemoteTeamRows = new List(); private readonly List _markerBuf = new List(); private long _snapSeqOut; private long _snapSeqIn = -1L; private long _roundIdOut; private long _lastLoadoutRound = -1L; private float _publishTimer; private string _pendingSnapshot; private float _remoteStaleTimer; private MatchPhase _remotePrevPhase = MatchPhase.Idle; private float _refreshTimer; private int _lastCountdownInt = -1; private bool _localAlivePrev = true; private int _lootSeq; private int _lastAppliedLootSeq = -1; private string _pendingLoot = ""; private float _pendingLootUntil; private float _cashLast = -1f; private int _earnedToday; private int _earnedDay = -1; private float _earnPubTimer; private int _earnLastPub = -1; private float _earnApplyTimer; private string _lastEarnDiag = ""; private float _countdownEnd; private float _matchEnd; private float _matchSeconds; private float _mpCheckTimer; private bool _pendingStart; private bool _pendingFresh; private GameModeType _pendingType; private bool _pendingStop; private bool _pendingEndEarly; private string _lastRemoteBanner = ""; private bool _lastLocalBag; private bool _bagAlivePrev = true; private bool _bagArrestedPrev; private int _bagCountPrev; private int _bagSpillSeqOut; private int _selfTakePrev; private Vector3 _localSpawn; private const float SelfTakeRadius = 6f; private readonly Dictionary _lootSeen = new Dictionary(); private readonly Dictionary _spillSeen = new Dictionary(); private bool _curfewAlivePrev = true; private bool _curfewArrestedPrev; private MedicalCentre _clinic; internal MatchPhase Phase { get; private set; } = MatchPhase.Idle; internal GameModeType ModeType { get; private set; } internal IGameMode CurrentMode { get; private set; } internal int Day { get; private set; } internal float RemainingSeconds { get; private set; } internal float Countdown { get; private set; } internal bool HasActiveMode => CurrentMode != null; internal bool IsMultiplayer => SessionUtil.IsMultiplayer(); internal bool IsHost => SessionUtil.IsHost(); internal bool IsRemote => _net != null && _net.Available && _net.IsInLobby && !_net.IsHost; internal string DayResult { get; private set; } = ""; internal bool RemoteChaosActive { get; private set; } internal bool IsTeamScoredMode => IsTeamScored(ModeType); internal int ActiveTeamCount => Registry.ActiveTeamCount; internal MatchContext Context => _ctx; internal int LocalTeam() { try { if (!IsRemote) { return Registry.Local?.Team ?? (-1); } string text = SanitizeName(LocalPlayerName()); if (string.IsNullOrEmpty(text)) { return -1; } for (int i = 0; i < RemoteRows.Count; i++) { if (string.Equals(RemoteRows[i].Name, text, StringComparison.Ordinal)) { return RemoteRows[i].Team; } } } catch { } return -1; } internal MatchController(PvpNet net = null) { _net = net; if (_net != null) { _net.OnSnapshot += OnSnapshotReceived; _net.OnLoot += OnLootGrant; _net.OnSpill += OnBagSpill; } _ctx = new MatchContext { Registry = Registry, Drops = Drops, Announcer = Announcer }; } internal void RequestStart(GameModeType type) { _pendingType = type; _pendingFresh = true; _pendingStart = true; _pendingStop = false; } internal void RequestContinue() { if (HasActiveMode) { _pendingType = ModeType; _pendingFresh = false; _pendingStart = true; _pendingStop = false; } } internal void RequestStop() { _pendingStop = true; _pendingStart = false; } internal void RequestEndEarly() { _pendingEndEarly = true; } private void StopMatch() { Phase = MatchPhase.Idle; CurrentMode = null; try { MatchToggles.RoundEnded(); } catch { } try { UnlimitedAmmo.RoundEnded(); } catch { } try { DoubleJump.RoundEnded(); } catch { } Announcer.Clear(); try { BagItem.UnequipLocal(); } catch { } try { DeadDropStash.ClearAllOurs(); } catch { } try { MapHider.RestoreAll(); } catch { } SleepScreen.Close(); _lastLocalBag = false; SetGameClock(running: true); PublishIfHost(force: true); PvpLog.Debug("[MultiplayerPVP] Match stopped."); } internal void Cleanup() { //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) try { SetGameClock(running: true); } catch { } _pendingStart = false; _pendingStop = false; Phase = MatchPhase.Idle; CurrentMode = null; Day = 0; RemainingSeconds = 0f; Countdown = 0f; _refreshTimer = 0f; try { Registry.Clear(); } catch { } try { Drops.Clear(); } catch { } try { Announcer.Clear(); } catch { } RemoteRows.Clear(); RemoteMarkers.Clear(); RemoteTeamRows.Clear(); try { BagItem.UnequipLocal(); } catch { } try { DeadDropStash.ClearAllOurs(); } catch { } try { MapHider.RestoreAll(); } catch { } try { SleepScreen.Close(); } catch { } try { HiderWhistle.Reset(); } catch { } try { MatchToggles.RoundEnded(); } catch { } RemoteChaosActive = false; UnlimitedAmmo.Reset(); DoubleJump.Reset(); try { StartingLoadout.Reset(); } catch { } BagItem.Reset(); ScoreHistory.Clear(); _lastLocalBag = false; _pendingEndEarly = false; _selfTakePrev = 0; _localSpawn = Vector3.zero; _cashLast = -1f; _earnedToday = 0; _earnedDay = -1; _earnLastPub = -1; _snapSeqIn = -1L; _lastLoadoutRound = -1L; _lastAppliedLootSeq = -1; _pendingSnapshot = null; _remotePrevPhase = MatchPhase.Idle; _remoteStaleTimer = 0f; _lastRemoteBanner = ""; } private void Begin(GameModeType type, bool fresh) { try { BeginCore(type, fresh); } catch (Exception ex) { MelonLogger.Error("[MultiplayerPVP] Match start failed: " + ex); Phase = MatchPhase.Idle; CurrentMode = null; try { SetGameClock(running: true); } catch { } Announcer.Announce("Match start failed — see log."); } } private void BeginCore(GameModeType type, bool fresh) { //IL_01c9: Unknown result type (might be due to invalid IL or missing references) PvpConfig.Load(); if (!IsMultiplayer) { Announcer.Announce("Multiplayer only — host a co-op lobby (2+ players).", 5f); PvpLog.Debug("[MultiplayerPVP] Start blocked: not a multiplayer session."); return; } if (!SessionUtil.IsHost()) { Announcer.Announce("Only the host can start a match."); PvpLog.Debug("[MultiplayerPVP] Start blocked: not the host."); return; } Registry.Refresh(); if (Registry.Players.Count == 0) { Announcer.Announce("No players found — join a game first."); return; } Drops.Refresh(fresh, type == GameModeType.StealTheBags); _ctx.PickupRadius = Mathf.Max(3, PvpConfig.PickupRadius.Value); SleepScreen.Close(); DayResult = ""; SeedClientReportDedup(); if (fresh) { Registry.ResetMatchState(); ScoreHistory.Clear(); Day = 1; _ctx.DaysCompleted = 0; GrantStartingCash(); } else { Day++; Registry.ResetDayScores(); } ModeType = type; CurrentMode = Create(type); _roundIdOut++; if (fresh && IsTeamScored(type)) { int num = Mathf.Max(1, Drops.Count); Registry.AssignTeams(Mathf.Min(PvpConfig.TeamCount.Value, num)); } SetupSpawns(); ReviveAllServerSide(); ReviveAndHealLocal(Vector3.zero); StartingLoadout.ClearLocalInventory(); StartingLoadout.GiveLocalFromCsv(StartingLoadout.SelectedCsv()); try { CurrentMode.OnMatchStart(_ctx); } catch (Exception ex) { MelonLogger.Warning("OnMatchStart: " + ex.Message); } SetGameClock(running: true); try { TimeManager instance = NetworkSingleton.Instance; if (GameCompat.Alive((Object)(object)instance)) { instance.SetTimeAndSync(PvpConfig.StartTime.Value); } } catch { } _matchSeconds = Mathf.Max(10f, PvpConfig.MatchMinutes.Value * 60f); RemainingSeconds = _matchSeconds; _countdownEnd = Time.realtimeSinceStartup + 3.2f; Countdown = 3.2f; _lastCountdownInt = -1; Phase = MatchPhase.Countdown; PvpLog.Debug($"[MultiplayerPVP] {GameModeInfo.Title(type)} — day {Day}, {Registry.Players.Count} player(s), {Drops.Count} zone(s)."); if (_net != null && _net.Available) { PvpLog.Debug($"[MultiplayerPVP][Net] {_net.ModdedMemberCount}/{_net.MemberCount} lobby player(s) have the mod" + (_net.AllMembersModded ? "." : " — mod-less players are scored host-side but get no HUD/teleport.")); } Announcer.Announce($"{GameModeInfo.Title(type)} — Day {Day}", 2.5f, notify: false); PublishIfHost(force: true); } internal void Tick(float dt) { PollLocalWeaponDrop(); SampleLocalEarnings(); PollLocalSelfTake(); PollLocalBagSpill(); PollCurfewRespawn(); HiderWhistle.Tick(this); if (Phase != MatchPhase.Idle) { MapHider.HideRemotePlayers(); } else { MapHider.RestoreAll(); } if (IsRemote) { if (!SessionUtil.IsMultiplayer()) { if (Phase != MatchPhase.Idle || RemoteRows.Count > 0) { Phase = MatchPhase.Idle; RemoteRows.Clear(); RemoteMarkers.Clear(); RemoteTeamRows.Clear(); _remotePrevPhase = MatchPhase.Idle; UpdateLocalBag(localHasBag: false); SleepScreen.Close(); RemoteChaosActive = false; UnlimitedAmmo.SetRemote(on: false); DoubleJump.SetRemote(on: false); _snapSeqIn = -1L; _lastLoadoutRound = -1L; _lastAppliedLootSeq = -1; _lastRemoteBanner = ""; } _pendingSnapshot = null; } else { ApplyPendingSnapshot(); if (Phase == MatchPhase.Active && RemainingSeconds > 0f) { RemainingSeconds = Mathf.Max(0f, RemainingSeconds - Time.unscaledDeltaTime); } _remoteStaleTimer += Time.unscaledDeltaTime; if (_remoteStaleTimer > 15f) { Phase = MatchPhase.Idle; RemoteRows.Clear(); RemoteMarkers.Clear(); RemoteTeamRows.Clear(); UpdateLocalBag(localHasBag: false); SleepScreen.Close(); RemoteChaosActive = false; UnlimitedAmmo.SetRemote(on: false); DoubleJump.SetRemote(on: false); _remotePrevPhase = MatchPhase.Idle; } } return; } if (_pendingStop) { _pendingStop = false; StopMatch(); } if (_pendingStart) { _pendingStart = false; Begin(_pendingType, _pendingFresh); } if (_pendingEndEarly) { _pendingEndEarly = false; if (Phase == MatchPhase.Active) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (_matchEnd - realtimeSinceStartup > 3f) { _matchEnd = realtimeSinceStartup + 3f; RemainingSeconds = 3f; Announcer.Announce("Host ended the match early!", 3f, notify: false); PublishIfHost(force: true); } } } if (Phase == MatchPhase.Idle) { PublishIfHost(force: false); return; } _mpCheckTimer += Time.unscaledDeltaTime; if (_mpCheckTimer >= 3f) { _mpCheckTimer = 0f; if (!IsMultiplayer) { Announcer.Announce("Match ended — needs 2+ players.", 5f); StopMatch(); return; } } _refreshTimer += Time.unscaledDeltaTime; if (_refreshTimer >= 2f) { _refreshTimer = 0f; try { Registry.Refresh(); Drops.Refresh(reshuffle: false, ModeType == GameModeType.StealTheBags); } catch (Exception ex) { MelonLogger.Warning("Refresh: " + ex.Message); } } PollClientReports(); switch (Phase) { case MatchPhase.Countdown: TickCountdown(dt); break; case MatchPhase.Active: TickActive(dt); break; } PublishIfHost(force: false); } private void OnSnapshotReceived(string raw) { _pendingSnapshot = raw; } private void UpdateLocalBag(bool localHasBag) { if (localHasBag != _lastLocalBag) { _lastLocalBag = localHasBag; BagItem.SetLocalCarrier(localHasBag); } } private bool LocalRowHasBag(MatchSnapshot snap) { string text = SanitizeName(LocalPlayerName()); if (string.IsNullOrEmpty(text)) { return false; } for (int i = 0; i < snap.Rows.Count; i++) { if (string.Equals(snap.Rows[i].Name, text, StringComparison.Ordinal)) { return snap.Rows[i].Bag; } } return false; } private static string LocalPlayerName() { try { Player local = Player.Local; if (GameCompat.Alive((Object)(object)local)) { return local.PlayerName; } } catch { } return null; } private void ApplyPendingSnapshot() { //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Unknown result type (might be due to invalid IL or missing references) string pendingSnapshot = _pendingSnapshot; if (pendingSnapshot == null) { return; } _pendingSnapshot = null; MatchSnapshot matchSnapshot = new MatchSnapshot(); if (!MatchSnapshot.TryParse(pendingSnapshot, matchSnapshot) || matchSnapshot.Seq <= _snapSeqIn) { return; } _snapSeqIn = matchSnapshot.Seq; _remoteStaleTimer = 0f; Phase = (MatchPhase)matchSnapshot.Phase; ModeType = (GameModeType)matchSnapshot.Mode; Day = matchSnapshot.Day; RemainingSeconds = matchSnapshot.Remaining; RemoteChaosActive = matchSnapshot.Chaos; UnlimitedAmmo.SetRemote(matchSnapshot.Ammo); DoubleJump.SetRemote(matchSnapshot.Jump); RemoteRows.Clear(); RemoteRows.AddRange(matchSnapshot.Rows); RemoteMarkers.Clear(); RemoteMarkers.AddRange(matchSnapshot.Markers); RemoteTeamRows.Clear(); RemoteTeamRows.AddRange(matchSnapshot.TeamRows); DayResult = matchSnapshot.Result ?? ""; if (Phase == MatchPhase.Countdown && _remotePrevPhase != MatchPhase.Countdown) { ClientSpawnFromSnapshot(matchSnapshot); StartingLoadout.ClearLocalInventory(); } if (matchSnapshot.RoundId > 0 && matchSnapshot.RoundId != _lastLoadoutRound && (Phase == MatchPhase.Countdown || Phase == MatchPhase.Active)) { _lastLoadoutRound = matchSnapshot.RoundId; StartingLoadout.GiveLocalFromCsv(matchSnapshot.Loadout); } if (Phase == MatchPhase.EndOfDay && _remotePrevPhase != MatchPhase.EndOfDay) { SleepScreen.Open(); } else if (Phase != MatchPhase.EndOfDay && _remotePrevPhase == MatchPhase.EndOfDay) { SleepScreen.Close(); } _remotePrevPhase = Phase; UpdateLocalBag(LocalRowHasBag(matchSnapshot)); string text = SanitizeName(LocalPlayerName()); if (!string.IsNullOrEmpty(text)) { for (int i = 0; i < matchSnapshot.Rows.Count; i++) { if (string.Equals(matchSnapshot.Rows[i].Name, text, StringComparison.Ordinal)) { _localSpawn = matchSnapshot.Rows[i].Spawn; break; } } } if (matchSnapshot.Banner != _lastRemoteBanner) { _lastRemoteBanner = matchSnapshot.Banner; if (!string.IsNullOrEmpty(matchSnapshot.Banner)) { Announcer.Announce(matchSnapshot.Banner, 3f, notify: false); } else { Announcer.Clear(); } } if (!string.IsNullOrEmpty(matchSnapshot.Loot)) { ApplyLoot(matchSnapshot.Loot); } } private void PublishIfHost(bool force) { //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_0505: Unknown result type (might be due to invalid IL or missing references) //IL_050a: Unknown result type (might be due to invalid IL or missing references) //IL_0513: Unknown result type (might be due to invalid IL or missing references) //IL_0518: Unknown result type (might be due to invalid IL or missing references) if (_net == null || !_net.Available || !_net.IsHost) { return; } _publishTimer += Time.unscaledDeltaTime; if (!force && _publishTimer < 1f) { return; } _publishTimer = 0f; MatchSnapshot matchSnapshot = new MatchSnapshot { Seq = ++_snapSeqOut, Phase = (int)Phase, Mode = (int)ModeType, Day = Day, Remaining = RemainingSeconds, Banner = (Announcer.HasBanner ? Announcer.Banner : ""), Result = ((Phase == MatchPhase.EndOfDay) ? DayResult : ""), Loot = ((Time.realtimeSinceStartup < _pendingLootUntil) ? _pendingLoot : ""), Chaos = ChaosDirector.On, Ammo = UnlimitedAmmo.Checked, Jump = DoubleJump.Checked, Loadout = StartingLoadout.SelectedCsv(), RoundId = _roundIdOut }; List players = Registry.Players; PvpPlayer pvpPlayer = null; try { pvpPlayer = Registry.Local; } catch { } for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer2 = players[i]; string score = pvpPlayer2.Name; try { if (CurrentMode != null) { score = CurrentMode.ScoreText(pvpPlayer2); } } catch { } bool bag = false; try { bag = pvpPlayer2.IsCarryingBag; } catch { } int num = SpawnZoneFor(pvpPlayer2); Vector3 spawn = ((num >= 0) ? Drops.ZonePosition(num) : Vector3.zero); matchSnapshot.Rows.Add(new SnapRow { Name = pvpPlayer2.Name, Team = pvpPlayer2.Team, Index = pvpPlayer2.Index, Score = score, Bag = bag, Spawn = spawn, Kills = pvpPlayer2.Kills, Downs = pvpPlayer2.Downs, Arrests = pvpPlayer2.Arrests, BagsCaptured = pvpPlayer2.BagsCaptured, BagsLost = pvpPlayer2.BagsLost, WeaponsLost = pvpPlayer2.WeaponsLost, WeaponsWon = pvpPlayer2.WeaponsWon, History = HistoryString(pvpPlayer2) }); } if (IsTeamScored(ModeType) && CurrentMode != null) { int activeTeamCount = Registry.ActiveTeamCount; for (int j = 0; j < activeTeamCount; j++) { int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; int num8 = 0; for (int k = 0; k < players.Count; k++) { PvpPlayer pvpPlayer3 = players[k]; if (pvpPlayer3.Team == j) { num2 += pvpPlayer3.Kills; num3 += pvpPlayer3.Downs; num4 += pvpPlayer3.Arrests; num5 += pvpPlayer3.BagsCaptured; num6 += pvpPlayer3.BagsLost; num7 += pvpPlayer3.WeaponsLost; num8 += pvpPlayer3.WeaponsWon; } } string score2 = ""; try { score2 = CurrentMode.TeamScoreText(_ctx, j); } catch { } matchSnapshot.TeamRows.Add(new SnapTeamRow { Team = j, Name = Registry.TeamLabel(j), Score = score2, Kills = num2, Downs = num3, Arrests = num4, BagsCaptured = num5, BagsLost = num6, WeaponsLost = num7, WeaponsWon = num8 }); } } bool localHasBag = false; try { localHasBag = pvpPlayer?.IsCarryingBag ?? false; } catch { } UpdateLocalBag(localHasBag); try { if (CurrentMode is IWorldMarkers worldMarkers) { _markerBuf.Clear(); worldMarkers.CollectMarkers(_ctx, Registry.Local, _markerBuf); for (int l = 0; l < _markerBuf.Count; l++) { WorldMarker worldMarker = _markerBuf[l]; matchSnapshot.Markers.Add(new SnapMarker { Pos = worldMarker.Pos, Color = worldMarker.Color, Label = worldMarker.Label, Team = worldMarker.Team }); } } } catch (Exception ex) { MelonLogger.Warning("Marker publish: " + ex.Message); } _net.PublishSnapshot(matchSnapshot.Serialize()); } private void TickCountdown(float dt) { Countdown = _countdownEnd - Time.realtimeSinceStartup; int num = Mathf.CeilToInt(Countdown); if (num != _lastCountdownInt && num >= 1 && num <= 3) { _lastCountdownInt = num; Announcer.Announce(num.ToString(), 0.9f, notify: false); } if (Countdown <= 0f) { _matchEnd = Time.realtimeSinceStartup + _matchSeconds; Phase = MatchPhase.Active; Announcer.Announce("GO!", 2.5f); try { CurrentMode.OnGo(_ctx); } catch (Exception ex) { MelonLogger.Warning("OnGo: " + ex.Message); } try { MatchToggles.RoundStarted(); } catch { } PublishIfHost(force: true); } } private void TickActive(float dt) { RemainingSeconds = Mathf.Max(0f, _matchEnd - Time.realtimeSinceStartup); PollCombatHits(); PollStats(); ApplyHorseEarnings(); try { CurrentMode.Tick(_ctx, dt); } catch (Exception ex) { MelonLogger.Warning("Mode.Tick: " + ex.Message); } bool flag = false; try { flag = CurrentMode.IsComplete(_ctx); } catch { } if (flag || RemainingSeconds <= 0f) { EndDay(); } } private void EndDay() { RemainingSeconds = 0f; Phase = MatchPhase.EndOfDay; _ctx.DaysCompleted++; try { CurrentMode.OnDayEnd(_ctx); } catch (Exception ex) { MelonLogger.Warning("OnDayEnd: " + ex.Message); } try { CurrentMode.OnMatchEnd(_ctx); } catch (Exception ex2) { MelonLogger.Warning("OnMatchEnd: " + ex2.Message); } try { MatchToggles.RoundEnded(); } catch { } try { UnlimitedAmmo.RoundEnded(); } catch { } try { DoubleJump.RoundEnded(); } catch { } DayResult = (Announcer.HasBanner ? Announcer.Banner : ""); try { BagItem.UnequipLocal(); } catch { } try { DeadDropStash.ClearAllOurs(); } catch { } _lastLocalBag = false; RecordHistory(); SetGameClock(running: false); SleepScreen.Open(); PublishIfHost(force: true); PvpLog.Debug($"[MultiplayerPVP] Day {Day} ended — end-of-day screen."); } private void RecordHistory() { List players = Registry.Players; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; HistoryOf(pvpPlayer, create: true)?.Add(pvpPlayer.Score); } } internal List HistoryOf(PvpPlayer p, bool create = false) { if (p == null) { return null; } string stableKey = p.StableKey; if (string.IsNullOrEmpty(stableKey)) { return null; } if (ScoreHistory.TryGetValue(stableKey, out var value)) { return value; } string name = p.Name; if (!string.IsNullOrEmpty(name) && name != stableKey && ScoreHistory.TryGetValue(name, out value)) { ScoreHistory.Remove(name); ScoreHistory[stableKey] = value; return value; } if (!create) { return null; } value = new List(); ScoreHistory[stableKey] = value; return value; } private string HistoryString(PvpPlayer p) { List list = HistoryOf(p); if (list == null || list.Count < 2) { return ""; } return string.Join(".", list); } private static bool IsCombatMode(GameModeType t) { return t == GameModeType.FreeForAll || t == GameModeType.Teams || t == GameModeType.Horse || t == GameModeType.HideAndSeek; } internal static bool IsTeamScored(GameModeType t) { return t == GameModeType.Teams || t == GameModeType.StealTheBags || t == GameModeType.Horse || t == GameModeType.SpeedRun; } private void PollCombatHits() { if (!IsCombatMode(ModeType)) { return; } List players = Registry.Players; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; if (!pvpPlayer.Valid) { continue; } try { if (pvpPlayer.PollTookDamage()) { DamageTracker.RecordHit(((Object)pvpPlayer.Game).GetInstanceID()); } } catch { } } } private void PollStats() { List players = Registry.Players; bool teamMode = ModeType == GameModeType.Teams; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; if (!pvpPlayer.Valid) { continue; } try { if (pvpPlayer.PollGotArrested()) { pvpPlayer.Arrests++; } if (!pvpPlayer.PollWentDownStats()) { continue; } pvpPlayer.Downs++; if (!RiotChaos.IsDownChaosCaused(pvpPlayer)) { PvpPlayer pvpPlayer2 = Combat.NearestLivingEnemy(_ctx, pvpPlayer, teamMode); if (pvpPlayer2 != null && pvpPlayer2 != pvpPlayer) { pvpPlayer2.Kills++; } } } catch { } } } private void PollClientReports() { if (_net == null || !_net.IsHost) { return; } try { List> list = _net.AllLoot(); for (int i = 0; i < list.Count; i++) { OnLootGrant(list[i].Key, list[i].Value); } List> list2 = _net.AllSpill(); for (int j = 0; j < list2.Count; j++) { OnBagSpill(list2[j].Key, list2[j].Value); } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] PollClientReports: " + ex.Message); } } private void SeedClientReportDedup() { if (_net == null || !_net.IsHost) { return; } try { List> list = _net.AllLoot(); for (int i = 0; i < list.Count; i++) { _lootSeen[list[i].Key] = list[i].Value; } List> list2 = _net.AllSpill(); for (int j = 0; j < list2.Count; j++) { _spillSeen[list2[j].Key] = list2[j].Value; } } catch { } } private void SampleLocalEarnings() { if (ModeType != GameModeType.Horse) { return; } if (Day != _earnedDay) { _earnedDay = Day; _earnedToday = 0; _cashLast = -1f; _earnLastPub = -1; } if (Phase != MatchPhase.Active) { return; } float cashBalance; try { MoneyManager instance = NetworkSingleton.Instance; if (!GameCompat.Alive((Object)(object)instance)) { return; } cashBalance = instance.cashBalance; } catch { return; } if (_cashLast < 0f) { _cashLast = cashBalance; return; } if (cashBalance > _cashLast) { int num = Mathf.RoundToInt(cashBalance - _cashLast); _earnedToday += num; PvpLog.Debug(string.Format("[MultiplayerPVP][HORSE] {0} cashBalance +{1} → day income {2} (cur={3:0})", IsRemote ? "CLIENT" : "HOST", num, _earnedToday, cashBalance)); } _cashLast = cashBalance; if (IsRemote && _net != null) { _earnPubTimer += Time.unscaledDeltaTime; if (_earnedToday != _earnLastPub && _earnPubTimer >= 0.5f) { _earnPubTimer = 0f; _earnLastPub = _earnedToday; _net.PublishEarnings(SanitizeName(LocalPlayerName()) + "~" + _earnedToday); PvpLog.Debug($"[MultiplayerPVP][HORSE] CLIENT published day income={_earnedToday} to host."); } } } private void ApplyHorseEarnings() { if (ModeType != GameModeType.Horse) { return; } List players = Registry.Players; PvpPlayer pvpPlayer = null; try { pvpPlayer = Registry.Local; } catch { } if (pvpPlayer != null) { pvpPlayer.DayScore = _earnedToday; } if (_net == null) { return; } _earnApplyTimer += Time.unscaledDeltaTime; if (_earnApplyTimer < 0.4f) { return; } _earnApplyTimer = 0f; List> list = _net.AllEarnings(); Dictionary dictionary = _net.AllIdentities(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("reports=").Append(list.Count).Append(" ids=") .Append(dictionary.Count) .Append(" localP=") .Append((pvpPlayer != null) ? SanitizeName(pvpPlayer.Name) : "NULL"); for (int i = 0; i < list.Count; i++) { ulong key = list[i].Key; string value = list[i].Value; int num = value.IndexOf('~'); if (num <= 0) { continue; } string text = value.Substring(0, num); if (int.TryParse(value.Substring(num + 1), out var result)) { PvpPlayer pvpPlayer2 = FindBySteamId(key, dictionary); string value2 = ((pvpPlayer2 != null) ? "ID" : null); if (pvpPlayer2 == null) { pvpPlayer2 = FindByName(text); value2 = ((pvpPlayer2 != null) ? "name" : "NOMATCH"); } if (pvpPlayer2 != null && pvpPlayer2 != pvpPlayer) { pvpPlayer2.DayScore = result; } stringBuilder.Append(" | '").Append(text).Append("'=") .Append(result) .Append(" ->") .Append(value2); } } stringBuilder.Append(" roster="); for (int j = 0; j < players.Count; j++) { stringBuilder.Append((j > 0) ? "," : "").Append(SanitizeName(players[j].Name)).Append('/') .Append(players[j].DayScore); } string text2 = stringBuilder.ToString(); if (text2 != _lastEarnDiag) { _lastEarnDiag = text2; PvpLog.Debug("[MultiplayerPVP][HORSE] HOST " + text2); } } private PvpPlayer FindBySteamId(ulong steamId, Dictionary idMap) { if (steamId == 0L || idMap == null || idMap.Count == 0) { return null; } List players = Registry.Players; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; string text = null; try { text = (GameCompat.Alive((Object)(object)pvpPlayer.Game) ? pvpPlayer.Game.PlayerCode : null); } catch { } if (!string.IsNullOrEmpty(text) && idMap.TryGetValue(text, out var value) && value == steamId) { return pvpPlayer; } } return null; } private void PollLocalWeaponDrop() { if (Phase != MatchPhase.Active || ModeType == GameModeType.SpeedRun) { _localAlivePrev = true; return; } try { Player local = Player.Local; if (!GameCompat.Alive((Object)(object)local)) { _localAlivePrev = true; return; } PlayerHealth componentInChildren = ((Component)local).GetComponentInChildren(true); bool flag; try { flag = ((!GameCompat.Alive((Object)(object)componentInChildren)) ? GameCompat.Alive((Object)(object)local) : componentInChildren.IsAlive); } catch { flag = true; } if (_localAlivePrev && !flag) { OnLocalDeath(local); } _localAlivePrev = flag; } catch { } } private void OnLocalDeath(Player lp) { List list = WeaponDrop.CollectAndRemoveLocalWeapons(); if (list.Count != 0) { string text = SanitizeName(LocalPlayerName()); bool flag = false; try { flag = PvpApi.IsLocalKoNpcCaused(); } catch { } Player val = (flag ? null : NearestLivingOther(lp)); string text2 = (((Object)(object)val != (Object)null) ? SanitizeName(SafeName(val)) : ""); string text3 = string.Join(",", list.ToArray()); PvpLog.Debug(string.Format("[MultiplayerPVP] WeaponDrop: '{0}' down, stripped {1} ({2}); killer='{3}'{4}, isHost={5}.", text, list.Count, text3, (text2.Length == 0) ? "(none)" : text2, flag ? " [NPC KO → confiscate]" : "", _net != null && _net.IsHost)); if (_net != null && _net.IsHost) { HostFormGrant(text2, text, text3); } else if (_net != null && _net.Available) { PvpNet net = _net; string[] array = new string[7]; int num = ++_lootSeq; array[0] = num.ToString(); array[1] = "~"; array[2] = text2; array[3] = "~"; array[4] = text; array[5] = "~"; array[6] = text3; net.PublishLoot(string.Concat(array)); PvpLog.Debug($"[MultiplayerPVP] WeaponDrop: reported to host (seq {_lootSeq})."); } else { Announcer.Announce(text + " - weapon confiscated", 4f, notify: false); } } } private void OnLootGrant(ulong fromId, string record) { try { if (_net != null && _net.IsHost && ParseGrant(record, out var _, out var killer, out var victim, out var idsCsv) && (!_lootSeen.TryGetValue(fromId, out var value) || !(value == record))) { _lootSeen[fromId] = record; HostFormGrant(killer, victim, idsCsv); } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] OnLootGrant: " + ex.Message); } } private void HostFormGrant(string killerName, string victimName, string idsCsv) { int num = ((!string.IsNullOrEmpty(idsCsv)) ? idsCsv.Split(new char[1] { ',' }).Length : 0); PvpPlayer pvpPlayer = FindByName(victimName); if (pvpPlayer != null) { pvpPlayer.WeaponsLost += num; } if (RiotChaos.IsDownChaosCaused(pvpPlayer)) { killerName = ""; } if (string.IsNullOrEmpty(killerName)) { PvpLog.Debug("[MultiplayerPVP] WeaponDrop: no killer → '" + victimName + "' confiscated (global banner)."); Announcer.Announce(victimName + " - weapon confiscated", 4f, notify: false); PublishIfHost(force: true); return; } PvpPlayer pvpPlayer2 = FindByName(killerName); if (pvpPlayer2 != null) { pvpPlayer2.WeaponsWon += num; } _pendingLoot = ++_lootSeq + "~" + SanitizeName(killerName) + "~" + SanitizeName(victimName) + "~" + idsCsv; _pendingLootUntil = Time.realtimeSinceStartup + 5f; PvpLog.Debug($"[MultiplayerPVP] WeaponDrop: host authored grant '{victimName}' → '{killerName}' ({num} wpn) seq {_lootSeq}."); Announcer.Announce(victimName + "'s weapon dropped to " + killerName + "!", 4f, notify: false); ApplyLoot(_pendingLoot); PublishIfHost(force: true); } private void ApplyLoot(string grant) { try { if (!ParseGrant(grant, out var seq, out var killer, out var victim, out var idsCsv) || seq <= _lastAppliedLootSeq || !string.Equals(killer, SanitizeName(LocalPlayerName()), StringComparison.Ordinal)) { return; } _lastAppliedLootSeq = seq; string[] array = idsCsv.Split(new char[1] { ',' }); int num = 0; for (int i = 0; i < array.Length; i++) { if (WeaponDrop.TryGiveLocal(array[i])) { num++; } } PvpLog.Debug($"[MultiplayerPVP] WeaponDrop: grant for me ('{victim}'→local): gave {num}/{array.Length}."); if (num <= 0) { Announcer.Notify("Couldn't loot " + victim + "'s weapon (bag full)."); } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] ApplyLoot: " + ex.Message); } } private void PollLocalBagSpill() { if (Phase != MatchPhase.Active || (ModeType != GameModeType.StealTheBags && ModeType != GameModeType.FindersKeepers)) { _bagAlivePrev = true; _bagArrestedPrev = false; _bagCountPrev = 0; return; } try { Player local = Player.Local; if (!GameCompat.Alive((Object)(object)local)) { _bagAlivePrev = true; _bagArrestedPrev = false; return; } int num = BagInventory.CountLocal(); PlayerHealth componentInChildren = ((Component)local).GetComponentInChildren(true); bool flag; try { flag = ((!GameCompat.Alive((Object)(object)componentInChildren)) ? GameCompat.Alive((Object)(object)local) : componentInChildren.IsAlive); } catch { flag = true; } if (_bagAlivePrev && !flag) { int num2 = BagInventory.RemoveLocal(); if (num2 > 0) { ReportBagSpill(num2, "downed"); } num = 0; } _bagAlivePrev = flag; bool flag2; try { flag2 = local.IsArrested; } catch { flag2 = false; } if (!_bagArrestedPrev && flag2) { int num3 = ((_bagCountPrev > 0) ? _bagCountPrev : num); if (num3 > 0) { ReportBagSpill(num3, "arrested"); } num = 0; } _bagArrestedPrev = flag2; _bagCountPrev = num; } catch { } } private void PollLocalSelfTake() { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: 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_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) if (Phase != MatchPhase.Active || ModeType != GameModeType.StealTheBags) { _selfTakePrev = 0; return; } try { int num = BagInventory.CountLocal(); int num2 = num - _selfTakePrev; if (num2 > 0) { Vector3 val = LocalStashPos(); if (val != Vector3.zero) { Player local = Player.Local; Vector3 val2 = Vector3.zero; if (GameCompat.Alive((Object)(object)local)) { try { val2 = ((Component)local).transform.position; } catch { val2 = Vector3.zero; } } if (val2 != Vector3.zero && Vector3.Distance(val2, val) <= 6f) { int num3 = BagInventory.RemoveLocal(num2); if (num3 > 0) { PvpLog.Debug($"[MultiplayerPVP] StealTheBags: confiscated {num3} self-taken bag(s) at own stash (anti-hoard)."); } num = BagInventory.CountLocal(); } } } _selfTakePrev = num; } catch { } } private Vector3 LocalStashPos() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) if (IsRemote) { return _localSpawn; } try { PvpPlayer local = Registry.Local; if (local != null) { int num = SpawnZoneFor(local); return (num >= 0) ? Drops.ZonePosition(num) : Vector3.zero; } } catch { } return Vector3.zero; } private void ReportBagSpill(int count, string reason) { string text = SanitizeName(LocalPlayerName()); if (_net != null && !_net.IsHost && _net.Available) { PvpNet net = _net; string[] array = new string[7]; int num = ++_bagSpillSeqOut; array[0] = num.ToString(); array[1] = "~"; array[2] = text; array[3] = "~"; array[4] = count.ToString(); array[5] = "~"; array[6] = reason; net.PublishSpill(string.Concat(array)); } else { HostSpillBags(text, count, reason); } } private void OnBagSpill(ulong fromId, string record) { try { if (_net == null || !_net.IsHost) { return; } string[] array = record.Split(new char[1] { '~' }); if (array.Length >= 4 && (!_spillSeen.TryGetValue(fromId, out var value) || !(value == record))) { _spillSeen[fromId] = record; if (int.TryParse(array[2], out var result)) { HostSpillBags(array[1], result, array[3]); } } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] OnBagSpill: " + ex.Message); } } private void HostSpillBags(string playerName, int count, string reason) { if (count <= 0) { return; } PvpPlayer victim = FindByName(playerName); if (CurrentMode is IBagSpillHandler bagSpillHandler) { try { if (bagSpillHandler.OnCarrierSpill(_ctx, victim, count, reason)) { PublishIfHost(force: true); return; } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] OnCarrierSpill: " + ex.Message); } } List list = new List(4); for (int i = 0; i < 8; i++) { if (list.Count >= 4) { break; } DeadDrop val = Drops.RandomNonZoneDrop(); if ((Object)(object)val != (Object)null && !list.Contains(val)) { list.Add(val); } } int num = DeadDropStash.InsertSpread(list, BagFactory.MatchId, count, BagFactory.Create); PvpPlayer pvpPlayer = FindByName(playerName); if (pvpPlayer != null) { pvpPlayer.BagsLost += count; } string arg = ((reason == "arrested") ? "was busted" : "was downed"); Announcer.Announce($"{playerName} {arg} — {count} bag(s) spilled back into a dead drop!", 5f, notify: false); if (num < count) { MelonLogger.Warning($"[MultiplayerPVP] Bag spill: only {num}/{count} re-spawned (drops full)."); } PublishIfHost(force: true); } private PvpPlayer FindByName(string sanitizedName) { if (string.IsNullOrEmpty(sanitizedName)) { return null; } List players = Registry.Players; for (int i = 0; i < players.Count; i++) { if (string.Equals(SanitizeName(players[i].Name), sanitizedName, StringComparison.Ordinal)) { return players[i]; } } return null; } private void PollCurfewRespawn() { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) if (Phase != MatchPhase.Active) { _curfewAlivePrev = true; _curfewArrestedPrev = false; return; } try { Player local = Player.Local; if (!GameCompat.Alive((Object)(object)local)) { _curfewAlivePrev = true; _curfewArrestedPrev = false; return; } PlayerHealth componentInChildren = ((Component)local).GetComponentInChildren(true); bool flag; try { flag = ((!GameCompat.Alive((Object)(object)componentInChildren)) ? GameCompat.Alive((Object)(object)local) : componentInChildren.IsAlive); } catch { flag = true; } bool flag2; try { flag2 = local.IsArrested; } catch { flag2 = false; } bool flag3 = (!_curfewAlivePrev && flag) || (_curfewArrestedPrev && !flag2); if (flag3 && flag && !flag2 && CurfewActive()) { Vector3 val = ClinicPosition(); if (val != Vector3.zero) { TeleportLocalTo(SafeSpawn(val)); } } _curfewAlivePrev = flag; _curfewArrestedPrev = flag2; } catch { _curfewAlivePrev = true; _curfewArrestedPrev = false; } } private static bool CurfewActive() { try { CurfewManager instance = NetworkSingleton.Instance; return GameCompat.Alive((Object)(object)instance) && instance.IsCurrentlyActive; } catch { return false; } } private Vector3 ClinicPosition() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) try { if (!GameCompat.Alive((Object)(object)_clinic)) { _clinic = Object.FindObjectOfType(); } if (GameCompat.Alive((Object)(object)_clinic)) { Transform respawnPoint = _clinic.RespawnPoint; if ((Object)(object)respawnPoint != (Object)null) { return respawnPoint.position; } } } catch { } return Vector3.zero; } private static bool ParseGrant(string rec, out int seq, out string killer, out string victim, out string idsCsv) { seq = 0; killer = ""; victim = ""; idsCsv = ""; if (string.IsNullOrEmpty(rec)) { return false; } string[] array = rec.Split(new char[1] { '~' }); if (array.Length < 4) { return false; } if (!int.TryParse(array[0], out seq)) { return false; } killer = array[1]; victim = array[2]; idsCsv = array[3]; return true; } private static Player NearestLivingOther(Player self) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) Player result = null; try { Vector3 position = ((Component)self).transform.position; List list = GameCompat.ToManaged(Player.PlayerList); float num = float.MaxValue; for (int i = 0; i < list.Count; i++) { Player val = list[i]; if (!GameCompat.Alive((Object)(object)val) || (Object)(object)val == (Object)(object)self) { continue; } try { PlayerHealth componentInChildren = ((Component)val).GetComponentInChildren(true); if (GameCompat.Alive((Object)(object)componentInChildren) && !componentInChildren.IsAlive) { continue; } } catch { } float num2 = Vector3.Distance(position, ((Component)val).transform.position); if (num2 < num) { num = num2; result = val; } } } catch { } return result; } private static string SafeName(Player p) { try { return GameCompat.Alive((Object)(object)p) ? p.PlayerName : ""; } catch { return ""; } } internal static string SanitizeName(string n) { if (string.IsNullOrEmpty(n)) { return ""; } return n.Replace('|', ' ').Replace('~', ' ').Replace(';', ' ') .Replace('=', ' ') .Replace(',', ' ') .Replace('"', ' ') .Replace('\\', ' '); } private int SpawnZoneFor(PvpPlayer p) { if (ModeType == GameModeType.SpeedRun) { return (Drops.Count <= 0) ? (-1) : 0; } return IsTeamScored(ModeType) ? Drops.ZoneForIndex(p.Team) : Drops.ZoneForIndex(p.Index); } private void SetupSpawns() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) if (!PvpConfig.TeleportToSpawns.Value || !Drops.Ready) { return; } List players = Registry.Players; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; int num = SpawnZoneFor(pvpPlayer); if (num >= 0) { Vector3 wanted = Drops.ZonePosition(num) + new Vector3(Random.Range(-2f, 2f), 0f, Random.Range(-2f, 2f)); pvpPlayer.TeleportTo(SafeSpawn(wanted)); } } } private static Vector3 SafeSpawn(Vector3 wanted) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) try { NavMeshHit val = default(NavMeshHit); if (NavMesh.SamplePosition(wanted, ref val, 8f, -1)) { return ((NavMeshHit)(ref val)).position + Vector3.up * 1f; } } catch { } return wanted; } private void ClientSpawnFromSnapshot(MatchSnapshot snap) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) Vector3 val = Vector3.zero; string text = SanitizeName(LocalPlayerName()); if (!string.IsNullOrEmpty(text)) { for (int i = 0; i < snap.Rows.Count; i++) { if (string.Equals(snap.Rows[i].Name, text, StringComparison.Ordinal)) { val = snap.Rows[i].Spawn; break; } } } ReviveAndHealLocal(val); if (PvpConfig.TeleportToSpawns.Value) { if (val != Vector3.zero) { TeleportLocalTo(SafeSpawn(val)); } else { ClientSelfSpawn(); } } } private static void ReviveAndHealLocal(Vector3 fallbackSpawn) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) try { Player local = Player.Local; if (!GameCompat.Alive((Object)(object)local)) { return; } PlayerHealth componentInChildren = ((Component)local).GetComponentInChildren(true); if (!GameCompat.Alive((Object)(object)componentInChildren)) { return; } bool flag = !componentInChildren.IsAlive; bool flag2 = false; Vector3 val = ((fallbackSpawn != Vector3.zero) ? SafeSpawn(fallbackSpawn) : ((Component)local).transform.position); try { DeathScreen instance = Singleton.Instance; if (GameCompat.Alive((Object)(object)instance)) { if (instance.isOpen) { flag2 = true; } try { ((MonoBehaviour)instance).StopAllCoroutines(); } catch { } instance.Close(); } } catch { } try { BlackOverlay instance2 = Singleton.Instance; if (GameCompat.Alive((Object)(object)instance2) && instance2.isShown) { flag2 = true; instance2.Close(0f); try { instance2.group.alpha = 0f; ((Behaviour)instance2.canvas).enabled = false; } catch { } } } catch { } try { HospitalBillScreen instance3 = Singleton.Instance; if (GameCompat.Alive((Object)(object)instance3) && instance3.isOpen) { flag2 = true; try { ((MonoBehaviour)instance3).StopAllCoroutines(); } catch { } try { instance3.isOpen = false; } catch { } try { ((Behaviour)instance3.Canvas).enabled = false; } catch { } try { instance3.CanvasGroup.interactable = false; } catch { } try { PlayerSingleton.Instance.RemoveActiveUIElement(((Object)instance3).name); } catch { } } } catch { } bool flag3 = false; if (flag || flag2) { try { if (local.IsArrested) { local.Free_Server(); flag3 = true; } } catch { } } if (flag) { componentInChildren.Revive(val, Quaternion.identity); } else if (flag3) { TeleportLocalTo(val); } componentInChildren.RecoverHealth(9999f); if (flag || flag2) { try { PlayerSingleton.Instance.RemoveActiveUIElement("Dead"); } catch { } try { PlayerSingleton.Instance.CanMove = true; } catch { } try { PlayerSingleton.Instance.SetCanLook(true); } catch { } try { PlayerSingleton.Instance.SetEquippingEnabled(true); } catch { } try { HUD instance4 = Singleton.Instance; if (GameCompat.Alive((Object)(object)instance4)) { instance4.SetCrosshairVisible(true); } } catch { } try { HUD instance5 = Singleton.Instance; if (GameCompat.Alive((Object)(object)instance5)) { ((Behaviour)instance5.canvas).enabled = true; } } catch { } } try { PostProcessingManager instance6 = Singleton.Instance; if (GameCompat.Alive((Object)(object)instance6)) { instance6.SetBlur(0f); } } catch { } PvpLog.Debug("[MultiplayerPVP] Round start: local player revived/healed" + ((flag || flag2) ? " + input/UI restored." : ".")); } catch (Exception ex) { MelonLogger.Warning("ReviveAndHealLocal: " + ex.Message); } } private void ReviveAllServerSide() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) List players = Registry.Players; for (int i = 0; i < players.Count; i++) { try { PvpPlayer pvpPlayer = players[i]; if (pvpPlayer.Valid && !pvpPlayer.IsAlive) { int num = SpawnZoneFor(pvpPlayer); Vector3 val = ((num >= 0) ? SafeSpawn(Drops.ZonePosition(num)) : pvpPlayer.Position); PlayerHealth health = pvpPlayer.Health; if (GameCompat.Alive((Object)(object)health)) { health.Revive(val, Quaternion.identity); } } } catch { } } } private void ClientSelfSpawn() { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) if (!PvpConfig.TeleportToSpawns.Value) { return; } try { List list = GameCompat.ToManaged(DeadDrop.DeadDrops); if (list.Count != 0) { DeadDrop val = list[Random.Range(0, list.Count)]; if (GameCompat.Alive((Object)(object)val)) { TeleportLocalTo(SafeSpawn(((Component)val).transform.position + new Vector3(Random.Range(-2f, 2f), 0f, Random.Range(-2f, 2f)))); } } } catch (Exception ex) { MelonLogger.Warning("ClientSelfSpawn: " + ex.Message); } } private static void TeleportLocalTo(Vector3 pos) { //IL_004c: 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) //IL_0039: Unknown result type (might be due to invalid IL or missing references) try { Player local = Player.Local; if (GameCompat.Alive((Object)(object)local)) { PlayerHealth componentInChildren = ((Component)local).GetComponentInChildren(true); if (GameCompat.Alive((Object)(object)componentInChildren) && !componentInChildren.IsAlive) { componentInChildren.Revive(pos, Quaternion.identity); } else { ((Component)local).transform.position = pos; } PvpLog.Debug("[MultiplayerPVP] Client spawned at its base."); } } catch (Exception ex) { MelonLogger.Warning("TeleportLocalTo: " + ex.Message); } } private void GrantStartingCash() { try { MoneyManager instance = NetworkSingleton.Instance; if (GameCompat.Alive((Object)(object)instance)) { float value = PvpConfig.StartingCash.Value; if (value > 0.5f) { instance.ChangeCashBalance(value, true, true); } } } catch (Exception ex) { MelonLogger.Warning("GrantStartingCash: " + ex.Message); } } private void SetGameClock(bool running) { try { TimeManager instance = NetworkSingleton.Instance; if (GameCompat.Alive((Object)(object)instance)) { instance.SetTimeSpeedMultiplier(running ? 1f : 0f); } } catch { } } private static IGameMode Create(GameModeType type) { return type switch { GameModeType.FreeForAll => new FreeForAllMode(), GameModeType.Teams => new TeamsMode(), GameModeType.Horse => new HorseMode(), GameModeType.FindersKeepers => new FindersKeepersMode(), GameModeType.StealTheBags => new StealTheBagsMode(), GameModeType.SpeedRun => new SpeedRunMode(), GameModeType.HideAndSeek => new HideAndSeekMode(), _ => new FreeForAllMode(), }; } } public enum MatchPhase { Idle, Countdown, Active, EndOfDay } internal class PlayerRegistry { internal readonly List Players = new List(); internal int ActiveTeamCount { get; private set; } internal PvpPlayer Local { get { Player local = Player.Local; if (!GameCompat.Alive((Object)(object)local)) { return null; } for (int i = 0; i < Players.Count; i++) { if (Players[i].Game == local || (Object)(object)Players[i].Game == (Object)(object)local) { return Players[i]; } } return null; } } internal int AliveCount { get { int num = 0; for (int i = 0; i < Players.Count; i++) { if (Players[i].Valid && !Players[i].Eliminated && Players[i].IsAlive) { num++; } } return num; } } internal void Refresh() { List list = GameCompat.ToManaged(Player.PlayerList); for (int num = Players.Count - 1; num >= 0; num--) { if (!Players[num].Valid) { Players.RemoveAt(num); } } foreach (Player item in list) { bool flag = false; for (int i = 0; i < Players.Count; i++) { if ((Object)(object)Players[i].Game == (Object)(object)item) { flag = true; break; } } if (!flag) { PvpPlayer pvpPlayer = new PvpPlayer(item, Players.Count); if (ActiveTeamCount > 0) { pvpPlayer.Team = -1; } Players.Add(pvpPlayer); } } for (int j = 0; j < Players.Count; j++) { Players[j].Index = j; } if (ActiveTeamCount <= 0) { return; } for (int k = 0; k < Players.Count; k++) { if (Players[k].Team < 0 || Players[k].Team >= ActiveTeamCount) { int team = SmallestTeam(); Players[k].Team = team; InheritTeamState(Players[k], team); } } } private void InheritTeamState(PvpPlayer joiner, int team) { for (int i = 0; i < Players.Count; i++) { PvpPlayer pvpPlayer = Players[i]; if (pvpPlayer != joiner && pvpPlayer.Team == team) { joiner.Eliminated = pvpPlayer.Eliminated; joiner.HorseLetters = pvpPlayer.HorseLetters; break; } } } internal void AssignTeams(int teamCount) { int count = Players.Count; if (count <= 0) { ActiveTeamCount = 0; return; } if (teamCount < 1) { teamCount = 1; } if (teamCount > count) { teamCount = count; } ActiveTeamCount = teamCount; List list = new List(count); for (int i = 0; i < count; i++) { list.Add(i); } for (int num = list.Count - 1; num > 0; num--) { int index = Random.Range(0, num + 1); int value = list[num]; list[num] = list[index]; list[index] = value; } for (int j = 0; j < list.Count; j++) { Players[list[j]].Team = j % teamCount; } } private int SmallestTeam() { int result = 0; int num = int.MaxValue; for (int i = 0; i < ActiveTeamCount; i++) { int num2 = 0; for (int j = 0; j < Players.Count; j++) { if (Players[j].Team == i) { num2++; } } if (num2 < num) { num = num2; result = i; } } return result; } internal int TeamScore(int team) { int num = 0; for (int i = 0; i < Players.Count; i++) { if (Players[i].Team == team) { num += Players[i].Score; } } return num; } internal int TeamDayScore(int team) { int num = 0; for (int i = 0; i < Players.Count; i++) { if (Players[i].Team == team) { num += Players[i].DayScore; } } return num; } internal string TeamLabel(int team) { int num = 0; PvpPlayer pvpPlayer = null; for (int i = 0; i < Players.Count; i++) { if (Players[i].Team == team) { num++; pvpPlayer = Players[i]; } } if (num == 1 && pvpPlayer != null) { return pvpPlayer.Name; } return PvpColors.TeamName(team); } internal int TeamTopScore(out bool tie) { tie = false; if (ActiveTeamCount <= 0) { return -1; } int result = -1; int num = int.MinValue; int num2 = 0; for (int i = 0; i < ActiveTeamCount; i++) { int num3 = TeamScore(i); if (num3 > num) { num = num3; result = i; num2 = 1; } else if (num3 == num) { num2++; } } tie = num2 > 1; return result; } internal void Clear() { Players.Clear(); ActiveTeamCount = 0; } internal void ResetMatchState() { ActiveTeamCount = 0; for (int i = 0; i < Players.Count; i++) { Players[i].ResetMatchState(); Players[i].Team = i; } } internal void ResetDayScores() { for (int i = 0; i < Players.Count; i++) { Players[i].DayScore = 0; } } internal PvpPlayer LowestDayScore() { PvpPlayer pvpPlayer = null; for (int i = 0; i < Players.Count; i++) { PvpPlayer pvpPlayer2 = Players[i]; if (!pvpPlayer2.Eliminated && (pvpPlayer == null || pvpPlayer2.DayScore < pvpPlayer.DayScore)) { pvpPlayer = pvpPlayer2; } } return pvpPlayer; } internal PvpPlayer LowestDayScore(out bool tie) { tie = false; PvpPlayer pvpPlayer = LowestDayScore(); if (pvpPlayer == null) { return null; } int num = 0; for (int i = 0; i < Players.Count; i++) { if (!Players[i].Eliminated && Players[i].DayScore == pvpPlayer.DayScore) { num++; } } tie = num > 1; return pvpPlayer; } internal PvpPlayer HighestScore() { PvpPlayer pvpPlayer = null; for (int i = 0; i < Players.Count; i++) { PvpPlayer pvpPlayer2 = Players[i]; if (pvpPlayer == null || pvpPlayer2.Score > pvpPlayer.Score) { pvpPlayer = pvpPlayer2; } } return pvpPlayer; } internal PvpPlayer Leader(out bool tie) { tie = false; PvpPlayer pvpPlayer = HighestScore(); if (pvpPlayer == null) { return null; } int num = 0; for (int i = 0; i < Players.Count; i++) { if (Players[i].Score == pvpPlayer.Score) { num++; } } tie = num > 1; return pvpPlayer; } internal void LogRoster() { PvpLog.Debug($"[MultiplayerPVP] Roster: {Players.Count} player(s)."); } } public static class PvpApi { private static readonly List> _npcKoAttributors = new List>(); private static int _seq; private static MatchController C => (MultiplayerPVPMod.Instance != null) ? MultiplayerPVPMod.Instance.Controller : null; private static PvpNet N => (MultiplayerPVPMod.Instance != null) ? MultiplayerPVPMod.Instance.Net : null; public static MatchPhase Phase => C?.Phase ?? MatchPhase.Idle; public static GameModeType Mode => C?.ModeType ?? GameModeType.FreeForAll; public static int Day => C?.Day ?? 0; public static float RemainingSeconds => C?.RemainingSeconds ?? 0f; public static bool IsTeamsMatchActive { get { MatchController c = C; return c != null && c.Phase == MatchPhase.Active && c.ModeType == GameModeType.Teams; } } public static bool IsMatchActive { get { MatchController c = C; return c != null && c.Phase == MatchPhase.Active; } } public static bool IsHost { get { try { PvpNet n = N; if (n != null && n.Available) { return n.IsHost; } } catch { } try { return SessionUtil.IsHost(); } catch { return false; } } } public static bool IsMultiplayer { get { try { return SessionUtil.IsMultiplayer(); } catch { return false; } } } public static Player LocalPlayer { get { try { return Player.Local; } catch { return null; } } } public static List Players { get { try { return GameCompat.ToManaged(Player.PlayerList); } catch { return new List(); } } } public static int LocalTeam { get { MatchController c = C; if (c == null) { return -1; } try { PvpPlayer pvpPlayer = ((c.Registry != null) ? c.Registry.Local : null); if (pvpPlayer != null) { return pvpPlayer.Team; } Player local = Player.Local; if (GameCompat.Alive((Object)(object)local)) { return TeamFromRows(c, local.PlayerName); } } catch { } return -1; } } public static event Action MatchStarted; public static event Action MatchGo; public static event Action DayEnded; public static event Action MatchEnded; public static event Action PhaseChanged; public static bool RegisterAddon(IPvpAddon addon) { try { return PvpAddonManager.Register(addon); } catch { return false; } } public static bool UnregisterAddon(IPvpAddon addon) { try { return PvpAddonManager.Unregister(addon); } catch { return false; } } public static PvpChannel Channel(string name) { try { return PvpAddonManager.Channel(name); } catch { return null; } } public static PvpMatchToggle RegisterMatchToggle(string id, string label, string description = null) { try { return MatchToggles.Register(id, label, description); } catch { return null; } } public static bool UnregisterMatchToggle(PvpMatchToggle toggle) { try { return MatchToggles.Unregister(toggle); } catch { return false; } } public static void RegisterNpcKoAttributor(Func attributor) { if (attributor == null) { return; } lock (_npcKoAttributors) { if (!_npcKoAttributors.Contains(attributor)) { _npcKoAttributors.Add(attributor); } } } public static bool UnregisterNpcKoAttributor(Func attributor) { if (attributor == null) { return false; } lock (_npcKoAttributors) { return _npcKoAttributors.Remove(attributor); } } internal static bool IsLocalKoNpcCaused() { Func[] array; lock (_npcKoAttributors) { if (_npcKoAttributors.Count == 0) { return false; } array = _npcKoAttributors.ToArray(); } for (int i = 0; i < array.Length; i++) { try { if (array[i]()) { return true; } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP][Addons] KO attributor: " + ex.Message); } } return false; } internal static void ResetEvents() { PvpApi.MatchStarted = null; PvpApi.MatchGo = null; PvpApi.DayEnded = null; PvpApi.MatchEnded = null; PvpApi.PhaseChanged = null; } internal static void RaiseMatchStarted(GameModeType mode, int day) { RaiseAll(PvpApi.MatchStarted, delegate(Delegate h) { ((Action)h)(mode, day); }); } internal static void RaiseGo(GameModeType mode) { RaiseAll(PvpApi.MatchGo, delegate(Delegate h) { ((Action)h)(mode); }); } internal static void RaiseDayEnded(GameModeType mode, int day) { RaiseAll(PvpApi.DayEnded, delegate(Delegate h) { ((Action)h)(mode, day); }); } internal static void RaiseMatchEnded(GameModeType mode) { RaiseAll(PvpApi.MatchEnded, delegate(Delegate h) { ((Action)h)(mode); }); } internal static void RaisePhaseChanged(MatchPhase oldPhase, MatchPhase newPhase) { RaiseAll(PvpApi.PhaseChanged, delegate(Delegate h) { ((Action)h)(oldPhase, newPhase); }); } private static void RaiseAll(Delegate evt, Action call) { if ((object)evt == null) { return; } Delegate[] invocationList = evt.GetInvocationList(); foreach (Delegate obj in invocationList) { try { call(obj); } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP][Addons] event listener: " + ex.Message); } } } public static void Notify(string text, float seconds = 4f) { try { C?.Announcer.Notify(text, seconds); } catch { } } public static void ShowLocalBanner(string text, float seconds, Color color) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) try { C?.Announcer.ShowLocalBanner(text, seconds, color); } catch { } } public static int GetTeam(Player p) { if (!GameCompat.Alive((Object)(object)p)) { return -1; } MatchController c = C; if (c == null) { return -1; } try { PlayerRegistry registry = c.Registry; if (registry != null) { List players = registry.Players; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; if (pvpPlayer != null && (pvpPlayer.Game == p || (Object)(object)pvpPlayer.Game == (Object)(object)p)) { return pvpPlayer.Team; } } } return TeamFromRows(c, p.PlayerName); } catch { return -1; } } private static int TeamFromRows(MatchController c, string name) { if (string.IsNullOrEmpty(name) || c.RemoteRows == null) { return -1; } name = MatchController.SanitizeName(name); List remoteRows = c.RemoteRows; for (int i = 0; i < remoteRows.Count; i++) { if (string.Equals(remoteRows[i].Name, name, StringComparison.Ordinal)) { return remoteRows[i].Team; } } return -1; } public static bool TryGetHideSeekRole(Player p, out bool isSeeker) { isSeeker = false; MatchController c = C; if (c == null || c.ModeType != GameModeType.HideAndSeek || c.Phase == MatchPhase.Idle) { return false; } if (!GameCompat.Alive((Object)(object)p)) { return false; } try { PlayerRegistry registry = c.Registry; if (registry != null && !c.IsRemote) { List players = registry.Players; for (int i = 0; i < players.Count; i++) { PvpPlayer pvpPlayer = players[i]; if (pvpPlayer != null && (pvpPlayer.Game == p || (Object)(object)pvpPlayer.Game == (Object)(object)p)) { isSeeker = pvpPlayer.IsSeeker; return true; } } return false; } string text = MatchController.SanitizeName(p.PlayerName); List remoteRows = c.RemoteRows; if (string.IsNullOrEmpty(text) || remoteRows == null) { return false; } for (int j = 0; j < remoteRows.Count; j++) { if (string.Equals(remoteRows[j].Name, text, StringComparison.Ordinal)) { string text2 = remoteRows[j].Score ?? ""; if (text2.StartsWith("Seeker", StringComparison.Ordinal) || text2.StartsWith("found", StringComparison.Ordinal)) { isSeeker = true; return true; } if (text2.StartsWith("hiding", StringComparison.Ordinal)) { isSeeker = false; return true; } return false; } } } catch { } return false; } public static bool TryGetIdentity(Player p, out ulong steamId, out string persona) { steamId = 0uL; persona = null; if (!GameCompat.Alive((Object)(object)p)) { persona = "Player"; return false; } try { persona = p.PlayerName; } catch { } try { PvpNet n = N; string text = null; try { text = p.PlayerCode; } catch { } if (n != null && !string.IsNullOrEmpty(text)) { Dictionary dictionary = n.AllIdentities(); if (dictionary != null && dictionary.TryGetValue(text, out var value) && value != 0) { steamId = value; string text2 = n.PersonaOf(value); if (!string.IsNullOrEmpty(text2)) { persona = text2; } if (string.IsNullOrEmpty(persona)) { persona = "Player"; } return true; } } } catch { } if (string.IsNullOrEmpty(persona)) { persona = "Player"; } return false; } public static string TeamName(int team) { try { return TeamsMode.TeamName(team); } catch { return "Team " + team; } } public static Color TeamColor(int team) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: 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) try { return PvpColors.For(team); } catch { return Color.white; } } public static void ReportCameraSighting(int spottedTeam, ulong steamId, string persona, string cameraName) { try { int localTeam = LocalTeam; if (localTeam >= 0) { int num = ++_seq; string record = num + "~" + localTeam + "~" + spottedTeam + "~" + San(persona) + "~" + San(cameraName); PvpNet n = N; ulong fromSteamId = n?.LocalSteamId ?? 0; SightingRelay.ShowFrom(fromSteamId, record); n?.PublishSighting(record); } } catch { } } internal static string San(string s) { if (string.IsNullOrEmpty(s)) { return "?"; } StringBuilder stringBuilder = new StringBuilder(s.Length); foreach (char c in s) { if (c != '|' && c != '~' && c != ';' && c != '=' && c != ',' && c != '"' && c != '\\') { stringBuilder.Append(c); } } string text = stringBuilder.ToString().Trim(); return (text.Length == 0) ? "?" : text; } } internal static class PvpColors { private static readonly Color[] Palette = (Color[])(object)new Color[8] { new Color(0.9f, 0.2f, 0.2f), new Color(0.25f, 0.5f, 1f), new Color(0.95f, 0.85f, 0.2f), new Color(0.3f, 0.85f, 0.35f), new Color(0.8f, 0.4f, 0.95f), new Color(0.95f, 0.55f, 0.15f), new Color(0.2f, 0.85f, 0.85f), new Color(0.95f, 0.45f, 0.7f) }; private static readonly string[] TeamNames = new string[4] { "Hunter", "Cartel", "Syndicate", "Outlaws" }; internal static Color For(int index) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (index < 0) { index = 0; } return Palette[index % Palette.Length]; } internal static string Name(int index) { return (index % Palette.Length) switch { 0 => "Red", 1 => "Blue", 2 => "Yellow", 3 => "Green", 4 => "Purple", 5 => "Orange", 6 => "Cyan", _ => "Pink", }; } internal static string TeamName(int team) { if (team < 0) { team = 0; } return TeamNames[team % TeamNames.Length]; } } internal static class PvpLog { internal static void Debug(string msg) { try { if (PvpConfig.DebugLogging != null && PvpConfig.DebugLogging.Value) { MelonLogger.Msg(msg); } } catch { } } } internal class PvpPlayer { internal readonly Player Game; internal string Name; internal int Index; internal int Team; internal int Score; internal int DayScore; internal string HorseLetters = ""; internal bool HasBag; internal int CarriedBags; internal int NextObjective; internal bool Eliminated; internal bool IsSeeker; internal int Kills; internal int Downs; internal int Arrests; internal int BagsCaptured; internal int BagsLost; internal int WeaponsLost; internal int WeaponsWon; private PlayerHealth _health; private bool _wasAlive = true; private float _lastHp = -1f; private bool _statsWasAlive = true; private bool _wasArrested; internal bool IsCarryingBag => HasBag || CarriedBags > 0; internal bool Valid => GameCompat.Alive((Object)(object)Game); internal string StableKey { get { try { if (GameCompat.Alive((Object)(object)Game)) { string playerCode = Game.PlayerCode; if (!string.IsNullOrEmpty(playerCode)) { return playerCode; } } } catch { } return Name ?? ""; } } internal PlayerHealth Health { get { if (!GameCompat.Alive((Object)(object)_health) && GameCompat.Alive((Object)(object)Game)) { try { _health = ((Component)Game).GetComponentInChildren(true); } catch { _health = null; } } return _health; } } internal Vector3 Position { get { //IL_002b: 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) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) try { return GameCompat.Alive((Object)(object)Game) ? ((Component)Game).transform.position : Vector3.zero; } catch { return Vector3.zero; } } } internal bool IsAlive { get { PlayerHealth health = Health; if (!GameCompat.Alive((Object)(object)health)) { return GameCompat.Alive((Object)(object)Game); } try { return health.IsAlive; } catch { return true; } } } internal float Hp { get { PlayerHealth health = Health; if (!GameCompat.Alive((Object)(object)health)) { return 0f; } try { return health.CurrentHealth; } catch { return 0f; } } } internal PvpPlayer(Player game, int index) { Game = game; Index = index; Team = index; try { Name = (((Object)(object)game != (Object)null) ? game.PlayerName : ("Player " + (index + 1))); } catch { Name = "Player " + (index + 1); } if (string.IsNullOrEmpty(Name)) { Name = "Player " + (index + 1); } } internal bool PollWentDown() { bool isAlive = IsAlive; bool result = _wasAlive && !isAlive; _wasAlive = isAlive; return result; } internal bool PollWentDownStats() { bool isAlive = IsAlive; bool result = _statsWasAlive && !isAlive; _statsWasAlive = isAlive; return result; } internal bool PollGotArrested() { bool flag; try { flag = GameCompat.Alive((Object)(object)Game) && Game.IsArrested; } catch { return false; } bool result = !_wasArrested && flag; _wasArrested = flag; return result; } internal bool PollTookDamage() { PlayerHealth health = Health; if (!GameCompat.Alive((Object)(object)health)) { return false; } float currentHealth; try { currentHealth = health.CurrentHealth; } catch { return false; } if (currentHealth <= 0f) { return false; } if (_lastHp < 0f) { _lastHp = currentHealth; return false; } bool result = currentHealth < _lastHp - 0.01f; _lastHp = currentHealth; return result; } internal void TeleportTo(Vector3 pos) { //IL_0051: 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) //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (!GameCompat.Alive((Object)(object)Game)) { return; } try { PlayerHealth health = Health; if (GameCompat.Alive((Object)(object)health) && !health.IsAlive) { health.Revive(pos, Quaternion.identity); } else { ((Component)Game).transform.position = pos; } } catch { } } internal void Down() { PlayerHealth health = Health; if (!GameCompat.Alive((Object)(object)health)) { return; } try { if (health.IsAlive) { health.Die(); } } catch { } } internal void ReviveAt(Vector3 pos) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) PlayerHealth health = Health; if (GameCompat.Alive((Object)(object)health)) { try { health.Revive(pos, Quaternion.identity); } catch { } _wasAlive = true; _statsWasAlive = true; _lastHp = -1f; } } internal void ResetMatchState() { Score = 0; DayScore = 0; HasBag = false; CarriedBags = 0; NextObjective = 0; Eliminated = false; IsSeeker = false; Kills = 0; Downs = 0; Arrests = 0; BagsCaptured = 0; BagsLost = 0; WeaponsLost = 0; WeaponsWon = 0; _wasAlive = true; _statsWasAlive = true; _wasArrested = false; _lastHp = -1f; } } internal static class RiotChaos { private static PvpMatchToggle _riot; private static PvpMatchToggle _chaos; private static bool _prevRiotReq; private static bool _prevChaosReq; internal static void Init() { _riot = MatchToggles.Register("riot", "Riot", "City riot for this round: Benzies storm out of the door nearest each cop and brawl with the police while you play. Mutually exclusive with Chaos."); _chaos = MatchToggles.Register("chaos", "Chaos", "The city turns on YOU for this round: cops, Benzies and civilians hunt every player on sight until KO. NPC knock-outs confiscate weapons. Mutually exclusive with Riot."); if (_riot != null) { _riot.Activated += OnRiotActivated; _riot.Deactivated += OnDeactivated; } if (_chaos != null) { _chaos.Activated += OnChaosActivated; _chaos.Deactivated += OnDeactivated; } PvpApi.RegisterNpcKoAttributor(IsKoFromChaos); } private static void OnRiotActivated() { if (SessionUtil.IsMultiplayer() && SessionUtil.IsServerAuthority()) { ChaosDirector.SetOn(on: false); RiotEngine.SetOn(on: true); Announce("⚠ RIOT — the Benzies are out!"); } } private static void OnChaosActivated() { if (SessionUtil.IsMultiplayer() && SessionUtil.IsServerAuthority()) { ChaosDirector.SetOn(on: true); RiotEngine.SetOn(on: true); Announce("☠ CHAOS — the whole city is out for YOUR blood!"); } } private static void OnDeactivated() { ChaosDirector.SetOn(on: false); RiotEngine.SetOn(on: false); } private static void Announce(string text) { try { ((MultiplayerPVPMod.Instance != null) ? MultiplayerPVPMod.Instance.Controller : null)?.Announcer.Announce(text, 5f, notify: false); } catch { } } internal static void Tick(float dt, MatchController c) { try { if (_riot != null && _chaos != null) { bool requested = _riot.Requested; bool requested2 = _chaos.Requested; if (requested && requested2) { if (requested2 && !_prevChaosReq) { _riot.Requested = false; } else { _chaos.Requested = false; } } if (_chaos.Requested && !_prevChaosReq) { UnlimitedAmmo.SetChecked(on: true); } _prevRiotReq = _riot.Requested; _prevChaosReq = _chaos.Requested; } } catch { } bool flag = c == null || c.Phase != MatchPhase.Active || !SessionUtil.IsMultiplayer(); if (ChaosDirector.On && flag) { ChaosDirector.SetOn(on: false); } if (RiotEngine.On && flag) { RiotEngine.SetOn(on: false); } RiotEngine.Tick(dt); ChaosDirector.Tick(dt); ChaosQuiet.Tick(dt, ChaosActiveLocal()); } internal static void OnSceneLoaded() { RiotEngine.Reset(); ChaosDirector.Reset(); ChaosQuiet.Reset(); } internal static void Deinit() { ChaosDirector.SetOn(on: false); RiotEngine.SetOn(on: false); try { ChaosQuiet.RestoreAll(); } catch { } RiotEngine.Reset(); ChaosDirector.Reset(); try { PvpApi.UnregisterNpcKoAttributor(IsKoFromChaos); } catch { } } private static bool ChaosActiveLocal() { if (ChaosDirector.On) { return true; } try { return ((MultiplayerPVPMod.Instance != null) ? MultiplayerPVPMod.Instance.Controller : null)?.RemoteChaosActive ?? false; } catch { return false; } } private static bool IsKoFromChaos() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (!ChaosActiveLocal()) { return false; } try { Player local = Player.Local; if ((Object)(object)local == (Object)null || (Object)(object)((Component)local).transform == (Object)null) { return false; } return ChaosKoHeuristic(((Component)local).transform.position, local); } catch { return false; } } internal static bool IsDownChaosCaused(PvpPlayer victim) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (!ChaosDirector.On || victim == null) { return false; } try { if (ChaosDirector.IsHunting(victim.Game) || RiotEngine.IsHuntingPlayer(victim.Game)) { return true; } return ChaosKoHeuristic(victim.Position, victim.Game); } catch { return false; } } private static bool ChaosKoHeuristic(Vector3 victimPos, Player victimSelf) { //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) float num = float.MaxValue; List playerList = Player.PlayerList; if (playerList != null) { for (int i = 0; i < playerList.Count; i++) { try { Player val = playerList[i]; if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).transform == (Object)null || ((Object)(object)victimSelf != (Object)null && (Object)(object)val == (Object)(object)victimSelf)) { continue; } PlayerHealth componentInChildren = ((Component)val).GetComponentInChildren(true); if (!((Object)(object)componentInChildren != (Object)null) || componentInChildren.IsAlive) { float num2 = Vector3.Distance(victimPos, ((Component)val).transform.position); if (num2 < num) { num = num2; } } } catch { } } } float num3 = float.MaxValue; try { List officers = PoliceOfficer.Officers; if (officers != null) { for (int j = 0; j < officers.Count; j++) { try { PoliceOfficer val2 = officers[j]; if (!((Object)(object)val2 == (Object)null) && !((Object)(object)((Component)val2).transform == (Object)null)) { float num4 = Vector3.Distance(victimPos, ((Component)val2).transform.position); if (num4 < num3) { num3 = num4; } } } catch { } } } } catch { } try { CartelGoon[] array = Object.FindObjectsOfType(); if (array != null) { for (int k = 0; k < array.Length; k++) { try { CartelGoon val3 = array[k]; if (!((Object)(object)val3 == (Object)null) && val3.IsGoonSpawned && !((Object)(object)((Component)val3).transform == (Object)null)) { float num5 = Vector3.Distance(victimPos, ((Component)val3).transform.position); if (num5 < num3) { num3 = num5; } } } catch { } } } } catch { } return (num3 <= 25f && num3 < num) || num > 60f; } } internal static class RiotEngine { private sealed class ActiveGoon { internal CartelGoon Goon; internal PoliceOfficer Target; internal Player TargetPlayer; internal float DespawnAt; } private const int POOL_RESERVE = 1; private const float GOON_GIVE_UP = 200f; private const float RETARGET_INTERVAL = 1.5f; private const float GOON_LIFETIME = 180f; private const float ORPHAN_RADIUS = 90f; private const float DOOR_FALLBACK_DIST = 18f; private static readonly List _active = new List(); private static readonly List _engagedOfficers = new List(); private static float _nextWave; private static float _retargetCd; internal static bool On { get; private set; } internal static bool IsHuntingPlayer(Player p) { if (!On || (Object)(object)p == (Object)null) { return false; } for (int i = 0; i < _active.Count; i++) { try { if ((Object)(object)_active[i].TargetPlayer == (Object)(object)p) { return true; } } catch { } } return false; } internal static void SetOn(bool on) { if (On != on) { On = on; if (!on) { Teardown(); } else { _nextWave = 0f; _retargetCd = 0f; } PvpLog.Debug("[MultiplayerPVP][Riot] engine " + (on ? "ON" : "OFF") + (ChaosDirector.On ? " (CHAOS targeting)" : "")); } } internal static void Reset() { _active.Clear(); _engagedOfficers.Clear(); On = false; } internal static void Tick(float dt) { if (!On) { return; } try { if (!SessionUtil.IsServerAuthority()) { SetOn(on: false); return; } float time = Time.time; Prune(time); _retargetCd -= dt; if (_retargetCd <= 0f) { _retargetCd = 1.5f; Retarget(); } if (time >= _nextWave && _active.Count < Mathf.Clamp(PvpConfig.RiotBenziesMax.Value, 1, 16)) { _nextWave = time + Mathf.Max(5f, PvpConfig.RiotWaveCooldownSeconds.Value); SpawnWave(); } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP][Riot] Tick: " + ex.Message); } } private static void SpawnWave() { //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.Clamp(PvpConfig.RiotBenziesPerOfficer.Value, 1, 4); int num2 = Mathf.Clamp(PvpConfig.RiotBenziesMax.Value, 1, 16); if (_active.Count >= num2) { return; } PoliceOfficer val; Vector3 officerPos; if (ChaosDirector.On) { val = NearestOfficerToAnyPlayer(PvpConfig.RiotOfficerScanRadius.Value, int.MaxValue, out officerPos); if ((Object)(object)val == (Object)null && !TryAnyAlivePlayerPos(out officerPos)) { return; } } else { val = NearestOfficerToAnyPlayer(PvpConfig.RiotOfficerScanRadius.Value, num, out officerPos); if ((Object)(object)val == (Object)null) { return; } } if (!TryNearestDoor(officerPos, PvpConfig.RiotDoorMaxDistance.Value, out var doorPos)) { doorPos = officerPos + RandomRingDir() * 18f; } doorPos = NavPoint(doorPos); GoonPool val2 = Object.FindObjectOfType(true); if ((Object)(object)val2 == (Object)null) { return; } int num3; try { num3 = val2.UnspawnedGoonCount - 1; } catch { return; } int num4 = (ChaosDirector.On ? num : (num - AssignedCount(val))); int num5 = Mathf.Min(num4, Mathf.Min(num3, num2 - _active.Count)); if (num5 <= 0) { return; } List list = val2.SpawnMultipleGoons(doorPos, num5, false); if (list == null) { return; } int num6 = 0; for (int i = 0; i < list.Count; i++) { CartelGoon val3 = list[i]; if ((Object)(object)val3 == (Object)null) { continue; } try { CombatBehaviour val4 = ((NPC)val3).Behaviour?.CombatBehaviour; if ((Object)(object)val4 != (Object)null) { val4.GiveUpRange = 200f; } } catch { } ActiveGoon activeGoon = new ActiveGoon { Goon = val3, DespawnAt = Time.time + 180f }; if (ChaosDirector.On) { Player val5 = ChaosDirector.NearestAlivePlayer(doorPos, 180f); if ((Object)(object)val5 != (Object)null && SicPlayer(activeGoon, val5)) { num6++; } } else if (Sic(activeGoon, val)) { num6++; } _active.Add(activeGoon); } if (num6 > 0) { PvpLog.Debug($"[MultiplayerPVP][Riot] Wave: {num6} Benzies out of the door at {doorPos} ({_active.Count} active, chaos={ChaosDirector.On})."); } } private static int AssignedCount(PoliceOfficer officer) { int num = 0; for (int i = 0; i < _active.Count; i++) { try { if ((Object)(object)_active[i].Target == (Object)(object)officer) { num++; } } catch { } } return num; } private static void Retarget() { //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_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) for (int num = _active.Count - 1; num >= 0; num--) { ActiveGoon activeGoon = _active[num]; Vector3 position; try { position = ((Component)activeGoon.Goon).transform.position; } catch { Despawn(num); continue; } if (ChaosDirector.On) { if (PlayerInvalid(activeGoon.TargetPlayer)) { Player val = ChaosDirector.NearestAlivePlayer(position, 180f); if ((Object)(object)val != (Object)null) { SicPlayer(activeGoon, val); } } continue; } activeGoon.TargetPlayer = null; if (TargetInvalid(activeGoon.Target)) { PoliceOfficer val2 = NearestOfficerTo(position, 90f); if ((Object)(object)val2 == (Object)null) { Despawn(num); } else { Sic(activeGoon, val2); } } } } private static void Prune(float now) { for (int num = _active.Count - 1; num >= 0; num--) { ActiveGoon activeGoon = _active[num]; bool flag; try { flag = (Object)(object)activeGoon.Goon == (Object)null || !activeGoon.Goon.IsGoonSpawned; } catch { flag = true; } if (flag) { _active.RemoveAt(num); } else if (now >= activeGoon.DespawnAt) { Despawn(num); } } } private static void Despawn(int idx) { try { CartelGoon goon = _active[idx].Goon; if ((Object)(object)goon != (Object)null && goon.IsGoonSpawned) { goon.Despawn(); } } catch { } _active.RemoveAt(idx); } private static bool Sic(ActiveGoon ag, PoliceOfficer officer) { try { ICombatTargetable val = GameCompat.As(officer); if (val == null) { return false; } bool flag = false; try { flag = !val.IsNull() && val.IsCurrentlyTargetable; } catch { } if (!flag) { return false; } ag.Goon.AttackEntity(val, false); ag.Target = officer; ag.TargetPlayer = null; if (PvpConfig.RiotOfficersFightBack.Value && !ChaosDirector.On) { try { CartelGoon goon = ag.Goon; object obj2; if (goon == null) { obj2 = null; } else { GameObject gameObject = ((Component)goon).gameObject; obj2 = ((gameObject != null) ? gameObject.GetComponent() : null); } NetworkObject val2 = (NetworkObject)obj2; CombatBehaviour val3 = ((officer == null) ? null : ((NPC)officer).Behaviour?.CombatBehaviour); if ((Object)(object)val2 != (Object)null && (Object)(object)val3 != (Object)null) { val3.SetTargetAndEnable_Server(val2); if (!_engagedOfficers.Contains(officer)) { _engagedOfficers.Add(officer); } } } catch { } } return true; } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP][Riot] Sic: " + ex.Message); return false; } } private static bool SicPlayer(ActiveGoon ag, Player prey) { try { ICombatTargetable val = GameCompat.As(prey); if (val == null) { return false; } bool flag = false; try { flag = !val.IsNull() && val.IsCurrentlyTargetable; } catch { } if (!flag) { return false; } ag.Goon.AttackEntity(val, false); ag.TargetPlayer = prey; ag.Target = null; return true; } catch { return false; } } private static bool PlayerInvalid(Player p) { try { if ((Object)(object)p == (Object)null || (Object)(object)((Component)p).transform == (Object)null) { return true; } PlayerHealth componentInChildren = ((Component)p).GetComponentInChildren(true); return (Object)(object)componentInChildren != (Object)null && !componentInChildren.IsAlive; } catch { return true; } } private static bool TryAnyAlivePlayerPos(out Vector3 pos) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) pos = Vector3.zero; try { List playerList = Player.PlayerList; if (playerList == null) { return false; } for (int i = 0; i < playerList.Count; i++) { Player val = playerList[i]; if (!PlayerInvalid(val)) { pos = ((Component)val).transform.position; return true; } } } catch { } return false; } private static void Teardown() { for (int i = 0; i < _engagedOfficers.Count; i++) { try { PoliceOfficer obj = _engagedOfficers[i]; CombatBehaviour val = ((obj == null) ? null : ((NPC)obj).Behaviour?.CombatBehaviour); if ((Object)(object)val != (Object)null) { try { ((Behaviour)val).Deactivate(); } catch { } try { ((Behaviour)val).Disable(); } catch { } } } catch { } } _engagedOfficers.Clear(); for (int j = 0; j < _active.Count; j++) { try { CartelGoon goon = _active[j].Goon; if ((Object)(object)goon != (Object)null && goon.IsGoonSpawned) { goon.Despawn(); } } catch { } } _active.Clear(); } private static bool TargetInvalid(PoliceOfficer o) { try { if ((Object)(object)o == (Object)null || (Object)(object)((Component)o).transform == (Object)null) { return true; } NPCHealth health = ((NPC)o).Health; return (Object)(object)health == (Object)null || health.IsDead || health.IsKnockedOut; } catch { return true; } } private static PoliceOfficer NearestOfficerToAnyPlayer(float radius, int perOfficer, out Vector3 officerPos) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) officerPos = Vector3.zero; PoliceOfficer result = null; float num = float.MaxValue; try { List officers = PoliceOfficer.Officers; List playerList = Player.PlayerList; if (officers == null || playerList == null) { return null; } for (int i = 0; i < officers.Count; i++) { try { PoliceOfficer val = officers[i]; if (TargetInvalid(val) || AssignedCount(val) >= perOfficer) { continue; } Vector3 position = ((Component)val).transform.position; for (int j = 0; j < playerList.Count; j++) { Player val2 = playerList[j]; if (!((Object)(object)val2 == (Object)null)) { float num2 = Vector3.Distance(position, ((Component)val2).transform.position); if (num2 <= radius && num2 < num) { num = num2; result = val; officerPos = position; } } } } catch { } } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP][Riot] OfficerScan: " + ex.Message); } return result; } private static PoliceOfficer NearestOfficerTo(Vector3 pos, float radius) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) PoliceOfficer result = null; float num = radius; try { List officers = PoliceOfficer.Officers; if (officers == null) { return null; } for (int i = 0; i < officers.Count; i++) { try { PoliceOfficer val = officers[i]; if (!TargetInvalid(val)) { float num2 = Vector3.Distance(((Component)val).transform.position, pos); if (num2 < num) { num = num2; result = val; } } } catch { } } } catch { } return result; } private static bool TryNearestDoor(Vector3 from, float maxDistance, out Vector3 doorPos) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) doorPos = from; try { StaticDoor[] array = Object.FindObjectsOfType(); if (array == null || array.Length == 0) { return false; } float num = maxDistance * maxDistance; bool result = false; for (int i = 0; i < array.Length; i++) { StaticDoor val; try { val = array[i]; } catch { continue; } if (!((Object)(object)val == (Object)null)) { Vector3 val2; try { Transform accessPoint = val.AccessPoint; val2 = (((Object)(object)accessPoint != (Object)null) ? accessPoint.position : ((Component)val).transform.position); } catch { continue; } Vector3 val3 = val2 - from; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; doorPos = val2; result = true; } } } return result; } catch { return false; } } private static Vector3 RandomRingDir() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) Vector2 insideUnitCircle = Random.insideUnitCircle; Vector2 val = ((Vector2)(ref insideUnitCircle)).normalized; if (((Vector2)(ref val)).sqrMagnitude < 0.01f) { val = Vector2.right; } return new Vector3(val.x, 0f, val.y); } private static Vector3 NavPoint(Vector3 p) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) try { NavMeshHit val = default(NavMeshHit); if (NavMesh.SamplePosition(p + Vector3.up * 1.5f, ref val, 12f, -1)) { return ((NavMeshHit)(ref val)).position; } } catch { } return p; } } internal static class SessionUtil { internal const int MinPlayers = 2; internal static int PlayerCount() { try { Lobby val = SafeLobby(); if ((Object)(object)val != (Object)null && val.IsInLobby) { return val.PlayerCount; } } catch { } try { return GameCompat.ToManaged(Player.PlayerList).Count; } catch { return 0; } } internal static bool IsMultiplayer() { return PlayerCount() >= 2; } internal static bool IsHost() { try { Lobby val = SafeLobby(); if ((Object)(object)val != (Object)null && val.IsInLobby) { return val.IsHost; } } catch { } return true; } internal static bool IsServerAuthority() { try { NetworkManager networkManager = InstanceFinder.NetworkManager; if ((Object)(object)networkManager == (Object)null) { return true; } return networkManager.IsServer; } catch { return true; } } private static Lobby SafeLobby() { try { return Singleton.Instance; } catch { return null; } } } internal static class SightingRelay { private static readonly Dictionary _lastSeq = new Dictionary(); internal static void OnReceived(ulong fromSteamId, string record) { ShowFrom(fromSteamId, record); } internal static void ShowFrom(ulong fromSteamId, string record) { //IL_0143: Unknown result type (might be due to invalid IL or missing references) try { if (string.IsNullOrEmpty(record)) { return; } string[] array = record.Split(new char[1] { '~' }); if (array.Length < 5 || !int.TryParse(array[0], out var result) || !int.TryParse(array[1], out var result2)) { return; } int.TryParse(array[2], out var result3); string text = Cap(array[3], 40); string text2 = Cap(array[4], 40); if (_lastSeq.TryGetValue(fromSteamId, out var value) && result <= value) { return; } _lastSeq[fromSteamId] = result; if (PvpApi.LocalTeam == result2) { MatchController matchController = ((MultiplayerPVPMod.Instance != null) ? MultiplayerPVPMod.Instance.Controller : null); if (matchController != null && matchController.Announcer != null) { matchController.Announcer.ShowLocalBanner("⚠ Enemy " + text + " (Team " + PvpApi.TeamName(result3) + ") spotted on camera " + text2, 5f, PvpApi.TeamColor(result3)); } } } catch { } } private static string Cap(string s, int max) { if (string.IsNullOrEmpty(s)) { return s; } return (s.Length > max) ? s.Substring(0, max) : s; } internal static void Reset() { try { _lastSeq.Clear(); } catch { } } } internal static class SleepScreen { internal static bool Suppressing; private static bool _open; internal static void Open() { Suppressing = true; if (_open) { return; } try { DailySummary instance = NetworkSingleton.Instance; if (GameCompat.Alive((Object)(object)instance)) { instance.Open(); _open = true; } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] SleepScreen.Open: " + ex.Message); } } internal static void Close() { Suppressing = false; if (!_open) { return; } _open = false; try { DailySummary instance = NetworkSingleton.Instance; if (GameCompat.Alive((Object)(object)instance)) { instance.Close(); } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] SleepScreen.Close: " + ex.Message); } } } internal static class StartingLoadout { private static readonly string[] BuiltIn = new string[5] { "revolver", "m1911", "pumpactionshotgun", "machete", "tacticalvest" }; private static readonly string[] ShotgunAliases = new string[3] { "pumpactionshotgun", "pumpshotgun", "shotgun" }; private static readonly HashSet _checked = new HashSet(); private static List _buttons; internal static List Buttons() { if (_buttons == null) { _buttons = new List(BuiltIn); } return _buttons; } internal static string LabelOf(string id) { return (id == "pumpactionshotgun") ? "pump shotgun" : id; } internal static bool IsChecked(string id) { return _checked.Contains(id); } internal static void SetChecked(string id, bool on) { if (on) { _checked.Add(id); } else { _checked.Remove(id); } } internal static void Reset() { _checked.Clear(); } internal static string SelectedCsv() { if (_checked.Count == 0) { return ""; } StringBuilder stringBuilder = new StringBuilder(); List list = Buttons(); for (int i = 0; i < list.Count; i++) { if (_checked.Contains(list[i])) { if (stringBuilder.Length > 0) { stringBuilder.Append(','); } stringBuilder.Append(list[i]); } } return stringBuilder.ToString(); } internal static int ClearLocalInventory() { int num = 0; try { PlayerInventory instance = PlayerSingleton.Instance; if (!GameCompat.Alive((Object)(object)instance)) { return 0; } List list; try { list = GameCompat.ToManaged(instance.GetAllInventorySlots()); } catch { return 0; } if (list == null) { return 0; } for (int i = 0; i < list.Count; i++) { ItemSlot val = list[i]; if (val == null) { continue; } ItemInstance itemInstance; try { itemInstance = val.ItemInstance; } catch { continue; } if (itemInstance != null) { try { val.ClearStoredInstance(true); num++; } catch { } } } if (num > 0) { PvpLog.Debug($"[MultiplayerPVP] Round start: cleared {num} inventory slot(s)."); } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] StartingLoadout.ClearLocalInventory: " + ex.Message); } return num; } internal static void GiveLocalFromCsv(string csv) { if (string.IsNullOrEmpty(csv)) { return; } try { int num = 0; StringBuilder stringBuilder = new StringBuilder(); string[] array = csv.Split(new char[1] { ',' }); foreach (string s in array) { string text = SanitizeId(s); if (text.Length == 0) { continue; } bool flag; if (text == ShotgunAliases[0]) { flag = false; for (int j = 0; j < ShotgunAliases.Length; j++) { if (flag) { break; } flag = WeaponDrop.TryGiveLocal(ShotgunAliases[j]); } } else { flag = WeaponDrop.TryGiveLocal(text); } if (flag) { num++; if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(text); } else { PvpLog.Debug("[MultiplayerPVP] Starting loadout: item id '" + text + "' not resolvable / no room."); } } if (num > 0) { PvpLog.Debug($"[MultiplayerPVP] Starting loadout: received {num} item(s) ({stringBuilder})."); PvpApi.Notify("Loadout: " + stringBuilder); } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] StartingLoadout: " + ex.Message); } } private static string SanitizeId(string s) { if (string.IsNullOrEmpty(s)) { return ""; } StringBuilder stringBuilder = new StringBuilder(s.Length); string text = s.Trim(); foreach (char c in text) { char c2 = char.ToLowerInvariant(c); if ((c2 >= 'a' && c2 <= 'z') || (c2 >= '0' && c2 <= '9')) { stringBuilder.Append(c2); } } return stringBuilder.ToString(); } } internal static class UnlimitedAmmo { private const float REFILL_INTERVAL = 0.2f; private static bool _checked; private static bool _remote; private static float _nextRefill; internal static bool Checked => _checked; internal static void SetChecked(bool on) { _checked = on; } internal static void SetRemote(bool on) { _remote = on; } internal static void RoundEnded() { _checked = false; } internal static void Reset() { _checked = false; _remote = false; _nextRefill = 0f; } internal static void Tick(MatchController c) { if (c != null && (c.Phase == MatchPhase.Countdown || c.Phase == MatchPhase.Active) && SessionUtil.IsMultiplayer() && (SessionUtil.IsServerAuthority() ? _checked : _remote)) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup < _nextRefill)) { _nextRefill = realtimeSinceStartup + 0.2f; RefillLocal(); } } } private static void RefillLocal() { try { PlayerInventory instance = PlayerSingleton.Instance; if (!GameCompat.Alive((Object)(object)instance)) { return; } List list; try { list = GameCompat.ToManaged(instance.GetAllInventorySlots()); } catch { return; } if (list == null) { return; } for (int i = 0; i < list.Count; i++) { try { ItemSlot val = list[i]; if (val == null) { continue; } IntegerItemInstance val2 = GameCompat.As(val.ItemInstance); if (val2 != null) { IntegerItemDefinition val3 = GameCompat.As(((ItemInstance)val2).Definition); if (!((Object)(object)val3 == (Object)null) && val3.DefaultValue > 0 && !((Object)(object)GameCompat.As(((ItemDefinition)val3).Equippable) == (Object)null) && val2.Value < val3.DefaultValue) { val2.SetValue(val3.DefaultValue); } } } catch { } } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] UnlimitedAmmo: " + ex.Message); } } } internal static class WeaponDrop { private static readonly string[] WeaponIds = new string[5] { "revolver", "m1911", "pistol", "baseballbat", "machete" }; private static readonly string[] WeaponHints = new string[10] { "pistol", "revolver", "rifle", "smg", "shotgun", "gun", "machete", "knife", "bat", "weapon" }; internal static bool IsWeaponId(string id) { if (string.IsNullOrEmpty(id)) { return false; } for (int i = 0; i < WeaponIds.Length; i++) { if (string.Equals(id, WeaponIds[i], StringComparison.OrdinalIgnoreCase)) { return true; } } string text = id.ToLowerInvariant(); for (int j = 0; j < WeaponHints.Length; j++) { if (text.Contains(WeaponHints[j])) { return true; } } return false; } internal static List CollectAndRemoveLocalWeapons() { List list = new List(); try { PlayerInventory instance = PlayerSingleton.Instance; if (!GameCompat.Alive((Object)(object)instance)) { return list; } List list2; try { list2 = GameCompat.ToManaged(instance.GetAllInventorySlots()); } catch { list2 = null; } if (list2 == null) { return list; } for (int i = 0; i < list2.Count; i++) { ItemSlot val = list2[i]; if (val == null) { continue; } ItemInstance itemInstance; try { itemInstance = val.ItemInstance; } catch { continue; } if (itemInstance == null) { continue; } string iD; try { iD = ((BaseItemInstance)itemInstance).ID; } catch { continue; } if (IsWeaponId(iD)) { int num; try { num = Math.Max(1, val.Quantity); } catch { num = 1; } try { val.ClearStoredInstance(true); } catch { continue; } for (int j = 0; j < num; j++) { list.Add(iD); } } } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] WeaponDrop.CollectAndRemoveLocalWeapons: " + ex.Message); } return list; } internal static bool TryGiveLocal(string weaponId) { if (string.IsNullOrWhiteSpace(weaponId)) { return false; } try { PlayerInventory instance = PlayerSingleton.Instance; if (!GameCompat.Alive((Object)(object)instance)) { return false; } ItemDefinition item = Registry.GetItem(weaponId); if (!GameCompat.Alive((Object)(object)item)) { return false; } ItemInstance defaultInstance = item.GetDefaultInstance(1); if (defaultInstance == null) { return false; } bool flag; try { flag = instance.CanItemFitInInventory(defaultInstance, 1); } catch { flag = false; } if (!flag) { return false; } instance.AddItemToInventory(defaultInstance); return true; } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] WeaponDrop.TryGiveLocal: " + ex.Message); return false; } } } } namespace MultiplayerPVP.Config { internal static class PvpConfig { internal static MelonPreferences_Category General; internal static MelonPreferences_Category Match; internal static MelonPreferences_Category Modes; internal static MelonPreferences_Category HideSeek; internal static MelonPreferences_Category Riot; internal static MelonPreferences_Category Chaos; internal const bool ShowHud = true; internal const int BagsPerPlayer = 1; internal static MelonPreferences_Entry PanelKey; internal static MelonPreferences_Entry DebugLogging; private static MelonPreferences_Entry ConfigLayoutVersion; internal static MelonPreferences_Entry StartingCash; internal static MelonPreferences_Entry StartTime; internal static MelonPreferences_Entry MatchMinutes; internal static MelonPreferences_Entry TeamCount; internal static MelonPreferences_Entry TeleportToSpawns; internal static MelonPreferences_Entry CtfScoreToWin; internal static MelonPreferences_Entry PickupRadius; internal static MelonPreferences_Entry BagProductId; internal static MelonPreferences_Entry BagPackagingId; internal static MelonPreferences_Entry HorseWord; internal static MelonPreferences_Entry SpeedRunBestSeconds; internal static MelonPreferences_Entry SeekerCount; internal static MelonPreferences_Entry HiderWhistle; internal static MelonPreferences_Entry WhistleIntervalSeconds; internal static MelonPreferences_Entry WhistleVolume; internal static MelonPreferences_Entry WhistleRangeMeters; internal static MelonPreferences_Entry RiotBenziesPerOfficer; internal static MelonPreferences_Entry RiotBenziesMax; internal static MelonPreferences_Entry RiotOfficerScanRadius; internal static MelonPreferences_Entry RiotDoorMaxDistance; internal static MelonPreferences_Entry RiotWaveCooldownSeconds; internal static MelonPreferences_Entry RiotOfficersFightBack; internal static MelonPreferences_Entry ChaosRadius; internal static MelonPreferences_Entry ChaosCivilians; internal static MelonPreferences_Entry ChaosMaxNpcs; internal static MelonPreferences_Entry ChaosWaveSeconds; private static bool _loaded; internal static void Load() { if (!_loaded) { _loaded = true; General = MelonPreferences.CreateCategory("MultiplayerPVP_General", "Multiplayer PVP - General"); Match = MelonPreferences.CreateCategory("MultiplayerPVP_Match", "Multiplayer PVP - Match"); Modes = MelonPreferences.CreateCategory("MultiplayerPVP_GameModes", "Multiplayer PVP - Game modes"); HideSeek = MelonPreferences.CreateCategory("MultiplayerPVP_HideAndSeek", "Multiplayer PVP - Hide & Seek"); Riot = MelonPreferences.CreateCategory("MultiplayerPVP_Riot", "Multiplayer PVP - Riot modifier"); Chaos = MelonPreferences.CreateCategory("MultiplayerPVP_Chaos", "Multiplayer PVP - Chaos modifier"); PanelKey = General.CreateEntry("PanelKey", 288, "Host panel key", "KeyCode that opens the host control panel.", false, false, (ValueValidator)null, (string)null); DebugLogging = General.CreateEntry("DebugLogging", false, "Debug logging", "Verbose diagnostic logging to MelonLoader/Latest.log (HORSE income, weapon-drop & bag-spill traces, networking, banner mirrors). Default OFF — warnings/errors are always logged regardless. Turn ON to troubleshoot.", false, false, (ValueValidator)null, (string)null); ConfigLayoutVersion = General.CreateEntry("ConfigLayoutVersion", 1, "Config layout version", "Internal migration marker — do not edit.", true, false, (ValueValidator)null, (string)null); StartingCash = Match.CreateEntry("StartingCash", 5000f, "Starting cash", "Cash GRANTED (added on top of the wallet) at match start. Never sets/overwrites the balance — existing money is kept. 0 = no grant.", false, false, (ValueValidator)null, (string)null); StartTime = Match.CreateEntry("StartTime", 700, "Day start time (HHMM)", "In-game clock each day/match starts at. 700 = 07:00, 1300 = 13:00, 0 = midnight.", false, false, (ValueValidator)null, (string)null); MatchMinutes = Match.CreateEntry("MatchMinutes", 12f, "Real minutes per day", "Real-time length of one in-game day. Drives the game clock speed.", false, false, (ValueValidator)null, (string)null); TeamCount = Match.CreateEntry("TeamCount", 4, "Teams", "Number of teams for the team modes (Teams, Steal the Bags, H.O.R.S.E., Speed Run). Capped at the number of dead-drop zones (4) and the player count; players are assigned at random, balanced as evenly as possible.", false, false, (ValueValidator)null, (string)null); TeleportToSpawns = Match.CreateEntry("TeleportToSpawns", true, "Teleport to spawns", "Teleport players to their dead-drop spawn at the start of each match.", false, false, (ValueValidator)null, (string)null); CtfScoreToWin = Modes.CreateEntry("CtfScoreToWin", 3, "Captures to win (Finders Keepers)", "Finders Keepers: returns needed to win a round.", false, false, (ValueValidator)null, (string)null); PickupRadius = Modes.CreateEntry("PickupRadius", 4, "Objective reach (m)", "Reach distance for Finders Keepers / Speed Run objectives (floored at 3). Steal the Bags does NOT use this — bags are taken/deposited directly via the dead-drop stash.", false, false, (ValueValidator)null, (string)null); BagProductId = Modes.CreateEntry("BagProductId", "ogkush", "Bag product", "Product the weed bag is made of (e.g. 'ogkush'). Packaged with the packaging below.", false, false, (ValueValidator)null, (string)null); BagPackagingId = Modes.CreateEntry("BagPackagingId", "baggie", "Bag packaging", "Packaging applied to the product so the bag is a real baggie (e.g. 'baggie', 'jar', 'brick').", false, false, (ValueValidator)null, (string)null); HorseWord = Modes.CreateEntry("HorseWord", "HORSE", "H.O.R.S.E. word", "Word spelled out one letter per lost day; completing it eliminates you.", false, false, (ValueValidator)null, (string)null); SpeedRunBestSeconds = Modes.CreateEntry("SpeedRunBestSeconds", -1f, "Speed Run best time", "Persisted Speed Run high-score in seconds (-1 = none yet). Updated automatically — not a user setting.", true, false, (ValueValidator)null, (string)null); SeekerCount = HideSeek.CreateEntry("SeekerCount", 1, "Seekers", "Number of armed seekers.", false, false, (ValueValidator)null, (string)null); HiderWhistle = HideSeek.CreateEntry("HiderWhistle", true, "Hider auto-whistle", "Every still-hiding player automatically whistles on an interval — a 3D tone at their position (direction + distance by ear). Audible ONLY to seekers; hiders stay undisturbed. Big-map quality of life.", false, false, (ValueValidator)null, (string)null); WhistleIntervalSeconds = HideSeek.CreateEntry("WhistleIntervalSeconds", 10, "Whistle interval (s)", "Seconds between automatic hider whistles (minimum 3). First whistle comes one full interval after GO.", false, false, (ValueValidator)null, (string)null); WhistleVolume = HideSeek.CreateEntry("WhistleVolume", 0.8f, "Whistle volume", "Whistle tone loudness (0-1).", false, false, (ValueValidator)null, (string)null); WhistleRangeMeters = HideSeek.CreateEntry("WhistleRangeMeters", 100f, "Whistle range (m)", "How far a whistle carries (20-400). Full volume only within ~5 m, fading to SILENT at this range — so a seeker has to be reasonably close to hear it, and proximity is clearly audible.", false, false, (ValueValidator)null, (string)null); RiotBenziesPerOfficer = Riot.CreateEntry("RiotBenziesPerOfficer", 2, "Benzies per officer", "How many Benzies gang up on each targeted officer (1-4).", false, false, (ValueValidator)null, (string)null); RiotBenziesMax = Riot.CreateEntry("RiotBenziesMax", 8, "Max active Benzies", "Hard cap of simultaneously active Benzies (1-16). Shared by the Riot AND Chaos Benzie engine.", false, false, (ValueValidator)null, (string)null); RiotOfficerScanRadius = Riot.CreateEntry("RiotOfficerScanRadius", 80f, "Officer scan radius (m)", "Waves target the nearest officer within this radius of ANY player.", false, false, (ValueValidator)null, (string)null); RiotDoorMaxDistance = Riot.CreateEntry("RiotDoorMaxDistance", 80f, "Door search radius (m)", "Benzies spawn at the door nearest to the wave anchor within this radius (ring fallback beyond).", false, false, (ValueValidator)null, (string)null); RiotWaveCooldownSeconds = Riot.CreateEntry("RiotWaveCooldownSeconds", 20f, "Wave cooldown (s)", "Minimum seconds between Benzie spawn waves (min 5).", false, false, (ValueValidator)null, (string)null); RiotOfficersFightBack = Riot.CreateEntry("RiotOfficersFightBack", true, "Officers fight back", "Explicitly trigger officer counter-attacks (vanilla cops NEVER retaliate against NPCs on their own).", false, false, (ValueValidator)null, (string)null); ChaosRadius = Chaos.CreateEntry("ChaosRadius", 60f, "Hunt radius (m)", "Cops/civilians within this radius of a player join the hunt against the players.", false, false, (ValueValidator)null, (string)null); ChaosCivilians = Chaos.CreateEntry("ChaosCivilians", true, "Civilians join", "Nearby civilian NPCs also attack players (fists). CAUTION: can pull in other mods' NPCs standing near a player.", false, false, (ValueValidator)null, (string)null); ChaosMaxNpcs = Chaos.CreateEntry("ChaosMaxNpcs", 8, "Max hunters (TOTAL)", "Hard TOTAL cap of NPCs (cops + civilians combined) simultaneously hunting players (1-16). Benzies are capped separately by the Riot 'Max active Benzies'.", false, false, (ValueValidator)null, (string)null); ChaosWaveSeconds = Chaos.CreateEntry("ChaosWaveSeconds", 20f, "Recruitment wave (s)", "New hunters are recruited only in waves this many seconds apart (min 5) — fallen ones are NOT instantly replaced.", false, false, (ValueValidator)null, (string)null); MigrateLegacyFlatSection(); PvpLog.Debug("[MultiplayerPVP] Config loaded."); } } private static void MigrateLegacyFlatSection() { if (ConfigLayoutVersion.Value >= 2) { return; } ConfigLayoutVersion.Value = 2; try { string text = Path.Combine(MelonEnvironment.UserDataDirectory, "MelonPreferences.cfg"); Dictionary dictionary = ReadLegacySection(text, "MultiplayerPVP"); if (dictionary.Count > 0) { int num = 0; num += Take(dictionary, PanelKey); num += Take(dictionary, DebugLogging); num += Take(dictionary, StartingCash); num += Take(dictionary, StartTime); num += Take(dictionary, MatchMinutes); num += Take(dictionary, TeamCount); num += Take(dictionary, TeleportToSpawns); num += Take(dictionary, CtfScoreToWin); num += Take(dictionary, PickupRadius); num += Take(dictionary, BagProductId); num += Take(dictionary, BagPackagingId); num += Take(dictionary, HorseWord); num += Take(dictionary, SpeedRunBestSeconds); num += Take(dictionary, SeekerCount); num += Take(dictionary, HiderWhistle); num += Take(dictionary, WhistleIntervalSeconds); num += Take(dictionary, WhistleVolume); num += Take(dictionary, WhistleRangeMeters); num += Take(dictionary, RiotBenziesPerOfficer); num += Take(dictionary, RiotBenziesMax); num += Take(dictionary, RiotOfficerScanRadius); num += Take(dictionary, RiotDoorMaxDistance); num += Take(dictionary, RiotWaveCooldownSeconds); num += Take(dictionary, RiotOfficersFightBack); num += Take(dictionary, ChaosRadius); num += Take(dictionary, ChaosCivilians); num += Take(dictionary, ChaosMaxNpcs); num += Take(dictionary, ChaosWaveSeconds); if (num > 0) { MelonLogger.Msg($"[MultiplayerPVP] Migrated {num} setting(s) from the old flat [MultiplayerPVP] config section."); } } try { MelonPreferences.RemoveCategoryFromFile(text, "MultiplayerPVP"); } catch { } } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP] Config migration: " + ex.Message); } try { MelonPreferences.Save(); } catch { } } private static Dictionary ReadLegacySection(string path, string section) { Dictionary dictionary = new Dictionary(); if (!File.Exists(path)) { return dictionary; } bool flag = false; string[] array = File.ReadAllLines(path); foreach (string text in array) { string text2 = text.Trim(); if (text2.Length == 0 || text2.StartsWith("#")) { continue; } if (text2.StartsWith("[")) { flag = text2 == "[" + section + "]"; } else if (flag) { int num = text2.IndexOf('='); if (num > 0) { dictionary[text2.Substring(0, num).Trim()] = text2.Substring(num + 1).Trim(); } } } return dictionary; } private static int Take(Dictionary raw, MelonPreferences_Entry e) { if (e == null || !raw.TryGetValue(((MelonPreferences_Entry)e).Identifier, out var value)) { return 0; } if (!float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return 0; } e.Value = result; return 1; } private static int Take(Dictionary raw, MelonPreferences_Entry e) { if (e == null || !raw.TryGetValue(((MelonPreferences_Entry)e).Identifier, out var value)) { return 0; } if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { e.Value = result; return 1; } if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { e.Value = (int)result2; return 1; } return 0; } private static int Take(Dictionary raw, MelonPreferences_Entry e) { if (e == null || !raw.TryGetValue(((MelonPreferences_Entry)e).Identifier, out var value)) { return 0; } if (value == "true") { e.Value = true; return 1; } if (value == "false") { e.Value = false; return 1; } return 0; } private static int Take(Dictionary raw, MelonPreferences_Entry e) { if (e == null || !raw.TryGetValue(((MelonPreferences_Entry)e).Identifier, out var value)) { return 0; } if (value.Length >= 2 && value[0] == '"' && value[value.Length - 1] == '"') { value = value.Substring(1, value.Length - 2); } e.Value = value; return 1; } } } namespace MultiplayerPVP.Compat { internal static class GameCompat { internal static List ToManaged(List src) where T : class { if (src == null) { return new List(); } List list = new List(src.Count); foreach (T item in src) { if (item != null) { list.Add(item); } } return list; } internal static T As(object obj) where T : class { return obj as T; } internal static bool Alive(Object o) { return o != (Object)null; } } } namespace MultiplayerPVP.Patches { [HarmonyPatch(typeof(DailySummary), "SleepEnd")] internal static class DailySummary_SleepEnd_Patch { [HarmonyPrefix] internal static bool Prefix() { return !SleepScreen.Suppressing; } } [HarmonyPatch(typeof(PlayerHealth), "TakeDamage")] internal static class PlayerHealth_TakeDamage_Patch { [HarmonyPostfix] internal static void Postfix(PlayerHealth __instance) { try { Player val = (((Object)(object)__instance != (Object)null) ? __instance.Player : null); if ((Object)(object)val != (Object)null) { DamageTracker.RecordHit(((Object)val).GetInstanceID()); } } catch { } } } internal static class ConsoleGuard { internal static bool MatchActive() { try { MatchController matchController = MultiplayerPVPMod.Instance?.Controller; return matchController != null && matchController.Phase != MatchPhase.Idle; } catch { return false; } } } [HarmonyPatch(typeof(ConsoleUI), "SetIsOpen")] internal static class ConsoleUI_SetIsOpen_Patch { [HarmonyPrefix] internal static bool Prefix(bool open) { try { if (open && ConsoleGuard.MatchActive()) { return false; } } catch { } return true; } } [HarmonyPatch(typeof(Console), "SubmitCommand", new Type[] { typeof(string) })] internal static class Console_SubmitCommand_Patch { [HarmonyPrefix] internal static bool Prefix() { try { if (ConsoleGuard.MatchActive()) { return false; } } catch { } return true; } } } namespace MultiplayerPVP.Addons { public interface IPvpAddon { string Id { get; } void OnRegistered(); void OnUpdate(float dt); void OnGui(); void OnSceneLoaded(int buildIndex, string sceneName); void OnMatchStarted(GameModeType mode, int day); void OnGo(GameModeType mode); void OnDayEnded(GameModeType mode, int day); void OnMatchEnded(GameModeType mode); void OnPhaseChanged(MatchPhase oldPhase, MatchPhase newPhase); void OnDeinit(); } public abstract class PvpAddonBase : IPvpAddon { public abstract string Id { get; } public virtual void OnRegistered() { } public virtual void OnUpdate(float dt) { } public virtual void OnGui() { } public virtual void OnSceneLoaded(int buildIndex, string sceneName) { } public virtual void OnMatchStarted(GameModeType mode, int day) { } public virtual void OnGo(GameModeType mode) { } public virtual void OnDayEnded(GameModeType mode, int day) { } public virtual void OnMatchEnded(GameModeType mode) { } public virtual void OnPhaseChanged(MatchPhase oldPhase, MatchPhase newPhase) { } public virtual void OnDeinit() { } } internal static class PvpAddonManager { private sealed class Entry { internal IPvpAddon Addon; internal bool Disabled; internal readonly Dictionary Strikes = new Dictionary(); } private const int MaxStrikes = 10; private static readonly List _entries = new List(); private static readonly Dictionary _channels = new Dictionary(); private static MatchPhase _prevPhase = MatchPhase.Idle; private static GameModeType _lastMode = GameModeType.FreeForAll; internal static PvpNet Net { get; private set; } internal static void Init(PvpNet net) { Net = net; } internal static bool Register(IPvpAddon addon) { if (addon == null) { return false; } string text; try { text = addon.Id; } catch { text = null; } if (string.IsNullOrEmpty(text)) { MelonLogger.Warning("[MultiplayerPVP][Addons] registration rejected: addon has no Id."); return false; } lock (_entries) { for (int i = 0; i < _entries.Count; i++) { if (_entries[i].Addon == addon || IdOf(_entries[i].Addon) == text) { MelonLogger.Warning("[MultiplayerPVP][Addons] '" + text + "' is already registered."); return false; } } _entries.Add(new Entry { Addon = addon }); } MelonLogger.Msg("[MultiplayerPVP][Addons] registered '" + text + "'."); Safe(FindEntry(addon), "OnRegistered", delegate(IPvpAddon a) { a.OnRegistered(); }); return true; } internal static bool Unregister(IPvpAddon addon) { if (addon == null) { return false; } lock (_entries) { for (int i = 0; i < _entries.Count; i++) { if (_entries[i].Addon == addon) { _entries.RemoveAt(i); MelonLogger.Msg("[MultiplayerPVP][Addons] unregistered '" + IdOf(addon) + "'."); return true; } } } return false; } internal static PvpChannel Channel(string name) { string text = PvpChannel.SanitizeName(name); if (text == null) { MelonLogger.Warning("[MultiplayerPVP][Addons] invalid channel name '" + (name ?? "(null)") + "'."); return null; } lock (_channels) { if (!_channels.TryGetValue(text, out var value)) { value = new PvpChannel(text); _channels[text] = value; } value.EnsureBound(); return value; } } internal static void Tick(float dt, MatchPhase phase, GameModeType mode, int day) { lock (_channels) { foreach (KeyValuePair channel in _channels) { channel.Value.EnsureBound(); } } if (phase != _prevPhase) { MatchPhase prev = _prevPhase; _prevPhase = phase; if (phase != MatchPhase.Idle) { _lastMode = mode; } DispatchAll("OnPhaseChanged", delegate(IPvpAddon a) { a.OnPhaseChanged(prev, phase); }); PvpApi.RaisePhaseChanged(prev, phase); if (phase == MatchPhase.Countdown || (phase == MatchPhase.Active && prev != MatchPhase.Countdown)) { DispatchAll("OnMatchStarted", delegate(IPvpAddon a) { a.OnMatchStarted(mode, day); }); PvpApi.RaiseMatchStarted(mode, day); } if (phase == MatchPhase.Active) { DispatchAll("OnGo", delegate(IPvpAddon a) { a.OnGo(mode); }); PvpApi.RaiseGo(mode); } if (phase == MatchPhase.EndOfDay) { DispatchAll("OnDayEnded", delegate(IPvpAddon a) { a.OnDayEnded(mode, day); }); PvpApi.RaiseDayEnded(mode, day); } if (phase == MatchPhase.Idle) { DispatchAll("OnMatchEnded", delegate(IPvpAddon a) { a.OnMatchEnded(_lastMode); }); PvpApi.RaiseMatchEnded(_lastMode); } } DispatchAll("OnUpdate", delegate(IPvpAddon a) { a.OnUpdate(dt); }); } internal static void DrawGui() { DispatchAll("OnGui", delegate(IPvpAddon a) { a.OnGui(); }); } internal static void SceneLoaded(int buildIndex, string sceneName) { DispatchAll("OnSceneLoaded", delegate(IPvpAddon a) { a.OnSceneLoaded(buildIndex, sceneName); }); } internal static void Deinit() { DispatchAll("OnDeinit", delegate(IPvpAddon a) { a.OnDeinit(); }); lock (_entries) { _entries.Clear(); } lock (_channels) { _channels.Clear(); } try { MatchToggles.Clear(); } catch { } try { PvpApi.ResetEvents(); } catch { } _prevPhase = MatchPhase.Idle; Net = null; } private static void DispatchAll(string hook, Action call) { Entry[] array; lock (_entries) { if (_entries.Count == 0) { return; } array = _entries.ToArray(); } for (int i = 0; i < array.Length; i++) { Safe(array[i], hook, call); } } private static void Safe(Entry e, string hook, Action call) { if (e == null || e.Disabled) { return; } try { call(e.Addon); e.Strikes.Remove(hook); } catch (Exception ex) { e.Strikes.TryGetValue(hook, out var value); value = (e.Strikes[hook] = value + 1); if (value >= 10) { e.Disabled = true; MelonLogger.Error("[MultiplayerPVP][Addons] '" + IdOf(e.Addon) + "' threw " + 10 + "x in a row in " + hook + " (last: " + ex.Message + ") — addon DISABLED."); } else { MelonLogger.Warning("[MultiplayerPVP][Addons] '" + IdOf(e.Addon) + "' " + hook + ": " + ex.Message); } } } private static Entry FindEntry(IPvpAddon addon) { lock (_entries) { for (int i = 0; i < _entries.Count; i++) { if (_entries[i].Addon == addon) { return _entries[i]; } } } return null; } private static string IdOf(IPvpAddon a) { try { return (a != null) ? (a.Id ?? "(null)") : "(null)"; } catch { return "(?)"; } } } public sealed class PvpChannel { public const int MaxPayload = 3800; private ClientSyncVar _var; private bool _bindLogged; public string Name { get; } public bool Ready => _var != null; public event Action OnMessage; internal PvpChannel(string sanitizedName) { Name = sanitizedName; } internal void EnsureBound() { if (_var != null) { return; } PvpNet net = PvpAddonManager.Net; if (net != null && net.Available) { _var = net.CreateAddonVar("ax" + Name, RaiseMessage); if (_var != null && !_bindLogged) { _bindLogged = true; PvpLog.Debug("[MultiplayerPVP][Addons] channel '" + Name + "' bound (key mpvp_ax" + Name + ")."); } } } public void Publish(string payload) { try { EnsureBound(); if (_var != null) { _var.Value = Sanitize(payload); } } catch { } } public List> ReadAll() { try { EnsureBound(); return PvpNet.ReadAllOf(_var); } catch { return new List>(); } } private void RaiseMessage(ulong from, string payload) { Action action = this.OnMessage; if (action == null) { return; } Delegate[] invocationList = action.GetInvocationList(); foreach (Delegate obj in invocationList) { try { ((Action)obj)(from, payload); } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP][Addons] channel '" + Name + "' handler: " + ex.Message); } } } private static string Sanitize(string s) { if (string.IsNullOrEmpty(s)) { return ""; } StringBuilder stringBuilder = new StringBuilder(s.Length); foreach (char c in s) { if (c != '"' && c != '\\') { stringBuilder.Append(c); } } if (stringBuilder.Length > 3800) { stringBuilder.Length = 3800; } return stringBuilder.ToString(); } internal static string SanitizeName(string name) { if (string.IsNullOrEmpty(name)) { return null; } StringBuilder stringBuilder = new StringBuilder(name.Length); foreach (char c in name) { char c2 = char.ToLowerInvariant(c); if ((c2 >= 'a' && c2 <= 'z') || (c2 >= '0' && c2 <= '9')) { stringBuilder.Append(c2); } if (stringBuilder.Length >= 24) { break; } } return (stringBuilder.Length == 0) ? null : stringBuilder.ToString(); } } public sealed class PvpMatchToggle { public string Id { get; } public string Label { get; } public string Description { get; } public bool Requested { get; set; } public bool ActiveForRound { get; internal set; } public event Action Activated; public event Action Deactivated; internal PvpMatchToggle(string id, string label, string description) { Id = id; Label = label ?? id; Description = description ?? ""; } internal void FireActivated() { ActiveForRound = true; Raise(this.Activated, "Activated"); } internal void FireDeactivated() { ActiveForRound = false; Raise(this.Deactivated, "Deactivated"); } private void Raise(Action evt, string hook) { if (evt == null) { return; } Delegate[] invocationList = evt.GetInvocationList(); foreach (Delegate obj in invocationList) { try { ((Action)obj)(); } catch (Exception ex) { MelonLogger.Warning("[MultiplayerPVP][Addons] toggle '" + Id + "' " + hook + ": " + ex.Message); } } } } internal static class MatchToggles { private static readonly List _list = new List(); internal static PvpMatchToggle Register(string id, string label, string description) { if (string.IsNullOrEmpty(id)) { return null; } lock (_list) { for (int i = 0; i < _list.Count; i++) { if (_list[i].Id == id) { return _list[i]; } } PvpMatchToggle pvpMatchToggle = new PvpMatchToggle(id, label, description); _list.Add(pvpMatchToggle); MelonLogger.Msg("[MultiplayerPVP][Addons] match toggle '" + id + "' registered."); return pvpMatchToggle; } } internal static bool Unregister(PvpMatchToggle t) { if (t == null) { return false; } lock (_list) { return _list.Remove(t); } } internal static PvpMatchToggle[] Snapshot() { lock (_list) { return _list.ToArray(); } } internal static void RoundStarted() { PvpMatchToggle[] array = Snapshot(); for (int i = 0; i < array.Length; i++) { if (array[i].Requested) { array[i].FireActivated(); } } } internal static void RoundEnded() { PvpMatchToggle[] array = Snapshot(); foreach (PvpMatchToggle pvpMatchToggle in array) { bool activeForRound = pvpMatchToggle.ActiveForRound; pvpMatchToggle.Requested = false; if (activeForRound) { pvpMatchToggle.FireDeactivated(); } else { pvpMatchToggle.ActiveForRound = false; } } } internal static void Clear() { RoundEnded(); lock (_list) { _list.Clear(); } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }