├── NuGet.Config
├── Models
├── StatsScreenType.cs
├── StatsUIReferences.cs
└── PlayerStatsList.cs
├── Helper.cs
├── LICENCE
├── BiggerLobby.sln
├── Patches
├── ChatPatches.cs
├── ListSizeTranspilers.cs
├── PlayerObjects.cs
└── NonGamePatches.cs
├── BiggerLobby.csproj
├── Plugin.cs
├── UI
└── ExpandedStatsUI.cs
└── .gitignore
/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Models/StatsScreenType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace BiggerLobby.Models
6 | {
7 | public enum StatsScreenType
8 | {
9 | FourPlayers,
10 | EightPlayers,
11 | MoreThanEightPlayers
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Models/StatsUIReferences.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using UnityEngine;
5 | using UnityEngine.UI;
6 |
7 | namespace BiggerLobby.Models
8 | {
9 | public class StatsUIReferences : MonoBehaviour
10 | {
11 | public Sprite StatsBoxesThin;
12 | public Sprite StatsBoxesGradeOnly;
13 | public Sprite CheckmarkThin;
14 | public ScrollRect ThinScrollRect;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Helper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace BiggerLobby
5 | {
6 | public static class Helper
7 | {
8 | public static T[] ResizeArray(T[] oldArray, int newSize) {
9 | if (oldArray.Length >= newSize)
10 | {
11 | return oldArray;
12 | }
13 | var newArray = new T[newSize];
14 | oldArray.CopyTo(newArray, 0);
15 | return newArray;
16 | }
17 | public static void ResizeList(this List list, int size, T element = default(T))
18 | {
19 | int count = list.Count;
20 |
21 | if (size < count)
22 | {
23 | list.RemoveRange(size, count - size);
24 | }
25 | else if (size > count)
26 | {
27 | if (size > list.Capacity) // Optimization
28 | list.Capacity = size;
29 |
30 | list.AddRange(Enumerable.Repeat(element, size - count));
31 | }
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Jayden Jones
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/BiggerLobby.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.5.002.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BiggerLobby", "BiggerLobby.csproj", "{401C49C2-A636-412E-9411-B9117C28B455}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {401C49C2-A636-412E-9411-B9117C28B455}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {401C49C2-A636-412E-9411-B9117C28B455}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {401C49C2-A636-412E-9411-B9117C28B455}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {401C49C2-A636-412E-9411-B9117C28B455}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {25E60979-670E-436C-B8C2-5214FD0A2F46}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/Models/PlayerStatsList.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using TMPro;
5 | using UnityEngine;
6 | using UnityEngine.UI;
7 |
8 | namespace BiggerLobby.Models
9 | {
10 | public class PlayerStatsList
11 | {
12 | public Transform transform; // lowercase so it's the same as the unity API
13 | public GameObject gameObject => transform.gameObject;
14 |
15 | // TODO: Setup project for nullables and make these properly null
16 | public List Names = new();
17 | public List States = new();
18 | public List Notes = new();
19 |
20 | public PlayerStatsList(Transform transform)
21 | {
22 | this.transform = transform;
23 | }
24 |
25 | public void AddPlayerSlotTransform(Transform playerSlot)
26 | {
27 | var playerName = playerSlot.GetChild(0).GetComponent();
28 | var playerSymbol = playerSlot.GetChild(1).GetComponent();
29 | var playerNotes = playerSlot.Find("Notes").GetComponent();
30 |
31 | Names.Add(playerName);
32 | States.Add(playerSymbol);
33 | Notes.Add(playerNotes);
34 | }
35 |
36 | public void AddPlayerSlotTransforms(List playerSlots)
37 | {
38 | foreach (var playerSlot in playerSlots)
39 | {
40 | AddPlayerSlotTransform(playerSlot);
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Patches/ChatPatches.cs:
--------------------------------------------------------------------------------
1 | using BepInEx;
2 | using GameNetcodeStuff;
3 | using HarmonyLib;
4 | using Steamworks.Data;
5 | using System;
6 | using System.Collections;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Reflection;
10 | using System.Reflection.Emit;
11 | using System.Text;
12 | using TMPro;
13 | using Unity.Netcode;
14 | using UnityEngine;
15 | using UnityEngine.UIElements;
16 |
17 | namespace BiggerLobby.Patches
18 | {
19 | [HarmonyPatch(typeof(HUDManager))]
20 | internal class ChatPatches
21 | {
22 | static MethodInfo TargetMethod()
23 | {
24 | return typeof(HUDManager)
25 | .GetMethod("AddChatMessage",
26 | BindingFlags.NonPublic | BindingFlags.Instance);
27 | }
28 |
29 | [HarmonyTranspiler]
30 | public static IEnumerable Transpiler(IEnumerable instructions)
31 | {
32 | var stringBuilderConstructor = AccessTools.Constructor(typeof(StringBuilder), new[] { typeof(string) });
33 | var applyCustomPlayerNumberMethod = AccessTools.Method(typeof(ChatPatches), nameof(ApplyCustomPlayerNumber));
34 |
35 | var codeMatcher = new CodeMatcher(instructions)
36 | .MatchForward(false, new CodeMatch(OpCodes.Newobj, stringBuilderConstructor))
37 | .ThrowIfInvalid("Unable to find StringBuilder constructor.")
38 | .Advance(1)
39 | .Insert(new CodeInstruction(OpCodes.Callvirt, applyCustomPlayerNumberMethod))
40 | .InstructionEnumeration();
41 |
42 | return codeMatcher;
43 | }
44 |
45 | public static StringBuilder ApplyCustomPlayerNumber(StringBuilder stringBuilder)
46 | {
47 | var playerScripts = Plugin.GetRealPlayerScripts(StartOfRound.Instance);
48 |
49 | // if (playerScripts == null || playerScripts.Length < 5)
50 | if (playerScripts == null)
51 | return stringBuilder;
52 |
53 | // Replace all players greater than index playerNum3 as needed
54 | // Leave playernum0-3 completely untouched to retain vanilla behaviour where possible
55 | for (int i = 4; i < playerScripts.Length; i++)
56 | {
57 | if (playerScripts[i]?.playerUsername == null)
58 | continue;
59 |
60 | stringBuilder.Replace($"[playerNum{i}]", playerScripts[i].playerUsername);
61 | }
62 |
63 | return stringBuilder;
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/BiggerLobby.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 | BiggerLobby
6 | Increase the max players to 50 in Lethal Company
7 | 2.7.0
8 | true
9 | latest
10 | $(NoWarn);Harmony003
11 |
12 |
13 |
14 |
15 | ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Lethal Company\Lethal Company_Data\Managed\Assembly-CSharp.dll
16 |
17 |
18 | ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Lethal Company\Lethal Company_Data\Managed\DissonanceVoip.dll
19 |
20 |
21 | ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Lethal Company\Lethal Company_Data\Managed\Facepunch Transport for Netcode for GameObjects.dll
22 |
23 |
24 | ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Lethal Company\Lethal Company_Data\Managed\Facepunch.Steamworks.Win64.dll
25 |
26 |
27 | ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Lethal Company\BepInEx\plugins\LC_API.dll
28 |
29 |
30 | ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Lethal Company\Lethal Company_Data\Managed\Unity.Collections.dll
31 |
32 |
33 | ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Lethal Company\Lethal Company_Data\Managed\Unity.Netcode.Components.dll
34 |
35 |
36 | ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Lethal Company\Lethal Company_Data\Managed\Unity.Netcode.Runtime.dll
37 |
38 |
39 | ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Lethal Company\Lethal Company_Data\Managed\Unity.TextMeshPro.dll
40 |
41 |
42 | ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Lethal Company\Lethal Company_Data\Managed\UnityEngine.UI.dll
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/Plugin.cs:
--------------------------------------------------------------------------------
1 | using BepInEx;
2 | using HarmonyLib;
3 | using System.Reflection;
4 | using LC_API;
5 | using GameNetcodeStuff;
6 | using UnityEngine;
7 | using UnityEngine.SceneManagement;
8 | using Unity.Netcode;
9 | using System.Collections.Generic;
10 | using UnityEngine.Audio;
11 | using Unity.Collections;
12 | using BepInEx.Configuration;
13 | using System.Linq;
14 | using BepInEx.Logging;
15 |
16 | namespace BiggerLobby
17 | {
18 | [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
19 | public class Plugin : BaseUnityPlugin
20 | {
21 | public static Plugin Instance;
22 | public static bool oldhastime;
23 | public static int MaxPlayers = 16;
24 | public static bool instantiating;
25 | public static NetworkObject[] PlayerObjects = new NetworkObject[]{ };
26 | //public static UnnamedStringMessageHandler MainCommunication;
27 | public static Harmony _harmony;
28 | public static Harmony _harmony2;
29 | public static ConfigEntry? _LoudnessMultiplier;
30 | public static bool Initialized = false;
31 |
32 | internal new static ManualLogSource? Logger { get; set; }
33 |
34 | public static IDictionary CustomNetObjects = new Dictionary { };
35 | private void Awake()
36 | {
37 | Instance = this;
38 | _LoudnessMultiplier =
39 | Config.Bind("General", "Player loudness", 1, "Default player loudness");
40 | Logger = base.Logger;
41 | _harmony = new Harmony(PluginInfo.PLUGIN_GUID);//todo: patch non menu changes only when lobby joined, then unpatch them after.
42 | _harmony2 = new Harmony(PluginInfo.PLUGIN_GUID + "A");
43 | _harmony.PatchAll(typeof(Patches.NonGamePatches));
44 | _harmony.PatchAll(typeof(Patches.NonGamePatches.InternalPatches));
45 | _harmony.PatchAll(typeof(Patches.NonGamePatches.InternalPatches2));
46 | Plugin.CustomNetObjects.Clear();
47 | Plugin._harmony2.PatchAll(typeof(Patches.ChatPatches));
48 | Plugin._harmony2.PatchAll(typeof(Patches.ListSizeTranspilers));
49 | Plugin._harmony2.PatchAll(typeof(Patches.PlayerObjects));
50 | Logger.LogInfo($"{PluginInfo.PLUGIN_GUID} loaded");
51 | }
52 |
53 | private void Start()
54 | {
55 | Initialize();
56 | }
57 |
58 | // Legacy behaviour for if BepInEx.cfg's "HideManagerGameObject" is set to false
59 | private void OnDestroy()
60 | {
61 | Initialize();
62 | }
63 |
64 | private void Initialize()
65 | {
66 | if (!Initialized)
67 | {
68 | Initialized = true;
69 | LC_API.ServerAPI.ModdedServer.SetServerModdedOnly();
70 | }
71 | }
72 |
73 | public static int GetPlayerCount()
74 | {
75 | return MaxPlayers;
76 | }
77 |
78 | public static int GetPlayerCountMinusOne()
79 | {
80 | return MaxPlayers - 1;
81 | }
82 |
83 | public static PlayerControllerB[] GetRealPlayerScripts(StartOfRound startOfRound)
84 | {
85 | if (startOfRound == null || startOfRound.allPlayerScripts == null)
86 | {
87 | return new PlayerControllerB[0]; // ??
88 | }
89 | // should probably be replaced with something smarter so this method doesn't have to run like 5 times every EndOfGameStats
90 | return startOfRound.allPlayerScripts.Where(x => x.isPlayerDead || x.isPlayerControlled).ToArray();
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Patches/ListSizeTranspilers.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection.Emit;
5 | using GameNetcodeStuff;
6 | using HarmonyLib;
7 | using BepInEx;
8 | using UnityEngine;
9 | using System.Reflection;
10 | using System;
11 |
12 | namespace BiggerLobby.Patches
13 | {
14 | [HarmonyPatch]
15 | public class ListSizeTranspilers
16 | {
17 | private static MethodInfo _playerCountMethod = AccessTools.Method(typeof(Plugin), "GetPlayerCount");
18 | private static MethodInfo _playerCountMinusOneMethod = AccessTools.Method(typeof(Plugin), "GetPlayerCountMinusOne");
19 | private static MethodInfo _realPlayerScriptsMethod = AccessTools.Method(typeof(Plugin), "GetRealPlayerScripts");
20 | /*private static List _playerCountInstructions = new List()
21 | {
22 | new CodeInstruction(OpCodes.Call, _playerCountMethod)
23 | };*/
24 |
25 | private static void CheckAndReplace(List codes, int index)
26 | {
27 | if (codes[index].opcode == OpCodes.Ldc_I4_4)
28 | {
29 | codes[index].opcode = OpCodes.Call;
30 | codes[index].operand = _playerCountMethod;
31 | }
32 | }
33 |
34 | [HarmonyPatch(typeof(HUDManager), "SyncAllPlayerLevelsServerRpc", new Type[] { })] // SyncAllPlayerLevelsServerRpc(int, int) uses a list instead of an array[4]
35 | [HarmonyPatch(typeof(DressGirlAI), "ChoosePlayerToHaunt")]
36 | [HarmonyPatch(typeof(CrawlerAI), "Start")]
37 | [HarmonyTranspiler]
38 | public static IEnumerable SyncLevelsRpc(IEnumerable instructions)
39 | {
40 | var codes = new List(instructions);
41 |
42 | for (int i = 0; i < codes.Count; i++)
43 | {
44 | if (codes[i].opcode == OpCodes.Newarr)
45 | {
46 | CheckAndReplace(codes, i - 1);
47 | }
48 | }
49 | return codes.AsEnumerable();
50 | }
51 |
52 | [HarmonyPatch(typeof(PlayerControllerB), "SendNewPlayerValuesServerRpc")]
53 | [HarmonyPatch(typeof(StartOfRound), "SyncShipUnlockablesClientRpc")]
54 | [HarmonyPatch(typeof(DressGirlAI), "ChoosePlayerToHaunt")]
55 | [HarmonyPatch(typeof(EnemyAI), "GetClosestPlayer")]
56 | [HarmonyPatch(typeof(SpringManAI), "DoAIInterval")]
57 | [HarmonyPatch(typeof(SpringManAI), "Update")]
58 | [HarmonyTranspiler]
59 | public static IEnumerable SendNewPlayerValuesServerRpc(IEnumerable instructions) {
60 | var codes = new List(instructions);
61 | for (int i = 0; i < codes.Count; i++)
62 | {
63 | if (codes[i].opcode == OpCodes.Blt)
64 | {
65 | CheckAndReplace(codes, i - 1);
66 | }
67 | }
68 | return codes.AsEnumerable();
69 | }
70 | [HarmonyPatch(typeof(QuickMenuManager), "ConfirmKickUserFromServer")]
71 | [HarmonyTranspiler]
72 | public static IEnumerable ConfirmKickUserFromServer(IEnumerable instructions)
73 | {
74 | var codes = new List(instructions);
75 | for (int i = 0; i < codes.Count; i++)
76 | {
77 | if (codes[i].opcode == OpCodes.Ldc_I4_3)
78 | {
79 | codes[i].opcode = OpCodes.Call;
80 | codes[i].operand = _playerCountMinusOneMethod; //lmfao
81 | break;
82 | }
83 | }
84 | return codes.AsEnumerable();
85 | }
86 |
87 | [HarmonyPatch(typeof(HUDManager), "FillEndGameStats")]
88 | [HarmonyTranspiler]
89 | public static IEnumerable FillEndGameStatsPatch(IEnumerable instructions)
90 | {
91 | var codes = new List(instructions);
92 | for (int i = 0; i < codes.Count; i++)
93 | {
94 | if (codes[i].opcode == OpCodes.Ldfld && codes[i].operand is FieldInfo fieldInfo && fieldInfo.Name == "allPlayerScripts")
95 | {
96 | // replace allPlayerScripts call with a devious custom method that will remove fake players
97 | codes[i].opcode = OpCodes.Call;
98 | codes[i].operand = _realPlayerScriptsMethod;
99 | }
100 | }
101 | return codes.Where(x => x.opcode != OpCodes.Nop).AsEnumerable();
102 | }
103 |
104 | [HarmonyPatch(typeof(StartOfRound), "SyncShipUnlockablesServerRpc")]
105 | [HarmonyPatch(typeof(StartOfRound), "OnClientConnect")]
106 | [HarmonyPatch(typeof(PlayerControllerB), "SpectateNextPlayer")]
107 | [HarmonyTranspiler]
108 | public static IEnumerable SyncShipUnlockablesServerRpc(IEnumerable instructions)
109 | {
110 | var codes = new List(instructions);
111 | for (int i = 0; i < codes.Count; i++)
112 | {
113 | if (codes[i].opcode == OpCodes.Ldc_I4_4)
114 | {
115 | codes[i].opcode = OpCodes.Call;
116 | codes[i].operand = _playerCountMethod;
117 | }
118 | }
119 | return codes.AsEnumerable();
120 | }
121 |
122 | }
123 | }
--------------------------------------------------------------------------------
/UI/ExpandedStatsUI.cs:
--------------------------------------------------------------------------------
1 | using BiggerLobby.Models;
2 | using System;
3 | using System.Collections;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Text;
7 | using TMPro;
8 | using UnityEngine;
9 | using UnityEngine.UI;
10 |
11 | namespace BiggerLobby.UI
12 | {
13 | public class ExpandedStatsUI : MonoBehaviour
14 | {
15 | private bool _initialized = false;
16 | private bool _debugStatsUI = false;
17 |
18 | private static StatsUIReferences? _statsUIReferences;
19 | private PlayerStatsList _fourPlayersList;
20 | private PlayerStatsList _eightPlayersList;
21 | private PlayerStatsList _moreThanEightPlayersList;
22 | private List _moreThanEightPlayersPages = new();
23 |
24 | // TODO: convert this to one variable somewhere, it's also used in the UI capping max player count
25 | public int UpperPlayerLimit = 40;
26 |
27 | // TODO: reimpl end game UI "checking off" players one at a time
28 | // this is implemented in an animation and might be a bit annoying to recreate
29 | // TODO: Add animation to page switch
30 | public float SecondsPanelVisible = 8.5f;
31 |
32 | private Sprite FourPlayerStatBoxes;
33 | private Sprite EightPlayerStatBoxes;
34 |
35 | private void Start()
36 | {
37 | if (_initialized) return;
38 |
39 | if (_debugStatsUI)
40 | {
41 | DebugStats();
42 | }
43 |
44 | SetupFourPlayerSlots();
45 | SetupEightPlayerSlots();
46 | SetupMoreThanEightPlayersSlots();
47 |
48 | EightPlayerStatBoxes = _statsUIReferences.StatsBoxesThin;
49 | FourPlayerStatBoxes = transform.GetChild(1).GetComponent().sprite;
50 |
51 | transform.GetChild(2).Find("AllDead").SetAsLastSibling();
52 | _initialized = true;
53 | }
54 |
55 | private void DebugStats()
56 | {
57 | // preview endgame stats here
58 | gameObject.GetComponent().enabled = false;
59 | transform.GetChild(0).GetComponent().alpha = 1;
60 | transform.GetChild(1).GetComponent().alpha = 1;
61 | transform.GetChild(2).GetComponent().alpha = 1;
62 | transform.GetChild(2).Find("AllDead").gameObject.SetActive(false);
63 | }
64 |
65 | // TODO: this breaks base game animation
66 | private void SetupFourPlayerSlots()
67 | {
68 | _fourPlayersList = new(CreateTransformAtParentOrigin("FourPlayersList", transform.GetChild(2)));
69 | // move player slots
70 |
71 | for (int i = 0; i < 4; i++)
72 | {
73 | var playerSlot = transform.GetChild(2).Find($"PlayerSlot{i + 1}");
74 | playerSlot.SetParent(_fourPlayersList.transform);
75 | _fourPlayersList.AddPlayerSlotTransform(playerSlot);
76 | }
77 | }
78 |
79 | // only run after isolating 4 player slots in SetupFourPlayerSlots();
80 | private void SetupEightPlayerSlots()
81 | {
82 | _eightPlayersList = new(CreateTransformAtParentOrigin("EightPlayersList", transform.GetChild(2)));
83 |
84 | var playerSlots = SetupEightPlayerPage(_eightPlayersList.transform);
85 | _eightPlayersList.AddPlayerSlotTransforms(playerSlots);
86 | }
87 |
88 | private void SetupMoreThanEightPlayersSlots()
89 | {
90 | _moreThanEightPlayersList = new(CreateTransformAtParentOrigin("MoreThanEightPlayersList", transform.GetChild(2)));
91 |
92 | int maxPageCount = (int)Math.Ceiling(UpperPlayerLimit / 8f);
93 | for (int i = 0; i < maxPageCount; i++)
94 | {
95 | var page = CreateTransformAtParentOrigin($"Page{i}", _moreThanEightPlayersList.transform);
96 | _moreThanEightPlayersPages.Add(page.gameObject);
97 | var playerSlots = SetupEightPlayerPage(page);
98 | _moreThanEightPlayersList.AddPlayerSlotTransforms(playerSlots);
99 |
100 | if (i != 0) page.gameObject.SetActive(false);
101 | }
102 | }
103 |
104 | private List SetupEightPlayerPage(Transform parent)
105 | {
106 | List playerSlots = new();
107 | for (int i = 0; i < 8; i++)
108 | {
109 | var otherPlayer = Instantiate(_fourPlayersList.transform.GetChild(0), parent, true);
110 | SetupPlayerSlot(otherPlayer);
111 | otherPlayer.localPosition = new Vector3(otherPlayer.localPosition.x, -26.1f * i, otherPlayer.localPosition.z);
112 | playerSlots.Add(otherPlayer);
113 | }
114 |
115 | return playerSlots;
116 | }
117 |
118 | private void SetupPlayerSlot(Transform playerSlot)
119 | {
120 | var playerNotes = playerSlot.Find("Notes").GetComponent();
121 | var playerName = playerSlot.GetChild(0).GetComponent();
122 | var playerNameRect = playerName.GetComponent();
123 | var playerSymbol = playerSlot.GetChild(1).GetComponent();
124 | var playerSymbolRect = playerSymbol.GetComponent();
125 |
126 | playerNotes.text = "* Most lazy employee\n* Most paranoid employee\n* Sustained the most injuries";
127 | playerNotes.fontSize = 9;
128 |
129 | playerName.text = "CrazyDude12WW";
130 | playerNameRect.localPosition = new Vector3(playerNameRect.localPosition.x, 101.5f, playerNameRect.localPosition.z);
131 |
132 | playerSymbol.sprite = _statsUIReferences.CheckmarkThin;
133 | playerSymbolRect.sizeDelta = new Vector2(playerSymbolRect.sizeDelta.x, 31.235f);
134 | playerSymbolRect.localPosition = new Vector3(playerSymbolRect.localPosition.x, 101.5f, playerSymbolRect.localPosition.z);
135 | }
136 |
137 | private Transform CreateTransformAtParentOrigin(string name, Transform parent)
138 | {
139 | var newTransform = new GameObject(name).transform;
140 | newTransform.SetParent(parent);
141 | newTransform.localPosition = Vector3.zero;
142 | newTransform.localRotation = Quaternion.identity;
143 | newTransform.localScale = Vector3.one;
144 |
145 | return newTransform;
146 | }
147 |
148 | public void LoadStatsUIBundle()
149 | {
150 | var bundlePath = Path.Join(Path.GetDirectoryName(Plugin.Instance.Info.Location), "statsuireferences");
151 | var bundle = AssetBundle.LoadFromFile(bundlePath);
152 | var asset = bundle.LoadAsset("assets/prefabs/statsuireferences.prefab");
153 | _statsUIReferences = asset.GetComponent();
154 | bundle.Unload(false);
155 | }
156 |
157 | public PlayerStatsList GetStatsListFromPlayerCount(int playerCount)
158 | {
159 | _fourPlayersList.gameObject.SetActive(false);
160 | _eightPlayersList.gameObject.SetActive(false);
161 | _moreThanEightPlayersList.gameObject.SetActive(false);
162 |
163 | PlayerStatsList playerStatsList = _fourPlayersList;
164 | if (playerCount > 8) playerStatsList = _moreThanEightPlayersList;
165 | else if (playerCount > 4) playerStatsList = _eightPlayersList;
166 |
167 | SetupStatsList(playerStatsList, playerCount);
168 | return playerStatsList;
169 | }
170 |
171 | private void SetupStatsList(PlayerStatsList playerStatsList, int playerCount)
172 | {
173 | playerStatsList.gameObject.SetActive(true);
174 | transform.GetChild(1).GetComponent().sprite = playerCount <= 4 ? FourPlayerStatBoxes : EightPlayerStatBoxes;
175 | if (playerCount > 8)
176 | {
177 | // need the fancy one!
178 | StartCoroutine(PaginatePlayers(playerCount));
179 | }
180 |
181 |
182 | // setup null players properly
183 | for (int i = 0; i < playerStatsList.Names.Count; i++)
184 | {
185 | playerStatsList.Names[i].text = "";
186 | playerStatsList.Notes[i].text = "";
187 | playerStatsList.States[i].enabled = false;
188 | }
189 | }
190 |
191 | private IEnumerator PaginatePlayers(int playerCount)
192 | {
193 | int maxPageCount = (int)Math.Ceiling(playerCount / 8f);
194 | float pageDuration = SecondsPanelVisible / (float)maxPageCount;
195 |
196 | foreach (var page in _moreThanEightPlayersPages)
197 | {
198 | page.SetActive(false);
199 | }
200 |
201 | for (int i = 0; i < maxPageCount; i++)
202 | {
203 | _moreThanEightPlayersPages[i].SetActive(true);
204 | if (i > 0) _moreThanEightPlayersPages[i - 1].SetActive(false);
205 | yield return new WaitForSeconds(pageDuration);
206 | }
207 | }
208 |
209 | public static ExpandedStatsUI GetFromAnimator(Animator endgameStatsAnimator)
210 | {
211 | if (endgameStatsAnimator.TryGetComponent(out ExpandedStatsUI component))
212 | {
213 | return component;
214 | }
215 | else
216 | {
217 | var statsUI = endgameStatsAnimator.gameObject.AddComponent();
218 | if (_statsUIReferences == null) statsUI.LoadStatsUIBundle();
219 | return statsUI;
220 | }
221 | }
222 |
223 | public static Sprite? GetReplacementCheckmark() => _statsUIReferences?.CheckmarkThin;
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # Tye
66 | .tye/
67 |
68 | # ASP.NET Scaffolding
69 | ScaffoldingReadMe.txt
70 |
71 | # StyleCop
72 | StyleCopReport.xml
73 |
74 | # Files built by Visual Studio
75 | *_i.c
76 | *_p.c
77 | *_h.h
78 | *.ilk
79 | *.meta
80 | *.obj
81 | *.iobj
82 | *.pch
83 | *.pdb
84 | *.ipdb
85 | *.pgc
86 | *.pgd
87 | *.rsp
88 | *.sbr
89 | *.tlb
90 | *.tli
91 | *.tlh
92 | *.tmp
93 | *.tmp_proj
94 | *_wpftmp.csproj
95 | *.log
96 | *.tlog
97 | *.vspscc
98 | *.vssscc
99 | .builds
100 | *.pidb
101 | *.svclog
102 | *.scc
103 |
104 | # Chutzpah Test files
105 | _Chutzpah*
106 |
107 | # Visual C++ cache files
108 | ipch/
109 | *.aps
110 | *.ncb
111 | *.opendb
112 | *.opensdf
113 | *.sdf
114 | *.cachefile
115 | *.VC.db
116 | *.VC.VC.opendb
117 |
118 | # Visual Studio profiler
119 | *.psess
120 | *.vsp
121 | *.vspx
122 | *.sap
123 |
124 | # Visual Studio Trace Files
125 | *.e2e
126 |
127 | # TFS 2012 Local Workspace
128 | $tf/
129 |
130 | # Guidance Automation Toolkit
131 | *.gpState
132 |
133 | # ReSharper is a .NET coding add-in
134 | _ReSharper*/
135 | *.[Rr]e[Ss]harper
136 | *.DotSettings.user
137 |
138 | # TeamCity is a build add-in
139 | _TeamCity*
140 |
141 | # DotCover is a Code Coverage Tool
142 | *.dotCover
143 |
144 | # AxoCover is a Code Coverage Tool
145 | .axoCover/*
146 | !.axoCover/settings.json
147 |
148 | # Coverlet is a free, cross platform Code Coverage Tool
149 | coverage*.json
150 | coverage*.xml
151 | coverage*.info
152 |
153 | # Visual Studio code coverage results
154 | *.coverage
155 | *.coveragexml
156 |
157 | # NCrunch
158 | _NCrunch_*
159 | .*crunch*.local.xml
160 | nCrunchTemp_*
161 |
162 | # MightyMoose
163 | *.mm.*
164 | AutoTest.Net/
165 |
166 | # Web workbench (sass)
167 | .sass-cache/
168 |
169 | # Installshield output folder
170 | [Ee]xpress/
171 |
172 | # DocProject is a documentation generator add-in
173 | DocProject/buildhelp/
174 | DocProject/Help/*.HxT
175 | DocProject/Help/*.HxC
176 | DocProject/Help/*.hhc
177 | DocProject/Help/*.hhk
178 | DocProject/Help/*.hhp
179 | DocProject/Help/Html2
180 | DocProject/Help/html
181 |
182 | # Click-Once directory
183 | publish/
184 |
185 | # Publish Web Output
186 | *.[Pp]ublish.xml
187 | *.azurePubxml
188 | # Note: Comment the next line if you want to checkin your web deploy settings,
189 | # but database connection strings (with potential passwords) will be unencrypted
190 | *.pubxml
191 | *.publishproj
192 |
193 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
194 | # checkin your Azure Web App publish settings, but sensitive information contained
195 | # in these scripts will be unencrypted
196 | PublishScripts/
197 |
198 | # NuGet Packages
199 | *.nupkg
200 | # NuGet Symbol Packages
201 | *.snupkg
202 | # The packages folder can be ignored because of Package Restore
203 | **/[Pp]ackages/*
204 | # except build/, which is used as an MSBuild target.
205 | !**/[Pp]ackages/build/
206 | # Uncomment if necessary however generally it will be regenerated when needed
207 | #!**/[Pp]ackages/repositories.config
208 | # NuGet v3's project.json files produces more ignorable files
209 | *.nuget.props
210 | *.nuget.targets
211 |
212 | # Microsoft Azure Build Output
213 | csx/
214 | *.build.csdef
215 |
216 | # Microsoft Azure Emulator
217 | ecf/
218 | rcf/
219 |
220 | # Windows Store app package directories and files
221 | AppPackages/
222 | BundleArtifacts/
223 | Package.StoreAssociation.xml
224 | _pkginfo.txt
225 | *.appx
226 | *.appxbundle
227 | *.appxupload
228 |
229 | # Visual Studio cache files
230 | # files ending in .cache can be ignored
231 | *.[Cc]ache
232 | # but keep track of directories ending in .cache
233 | !?*.[Cc]ache/
234 |
235 | # Others
236 | ClientBin/
237 | ~$*
238 | *~
239 | *.dbmdl
240 | *.dbproj.schemaview
241 | *.jfm
242 | *.pfx
243 | *.publishsettings
244 | orleans.codegen.cs
245 |
246 | # Including strong name files can present a security risk
247 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
248 | #*.snk
249 |
250 | # Since there are multiple workflows, uncomment next line to ignore bower_components
251 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
252 | #bower_components/
253 |
254 | # RIA/Silverlight projects
255 | Generated_Code/
256 |
257 | # Backup & report files from converting an old project file
258 | # to a newer Visual Studio version. Backup files are not needed,
259 | # because we have git ;-)
260 | _UpgradeReport_Files/
261 | Backup*/
262 | UpgradeLog*.XML
263 | UpgradeLog*.htm
264 | ServiceFabricBackup/
265 | *.rptproj.bak
266 |
267 | # SQL Server files
268 | *.mdf
269 | *.ldf
270 | *.ndf
271 |
272 | # Business Intelligence projects
273 | *.rdl.data
274 | *.bim.layout
275 | *.bim_*.settings
276 | *.rptproj.rsuser
277 | *- [Bb]ackup.rdl
278 | *- [Bb]ackup ([0-9]).rdl
279 | *- [Bb]ackup ([0-9][0-9]).rdl
280 |
281 | # Microsoft Fakes
282 | FakesAssemblies/
283 |
284 | # GhostDoc plugin setting file
285 | *.GhostDoc.xml
286 |
287 | # Node.js Tools for Visual Studio
288 | .ntvs_analysis.dat
289 | node_modules/
290 |
291 | # Visual Studio 6 build log
292 | *.plg
293 |
294 | # Visual Studio 6 workspace options file
295 | *.opt
296 |
297 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
298 | *.vbw
299 |
300 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
301 | *.vbp
302 |
303 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
304 | *.dsw
305 | *.dsp
306 |
307 | # Visual Studio 6 technical files
308 | *.ncb
309 | *.aps
310 |
311 | # Visual Studio LightSwitch build output
312 | **/*.HTMLClient/GeneratedArtifacts
313 | **/*.DesktopClient/GeneratedArtifacts
314 | **/*.DesktopClient/ModelManifest.xml
315 | **/*.Server/GeneratedArtifacts
316 | **/*.Server/ModelManifest.xml
317 | _Pvt_Extensions
318 |
319 | # Paket dependency manager
320 | .paket/paket.exe
321 | paket-files/
322 |
323 | # FAKE - F# Make
324 | .fake/
325 |
326 | # CodeRush personal settings
327 | .cr/personal
328 |
329 | # Python Tools for Visual Studio (PTVS)
330 | __pycache__/
331 | *.pyc
332 |
333 | # Cake - Uncomment if you are using it
334 | # tools/**
335 | # !tools/packages.config
336 |
337 | # Tabs Studio
338 | *.tss
339 |
340 | # Telerik's JustMock configuration file
341 | *.jmconfig
342 |
343 | # BizTalk build output
344 | *.btp.cs
345 | *.btm.cs
346 | *.odx.cs
347 | *.xsd.cs
348 |
349 | # OpenCover UI analysis results
350 | OpenCover/
351 |
352 | # Azure Stream Analytics local run output
353 | ASALocalRun/
354 |
355 | # MSBuild Binary and Structured Log
356 | *.binlog
357 |
358 | # NVidia Nsight GPU debugger configuration file
359 | *.nvuser
360 |
361 | # MFractors (Xamarin productivity tool) working folder
362 | .mfractor/
363 |
364 | # Local History for Visual Studio
365 | .localhistory/
366 |
367 | # Visual Studio History (VSHistory) files
368 | .vshistory/
369 |
370 | # BeatPulse healthcheck temp database
371 | healthchecksdb
372 |
373 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
374 | MigrationBackup/
375 |
376 | # Ionide (cross platform F# VS Code tools) working folder
377 | .ionide/
378 |
379 | # Fody - auto-generated XML schema
380 | FodyWeavers.xsd
381 |
382 | # VS Code files for those working on multiple tools
383 | .vscode/*
384 | !.vscode/settings.json
385 | !.vscode/tasks.json
386 | !.vscode/launch.json
387 | !.vscode/extensions.json
388 | *.code-workspace
389 |
390 | # Local History for Visual Studio Code
391 | .history/
392 |
393 | # Windows Installer files from build outputs
394 | *.cab
395 | *.msi
396 | *.msix
397 | *.msm
398 | *.msp
399 |
400 | # JetBrains Rider
401 | *.sln.iml
402 |
403 | ##
404 | ## Visual studio for Mac
405 | ##
406 |
407 |
408 | # globs
409 | Makefile.in
410 | *.userprefs
411 | *.usertasks
412 | config.make
413 | config.status
414 | aclocal.m4
415 | install-sh
416 | autom4te.cache/
417 | *.tar.gz
418 | tarballs/
419 | test-results/
420 |
421 | # Mac bundle stuff
422 | *.dmg
423 | *.app
424 |
425 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
426 | # General
427 | .DS_Store
428 | .AppleDouble
429 | .LSOverride
430 |
431 | # Icon must end with two \r
432 | Icon
433 |
434 |
435 | # Thumbnails
436 | ._*
437 |
438 | # Files that might appear in the root of a volume
439 | .DocumentRevisions-V100
440 | .fseventsd
441 | .Spotlight-V100
442 | .TemporaryItems
443 | .Trashes
444 | .VolumeIcon.icns
445 | .com.apple.timemachine.donotpresent
446 |
447 | # Directories potentially created on remote AFP share
448 | .AppleDB
449 | .AppleDesktop
450 | Network Trash Folder
451 | Temporary Items
452 | .apdisk
453 |
454 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
455 | # Windows thumbnail cache files
456 | Thumbs.db
457 | ehthumbs.db
458 | ehthumbs_vista.db
459 |
460 | # Dump file
461 | *.stackdump
462 |
463 | # Folder config file
464 | [Dd]esktop.ini
465 |
466 | # Recycle Bin used on file shares
467 | $RECYCLE.BIN/
468 |
469 | # Windows Installer files
470 | *.cab
471 | *.msi
472 | *.msix
473 | *.msm
474 | *.msp
475 |
476 | # Windows shortcuts
477 | *.lnk
478 |
--------------------------------------------------------------------------------
/Patches/PlayerObjects.cs:
--------------------------------------------------------------------------------
1 | using GameNetcodeStuff;
2 | using System.Numerics;
3 | using HarmonyLib;
4 | using Unity.Netcode;
5 | using UnityEngine;
6 | using System.Reflection;
7 | using System;
8 | using System.Collections.Generic;
9 | using Netcode.Transports.Facepunch;
10 | using System.Linq;
11 | using System.Reflection.Emit;
12 | using UnityEngine.Audio;
13 | using Steamworks.Ugc;
14 | using UnityEngine.Assertions;
15 | using TMPro;
16 | using Steamworks.Data;
17 | using Steamworks;
18 | using BepInEx;
19 | using UnityEngine.UIElements;
20 | using System.Text.RegularExpressions;
21 | using Dissonance.Integrations.Unity_NFGO;
22 | using Unity.Collections;
23 | using System.Runtime.CompilerServices;
24 | using UnityEngine.SceneManagement;
25 | using UnityEngine.UI;
26 | using Dissonance;
27 |
28 | namespace BiggerLobby.Patches
29 | {
30 | [HarmonyPatch]
31 | internal class PlayerObjects
32 | {
33 | [HarmonyPatch(typeof(StartOfRound), "Awake")]
34 | [HarmonyPrefix]
35 | public static void ResizeLists(ref StartOfRound __instance)
36 | {
37 | __instance.allPlayerObjects = Helper.ResizeArray(__instance.allPlayerObjects, Plugin.MaxPlayers);
38 | __instance.allPlayerScripts = Helper.ResizeArray(__instance.allPlayerScripts, Plugin.MaxPlayers);
39 | __instance.gameStats.allPlayerStats = Helper.ResizeArray(__instance.gameStats.allPlayerStats, Plugin.MaxPlayers);
40 | __instance.playerSpawnPositions = Helper.ResizeArray(__instance.playerSpawnPositions, Plugin.MaxPlayers);
41 | for (int j = 4; j < Plugin.MaxPlayers; j++)
42 | {
43 | __instance.gameStats.allPlayerStats[j] = new PlayerStats();
44 | __instance.playerSpawnPositions[j] = __instance.playerSpawnPositions[0];
45 | }
46 | }
47 | [HarmonyPatch(typeof(ForestGiantAI), "Start")]
48 | [HarmonyPrefix]
49 | public static bool ResizeLists2(ref ForestGiantAI __instance)
50 | {
51 | __instance.playerStealthMeters = Helper.ResizeArray(__instance.playerStealthMeters, Plugin.MaxPlayers);
52 | return(true);
53 | }
54 | [HarmonyPatch(typeof(HUDManager), "Awake")]
55 | [HarmonyPostfix]
56 | public static void ResizeLists2(ref HUDManager __instance)
57 | {
58 | // __instance.endgameStatsAnimator = UnityEngine.Object.Instantiate(__instance.endgameStatsAnimator);//lmao trolled
59 | // NOTE: __instance.playerLevels.Length is 5 by default!
60 | // __instance.playerLevels = Helper.ResizeArray(__instance.playerLevels, Plugin.MaxPlayers + 1);
61 | // for (int j = 4; j < Plugin.MaxPlayers; j++)
62 | // {
63 | // __instance.playerLevels[j] = new PlayerLevel();
64 | //}
65 | }
66 |
67 | [HarmonyPatch(typeof(SoundManager), "Awake")]
68 | [HarmonyPostfix]
69 | public static void SoundWake(ref SoundManager __instance)
70 | {
71 | __instance.playerVoiceMixers = Helper.ResizeArray(__instance.playerVoiceMixers, Plugin.MaxPlayers);
72 | for (int j = 0; j < Plugin.MaxPlayers; j++)
73 | {
74 | __instance.playerVoiceMixers[j] = __instance.diageticMixer.outputAudioMixerGroup;
75 |
76 | }
77 | }
78 |
79 | [HarmonyPatch(typeof(SoundManager), "Start")]
80 | [HarmonyPostfix]
81 | public static void ResizeSoundManagerLists(ref SoundManager __instance)
82 | {
83 | __instance. playerVoicePitchLerpSpeed = new float[Plugin.MaxPlayers + 1];
84 | __instance.playerVoicePitchTargets = new float[Plugin.MaxPlayers + 1];
85 | __instance.playerVoiceVolumes = new float[Plugin.MaxPlayers + 1];
86 | __instance.playerVoicePitches = new float[Plugin.MaxPlayers+1];
87 | for (int i = 1; i < Plugin.MaxPlayers+1; i++)
88 | {
89 | __instance.playerVoicePitchLerpSpeed[i] = 3f;
90 | __instance.playerVoicePitchTargets[i] = 1f;
91 | __instance.playerVoicePitches[i] = 1f;
92 | __instance.playerVoiceVolumes[i] = 1f;
93 | }
94 | }
95 | private static StartOfRound startOfRound;
96 | private static bool instantiating = false;
97 | private static int nextClientId = 0;
98 | private static PlayerControllerB referencePlayer;
99 | [HarmonyPatch(typeof(EnemyAI),"EnableEnemyMesh")]
100 | [HarmonyPrefix]
101 | public static bool EnableEnemyMesh(EnemyAI __instance, bool enable, bool overrideDoNotSet = false)
102 | {
103 | int layer = ((!enable) ? 23 : 19);
104 | for (int i = 0; i < __instance.skinnedMeshRenderers.Length; i++)
105 | {
106 | if (__instance.skinnedMeshRenderers[i] && (!__instance.skinnedMeshRenderers[i].CompareTag("DoNotSet") || overrideDoNotSet))
107 | {
108 | __instance.skinnedMeshRenderers[i].gameObject.layer = layer;
109 | }
110 | }
111 | for (int j = 0; j < __instance.meshRenderers.Length; j++)
112 | {
113 | if (__instance.meshRenderers[j] && (!__instance.meshRenderers[j].CompareTag("DoNotSet") || overrideDoNotSet))
114 | {
115 | __instance.meshRenderers[j].gameObject.layer = layer;
116 | }
117 | }
118 | return (false);
119 | }
120 | [HarmonyPatch(typeof(ShipTeleporter), "Awake")]
121 | [HarmonyPrefix]
122 | public static bool Awake2(ShipTeleporter __instance)
123 | {
124 | int[] playersBeingTeleported2 = new int[Plugin.MaxPlayers];
125 | for (int i = 0; i < Plugin.MaxPlayers; i++)
126 | {
127 | playersBeingTeleported2[i] = -1;
128 | }
129 | typeof(ShipTeleporter).GetField("playersBeingTeleported", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, playersBeingTeleported2);
130 | __instance.buttonTrigger.interactable = false;
131 | typeof(ShipTeleporter).GetField("cooldownTime", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, __instance.cooldownAmount);
132 | return false;
133 | }
134 | [HarmonyPatch(typeof(NetworkSceneManager), "PopulateScenePlacedObjects")]
135 | [HarmonyPrefix]
136 | public static bool AddPlayers(NetworkSceneManager __instance)
137 | {
138 | startOfRound = StartOfRound.Instance;
139 | if (startOfRound.allPlayerObjects[Plugin.MaxPlayers - 1] != null)
140 | {
141 | return(true);
142 | }
143 | referencePlayer = startOfRound.allPlayerObjects[0].GetComponent();
144 | var playerPrefab = startOfRound.playerPrefab;
145 | var playerContainer = startOfRound.playersContainer.transform;
146 | FieldInfo GlobalObjectIdHash = (typeof(NetworkObject).GetField("GlobalObjectIdHash", BindingFlags.NonPublic | BindingFlags.Instance));
147 | PropertyInfo NetworkObjectId = (typeof(NetworkObject).GetProperty("NetworkObjectId", BindingFlags.Public | BindingFlags.Instance));
148 | FieldInfo SceneObjs = (typeof(NetworkSceneManager).GetField("ScenePlacedObjects", BindingFlags.NonPublic | BindingFlags.Instance));
149 | instantiating = true;
150 | var spawnMethod = typeof(NetworkSpawnManager).GetMethod(
151 | "SpawnNetworkObjectLocally",
152 | BindingFlags.Instance | BindingFlags.NonPublic,
153 | null,
154 | CallingConventions.Any,
155 | new Type[] { typeof(NetworkObject), typeof(ulong), typeof(bool), typeof(bool), typeof(ulong), typeof(bool) },
156 | null
157 | );
158 | for (int i = 4; i < Plugin.MaxPlayers; i++)
159 | {
160 |
161 | nextClientId = i;
162 | var newPlayer = GameObject.Instantiate(playerPrefab, playerContainer);
163 | var newScript = newPlayer.GetComponent();
164 | var netObject = newPlayer.GetComponent();
165 | var plrphysbox = newPlayer.transform.Find("PlayerPhysicsBox").gameObject.GetComponent();
166 | var itemholder = newPlayer.transform.Find("ScavengerModel/metarig/ScavengerModelArmsOnly/metarig/spine.003/shoulder.R/arm.R_upper/arm.R_lower/hand.R/LocalItemHolder").gameObject.GetComponent();
167 | var itemholder2 = newPlayer.transform.Find("ScavengerModel/metarig/spine/spine.001/spine.002/spine.003/shoulder.R/arm.R_upper/arm.R_lower/hand.R/ServerItemHolder").gameObject.GetComponent();
168 | newScript.TeleportPlayer(StartOfRound.Instance.notSpawnedPosition.position);
169 | startOfRound.allPlayerObjects[i] = newPlayer;
170 | startOfRound.allPlayerScripts[i] = newScript;
171 | uint hash = 6942069u + (uint)i;
172 | ulong hash2 = 6942069ul + (ulong)i;
173 | uint hash3 = 123456789u + (uint)i;
174 | uint hash4 = 987654321u + (uint)i;
175 | uint hash5 = 124585949u + (uint)i;
176 | ulong hash6 = 123456789ul + (ulong)i;
177 | ulong hash7 = 987654321ul + (ulong)i;
178 | ulong hash8 = 124585949ul + (ulong)i;
179 | int handle = netObject.gameObject.scene.handle;
180 | GlobalObjectIdHash.SetValue(netObject, hash);
181 | NetworkObjectId.SetValue(netObject, hash2);
182 | GlobalObjectIdHash.SetValue(plrphysbox, hash3);
183 | NetworkObjectId.SetValue(plrphysbox, hash6);
184 | GlobalObjectIdHash.SetValue(itemholder, hash4);
185 | NetworkObjectId.SetValue(itemholder, hash7);
186 | GlobalObjectIdHash.SetValue(itemholder2, hash5);
187 | NetworkObjectId.SetValue(itemholder2, hash8);
188 | ManualCameraRenderer[] deezlist = UnityEngine.Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None);
189 | for (int j = 0; j < deezlist.Length; j++)
190 | {
191 | ManualCameraRenderer CR = deezlist[j];
192 | CR.AddTransformAsTargetToRadar(newScript.transform, "Player #" + j.ToString(), false);
193 | }
194 | }
195 | instantiating = false;
196 | return (true);
197 | }
198 | [HarmonyPatch(typeof(QuickMenuManager), "AddUserToPlayerList")]
199 | [HarmonyPrefix]
200 | public static bool AddUserToPlayerList(QuickMenuManager __instance, ulong steamId, string playerName, int playerObjectId)
201 | {
202 | if (playerObjectId >= 0 && playerObjectId <= Plugin.MaxPlayers)
203 | {
204 | __instance.playerListSlots[playerObjectId].KickUserButton.SetActive(StartOfRound.Instance.IsServer);
205 | __instance.playerListSlots[playerObjectId].slotContainer.SetActive(value: true);
206 | __instance.playerListSlots[playerObjectId].isConnected = true;
207 | __instance.playerListSlots[playerObjectId].playerSteamId = steamId;
208 | __instance.playerListSlots[playerObjectId].usernameHeader.text = playerName.Replace("bizzlemip", "bizzlemip"); ;
209 | if (GameNetworkManager.Instance.localPlayerController != null)
210 | {
211 | __instance.playerListSlots[playerObjectId].volumeSliderContainer.SetActive(playerObjectId != (int)GameNetworkManager.Instance.localPlayerController.playerClientId);
212 | }
213 | }
214 | return (false);
215 | }
216 | [HarmonyPatch(typeof(QuickMenuManager), "Update")]
217 | [HarmonyPrefix]
218 | private static bool Update(QuickMenuManager __instance)
219 | {
220 | for (int i = 0; i < __instance.playerListSlots.Length; i++)
221 | {
222 | if (__instance.playerListSlots[i].isConnected)
223 | {
224 | float num = __instance.playerListSlots[i].volumeSlider.value / __instance.playerListSlots[i].volumeSlider.maxValue;
225 | if (num == -1f)
226 | {
227 | SoundManager.Instance.playerVoiceVolumes[i] = -1f;//-70f;//dude what the fuck? why? dont do that (joke)
228 | }
229 | else
230 | {
231 | SoundManager.Instance.playerVoiceVolumes[i] = num;
232 | }
233 | }
234 | }
235 | return (false);
236 | }
237 | [HarmonyPatch(typeof(QuickMenuManager), "Start")]
238 | [HarmonyPrefix]
239 |
240 | public static bool FixPlayerList(ref QuickMenuManager __instance)
241 | {
242 | GameObject OldMask = null;
243 | GameObject ogparent = __instance.playerListPanel.transform.Find("Image").gameObject;
244 | if (ogparent.transform.Find("Mask"))
245 | {
246 | OldMask = ogparent.transform.Find("Mask").gameObject;
247 | }
248 | GameObject Mask = new GameObject("Mask");
249 | GameObject newFrame = new GameObject("ScrollViewport");
250 | GameObject bgCollision = new GameObject("BGCollision");
251 | GameObject newFrame2 = new GameObject("ScrollContent");
252 | Mask.transform.SetParent(ogparent.transform);
253 | newFrame.transform.SetParent(Mask.transform);
254 | bgCollision.transform.SetParent(newFrame.transform);
255 | newFrame2.transform.SetParent(newFrame.transform);
256 | Mask.transform.localScale = UnityEngine.Vector3.one;
257 | newFrame.transform.localScale = UnityEngine.Vector3.one;
258 | newFrame2.transform.localScale = UnityEngine.Vector3.one;
259 | Mask.AddComponent().sizeDelta = new UnityEngine.Vector2(300, 280f);
260 | Mask.transform.localPosition = new UnityEngine.Vector3(-10, 110, 0);
261 | newFrame.transform.localPosition = new UnityEngine.Vector3(0, -10, 0);
262 | newFrame2.AddComponent().pivot = new UnityEngine.Vector2(0.5f, 1);
263 | Mask.GetComponent().pivot = new UnityEngine.Vector2(0.5f, 1);
264 | Mask.transform.localPosition = new UnityEngine.Vector3(-10, 110, 0);
265 | Mask.AddComponent();
266 | VerticalLayoutGroup VLG = newFrame2.AddComponent();
267 | ContentSizeFitter CSF = newFrame2.AddComponent();
268 | ScrollRect SR = newFrame.AddComponent();
269 | SR.viewport = newFrame.AddComponent();
270 | SR.content = newFrame2.GetComponent();
271 | SR.horizontal = false;
272 | UnityEngine.UI.Image image = bgCollision.AddComponent();
273 | bgCollision.GetComponent().anchorMin = new UnityEngine.Vector2(0, 0);
274 | bgCollision.GetComponent().anchorMax = new UnityEngine.Vector2(1, 1);
275 | image.color = new UnityEngine.Color(255, 255, 255, 0);
276 | VLG.spacing = 50;
277 | CSF.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
278 | CSF.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
279 | __instance.playerListSlots = Helper.ResizeArray(__instance.playerListSlots, Plugin.MaxPlayers);
280 | for (int i = 0; i < Plugin.MaxPlayers; i++)
281 | {
282 | if (i < 4)
283 | {
284 | __instance.playerListSlots[i].slotContainer.transform.SetParent(newFrame2.transform);
285 | }
286 | else
287 | {
288 | PlayerListSlot NewSlot = new PlayerListSlot();
289 | GameObject NewSlot2 = UnityEngine.Object.Instantiate(__instance.playerListSlots[0].slotContainer);
290 | NewSlot.slotContainer = NewSlot2;
291 | NewSlot.volumeSliderContainer = NewSlot2.transform.Find("VoiceVolumeSlider").gameObject;
292 | NewSlot.KickUserButton = NewSlot2.transform.Find("KickButton").gameObject;
293 | QuickMenuManager yeahoriginal = __instance;
294 | int localI = i;
295 | NewSlot.KickUserButton.GetComponent().onClick.AddListener(() => {
296 | yeahoriginal.KickUserFromServer(localI);
297 | });
298 | NewSlot.isConnected = false;
299 | NewSlot.usernameHeader = NewSlot2.transform.Find("PlayerNameButton").Find("PName").gameObject.GetComponent();
300 | NewSlot.volumeSlider = NewSlot2.transform.Find("VoiceVolumeSlider").Find("Slider").gameObject.GetComponent();
301 | NewSlot.playerSteamId = __instance.playerListSlots[0].playerSteamId;
302 | NewSlot2.transform.SetParent(newFrame2.transform,false);
303 | __instance.playerListSlots[i] = NewSlot;
304 | }
305 |
306 | }
307 | if (OldMask != null){
308 | GameObject.Destroy(OldMask);
309 | }
310 | return (true);
311 | }
312 |
313 | [HarmonyPatch(typeof(ManualCameraRenderer), "Awake")]
314 | [HarmonyPrefix]
315 |
316 | public static bool Mawake(ref ManualCameraRenderer __instance)
317 | {
318 | for (int i = 0; i < 4; i++)
319 | {
320 | __instance.radarTargets.Add(new TransformAndName(StartOfRound.Instance.allPlayerScripts[i].transform, StartOfRound.Instance.allPlayerScripts[i].playerUsername));
321 | }
322 | __instance.targetTransformIndex = 0;
323 | __instance.targetedPlayer = StartOfRound.Instance.allPlayerScripts[0];
324 | return (false);
325 | }//I got a glock in my rari
326 |
327 | [HarmonyPatch(typeof(PlayerControllerB), "Awake")]
328 | [HarmonyPrefix]
329 |
330 | public static bool FixPlayerObject(ref PlayerControllerB __instance)
331 | {
332 | if (!instantiating) return (true);
333 | __instance.gameObject.name = $"ExtraPlayer{nextClientId}";
334 | __instance.playerClientId = (ulong)nextClientId;
335 | __instance.actualClientId = (ulong)nextClientId;
336 |
337 | StartOfRound.Instance.allPlayerObjects[nextClientId] = __instance.transform.parent.gameObject;
338 | StartOfRound.Instance.allPlayerScripts[nextClientId] = __instance;
339 | var fields = typeof(PlayerControllerB).GetFields();
340 | foreach (FieldInfo field in fields)
341 | {
342 | var myValue = field.GetValue(__instance);
343 | var referenceValue = field.GetValue(referencePlayer);
344 | if (myValue == null && referenceValue != null)
345 | field.SetValue(__instance, referenceValue);
346 | }
347 | __instance.enabled = true;
348 | return (true);
349 | }
350 |
351 | [HarmonyPatch(typeof(StartOfRound), "GetPlayerSpawnPosition")]
352 | [HarmonyTranspiler]
353 |
354 | public static IEnumerable GetPlayerSpawnPosition(IEnumerable instructions)
355 | {
356 | var codes = new List(instructions);
357 | codes[0].opcode = OpCodes.Ldc_I4_1;
358 | return codes.AsEnumerable();
359 | }
360 | }
361 | }
--------------------------------------------------------------------------------
/Patches/NonGamePatches.cs:
--------------------------------------------------------------------------------
1 | using BepInEx;
2 | using HarmonyLib;
3 | using Steamworks.Data;
4 | using Steamworks;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Text;
8 | using System.Reflection;
9 | using Unity.Netcode;
10 | using UnityEngine;
11 | using System.Text.RegularExpressions;
12 | using TMPro;
13 | using System.Security.Cryptography;
14 | using LC_API;
15 | using System.Security.Permissions;
16 | using UnityEngine.SceneManagement;
17 | using System.Linq;
18 | using GameNetcodeStuff;
19 | using System.Runtime.CompilerServices;
20 | using System.Collections;
21 | using BiggerLobby.UI;
22 | using BiggerLobby.Models;
23 |
24 | namespace BiggerLobby.Patches
25 | {
26 | [HarmonyPatch]
27 | public class NonGamePatches
28 | {
29 | private static PropertyInfo _playbackVolumeProperty = typeof(Dissonance.Audio.Playback.VoicePlayback).GetInterface("IVoicePlaybackInternal").GetProperty("PlaybackVolume");
30 | private static FieldInfo _lobbyListField = AccessTools.Field(typeof(SteamLobbyManager), "currentLobbyList");
31 |
32 | [HarmonyPatch(typeof(StartOfRound), "UpdatePlayerVoiceEffects")]
33 | [HarmonyPrefix]
34 | public static void UpdatePlayerVoiceEffects(StartOfRound __instance)
35 | {
36 | if (GameNetworkManager.Instance == null || GameNetworkManager.Instance.localPlayerController == null)
37 | {
38 | return;
39 | }
40 | (typeof(StartOfRound)).GetField("updatePlayerVoiceInterval", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance,2f);
41 | PlayerControllerB playerControllerB = ((!GameNetworkManager.Instance.localPlayerController.isPlayerDead || !(GameNetworkManager.Instance.localPlayerController.spectatedPlayerScript != null)) ? GameNetworkManager.Instance.localPlayerController : GameNetworkManager.Instance.localPlayerController.spectatedPlayerScript);
42 | for (int i = 0; i < __instance.allPlayerScripts.Length; i++)
43 | {
44 | PlayerControllerB playerControllerB2 = __instance.allPlayerScripts[i];
45 | if ((!playerControllerB2.isPlayerControlled && !playerControllerB2.isPlayerDead) || playerControllerB2 == GameNetworkManager.Instance.localPlayerController)
46 | {
47 | continue;
48 | }
49 | if (playerControllerB2.voicePlayerState == null || playerControllerB2.currentVoiceChatIngameSettings._playerState == null || playerControllerB2.currentVoiceChatAudioSource == null)
50 | {
51 | __instance.RefreshPlayerVoicePlaybackObjects();
52 | if (playerControllerB2.voicePlayerState == null || playerControllerB2.currentVoiceChatAudioSource == null)
53 | {
54 | Debug.Log($"Was not able to access voice chat object for player #{i}; {playerControllerB2.voicePlayerState == null}; {playerControllerB2.currentVoiceChatAudioSource == null}");
55 | continue;
56 | }
57 | }
58 | AudioSource currentVoiceChatAudioSource = __instance.allPlayerScripts[i].currentVoiceChatAudioSource;
59 | bool flag = playerControllerB2.speakingToWalkieTalkie && playerControllerB.holdingWalkieTalkie && playerControllerB2 != playerControllerB;
60 | if (playerControllerB2.isPlayerDead)
61 | {
62 | currentVoiceChatAudioSource.GetComponent().enabled = false;
63 | currentVoiceChatAudioSource.GetComponent().enabled = false;
64 | currentVoiceChatAudioSource.panStereo = 0f;
65 | SoundManager.Instance.playerVoicePitchTargets[playerControllerB2.playerClientId] = 1f;
66 | SoundManager.Instance.SetPlayerPitch(1f, (int)playerControllerB2.playerClientId);
67 | if (GameNetworkManager.Instance.localPlayerController.isPlayerDead)
68 | {
69 | currentVoiceChatAudioSource.spatialBlend = 0f;
70 | playerControllerB2.currentVoiceChatIngameSettings.set2D = true;
71 | if (playerControllerB2.currentVoiceChatIngameSettings != null && playerControllerB2.currentVoiceChatIngameSettings._playbackComponent != null)
72 | {
73 | _playbackVolumeProperty.SetValue(playerControllerB2.currentVoiceChatIngameSettings._playbackComponent, Mathf.Clamp((SoundManager.Instance.playerVoiceVolumes[i] + 1) * (2 * Plugin._LoudnessMultiplier.Value), 0f, 1f));
74 | }
75 | }
76 | else
77 | {
78 | currentVoiceChatAudioSource.spatialBlend = 1f;
79 | playerControllerB2.currentVoiceChatIngameSettings.set2D = false;
80 | //playerControllerB2.voicePlayerState.Volume = 0f;
81 | if (playerControllerB2.currentVoiceChatIngameSettings != null && playerControllerB2.currentVoiceChatIngameSettings._playbackComponent != null)
82 | {
83 | _playbackVolumeProperty.SetValue(playerControllerB2.currentVoiceChatIngameSettings._playbackComponent, 0);
84 | }
85 | }
86 | continue;
87 | }
88 | AudioLowPassFilter component = currentVoiceChatAudioSource.GetComponent();
89 | OccludeAudio component2 = currentVoiceChatAudioSource.GetComponent();
90 | component.enabled = true;
91 | component2.overridingLowPass = flag || __instance.allPlayerScripts[i].voiceMuffledByEnemy;
92 | currentVoiceChatAudioSource.GetComponent().enabled = flag;
93 | if (!flag)
94 | {
95 | currentVoiceChatAudioSource.spatialBlend = 1f;
96 | playerControllerB2.currentVoiceChatIngameSettings.set2D = false;
97 | currentVoiceChatAudioSource.bypassListenerEffects = false;
98 | currentVoiceChatAudioSource.bypassEffects = false;
99 | currentVoiceChatAudioSource.outputAudioMixerGroup = SoundManager.Instance.playerVoiceMixers[playerControllerB2.playerClientId];
100 | component.lowpassResonanceQ = 1f;
101 | }
102 | else
103 | {
104 | currentVoiceChatAudioSource.spatialBlend = 0f;
105 | playerControllerB2.currentVoiceChatIngameSettings.set2D = true;
106 | if (GameNetworkManager.Instance.localPlayerController.isPlayerDead)
107 | {
108 | currentVoiceChatAudioSource.panStereo = 0f;
109 | currentVoiceChatAudioSource.outputAudioMixerGroup = SoundManager.Instance.playerVoiceMixers[playerControllerB2.playerClientId];
110 | currentVoiceChatAudioSource.bypassListenerEffects = false;
111 | currentVoiceChatAudioSource.bypassEffects = false;
112 | }
113 | else
114 | {
115 | currentVoiceChatAudioSource.panStereo = 0.4f;
116 | currentVoiceChatAudioSource.bypassListenerEffects = false;
117 | currentVoiceChatAudioSource.bypassEffects = false;
118 | currentVoiceChatAudioSource.outputAudioMixerGroup = SoundManager.Instance.playerVoiceMixers[playerControllerB2.playerClientId];
119 | }
120 | component2.lowPassOverride = 4000f;
121 | component.lowpassResonanceQ = 3f;
122 | }
123 | /*if (GameNetworkManager.Instance.localPlayerController.isPlayerDead)
124 | {
125 | playerControllerB2.voicePlayerState.Volume = 0.8f;
126 | }
127 | else
128 | {*/
129 | if (playerControllerB2.currentVoiceChatIngameSettings != null && playerControllerB2.currentVoiceChatIngameSettings._playbackComponent != null)
130 | {
131 | _playbackVolumeProperty.SetValue(playerControllerB2.currentVoiceChatIngameSettings._playbackComponent, Mathf.Clamp((SoundManager.Instance.playerVoiceVolumes[i] + 1) * (2 * Plugin._LoudnessMultiplier.Value), 0f, 1f));
132 | }
133 | }
134 | }
135 | [HarmonyPatch(typeof(StartOfRound), "Awake")]
136 | [HarmonyPrefix]
137 | public static void ResizeLists(ref StartOfRound __instance)
138 | {
139 | __instance.allPlayerObjects = Helper.ResizeArray(__instance.allPlayerObjects, Plugin.MaxPlayers);
140 | __instance.allPlayerScripts = Helper.ResizeArray(__instance.allPlayerScripts, Plugin.MaxPlayers);
141 | __instance.gameStats.allPlayerStats = Helper.ResizeArray(__instance.gameStats.allPlayerStats, Plugin.MaxPlayers);
142 | __instance.playerSpawnPositions = Helper.ResizeArray(__instance.playerSpawnPositions, Plugin.MaxPlayers);
143 | for (int j = 4; j < Plugin.MaxPlayers; j++)
144 | {
145 | __instance.gameStats.allPlayerStats[j] = new PlayerStats();
146 | __instance.playerSpawnPositions[j] = __instance.playerSpawnPositions[0];
147 | }
148 | }
149 |
150 | [HarmonyPatch(typeof(HUDManager), "Awake")]
151 | [HarmonyPrefix]
152 | public static void ResizeHUD(ref HUDManager __instance)
153 | {
154 | var expandedStats = ExpandedStatsUI.GetFromAnimator(__instance.endgameStatsAnimator);
155 | }
156 |
157 | [HarmonyPatch(typeof(SoundManager), "SetPlayerVoiceFilters")]
158 | [HarmonyPrefix]
159 | public static bool SetPlayerVoiceFilters(ref SoundManager __instance)
160 | {
161 | for (int j = 0; j < StartOfRound.Instance.allPlayerScripts.Length; j++)
162 | {
163 | if (!StartOfRound.Instance.allPlayerScripts[j].isPlayerControlled && !StartOfRound.Instance.allPlayerScripts[j].isPlayerDead)
164 | {
165 | __instance.playerVoicePitches[j] = 1f;
166 | __instance.playerVoiceVolumes[j] = 1f;
167 | continue;
168 | }
169 | if (StartOfRound.Instance.allPlayerScripts[j].voicePlayerState != null) {
170 | (typeof(Dissonance.Audio.Playback.VoicePlayback).GetProperty("Dissonance.Audio.Playback.IVoicePlaybackInternal.PlaybackVolume", BindingFlags.NonPublic | BindingFlags.Instance)).SetValue(StartOfRound.Instance.allPlayerScripts[j].currentVoiceChatIngameSettings._playbackComponent, Mathf.Clamp((SoundManager.Instance.playerVoiceVolumes[j] + 1) * (2 * Plugin._LoudnessMultiplier.Value), 0f, 1f));
171 | }
172 | if (Mathf.Abs(__instance.playerVoicePitches[j] - __instance.playerVoicePitchTargets[j]) > 0.025f)
173 | {
174 | __instance.playerVoicePitches[j] = Mathf.Lerp(__instance.playerVoicePitches[j], __instance.playerVoicePitchTargets[j], 3f * Time.deltaTime);
175 | }
176 | else if (__instance.playerVoicePitches[j] != __instance.playerVoicePitchTargets[j])
177 | {
178 | __instance.playerVoicePitches[j] = __instance.playerVoicePitchTargets[j];
179 | }
180 | }
181 | return (false);
182 | }
183 | [HarmonyPatch(typeof(MenuManager), "OnEnable")]
184 | [HarmonyPostfix]
185 | [HarmonyWrapSafe]
186 | public static void CustomMenu(ref MenuManager __instance)
187 | {
188 | if (__instance.isInitScene)
189 | {
190 | return;
191 | }
192 |
193 | float spacingAmount = 30f;
194 |
195 | // Get references to all main menu UI objects we need to modify
196 | var hostSettingsContainer = __instance.HostSettingsOptionsNormal.transform.parent.parent.gameObject?.GetComponent();
197 | var lobbyHostOptions = hostSettingsContainer?.Find("LobbyHostOptions")?.GetComponent();
198 | var optionsNormal = lobbyHostOptions?.Find("OptionsNormal")?.GetComponent();
199 |
200 | if (hostSettingsContainer == null || lobbyHostOptions == null || optionsNormal == null)
201 | return;
202 |
203 | var serverNameField = optionsNormal?.Find("ServerNameField")?.GetComponent();
204 | var serverTagInputField = optionsNormal?.Find("ServerTagInputField")?.GetComponent();
205 | var confirmButton = hostSettingsContainer?.Find("Confirm")?.GetComponent();
206 | var backButton = hostSettingsContainer?.Find("Back")?.GetComponent();
207 | var privatePublicDescription = hostSettingsContainer?.Find("PrivatePublicDescription")?.GetComponent();
208 |
209 | if (serverNameField == null || serverTagInputField == null || confirmButton == null || backButton == null || privatePublicDescription == null)
210 | return;
211 |
212 | // Create new field for user-inputted player counts
213 | var playerNumberField = UnityEngine.Object.Instantiate(serverNameField, serverNameField.transform.parent);
214 | var playerNumberInputField = playerNumberField.GetComponent();
215 |
216 | // Resize and reposition UI elements to fit the new field
217 | hostSettingsContainer.sizeDelta = hostSettingsContainer.sizeDelta + new Vector2(0, spacingAmount / 2f);
218 | lobbyHostOptions.anchoredPosition = lobbyHostOptions.anchoredPosition + new Vector2(0, spacingAmount / 4f);
219 | serverTagInputField.anchoredPosition = serverTagInputField.anchoredPosition + new Vector2(0, -spacingAmount / 2f);
220 | confirmButton.anchoredPosition = confirmButton.anchoredPosition + new Vector2(0, -spacingAmount / 2f);
221 | backButton.anchoredPosition = backButton.anchoredPosition + new Vector2(0, -spacingAmount / 2f);
222 |
223 | foreach (RectTransform transform in optionsNormal)
224 | {
225 | if (transform == null || transform.name == "EnterAName" || transform == serverNameField || transform == serverTagInputField)
226 | continue;
227 |
228 | transform.anchoredPosition = transform.anchoredPosition + new Vector2(0, -spacingAmount);
229 | // Specifically make the tag
230 | }
231 |
232 | // Finish setting up custom players field
233 | // TODO: Clean this bit up, it's still mostly legacy code
234 | playerNumberField.name = "ServerPlayersField";
235 | playerNumberInputField.contentType = TMP_InputField.ContentType.IntegerNumber;
236 | playerNumberField.transform.Find("Text Area").Find("Placeholder").gameObject.GetComponent().text = "Max players (16)...";
237 |
238 | void OnChange()
239 | {
240 | string text = Regex.Replace(playerNumberInputField.text, "[^0-9]", "");
241 | int newnumber;
242 | if (!(int.TryParse(text, out newnumber)))
243 | {
244 | newnumber = 16;
245 | }
246 | newnumber = Math.Min(Math.Max(newnumber, 4), 40);
247 | Plugin.Logger?.LogInfo($"Setting max player count to: {newnumber}");
248 | if (newnumber > 16)
249 | {
250 | privatePublicDescription.text = "Notice: High max player counts\nmay cause lag.";
251 | }
252 | else
253 | {
254 | if (privatePublicDescription.text == "Notice: High max player counts\nmay cause lag.")
255 | {
256 | privatePublicDescription.text = "yeah you should be good now lol";
257 | }
258 | }
259 |
260 | }
261 | playerNumberInputField.onValueChanged.AddListener(delegate { OnChange(); });
262 | }
263 | [HarmonyPatch(typeof(MenuManager), "StartHosting")]
264 | [HarmonyPrefix]
265 | public static bool StartHost(MenuManager __instance)
266 | {
267 | if (GameNetworkManager.Instance.currentLobby == null)
268 | {
269 | return (true);
270 | }
271 | GameObject SPF = __instance.HostSettingsOptionsNormal.transform.Find("ServerPlayersField").gameObject;
272 | GameObject Input = SPF.transform.Find("Text Area").Find("Text").gameObject;
273 | TextMeshProUGUI iTextMeshProUGUI = Input.GetComponent();
274 | string text = Regex.Replace(iTextMeshProUGUI.text, "[^0-9]", "");
275 | int newnumber;
276 | if (!(int.TryParse(text, out newnumber)))
277 | {
278 | newnumber = 16;
279 | }
280 | newnumber = Math.Min(Math.Max(newnumber, 4), 40);
281 | Lobby lobby = GameNetworkManager.Instance.currentLobby ?? new Lobby();
282 | lobby.SetData("MaxPlayers", newnumber.ToString());
283 | Plugin.Logger?.LogInfo($"Setting max players to {newnumber}!");
284 | Plugin.MaxPlayers = newnumber;
285 | if (GameNetworkManager.Instance != null) GameNetworkManager.Instance.maxAllowedPlayers = Plugin.MaxPlayers;
286 | return (true);
287 | }
288 |
289 | [HarmonyPatch(typeof(HUDManager), "FillEndGameStats")]
290 | [HarmonyPrefix]
291 | public static void FillEndGameStats(HUDManager __instance)
292 | {
293 | // Modify the arrays to our liking
294 | // This is probably unnecessary to run every time, and could just be initalized when the player count changes
295 | var expandedStats = ExpandedStatsUI.GetFromAnimator(__instance.endgameStatsAnimator);
296 | if (expandedStats == null || StartOfRound.Instance == null) return; // something has gone terribly wrong
297 | var statsList = expandedStats.GetStatsListFromPlayerCount(Plugin.GetRealPlayerScripts(StartOfRound.Instance).Length);
298 |
299 | __instance.statsUIElements.playerNamesText = statsList.Names.ToArray();
300 | __instance.statsUIElements.playerStates = statsList.States.ToArray();
301 | __instance.statsUIElements.playerNotesText = statsList.Notes.ToArray();
302 |
303 | Plugin.Logger?.LogInfo("Adding EXPANDED stats!");
304 | }
305 |
306 | [HarmonyPatch(typeof(HUDManager), "FillEndGameStats")]
307 | [HarmonyPostfix]
308 | public static void FillEndGameStatsPostfix(HUDManager __instance)
309 | {
310 | // Make notes smaller for 5+ players so we can fit everything on the screen
311 | if (StartOfRound.Instance == null) return;
312 | var playerCount = Plugin.GetRealPlayerScripts(StartOfRound.Instance).Length;
313 | if (playerCount > 4)
314 | {
315 | foreach (var notesText in __instance.statsUIElements.playerNotesText)
316 | {
317 | if (notesText.text == "") continue;
318 | notesText.text = notesText.text.Replace("Notes:", "").Trim();
319 | }
320 |
321 | var replacementCheckmark = ExpandedStatsUI.GetReplacementCheckmark();
322 | if (replacementCheckmark == null) return;
323 | foreach (var playerState in __instance.statsUIElements.playerStates)
324 | {
325 | if (playerState.sprite != __instance.statsUIElements.aliveIcon) continue;
326 | playerState.sprite = replacementCheckmark;
327 | }
328 | }
329 | }
330 |
331 | [HarmonyPatch(typeof(GameNetworkManager),"StartHost")]
332 | [HarmonyPrefix]
333 | public static bool DoTheThe()
334 | {
335 | Plugin.CustomNetObjects.Clear();
336 | return (true);
337 | }
338 | [HarmonyPatch(typeof(GameNetworkManager), "StartClient")]
339 | [HarmonyPrefix]
340 | public static bool StartClient(GameNetworkManager __instance)
341 | {
342 | Plugin.CustomNetObjects.Clear();
343 | return (true);
344 | }
345 | [HarmonyPatch(typeof(MenuManager), "StartAClient")]
346 | [HarmonyPrefix]
347 | public static bool StartAClient()
348 | {
349 | Plugin.CustomNetObjects.Clear();
350 | Plugin.Logger?.LogInfo("LAN Running.");
351 | return (true);
352 | }
353 | [HarmonyPatch(typeof(SteamLobbyManager), "loadLobbyListAndFilter")]
354 | [HarmonyPostfix]
355 | public static IEnumerator LoadLobbyListAndFilter(IEnumerator result, SteamLobbyManager __instance)
356 | {
357 | // Run original enumerator code
358 | while (result.MoveNext())
359 | yield return result.Current;
360 |
361 | // Ideally this would happen as the enumerator executes but I just want to get something working RN
362 | // inject into existing server list
363 | Plugin.Logger?.LogInfo("Injecting BL playercounts into lobby list.");
364 | LobbySlot[] lobbySlots = __instance.levelListContainer.GetComponentsInChildren(true);
365 |
366 | foreach(var lobbySlot in lobbySlots)
367 | {
368 | try
369 | {
370 | // TODO: replace with custom graphic or something neat
371 | // TODO: Make this not count towards the 40 character max. don't wanna fix rn
372 | lobbySlot.LobbyName.text = lobbySlot.LobbyName.text.Replace("[BiggerLobby]", "[BL]");
373 | string text = lobbySlot.thisLobby.GetData("MaxPlayers");
374 | int maxPlayers;
375 | if (!(int.TryParse(text, out maxPlayers)))
376 | {
377 | maxPlayers = 4;
378 | }
379 | lobbySlot.playerCount.text = lobbySlot.playerCount.text.Replace("/ 4", $"/ {maxPlayers}");
380 | }
381 | catch (Exception ex)
382 | {
383 | Plugin.Logger?.LogWarning("Exception while injecting BL lobby metadata:");
384 | Plugin.Logger?.LogWarning(ex);
385 | }
386 | }
387 | }
388 |
389 | [HarmonyPatch(typeof(SteamMatchmaking), nameof(SteamMatchmaking.CreateLobbyAsync))]
390 | [HarmonyPrefix]
391 | public static void SetMaxMembers(ref int maxMembers)
392 | {
393 | maxMembers = Plugin.MaxPlayers;
394 | }
395 | [HarmonyPatch(typeof(GameNetworkManager))]
396 | internal class InternalPatches
397 | {
398 | static MethodInfo TargetMethod()
399 | {
400 | return typeof(GameNetworkManager)
401 | .GetMethod("ConnectionApproval",
402 | BindingFlags.NonPublic | BindingFlags.Instance);
403 | }
404 | [HarmonyPrefix]
405 | static bool PostFix(GameNetworkManager __instance, NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response)
406 | {
407 | Debug.Log("Connection approval callback! Game version of client request: " + Encoding.ASCII.GetString(request.Payload).ToString());
408 | Debug.Log($"Joining client id: {request.ClientNetworkId}; Local/host client id: {NetworkManager.Singleton.LocalClientId}");
409 | if (request.ClientNetworkId == NetworkManager.Singleton.LocalClientId)
410 | {
411 | Debug.Log("Stopped connection approval callback, as the client in question was the host!");
412 | return (false);
413 | }
414 | bool flag = !__instance.disallowConnection;
415 | if (flag)
416 | {
417 | string @string = Encoding.ASCII.GetString(request.Payload);
418 | string[] array = @string.Split(",");
419 | if (string.IsNullOrEmpty(@string))
420 | {
421 | response.Reason = "Unknown; please verify your game files.";
422 | flag = false;
423 | }
424 | else if (__instance.gameHasStarted)
425 | {
426 | response.Reason = "Game has already started!";
427 | flag = false;
428 | }
429 | else if (__instance.gameVersionNum.ToString() != array[0])
430 | {
431 | response.Reason = $"Game version mismatch! Their version: {__instance.gameVersionNum}. Your version: {array[0]}";
432 | flag = false;
433 | }
434 | else if (!__instance.disableSteam && (StartOfRound.Instance == null || array.Length < 2 || StartOfRound.Instance.KickedClientIds.Contains((ulong)Convert.ToInt64(array[1]))))
435 | {
436 | response.Reason = "You cannot rejoin after being kicked.";
437 | flag = false;
438 | }
439 | else if (!(@string.Contains("BiggerLobbyVersion2.5.0")))
440 | {
441 | response.Reason = "You need to have BiggerLobby V2.5.0 to join this server!";
442 | flag = false;
443 | }
444 | }
445 | else
446 | {
447 | response.Reason = "The host was not accepting connections.";
448 | }
449 | Debug.Log($"Approved connection?: {flag}. Connected players #: {__instance.connectedPlayers}");
450 | Debug.Log("Disapproval reason: " + response.Reason);
451 | response.CreatePlayerObject = false;
452 | response.Approved = flag;
453 | response.Pending = false;
454 | return (false);
455 | } //etc
456 | }
457 | [HarmonyPatch(typeof(GameNetworkManager))]
458 | internal class InternalPatches2
459 | {
460 | static MethodInfo TargetMethod()
461 | {
462 | return typeof(GameNetworkManager)
463 | .GetMethod("SteamMatchmaking_OnLobbyCreated",
464 | BindingFlags.NonPublic | BindingFlags.Instance);
465 | }
466 | [HarmonyPostfix]
467 | static void PostFix(GameNetworkManager __instance, Result result, Lobby lobby)
468 | {
469 | lobby.SetData("name", "[BiggerLobby]" + lobby.GetData("name"));
470 | } //etc
471 | }
472 | [HarmonyPatch(typeof(GameNetworkManager), "SetConnectionDataBeforeConnecting")]
473 | [HarmonyPrefix]
474 | public static bool SetConnectionDataBeforeConnecting(GameNetworkManager __instance)
475 | {
476 | __instance.localClientWaitingForApproval = true;
477 | Debug.Log("Game version: " + __instance.gameVersionNum);
478 | if (__instance.disableSteam)
479 | {
480 | NetworkManager.Singleton.NetworkConfig.ConnectionData = Encoding.ASCII.GetBytes(__instance.gameVersionNum.ToString() + "," + "BiggerLobbyVersion2.5.0");//this nonsense ass string exists to tell the server if youre running biggerlobby for some reason. Also she fortnite on my burger till I battle pass
481 | }
482 | else
483 | {
484 | NetworkManager.Singleton.NetworkConfig.ConnectionData = Encoding.ASCII.GetBytes(__instance.gameVersionNum + "," + (ulong)SteamClient.SteamId + "," + "BiggerLobbyVersion2.5.0");
485 | }
486 | return (false);
487 | }
488 |
489 | [HarmonyPatch(typeof(GameNetworkManager), nameof(GameNetworkManager.LobbyDataIsJoinable))]
490 | [HarmonyPrefix]
491 | public static bool SkipLobbySizeCheck(ref GameNetworkManager __instance, ref bool __result, Lobby lobby)
492 | {
493 | string data = lobby.GetData("vers");
494 | string text = lobby.GetData("MaxPlayers");
495 | int newnumber;
496 | if (!(int.TryParse(text, out newnumber)))
497 | {
498 | newnumber = 16;
499 | }
500 | newnumber = Math.Min(Math.Max(newnumber, 4), 40);
501 | if (lobby.MemberCount >= newnumber || lobby.MemberCount < 1)
502 | {
503 | Debug.Log($"Lobby join denied! Too many members in lobby! {lobby.Id}");
504 | UnityEngine.Object.FindObjectOfType().SetLoadingScreen(isLoading: false, RoomEnter.Full, "The server is full!");
505 | __result = false;
506 | return false;
507 | }
508 | if (data != __instance.gameVersionNum.ToString())
509 | {
510 | Debug.Log($"Lobby join denied! Attempted to join vers.{data} lobby id: {lobby.Id}");
511 | UnityEngine.Object.FindObjectOfType().SetLoadingScreen(isLoading: false, RoomEnter.DoesntExist, $"The server host is playing on version {data} while you are on version {__instance.gameVersionNum}.");
512 | __result = false;
513 | return false;
514 | }
515 | if (lobby.GetData("joinable") == "false")
516 | {
517 | Debug.Log("Lobby join denied! Host lobby is not joinable");
518 | UnityEngine.Object.FindObjectOfType().SetLoadingScreen(isLoading: false, RoomEnter.DoesntExist, "The server host has already landed their ship, or they are still loading in.");
519 | __result = false;
520 | return false;
521 | }
522 | Plugin.MaxPlayers = newnumber;
523 | Plugin.Logger?.LogInfo($"Setting max players to {newnumber}!");
524 | if (__instance != null) __instance.maxAllowedPlayers = Plugin.MaxPlayers;
525 | // Lobby member count check is skipped here, see original method
526 | __result = true;
527 | return false;
528 | }
529 | }
530 | }
531 |
--------------------------------------------------------------------------------