using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mirror;
using Steamworks;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("CrewSizeMod")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.10.8.0")]
[assembly: AssemblyInformationalVersion("0.10.8")]
[assembly: AssemblyProduct("CrewSizeMod")]
[assembly: AssemblyTitle("CrewSizeMod")]
[assembly: AssemblyVersion("0.10.8.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.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 CrewSizeMod
{
[BepInPlugin("com.hunter.crewsizemod", "Crew Size Mod", "0.10.9")]
public class Plugin : BaseUnityPlugin
{
public const string Guid = "com.hunter.crewsizemod";
public const string Name = "Crew Size Mod";
public const string Version = "0.10.9";
internal static ManualLogSource Log;
internal static ConfigEntry<int> MaxPlayers;
internal static ConfigEntry<int> TestSlotLimitOverride;
internal static ConfigEntry<bool> EnableModPresenceCheck;
internal static ConfigEntry<bool> ExperimentalExpandPlayerUI;
internal static ConfigEntry<int> ExperimentalExpandPreviewCount;
internal static ConfigEntry<bool> PlaceOverflowInLobbyRow;
internal static ConfigEntry<bool> ExperimentalCloneLobbyParkingSpots;
internal static ConfigEntry<int> ExperimentalLobbyPreviewCount;
internal static ConfigEntry<string> LobbyManualSpots;
internal static ConfigEntry<bool> ShowTwitchCredit;
internal static ConfigEntry<ScreenCorner> TwitchCreditCorner;
internal static ConfigEntry<float> CreditWidth;
private void Awake()
{
//IL_002e: Unknown result type (might be due to invalid IL or missing references)
//IL_0038: Expected O, but got Unknown
//IL_0060: Unknown result type (might be due to invalid IL or missing references)
//IL_006a: 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_0168: Unknown result type (might be due to invalid IL or missing references)
//IL_0172: Expected O, but got Unknown
//IL_017c: Unknown result type (might be due to invalid IL or missing references)
//IL_0181: Unknown result type (might be due to invalid IL or missing references)
//IL_026d: Unknown result type (might be due to invalid IL or missing references)
//IL_0277: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
MaxPlayers = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxPlayers", 8, new ConfigDescription("Total players allowed in a session. EVERY player must run this mod with the SAME value; the HOST's value is the one that actually sizes the session. The game's authored ceiling is ~6-8; higher values are accepted but get rougher (netcode/physics strain, UI authored for 4, and the lobby parking spots only extrapolate cleanly to ~8).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 12), Array.Empty<object>()));
TestSlotLimitOverride = ((BaseUnityPlugin)this).Config.Bind<int>("Testing", "TestSlotLimitOverride", 0, new ConfigDescription("TEST ONLY. If > 0, pretend the lobby has only this many slots, so overflow skipping triggers earlier. Set to 1 to reproduce the overflow path with just 2 machines (player 2 becomes the 'overflow' player). Set to 0 for normal play (uses the real slot count).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16), Array.Empty<object>()));
EnableModPresenceCheck = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableModPresenceCheck", true, "Advertise this machine as modded via Steam lobby member data, and log which lobby members are running vanilla (their voice/UI may glitch at 5+ players, since those fixes run client-side). Purely informational - no gameplay effect. Also the seam for any future enhancements that require everyone to have the mod.");
ExperimentalExpandPlayerUI = ((BaseUnityPlugin)this).Config.Bind<bool>("Experimental", "ExpandPlayerUI", false, "EXPERIMENTAL (default OFF). When ON, the ready-up proceed icons are EXPANDED to show ALL players by cloning the game's 4 authored icon slots at runtime, instead of capping the display at 4. Purely local/cosmetic per machine (no networking). Leave OFF for the stable behaviour; turn ON only to test the 'show all 8' layout. If the cloned icons look misaligned, that's the authored layout not auto-arranging - report how it looks and it can be positioned manually.");
ExperimentalExpandPreviewCount = ((BaseUnityPlugin)this).Config.Bind<int>("Experimental", "ExpandPreviewCount", 0, new ConfigDescription("TEST AID for ExpandPlayerUI. If > 0 (and ExpandPlayerUI is ON), the proceed-icon row is padded to this many icons by duplicating a player, so you can see the N-icon layout SOLO without needing N real players. e.g. set to 8 to preview the 8-player row with just 1-2 machines. 0 = off (only real players show).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16), Array.Empty<object>()));
LobbyManualSpots = ((BaseUnityPlugin)this).Config.Bind<string>("General", "LobbyManualSpots", "", "Manually position EVERY lobby parking spot (the real ones AND the overflow ones), as semicolon-separated 'x,z' world coordinates, one per player index starting at 0. The host log prints a ready-to-paste line ('Lobby spot positions ...') with the current layout - copy it here, tweak the numbers, relaunch, repeat. An empty entry (e.g. two semicolons in a row) leaves that spot at its default. Leave the whole value blank to use the game's real spots + auto-placed overflow. Example: '-10.8,14.9; -10.8,18; -17,12.6; -17,15.9; -4,17; -4,20; 2,19; 2,22'");
PlaceOverflowInLobbyRow = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "PlaceOverflowInLobbyRow", true, "When ON (default), overflow players' lobby bodies are positioned at extrapolated parking spots in line with the 4 real ones, instead of spawning at the prefab's default position. Cosmetic only (these bodies are destroyed before the run). Turn OFF if the extrapolated placement looks wrong in your lobby.");
ExperimentalCloneLobbyParkingSpots = ((BaseUnityPlugin)this).Config.Bind<bool>("Experimental", "CloneLobbyParkingSpots", false, "EXPERIMENTAL / PATH B (default OFF). When ON, overflow players get a CLONED real LobbyPlayer parking spot (with the assigned animation) instead of a forklift stand-in. Requires EVERY player to have the mod (it registers a Mirror spawn handler so the cloned spot replicates to clients). This is the fragile path - if a clone fails it falls back to the forklift body automatically. Leave OFF unless you're testing it.");
ExperimentalLobbyPreviewCount = ((BaseUnityPlugin)this).Config.Bind<int>("Experimental", "LobbyPreviewCount", 0, new ConfigDescription("SOLO TEST AID. If > 0, spawn this many FAKE forklifts at the non-host parking spots (index 1, 2, 3, ...) the moment the lobby loads - so you can lay out the whole lobby ALONE. Set to 7 to see a full 8-player lobby (you at spot 0 + 7 fakes). Each fake sits at its spot's LobbyManualSpots override or the default. Uses forklifts, or cloned spots if CloneLobbyParkingSpots is ON. Fakes are unowned and destroyed at run start. 0 = off.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16), Array.Empty<object>()));
Harmony val = new Harmony("com.hunter.crewsizemod");
val.PatchAll();
Log.LogInfo((object)string.Format("{0} {1} loaded. Target max players = {2}", "Crew Size Mod", "0.10.9", MaxPlayers.Value));
if (TestSlotLimitOverride.Value > 0)
{
Log.LogWarning((object)($"TestSlotLimitOverride = {TestSlotLimitOverride.Value} - TEST MODE is ON " + "(forces the overflow path early). Set it to 0 in the config for normal play."));
}
if (EnableModPresenceCheck.Value)
{
ModPresence.Init();
}
ShowTwitchCredit = ((BaseUnityPlugin)this).Config.Bind<bool>("Credit", "ShowTwitchCredit", true, "Show a small 'MoreCrew by Izaya_here / Follow on Twitch' credit in the corner during the lobby and break room (hidden during the actual shift). Default ON.");
TwitchCreditCorner = ((BaseUnityPlugin)this).Config.Bind<ScreenCorner>("Credit", "Corner", ScreenCorner.TopLeft, "Which screen corner the credit sits in. Move it if it overlaps the game's UI.");
CreditWidth = ((BaseUnityPlugin)this).Config.Bind<float>("Credit", "ImageWidth", 320f, new ConfigDescription("On-screen width (pixels) of the credit image (credit.png, shipped next to the mod DLL). Height auto-scales to the image's aspect ratio. Lower it to make the logo more subtle.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(120f, 900f), Array.Empty<object>()));
Log.LogInfo((object)($"Twitch credit enabled (ShowTwitchCredit={ShowTwitchCredit.Value}, " + $"corner={TwitchCreditCorner.Value}); rendered via uGUI canvas."));
foreach (MethodBase patchedMethod in val.GetPatchedMethods())
{
Log.LogInfo((object)(" [patched] " + patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name));
}
Type type = AccessTools.TypeByName("LobbyManager");
Log.LogInfo((object)(" [resolve] LobbyManager type = " + ((type != null) ? "FOUND" : "NULL") + ", ServerAddPlayer = " + ((AccessTools.Method(type, "ServerAddPlayer", (Type[])null, (Type[])null) != null) ? "FOUND" : "NULL")));
}
}
[HarmonyPatch(typeof(NetworkServer), "Listen")]
internal static class Patch_NetworkServerListen
{
private static void Prefix(ref int maxConns)
{
int value = Plugin.MaxPlayers.Value;
Plugin.Log.LogInfo((object)$"NetworkServer.Listen maxConns {maxConns} -> {value}");
maxConns = value;
}
}
[HarmonyPatch(typeof(NetworkManager), "Awake")]
internal static class Patch_NetworkManagerMaxConnections
{
private static void Postfix(NetworkManager __instance)
{
__instance.maxConnections = Plugin.MaxPlayers.Value;
Plugin.Log.LogInfo((object)$"NetworkManager.maxConnections -> {__instance.maxConnections}");
}
}
[HarmonyPatch(typeof(SteamMatchmaking), "CreateLobby")]
internal static class Patch_CreateLobby
{
private static void Prefix(ref int cMaxMembers)
{
Plugin.Log.LogInfo((object)$"SteamMatchmaking.CreateLobby cMaxMembers {cMaxMembers} -> {Plugin.MaxPlayers.Value}");
cMaxMembers = Plugin.MaxPlayers.Value;
}
}
[HarmonyPatch(typeof(SteamMatchmaking), "SetLobbyMemberLimit")]
internal static class Patch_SetLobbyMemberLimit
{
private static void Prefix(ref int cMaxMembers)
{
Plugin.Log.LogInfo((object)$"SteamMatchmaking.SetLobbyMemberLimit cMaxMembers {cMaxMembers} -> {Plugin.MaxPlayers.Value}");
cMaxMembers = Plugin.MaxPlayers.Value;
}
}
internal static class Recon
{
public static void DumpType(string typeName)
{
Type type = AccessTools.TypeByName(typeName);
if (type == null)
{
Plugin.Log.LogWarning((object)("Type not found: " + typeName));
return;
}
Plugin.Log.LogInfo((object)("--- " + type.FullName + " ---"));
FieldInfo[] fields = type.GetFields(AccessTools.all);
foreach (FieldInfo fieldInfo in fields)
{
Plugin.Log.LogInfo((object)(" field " + fieldInfo.FieldType.Name + " " + fieldInfo.Name));
}
MethodInfo[] methods = type.GetMethods(AccessTools.all);
foreach (MethodInfo methodInfo in methods)
{
string[] value = Array.ConvertAll(methodInfo.GetParameters(), (ParameterInfo p) => p.ParameterType.Name);
Plugin.Log.LogInfo((object)(" method " + methodInfo.Name + "(" + string.Join(", ", value) + ")"));
}
}
}
[HarmonyPatch]
internal static class Patch_InviteButtonAlwaysOn
{
private static readonly Dictionary<Type, FieldInfo> _btnFields = new Dictionary<Type, FieldInfo>();
private static PropertyInfo _interactableProp;
private static bool _warned;
private static IEnumerable<MethodBase> TargetMethods()
{
string[] array = new string[2] { "LobbyButtonsUI", "GameMenuUI" };
for (int i = 0; i < array.Length; i++)
{
Type type = AccessTools.TypeByName(array[i]);
MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "OnUpdatePresentation", (Type[])null, (Type[])null) : null);
if (methodInfo != null)
{
yield return methodInfo;
}
}
}
private static void Postfix(object __instance)
{
try
{
Type type = __instance.GetType();
if (!_btnFields.TryGetValue(type, out var value))
{
value = AccessTools.Field(type, "inviteFriendsButton");
_btnFields[type] = value;
}
object obj = value?.GetValue(__instance);
if (obj != null)
{
if (_interactableProp == null)
{
_interactableProp = AccessTools.Property(obj.GetType(), "interactable");
}
_interactableProp?.SetValue(obj, true);
}
}
catch (Exception ex)
{
if (!_warned)
{
_warned = true;
Plugin.Log.LogWarning((object)("invite-button postfix: " + ex.Message + " (logged once)"));
}
}
}
}
internal static class OverflowTracker
{
public static readonly HashSet<int> Conns = new HashSet<int>();
public static readonly List<GameObject> Bodies = new List<GameObject>();
private static List<Vector3?> _manual;
private static bool _manualParsed;
private static bool _loggedSpots;
public static int EffectiveSlots(object lobbyManager)
{
int num = (AccessTools.Field(lobbyManager.GetType(), "_serverAvailablePlayers")?.GetValue(lobbyManager) as ICollection)?.Count ?? 0;
if (Plugin.TestSlotLimitOverride.Value <= 0)
{
return num;
}
return Math.Min(num, Plugin.TestSlotLimitOverride.Value);
}
private static List<Vector3?> Manual()
{
//IL_0096: Unknown result type (might be due to invalid IL or missing references)
if (_manualParsed)
{
return _manual;
}
_manualParsed = true;
_manual = new List<Vector3?>();
string text = Plugin.LobbyManualSpots?.Value ?? "";
if (string.IsNullOrWhiteSpace(text))
{
return _manual;
}
string[] array = text.Split(';');
for (int i = 0; i < array.Length; i++)
{
string[] array2 = array[i].Split(',');
if (array2.Length >= 2 && float.TryParse(array2[0].Trim(), out var result) && float.TryParse(array2[1].Trim(), out var result2))
{
_manual.Add((Vector3?)new Vector3(result, 0f, result2));
}
else
{
_manual.Add(null);
}
}
return _manual;
}
public static Vector3? ManualSpot(int index)
{
List<Vector3?> list = Manual();
if (list == null || index < 0 || index >= list.Count)
{
return null;
}
return list[index];
}
public static Vector3 AutoSpot(List<Vector3> positions, int index)
{
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_0041: 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_0090: 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_00c6: Unknown result type (might be due to invalid IL or missing references)
//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
int count = positions.Count;
if (index >= 0 && index < count)
{
return positions[index];
}
int num = index - count;
int num2 = num % count;
int num3 = 1 + num / count;
int num4 = -1;
for (int i = 0; i < count; i++)
{
if (i != num2 && Mathf.Abs(positions[i].x - positions[num2].x) < 3f)
{
num4 = i;
break;
}
}
float num5 = ((num4 < 0) ? ((num2 % 2 == 0) ? (-1f) : 1f) : ((positions[num2].z < positions[num4].z) ? (-1f) : 1f));
return positions[num2] + new Vector3(0f, 0f, num5 * 2f * (float)num3);
}
private static List<Vector3> ReadSpotPositions(object lobbyManager, out IList list)
{
//IL_005d: Unknown result type (might be due to invalid IL or missing references)
list = null;
list = AccessTools.Field(lobbyManager.GetType(), "_serverAvailablePlayers")?.GetValue(lobbyManager) as IList;
if (list == null || list.Count == 0)
{
return null;
}
List<Vector3> list2 = new List<Vector3>();
foreach (object item in list)
{
Component val = (Component)((item is Component) ? item : null);
if (val != null)
{
list2.Add(val.transform.position);
}
}
return list2;
}
private static void LogSpotsOnce(List<Vector3> positions)
{
//IL_004f: 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_0054: 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)
//IL_0066: Unknown result type (might be due to invalid IL or missing references)
if (_loggedSpots)
{
return;
}
_loggedSpots = true;
int num = Math.Max(Plugin.MaxPlayers.Value, positions.Count);
StringBuilder stringBuilder = new StringBuilder("Lobby spot positions (copy into [General] LobbyManualSpots to customize): ");
for (int i = 0; i < num; i++)
{
Vector3 val = (Vector3)(((??)ManualSpot(i)) ?? AutoSpot(positions, i));
stringBuilder.Append($"{val.x:0.0},{val.z:0.0}");
if (i < num - 1)
{
stringBuilder.Append("; ");
}
}
Plugin.Log.LogInfo((object)stringBuilder.ToString());
}
public static void ApplyManualToRealSpots(object lobbyManager)
{
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
try
{
if (ReadSpotPositions(lobbyManager, out var list) == null)
{
return;
}
for (int i = 0; i < list.Count; i++)
{
Vector3? val = ManualSpot(i);
if (val.HasValue)
{
object? obj = list[i];
Component val2 = (Component)((obj is Component) ? obj : null);
if (val2 != null)
{
val2.transform.position = val.Value;
}
}
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("apply manual spots: " + ex.Message));
}
}
public static int RealSpotCount(object lobbyManager)
{
return (AccessTools.Field(lobbyManager.GetType(), "_serverAvailablePlayers")?.GetValue(lobbyManager) as ICollection)?.Count ?? 0;
}
public static bool TryGetLobbyRowSpot(object lobbyManager, int playerIndex, out Vector3 pos, out Quaternion rot)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_0075: 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)
//IL_008c: Unknown result type (might be due to invalid IL or missing references)
//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
pos = Vector3.zero;
rot = Quaternion.identity;
try
{
if (!(AccessTools.Field(lobbyManager.GetType(), "_serverAvailablePlayers")?.GetValue(lobbyManager) is IList list) || list.Count == 0)
{
return false;
}
List<Vector3> list2 = new List<Vector3>();
foreach (object item in list)
{
Component val = (Component)((item is Component) ? item : null);
if (val != null)
{
list2.Add(val.transform.position);
rot = val.transform.rotation;
}
}
if (list2.Count == 0)
{
return false;
}
LogSpotsOnce(list2);
pos = (Vector3)(((??)ManualSpot(playerIndex)) ?? AutoSpot(list2, playerIndex));
return true;
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("lobby-row spot: " + ex.Message));
return false;
}
}
}
[HarmonyPatch]
internal static class Patch_LobbyAddOverflow
{
private static MethodBase TargetMethod()
{
Type type = AccessTools.TypeByName("LobbyManager");
if (!(type != null))
{
return null;
}
return AccessTools.Method(type, "ServerAddPlayer", (Type[])null, (Type[])null);
}
private static bool Prefix(object __instance, NetworkConnectionToClient conn, int playerIndex)
{
//IL_0015: 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_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_005c: Unknown result type (might be due to invalid IL or missing references)
//IL_005d: Unknown result type (might be due to invalid IL or missing references)
//IL_012c: Unknown result type (might be due to invalid IL or missing references)
//IL_012d: Unknown result type (might be due to invalid IL or missing references)
try
{
int num = OverflowTracker.EffectiveSlots(__instance);
if (num > 0 && playerIndex >= num)
{
Vector3 pos = Vector3.zero;
Quaternion rot = Quaternion.identity;
bool flag = Plugin.PlaceOverflowInLobbyRow != null && Plugin.PlaceOverflowInLobbyRow.Value && OverflowTracker.TryGetLobbyRowSpot(__instance, playerIndex, out pos, out rot);
if (Plugin.ExperimentalCloneLobbyParkingSpots != null && Plugin.ExperimentalCloneLobbyParkingSpots.Value)
{
if (LobbyCloneSpawner.TrySpawnClonedSpot(conn, playerIndex, flag, pos, rot, out var body))
{
OverflowTracker.Conns.Add(conn.connectionId);
OverflowTracker.Bodies.Add(body);
Plugin.Log.LogInfo((object)($"[Experimental] Overflow player (index {playerIndex}, connId " + $"{conn.connectionId}) got a CLONED LobbyPlayer spot. Ready gate ignores it."));
return false;
}
Plugin.Log.LogWarning((object)"[Experimental] LobbyPlayer clone failed - falling back to forklift body.");
}
NetworkManager singleton = NetworkManager.singleton;
GameObject val = (((Object)(object)singleton != (Object)null) ? singleton.playerPrefab : null);
if ((Object)(object)val == (Object)null)
{
Plugin.Log.LogWarning((object)"Overflow: NetworkManager.singleton.playerPrefab is null; falling back to skip (player will likely strand).");
OverflowTracker.Conns.Add(conn.connectionId);
return false;
}
GameObject val2 = (flag ? Object.Instantiate<GameObject>(val, pos, rot) : Object.Instantiate<GameObject>(val));
NetworkServer.AddPlayerForConnection(conn, val2);
OverflowTracker.Conns.Add(conn.connectionId);
OverflowTracker.Bodies.Add(val2);
Plugin.Log.LogInfo((object)($"Overflow lobby player (index {playerIndex} >= {num} slots, " + $"connId {conn.connectionId}) - gave it a forklift body at " + (flag ? ((object)(Vector3)(ref pos)).ToString() : "default position") + " via AddPlayerForConnection (no lobby slot). Ready gate will ignore it."));
return false;
}
}
catch (Exception arg)
{
Plugin.Log.LogWarning((object)$"overflow-add prefix: {arg}");
if (conn != null)
{
OverflowTracker.Conns.Add(conn.connectionId);
}
return false;
}
return true;
}
}
[HarmonyPatch]
internal static class Patch_LobbyDisconnectOverflow
{
private static MethodBase TargetMethod()
{
Type type = AccessTools.TypeByName("LobbyManager");
if (!(type != null))
{
return null;
}
return AccessTools.Method(type, "ServerDisconnected", (Type[])null, (Type[])null);
}
private static bool Prefix(object __instance, NetworkConnectionToClient conn, int playerIndex)
{
try
{
int num = OverflowTracker.EffectiveSlots(__instance);
if ((conn != null && OverflowTracker.Conns.Contains(conn.connectionId)) || (num > 0 && playerIndex >= num))
{
if (conn != null)
{
OverflowTracker.Conns.Remove(conn.connectionId);
}
OverflowTracker.Bodies.RemoveAll((GameObject b) => (Object)(object)b == (Object)null);
Plugin.Log.LogInfo((object)$"Overflow disconnect (index {playerIndex}) - skipping slot teardown.");
return false;
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("overflow-disconnect prefix: " + ex.Message));
}
return true;
}
}
[HarmonyPatch]
internal static class Patch_CleanupOverflowBodies
{
private static MethodBase TargetMethod()
{
Type type = AccessTools.TypeByName("AggroNetworkManager");
if (!(type != null))
{
return null;
}
return AccessTools.Method(type, "SpawnPlayers", (Type[])null, (Type[])null);
}
private static void Prefix()
{
try
{
int num = 0;
foreach (GameObject body in OverflowTracker.Bodies)
{
if ((Object)(object)body == (Object)null)
{
continue;
}
try
{
NetworkIdentity component = body.GetComponent<NetworkIdentity>();
NetworkConnectionToClient val = (((Object)(object)component != (Object)null) ? component.connectionToClient : null);
if (val != null)
{
NetworkServer.RemovePlayerForConnection(val, (RemovePlayerOptions)2);
}
else
{
NetworkServer.Destroy(body);
}
num++;
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("overflow body cleanup: " + ex.Message));
try
{
Object.Destroy((Object)(object)body);
}
catch
{
}
}
}
if (num > 0)
{
Plugin.Log.LogInfo((object)($"Run start: destroyed {num} leftover overflow lobby body(ies); " + "SpawnPlayers will give those connections clean run forklifts."));
}
}
finally
{
OverflowTracker.Bodies.Clear();
OverflowTracker.Conns.Clear();
}
}
}
[HarmonyPatch]
internal static class Patch_VoiceOverflowGuard
{
private static IEnumerable<MethodBase> TargetMethods()
{
Type t = AccessTools.TypeByName("VoiceManager");
string[] array = new string[4] { "AddPlayer", "RemovePlayer", "OnPlayerJoinedSession", "OnPlayerLeftSession" };
foreach (string text in array)
{
MethodInfo methodInfo = ((t != null) ? AccessTools.Method(t, text, (Type[])null, (Type[])null) : null);
if (methodInfo != null)
{
yield return methodInfo;
}
}
}
private static Exception Finalizer(Exception __exception, MethodBase __originalMethod)
{
if (__exception is ArgumentOutOfRangeException || __exception is IndexOutOfRangeException)
{
Plugin.Log.LogInfo((object)("Voice overflow in " + __originalMethod.Name + " swallowed (player 5+ has no in-game voice; this is expected with Crew Size Mod)."));
return null;
}
return __exception;
}
}
[HarmonyPatch]
internal static class Patch_ReadyIgnoreSlotless
{
private static bool _lastReady;
private static bool _resolved;
private static bool _warnedCatch;
private static PropertyInfo _suppressProp;
private static FieldInfo _proceedingField;
private static MethodInfo _containsM;
private static MethodBase TargetMethod()
{
Type type = AccessTools.TypeByName("PlayersManager");
if (!(type != null))
{
return null;
}
return AccessTools.Method(type, "ServerIsReadyToProceed", (Type[])null, (Type[])null);
}
private static bool Prefix(object __instance, ref bool __result)
{
try
{
if (!NetworkServer.active)
{
__result = false;
_lastReady = false;
return false;
}
if (!_resolved)
{
_resolved = true;
Type type = __instance.GetType();
_suppressProp = AccessTools.Property(type, "serverSuppressProceed");
_proceedingField = AccessTools.Field(type, "_syncPlayersProceeding");
}
if (_suppressProp != null && (bool)_suppressProp.GetValue(__instance))
{
__result = false;
_lastReady = false;
return false;
}
object obj = _proceedingField?.GetValue(__instance);
if (obj == null)
{
return true;
}
ICollection<NetworkIdentity> collection = obj as ICollection<NetworkIdentity>;
if (collection == null && _containsM == null)
{
_containsM = AccessTools.Method(obj.GetType(), "Contains", (Type[])null, (Type[])null);
}
if (collection == null && _containsM == null)
{
return true;
}
object[] array = ((collection == null) ? new object[1] : null);
foreach (KeyValuePair<int, NetworkConnectionToClient> connection in NetworkServer.connections)
{
if (OverflowTracker.Conns.Contains(connection.Key))
{
continue;
}
NetworkIdentity val = ((connection.Value != null) ? ((NetworkConnection)connection.Value).identity : null);
if (!((Object)(object)val == (Object)null))
{
bool flag;
if (collection != null)
{
flag = collection.Contains(val);
}
else
{
array[0] = val;
flag = (bool)_containsM.Invoke(obj, array);
}
if (!flag)
{
__result = false;
_lastReady = false;
return false;
}
}
}
__result = true;
if (!_lastReady)
{
_lastReady = true;
Plugin.Log.LogInfo((object)"ReadyToProceed: all slotted players ready (slotless overflow ignored).");
}
return false;
}
catch (Exception ex)
{
if (!_warnedCatch)
{
_warnedCatch = true;
Plugin.Log.LogWarning((object)("ready-ignore-slotless prefix: " + ex.Message + "; falling back to original (logged once)."));
}
return true;
}
}
}
[HarmonyPatch]
internal static class Patch_SwallowOverflowBodyNre
{
private static readonly HashSet<string> _noticed = new HashSet<string>();
private static IEnumerable<MethodBase> TargetMethods()
{
(string, string[])[] array = new(string, string[])[2]
{
("PlayerEffects", new string[3] { "OnUpdateSimulation", "OnUpdateSimulationEarly", "OnUpdateSimulationLate" }),
("FloaterPopulator", new string[1] { "OnEntityCreated" })
};
(string type, string[] methods)[] array2 = array;
for (int i = 0; i < array2.Length; i++)
{
(string type, string[] methods) tuple = array2[i];
string item = tuple.type;
string[] item2 = tuple.methods;
Type t = AccessTools.TypeByName(item);
if (t == null)
{
continue;
}
string[] array3 = item2;
foreach (string text in array3)
{
MethodInfo methodInfo = AccessTools.Method(t, text, (Type[])null, (Type[])null);
if (methodInfo != null)
{
yield return methodInfo;
}
}
}
}
private static Exception Finalizer(Exception __exception, MethodBase __originalMethod)
{
if (__exception is NullReferenceException)
{
string text = __originalMethod.DeclaringType?.Name + "." + __originalMethod.Name;
if (_noticed.Add(text))
{
Plugin.Log.LogInfo((object)("Swallowing " + text + " NullReferenceException on the overflow player's forklift (cosmetic; not a crash). This notice prints once per method."));
}
return null;
}
return __exception;
}
}
[HarmonyPatch]
internal static class Patch_QuotaReportPlayerCap
{
private static MethodBase TargetMethod()
{
Type type = AccessTools.TypeByName("QuotaReportUI");
if (!(type != null))
{
return null;
}
return AccessTools.Method(type, "UpdateUIData", (Type[])null, (Type[])null);
}
private static void Prefix(object __instance, IList playerResults, out object __state)
{
__state = null;
try
{
if (playerResults == null)
{
return;
}
int num = (AccessTools.Field(__instance.GetType(), "allPlayerCrashoutCounts")?.GetValue(__instance) as Array)?.Length ?? 4;
if (num < 1)
{
num = 4;
}
if (playerResults.Count > num)
{
List<object> list = new List<object>();
for (int num2 = playerResults.Count - 1; num2 >= num; num2--)
{
list.Add(playerResults[num2]);
playerResults.RemoveAt(num2);
}
__state = list;
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("quota-report cap prefix: " + ex.Message));
}
}
private static void Postfix(IList playerResults, object __state)
{
try
{
if (__state is List<object> list && playerResults != null)
{
for (int num = list.Count - 1; num >= 0; num--)
{
playerResults.Add(list[num]);
}
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("quota-report cap postfix: " + ex.Message));
}
}
}
[HarmonyPatch]
internal static class Patch_ProceedUiPlayerCap
{
private static FieldInfo _fProceeds;
private static FieldInfo _fIcons;
private static FieldInfo _fParents;
private static bool _expandNoticed;
private static bool _warnedCap;
private static MethodBase TargetMethod()
{
Type type = AccessTools.TypeByName("PlayerProceedUI");
if (!(type != null))
{
return null;
}
return AccessTools.Method(type, "OnUpdatePresentation", (Type[])null, (Type[])null);
}
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Expected O, but got Unknown
//IL_006d: Unknown result type (might be due to invalid IL or missing references)
//IL_0077: Expected O, but got Unknown
List<CodeInstruction> list = new List<CodeInstruction>(instructions);
MethodInfo method = typeof(Patch_ProceedUiPlayerCap).GetMethod("Cap", BindingFlags.Static | BindingFlags.NonPublic);
for (int i = 0; i < list.Count; i++)
{
MethodInfo methodInfo = list[i].operand as MethodInfo;
if (methodInfo != null && methodInfo.Name == "GetAllPlayerProceeds")
{
list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)method));
list.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_0, (object)null));
Plugin.Log.LogInfo((object)"PlayerProceedUI: injected proceed-icon cap after GetAllPlayerProceeds.");
return list;
}
}
Plugin.Log.LogWarning((object)"PlayerProceedUI: GetAllPlayerProceeds call not found; proceed-icon cap NOT applied (5+ players may flood this UI). Method left unchanged.");
return list;
}
private static void Cap(object self)
{
try
{
Type type = self.GetType();
if (_fProceeds == null)
{
_fProceeds = AccessTools.Field(type, "_playerProceeds");
}
if (_fIcons == null)
{
_fIcons = AccessTools.Field(type, "playerIcons");
}
if (_fParents == null)
{
_fParents = AccessTools.Field(type, "playerParents");
}
IList list = _fProceeds?.GetValue(self) as IList;
IList list2 = _fIcons?.GetValue(self) as IList;
if (list == null || list2 == null)
{
return;
}
if (Plugin.ExperimentalExpandPlayerUI != null && Plugin.ExperimentalExpandPlayerUI.Value)
{
int num = Plugin.ExperimentalExpandPreviewCount?.Value ?? 0;
if (num > list.Count && list.Count > 0)
{
object value = list[list.Count - 1];
while (list.Count < num)
{
list.Add(value);
}
}
EnsureExpanded(_fParents?.GetValue(self) as IList, list2, list.Count);
}
else
{
while (list.Count > list2.Count)
{
list.RemoveAt(list.Count - 1);
}
}
}
catch (Exception ex)
{
if (!_warnedCap)
{
_warnedCap = true;
Plugin.Log.LogWarning((object)("proceed-icon adjust: " + ex.Message + " (logged once)"));
}
}
}
private static void EnsureExpanded(IList parents, IList icons, int needed)
{
if (parents == null || icons == null)
{
return;
}
int count = icons.Count;
if (count == 0 || parents.Count == 0)
{
return;
}
int num = Math.Min(needed, 16);
int index = count - 1;
object? obj = parents[index];
GameObject val = (GameObject)((obj is GameObject) ? obj : null);
object? obj2 = icons[index];
Component val2 = (Component)((obj2 is Component) ? obj2 : null);
if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null)
{
return;
}
int num2 = 0;
while (icons.Count < num)
{
GameObject val3 = Object.Instantiate<GameObject>(val, val.transform.parent);
((Object)val3).name = ((Object)val).name + "_crewmod" + icons.Count;
Transform val4 = MapByPath(val.transform, val2.transform, val3.transform);
Component val5 = (((Object)(object)val4 != (Object)null) ? ((Component)val4).GetComponent(((object)val2).GetType()) : val3.GetComponentInChildren(((object)val2).GetType(), true));
if ((Object)(object)val5 == (Object)null)
{
Object.Destroy((Object)(object)val3);
break;
}
parents.Add(val3);
icons.Add(val5);
num2++;
}
if (num2 > 0 && !_expandNoticed)
{
_expandNoticed = true;
Plugin.Log.LogInfo((object)($"[Experimental] Expanded proceed icons from {count} to {icons.Count} " + "(cloned authored slots to show all players). Check the layout in-game."));
}
}
private static Transform MapByPath(Transform root, Transform target, Transform newRoot)
{
List<int> list = new List<int>();
Transform val = target;
while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root)
{
list.Add(val.GetSiblingIndex());
val = val.parent;
}
if ((Object)(object)val != (Object)(object)root)
{
return null;
}
list.Reverse();
Transform val2 = newRoot;
foreach (int item in list)
{
if (item < 0 || item >= val2.childCount)
{
return null;
}
val2 = val2.GetChild(item);
}
return val2;
}
}
internal static class ModPresence
{
public const string Key = "CrewSizeMod";
private static Callback<LobbyEnter_t> _enterCb;
private static Callback<LobbyDataUpdate_t> _dataCb;
private static CSteamID _lobby;
private static string _lastSummary = "";
public static bool AllPeersModded { get; private set; }
public static int MemberCount { get; private set; }
public static int ModdedCount { get; private set; }
public static void Init()
{
try
{
_enterCb = Callback<LobbyEnter_t>.Create((DispatchDelegate<LobbyEnter_t>)OnLobbyEnter);
_dataCb = Callback<LobbyDataUpdate_t>.Create((DispatchDelegate<LobbyDataUpdate_t>)OnLobbyDataUpdate);
Plugin.Log.LogInfo((object)"Mod-presence handshake registered (advertises via Steam lobby member data).");
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("presence init failed (non-critical, informational only): " + ex.Message));
}
}
private static void OnLobbyEnter(LobbyEnter_t e)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
try
{
_lobby = new CSteamID(e.m_ulSteamIDLobby);
SteamMatchmaking.SetLobbyMemberData(_lobby, "CrewSizeMod", "0.10.9");
_lastSummary = "";
Scan();
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("presence enter: " + ex.Message));
}
}
private static void OnLobbyDataUpdate(LobbyDataUpdate_t e)
{
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
if (((CSteamID)(ref _lobby)).IsValid() && e.m_ulSteamIDLobby == _lobby.m_SteamID)
{
Scan();
}
}
private static void Scan()
{
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_002d: 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_0034: Unknown result type (might be due to invalid IL or missing references)
try
{
if (!((CSteamID)(ref _lobby)).IsValid())
{
return;
}
int numLobbyMembers = SteamMatchmaking.GetNumLobbyMembers(_lobby);
int num = 0;
for (int i = 0; i < numLobbyMembers; i++)
{
CSteamID lobbyMemberByIndex = SteamMatchmaking.GetLobbyMemberByIndex(_lobby, i);
if (!string.IsNullOrEmpty(SteamMatchmaking.GetLobbyMemberData(_lobby, lobbyMemberByIndex, "CrewSizeMod")))
{
num++;
}
}
MemberCount = numLobbyMembers;
ModdedCount = num;
AllPeersModded = numLobbyMembers > 0 && num >= numLobbyMembers;
string text = $"{num}/{numLobbyMembers}";
if (text == _lastSummary)
{
return;
}
_lastSummary = text;
if (numLobbyMembers > 0)
{
if (num >= numLobbyMembers)
{
Plugin.Log.LogInfo((object)$"Crew Size Mod presence: all {numLobbyMembers} lobby member(s) have the mod - full experience.");
}
else
{
Plugin.Log.LogWarning((object)($"Crew Size Mod presence: {num}/{numLobbyMembers} lobby member(s) have the mod. " + $"The {numLobbyMembers - num} without it can still play, but may see voice/UI glitches at 5+ players " + "(those fixes run client-side). Ask them to install Crew Size Mod for the clean experience."));
}
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("presence scan: " + ex.Message));
}
}
}
[HarmonyPatch]
internal static class Patch_ProceedUiDebugLog
{
private static IEnumerable<MethodBase> TargetMethods()
{
Type t = AccessTools.TypeByName("PlayerProceedUI");
string[] array = new string[2] { "Show", "Hide" };
foreach (string text in array)
{
MethodInfo methodInfo = ((t != null) ? AccessTools.Method(t, text, (Type[])null, (Type[])null) : null);
if (methodInfo != null)
{
yield return methodInfo;
}
}
}
private static void Postfix(object __instance, MethodBase __originalMethod)
{
//IL_006a: Unknown result type (might be due to invalid IL or missing references)
//IL_006f: Unknown result type (might be due to invalid IL or missing references)
if (Plugin.ExperimentalExpandPlayerUI == null || !Plugin.ExperimentalExpandPlayerUI.Value)
{
return;
}
try
{
if (__originalMethod.Name == "Hide")
{
Plugin.Log.LogInfo((object)"PlayerProceedUI HIDDEN.");
return;
}
int num = 0;
if (AccessTools.Field(__instance.GetType(), "playerIcons")?.GetValue(__instance) is ICollection collection)
{
num = collection.Count;
}
Scene activeScene = SceneManager.GetActiveScene();
string name = ((Scene)(ref activeScene)).name;
Plugin.Log.LogInfo((object)($"PlayerProceedUI SHOWN - scene '{name}', {num} authored icon slot(s). " + "(These are the ready-up proceed icons; expand line follows if it grows.)"));
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("proceed-ui debug log: " + ex.Message));
}
}
}
internal static class LobbyCloneSpawner
{
public const uint AssetId = 3320164359u;
private static bool _registered;
private static GameObject _cachedTemplate;
public static void RegisterFresh()
{
_registered = false;
_cachedTemplate = null;
Register();
}
public static void Register()
{
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Expected O, but got Unknown
//IL_003f: Expected O, but got Unknown
if (_registered || Plugin.ExperimentalCloneLobbyParkingSpots == null || !Plugin.ExperimentalCloneLobbyParkingSpots.Value)
{
return;
}
try
{
NetworkClient.RegisterSpawnHandler(3320164359u, new SpawnHandlerDelegate(SpawnHandler), new UnSpawnDelegate(UnspawnHandler));
_registered = true;
Plugin.Log.LogInfo((object)$"[Experimental] Registered lobby-spot spawn handler (assetId 0x{3320164359u:X}).");
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("[Experimental] register spawn handler: " + ex.Message));
}
}
private static GameObject SpawnHandler(SpawnMessage msg)
{
//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_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Unknown result type (might be due to invalid IL or missing references)
try
{
GameObject val = FindTemplate();
if ((Object)(object)val == (Object)null)
{
Plugin.Log.LogWarning((object)"[Experimental] spawn handler: no LobbyPlayer template on this client.");
return null;
}
return Object.Instantiate<GameObject>(val, msg.position, msg.rotation);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("[Experimental] spawn handler: " + ex.Message));
return null;
}
}
private static void UnspawnHandler(GameObject obj)
{
if ((Object)(object)obj != (Object)null)
{
Object.Destroy((Object)(object)obj);
}
}
private static GameObject FindTemplate()
{
if ((Object)(object)_cachedTemplate != (Object)null)
{
return _cachedTemplate;
}
Type type = AccessTools.TypeByName("LobbyPlayer");
if (type == null)
{
return null;
}
FieldInfo fieldInfo = AccessTools.Field(type, "lobbyPlayerIndex");
Object[] array = Object.FindObjectsOfType(type);
GameObject val = null;
int num = int.MaxValue;
Object[] array2 = array;
foreach (Object obj in array2)
{
Component val2 = (Component)(object)((obj is Component) ? obj : null);
if (val2 == null)
{
continue;
}
int num2 = 0;
try
{
if (fieldInfo != null)
{
num2 = (int)fieldInfo.GetValue(val2);
}
}
catch
{
}
if (num2 < num)
{
num = num2;
val = val2.gameObject;
}
}
_cachedTemplate = val;
return val;
}
private static GameObject MakeStampedClone(bool placed, Vector3 pos, Quaternion rot, int index, out object lp, out Type lpType)
{
//IL_0034: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
lp = null;
lpType = null;
GameObject val = FindTemplate();
if ((Object)(object)val == (Object)null)
{
Plugin.Log.LogWarning((object)"[Experimental] clone: no LobbyPlayer template.");
return null;
}
GameObject val2 = (placed ? Object.Instantiate<GameObject>(val, pos, rot) : Object.Instantiate<GameObject>(val));
NetworkIdentity component = val2.GetComponent<NetworkIdentity>();
if ((Object)(object)component == (Object)null)
{
Object.Destroy((Object)(object)val2);
Plugin.Log.LogWarning((object)"[Experimental] clone has no NetworkIdentity.");
return null;
}
Type typeFromHandle = typeof(NetworkIdentity);
AccessTools.Field(typeFromHandle, "sceneId")?.SetValue(component, 0uL);
AccessTools.Field(typeFromHandle, "_assetId")?.SetValue(component, 3320164359u);
lpType = AccessTools.TypeByName("LobbyPlayer");
lp = ((lpType != null) ? val2.GetComponent(lpType) : null);
if (lpType != null)
{
AccessTools.Field(lpType, "lobbyPlayerIndex")?.SetValue(lp, index);
}
return val2;
}
private static void MarkAssigned(object lp, Type lpType)
{
if (lp != null && lpType != null)
{
AccessTools.Method(lpType, "ServerPlayerAssigned", (Type[])null, (Type[])null)?.Invoke(lp, null);
}
}
public static bool TrySpawnClonedSpot(NetworkConnectionToClient conn, int index, bool placed, Vector3 pos, Quaternion rot, out GameObject body)
{
//IL_000a: 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)
body = null;
try
{
Register();
object lp;
Type lpType;
GameObject val = MakeStampedClone(placed, pos, rot, index, out lp, out lpType);
if ((Object)(object)val == (Object)null)
{
return false;
}
NetworkServer.AddPlayerForConnection(conn, val);
MarkAssigned(lp, lpType);
body = val;
return true;
}
catch (Exception arg)
{
Plugin.Log.LogWarning((object)$"[Experimental] clone spawn failed: {arg}");
return false;
}
}
public static GameObject TrySpawnPreviewClone(bool placed, Vector3 pos, Quaternion rot, int index)
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
try
{
Register();
object lp;
Type lpType;
GameObject val = MakeStampedClone(placed, pos, rot, index, out lp, out lpType);
if ((Object)(object)val == (Object)null)
{
return null;
}
if (NetworkServer.active)
{
NetworkServer.Spawn(val, (NetworkConnectionToClient)null);
}
MarkAssigned(lp, lpType);
return val;
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("[Experimental] preview clone: " + ex.Message));
return null;
}
}
}
[HarmonyPatch]
internal static class Patch_LobbyPreviewSpawner
{
private static MethodBase TargetMethod()
{
Type type = AccessTools.TypeByName("LobbyManager");
if (!(type != null))
{
return null;
}
return AccessTools.Method(type, "OnEntityStart", (Type[])null, (Type[])null);
}
private static void Postfix(object __instance)
{
//IL_005c: 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_009e: Unknown result type (might be due to invalid IL or missing references)
//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
if (!NetworkServer.active)
{
return;
}
try
{
OverflowTracker.ApplyManualToRealSpots(__instance);
int num = Plugin.ExperimentalLobbyPreviewCount?.Value ?? 0;
if (num <= 0)
{
return;
}
int num2 = 0;
for (int i = 0; i < num; i++)
{
int num3 = 1 + i;
Vector3 pos;
Quaternion rot;
bool flag = OverflowTracker.TryGetLobbyRowSpot(__instance, num3, out pos, out rot);
GameObject val = null;
if (Plugin.ExperimentalCloneLobbyParkingSpots != null && Plugin.ExperimentalCloneLobbyParkingSpots.Value)
{
val = LobbyCloneSpawner.TrySpawnPreviewClone(flag, pos, rot, num3);
}
if ((Object)(object)val == (Object)null)
{
GameObject val2 = NetworkManager.singleton?.playerPrefab;
if ((Object)(object)val2 == (Object)null)
{
continue;
}
val = (flag ? Object.Instantiate<GameObject>(val2, pos, rot) : Object.Instantiate<GameObject>(val2));
NetworkIdentity component = val.GetComponent<NetworkIdentity>();
if ((Object)(object)component != (Object)null && component.netId == 0)
{
NetworkServer.Spawn(val, (NetworkConnectionToClient)null);
}
}
if ((Object)(object)val != (Object)null)
{
OverflowTracker.Bodies.Add(val);
num2++;
}
}
Plugin.Log.LogInfo((object)$"[Experimental] Spawned {num2} fake lobby forklift(s) at spots 1..{num} for solo layout testing.");
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("lobby preview spawn: " + ex.Message));
}
}
}
[HarmonyPatch]
internal static class Patch_RegisterLobbyCloneHandler
{
private static MethodBase TargetMethod()
{
Type type = AccessTools.TypeByName("AggroNetworkManager");
if (!(type != null))
{
return null;
}
return AccessTools.Method(type, "OnStartClient", (Type[])null, (Type[])null);
}
private static void Postfix()
{
LobbyCloneSpawner.RegisterFresh();
}
}
internal static class UiExpand
{
public static void EnsureExpanded(object instance, FieldInfo field, int target)
{
if (instance == null || field == null)
{
return;
}
int num = Math.Min(target, 16);
object value = field.GetValue(instance);
if (value is Array array)
{
int length = array.Length;
if (length == 0 || length >= num)
{
return;
}
Type elementType = array.GetType().GetElementType();
object? value2 = array.GetValue(length - 1);
Component val = (Component)((value2 is Component) ? value2 : null);
if ((Object)(object)val == (Object)null)
{
return;
}
Array array2 = Array.CreateInstance(elementType, num);
Array.Copy(array, array2, length);
for (int i = length; i < num; i++)
{
GameObject val2 = Object.Instantiate<GameObject>(val.gameObject, val.gameObject.transform.parent);
Component component = val2.GetComponent(elementType);
if ((Object)(object)component == (Object)null)
{
Object.Destroy((Object)(object)val2);
break;
}
array2.SetValue(component, i);
}
field.SetValue(instance, array2);
}
else
{
if (!(value is IList list) || list.Count == 0 || list.Count >= num)
{
return;
}
object? obj = list[list.Count - 1];
Component val3 = (Component)((obj is Component) ? obj : null);
if ((Object)(object)val3 == (Object)null)
{
return;
}
Type type = ((object)val3).GetType();
while (list.Count < num)
{
GameObject val4 = Object.Instantiate<GameObject>(val3.gameObject, val3.gameObject.transform.parent);
Component component2 = val4.GetComponent(type);
if ((Object)(object)component2 == (Object)null)
{
Object.Destroy((Object)(object)val4);
break;
}
list.Add(component2);
}
}
}
}
[HarmonyPatch]
internal static class Patch_ShiftProceedAreaExpand
{
private static FieldInfo _f;
private static bool _warned;
private static MethodBase TargetMethod()
{
Type type = AccessTools.TypeByName("ShiftProceedArea");
if (!(type != null))
{
return null;
}
return AccessTools.Method(type, "OnUpdatePresentation", (Type[])null, (Type[])null);
}
private static void Prefix(object __instance)
{
try
{
if (_f == null)
{
_f = AccessTools.Field(__instance.GetType(), "playerImages");
}
UiExpand.EnsureExpanded(__instance, _f, Plugin.MaxPlayers.Value);
}
catch (Exception ex)
{
if (!_warned)
{
_warned = true;
Plugin.Log.LogWarning((object)("shift-proceed-area expand: " + ex.Message + " (logged once)"));
}
}
}
}
[HarmonyPatch]
internal static class Patch_ModifierVoteExpand
{
private static bool _resolved;
private static bool _warned;
private static FieldInfo _fUndecided;
private static FieldInfo _fBtnA;
private static FieldInfo _fBtnB;
private static FieldInfo _fPlayers;
private static FieldInfo[] _btnFields;
private static MethodBase TargetMethod()
{
Type type = AccessTools.TypeByName("ModifierChoiceManagerUI");
if (!(type != null))
{
return null;
}
return AccessTools.Method(type, "RefreshVotes", (Type[])null, (Type[])null);
}
private static void Prefix(object __instance)
{
try
{
int value = Plugin.MaxPlayers.Value;
if (!_resolved)
{
_resolved = true;
Type type = __instance.GetType();
_fUndecided = AccessTools.Field(type, "undecidedPlayers");
_fBtnA = AccessTools.Field(type, "choiceButtonA");
_fBtnB = AccessTools.Field(type, "choiceButtonB");
_btnFields = new FieldInfo[2] { _fBtnA, _fBtnB };
}
UiExpand.EnsureExpanded(__instance, _fUndecided, value);
FieldInfo[] btnFields = _btnFields;
for (int i = 0; i < btnFields.Length; i++)
{
object obj = btnFields[i]?.GetValue(__instance);
if (obj != null)
{
if (_fPlayers == null)
{
_fPlayers = AccessTools.Field(obj.GetType(), "players");
}
UiExpand.EnsureExpanded(obj, _fPlayers, value);
}
}
}
catch (Exception ex)
{
if (!_warned)
{
_warned = true;
Plugin.Log.LogWarning((object)("modifier-vote expand: " + ex.Message + " (logged once)"));
}
}
}
}
[HarmonyPatch]
internal static class Patch_SwallowUiOverflow
{
private static readonly HashSet<string> _noticed = new HashSet<string>();
private static IEnumerable<MethodBase> TargetMethods()
{
(string, string[])[] array = new(string, string[])[2]
{
("ShiftProceedArea", new string[1] { "OnUpdatePresentation" }),
("ModifierChoiceManagerUI", new string[2] { "RefreshVotes", "OnUpdatePresentation" })
};
(string type, string[] methods)[] array2 = array;
for (int i = 0; i < array2.Length; i++)
{
(string type, string[] methods) tuple = array2[i];
string item = tuple.type;
string[] item2 = tuple.methods;
Type t = AccessTools.TypeByName(item);
if (t == null)
{
continue;
}
string[] array3 = item2;
foreach (string text in array3)
{
MethodInfo methodInfo = AccessTools.Method(t, text, (Type[])null, (Type[])null);
if (methodInfo != null)
{
yield return methodInfo;
}
}
}
}
private static Exception Finalizer(Exception __exception, MethodBase __originalMethod)
{
if (__exception is IndexOutOfRangeException || __exception is ArgumentOutOfRangeException)
{
string text = __originalMethod.DeclaringType?.Name + "." + __originalMethod.Name;
if (_noticed.Add(text))
{
Plugin.Log.LogInfo((object)("Swallowing " + text + " index overflow at 5+ players (cosmetic; prevents flood/lag). Prints once."));
}
return null;
}
return __exception;
}
}
internal enum ScreenCorner
{
TopLeft,
TopRight,
BottomLeft,
BottomRight
}
internal static class CreditUI
{
private const string Text = "MoreCrew by Izaya_here\nFollow on Twitch: twitch.tv/izaya_here";
private static GameObject _go;
private static RectTransform _rt;
private static Text _text;
private static RawImage _raw;
private static Texture2D _tex;
private static bool _created;
private static bool _failed;
internal static object ShiftMgr;
private static FieldInfo _phaseField;
private const int PHASE_SHIFT = 3;
private static bool _lastInShift;
private static int _lastFrame = -1;
private static ScreenCorner _appliedCorner = (ScreenCorner)(-1);
private static float _appliedWidth = float.NaN;
internal static void SetShiftManager(object mgr)
{
ShiftMgr = mgr;
if (mgr != null && _phaseField == null)
{
_phaseField = AccessTools.Field(mgr.GetType(), "_shiftPhase");
}
}
internal static void ClearShiftManager(object mgr)
{
if (mgr == null || ShiftMgr == mgr)
{
ShiftMgr = null;
}
}
private static bool InActiveShift()
{
try
{
object shiftMgr = ShiftMgr;
if ((Object)((shiftMgr is Object) ? shiftMgr : null) == (Object)null)
{
return false;
}
if (_phaseField == null)
{
return false;
}
return Convert.ToInt32(_phaseField.GetValue(ShiftMgr)) == 3;
}
catch
{
return false;
}
}
public static bool ShouldShow()
{
if (Plugin.ShowTwitchCredit == null || !Plugin.ShowTwitchCredit.Value)
{
return false;
}
bool flag = InActiveShift();
if (flag != _lastInShift)
{
_lastInShift = flag;
Plugin.Log.LogInfo((object)$"Credit: in-round = {flag} (credit hidden while true).");
}
return !flag;
}
public static void Tick()
{
if (_failed)
{
return;
}
int frameCount = Time.frameCount;
if (frameCount == _lastFrame)
{
return;
}
_lastFrame = frameCount;
try
{
if (!_created)
{
Create();
}
if ((Object)(object)_go != (Object)null)
{
ApplyCorner();
bool flag = ShouldShow();
if (_go.activeSelf != flag)
{
_go.SetActive(flag);
}
}
}
catch (Exception ex)
{
_failed = true;
Plugin.Log.LogWarning((object)("credit ui: " + ex.Message));
}
}
private static string PngPath()
{
try
{
string directoryName = Path.GetDirectoryName(typeof(Plugin).Assembly.Location);
return string.IsNullOrEmpty(directoryName) ? null : Path.Combine(directoryName, "credit.png");
}
catch
{
return null;
}
}
private static Texture2D LoadPng()
{
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Expected O, but got Unknown
try
{
string text = PngPath();
if (text == null || !File.Exists(text))
{
return null;
}
byte[] array = File.ReadAllBytes(text);
Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
if (!ImageConversion.LoadImage(val, array))
{
return null;
}
((Texture)val).wrapMode = (TextureWrapMode)1;
((Texture)val).filterMode = (FilterMode)1;
return val;
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("credit png load: " + ex.Message));
return null;
}
}
private static void Create()
{
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Expected O, but got Unknown
//IL_015b: Unknown result type (might be due to invalid IL or missing references)
//IL_0160: Unknown result type (might be due to invalid IL or missing references)
//IL_0176: Unknown result type (might be due to invalid IL or missing references)
//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
//IL_0207: Unknown result type (might be due to invalid IL or missing references)
//IL_021b: Unknown result type (might be due to invalid IL or missing references)
//IL_0243: Unknown result type (might be due to invalid IL or missing references)
//IL_0069: Unknown result type (might be due to invalid IL or missing references)
//IL_006e: Unknown result type (might be due to invalid IL or missing references)
//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
_created = true;
_go = new GameObject("CrewSizeMod.CreditCanvas");
Object.DontDestroyOnLoad((Object)(object)_go);
Canvas obj = _go.AddComponent<Canvas>();
obj.renderMode = (RenderMode)0;
obj.sortingOrder = 30000;
_go.AddComponent<CanvasScaler>().uiScaleMode = (ScaleMode)0;
_tex = LoadPng();
if ((Object)(object)_tex != (Object)null)
{
GameObject val = new GameObject("Image");
val.transform.SetParent(_go.transform, false);
_raw = val.AddComponent<RawImage>();
_raw.texture = (Texture)(object)_tex;
((Graphic)_raw).raycastTarget = false;
_rt = ((Graphic)_raw).rectTransform;
float num = Plugin.CreditWidth?.Value ?? 320f;
float num2 = num * (float)((Texture)_tex).height / (float)Mathf.Max(1, ((Texture)_tex).width);
_rt.sizeDelta = new Vector2(num, num2);
ApplyCorner();
Plugin.Log.LogInfo((object)$"Credit UI canvas created (uGUI image, credit.png {((Texture)_tex).width}x{((Texture)_tex).height} -> {(int)num}x{(int)num2} px).");
}
else
{
GameObject val2 = new GameObject("Text");
val2.transform.SetParent(_go.transform, false);
_text = val2.AddComponent<Text>();
_text.font = Font.CreateDynamicFontFromOSFont("Arial", 20);
_text.fontSize = 20;
_text.fontStyle = (FontStyle)1;
((Graphic)_text).color = Color.white;
_text.text = "MoreCrew by Izaya_here\nFollow on Twitch: twitch.tv/izaya_here";
_text.horizontalOverflow = (HorizontalWrapMode)1;
_text.verticalOverflow = (VerticalWrapMode)1;
((Graphic)_text).raycastTarget = false;
Shadow obj2 = val2.AddComponent<Shadow>();
obj2.effectColor = new Color(0f, 0f, 0f, 0.85f);
obj2.effectDistance = new Vector2(1.5f, -1.5f);
_rt = ((Graphic)_text).rectTransform;
_rt.sizeDelta = new Vector2(520f, 64f);
ApplyCorner();
Plugin.Log.LogInfo((object)"Credit UI canvas created (uGUI text fallback - credit.png not found next to the DLL).");
}
}
private static void ApplyCorner()
{
//IL_0093: 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_00ab: Unknown result type (might be due to invalid IL or missing references)
//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
//IL_0122: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)_rt == (Object)null)
{
return;
}
ScreenCorner screenCorner = Plugin.TwitchCreditCorner?.Value ?? ScreenCorner.TopLeft;
float num = Plugin.CreditWidth?.Value ?? 320f;
if (screenCorner != _appliedCorner || num != _appliedWidth)
{
_appliedCorner = screenCorner;
_appliedWidth = num;
bool flag = screenCorner == ScreenCorner.TopLeft || screenCorner == ScreenCorner.BottomLeft;
bool flag2 = screenCorner == ScreenCorner.TopLeft || screenCorner == ScreenCorner.TopRight;
Vector2 val = default(Vector2);
((Vector2)(ref val))..ctor(flag ? 0f : 1f, flag2 ? 1f : 0f);
_rt.anchorMin = val;
_rt.anchorMax = val;
_rt.pivot = val;
_rt.anchoredPosition = new Vector2(flag ? 18f : (-18f), flag2 ? (-14f) : 14f);
if ((Object)(object)_raw != (Object)null && (Object)(object)_tex != (Object)null)
{
float num2 = num * (float)((Texture)_tex).height / (float)Mathf.Max(1, ((Texture)_tex).width);
_rt.sizeDelta = new Vector2(num, num2);
}
if ((Object)(object)_text != (Object)null)
{
_text.alignment = (TextAnchor)(screenCorner switch
{
ScreenCorner.BottomRight => 8,
ScreenCorner.TopRight => 2,
ScreenCorner.TopLeft => 0,
_ => 6,
});
}
}
}
}
[HarmonyPatch]
internal static class Patch_CreditDriver
{
private static bool _logged;
private static IEnumerable<MethodBase> TargetMethods()
{
(string, string)[] array = new(string, string)[4]
{
("GameManager", "Update"),
("ShiftManager", "OnUpdatePresentation"),
("GameMenuUI", "OnUpdatePresentation"),
("LobbyButtonsUI", "OnUpdatePresentation")
};
(string type, string method)[] array2 = array;
for (int i = 0; i < array2.Length; i++)
{
(string type, string method) tuple = array2[i];
string item = tuple.type;
string item2 = tuple.method;
Type type = AccessTools.TypeByName(item);
MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, item2, (Type[])null, (Type[])null) : null);
if (methodInfo != null)
{
yield return methodInfo;
}
}
}
private static void Postfix()
{
if (!_logged)
{
_logged = true;
Plugin.Log.LogInfo((object)"Credit driver is firing (uGUI path active).");
}
CreditUI.Tick();
}
}
[HarmonyPatch]
internal static class Patch_ShiftLifecycle
{
private static IEnumerable<MethodBase> TargetMethods()
{
Type t = AccessTools.TypeByName("ShiftManager");
if (t == null)
{
yield break;
}
string[] array = new string[2] { "OnEntityStart", "OnEntityDestroyed" };
foreach (string text in array)
{
MethodInfo methodInfo = AccessTools.Method(t, text, (Type[])null, (Type[])null);
if (methodInfo != null)
{
yield return methodInfo;
}
}
}
private static void Postfix(object __instance, MethodBase __originalMethod)
{
if (__originalMethod.Name == "OnEntityStart")
{
CreditUI.SetShiftManager(__instance);
}
else
{
CreditUI.ClearShiftManager(__instance);
}
}
}
[HarmonyPatch]
internal static class Patch_FinalReportPlayerCap
{
private static readonly string[] PerPlayerLists = new string[12]
{
"playerIconImages", "playerNameTexts", "playerHighlightImages", "playerIconObjects", "crashoutCountObjects", "nitroCountObjects", "driftCountObjects", "upgradeCountObjects", "crashoutCountTexts", "nitroCountTexts",
"driftCountTexts", "upgradeCountTexts"
};
private static bool _noticed;
private static MethodBase TargetMethod()
{
Type type = AccessTools.TypeByName("ReportUI");
if (!(type != null))
{
return null;
}
return AccessTools.Method(type, "StartSequenceCo", (Type[])null, (Type[])null);
}
private static void Postfix(object __instance, object __result)
{
try
{
if (__instance == null || __result == null)
{
return;
}
FieldInfo fieldInfo = AccessTools.Field(__result.GetType(), "playerResults");
if (fieldInfo == null || !(fieldInfo.GetValue(__result) is Array array))
{
return;
}
int num = RowCapacity(__instance);
if (num >= 1 && array.Length > num)
{
Array array2 = Array.CreateInstance(array.GetType().GetElementType(), num);
Array.Copy(array, array2, num);
fieldInfo.SetValue(__result, array2);
if (!_noticed)
{
_noticed = true;
Plugin.Log.LogWarning((object)($"Final report (ReportUI): capped {array.Length} players -> {num} UI rows to " + "avoid the end-of-run freeze. Players 5+ get no row on the grade screen (cosmetic)."));
}
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("final-report cap postfix: " + ex.Message));
}
}
private static int RowCapacity(object reportUi)
{
int num = int.MaxValue;
Type type = reportUi.GetType();
string[] perPlayerLists = PerPlayerLists;
foreach (string text in perPlayerLists)
{
if (AccessTools.Field(type, text)?.GetValue(reportUi) is IList list && list.Count > 0 && list.Count < num)
{
num = list.Count;
}
}
if (num != int.MaxValue)
{
return num;
}
return 4;
}
}
}