using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Unity.Collections; using Unity.Netcode; using UnityEngine; 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: AssemblyTitle("ScrapVisbility")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ScrapVisbility")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("8a6853bd-bdc9-4741-95c7-5aa2c8c6a6f9")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace YourFurnace.RandomStartSuit; [BepInPlugin("YourFurnace.RandomStartSuit", "RandomStartSuit", "1.6.0")] public class Plugin : BaseUnityPlugin { private sealed class SuitAssignment { public ulong ClientId; public int PlayerIndex; public int SuitId; public float ReapplyUntil; } [HarmonyPatch(typeof(UnlockableSuit), "SwitchSuitServerRpc")] private static class SwitchSuitServerRpcPatch { private static void Postfix(UnlockableSuit __instance, int playerID) { try { if (!((Object)(object)Instance == (Object)null) && !((Object)(object)__instance == (Object)null) && suppressSuitCapture <= 0) { Instance.HostObservedSuitChangeByPlayerIndex(playerID, __instance.suitID, "SwitchSuitServerRpc"); } } catch (Exception ex) { if (Log != null) { Log.LogWarning((object)("SwitchSuitServerRpc patch failed: " + ShortException(ex))); } } } } [HarmonyPatch(typeof(UnlockableSuit), "SwitchSuitToThis")] private static class SwitchSuitToThisPatch { private static void Postfix(UnlockableSuit __instance, PlayerControllerB playerWhoTriggered) { try { if (!((Object)(object)Instance == (Object)null) && !((Object)(object)__instance == (Object)null) && suppressSuitCapture <= 0) { Instance.HostObservedSuitChangeForPlayer(playerWhoTriggered, __instance.suitID, "SwitchSuitToThis"); } } catch (Exception ex) { if (Log != null) { Log.LogWarning((object)("SwitchSuitToThis patch failed: " + ShortException(ex))); } } } } [HarmonyPatch(typeof(UnlockableSuit), "SwitchSuitForPlayer")] private static class SwitchSuitForPlayerPatch { private static void Postfix(PlayerControllerB player, int suitID) { try { if (!((Object)(object)Instance == (Object)null) && suppressSuitCapture <= 0) { Instance.HostObservedSuitChangeForPlayer(player, suitID, "SwitchSuitForPlayer"); } } catch (Exception ex) { if (Log != null) { Log.LogWarning((object)("SwitchSuitForPlayer patch failed: " + ShortException(ex))); } } } } public const string PluginGuid = "YourFurnace.RandomStartSuit"; public const string PluginName = "RandomStartSuit"; public const string PluginVersion = "1.6.0"; private const string AssignmentMessage = "YourFurnace.RandomStartSuit.Assignments.v6"; private const string RequestMessage = "YourFurnace.RandomStartSuit.RequestAssignments.v6"; private const string AllowedFirstRandomMessage = "YourFurnace.RandomStartSuit.AllowedFirstRandom.v6"; private const string FirstRandomPickMessage = "YourFurnace.RandomStartSuit.FirstRandomPick.v6"; internal static Plugin Instance; private static ManualLogSource Log; private static readonly Random Rng = new Random(); private static int suppressSuitCapture; private ConfigEntry allowedSuitsCsv; private ConfigEntry firstAssignmentDelay; private ConfigEntry hostScanInterval; private ConfigEntry hostFallbackFirstRandomSeconds; private ConfigEntry clientRequestRetrySeconds; private ConfigEntry clientRequestMaxAttempts; private ConfigEntry eventReapplySeconds; private ConfigEntry eventReapplyInterval; private ConfigEntry onlyAssignInShipLobby; private ConfigEntry logDebug; private readonly Dictionary hostOfficialByClientId = new Dictionary(); private readonly Dictionary clientOfficialByClientId = new Dictionary(); private readonly Dictionary firstSeenAtByClientId = new Dictionary(); private readonly Dictionary allowedOfferSentAtByClientId = new Dictionary(); private readonly Dictionary pendingFirstPickByClientId = new Dictionary(); private NetworkManager registeredNetworkManager; private Harmony harmony; private int currentStartOfRoundId = -1; private float nextHostScanAt; private float nextLocalApplyAt; private float nextClientRequestAt; private int clientRequestAttempts; private bool clientFirstPickSent; private bool clientFirstPickAccepted; private void Awake() { //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; allowedSuitsCsv = ((BaseUnityPlugin)this).Config.Bind("Suit Selection", "Allowed suit names", "FemaleBlue,FemaleClothed,FemaleLime,FemaleOrange,FemalePink,FemaleStar,RealMan", "Comma separated exact unlockable names used only for the first random suit. Manual suit changes may use any valid suit."); firstAssignmentDelay = ((BaseUnityPlugin)this).Config.Bind("Assignment", "First assignment delay seconds", 3f, "How long the host waits after a player appears before choosing that player's first random suit."); hostScanInterval = ((BaseUnityPlugin)this).Config.Bind("Assignment", "Host scan interval seconds", 0.5f, "How often the host checks for newly joined players that still need their first random suit."); hostFallbackFirstRandomSeconds = ((BaseUnityPlugin)this).Config.Bind("Assignment", "Host fallback first random seconds", 8f, "If the client never answers the allowed-list handshake, the host picks from the allowed list after this many seconds. This prevents a player from being stuck in the vanilla orange suit."); clientRequestRetrySeconds = ((BaseUnityPlugin)this).Config.Bind("Networking", "Client request retry seconds", 2f, "How often a joining client asks the host for the official suit table / allowed random list until the host gives that client an official suit."); clientRequestMaxAttempts = ((BaseUnityPlugin)this).Config.Bind("Networking", "Client request max attempts", 8, "Maximum handshake request attempts a client sends after joining. This is not a forever loop."); onlyAssignInShipLobby = ((BaseUnityPlugin)this).Config.Bind("Assignment", "Only assign while in ship lobby", false, "If true, the host stops giving first random suits once the ship leaves the lobby phase."); eventReapplySeconds = ((BaseUnityPlugin)this).Config.Bind("Networking", "Event local apply seconds", 2.5f, "After the host sends a suit update, each machine reapplies that official update briefly to beat vanilla spawn/default-suit timing. This is not a forever loop."); eventReapplyInterval = ((BaseUnityPlugin)this).Config.Bind("Networking", "Event local apply interval seconds", 0.25f, "How often a machine reapplies the most recent official update during the short event window."); logDebug = ((BaseUnityPlugin)this).Config.Bind("Debug", "Log debug info", true, "Logs first random assignments, manual suit changes captured by the host, and official table sends."); try { harmony = new Harmony("YourFurnace.RandomStartSuit"); harmony.PatchAll(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not apply Harmony patches: " + ShortException(ex))); } SceneManager.sceneLoaded += OnSceneLoaded; ((BaseUnityPlugin)this).Logger.LogInfo((object)"RandomStartSuit 1.6.0 loaded. Host authoritative. First join ignores vanilla default suit and uses random handshake/fallback. Event sync only."); } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; UnregisterMessages(); try { if (harmony != null) { harmony.UnpatchSelf(); } } catch { } if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (IsMenuScene(((Scene)(ref scene)).name)) { ClearAllState(); DebugLog("Menu scene loaded. Assignment state cleared."); } } private void Update() { EnsureMessageHandlers(); StartOfRound startOfRound = GetStartOfRound(); if ((Object)(object)startOfRound == (Object)null) { return; } int instanceID = ((Object)startOfRound).GetInstanceID(); if (instanceID != currentStartOfRoundId) { currentStartOfRoundId = instanceID; hostOfficialByClientId.Clear(); clientOfficialByClientId.Clear(); firstSeenAtByClientId.Clear(); allowedOfferSentAtByClientId.Clear(); pendingFirstPickByClientId.Clear(); nextHostScanAt = Time.realtimeSinceStartup + 0.25f; nextLocalApplyAt = 0f; nextClientRequestAt = Time.realtimeSinceStartup + 1f; clientRequestAttempts = 0; clientFirstPickSent = false; clientFirstPickAccepted = false; DebugLog("New StartOfRound detected. Suit state reset for this round instance."); } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening) { return; } if (singleton.IsServer || singleton.IsHost) { if (Time.realtimeSinceStartup >= nextHostScanAt) { nextHostScanAt = Time.realtimeSinceStartup + Mathf.Max(0.15f, hostScanInterval.Value); HostScanForFirstRandomSuits(startOfRound); RemoveStaleHostAssignments(startOfRound); } } else if (singleton.IsClient && !clientFirstPickAccepted && Time.realtimeSinceStartup >= nextClientRequestAt && clientRequestAttempts < Mathf.Max(1, clientRequestMaxAttempts.Value)) { clientRequestAttempts++; nextClientRequestAt = Time.realtimeSinceStartup + Mathf.Max(0.5f, clientRequestRetrySeconds.Value); SendAssignmentRequestToHost(); } if (Time.realtimeSinceStartup >= nextLocalApplyAt) { nextLocalApplyAt = Time.realtimeSinceStartup + Mathf.Max(0.05f, eventReapplyInterval.Value); if (singleton.IsServer || singleton.IsHost) { ApplyAssignmentsLocally(startOfRound, hostOfficialByClientId.Values, "host-event-reapply", logResult: false); } else { ApplyAssignmentsLocally(startOfRound, clientOfficialByClientId.Values, "client-event-reapply", logResult: false); } } } private void ClearAllState() { hostOfficialByClientId.Clear(); clientOfficialByClientId.Clear(); firstSeenAtByClientId.Clear(); allowedOfferSentAtByClientId.Clear(); pendingFirstPickByClientId.Clear(); currentStartOfRoundId = -1; clientRequestAttempts = 0; clientFirstPickSent = false; clientFirstPickAccepted = false; nextClientRequestAt = 0f; nextHostScanAt = 0f; nextLocalApplyAt = 0f; } private void HostScanForFirstRandomSuits(StartOfRound start) { if (onlyAssignInShipLobby.Value && !start.inShipPhase) { return; } List allowedSuitIds = GetAllowedSuitIds(start); if (allowedSuitIds.Count == 0) { DebugLog("No allowed first-random suits found from config: " + allowedSuitsCsv.Value); return; } PlayerControllerB[] allPlayerScripts = start.allPlayerScripts; if (allPlayerScripts == null) { return; } NetworkManager singleton = NetworkManager.Singleton; ulong num = (((Object)(object)singleton != (Object)null) ? singleton.LocalClientId : 0); float realtimeSinceStartup = Time.realtimeSinceStartup; bool flag = false; for (int i = 0; i < allPlayerScripts.Length; i++) { PlayerControllerB player = allPlayerScripts[i]; if (!IsRealPlayer(player)) { continue; } ulong playerClientId = GetPlayerClientId(player, i); if (hostOfficialByClientId.ContainsKey(playerClientId)) { continue; } if (!firstSeenAtByClientId.ContainsKey(playerClientId)) { firstSeenAtByClientId[playerClientId] = realtimeSinceStartup; DebugLog("Host saw new player for first random timer. playerIndex=" + i + " clientId=" + playerClientId); } else { if (realtimeSinceStartup - firstSeenAtByClientId[playerClientId] < Mathf.Max(0f, firstAssignmentDelay.Value)) { continue; } int value; if (playerClientId == num) { value = allowedSuitIds[Rng.Next(allowedSuitIds.Count)]; flag |= HostSetOfficialFirstRandom(start, player, i, playerClientId, value, "host-local-random"); continue; } if (pendingFirstPickByClientId.TryGetValue(playerClientId, out value)) { if (!allowedSuitIds.Contains(value)) { DebugLog("Host rejected invalid pending first pick from clientId=" + playerClientId + " suitID=" + value + ". Picking fallback from host list."); value = allowedSuitIds[Rng.Next(allowedSuitIds.Count)]; } pendingFirstPickByClientId.Remove(playerClientId); flag |= HostSetOfficialFirstRandom(start, player, i, playerClientId, value, "client-picked-from-host-list"); continue; } float value2; bool flag2 = allowedOfferSentAtByClientId.TryGetValue(playerClientId, out value2); if (!flag2 || realtimeSinceStartup - value2 >= 2f) { SendAllowedFirstRandomListToClient(playerClientId, allowedSuitIds); allowedOfferSentAtByClientId[playerClientId] = realtimeSinceStartup; if (!flag2) { value2 = realtimeSinceStartup; } } if (realtimeSinceStartup - firstSeenAtByClientId[playerClientId] >= Mathf.Max(firstAssignmentDelay.Value + 1f, hostFallbackFirstRandomSeconds.Value)) { value = allowedSuitIds[Rng.Next(allowedSuitIds.Count)]; DebugLog("Host fallback picked first random suit for clientId=" + playerClientId + " because no handshake pick arrived."); flag |= HostSetOfficialFirstRandom(start, player, i, playerClientId, value, "host-fallback-random"); } } } if (flag) { BroadcastAssignmentsToClients(); } } private bool HostSetOfficialFirstRandom(StartOfRound start, PlayerControllerB player, int playerIndex, ulong clientId, int suitId, string reason) { if (hostOfficialByClientId.ContainsKey(clientId)) { return false; } if (!IsValidSuitId(start, suitId)) { return false; } SuitAssignment suitAssignment = new SuitAssignment { ClientId = clientId, PlayerIndex = playerIndex, SuitId = suitId, ReapplyUntil = Time.realtimeSinceStartup + Mathf.Max(0.5f, eventReapplySeconds.Value) }; hostOfficialByClientId[clientId] = suitAssignment; firstSeenAtByClientId[clientId] = Time.realtimeSinceStartup; DebugLog("Host accepted first random suit from " + reason + ": Player #" + playerIndex + " clientId=" + clientId + " suit=" + GetUnlockableName(start, suitId) + " id=" + suitId); ApplyAssignmentLocally(start, suitAssignment, "host-first-random", logResult: true); return true; } private void RemoveStaleHostAssignments(StartOfRound start) { PlayerControllerB[] allPlayerScripts = start.allPlayerScripts; if (allPlayerScripts == null) { return; } HashSet liveClientIds = new HashSet(); for (int i = 0; i < allPlayerScripts.Length; i++) { if (IsRealPlayer(allPlayerScripts[i])) { liveClientIds.Add(GetPlayerClientId(allPlayerScripts[i], i)); } } List list = hostOfficialByClientId.Keys.Where((ulong clientId) => !liveClientIds.Contains(clientId)).ToList(); if (list.Count == 0) { return; } foreach (ulong item in list) { hostOfficialByClientId.Remove(item); firstSeenAtByClientId.Remove(item); allowedOfferSentAtByClientId.Remove(item); pendingFirstPickByClientId.Remove(item); DebugLog("Host removed stale suit assignment for disconnected clientId=" + item); } BroadcastAssignmentsToClients(); } internal void HostObservedSuitChangeByPlayerIndex(int playerIndex, int suitId, string reason) { if (suppressSuitCapture > 0) { return; } NetworkManager singleton = NetworkManager.Singleton; if (!((Object)(object)singleton == (Object)null) && (singleton.IsServer || singleton.IsHost)) { StartOfRound startOfRound = GetStartOfRound(); if (!((Object)(object)startOfRound == (Object)null) && startOfRound.allPlayerScripts != null && playerIndex >= 0 && playerIndex < startOfRound.allPlayerScripts.Length) { PlayerControllerB player = startOfRound.allPlayerScripts[playerIndex]; HostObservedSuitChangeForPlayer(player, suitId, reason); } } } internal void HostObservedSuitChangeForPlayer(PlayerControllerB player, int suitId, string reason) { if (suppressSuitCapture > 0) { return; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || (!singleton.IsServer && !singleton.IsHost)) { return; } StartOfRound startOfRound = GetStartOfRound(); if ((Object)(object)startOfRound == (Object)null || !IsRealPlayer(player) || !IsValidSuitId(startOfRound, suitId)) { return; } int playerIndex = GetPlayerIndex(startOfRound, player); if (playerIndex >= 0) { ulong playerClientId = GetPlayerClientId(player, playerIndex); SuitAssignment value; if (!hostOfficialByClientId.ContainsKey(playerClientId)) { DebugLog("Host ignored pre-random suit change from " + reason + " for unassigned playerIndex=" + playerIndex + " clientId=" + playerClientId + " suit=" + GetUnlockableName(startOfRound, suitId) + " id=" + suitId); } else if (!hostOfficialByClientId.TryGetValue(playerClientId, out value) || value.SuitId != suitId || value.PlayerIndex != playerIndex) { SuitAssignment suitAssignment = new SuitAssignment { ClientId = playerClientId, PlayerIndex = playerIndex, SuitId = suitId, ReapplyUntil = Time.realtimeSinceStartup + Mathf.Max(0.5f, eventReapplySeconds.Value) }; hostOfficialByClientId[playerClientId] = suitAssignment; firstSeenAtByClientId[playerClientId] = Time.realtimeSinceStartup; DebugLog("Host accepted suit change from " + reason + ": Player #" + playerIndex + " clientId=" + playerClientId + " suit=" + GetUnlockableName(startOfRound, suitId) + " id=" + suitId); ApplyAssignmentLocally(startOfRound, suitAssignment, "host-accepted-change", logResult: false); BroadcastAssignmentsToClients(); } } } private void ApplyAssignmentsLocally(StartOfRound start, IEnumerable assignments, string reason, bool logResult) { float realtimeSinceStartup = Time.realtimeSinceStartup; foreach (SuitAssignment item in assignments.ToList()) { if (!(item.ReapplyUntil <= 0f) && !(realtimeSinceStartup > item.ReapplyUntil)) { ApplyAssignmentLocally(start, item, reason, logResult); } } } private bool ApplyAssignmentLocally(StartOfRound start, SuitAssignment assignment, string reason, bool logResult) { PlayerControllerB val = FindPlayer(start, assignment); if ((Object)(object)val == (Object)null) { if (logResult) { DebugLog("Apply " + reason + " failed. Player not found for playerIndex=" + assignment.PlayerIndex + " clientId=" + assignment.ClientId); } return false; } bool result = ApplySuitToPlayer(val, assignment.SuitId); if (logResult || (logDebug.Value && reason == "client-received")) { DebugLog("Apply " + reason + " playerIndex=" + assignment.PlayerIndex + " clientId=" + assignment.ClientId + " suit=" + GetUnlockableName(start, assignment.SuitId) + " id=" + assignment.SuitId + " applied=" + result); } return result; } private bool ApplySuitToPlayer(PlayerControllerB player, int suitId) { if ((Object)(object)player == (Object)null || suitId < 0) { return false; } suppressSuitCapture++; try { player.currentSuitID = suitId; UnlockableSuit.SwitchSuitForPlayer(player, suitId, false); player.currentSuitID = suitId; return true; } catch (Exception ex) { DebugLog("SwitchSuitForPlayer failed for suitID=" + suitId + ": " + ShortException(ex)); } finally { suppressSuitCapture = Math.Max(0, suppressSuitCapture - 1); } try { player.currentSuitID = suitId; return true; } catch { return false; } } private void EnsureMessageHandlers() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening) { if ((Object)(object)registeredNetworkManager != (Object)null) { UnregisterMessages(); } } else if (!((Object)(object)registeredNetworkManager == (Object)(object)singleton)) { UnregisterMessages(); try { singleton.CustomMessagingManager.RegisterNamedMessageHandler("YourFurnace.RandomStartSuit.Assignments.v6", new HandleNamedMessageDelegate(OnAssignmentsMessageReceived)); singleton.CustomMessagingManager.RegisterNamedMessageHandler("YourFurnace.RandomStartSuit.RequestAssignments.v6", new HandleNamedMessageDelegate(OnRequestMessageReceived)); singleton.CustomMessagingManager.RegisterNamedMessageHandler("YourFurnace.RandomStartSuit.AllowedFirstRandom.v6", new HandleNamedMessageDelegate(OnAllowedFirstRandomListMessageReceived)); singleton.CustomMessagingManager.RegisterNamedMessageHandler("YourFurnace.RandomStartSuit.FirstRandomPick.v6", new HandleNamedMessageDelegate(OnFirstRandomPickMessageReceived)); registeredNetworkManager = singleton; DebugLog("Registered RandomStartSuit named messages."); } catch (Exception ex) { Log.LogWarning((object)("Could not register named messages: " + ShortException(ex))); } } } private void UnregisterMessages() { if ((Object)(object)registeredNetworkManager == (Object)null || registeredNetworkManager.CustomMessagingManager == null) { registeredNetworkManager = null; return; } try { registeredNetworkManager.CustomMessagingManager.UnregisterNamedMessageHandler("YourFurnace.RandomStartSuit.Assignments.v6"); registeredNetworkManager.CustomMessagingManager.UnregisterNamedMessageHandler("YourFurnace.RandomStartSuit.RequestAssignments.v6"); registeredNetworkManager.CustomMessagingManager.UnregisterNamedMessageHandler("YourFurnace.RandomStartSuit.AllowedFirstRandom.v6"); registeredNetworkManager.CustomMessagingManager.UnregisterNamedMessageHandler("YourFurnace.RandomStartSuit.FirstRandomPick.v6"); } catch { } registeredNetworkManager = null; } private void BroadcastAssignmentsToClients() { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening || singleton.CustomMessagingManager == null || (!singleton.IsServer && !singleton.IsHost)) { return; } try { foreach (ulong connectedClientsId in singleton.ConnectedClientsIds) { if (connectedClientsId != singleton.LocalClientId) { FastBufferWriter val = BuildAssignmentsWriter(); try { singleton.CustomMessagingManager.SendNamedMessage("YourFurnace.RandomStartSuit.Assignments.v6", connectedClientsId, val, (NetworkDelivery)3); } finally { ((IDisposable)(FastBufferWriter)(ref val)).Dispose(); } } } DebugLog("Host broadcast official suit table with " + hostOfficialByClientId.Count + " assignment(s)."); } catch (Exception ex) { Log.LogWarning((object)("Could not broadcast assignments: " + ShortException(ex))); } } private void SendAssignmentsToClient(ulong targetClientId) { //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_0062: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening || singleton.CustomMessagingManager == null || (!singleton.IsServer && !singleton.IsHost)) { return; } try { FastBufferWriter val = BuildAssignmentsWriter(); try { singleton.CustomMessagingManager.SendNamedMessage("YourFurnace.RandomStartSuit.Assignments.v6", targetClientId, val, (NetworkDelivery)3); } finally { ((IDisposable)(FastBufferWriter)(ref val)).Dispose(); } DebugLog("Host answered table request from clientId=" + targetClientId + " with " + hostOfficialByClientId.Count + " assignment(s)."); } catch (Exception ex) { Log.LogWarning((object)("Could not send assignments to clientId=" + targetClientId + ": " + ShortException(ex))); } } private FastBufferWriter BuildAssignmentsWriter() { //IL_0023: 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_0059: 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_0071: 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_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: 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_00b6: Unknown result type (might be due to invalid IL or missing references) int count = hostOfficialByClientId.Count; FastBufferWriter result = default(FastBufferWriter); ((FastBufferWriter)(ref result))..ctor(16 + count * 24, (Allocator)2, -1); ((FastBufferWriter)(ref result)).WriteValueSafe(ref count, default(ForPrimitives)); foreach (SuitAssignment value in hostOfficialByClientId.Values) { ((FastBufferWriter)(ref result)).WriteValueSafe(ref value.ClientId, default(ForPrimitives)); ((FastBufferWriter)(ref result)).WriteValueSafe(ref value.PlayerIndex, default(ForPrimitives)); ((FastBufferWriter)(ref result)).WriteValueSafe(ref value.SuitId, default(ForPrimitives)); } return result; } private void SendAssignmentRequestToHost() { //IL_0040: 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_005b: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening || singleton.CustomMessagingManager == null) { return; } try { FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(4, (Allocator)2, -1); try { int num = 1; ((FastBufferWriter)(ref val)).WriteValueSafe(ref num, default(ForPrimitives)); singleton.CustomMessagingManager.SendNamedMessage("YourFurnace.RandomStartSuit.RequestAssignments.v6", 0uL, val, (NetworkDelivery)3); } finally { ((IDisposable)(FastBufferWriter)(ref val)).Dispose(); } DebugLog("Client requested official suit table from host."); } catch (Exception ex) { DebugLog("Could not request assignments from host: " + ShortException(ex)); } } private void OnRequestMessageReceived(ulong senderClientId, FastBufferReader reader) { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || (!singleton.IsServer && !singleton.IsHost)) { return; } DebugLog("Host received official table request from clientId=" + senderClientId); SendAssignmentsToClient(senderClientId); StartOfRound startOfRound = GetStartOfRound(); if (!((Object)(object)startOfRound == (Object)null) && (!onlyAssignInShipLobby.Value || startOfRound.inShipPhase) && !hostOfficialByClientId.ContainsKey(senderClientId)) { List allowedSuitIds = GetAllowedSuitIds(startOfRound); if (allowedSuitIds.Count > 0) { SendAllowedFirstRandomListToClient(senderClientId, allowedSuitIds); allowedOfferSentAtByClientId[senderClientId] = Time.realtimeSinceStartup; } } } private void SendAllowedFirstRandomListToClient(ulong targetClientId, List allowedSuitIds) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008f: 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_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening || singleton.CustomMessagingManager == null || (!singleton.IsServer && !singleton.IsHost) || allowedSuitIds == null || allowedSuitIds.Count == 0) { return; } try { FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(8 + allowedSuitIds.Count * 4, (Allocator)2, -1); try { int count = allowedSuitIds.Count; ((FastBufferWriter)(ref val)).WriteValueSafe(ref count, default(ForPrimitives)); for (int i = 0; i < allowedSuitIds.Count; i++) { count = allowedSuitIds[i]; ((FastBufferWriter)(ref val)).WriteValueSafe(ref count, default(ForPrimitives)); } singleton.CustomMessagingManager.SendNamedMessage("YourFurnace.RandomStartSuit.AllowedFirstRandom.v6", targetClientId, val, (NetworkDelivery)3); } finally { ((IDisposable)(FastBufferWriter)(ref val)).Dispose(); } DebugLog("Host sent allowed first-random suit list to clientId=" + targetClientId + " count=" + allowedSuitIds.Count); } catch (Exception ex) { Log.LogWarning((object)("Could not send allowed first-random list to clientId=" + targetClientId + ": " + ShortException(ex))); } } private void OnAllowedFirstRandomListMessageReceived(ulong senderClientId, FastBufferReader reader) { //IL_002b: 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_005f: 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) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || singleton.IsServer) { return; } try { int num = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe(ref num, default(ForPrimitives)); if (num > 0) { List list = new List(num); int item = default(int); for (int i = 0; i < num; i++) { ((FastBufferReader)(ref reader)).ReadValueSafe(ref item, default(ForPrimitives)); list.Add(item); } if (clientFirstPickAccepted || clientOfficialByClientId.ContainsKey(singleton.LocalClientId)) { DebugLog("Client ignored allowed first-random list because host already sent an official suit."); return; } if (clientFirstPickSent && clientRequestAttempts < 2) { DebugLog("Client ignored duplicate allowed first-random list because a first pick was already sent."); return; } int suitId = list[Rng.Next(list.Count)]; clientFirstPickSent = true; SendFirstRandomPickToHost(suitId); } } catch (Exception ex) { Log.LogWarning((object)("Could not read allowed first-random list: " + ShortException(ex))); } } private void SendFirstRandomPickToHost(int suitId) { //IL_0041: 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_005b: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening || singleton.CustomMessagingManager == null) { return; } try { FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(4, (Allocator)2, -1); try { ((FastBufferWriter)(ref val)).WriteValueSafe(ref suitId, default(ForPrimitives)); singleton.CustomMessagingManager.SendNamedMessage("YourFurnace.RandomStartSuit.FirstRandomPick.v6", 0uL, val, (NetworkDelivery)3); } finally { ((IDisposable)(FastBufferWriter)(ref val)).Dispose(); } DebugLog("Client picked first random suitID=" + suitId + " from host allowed list and sent it for host approval."); } catch (Exception ex) { DebugLog("Could not send first-random pick to host: " + ShortException(ex)); } } private void OnFirstRandomPickMessageReceived(ulong senderClientId, FastBufferReader reader) { //IL_0036: 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) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || (!singleton.IsServer && !singleton.IsHost)) { return; } try { int requestedSuitId = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe(ref requestedSuitId, default(ForPrimitives)); HostAcceptFirstRandomChoice(senderClientId, requestedSuitId); } catch (Exception ex) { Log.LogWarning((object)("Could not read first-random pick from clientId=" + senderClientId + ": " + ShortException(ex))); } } private void HostAcceptFirstRandomChoice(ulong senderClientId, int requestedSuitId) { StartOfRound startOfRound = GetStartOfRound(); if ((Object)(object)startOfRound == (Object)null || startOfRound.allPlayerScripts == null) { pendingFirstPickByClientId[senderClientId] = requestedSuitId; DebugLog("Host queued first-random pick from clientId=" + senderClientId + " because StartOfRound/player list is not ready yet."); return; } if (hostOfficialByClientId.ContainsKey(senderClientId)) { DebugLog("Host ignored first-random pick from clientId=" + senderClientId + " because that client already has an official suit."); SendAssignmentsToClient(senderClientId); return; } if (onlyAssignInShipLobby.Value && !startOfRound.inShipPhase) { DebugLog("Host ignored first-random pick from clientId=" + senderClientId + " because ship lobby assignment is disabled now."); return; } List allowedSuitIds = GetAllowedSuitIds(startOfRound); int num = requestedSuitId; if (!allowedSuitIds.Contains(num)) { if (allowedSuitIds.Count == 0) { DebugLog("Host rejected first-random pick from clientId=" + senderClientId + " because no allowed suits exist."); return; } DebugLog("Host rejected first-random pick from clientId=" + senderClientId + " suitID=" + num + ". Picking fallback from host list."); num = allowedSuitIds[Rng.Next(allowedSuitIds.Count)]; } int playerIndex; PlayerControllerB val = FindPlayerByClientId(startOfRound, senderClientId, out playerIndex); if ((Object)(object)val == (Object)null) { pendingFirstPickByClientId[senderClientId] = num; DebugLog("Host queued first-random pick from clientId=" + senderClientId + " because player object was not ready yet."); } else if (HostSetOfficialFirstRandom(startOfRound, val, playerIndex, senderClientId, num, "client-picked-from-host-list")) { BroadcastAssignmentsToClients(); } } private void OnAssignmentsMessageReceived(ulong senderClientId, FastBufferReader reader) { //IL_0042: 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_008b: 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_009f: 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_00b3: 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) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || singleton.IsServer) { return; } StartOfRound startOfRound = GetStartOfRound(); if ((Object)(object)startOfRound == (Object)null) { return; } try { int num = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe(ref num, default(ForPrimitives)); clientOfficialByClientId.Clear(); float reapplyUntil = Time.realtimeSinceStartup + Mathf.Max(0.5f, eventReapplySeconds.Value); bool flag = false; ulong num2 = default(ulong); int playerIndex = default(int); int suitId = default(int); for (int i = 0; i < num; i++) { ((FastBufferReader)(ref reader)).ReadValueSafe(ref num2, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref playerIndex, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref suitId, default(ForPrimitives)); SuitAssignment suitAssignment = new SuitAssignment { ClientId = num2, PlayerIndex = playerIndex, SuitId = suitId, ReapplyUntil = reapplyUntil }; clientOfficialByClientId[num2] = suitAssignment; if ((Object)(object)singleton != (Object)null && num2 == singleton.LocalClientId) { flag = true; clientFirstPickAccepted = true; } ApplyAssignmentLocally(startOfRound, suitAssignment, "client-received", logResult: true); } if (!flag && (Object)(object)singleton != (Object)null && singleton.IsClient && !singleton.IsServer) { nextClientRequestAt = Time.realtimeSinceStartup + Mathf.Max(0.5f, clientRequestRetrySeconds.Value); } } catch (Exception ex) { Log.LogWarning((object)("Could not read official suit table message: " + ShortException(ex))); } } private List GetAllowedSuitIds(StartOfRound start) { HashSet hashSet = ParseWhitelist(allowedSuitsCsv.Value); List list = new List(); if ((Object)(object)start == (Object)null || (Object)(object)start.unlockablesList == (Object)null || start.unlockablesList.unlockables == null) { return list; } List unlockables = start.unlockablesList.unlockables; for (int i = 0; i < unlockables.Count; i++) { UnlockableItem val = unlockables[i]; if (val != null && !string.IsNullOrWhiteSpace(val.unlockableName)) { string item = NormalizeName(val.unlockableName); if (hashSet.Contains(item)) { list.Add(i); } } } if (logDebug.Value) { string text = string.Join(" ", list.Select((int id) => "[" + GetUnlockableName(start, id) + " id=" + id + "]").ToArray()); DebugLog("Allowed first-random suit matches: " + text); } return list; } private static HashSet ParseWhitelist(string csv) { HashSet hashSet = new HashSet(); if (string.IsNullOrWhiteSpace(csv)) { return hashSet; } string[] array = csv.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = NormalizeName(array[i]); if (!string.IsNullOrEmpty(text)) { hashSet.Add(text); } } return hashSet; } private static string NormalizeName(string text) { if (string.IsNullOrWhiteSpace(text)) { return string.Empty; } char[] value = text.Trim().ToLowerInvariant().Where(char.IsLetterOrDigit) .ToArray(); return new string(value); } private static StartOfRound GetStartOfRound() { try { if ((Object)(object)StartOfRound.Instance != (Object)null) { return StartOfRound.Instance; } } catch { } return Object.FindObjectOfType(); } private static bool IsRealPlayer(PlayerControllerB player) { if ((Object)(object)player == (Object)null) { return false; } return player.isPlayerControlled; } private static ulong GetPlayerClientId(PlayerControllerB player, int playerIndex) { if ((Object)(object)player == (Object)null) { return (ulong)Mathf.Max(0, playerIndex); } try { if (player.actualClientId != 0L || playerIndex == 0) { return player.actualClientId; } } catch { } try { return player.playerClientId; } catch { return (ulong)Mathf.Max(0, playerIndex); } } private static int GetPlayerIndex(StartOfRound start, PlayerControllerB player) { if ((Object)(object)start == (Object)null || start.allPlayerScripts == null || (Object)(object)player == (Object)null) { return -1; } for (int i = 0; i < start.allPlayerScripts.Length; i++) { if ((Object)(object)start.allPlayerScripts[i] == (Object)(object)player) { return i; } } try { for (int j = 0; j < start.allPlayerScripts.Length; j++) { PlayerControllerB val = start.allPlayerScripts[j]; if ((Object)(object)val != (Object)null && GetPlayerClientId(val, j) == GetPlayerClientId(player, j)) { return j; } } } catch { } return -1; } private static PlayerControllerB FindPlayerByClientId(StartOfRound start, ulong clientIdToFind, out int playerIndex) { playerIndex = -1; if ((Object)(object)start == (Object)null || start.allPlayerScripts == null) { return null; } PlayerControllerB[] allPlayerScripts = start.allPlayerScripts; for (int i = 0; i < allPlayerScripts.Length; i++) { PlayerControllerB val = allPlayerScripts[i]; if (IsRealPlayer(val)) { ulong playerClientId = GetPlayerClientId(val, i); if (playerClientId == clientIdToFind) { playerIndex = i; return val; } } } return null; } private static PlayerControllerB FindPlayer(StartOfRound start, SuitAssignment assignment) { if ((Object)(object)start == (Object)null || start.allPlayerScripts == null || assignment == null) { return null; } PlayerControllerB[] allPlayerScripts = start.allPlayerScripts; if (assignment.PlayerIndex >= 0 && assignment.PlayerIndex < allPlayerScripts.Length && IsRealPlayer(allPlayerScripts[assignment.PlayerIndex])) { ulong playerClientId = GetPlayerClientId(allPlayerScripts[assignment.PlayerIndex], assignment.PlayerIndex); if (playerClientId == assignment.ClientId) { return allPlayerScripts[assignment.PlayerIndex]; } } for (int i = 0; i < allPlayerScripts.Length; i++) { PlayerControllerB val = allPlayerScripts[i]; if (IsRealPlayer(val)) { ulong playerClientId2 = GetPlayerClientId(val, i); if (playerClientId2 == assignment.ClientId) { return val; } } } return null; } private static bool IsValidSuitId(StartOfRound start, int suitId) { try { return (Object)(object)start != (Object)null && (Object)(object)start.unlockablesList != (Object)null && start.unlockablesList.unlockables != null && suitId >= 0 && suitId < start.unlockablesList.unlockables.Count; } catch { return false; } } private static string GetUnlockableName(StartOfRound start, int suitId) { try { if (IsValidSuitId(start, suitId)) { UnlockableItem val = start.unlockablesList.unlockables[suitId]; if (val != null && !string.IsNullOrWhiteSpace(val.unlockableName)) { return val.unlockableName; } } } catch { } return "SuitID " + suitId; } private static bool IsMenuScene(string sceneName) { if (string.IsNullOrWhiteSpace(sceneName)) { return false; } return sceneName.IndexOf("menu", StringComparison.OrdinalIgnoreCase) >= 0; } private static string ShortException(Exception ex) { if (ex == null) { return "unknown error"; } if (ex.InnerException != null) { return ex.GetType().Name + ": " + ex.Message + " -> " + ex.InnerException.GetType().Name + ": " + ex.InnerException.Message; } return ex.GetType().Name + ": " + ex.Message; } private void DebugLog(string message) { if (logDebug != null && logDebug.Value) { Log.LogInfo((object)message); } } }