Due to update 2.4.3, some mods may no longer function. FixedConfig may be necessary.
Decompiled source of NextMapVote v1.0.1
plugins/PMod.NextMapVote.dll
Decompiled 2 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Steamworks; using Steamworks.Data; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("0.0.0.0")] [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 PMod.NextMapVote { [BepInPlugin("com.pmod.nextmapvote", "PMod Next Map Vote", "1.0.0")] [BepInProcess("BoplBattle.exe")] public sealed class NextMapVotePlugin : BaseUnityPlugin { internal static ManualLogSource Log; internal static NextMapVotePlugin Instance; internal static bool Enabled; internal static int VoteSeconds; internal static bool AutoResolve; internal ConfigEntry<bool> CfgEnabled; internal ConfigEntry<int> CfgVoteSeconds; internal ConfigEntry<bool> CfgAutoResolve; private Harmony _harmony; private static float _nextHeartbeatLog; private static float _lastDebugOpen; private static int _lastSceneIndex = int.MinValue; private static readonly HashSet<ulong> _knownPlayers = new HashSet<ulong>(); private static bool _initialized; internal const byte Header0 = 78; internal const byte Header1 = 77; internal const byte Header2 = 86; internal const byte Header3 = 49; internal const byte OpBegin = 66; internal const byte OpVote = 86; internal const byte OpResult = 82; internal const byte OpTally = 84; private static readonly HashSet<string> _loggedSources = new HashSet<string>(); private void Awake() { //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Expected O, but got Unknown //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Expected O, but got Unknown //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Expected O, but got Unknown //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Expected O, but got Unknown //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Expected O, but got Unknown //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Expected O, but got Unknown //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Expected O, but got Unknown //IL_02e4: Unknown result type (might be due to invalid IL or missing references) //IL_02f1: Expected O, but got Unknown //IL_031f: Unknown result type (might be due to invalid IL or missing references) //IL_032c: Expected O, but got Unknown //IL_035a: Unknown result type (might be due to invalid IL or missing references) //IL_0367: Expected O, but got Unknown if ((Object)(object)Instance != (Object)null) { Object.Destroy((Object)(object)this); return; } Instance = this; Log = ((BaseUnityPlugin)this).Logger; ((BaseUnityPlugin)this).Logger.LogInfo((object)"NextMapVote plugin loaded"); CfgEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable voting for the next stage after a round ends."); CfgVoteSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("General", "VoteDurationSeconds", 10, "Seconds each player gets to cast a vote (3-30)."); CfgAutoResolve = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AutoResolveWhenAllVoted", true, "Resolve the vote the moment every participant has voted."); Enabled = CfgEnabled.Value; VoteSeconds = CfgVoteSeconds.Value; AutoResolve = CfgAutoResolve.Value; ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Config: Enabled={CfgEnabled.Value}, VoteSeconds={CfgVoteSeconds.Value}, AutoResolveWhenAllVoted={CfgAutoResolve.Value}"); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Build settings scenes: {SceneManager.sceneCountInBuildSettings} (maps = {SceneManager.sceneCountInBuildSettings - 6})"); _harmony = new Harmony("com.pmod.nextmapvote"); _harmony.Patch((MethodBase)AccessTools.Method(typeof(GameSession), "RandomBagLevel", (Type[])null, (Type[])null), new HarmonyMethod(typeof(NextMapVotePlugin), "RandomBagLevelPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)AccessTools.Method(typeof(GameSessionHandler), "prepareNextlevel", (Type[])null, (Type[])null), new HarmonyMethod(typeof(NextMapVotePlugin), "PrepareNextLevelPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)AccessTools.Method(typeof(SteamManager), "ForceLoadNextLevel", (Type[])null, (Type[])null), new HarmonyMethod(typeof(NextMapVotePlugin), "ForceLoadNextLevelPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)AccessTools.Method(typeof(CharacterStatsList), "ForceNextLevelImmediately", (Type[])null, (Type[])null), new HarmonyMethod(typeof(NextMapVotePlugin), "ForceNextLevelImmediatelyPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)AccessTools.Method(typeof(SteamSocket), "OnMessage", (Type[])null, (Type[])null), new HarmonyMethod(typeof(NextMapVotePlugin), "OnMessagePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)AccessTools.Method(typeof(GameSessionHandler), "LoadNextLevelScene", (Type[])null, (Type[])null), new HarmonyMethod(typeof(NextMapVotePlugin), "LoadNextLevelScenePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)AccessTools.Method(typeof(SteamManager), "Update", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(NextMapVotePlugin), "GameTickPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)AccessTools.Method(typeof(GameSessionHandler), "Update", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(NextMapVotePlugin), "GameTickPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)AccessTools.Method(typeof(MainMenu), "Update", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(NextMapVotePlugin), "GameTickPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogMessage((object)"Harmony patches applied"); _initialized = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin ready. Driven by game tick hooks."); } internal static Type ResolveTypeNoWarn(string typeName) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { Type type = assembly.GetType(typeName, throwOnError: false); if (type != null) { return type; } } catch { } } return null; } private static void MarkSource(string src) { if (_loggedSources.Add(src)) { ManualLogSource log = Log; if (log != null) { log.LogMessage((object)("[TICK-SRC] " + src + " IS ALIVE")); } } } private static void GameTickPostfix() { MarkSource("postfix"); StaticTick(); } private static void StaticTick() { if (!Enabled) { if (VoteManager.Phase != VotePhase.Idle) { VoteManager.AbortVote(); } return; } PollSceneChange(); TrackConnectedPlayers(); VoteManager.Tick(); VoteManager.TryResumeBlockedLoad(); if (Time.unscaledTime >= _nextHeartbeatLog) { _nextHeartbeatLog = Time.unscaledTime + 60f; ManualLogSource log = Log; if (log != null) { log.LogMessage((object)$"[Heartbeat] tick alive. phase={VoteManager.Phase}"); } } if (Time.unscaledTime - _lastDebugOpen > 0.35f && DebugHotkeyPressed()) { _lastDebugOpen = Time.unscaledTime; ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)"[HOTKEY] F5 pressed - opening vote window"); } VoteManager.OpenDebugVote(); } } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } private static void PollSceneChange() { //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) Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).buildIndex != _lastSceneIndex) { _lastSceneIndex = ((Scene)(ref activeScene)).buildIndex; ManualLogSource log = Log; if (log != null) { log.LogMessage((object)$"[SceneLoaded] index={((Scene)(ref activeScene)).buildIndex} name={((Scene)(ref activeScene)).name} (votePhase={VoteManager.Phase}, currentLevel={GameSession.CurrentLevel()})"); } if (((Scene)(ref activeScene)).buildIndex >= 6) { VoteManager.HandleSceneStarted(); } else { VoteManager.HandleReturnedToMenu(); } } } private static bool DebugHotkeyPressed() { try { Keyboard current = Keyboard.current; if (current != null && ((ButtonControl)current[(Key)98]).wasPressedThisFrame) { return true; } } catch { } try { if (Input.GetKeyDown((KeyCode)286)) { return true; } } catch { } return false; } private static void TrackConnectedPlayers() { if ((Object)(object)SteamManager.instance == (Object)null || SteamManager.instance.connectedPlayers == null) { _knownPlayers.Clear(); return; } HashSet<ulong> hashSet = new HashSet<ulong>(); foreach (SteamConnection connectedPlayer in SteamManager.instance.connectedPlayers) { if (connectedPlayer != null && ((ConnectionManager)connectedPlayer).Connected && ((SteamId)(ref connectedPlayer.id)).IsValid) { hashSet.Add(connectedPlayer.id.Value); } } foreach (ulong item in hashSet) { if (_knownPlayers.Add(item)) { SteamConnection conn = FindConnection(item); ManualLogSource log = Log; if (log != null) { log.LogMessage((object)$"CONNECTED with: {PlayerName(conn, item)} ({item})"); } } } if (_knownPlayers.Count <= 0) { return; } List<ulong> list = new List<ulong>(); foreach (ulong knownPlayer in _knownPlayers) { if (!hashSet.Contains(knownPlayer)) { list.Add(knownPlayer); } } foreach (ulong item2 in list) { _knownPlayers.Remove(item2); ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)$"DISCONNECTED: {item2}"); } } } private static SteamConnection FindConnection(ulong steamId) { foreach (SteamConnection connectedPlayer in SteamManager.instance.connectedPlayers) { if (connectedPlayer != null && ((SteamId)(ref connectedPlayer.id)).IsValid && connectedPlayer.id.Value == steamId) { return connectedPlayer; } } return null; } private static string PlayerName(SteamConnection conn, ulong steamId) { //IL_0024: 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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (conn != null && !string.IsNullOrEmpty(conn.steamName)) { return conn.steamName; } Friend val = new Friend(new SteamId { Value = steamId }); return ((Friend)(ref val)).Name; } private void OnGUI() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Invalid comparison between Unknown and I4 //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Invalid comparison between Unknown and I4 if (CfgEnabled.Value) { if (VoteManager.IsVoting || VoteManager.ShowResolvedOverlay) { VoteOverlayUI.Draw(); } else if ((int)Event.current.type == 4 && (int)Event.current.keyCode == 286 && Time.unscaledTime - _lastDebugOpen > 0.35f) { _lastDebugOpen = Time.unscaledTime; ((BaseUnityPlugin)this).Logger.LogMessage((object)"[HOTKEY] (OnGUI) F5 pressed - opening vote window"); VoteManager.OpenDebugVote(); } } } private static bool PrepareNextLevelPrefix() { if (!_initialized) { return true; } ManualLogSource log = Log; if (log != null) { log.LogMessage((object)($"[RoundEnd] prepareNextlevel. online={GameLobby.isOnlineGame}, replay={GameLobby.isPlayingAReplay}, " + $"init={GameSession.IsInitialized()}, host={VoteManager.IsHostInfo}")); } if (GameLobby.isOnlineGame && !SteamManager.LocalPlayerIsLobbyOwner) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)"[RoundEnd] -> online client, waiting for host Begin packet"); } return true; } if (VoteManager.TryHandleRandomBagSelection()) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogMessage((object)"[RoundEnd] -> vote opened"); } } else { ManualLogSource log4 = Log; if (log4 != null) { log4.LogMessage((object)"[RoundEnd] -> vote NOT opened (falling back to vanilla RNG)"); } } return true; } private static bool RandomBagLevelPrefix(ref int __result) { if (!_initialized) { return true; } ManualLogSource log = Log; if (log != null) { log.LogMessage((object)$"[RandomBag] fired. online={GameLobby.isOnlineGame}, voting={VoteManager.IsVoting}, phase={VoteManager.Phase}"); } if (VoteManager.IsVoting || VoteManager.HasResult) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)"[RandomBag] suppressed (vote in progress/resolved), keeping map from vote"); } return false; } return true; } private static bool ForceLoadNextLevelPrefix() { if (!_initialized) { return true; } ManualLogSource log = Log; if (log != null) { log.LogMessage((object)$"[ForceNext] online next-level drill. phase={VoteManager.Phase}, host={VoteManager.IsHostInfo}"); } if (VoteManager.LoadShouldBlock) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)"[ForceNext] vote still active - blocking level start until it closes"); } VoteManager.MarkLoadBlocked(VoteManager.LoadBlocker.ForceLoadNextLevel); return false; } if (!GameLobby.isOnlineGame || !SteamManager.LocalPlayerIsLobbyOwner) { return true; } if (!VoteManager.VoteHandledForTransition && VoteManager.TryHandleRandomBagSelection()) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogMessage((object)"[ForceNext] fallback vote started - blocking level load until resolved"); } VoteManager.MarkLoadBlocked(VoteManager.LoadBlocker.ForceLoadNextLevel); return false; } return true; } private static bool ForceNextLevelImmediatelyPrefix() { if (!_initialized) { return true; } ManualLogSource log = Log; if (log != null) { log.LogMessage((object)$"[ForceNextImmediate] round-end load drill. phase={VoteManager.Phase}, host={VoteManager.IsHostInfo}"); } if (VoteManager.LoadShouldBlock) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)"[ForceNextImmediate] vote still active - blocking level start until it closes"); } VoteManager.MarkLoadBlocked(VoteManager.LoadBlocker.ForceNextLevelImmediately); return false; } if (GameLobby.isOnlineGame && !SteamManager.LocalPlayerIsLobbyOwner) { return true; } if (!VoteManager.VoteHandledForTransition && VoteManager.TryHandleRandomBagSelection()) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogMessage((object)"[ForceNextImmediate] fallback vote started - blocking level load until resolved"); } VoteManager.MarkLoadBlocked(VoteManager.LoadBlocker.ForceNextLevelImmediately); return false; } return true; } private static bool LoadNextLevelScenePrefix() { if (_initialized) { ManualLogSource log = Log; if (log != null) { log.LogMessage((object)$"[LoadNextScene] loading map {(int)GameSession.CurrentLevel()} (vote active={VoteManager.LoadShouldBlock})"); } } if (VoteManager.LoadShouldBlock) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)"[LoadNextScene] vote still active - blocking load, will resume after the vote closes"); } VoteManager.MarkLoadBlocked(VoteManager.LoadBlocker.ForceNextLevelImmediately); return false; } VoteManager.FinalizeBeforeLevelLoad(); return true; } public static bool OnMessagePrefix(SteamSocket __instance, NetIdentity identity, IntPtr data, int size) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) if (!_initialized || size < 5) { return true; } byte[] array = new byte[4]; Marshal.Copy(data, array, 0, 4); if (array[0] != 78 || array[1] != 77 || array[2] != 86 || array[3] != 49) { return true; } byte[] array2 = new byte[size]; Marshal.Copy(data, array2, 0, size); ManualLogSource log = Log; if (log != null) { log.LogMessage((object)$"[Net] packet from {((NetIdentity)(ref identity)).SteamId.Value} size={size} op={(char)array2[4]}"); } VoteManager.HandleInboundPayload(((NetIdentity)(ref identity)).SteamId, array2, size); return false; } } internal static class PreviewLib { private static Type _t; private static bool _resolved; private static bool _loggedMissing; private static MethodInfo _tryGetSprite; private static MethodInfo _ensurePrepared; private static MethodInfo _ensureRealPreviewSync; private static PropertyInfo _preloadTotal; private static PropertyInfo _preloadDone; private static PropertyInfo _inMenu; private static PropertyInfo _customAvailable; private static PropertyInfo _customCount; private static MethodInfo _customName; private static MethodInfo _commitCustomMap; private static MethodInfo _baseLevelId; private static MethodInfo _ensureCommitted; private static PropertyInfo _currentCustomIndex; public static bool Available { get { EnsureResolved(); return _t != null; } } public static int PreloadTotal { get { EnsureResolved(); return (_preloadTotal != null) ? ((int)_preloadTotal.GetValue(null, null)) : 0; } } public static int PreloadDone { get { EnsureResolved(); return (_preloadDone != null) ? ((int)_preloadDone.GetValue(null, null)) : 0; } } public static bool InMenu { get { EnsureResolved(); return _inMenu != null && (bool)_inMenu.GetValue(null, null); } } public static bool CustomMapsAvailable { get { EnsureResolved(); return _customAvailable != null && (bool)_customAvailable.GetValue(null, null); } } public static int CustomMapCount { get { EnsureResolved(); return (_customCount != null) ? ((int)_customCount.GetValue(null, null)) : 0; } } public static int CurrentCustomMapIndex { get { EnsureResolved(); return (_currentCustomIndex != null) ? ((int)_currentCustomIndex.GetValue(null, null)) : (-1); } } public static bool TryGetSprite(int mapId, out Sprite sprite) { sprite = null; EnsureResolved(); if (_tryGetSprite == null) { return false; } object[] array = new object[2] { mapId, null }; bool flag = (bool)_tryGetSprite.Invoke(null, array); sprite = (Sprite)(flag ? /*isinst with value type is only supported in some contexts*/: null); return flag; } public static void EnsurePrepared(int mapId) { EnsureResolved(); _ensurePrepared?.Invoke(null, new object[1] { mapId }); } public static void EnsureRealPreviewSync(int mapId) { EnsureResolved(); _ensureRealPreviewSync?.Invoke(null, new object[1] { mapId }); } public static string CustomMapName(int customIndex) { EnsureResolved(); return (_customName != null) ? ((string)_customName.Invoke(null, new object[1] { customIndex })) : null; } public static int CustomMapBaseLevelId(int customIndex) { EnsureResolved(); return (_baseLevelId != null) ? ((int)_baseLevelId.Invoke(null, new object[1] { customIndex })) : 0; } public static void CommitCustomMap(int customIndex) { EnsureResolved(); _commitCustomMap?.Invoke(null, new object[1] { customIndex }); } public static void EnsureCommitted(int customIndex) { EnsureResolved(); _ensureCommitted?.Invoke(null, new object[1] { customIndex }); } private static void EnsureResolved() { if (_resolved) { return; } _resolved = true; _t = NextMapVotePlugin.ResolveTypeNoWarn("PMod.MapPreviewGenerator.PreviewLibrary"); if (_t == null) { LogMissing(); return; } _tryGetSprite = Get("TryGetSprite"); _ensurePrepared = Get("EnsurePrepared"); _ensureRealPreviewSync = Get("EnsureRealPreviewSync"); _preloadTotal = GetProp("PreloadTotal"); _preloadDone = GetProp("PreloadDone"); _inMenu = GetProp("InMenu"); _customAvailable = GetProp("CustomMapsAvailable"); _customCount = GetProp("CustomMapCount"); _currentCustomIndex = GetProp("CurrentCustomMapIndex"); _customName = Get("CustomMapName"); _commitCustomMap = Get("CommitCustomMap"); _baseLevelId = Get("CustomMapBaseLevelId"); _ensureCommitted = Get("EnsureCommitted"); if (_tryGetSprite == null || _ensurePrepared == null) { _t = null; LogMissing(); } } private static MethodInfo Get(string name) { if (_t == null) { return null; } return _t.GetMethod(name, BindingFlags.Static | BindingFlags.Public); } private static PropertyInfo GetProp(string name) { if (_t == null) { return null; } return _t.GetProperty(name, BindingFlags.Static | BindingFlags.Public); } private static void LogMissing() { if (!_loggedMissing) { _loggedMissing = true; ManualLogSource log = NextMapVotePlugin.Log; if (log != null) { log.LogWarning((object)"PreviewLib: map preview generator mod (PMod.MapPreviewGenerator) not found - running without map previews and without BoplMapMaker custom-map voting. Install the preview generator mod to enable them."); } } } } public enum VotePhase { Idle, Voting, Resolved } public static class VoteManager { public enum LoadBlocker { None, ForceLoadNextLevel, ForceNextLevelImmediately } public struct VoterSlot { public int PlayerId; public Color Color; public int VotedIndex; } private static readonly List<int> _candidates = new List<int>(3); private static readonly List<int> _candidateVotes = new List<int>(3); private static readonly Dictionary<int, int> _localVotesByPlayerId = new Dictionary<int, int>(4); private static readonly Dictionary<ulong, int> _remoteVotesBySteamId = new Dictionary<ulong, int>(8); private static int _localSeatCount; private static int _remoteSeatCount; private static float _deadline; private static bool _loadBlocked; private static float _resolvedAt; private static float _voteOpenedAt; private static bool _voteHandledForTransition; private static LoadBlocker _loadBlocker = LoadBlocker.None; public const float ResolvedDisplayDuration = 1.5f; private const int MaxCandidates = 3; private static FieldInfo _bagField; private static int _committedMapMakerIndex = -1; private static readonly Color[] FallbackPlayerColors = (Color[])(object)new Color[4] { new Color(0.92f, 0.27f, 0.27f, 1f), new Color(0.27f, 0.72f, 0.38f, 1f), new Color(0.32f, 0.55f, 0.95f, 1f), new Color(0.9f, 0.72f, 0.22f, 1f) }; private static readonly Dictionary<int, Color> _playerColorCache = new Dictionary<int, Color>(4); private static readonly Dictionary<int, Material> _slimeMaterialCache = new Dictionary<int, Material>(4); public static VotePhase Phase { get; private set; } = VotePhase.Idle; public static int ResolvedIndex { get; private set; } = -1; public static IReadOnlyList<int> CandidateMapIds => _candidates; public static IReadOnlyList<int> CandidateVotes => _candidateVotes; public static int CandidateCount => _candidates.Count; public static float TimeLeft => Mathf.Max(0f, _deadline - Time.unscaledTime); public static bool IsVoting => Phase == VotePhase.Voting; public static bool LoadShouldBlock => Phase == VotePhase.Voting || Phase == VotePhase.Resolved; public static bool HasResult => Phase == VotePhase.Resolved && ResolvedIndex >= 0; public static bool VoteHandledForTransition => _voteHandledForTransition; public static float VoteOpenedAt => _voteOpenedAt; public static string IsHostInfo => (!GameLobby.isOnlineGame) ? "offline(=host)" : ((SteamClient.IsValid && SteamManager.LocalPlayerIsLobbyOwner) ? "yes" : "no"); public static float ResolvedShowTimeLeft => Mathf.Max(0f, _resolvedAt + 1.5f - Time.unscaledTime); public static bool ShowResolvedOverlay => Phase == VotePhase.Resolved && ResolvedShowTimeLeft > 0f; private static FieldInfo BagField => _bagField ?? (_bagField = typeof(GameSession).GetField("playedLevelsBag", BindingFlags.Static | BindingFlags.NonPublic)); public static bool TryHandleRandomBagSelection() { if (Phase != VotePhase.Idle) { return false; } if (!NextMapVotePlugin.Enabled) { Log("try-select FAIL: plugin disabled"); return false; } if (GameLobby.isPlayingAReplay) { Log("try-select FAIL: replay, skipping vote"); return false; } if (!GameSession.IsInitialized()) { Log("try-select FAIL: GameSession not initialized"); return false; } int num = MapCount(); if (num < 2) { Log($"try-select FAIL: only {num} map(s) in build"); return false; } List<int> list = (PreviewLib.CustomMapsAvailable ? CollectCustomUnplayedMaps() : CollectUnplayedMaps(num)); if (list.Count == 0) { Log("try-select FAIL: no unplayed maps to vote on"); return false; } List<int> list2 = TakeCandidates(list, Mathf.Min(3, list.Count)); if (list2.Count == 0) { Log("try-select FAIL: empty candidate list"); return false; } Log(string.Format("try-select: picked {0} maps from {1} total: {2}", list2.Count, num, string.Join(", ", list2))); BeginVote(list2); return true; } public static void OpenDebugVote() { if (Phase != VotePhase.Idle) { Log("debug-vote: a vote is already running, ignoring hotkey"); return; } int num = MapCount(); if (num < 2) { Log($"debug-vote FAIL: only {num} map(s)"); return; } List<int> list = (PreviewLib.CustomMapsAvailable ? CollectCustomUnplayedMaps() : CollectUnplayedMaps(num)); if (list.Count == 0) { Log("debug-vote FAIL: no maps, using all"); for (int i = 0; i < num; i++) { list.Add(i); } } List<int> list2 = TakeCandidates(list, Mathf.Min(3, list.Count)); Log("debug-vote (HOTKEY): opening vote for " + string.Join(", ", list2)); BeginVote(list2); } public static void FinalizeBeforeLevelLoad() { _voteHandledForTransition = false; if (Phase == VotePhase.Voting) { Resolve(); } } public static void MarkLoadBlocked(LoadBlocker kind) { _loadBlocked = true; _loadBlocker = kind; Log($"Load blocked ({kind}) until vote resolves"); } public static void TryResumeBlockedLoad() { if (Phase != VotePhase.Idle || !_loadBlocked) { return; } LoadBlocker loadBlocker = _loadBlocker; _loadBlocked = false; _loadBlocker = LoadBlocker.None; Log($"Resuming blocked level load ({loadBlocker}, next map = {GameSession.CurrentLevel()})"); try { if (loadBlocker == LoadBlocker.ForceLoadNextLevel) { SteamManager.ForceLoadNextLevel(); } else { CharacterStatsList.ForceNextLevelImmediately(); } } catch (Exception arg) { Log($"Resume FAILED ({loadBlocker}): {arg}"); } } private static int MapCount() { if (PreviewLib.CustomMapsAvailable) { return PreviewLib.CustomMapCount; } int num = SceneManager.sceneCountInBuildSettings - 6; return Mathf.Max(0, num); } private static List<int> CollectCustomUnplayedMaps() { int customMapCount = PreviewLib.CustomMapCount; int currentCustomMapIndex = PreviewLib.CurrentCustomMapIndex; List<int> list = new List<int>(customMapCount); for (int i = 0; i < customMapCount; i++) { if (i != currentCustomMapIndex) { list.Add(i); } } return list; } private static bool[] PlayedBag() { FieldInfo bagField = BagField; if (bagField == null) { return null; } return bagField.GetValue(null) as bool[]; } private static void WritePlayedBag(bool[] bag) { FieldInfo bagField = BagField; if (bagField != null) { bagField.SetValue(null, bag); } } private static List<int> CollectUnplayedMaps(int mapCount) { bool[] array = PlayedBag() ?? new bool[mapCount]; if (array.Length != mapCount) { Array.Resize(ref array, mapCount); } int num = GameSession.CurrentLevel(); if (num >= 0 && num < mapCount) { array[num] = true; } bool flag = true; for (int i = 0; i < mapCount; i++) { if (!array[i]) { flag = false; break; } } if (flag) { for (int j = 0; j < mapCount; j++) { array[j] = false; } if (num >= 0 && num < mapCount) { array[num] = true; } } List<int> list = new List<int>(mapCount); for (int k = 0; k < mapCount; k++) { if (!array[k]) { list.Add(k); } } if (list.Count > 1) { int num2 = list.IndexOf(num); if (num2 >= 0) { list.RemoveAt(num2); } } WritePlayedBag(array); return list; } private static List<int> TakeCandidates(List<int> src, int take) { for (int num = src.Count - 1; num > 0; num--) { int index = Random.Range(0, num + 1); int value = src[num]; src[num] = src[index]; src[index] = value; } return (src.Count <= take) ? src : src.GetRange(0, take); } private static void BeginVote(List<int> mapIds) { NextMapVotePlugin instance = NextMapVotePlugin.Instance; ClearState(); _committedMapMakerIndex = -1; _voteHandledForTransition = true; Phase = VotePhase.Voting; ResolvedIndex = -1; _candidates.AddRange(mapIds); for (int i = 0; i < _candidates.Count; i++) { _candidateVotes.Add(0); } _deadline = Time.unscaledTime + (float)Mathf.Clamp(NextMapVotePlugin.VoteSeconds, 3, 30); _voteOpenedAt = Time.unscaledTime; CountExpectedSeats(); if (GameLobby.isOnlineGame && IsHost()) { Log("BeginVote: broadcasting candidate maps " + string.Join(",", mapIds) + " to clients"); Broadcast(BeginPacket(mapIds)); } RefreshPreviews(); Log("BeginVote: VOTING now. candidates=[" + string.Join(", ", mapIds) + "], deadline in " + $"{Mathf.Max(0f, _deadline - Time.unscaledTime):F1}s, localSeats={_localSeatCount}, remoteSeats={_remoteSeatCount}"); } private static void CountExpectedSeats() { _localSeatCount = 0; _remoteSeatCount = 0; _remoteVotesBySteamId.Clear(); List<Player> list = SafePlayerList(); if (list != null) { foreach (Player item in list) { if (item != null && item.IsLocalPlayer) { _localSeatCount++; } } } if (!GameLobby.isOnlineGame || !((Object)(object)SteamManager.instance != (Object)null)) { return; } List<SteamConnection> connectedPlayers = SteamManager.instance.connectedPlayers; if (connectedPlayers == null) { return; } foreach (SteamConnection item2 in connectedPlayers) { if (item2 != null && ((ConnectionManager)item2).Connected && ((SteamId)(ref item2.id)).IsValid) { _remoteSeatCount++; } } } private static void CommitWinningMap(int mapId) { if (PreviewLib.CustomMapsAvailable) { PreviewLib.CommitCustomMap(mapId); _committedMapMakerIndex = mapId; int num = PreviewLib.CustomMapBaseLevelId(mapId); GameSession.SetCurrentLevel((byte)num); if (GameLobby.isOnlineGame) { SteamManager.startParameters.currentLevel = (byte)num; } Log($"CommitWinner: custom map {mapId} committed to MapMaker (biome base level {num})"); } else { _committedMapMakerIndex = -1; GameSession.SetCurrentLevel((byte)mapId); if (GameLobby.isOnlineGame) { SteamManager.startParameters.currentLevel = (byte)mapId; } } } public static void Tick() { if (PreviewLib.CustomMapsAvailable && _committedMapMakerIndex >= 0) { PreviewLib.EnsureCommitted(_committedMapMakerIndex); } if (Phase == VotePhase.Resolved) { if (_loadBlocked && Time.unscaledTime - _resolvedAt > 10f) { Log("WATCHDOG: resolved phase stuck for 10s - closing vote, resuming load"); AbortVote(); } else if (ResolvedShowTimeLeft <= 0f) { AbortVote(); } } else { if (Phase != VotePhase.Voting) { return; } if (IsHost()) { NextMapVotePlugin instance = NextMapVotePlugin.Instance; bool flag = _localVotesByPlayerId.Count >= _localSeatCount && _remoteVotesBySteamId.Count >= _remoteSeatCount; bool flag2 = NextMapVotePlugin.AutoResolve && flag; if (Time.unscaledTime >= _deadline || flag2) { if (flag2) { Log("Tick: everyone voted -> early resolve"); } Resolve(); } } else if (Time.unscaledTime - _deadline > 5f) { Log("WATCHDOG: no host Result 5s after deadline - picking locally to unblock"); ResolveFallback(); } } } private static void ResolveFallback() { if (_candidates.Count == 0) { AbortVote(); return; } int num = (ResolvedIndex = Random.Range(0, _candidates.Count)); Phase = VotePhase.Resolved; _resolvedAt = Time.unscaledTime; int num3 = _candidates[num]; CommitWinningMap(num3); PreviewLib.EnsureRealPreviewSync(num3); Log($"ResolveFallback: picked map {num3} (index {num})"); } private static void Resolve() { if (_candidates.Count == 0) { Log("Resolve: no candidates, aborting vote"); AbortVote(); return; } Log(string.Format("Resolve: tallies=[{0}] (local={1}/{2}, ", string.Join(", ", _candidateVotes), _localVotesByPlayerId.Count, _localSeatCount) + $"remote={_remoteVotesBySteamId.Count}/{_remoteSeatCount})"); int num = -1; List<int> list = new List<int>(3); for (int i = 0; i < _candidateVotes.Count; i++) { int num2 = _candidateVotes[i]; if (num2 > num) { num = num2; list.Clear(); list.Add(i); } else if (num2 == num) { list.Add(i); } } ResolvedIndex = ((num <= 0) ? Random.Range(0, _candidates.Count) : list[Random.Range(0, list.Count)]); Phase = VotePhase.Resolved; _resolvedAt = Time.unscaledTime; if (list.Count > 1) { Log(string.Format("Resolve: TIE between indices [{0}] - random pick {1}", string.Join(", ", list), ResolvedIndex)); } Log($"Resolve: winner candidate index {ResolvedIndex} = map {_candidates[ResolvedIndex]} ({_candidateVotes[ResolvedIndex]} votes)"); int num3 = _candidates[ResolvedIndex]; CommitWinningMap(num3); PreviewLib.EnsureRealPreviewSync(num3); if (GameLobby.isOnlineGame && IsHost()) { Log($"Resolve: broadcast result map {num3} to clients, startParameters.currentLevel={SteamManager.startParameters.currentLevel}"); Broadcast(ResultPacket(ResolvedIndex, num3)); } Log($"Resolve: winner map {num3} committed"); } public static void AbortVote() { if (Phase != VotePhase.Idle) { Log("ABORT vote -> Idle"); } bool loadBlocked = _loadBlocked; LoadBlocker loadBlocker = _loadBlocker; Phase = VotePhase.Idle; ResolvedIndex = -1; ClearState(); _loadBlocked = loadBlocked; _loadBlocker = loadBlocker; } public static void HandleSceneStarted() { _committedMapMakerIndex = -1; if (Phase == VotePhase.Resolved) { Log($"Scene started (map {GameSession.CurrentLevel()}); resolved overlay fading out"); } } public static void HandleReturnedToMenu() { if (Phase != VotePhase.Idle) { Log("Returned to menu mid-vote; aborting"); AbortVote(); _loadBlocked = false; _loadBlocker = LoadBlocker.None; } _committedMapMakerIndex = -1; } public static void ClearState() { _candidates.Clear(); _candidateVotes.Clear(); _localVotesByPlayerId.Clear(); _remoteVotesBySteamId.Clear(); _localSeatCount = 0; _remoteSeatCount = 0; _deadline = 0f; _voteOpenedAt = 0f; _loadBlocked = false; _loadBlocker = LoadBlocker.None; _resolvedAt = 0f; _playerColorCache.Clear(); _slimeMaterialCache.Clear(); } public static List<VoterSlot> GetVoterSlots() { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: 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) List<VoterSlot> list = new List<VoterSlot>(4); List<Player> list2 = SafePlayerList(); if (list2 == null) { return list; } foreach (Player item in list2) { if (item == null) { continue; } int votedIndex = -1; int value2; if (item.IsLocalPlayer) { if (_localVotesByPlayerId.TryGetValue(item.Id, out var value)) { votedIndex = value; } } else if (_remoteVotesBySteamId.TryGetValue(item.steamId.Value, out value2)) { votedIndex = value2; } list.Add(new VoterSlot { PlayerId = item.Id, Color = PlayerColorOf(item), VotedIndex = votedIndex }); } return list; } private static Color PlayerColorOf(Player p) { //IL_0020: 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_002c: 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_0034: 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_001b: 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) int id = p.Id; if (_playerColorCache.TryGetValue(id, out var value)) { return value; } Color val = ResolvePlayerColor(p); _playerColorCache[id] = val; return val; } private static Color ResolvePlayerColor(Player p) { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: 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_00b7: 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_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) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: 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_0063: 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_0053: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: 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_0071: 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_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) Material val = null; try { val = p.Color; } catch { val = null; } if ((Object)(object)val == (Object)null) { val = SlimeMaterialFor(p.Id); } Color val2; if ((Object)(object)val != (Object)null) { val2 = MaterialColorOf(val, "_ShadowColor"); if (IsUsablePlayerColor(val2)) { return val2; } val2 = MaterialColorOf(val, "_MainColor"); if (IsUsablePlayerColor(val2)) { return val2; } val2 = SafeMaterialColor(val); if (IsUsablePlayerColor(val2)) { return val2; } } val2 = FallbackPlayerColors[Mathf.Abs(p.Id) % FallbackPlayerColors.Length]; LogColorDiagnostics(p, val, val2); return val2; } private static Color MaterialColorOf(Material m, string property) { //IL_001e: 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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_000b: 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) try { return m.HasProperty(property) ? m.GetColor(property) : Color.clear; } catch { return Color.clear; } } private static Color SafeMaterialColor(Material m) { //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 m.color; } catch { return Color.clear; } } private static bool IsUsablePlayerColor(Color c) { //IL_0001: 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_0014: 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_0029: 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_0036: Unknown result type (might be due to invalid IL or missing references) return c.a > 0.01f && c.r + c.g + c.b > 0.05f && c.r + c.g + c.b < 3f; } private static void LogColorDiagnostics(Player p, Material m, Color dotColor) { //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) //IL_0051: 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_009a: Unknown result type (might be due to invalid IL or missing references) try { string text = ((!((Object)(object)m == (Object)null)) ? string.Format("shadow={0} main={1} reflection={2} color={3}", MaterialColorOf(m, "_ShadowColor"), MaterialColorOf(m, "_MainColor"), MaterialColorOf(m, "_ReflectionColour"), SafeMaterialColor(m)) : "no material (fallback palette)"); Log($"PlayerColor: id={p.Id} local={p.IsLocalPlayer} -> dot={dotColor} ({text})"); } catch { } } private static Material SlimeMaterialFor(int playerId) { if (_slimeMaterialCache.TryGetValue(playerId, out var value)) { return value; } Material val = null; try { SlimeController[] array = Object.FindObjectsOfType<SlimeController>(); foreach (SlimeController val2 in array) { if ((Object)(object)val2 == (Object)null) { continue; } int playerId2; try { playerId2 = val2.GetPlayerId(); } catch { continue; } if (playerId2 == playerId) { try { val = val2.GetPlayerMaterial(); } catch { val = null; } if ((Object)(object)val != (Object)null) { break; } } } } catch { } _slimeMaterialCache[playerId] = val; return val; } private static void Log(string msg) { ManualLogSource log = NextMapVotePlugin.Log; if (log != null) { log.LogMessage((object)msg); } } public static void CastKeyboardMouseVote(int candidateIndex) { int num = FirstLocalPlayerId(); if (num >= 0) { CastLocalVote(num, candidateIndex); } } public static void CastLocalVote(int playerId, int candidateIndex) { if (Phase == VotePhase.Voting && candidateIndex >= 0 && candidateIndex < _candidates.Count && !_localVotesByPlayerId.ContainsKey(playerId)) { _localVotesByPlayerId[playerId] = candidateIndex; _candidateVotes[candidateIndex]++; Log($"Vote: local player {playerId} -> candidate {candidateIndex} (map {_candidates[candidateIndex]})"); if (IsHost()) { BroadcastTally(); } else if (GameLobby.isOnlineGame) { Log($"Vote: sending vote to host (map {_candidates[candidateIndex]})"); SendVoteToHost(candidateIndex, _candidates[candidateIndex]); } } } private static int FirstLocalPlayerId() { List<Player> list = SafePlayerList(); if (list == null) { return -1; } foreach (Player item in list) { if (item != null && item.IsLocalPlayer) { return item.Id; } } return -1; } public static void HandleInboundPayload(SteamId sender, byte[] pkt, int size) { //IL_0048: 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_00ac: 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_00da: 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_0071: 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) if (pkt != null && size >= 5) { switch (pkt[4]) { case 66: Log($"recv Begin from {sender.Value}"); HandleBegin(sender, pkt, size); break; case 86: Log($"recv Vote from {sender.Value} (phase={Phase})"); HandleRemoteVote(sender, pkt, size); break; case 84: HandleRemoteTally(sender, pkt, size); break; case 82: Log($"recv Result from {sender.Value}"); HandleRemoteResult(sender, pkt, size); break; default: Log($"recv UNKNOWN op {(char)pkt[4]} from {sender.Value}"); break; } } } private static void HandleBegin(SteamId sender, byte[] pkt, int size) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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) if (!SteamClient.IsValid) { return; } if (SteamId.op_Implicit(LobbyOwnerSteamId()) != SteamId.op_Implicit(sender)) { Log($"HandleBegin: ignored, sender {sender.Value} is not lobby owner"); return; } if (_loadBlocked) { Log("HandleBegin: ignored - a level load is still pending, this vote is for a future transition"); return; } List<int> list = new List<int>(3); for (int i = 0; i < 3 && 5 + i < size; i++) { byte b = pkt[5 + i]; if (b == byte.MaxValue) { break; } list.Add(b); } if (list.Count == 0) { Log("HandleBegin: no candidate ids in packet"); return; } Log("HandleBegin: host starts vote, candidates=[" + string.Join(", ", list) + "]"); BeginVote(list); } private static void HandleRemoteVote(SteamId sender, byte[] pkt, int size) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) if (Phase != VotePhase.Voting || size < 7) { return; } int num = pkt[5]; int num2 = pkt[6]; if (num < 0 || num >= _candidates.Count || _candidates[num] != num2) { Log($"HandleRemoteVote: invalid (index={num}, map={num2}) -> ignored"); return; } ulong value = sender.Value; if (!_remoteVotesBySteamId.ContainsKey(value)) { _remoteVotesBySteamId[value] = num; _candidateVotes[num]++; Log($"HandleRemoteVote: {sender.Value} voted map {num2} (index {num})"); if (IsHost()) { BroadcastTally(); } } } private static void HandleRemoteTally(SteamId sender, byte[] pkt, int size) { //IL_0021: 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) if ((Phase != VotePhase.Voting && Phase != VotePhase.Resolved) || SteamId.op_Implicit(LobbyOwnerSteamId()) != SteamId.op_Implicit(sender)) { return; } int num = Mathf.Min(_candidates.Count, size - 5); for (int i = 0; i < num; i++) { _candidateVotes[i] = pkt[5 + i]; } _remoteVotesBySteamId.Clear(); if (size >= 9) { int num2 = pkt[8]; for (int j = 0; j < num2 && 9 + j * 9 + 9 <= size; j++) { ulong key = BitConverter.ToUInt64(pkt, 9 + j * 9); int num3 = pkt[9 + j * 9 + 8]; if (num3 >= 0 && num3 < _candidates.Count) { _remoteVotesBySteamId[key] = num3; } } } Log(string.Format("HandleTally: votes=[{0}], voters={1}", string.Join(", ", _candidateVotes), _remoteVotesBySteamId.Count)); } private static void BroadcastTally() { List<KeyValuePair<ulong, int>> list = new List<KeyValuePair<ulong, int>>(8); List<Player> list2 = SafePlayerList(); if (list2 != null) { foreach (Player item in list2) { if (item != null && item.IsLocalPlayer && _localVotesByPlayerId.TryGetValue(item.Id, out var value)) { ulong num = LocalPlayerSteamId(item); if (num != 0) { list.Add(new KeyValuePair<ulong, int>(num, value)); } } } } foreach (KeyValuePair<ulong, int> item2 in _remoteVotesBySteamId) { list.Add(item2); } int num2 = Mathf.Min(list.Count, 8); byte[] array = HeaderPacket(84, 4 + num2 * 9); for (int i = 0; i < _candidateVotes.Count && i < 3; i++) { array[5 + i] = (byte)_candidateVotes[i]; } array[8] = (byte)num2; for (int j = 0; j < num2; j++) { byte[] bytes = BitConverter.GetBytes(list[j].Key); for (int k = 0; k < 8; k++) { array[9 + j * 9 + k] = bytes[k]; } array[9 + j * 9 + 8] = (byte)list[j].Value; } Broadcast(array); } private static ulong LocalPlayerSteamId(Player p) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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_0048: Unknown result type (might be due to invalid IL or missing references) if (p != null && p.steamId.Value != 0) { return p.steamId.Value; } try { if (SteamClient.IsValid && SteamClient.SteamId.Value != 0) { return SteamClient.SteamId.Value; } } catch { } return 0uL; } private static void HandleRemoteResult(SteamId sender, byte[] pkt, int size) { //IL_000e: 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) if (size < 7) { return; } if (SteamId.op_Implicit(LobbyOwnerSteamId()) != SteamId.op_Implicit(sender)) { Log("HandleResult: sender is not lobby owner, ignored"); return; } int num = pkt[5]; int num2 = pkt[6]; if (num < 0 || num >= _candidates.Count) { Log($"HandleResult: bad winner index {num}"); return; } if (_candidates[num] != num2) { Log($"HandleResult: mismatch - winner index {num} is map {_candidates[num]} but packet says {num2}, ignoring stale result"); return; } ResolvedIndex = num; Phase = VotePhase.Resolved; _resolvedAt = Time.unscaledTime; CommitWinningMap(num2); PreviewLib.EnsureRealPreviewSync(num2); Log($"HandleResult: host picked map {num2} (index {num})"); } private static byte[] HeaderPacket(byte op, int extra) { byte[] array = new byte[5 + extra]; array[0] = 78; array[1] = 77; array[2] = 86; array[3] = 49; array[4] = op; return array; } public static byte[] BeginPacket(IReadOnlyList<int> ids) { byte[] array = HeaderPacket(66, 3); for (int i = 0; i < 3; i++) { array[5 + i] = ((i < ids.Count) ? ((byte)ids[i]) : byte.MaxValue); } return array; } public static byte[] VotePacket(int index, int mapId) { byte[] array = HeaderPacket(86, 2); array[5] = (byte)index; array[6] = (byte)mapId; return array; } public static byte[] ResultPacket(int index, int mapId) { byte[] array = HeaderPacket(82, 2); array[5] = (byte)index; array[6] = (byte)mapId; return array; } private static bool IsHost() { if (!GameLobby.isOnlineGame) { return true; } return SteamClient.IsValid && SteamManager.LocalPlayerIsLobbyOwner; } private static SteamId LobbyOwnerSteamId() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_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_0024: 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) if (!SteamClient.IsValid || (Object)(object)SteamManager.instance == (Object)null) { return default(SteamId); } return ((Lobby)(ref SteamManager.instance.currentLobby)).Owner.Id; } private static void Broadcast(byte[] pkt) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) SteamManager instance = SteamManager.instance; if ((Object)(object)instance == (Object)null || instance.connectedPlayers == null) { return; } foreach (SteamConnection connectedPlayer in instance.connectedPlayers) { if (connectedPlayer != null && ((ConnectionManager)connectedPlayer).Connected) { ((Connection)(ref ((ConnectionManager)connectedPlayer).Connection)).SendMessage(pkt, (SendType)8); } } } private static void SendVoteToHost(int index, int mapId) { //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_005f: 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_0087: Unknown result type (might be due to invalid IL or missing references) SteamManager instance = SteamManager.instance; if ((Object)(object)instance == (Object)null || instance.connectedPlayers == null) { return; } SteamId val = LobbyOwnerSteamId(); foreach (SteamConnection connectedPlayer in instance.connectedPlayers) { if (connectedPlayer == null || !((ConnectionManager)connectedPlayer).Connected || SteamId.op_Implicit(connectedPlayer.id) != SteamId.op_Implicit(val)) { continue; } ((Connection)(ref ((ConnectionManager)connectedPlayer).Connection)).SendMessage(VotePacket(index, mapId), (SendType)8); break; } } public static void RefreshPreviews() { foreach (int candidate in _candidates) { PreviewLib.EnsurePrepared(candidate); } } private static List<Player> SafePlayerList() { PlayerHandler val = PlayerHandler.Get(); return (val != null) ? val.PlayerList() : null; } } public static class VoteOverlayUI { private static readonly Color PanelBg = new Color(0.06f, 0.07f, 0.09f, 0.96f); private static readonly Color CardBg = new Color(0.15f, 0.16f, 0.19f, 0.97f); private static readonly Color CardHoverBg = new Color(0.22f, 0.23f, 0.28f, 0.98f); private static readonly Color TitleColor = new Color(0.94f, 0.95f, 0.97f, 1f); private static readonly Color CaptionColor = new Color(0.74f, 0.79f, 0.87f, 1f); private static Texture2D _bgTex; private static Texture2D _cardTex; private static Texture2D _cardHoverTex; private static Texture2D _greenTex; private static Texture2D _dotTex; private static GUIStyle _panelStyle; private static GUIStyle _titleStyle; private static GUIStyle _captionStyle; private static GUIStyle _cardStyle; private static GUIStyle _cardHoverStyle; private static GUIStyle _mapNameStyle; private static float _preloadFade; private static int _preloadFadeFrame = -1; private const float CardW = 300f; private const float PreviewH = 190f; private const float CardH = 266f; private const float Spacing = 32f; public static void Draw() { //IL_0146: 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_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Invalid comparison between Unknown and I4 //IL_01fd: 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) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Invalid comparison between Unknown and I4 //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Invalid comparison between Unknown and I4 //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_029f: Invalid comparison between Unknown and I4 if (!VoteManager.IsVoting && !VoteManager.ShowResolvedOverlay) { return; } EnsureStyles(); int candidateCount = VoteManager.CandidateCount; if (candidateCount <= 0) { return; } bool flag = !VoteManager.IsVoting; float num = 1f; float num2 = 0f; if (flag) { float resolvedShowTimeLeft = VoteManager.ResolvedShowTimeLeft; float num3 = 1.5f - resolvedShowTimeLeft; if (num3 < 1f) { num2 = ((Mathf.Sin(num3 * 6.5f) > 0f) ? 1f : 0.12f); } else { num = Mathf.Clamp01(resolvedShowTimeLeft / 0.5f); num2 = 1f; } } num *= Mathf.Clamp01((Time.unscaledTime - VoteManager.VoteOpenedAt) / 0.4f); float num4 = Screen.width; float num5 = Screen.height; float num6 = (float)candidateCount * 300f + (float)(candidateCount - 1) * 32f; float num7 = (num4 - num6) / 2f; float num8 = num5 - 266f - 96f; Rect val = default(Rect); ((Rect)(ref val))..ctor(num7 - 24f, num8 - 56f, num6 + 48f, 362f); GUI.color = new Color(1f, 1f, 1f, num); GUI.Box(val, GUIContent.none, _panelStyle); int num9 = Mathf.CeilToInt(VoteManager.TimeLeft); GUI.Label(new Rect(((Rect)(ref val)).x + 18f, ((Rect)(ref val)).y + 10f, ((Rect)(ref val)).width - 36f, 32f), "VOTE FOR NEXT MAP (" + num9 + "s)", _titleStyle); Rect val2 = default(Rect); for (int i = 0; i < candidateCount; i++) { ((Rect)(ref val2))..ctor(num7, num8, 300f, 266f); DrawCard(val2, i, flag); if (flag && i == VoteManager.ResolvedIndex) { DrawGreenBorder(val2, num2 * num); } num7 += 332f; } if (!flag && (int)Event.current.type == 4) { if ((int)Event.current.keyCode == 49) { VoteManager.CastKeyboardMouseVote(0); Event.current.Use(); } else if ((int)Event.current.keyCode == 50) { VoteManager.CastKeyboardMouseVote(1); Event.current.Use(); } else if ((int)Event.current.keyCode == 51) { VoteManager.CastKeyboardMouseVote(2); Event.current.Use(); } } GUI.color = Color.white; } public static void DrawPreloadBar() { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) int preloadTotal = PreviewLib.PreloadTotal; int preloadDone = PreviewLib.PreloadDone; if (preloadTotal <= 0) { _preloadFade = 0f; return; } if (Time.frameCount != _preloadFadeFrame) { _preloadFadeFrame = Time.frameCount; bool flag = preloadDone >= preloadTotal; bool inMenu = PreviewLib.InMenu; float num = ((!flag && inMenu) ? 1f : 0f); _preloadFade = Mathf.MoveTowards(_preloadFade, num, Time.unscaledDeltaTime * 2f); } if (!(_preloadFade <= 0.01f)) { EnsureStyles(); GUI.color = new Color(1f, 1f, 1f, _preloadFade); float num2 = Mathf.Min(560f, (float)Screen.width * 0.55f); Rect val = default(Rect); ((Rect)(ref val))..ctor(((float)Screen.width - num2) / 2f, 16f, num2, 58f); GUI.Box(val, GUIContent.none, _panelStyle); float num3 = Mathf.Clamp01((float)preloadDone / (float)preloadTotal); GUI.Label(new Rect(((Rect)(ref val)).x + 16f, ((Rect)(ref val)).y + 8f, ((Rect)(ref val)).width - 32f, 24f), $"Next Map Vote: loading map previews... {preloadDone}/{preloadTotal} ({Mathf.RoundToInt(num3 * 100f)}%)", _captionStyle); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(((Rect)(ref val)).x + 16f, ((Rect)(ref val)).y + 36f, ((Rect)(ref val)).width - 32f, 12f); GUI.DrawTexture(val2, (Texture)(object)_cardTex, (ScaleMode)0); if (num3 > 0.01f) { GUI.DrawTexture(new Rect(((Rect)(ref val2)).x, ((Rect)(ref val2)).y, ((Rect)(ref val2)).width * num3, ((Rect)(ref val2)).height), (Texture)(object)_greenTex, (ScaleMode)0); } GUI.color = Color.white; } } private static void DrawGreenBorder(Rect r, float alpha) { //IL_0026: 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) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) if (!(alpha <= 0.01f)) { GUI.color = new Color(0.3f, 1f, 0.35f, alpha); GUI.DrawTexture(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).y, ((Rect)(ref r)).width, 6f), (Texture)(object)_greenTex, (ScaleMode)0); GUI.DrawTexture(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).yMax - 6f, ((Rect)(ref r)).width, 6f), (Texture)(object)_greenTex, (ScaleMode)0); GUI.DrawTexture(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).y + 6f, 6f, ((Rect)(ref r)).height - 12f), (Texture)(object)_greenTex, (ScaleMode)0); GUI.DrawTexture(new Rect(((Rect)(ref r)).xMax - 6f, ((Rect)(ref r)).y + 6f, 6f, ((Rect)(ref r)).height - 12f), (Texture)(object)_greenTex, (ScaleMode)0); GUI.color = Color.white; } } private static void DrawCard(Rect card, int index, bool resolved) { //IL_0026: 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_011b: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: 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_01f5: 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_0284: 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) //IL_02e4: Unknown result type (might be due to invalid IL or missing references) if (index < 0 || index >= VoteManager.CandidateCount) { return; } bool flag = ((Rect)(ref card)).Contains(Event.current.mousePosition); GUI.Box(card, GUIContent.none, flag ? _cardHoverStyle : _cardStyle); Rect val = default(Rect); ((Rect)(ref val))..ctor(((Rect)(ref card)).x + 8f, ((Rect)(ref card)).y + 8f, ((Rect)(ref card)).width - 16f, 190f); int num = VoteManager.CandidateMapIds[index]; if (PreviewLib.TryGetSprite(num, out var sprite) && (Object)(object)sprite != (Object)null && (Object)(object)sprite.texture != (Object)null) { Rect textureRect = sprite.textureRect; Texture texture = (Texture)(object)sprite.texture; Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(((Rect)(ref textureRect)).x / (float)texture.width, ((Rect)(ref textureRect)).y / (float)texture.height, ((Rect)(ref textureRect)).width / (float)texture.width, ((Rect)(ref textureRect)).height / (float)texture.height); GUI.DrawTextureWithTexCoords(val, texture, val2, true); } else { GUI.Box(val, GUIContent.none, _cardStyle); } List<VoteManager.VoterSlot> voterSlots = VoteManager.GetVoterSlots(); float num2 = ((Rect)(ref val)).yMax - 28f - 6f; float num3 = ((Rect)(ref val)).x + 8f; Rect val3 = default(Rect); foreach (VoteManager.VoterSlot item in voterSlots) { if (item.VotedIndex == index) { ((Rect)(ref val3))..ctor(num3, num2, 28f, 28f); GUI.color = new Color(0f, 0f, 0f, 1f); GUI.DrawTexture(new Rect(num3 - 2f, num2 - 2f, 32f, 32f), (Texture)(object)_dotTex, (ScaleMode)0); GUI.color = item.Color; GUI.DrawTexture(val3, (Texture)(object)_dotTex, (ScaleMode)0); GUI.color = Color.white; num3 += 36f; } } string text = (PreviewLib.CustomMapsAvailable ? PreviewLib.CustomMapName(num) : ("MAP " + num)); GUI.Label(new Rect(((Rect)(ref card)).x + 12f, ((Rect)(ref val)).yMax + 10f, ((Rect)(ref card)).width - 24f, 30f), text, _mapNameStyle); GUI.Label(new Rect(((Rect)(ref card)).x + 12f, ((Rect)(ref val)).yMax + 40f, ((Rect)(ref card)).width - 24f, 20f), "1/2/3 or click", _captionStyle); if (!resolved && flag && (int)Event.current.type == 0 && Event.current.button == 0) { VoteManager.CastKeyboardMouseVote(index); Event.current.Use(); } } private static void EnsureStyles() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_0052: 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_008d: Expected O, but got Unknown //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Expected O, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Expected O, but got Unknown //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Expected O, but got Unknown //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Expected O, but got Unknown //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Expected O, but got Unknown //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Expected O, but got Unknown //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Expected O, but got Unknown //IL_025b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_bgTex == (Object)null) { _bgTex = MakeTex(PanelBg); _cardTex = MakeTex(CardBg); _cardHoverTex = MakeTex(CardHoverBg); _greenTex = MakeTex(new Color(0.3f, 1f, 0.35f, 1f)); _dotTex = MakeCircleTex(); } if (_panelStyle == null) { _panelStyle = new GUIStyle(GUI.skin.box); _panelStyle.normal.background = _bgTex; _panelStyle.border = new RectOffset(24, 24, 24, 24); } if (_titleStyle == null) { _titleStyle = new GUIStyle(GUI.skin.label); _titleStyle.fontSize = 26; _titleStyle.fontStyle = (FontStyle)1; _titleStyle.alignment = (TextAnchor)4; _titleStyle.normal.textColor = TitleColor; } if (_captionStyle == null) { _captionStyle = new GUIStyle(GUI.skin.label); _captionStyle.fontSize = 14; _captionStyle.alignment = (TextAnchor)3; _captionStyle.normal.textColor = CaptionColor; } if (_cardStyle == null) { _cardStyle = new GUIStyle(GUI.skin.box); _cardStyle.normal.background = _cardTex; _cardStyle.border = new RectOffset(18, 18, 18, 18); } if (_cardHoverStyle == null) { _cardHoverStyle = new GUIStyle(GUI.skin.box); _cardHoverStyle.normal.background = _cardHoverTex; _cardHoverStyle.border = new RectOffset(18, 18, 18, 18); } if (_mapNameStyle == null) { _mapNameStyle = new GUIStyle(GUI.skin.label); _mapNameStyle.fontSize = 20; _mapNameStyle.fontStyle = (FontStyle)1; _mapNameStyle.alignment = (TextAnchor)3; _mapNameStyle.normal.textColor = TitleColor; } } private static Texture2D MakeTex(Color c) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Expected O, but got Unknown //IL_000c: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(1, 1); val.SetPixel(0, 0, c); val.Apply(); return val; } private static Texture2D MakeCircleTex() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(32, 32, (TextureFormat)4, false); float num = 15.5f; for (int i = 0; i < 32; i++) { for (int j = 0; j < 32; j++) { float num2 = ((float)j - num) / num; float num3 = ((float)i - num) / num; float num4 = Mathf.Sqrt(num2 * num2 + num3 * num3); float num5 = Mathf.Clamp01((1f - num4) * 10f); val.SetPixel(j, i, new Color(1f, 1f, 1f, num5)); } } val.Apply(); ((Object)val).name = "nextmapvote_dot"; return val; } } }