├── .github └── FUNDING.yml ├── Patches ├── VersionFudge.cs ├── FullDebug.cs ├── InviteCode.cs ├── MenuPatch.cs ├── Handicapper.cs └── MultiPick.cs ├── LICENSE ├── README.md ├── MorePlayers.csproj ├── .gitignore ├── hardcoded_player_number_ultimate_chicken_horse.txt └── MorePlayersMod.cs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | github: batram 3 | patreon: upmoep 4 | -------------------------------------------------------------------------------- /Patches/VersionFudge.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | 3 | namespace MorePlayers 4 | { 5 | [HarmonyPatch(typeof(TabletMainMenuHome), nameof(TabletMainMenuHome.Update))] 6 | static class TabletMainMenuHomeUpdateCtorPatch 7 | { 8 | static void Postfix(TabletMainMenuHome __instance) 9 | { 10 | bool needs_update = !MorePlayersMod.og_version.StartsWith(GameState.GetLocalizationVersionNumber()); 11 | __instance.showingPleaseUpdate = needs_update; 12 | __instance.pleaseUpdateButton.gameObject.SetActive(needs_update); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Patches/FullDebug.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using UnityEngine; 3 | using UnityEngine.Networking; 4 | 5 | namespace MorePlayers 6 | { 7 | [HarmonyPatch(typeof(LevelSelectController), nameof(LevelSelectController.SetupLobbyAfterWait))] 8 | [HarmonyPatch(typeof(StartGameState), nameof(StartGameState.Awake))] 9 | static class StartGameStateCtorPatch 10 | { 11 | [System.Obsolete] 12 | static void Postfix() 13 | { 14 | if (MorePlayersMod.fullDebug.Value) 15 | { 16 | LogFilter.currentLogLevel = 0; 17 | Debug.logger.logEnabled = true; 18 | 19 | Debug.Log("StartGameStateCtorPatch"); 20 | Application.SetStackTraceLogType(LogType.Error, StackTraceLogType.Full); 21 | Application.SetStackTraceLogType(LogType.Assert, StackTraceLogType.Full); 22 | Application.SetStackTraceLogType(LogType.Warning, StackTraceLogType.Full); 23 | Application.SetStackTraceLogType(LogType.Log, StackTraceLogType.Full); 24 | Application.SetStackTraceLogType(LogType.Exception, StackTraceLogType.Full); 25 | 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Even More Players 2 | This is an `Ultimate Chicken Horse` `BepInEx` mod that allows for more than 4 players to play local and online. 3 | 4 | [Watch the video](https://user-images.githubusercontent.com/1382274/167601047-5b48eaa5-2b1d-42b3-8db4-5b271f7f2475.mp4) 5 | 6 | All players need the mod. 7 | The mod is disabled in normal Online play and only enabled when playing online via the "More" button. 8 | 9 | 10 | 11 | Multiple players can select the same characters. 12 | 13 | 14 | 15 | 16 | ## Thunderstore installation 17 | The mod is available via [thunderstore.io](https://thunderstore.io/c/ultimate-chicken-horse/p/murks/Even_More_Players/) and can be installed using [r2modman](https://github.com/ebkr/r2modmanPlus/releases/latest). 18 | 19 | ## Manual installation 20 | - Download [BepInEx Version 5](https://github.com/BepInEx/BepInEx/releases/latest) for your platform (windows64 or linux) (UCH is a x64 program) 21 | - Download [the latest EvenMorePlayers release (MorePlayersMod-x.x.x.x.zip)](https://github.com/batram/UCH-EvenMorePlayers/releases) 22 | - Put all the contents inside the zip files into your `Ultimate Chicken Horse` folder found via `Steam -> Manage -> Browse Local Files`. 23 | Run game! (Linux users need an additional step, follow instructions in BepInEx) 24 | 25 | ## Help 26 | If you have questions, comments or suggestions join the [UCH Mods discord](https://discord.gg/DACBGXVww7) 27 | 28 | [Tips for Controller issues](https://github.com/batram/UCH-EvenMorePlayers/wiki/Controller-Input-Issues) 29 | 30 | 31 | ## Credits 32 | - [Clever Endeavour Games](https://www.cleverendeavourgames.com/) 33 | - [BepInEx](https://github.com/BepInEx/BepInEx) team 34 | - [Harmony](https://github.com/pardeike/Harmony) by Andreas Pardeike 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /MorePlayers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | C:\Program Files (x86)\Steam\steamapps\common\Ultimate Chicken Horse\ 6 | 7 | 8 | net48 9 | true 10 | false 11 | MorePlayers 12 | MorePlayersMod 13 | plugins\ 14 | false 15 | false 16 | None 17 | 0.4.8 18 | 0.0.0.7 19 | 20 | 21 | 22 | 23 | 24 | $(UCHfolder)\UltimateChickenHorse_Data\Managed\Assembly-CSharp.dll 25 | 26 | 27 | $(UCHfolder)\BepInEx\core\BepInEx.dll 28 | False 29 | 30 | 31 | $(UCHfolder)\BepInEx\core\0Harmony.dll 32 | False 33 | 34 | 35 | $(UCHfolder)\UltimateChickenHorse_Data\Managed\com.unity.multiplayer-hlapi.Runtime.dll 36 | 37 | 38 | $(UCHfolder)\UltimateChickenHorse_Data\Managed\GameSparks.Api.dll 39 | 40 | 41 | $(UCHfolder)\UltimateChickenHorse_Data\Managed\InControl.dll 42 | 43 | 44 | $(UCHfolder)\UltimateChickenHorse_Data\Managed\UnityEngine.dll 45 | False 46 | 47 | 48 | $(UCHfolder)\UltimateChickenHorse_Data\Managed\UnityEngine.CoreModule.dll 49 | False 50 | 51 | 52 | $(UCHfolder)\UltimateChickenHorse_Data\Managed\UnityEngine.Physics2DModule.dll 53 | 54 | 55 | $(UCHfolder)\UltimateChickenHorse_Data\Managed\UnityEngine.UI.dll 56 | 57 | 58 | $(UCHfolder)\UltimateChickenHorse_Data\Managed\UnityEngine.UNETModule.dll 59 | 60 | 61 | $(UCHfolder)\UltimateChickenHorse_Data\Managed\UnityEngine.UnityWebRequestModule.dll 62 | 63 | 64 | $(UCHfolder)\UltimateChickenHorse_Data\Managed\UnityEngine.IMGUIModule.dll 65 | 66 | 67 | $(UCHfolder)\UltimateChickenHorse_Data\Managed\UnityEngine.InputLegacyModule.dll 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | if not exist "$(UCHfolder)\BepInEx\plugins\" mkdir "$(UCHfolder)\BepInEx\plugins\" 79 | if not exist "$(UCHfolder)\BepInEx\plugins\MorePlayersMod\" mkdir "$(UCHfolder)\BepInEx\plugins\MorePlayersMod\" 80 | copy "MorePlayersMod.dll" "$(UCHfolder)\BepInEx\plugins\MorePlayersMod\MorePlayersMod.dll" /y 81 | 82 | start explorer.exe "steam://rungameid/386940" 83 | 84 | 85 | 86 | 87 | 88 | taskkill /f /fi "imagename eq UltimateChickenHorse.exe" 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /.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/master/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 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /Patches/InviteCode.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using I2.Loc; 3 | using System; 4 | using System.Text.RegularExpressions; 5 | using UnityEngine; 6 | using UnityEngine.UI; 7 | 8 | namespace MorePlayers 9 | { 10 | static class MoreCode 11 | { 12 | public const char Marker = 'M'; 13 | public const string Stars = "*****"; 14 | 15 | public static float lastCodeInputFocus = 0; 16 | 17 | public static bool IsValid(string code) 18 | { 19 | return code.Length == 5 && (code[0] == Marker || code[0] == Char.ToLower(Marker)); 20 | } 21 | 22 | public static string Fudge(string code) 23 | { 24 | return Marker + code; 25 | } 26 | 27 | public static string UnFudge(string code) 28 | { 29 | return code.Substring(1); 30 | } 31 | 32 | public static void FudgeJoin(PickableNetworkButton __instance, string text) 33 | { 34 | if (!text.NullOrEmpty()) 35 | { 36 | __instance.inputField.text = text; 37 | GameSettings.GetInstance().StartAsHost = false; 38 | GameSettings.GetInstance().StartLocal = false; 39 | Matchmaker.Instance.JoinLobby(MoreCode.UnFudge(__instance.inputField.text), true, delegate (bool success) 40 | { 41 | if (success) 42 | { 43 | AnalyticEvent.JoinMatchEvent(Matchmaker.CurrentMatchmakingLobby.GetLobbyGuid(), AnalyticEvent.JoinMethod.CODE, Matchmaker.CurrentMatchmakingLobby.LobbyIsCrossplay(Application.platform)); 44 | } 45 | }); 46 | } 47 | 48 | } 49 | 50 | public static string CleanCode(string code) 51 | { 52 | if (code.NullOrEmpty()) 53 | { 54 | return null; 55 | } 56 | string text = Regex.Replace(code.ToUpper(), "[^A-Za-z]", ""); 57 | if (!IsValid(text)) 58 | { 59 | return null; 60 | } 61 | return text; 62 | } 63 | 64 | public static void CleanGUI() 65 | { 66 | var inputField_go = GameObject.Find("CodeInputField"); 67 | var inputField = inputField_go?.GetComponent(); 68 | if (inputField) 69 | { 70 | inputField.characterLimit = 4; 71 | ((Text)inputField.placeholder).text = "ABCD"; 72 | } 73 | } 74 | } 75 | 76 | [HarmonyPatch(typeof(PickableNetworkButton), nameof(PickableNetworkButton.Update))] 77 | static class PickableNetworkButtonUpdateCtorPatch 78 | { 79 | static bool Prefix(PickableNetworkButton __instance) 80 | { 81 | switch (__instance.job) 82 | { 83 | case PickableNetworkButton.NetworkButtonJobs.EnterLobbyCode: 84 | if (__instance.inputField.isFocused) 85 | { 86 | MoreCode.lastCodeInputFocus = 0.15f; 87 | } 88 | else 89 | { 90 | if (MoreCode.lastCodeInputFocus > 0) 91 | { 92 | MoreCode.lastCodeInputFocus -= Time.deltaTime; 93 | } 94 | } 95 | __instance.inputField.characterLimit = 5; 96 | ((Text)__instance.inputField.placeholder).text = MoreCode.Fudge("ABCD"); 97 | if (Input.GetKeyDown(KeyCode.Return) && MoreCode.IsValid(__instance.inputField.text) && MoreCode.lastCodeInputFocus > 0) 98 | { 99 | UserMessageManager.Instance.UserMessage("trying to join: " + __instance.inputField.text, 2f, UserMessageManager.UserMsgPriority.lo, true); 100 | MoreCode.FudgeJoin(__instance, __instance.inputField.text); 101 | } 102 | __instance.Show(true); 103 | return false; 104 | case PickableNetworkButton.NetworkButtonJobs.JoinLobbyByCode: 105 | __instance.Show(MoreCode.IsValid(__instance.inputField.text)); 106 | return false; 107 | case PickableNetworkButton.NetworkButtonJobs.MyLobbyCode: 108 | __instance.Show(GameSettings.GetInstance().UseUnityRelay); 109 | if (PickableNetworkButton.showCode && Matchmaker.CurrentMatchmakingLobby != null) 110 | { 111 | if (__instance.buttonText != null) 112 | { 113 | __instance.buttonText.text = MoreCode.Fudge(Matchmaker.CurrentMatchmakingLobby.GetLobbyCode()); 114 | return false; 115 | } 116 | } 117 | else if (__instance.buttonText != null) 118 | { 119 | __instance.buttonText.text = MoreCode.Stars; 120 | return false; 121 | } 122 | return false; 123 | } 124 | return true; 125 | } 126 | } 127 | 128 | [HarmonyPatch(typeof(PickableNetworkButton), nameof(PickableNetworkButton.OnAccept))] 129 | static class PickableNetworkOnAcceptCtorPatch 130 | { 131 | static bool Prefix(PickableNetworkButton __instance) 132 | { 133 | switch (__instance.job) 134 | { 135 | case PickableNetworkButton.NetworkButtonJobs.MyLobbyCode: 136 | if (Matchmaker.CurrentMatchmakingLobby != null) 137 | { 138 | GUIUtility.systemCopyBuffer = MoreCode.Fudge(Matchmaker.CurrentMatchmakingLobby.GetLobbyCode()); 139 | UserMessageManager.Instance.UserMessage(ScriptLocalization.Snapshot.ShareableCodeClipboard, 2f, UserMessageManager.UserMsgPriority.lo, true); 140 | } 141 | return false; 142 | case PickableNetworkButton.NetworkButtonJobs.PasteLobbyCode: 143 | string text = MoreCode.CleanCode(GUIUtility.systemCopyBuffer); 144 | MoreCode.FudgeJoin(__instance, text); 145 | return false; 146 | case PickableNetworkButton.NetworkButtonJobs.JoinLobbyByCode: 147 | MoreCode.FudgeJoin(__instance, __instance.inputField.text); 148 | return false; 149 | 150 | } 151 | return true; 152 | } 153 | } 154 | 155 | [HarmonyPatch(typeof(TabletLobbyOptionsScreen), nameof(TabletLobbyOptionsScreen.OnClickShowToggle))] 156 | static class TabletLobbyOptionsScreenCtorPatch 157 | { 158 | static bool Prefix(TabletLobbyOptionsScreen __instance) 159 | { 160 | __instance.lobbyCodeShown = !__instance.lobbyCodeShown; 161 | if (__instance.lobbyCodeShown) 162 | { 163 | //AkSoundEngine.PostEvent("UI_UPad_Online_Lobby_Code_Show", __instance.gameObject); 164 | __instance.lobbyCodeText.text = MoreCode.Fudge(Matchmaker.CurrentMatchmakingLobby.GetLobbyCode()); 165 | return false; 166 | } 167 | //kSoundEngine.PostEvent("UI_UPad_Online_Lobby_Code_Hide", __instance.gameObject); 168 | __instance.lobbyCodeText.text = MoreCode.Stars; 169 | return false; 170 | } 171 | } 172 | 173 | [HarmonyPatch(typeof(TabletLobbyOptionsScreen), nameof(TabletLobbyOptionsScreen.OnClickCopyLobbyCode))] 174 | static class TabletLobbyOptionsScreenOnClickCopyLobbyCodeCtorPatch 175 | { 176 | static bool Prefix() 177 | { 178 | QuickSaver.CopyStringToClipboard(MoreCode.Fudge(Matchmaker.CurrentMatchmakingLobby.GetLobbyCode())); 179 | UserMessageManager.Instance.UserMessage(ScriptLocalization.Snapshot.ShareableCodeClipboard, 2f, UserMessageManager.UserMsgPriority.lo, true); 180 | return false; 181 | } 182 | } 183 | 184 | [HarmonyPatch(typeof(TabletLobbyOptionsScreen), nameof(TabletLobbyOptionsScreen.Awake))] 185 | static class TabletLobbyOptionsScreenAwakeCtorPatch 186 | { 187 | static void Prefix(TabletLobbyOptionsScreen __instance) 188 | { 189 | __instance.lobbyCodeText.text = MoreCode.Stars; 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /hardcoded_player_number_ultimate_chicken_horse.txt: -------------------------------------------------------------------------------- 1 | [DD] done for a first try 2 | 3 | class Controller 4 | [DD] associatedChars = new Character.Animals[4]; 5 | 6 | [DD] function Controller.AddPlayer hardcoded 4 comparison 7 | [DD] function Controller.AssociateCharacter hardcoded 4 comparison 8 | [DD] function Controller.ClearPlayers hardcoded 4 size 9 | [DD] function Controller.GetLastPlayerNumber hardcoded 3 comparison (in for loop 3 to 0) 10 | [DD] function Controller.GetLastPlayerNumberAfter hardcoded 3 comparison (in for loop 3 to 0) 11 | [no clue] function Controller.RemovePlayer num calc problems 12 | 13 | 14 | [DD] class ControllerDisconnect 15 | [DD] ConnectPrompts = init to 4 MonoBehaviour 16 | [DD] orphanedReceivers = List with 4 entries 17 | [DD] orphanedCharacters = new Character.Animals[4][]; 18 | [DD? showingPrompts = new bool[4]; 19 | 20 | [DD] function ControllerDisconnect.SetPromptForPlayer hardcoded 4 comparison 21 | 22 | 23 | [DD] class GraphScoreBoard 24 | [DD] ScorePositions = new RectTransform[4]; 25 | 26 | [DD] function GraphScoreBoard.SetPlayerCount hardcoded 4 comparison, add ScorePositions for additional players 27 | [DD] function GraphScoreBoard.SetPlayerCharacter hardcoded 3 comparison (order: 0 to 3) 28 | 29 | 30 | [DD] InputManager 31 | [DD] function InputManager.EnableNativeInput patch getter to always return true, so we can use more controllers 32 | 33 | 34 | [DD] class InventoryBook 35 | [DD] function InventoryBook.HasCursor hardcoded 4 comparison 36 | [DD] function InventoryBook.GetCursor hardcoded 4 comparison 37 | [DD] function InventoryBook.AddPlayer hardcoded 4 comparison, fixup cursor spawn location for additional players 38 | 39 | 40 | [DD] class KickTracker 41 | [DD] constructor KickTracker hardcoded 4 comparison and size 42 | 43 | [DD] function KickTracker.ClearPlayer hardcoded 4 comparison 44 | [DD] function KickTracker.CountVotes hardcoded 4 comparison 45 | [DD] function KickTracker.VotesFromNetworkNumber hardcoded 4 comparison [!U8 missing] 46 | 47 | 48 | [DD] class LevelPortal 49 | [DD] VoteArrows fudge with VoteArrows 50 | 51 | 52 | [DD] class LevelSelectController 53 | [DD] JoinedPlayers = new LobbyPlayer[4]; 54 | [DD] PlayerJoinIndicators map indicators to 0..3, we reuse existing 4 indicators for multiple players 55 | [DD] CursorSpawnPoint map cursor spawn points to 0..3 (TODO: add new positions) 56 | [DD] UndergroundCharacterPosition map cursor spawn points to 0..3 (TODO: add new positions) 57 | 58 | 59 | [DD] class LobbyPointCounter 60 | [DD] playerJoinedGame 61 | [DD] playerPlayedGame 62 | [DD] playerAFK 63 | 64 | [DD] function handleEvent hardcoded 4 GameStartEvent 65 | [DD] function Reset hardcoded reset of playerPlayedGame for 0..3 66 | 67 | 68 | [DD] class LobbySkillTracker 69 | [DD] ratings = new Rating[4]; 70 | 71 | [DD] function UpdateLobbyInfo hardcoded 4 comparison !!carefull Ldc_I4_4 in stelem.ref array construction 72 | [DD] function RecalculateScores hardcoded 4 comparison !!carefull 4 also used as unrelated array index 73 | 74 | 75 | [DD] class PartyBox 76 | [DD] function PartyBox.AddPlayer hardcoded 4 comparison !!carefull additional ldc.i4.4 in IL code unrelated to player number as array index 77 | [DD] function PartyBox.SetPlayerCount hardcoded 4 comparison 78 | 79 | 80 | [DD] class StatTracker 81 | [DD] saveFiles = new SaveFileData[4]; 82 | [DD] saveStatuses = new StatTracker.SaveFileStatus[4]; 83 | 84 | [DD] function StatTracker.GetSaveFileDataForLocalPlayer hardcoded 4 85 | [DD] function StatTracker.OnLocalPlayerAdded hardcoded 4 86 | [DD] function StatTracker.SaveGameForAnimal hardcoded 4 87 | 88 | 89 | [DD] class VersusControl 90 | [DD] winOrder = new GamePlayer[4]; 91 | [DD] RemainingPlacements = new int[4]; 92 | 93 | [DD] function VersusControl.playersLeftToPlace hardcoded 4 comparison 94 | [DD] function ShuffleStartPosition fails for larger amount of players (integer overflow???) 95 | 96 | 97 | [DD] class SteamMatchmaker 98 | [DD] function createSocialLobby hardcoded 4 size 99 | [DD] function OnSteamLobbyJoinRequested hardcoded 5 comparison 100 | 101 | 102 | [DD] class LobbyManager 103 | [DD] function OnLobbyClientAddPlayerFailed hardcoded 5 comparison 104 | 105 | 106 | class PickableNetworkButton 107 | [DD] function OnAccept hardcoded 4 //this.NumberOfPlayersInSocialLobby < 4 108 | [DD] function Update hardcoded 4 // !!carefull invite code also checks 4 109 | function SetSearchResultInfo hardcoded 4 in string ... //max number of players displayed 110 | 111 | 112 | [DD] class SteamLobbySearchList 113 | [DD] function checkForListUpdates hardcoded 4 comparison 114 | 115 | 116 | [DD] class UnityMatchmaker 117 | [DD] function CheckHostConnectivity hardcoded 4 comparison 118 | [DD] function CreateUnityMatch hardcoded 4 size 119 | 120 | 121 | [DD] class NetworkLobbyManager 122 | [DD] m_MaxPlayers = 4; 123 | 124 | 125 | [DD] class GameSettings 126 | [DD] int MaxPlayers = 4; 127 | [DD] PlayerColors Random generate colors for new players 128 | 129 | 130 | [DD] class PlayerManager maybe not needed if GameSettings.GetInstance().MaxPlayers is correct from the start 131 | [DD] maxPlayers 132 | playerList 133 | 134 | 135 | [DD] class LobbyManager 136 | [DD] lobbySlots 137 | 138 | 139 | [DD] class ChallengeScoreboard 140 | [DD] players 141 | [DD] function CollectPlayerIds 142 | 143 | 144 | [DD] class Tablet 145 | [DD] untrackedCursors 146 | 147 | 148 | [DD] GameState 149 | [DD] PlayerScores 150 | 151 | 152 | [DD] class TurnIndicator 153 | [DD] function SetPlayerCount hardcoded 4 comparison 154 | 155 | 156 | [DD] class SwitchController 157 | [DD] function Reset hardcoded 4 size 158 | 159 | 160 | [DD] class KeyboardInput 161 | [DD] function Reset hardcoded 4 size 162 | 163 | 164 | [DD] class GamesparksMatchmakingLobby 165 | [DD] function SetPlayerCount cap reported player number at 3 166 | 167 | 168 | [DD] class SteamMatchmakingLobby 169 | [DD] function SetPlayerCount cap reported player number at 3 170 | 171 | 172 | [DD] class LivesDisplayController 173 | [DD] livesDisplayBoxes not enough livesDisplayBoxes for more than 8 players ... 174 | 175 | 176 | [DD] class PlayerStatusDisplay 177 | [DD] public StatusSlot[] Slots not enough Slots for more than 4 players (Challenge mode) 178 | [DD] function SetSlotCount hardcoded 4 179 | 180 | 181 | [DD] class NetworkManager 182 | [DD] private int m_MaxConnections = 4 would only allow 5 players (host + 4 external) // patched via maxConnections 183 | 184 | 185 | [DD] class BeeSwarm 186 | [DD] function Awake hardcoded 4 size 187 | 188 | 189 | [DD] class Matchmaker 190 | [DD] function CleanUpPlayers hardcoded 5 191 | 192 | 193 | class GameControl 194 | [DD] protected bool[] showScoreButtons 195 | = new bool[PlayerManager.maxPlayers]; assigned before fix 196 | 197 | [DD] function ReceiveEvent PlayerBitMask to inputPlayerNumber only for 4 players 198 | !! BitMask int overflow for more than 94 players 199 | 200 | -------------------------------------------------------------------------------- /Patches/MenuPatch.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using System.Reflection; 3 | using UnityEngine; 4 | using UnityEngine.UI; 5 | 6 | namespace MorePlayers 7 | { 8 | static class MenuPatch 9 | { 10 | static public void PatchMenu() 11 | { 12 | var harmony = new Harmony("EvenMorePlayers.MenuPatch"); 13 | var original = typeof(TabletMainMenuHome).GetMethod(nameof(TabletMainMenuHome.Initialize)); 14 | var postfix = typeof(TabletMainMenuHomeCtorPatch).GetMethod(nameof(TabletMainMenuHomeCtorPatch.Postfix)); 15 | harmony.Patch(original, postfix: new HarmonyMethod(postfix)); 16 | 17 | var update_original = typeof(TabletMainMenuHome).GetMethod("Update", BindingFlags.NonPublic | BindingFlags.Instance); 18 | var update_postfix = typeof(TabletMainMenuHomeScoochButtonsCtorPatch).GetMethod("Postfix"); 19 | harmony.Patch(update_original, postfix: new HarmonyMethod(update_postfix)); 20 | 21 | var accept_original = typeof(TabletButton).GetMethod(nameof(TabletButton.OnAccept)); 22 | var accept_prefix = typeof(TabletButtonOnAcceptCtorPatch).GetMethod(nameof(TabletButtonOnAcceptCtorPatch.Prefix)); 23 | harmony.Patch(accept_original, prefix: new HarmonyMethod(accept_prefix)); 24 | 25 | var onlinestate_original = typeof(TabletMainMenuOnlineIndicator).GetMethod("SetPlayOnlineButtonState", BindingFlags.NonPublic | BindingFlags.Instance); 26 | var onlinestate_prefix = typeof(TabletMainMenuOnlineIndicatorCtorPatch).GetMethod("Prefix"); 27 | harmony.Patch(onlinestate_original, prefix: new HarmonyMethod(onlinestate_prefix)); 28 | 29 | var version_original = typeof(GameSettings).GetMethod("get_VersionNumber"); 30 | var version_prefix = typeof(GameSettingsVersionCtorPatch).GetMethod("Prefix"); 31 | harmony.Patch(version_original, prefix: new HarmonyMethod(version_prefix)); 32 | } 33 | } 34 | 35 | //TabletButton::OnAccept(PickCursor pickCursor) 36 | static class TabletButtonOnAcceptCtorPatch 37 | { 38 | static public void Prefix(TabletButton __instance) 39 | { 40 | Debug.Log("Pressed da button: " + __instance.gameObject.name); 41 | if (__instance.gameObject.name == "Play More") 42 | { 43 | if (!GameSettings.GetInstance().versionNumber.Contains(MorePlayersMod.mod_version_full)) 44 | { 45 | GameSettings.GetInstance().versionNumber = GameSettings.GetInstance().VersionNumber + MorePlayersMod.mod_version_full; 46 | if (PlayerManager.maxPlayers != MorePlayersMod.newPlayerLimit.Value) 47 | { 48 | PlayerManager.maxPlayers = MorePlayersMod.newPlayerLimit.Value; 49 | new Harmony("EvenMorePlayers.PlayerNumPatch").PatchAll(); 50 | } 51 | } 52 | } 53 | else if (__instance.gameObject.name == "Play Online") 54 | { 55 | GameSettings.GetInstance().versionNumber = MorePlayersMod.og_version; 56 | PlayerManager.maxPlayers = 4; 57 | Harmony.UnpatchID("EvenMorePlayers.PlayerNumPatch"); 58 | MoreCode.CleanGUI(); 59 | } 60 | } 61 | } 62 | 63 | static class TabletMainMenuHomeCtorPatch 64 | { 65 | static public void Postfix(ChallengeScoreboard __instance) 66 | { 67 | __instance.players = new ChallengeScoreboard.ChallengePlayer[PlayerManager.maxPlayers]; 68 | GameObject more_button = GameObject.Find("main Buttons/Play More"); 69 | if (more_button == null) 70 | { 71 | //Adjust local button 72 | GameObject play_local = GameObject.Find("main Buttons/Play"); 73 | var local_label = play_local.transform.Find("Text Label").GetComponent(); 74 | local_label.text = "Local"; 75 | local_label.transform.position -= new Vector3(0.65f, 0f, 0f); 76 | var local_image = play_local.transform.Find("Image"); 77 | local_image.transform.localScale = new Vector3(0.7073f, 0.7073f, 1f); 78 | local_image.transform.position -= new Vector3(0.04f, 0f, 0f); 79 | 80 | //Adjust online button 81 | GameObject play_online = GameObject.Find("main Buttons/Play Online"); 82 | var online_label = play_online.transform.Find("Text Label").GetComponent(); 83 | online_label.text = "Online"; 84 | online_label.transform.position -= new Vector3(0.7f, 0f, 0f); 85 | var online_image = play_online.transform.Find("Image"); 86 | online_image.transform.localScale = new Vector3(0.8073f, 0.8073f, 1f); 87 | online_image.transform.position -= new Vector3(0.11f, 0f, 0f); 88 | 89 | //Add more button 90 | more_button = UnityEngine.Object.Instantiate(play_online); 91 | more_button.name = "Play More"; 92 | more_button.transform.SetParent(play_online.transform.parent); 93 | more_button.transform.localScale = new Vector3(1f, 1f, 1f); 94 | var more_label = more_button.transform.Find("Text Label").GetComponent(); 95 | more_label.text = "More"; 96 | more_label.transform.position -= new Vector3(0.6655f, 0f, 0f); 97 | 98 | var more_image = more_button.transform.Find("Image"); 99 | more_image.transform.localScale = new Vector3(-0.5073f, 0.5073f, 1f); 100 | more_image.transform.position += new Vector3(-0.06f, 0.5073f, 0f); 101 | var more_image1 = Object.Instantiate(more_image.gameObject, more_image.transform.parent); 102 | more_image1.name = "Image1"; 103 | more_image1.transform.position = more_image.transform.position; 104 | more_image1.transform.position -= new Vector3(0.6f, 1.2f, 0f); 105 | more_image1.transform.localScale = new Vector3(-0.5073f, 0.5073f, 1f); 106 | 107 | var more_image2 = Object.Instantiate(more_image.gameObject, more_image.transform.parent); 108 | more_image2.name = "Image2"; 109 | more_image2.transform.position = more_image.transform.position; 110 | more_image2.transform.position -= new Vector3(-0.6f, 1.2f, 0f); 111 | more_image2.transform.localScale = new Vector3(-0.5073f, 0.5073f, 1f); 112 | } 113 | } 114 | } 115 | 116 | static class TabletMainMenuHomeScoochButtonsCtorPatch 117 | { 118 | static public void Postfix(PickableMainMenuButton __instance) 119 | { 120 | GameObject more_button = GameObject.Find("main Buttons/Play More"); 121 | if (more_button) 122 | { 123 | //Adjust local button 124 | GameObject play_local = GameObject.Find("main Buttons/Play"); 125 | play_local.transform.localPosition = new Vector2(-320f, play_local.transform.localPosition.y); 126 | play_local.transform.localScale = new Vector3(1.015f, 1, 1); 127 | var local_label = play_local.transform.Find("Text Label").GetComponent(); 128 | local_label.text = "Local"; 129 | 130 | //Adjust online button 131 | GameObject play_online = GameObject.Find("main Buttons/Play Online"); 132 | play_online.transform.localScale = new Vector3(1.015f, 1, 1); 133 | var online_label = play_online.transform.Find("Text Label").GetComponent(); 134 | online_label.text = "Online"; 135 | 136 | //Adjust more button 137 | more_button.transform.localScale = new Vector3(1.015f, 1, 1); 138 | more_button.transform.localPosition = new Vector2(320f, more_button.transform.localPosition.y); 139 | var more_label = more_button.transform.Find("Text Label").GetComponent(); 140 | more_label.text = "More"; 141 | } 142 | } 143 | } 144 | 145 | static class TabletMainMenuOnlineIndicatorCtorPatch 146 | { 147 | static public void Prefix(bool spinnerActive, bool buttonActive) 148 | { 149 | GameObject more_button = GameObject.Find("main Buttons/Play More"); 150 | if (more_button) 151 | { 152 | more_button.transform.Find("LoadingSpinner")?.gameObject.SetActive(spinnerActive); 153 | TabletDisableGroup tdg = more_button.GetComponent(); 154 | if (tdg != null && more_button.GetComponent().Disabled != !buttonActive) 155 | { 156 | more_button.GetComponent().SetDisabled(!buttonActive); 157 | TabletButton tb = more_button.GetComponent(); 158 | more_button.transform.Find("Image1").GetComponent().color = tb.labelImage.color; 159 | more_button.transform.Find("Image2").GetComponent().color = tb.labelImage.color; 160 | } 161 | } 162 | } 163 | } 164 | 165 | static class GameSettingsVersionCtorPatch 166 | { 167 | static public bool Prefix(GameSettings __instance, ref string __result) 168 | { 169 | if (GameSettings.GetInstance().versionNumber.Contains(MorePlayersMod.mod_version_full)) 170 | { 171 | __result = GameSettings.GetInstance().versionNumber; 172 | return false; 173 | } 174 | 175 | return true; 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /Patches/Handicapper.cs: -------------------------------------------------------------------------------- 1 | using GameEvent; 2 | using HarmonyLib; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using UnityEngine; 6 | using UnityEngine.UI; 7 | 8 | namespace MorePlayers 9 | { 10 | static class HandiShuffle 11 | { 12 | public static void ShowLine(HandicapLine line, bool show) 13 | { 14 | line.ScorelineStretcher.SetActive(show); 15 | line.AnimalName.gameObject.SetActive(show); 16 | line.HandicapNumber.gameObject.SetActive(show); 17 | } 18 | 19 | public static void reCap() 20 | { 21 | if (!MorePlayersMod.shuffleScoreBalancer.Value) 22 | return; 23 | 24 | GameObject handicapper = GameObject.Find("Handicapper"); 25 | handicap handi = handicapper?.GetComponent(); 26 | HandiShuffle.pushHandicaps(handi, -1); 27 | } 28 | 29 | 30 | public static void pushHandicaps(handicap handicap, int networkPlayerNumber) 31 | { 32 | if (!MorePlayersMod.shuffleScoreBalancer.Value) 33 | return; 34 | 35 | HandicapLine slot0 = handicap.HandicapLineSlots[0].GetComponentInChildren(); 36 | HandicapLine slot1 = handicap.HandicapLineSlots[1].GetComponentInChildren(); 37 | HandicapLine slot2 = handicap.HandicapLineSlots[2].GetComponentInChildren(); 38 | HandicapLine slot3 = handicap.HandicapLineSlots[3].GetComponentInChildren(); 39 | 40 | HandicapLine[] lines = { slot0, slot1, slot2, slot3 }; 41 | int[] playerNumbers = { slot0.PlayerNetworkNumber, slot1.PlayerNetworkNumber, slot2.PlayerNetworkNumber, slot3.PlayerNetworkNumber }; 42 | playerNumbers = playerNumbers.Where(c => 43 | { 44 | LobbyPlayer player = LobbyManager.instance.GetLobbyPlayer(c); 45 | return (c != networkPlayerNumber && c != -1 && player?.PickedAnimal != Character.Animals.NONE); 46 | }).ToArray(); 47 | 48 | if (playerNumbers.Length < 4) 49 | { 50 | //add all players with non 100 handicap to list when list is empty 51 | foreach (LobbyPlayer player in LobbyManager.instance.GetLobbyPlayers()) 52 | { 53 | if (player?.PickedAnimal != Character.Animals.NONE && player.handicap != 100) 54 | { 55 | if (player.networkNumber != networkPlayerNumber && !playerNumbers.Contains(player.networkNumber)) 56 | playerNumbers = playerNumbers.Append(player.networkNumber).ToArray(); 57 | } 58 | } 59 | } 60 | 61 | if (playerNumbers.Length < 4) 62 | { 63 | int[] fillPlayerNumbers = { -1, -1, -1, -1 }; 64 | for (int x = 0; x < playerNumbers.Length; x++) 65 | { 66 | fillPlayerNumbers[x] = playerNumbers[x]; 67 | } 68 | playerNumbers = fillPlayerNumbers; 69 | } 70 | 71 | Dictionary ogNums = new Dictionary(); 72 | 73 | foreach (HandicapLine line in lines) 74 | { 75 | if (!ogNums.ContainsKey(line.PlayerNetworkNumber) && line.PlayerNetworkNumber != -1) 76 | { 77 | ogNums.Add(line.PlayerNetworkNumber, line.currentHandicapFloat); 78 | } 79 | line.PlayerNetworkNumber = -1; 80 | } 81 | 82 | int[] newPlayerNumbers = { networkPlayerNumber, playerNumbers[0], playerNumbers[1], playerNumbers[2] }; 83 | 84 | if (networkPlayerNumber == -1) 85 | { 86 | newPlayerNumbers = playerNumbers; 87 | } 88 | 89 | int i = 0; 90 | foreach (int playerNum in newPlayerNumbers) 91 | { 92 | bool emptySlot = playerNum == -1; 93 | 94 | lines[i].PlayerNetworkNumber = playerNum; 95 | ShowLine(lines[i], !emptySlot); 96 | 97 | if (!emptySlot) 98 | { 99 | bool skipTransition = playerNum != networkPlayerNumber || networkPlayerNumber == -1; 100 | updateHandicapLine(lines[i], playerNum, skipTransition, ogNums); 101 | } 102 | i++; 103 | } 104 | } 105 | 106 | private static void updateHandicapLine(HandicapLine line, int networkPlayerNumber, bool skipTransition, Dictionary ogNums) 107 | { 108 | LobbyPlayer player = LobbyManager.instance.GetLobbyPlayer(networkPlayerNumber); 109 | if (!player) 110 | { 111 | line.PlayerNetworkNumber = -1; 112 | ShowLine(line, false); 113 | return; 114 | } 115 | 116 | line.AnimalName.text = player.playerName; 117 | 118 | line.targetHandicap = player.handicap; 119 | line.HandicapNumber.text = line.targetHandicap.ToString() + "%"; 120 | if (skipTransition) 121 | { 122 | // skip transition animation if just switching lines 123 | line.currentHandicapFloat = (float)line.targetHandicap / 100f; 124 | line.ScorelineStretcher.transform.localScale = new Vector3(line.initialScale.x * line.currentHandicapFloat, line.initialScale.y, line.initialScale.y); 125 | } 126 | else 127 | { 128 | if (ogNums.ContainsKey(networkPlayerNumber)) 129 | { 130 | line.currentHandicapFloat = ogNums.GetValueSafe(networkPlayerNumber); 131 | } 132 | } 133 | 134 | } 135 | } 136 | 137 | [HarmonyPatch(typeof(handicap), nameof(handicap.handleEvent))] 138 | static class HandicapHandleEventCtorPatch 139 | { 140 | static void Postfix(handicap __instance, GameEvent.GameEvent e) 141 | { 142 | if (e.GetType() == typeof(NetworkMessageReceivedEvent)) 143 | { 144 | //Set on Client 145 | NetworkMessageReceivedEvent networkMessageReceivedEvent = e as NetworkMessageReceivedEvent; 146 | short msgType = networkMessageReceivedEvent.Message.msgType; 147 | 148 | if (msgType == NetMsgTypes.PlayerHandicapSet) 149 | { 150 | MsgPlayerHandicapSet msgPlayerHandicapSet = (MsgPlayerHandicapSet)networkMessageReceivedEvent.ReadMessage; 151 | HandiShuffle.pushHandicaps(__instance, msgPlayerHandicapSet.NetworkPlayerNumber); 152 | } 153 | if (msgType == NetMsgTypes.SetCustomPortalInfo || msgType == NetMsgTypes.CommunicateCharacterOutfits) 154 | { 155 | HandiShuffle.pushHandicaps(__instance, -1); 156 | } 157 | } 158 | else 159 | { 160 | HandiShuffle.pushHandicaps(__instance, -1); 161 | } 162 | } 163 | } 164 | 165 | [HarmonyPatch(typeof(handicap), nameof(handicap.Start))] 166 | static class HandicapStartCtorPatch 167 | { 168 | static void Postfix(handicap __instance) 169 | { 170 | if (!MorePlayersMod.shuffleScoreBalancer.Value) 171 | return; 172 | 173 | GameObject title = GameObject.Find("Handicapper/Canvas/Title"); 174 | Text titleText = title.GetComponent(); 175 | titleText.text = "modded Score Balancer"; 176 | 177 | foreach (Transform t in __instance.HandicapLineSlots) 178 | { 179 | HandicapLine line = t.GetComponentInChildren(); 180 | 181 | if (!line.currentlyShown || line.targetHandicap == -999) 182 | { 183 | line.PlayerNetworkNumber = -1; 184 | HandiShuffle.ShowLine(line, false); 185 | } 186 | } 187 | 188 | GameEventManager.ChangeListener(__instance, true); 189 | GameEventManager.ChangeListener(__instance, true); 190 | GameEventManager.ChangeListener(__instance, true); 191 | GameEventManager.ChangeListener(__instance, true); 192 | GameEventManager.ChangeListener(__instance, true); 193 | GameEventManager.ChangeListener(__instance, true); 194 | } 195 | } 196 | 197 | [HarmonyPatch(typeof(HandicapLine), nameof(HandicapLine.SetName))] 198 | static class HandicapLineSetNameCtorPatch 199 | { 200 | static bool Prefix(HandicapLine __instance, Character.Animals animal, bool altSkin) 201 | { 202 | if (!MorePlayersMod.shuffleScoreBalancer.Value) 203 | return true; 204 | 205 | LobbyPlayer lobbyPlayer = LobbyManager.instance.GetLobbyPlayer(__instance.PlayerNetworkNumber); 206 | __instance.AnimalName.text = lobbyPlayer.playerName; 207 | return false; 208 | } 209 | } 210 | 211 | [HarmonyPatch(typeof(LobbyPlayer), nameof(LobbyPlayer.CmdSetPlayerHandicap))] 212 | static class LobbyPlayerHandleEventCtorPatch 213 | { 214 | static void Postfix(LobbyPlayer __instance, int newHandicap) 215 | { 216 | HandiShuffle.reCap(); 217 | } 218 | } 219 | 220 | [HarmonyPatch(typeof(LobbyPlayer), nameof(LobbyPlayer.DoCharacterPickedEvent))] 221 | static class LobbyPlayerDoCharacterPickedEventCtorPatch4Handicap 222 | { 223 | static void Postfix(ref bool clearOutfit) 224 | { 225 | HandiShuffle.reCap(); 226 | } 227 | } 228 | 229 | [HarmonyPatch(typeof(LobbyPlayer), nameof(LobbyPlayer.RpcRequestPickResponse))] 230 | static class LobbyPlayerRpcRequestPickResponseCtorPatch4Handicap 231 | { 232 | static void Postfix() 233 | { 234 | HandiShuffle.reCap(); 235 | } 236 | } 237 | } -------------------------------------------------------------------------------- /Patches/MultiPick.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using System; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.Networking; 6 | 7 | namespace MorePlayers 8 | { 9 | static class MultiPick 10 | { 11 | 12 | public const int multiMagicNumber = 10000; 13 | 14 | public static Character SpawnCharacter(Character to_clone, Vector3 position) 15 | { 16 | Character new_character = UnityEngine.Object.Instantiate(to_clone, position, Quaternion.identity); 17 | new_character.GetComponent().ForceSceneId(0); 18 | UnityEngine.Object.Destroy(new_character.GetComponent()); 19 | 20 | new_character.picked = true; 21 | new_character.FindPlayerOnSpawn = true; 22 | new_character.gameObject.transform.parent = null; 23 | 24 | //Remove all original ArtMatches, new ones are added during pick event (breaks outfit selection) 25 | var arties = new_character.GetComponentsInChildren(); 26 | for (var i = 0; i < arties.Length; i++) 27 | { 28 | UnityEngine.Object.Destroy(arties[i].gameObject); 29 | } 30 | 31 | NetworkServer.Spawn(new_character.gameObject); 32 | 33 | return new_character; 34 | } 35 | } 36 | 37 | public class OGProtection : MonoBehaviour 38 | { 39 | 40 | } 41 | 42 | [HarmonyPatch(typeof(LevelSelectController), nameof(LevelSelectController.RpcResetCharacter))] 43 | static class LevelSelectControllerRpcResetCharacterCtorPatch 44 | { 45 | static void Prefix(LevelSelectController __instance, GameObject characterObj) 46 | { 47 | Character c = characterObj.GetComponent(); 48 | if (c) 49 | { 50 | __instance.MainCamera.RemoveTarget(c); 51 | } 52 | } 53 | 54 | static void Postfix(GameObject characterObj) 55 | { 56 | NetworkServer.Destroy(characterObj); 57 | } 58 | } 59 | 60 | [HarmonyPatch(typeof(Character), nameof(Character.Awake))] 61 | static class CharacterCtorPatch 62 | { 63 | static void Prefix(Character __instance) 64 | { 65 | //Turn on all colliders, fix for collision modifier, needed on client 66 | foreach (BoxCollider2D c in __instance.gameObject.GetComponents()) 67 | { 68 | c.enabled = true; 69 | } 70 | 71 | __instance.RefreshScale(); 72 | } 73 | } 74 | 75 | [HarmonyPatch(typeof(OutfitManager), nameof(OutfitManager.RebuildDatabase))] 76 | static class OutfitManagerRebuildDatabaseTakenCtorPatch 77 | { 78 | static bool Prefix(OutfitManager __instance) 79 | { 80 | Debug.Log("OutfitManager.RebuildDatabase: fix"); 81 | 82 | __instance.characterOutfitsUnlocked.Clear(); 83 | __instance.characterOutfitsAll.Clear(); 84 | __instance.characterArtMatchers = UnityEngine.Object.FindObjectsOfType(); 85 | foreach (ArtMatcher artMatcher in __instance.characterArtMatchers) 86 | { 87 | if (artMatcher.character.name.Contains("Clone") && !artMatcher.character.name.Contains("moep") && artMatcher.character.GetComponent()) 88 | { 89 | artMatcher.character.name += " moep " + artMatcher.character.GetComponent().netId; 90 | } 91 | 92 | if (!__instance.characterOutfitsUnlocked.ContainsKey(artMatcher.character)) 93 | { 94 | __instance.characterOutfitsUnlocked.Add(artMatcher.character, new List[Outfit.NumOutfitTypes]); 95 | } 96 | if (!__instance.characterOutfitsAll.ContainsKey(artMatcher.character)) 97 | { 98 | __instance.characterOutfitsAll.Add(artMatcher.character, new List[Outfit.NumOutfitTypes]); 99 | } 100 | 101 | for (int j = 0; j < Outfit.NumOutfitTypes; j++) 102 | { 103 | __instance.characterOutfitsUnlocked[artMatcher.character][j] = new List(); 104 | __instance.characterOutfitsAll[artMatcher.character][j] = new List(); 105 | } 106 | 107 | foreach (Outfit outfit in artMatcher.outfits) 108 | { 109 | if (outfit.outfitType != Outfit.OutfitType.FollowOutfit && outfit.outfitType != Outfit.OutfitType.Zombie) 110 | { 111 | __instance.characterOutfitsAll[artMatcher.character][(int)outfit.outfitType].Add(outfit); 112 | bool flag = outfit.Unlocked; 113 | if (!flag && artMatcher.GetDefaultForcedOutfit(outfit.outfitType) == outfit) 114 | { 115 | outfit.TempUnlocked = true; 116 | flag = true; 117 | } 118 | if (flag) 119 | { 120 | __instance.characterOutfitsUnlocked[artMatcher.character][(int)outfit.outfitType].Add(outfit); 121 | } 122 | } 123 | } 124 | 125 | if (artMatcher.character && artMatcher.character.associatedLobbyPlayer) 126 | { 127 | artMatcher.character.SetOutfitsFromArray(artMatcher.character.associatedLobbyPlayer.characterOutfitsList); 128 | } 129 | } 130 | 131 | return false; 132 | } 133 | } 134 | 135 | [HarmonyPatch(typeof(OutfitController), nameof(OutfitController.Show))] 136 | static class OutfitControllerupdateImagesCtorPatch 137 | { 138 | static void Prefix(OutfitController __instance) 139 | { 140 | __instance.OutfitManager.RebuildDatabase(); 141 | } 142 | } 143 | 144 | [HarmonyPatch(typeof(Character), nameof(Character.GetOutfitsAsArray))] 145 | static class GetOutfitsAsArrayCtorPatch 146 | { 147 | static void Postfix(Character __instance, ref int[] __result) 148 | { 149 | var netid = (int)__instance.GetComponent()?.netId.Value; 150 | if (netid != 0) 151 | { 152 | Array.Resize(ref __result, __result.Length + 1); 153 | __result[__result.Length - 1] = netid; 154 | } 155 | } 156 | } 157 | 158 | [HarmonyPatch(typeof(Character), nameof(Character.SetOutfitsFromArray), new Type[] { typeof(SyncListInt) })] 159 | static class SetOutfitsFromArraySyncListIntCtorPatch 160 | { 161 | static bool Prefix(Character __instance, SyncListInt outfitsSyncList) 162 | { 163 | Debug.Log("Character.SetOutfitsFromArray " + outfitsSyncList.Count); 164 | int[] array = new int[outfitsSyncList.Count == 7 ? 7 : 6]; 165 | for (int i = 0; i < array.Length; i++) 166 | { 167 | if (outfitsSyncList.Count > i) 168 | { 169 | array[i] = outfitsSyncList[i]; 170 | } 171 | else 172 | { 173 | array[i] = -1; 174 | } 175 | } 176 | __instance.SetOutfitsFromArray(array); 177 | return false; 178 | } 179 | } 180 | 181 | [HarmonyPatch(typeof(Character), nameof(Character.SetOutfitsFromArray), new Type[] { typeof(int[]) })] 182 | static class SetOutfitsFromArrayCtorPatch 183 | { 184 | static bool Prefix(Character __instance, ref int[] outfitsArray) 185 | { 186 | if (outfitsArray.Length == 7) 187 | { 188 | var character_go = ClientScene.FindLocalObject(new NetworkInstanceId((uint)outfitsArray[6])); 189 | var character = character_go?.GetComponent(); 190 | Array.Resize(ref outfitsArray, outfitsArray.Length - 1); 191 | if (character) 192 | { 193 | character.SetOutfitsFromArray(outfitsArray); 194 | return false; 195 | } 196 | } 197 | return true; 198 | } 199 | } 200 | 201 | [HarmonyPatch(typeof(LevelSelectController), nameof(LevelSelectController.IsCharacterTaken))] 202 | static class LevelSelectControllerIsCharacterTakenCtorPatch 203 | { 204 | static void Postfix(ref bool __result, LevelSelectController __instance) 205 | { 206 | Debug.Log("LevelSelectController.IsCharacterTaken false"); 207 | __result = false; 208 | } 209 | } 210 | 211 | [HarmonyPatch(typeof(Modifiers), nameof(Modifiers.OnModifiersDynamicChange))] 212 | static class ModifiersCtorPatch 213 | { 214 | static bool Prefix(Modifiers __instance) 215 | { 216 | Debug.Log("Modifiers.OnModifiersDynamicChange: fix size modifier " + __instance.CharacterScale + " " + Modifiers.GetInstance().CharacterScaleAudioStateString); 217 | if (LobbyManager.instance != null && LobbyManager.instance.CurrentLevelSelectController) 218 | { 219 | __instance.modsApplied = __instance.modsPreview; 220 | LobbyManager.instance.CurrentLevelSelectController.RefreshCharacterPosition(); 221 | } 222 | if (LobbyManager.instance != null && (LobbyManager.instance.CurrentGameController != null || LobbyManager.instance.CurrentLevelSelectController != null)) 223 | { 224 | Character[] array = UnityEngine.Object.FindObjectsOfType(); 225 | for (int i = 0; i < array.Length; i++) 226 | { 227 | array[i].RefreshScale(); 228 | } 229 | Time.timeScale = __instance.GameSpeed; 230 | } 231 | //AkSoundEngine.PostEvent(__instance.RateOfFireAudioEventStrings[__instance.RateOfFireMode], LobbyManager.instance.gameObject); 232 | 233 | return false; 234 | } 235 | } 236 | 237 | [HarmonyPatch(typeof(LevelSelectController), nameof(LevelSelectController.setupController))] 238 | static class LevelSelectControllerSetupControllerCtorPatch 239 | { 240 | static bool Prefix(LevelSelectController __instance, LobbyPlayer lobbyPl) 241 | { 242 | Player localPlayer = lobbyPl.LocalPlayer; 243 | Character.Animals[] associatedCharacters = localPlayer.UseController.GetAssociatedCharacters(); 244 | if (!localPlayer.UseController.ControlsPlayer(localPlayer.Number)) 245 | { 246 | localPlayer.UseController.AddPlayer(localPlayer.Number); 247 | } 248 | for (int i = associatedCharacters.Length - 1; i >= 0; i--) 249 | { 250 | if (associatedCharacters[i] != Character.Animals.NONE && lobbyPl.PickedAnimal == associatedCharacters[i]) 251 | { 252 | Debug.Log("Setting up " + associatedCharacters[i].ToString()); 253 | __instance.MainCamera.SetFrameSizes(__instance.CameraHeight); 254 | lobbyPl.PlayerStatus = LobbyPlayer.Status.CHARACTER; 255 | 256 | Character[] chars = GameObject.FindObjectsOfType(); 257 | foreach (Character character in chars) 258 | { 259 | if (associatedCharacters[i] == character.CharacterSprite) 260 | { 261 | if (!character.picked) 262 | { 263 | Debug.Log("Requesting AssociatedCharacter " + associatedCharacters[i].ToString()); 264 | 265 | LobbyCursor lobbyCursor = (LobbyCursor)localPlayer.AssociatedLobbyPlayer.CursorInstance; 266 | if (lobbyCursor != null) 267 | { 268 | localPlayer.AssociatedLobbyPlayer.requestedCharacterInstance = null; 269 | lobbyCursor.UseCamera = __instance.MainCamera.GetComponent(); 270 | localPlayer.UseController.AddReceiver(lobbyCursor); 271 | } 272 | 273 | localPlayer.AssociatedLobbyPlayer.RequestPickCharacter(character); 274 | } 275 | } 276 | } 277 | } 278 | } 279 | return false; 280 | } 281 | } 282 | 283 | [HarmonyPatch(typeof(LobbyPlayer), nameof(LobbyPlayer.CmdRequestPickCharacter))] 284 | static class LobbyPlayerCmdRequestPickCharacterCtorPatch 285 | { 286 | static bool Prefix(LobbyPlayer __instance, NetworkInstanceId characterInstanceId, Character.Animals animal) 287 | { 288 | var character_go = NetworkServer.FindLocalObject(characterInstanceId); 289 | if (character_go && character_go.GetComponent()) 290 | { 291 | Character requested_character = character_go.GetComponent(); 292 | 293 | if (requested_character.picked || requested_character.gameObject.GetComponent() != null) 294 | { 295 | Vector3 spawn_position = requested_character.transform.position; 296 | if (__instance.playerStatus == LobbyPlayer.Status.CHARACTER && (!GameState.GetInstance().currentSnapshotInfo.snapshotName.NullOrEmpty() || GameState.GetInstance().lastLevelPlayed == GameState.GetLevelSceneName(GameState.LevelName.BLANKLEVEL))) 297 | { 298 | spawn_position = LobbyManager.instance.CurrentLevelSelectController.UndergroundCharacterPosition[__instance.networkNumber - 1].position; 299 | } 300 | 301 | Character nspawn_char = MultiPick.SpawnCharacter(requested_character, spawn_position); 302 | uint new_id = nspawn_char.gameObject.GetComponent().netId.Value; 303 | 304 | __instance.CallCmdAssignCharacter(new_id, __instance.networkNumber, __instance.localNumber, false); 305 | __instance.CallRpcRequestPickResponse((int)(new_id * MultiPick.multiMagicNumber) + __instance.networkNumber, false); 306 | } 307 | else 308 | { 309 | __instance.CallCmdAssignCharacter(characterInstanceId.Value, __instance.networkNumber, __instance.localNumber, false); 310 | __instance.CallRpcRequestPickResponse(__instance.networkNumber, true); 311 | } 312 | } 313 | else 314 | { 315 | __instance.CallRpcRequestPickResponse(__instance.networkNumber, false); 316 | } 317 | 318 | return false; 319 | } 320 | } 321 | 322 | [HarmonyPatch(typeof(LobbyPlayer), nameof(LobbyPlayer.RpcRequestPickResponse))] 323 | static class LobbyPlayerRpcRequestPickResponseCtorPatch 324 | { 325 | static bool Prefix(LobbyPlayer __instance, ref int playerNetworkNumber, ref bool response) 326 | { 327 | Debug.Log("LobbyPlayer.RpcRequestPickResponse Prefix " + playerNetworkNumber + " response " + response); 328 | 329 | if (!response && playerNetworkNumber > MultiPick.multiMagicNumber) 330 | { 331 | var new_id = playerNetworkNumber / MultiPick.multiMagicNumber; 332 | playerNetworkNumber %= MultiPick.multiMagicNumber; 333 | 334 | var character_go = ClientScene.FindLocalObject(new NetworkInstanceId((uint)new_id)); 335 | if (character_go) 336 | { 337 | var car = character_go.GetComponent(); 338 | Debug.Log("Got reassign Character comp " + car); 339 | if (car) 340 | { 341 | __instance.requestedCharacterInstance = car; 342 | response = true; 343 | } 344 | } 345 | 346 | } 347 | return true; 348 | } 349 | 350 | static void Postfix(LobbyPlayer __instance, ref int playerNetworkNumber, ref bool response) 351 | { 352 | Debug.Log("LobbyPlayer.RpcRequestPickResponse Postfix " + playerNetworkNumber + " response " + response); 353 | } 354 | } 355 | 356 | [HarmonyPatch(typeof(LevelSelectController), nameof(LevelSelectController.Start))] 357 | static class LevelSelectControllerStartCtorPatch 358 | { 359 | static void Prefix(LevelSelectController __instance) 360 | { 361 | Character[] chars = GameObject.FindObjectsOfType(); 362 | Debug.Log("Found " + chars.Length + " chars"); 363 | foreach (Character ca in chars) 364 | { 365 | ca.gameObject.AddComponent(); 366 | } 367 | } 368 | } 369 | 370 | [HarmonyPatch(typeof(LevelSelectController), nameof(LevelSelectController.SetupLobbyAfterWait))] 371 | static class LevelSelectControllerSetupLobbyAfterWaitCtorPatch 372 | { 373 | static void Prefix(LevelSelectController __instance) 374 | { 375 | Character[] chars = GameObject.FindObjectsOfType(); 376 | var num = 0; 377 | foreach (Character ca in chars) 378 | { 379 | if (ca.Picked && !ca.Sitting) 380 | { 381 | ca.frozen = false; 382 | __instance.MainCamera.AddTarget(ca); 383 | num++; 384 | ca.SetLobbyCollider(true); 385 | } 386 | } 387 | if (num > 0) 388 | { 389 | __instance.MainCamera.SetFrameSizes(__instance.CameraHeight); 390 | } 391 | } 392 | } 393 | 394 | [HarmonyPatch(typeof(LobbyPlayer), nameof(LobbyPlayer.DoCharacterPickedEvent))] 395 | static class LobbyPlayerDoCharacterPickedEventCtorPatch 396 | { 397 | static void Prefix(ref bool clearOutfit) 398 | { 399 | Debug.Log("DoCharacterPickedEvent " + clearOutfit); 400 | clearOutfit = false; 401 | } 402 | } 403 | 404 | [HarmonyPatch(typeof(LevelSelectController), nameof(LevelSelectController.setupLobby))] 405 | static class LevelSelectControllerSetupLobbyCtorPatch 406 | { 407 | static void Postfix(LevelSelectController __instance) 408 | { 409 | if (!GameState.GetInstance().currentSnapshotInfo.snapshotName.NullOrEmpty() || GameState.GetInstance().lastLevelPlayed == GameState.GetLevelSceneName(GameState.LevelName.BLANKLEVEL)) 410 | { 411 | foreach (LobbyStartPoint lobbyStartPoint in __instance.StartingPoints) 412 | { 413 | Character componentInChildren = lobbyStartPoint.GetComponentInChildren(); 414 | componentInChildren.PositionCharacter(lobbyStartPoint.transform.position, true); 415 | } 416 | } 417 | } 418 | } 419 | 420 | [HarmonyPatch(typeof(GraphScoreBoard), nameof(GraphScoreBoard.MarkPlayerDisconnected))] 421 | static class GraphScoreBoardMarkPlayerDisconnectedCtorPatch 422 | { 423 | static bool Prefix() 424 | { 425 | //Disconnect Animal Enum based, reimplement in VersusControl.handleEvent 426 | return false; 427 | } 428 | } 429 | 430 | [HarmonyPatch(typeof(VersusControl), nameof(VersusControl.handleEvent))] 431 | static class VersusControlHandleEventCtorPatch 432 | { 433 | static void Prefix(VersusControl __instance, GameEvent.GameEvent e) 434 | { 435 | Type type = e.GetType(); 436 | if (type == typeof(GameEvent.GamePlayerRemovedEvent)) 437 | { 438 | GameEvent.GamePlayerRemovedEvent removedEvent = e as GameEvent.GamePlayerRemovedEvent; 439 | var relation = __instance.graphScoreBoardInstance.scorelineRelation; 440 | if (relation.ContainsKey(removedEvent.PlayerNetworkNumber)) 441 | { 442 | relation[removedEvent.PlayerNetworkNumber].SetDisconnected(true); 443 | } 444 | } 445 | } 446 | } 447 | } -------------------------------------------------------------------------------- /MorePlayersMod.cs: -------------------------------------------------------------------------------- 1 | using BepInEx; 2 | using BepInEx.Configuration; 3 | using HarmonyLib; 4 | using InControl; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Reflection; 8 | using System.Reflection.Emit; 9 | using UnityEngine; 10 | using UnityEngine.Networking; 11 | 12 | [assembly: AssemblyVersion("0.9.0.2")] 13 | [assembly: AssemblyInformationalVersion("0.9.0.2.5")] 14 | 15 | namespace MorePlayers 16 | { 17 | [BepInPlugin("EvenMorePlayers", "EvenMorePlayers", "0.9.0.2")] 18 | public class MorePlayersMod : BaseUnityPlugin 19 | { 20 | public static ConfigEntry newPlayerLimit; 21 | public static ConfigEntry fullDebug; 22 | public static ConfigEntry shuffleScoreBalancer; 23 | 24 | public static string og_version; 25 | 26 | public static string mod_version = "0.9.0.2"; 27 | public static string mod_version_full = " [EvenMorePlayers: " + mod_version + "]"; 28 | 29 | void Awake() 30 | { 31 | newPlayerLimit = Config.Bind("General", "newPlayerLimit", 100, "Maximum number of Players"); 32 | fullDebug = Config.Bind("General", "fullDebug", false, "Enable more lines in debug output"); 33 | shuffleScoreBalancer = Config.Bind("General", "shuffleScoreBalancer", true, "On the Score Balancer in the Treehouse show the Player that last change it on top"); 34 | 35 | og_version = GameSettings.GetInstance().versionNumber; 36 | PlayerManager.maxPlayers = newPlayerLimit.Value; 37 | new Harmony("EvenMorePlayers.PlayerNumPatch").PatchAll(); 38 | MenuPatch.PatchMenu(); 39 | Debug.Log("[MorePlayersMod] started."); 40 | } 41 | } 42 | 43 | [HarmonyPatch] 44 | static class Switch4ForMaxNumPatch 45 | { 46 | static IEnumerable TargetMethods() 47 | { 48 | yield return AccessTools.Method(typeof(BeeSwarm), nameof(BeeSwarm.Awake)); 49 | yield return AccessTools.Method(typeof(ChallengeScoreboard), nameof(ChallengeScoreboard.CollectPlayerIds)); 50 | yield return AccessTools.Method(typeof(Controller), nameof(Controller.AddPlayer)); 51 | yield return AccessTools.Method(typeof(Controller), nameof(Controller.AssociateCharacter)); 52 | yield return AccessTools.Method(typeof(ControllerDisconnect), nameof(ControllerDisconnect.SetPromptForPlayer)); 53 | yield return AccessTools.Method(typeof(GraphScoreBoard), nameof(GraphScoreBoard.SetPlayerCount)); 54 | yield return AccessTools.Method(typeof(InventoryBook), nameof(InventoryBook.AddPlayer)); 55 | yield return AccessTools.Method(typeof(InventoryBook), nameof(InventoryBook.GetCursor)); 56 | yield return AccessTools.Method(typeof(InventoryBook), nameof(InventoryBook.HasCursor)); 57 | yield return AccessTools.Method(typeof(KeyboardInput), nameof(KeyboardInput.Reset)); 58 | yield return AccessTools.Constructor(typeof(KickTracker)); 59 | yield return AccessTools.Method(typeof(KickTracker), nameof(KickTracker.ClearPlayer)); 60 | yield return AccessTools.Method(typeof(KickTracker), nameof(KickTracker.CountVotes)); 61 | yield return AccessTools.Method(typeof(KickTracker), nameof(KickTracker.VotesFromNetworkNumber)); 62 | yield return AccessTools.Method(typeof(LobbyPointCounter), nameof(LobbyPointCounter.handleEvent)); 63 | yield return AccessTools.Method(typeof(PartyBox), nameof(PartyBox.SetPlayerCount)); 64 | yield return AccessTools.Method(typeof(PickableNetworkButton), nameof(PickableNetworkButton.OnAccept)); 65 | yield return AccessTools.Method(typeof(PlayerStatusDisplay), nameof(PlayerStatusDisplay.SetSlotCount)); 66 | yield return AccessTools.Method(typeof(StatTracker), nameof(StatTracker.GetSaveFileDataForLocalPlayer)); 67 | yield return AccessTools.Method(typeof(StatTracker), nameof(StatTracker.OnLocalPlayerAdded)); 68 | yield return AccessTools.Method(typeof(StatTracker), nameof(StatTracker.SaveGameForAnimal)); 69 | yield return AccessTools.Method(typeof(SteamLobbySearchList), nameof(SteamLobbySearchList.checkForListUpdates)); 70 | yield return AccessTools.Method(typeof(SteamMatchmaker), nameof(SteamMatchmaker.createSocialLobby)); 71 | yield return AccessTools.Method(typeof(SwitchController), nameof(SwitchController.Reset)); 72 | yield return AccessTools.Method(typeof(TurnIndicator), nameof(TurnIndicator.SetPlayerCount)); 73 | yield return AccessTools.Method(typeof(UnityMatchmaker), nameof(UnityMatchmaker.CheckHostConnectivity)); 74 | //yield return AccessTools.Method(typeof(UnityMatchmaker), nameof(UnityMatchmaker.CreateUnityMatch)); 75 | yield return AccessTools.Method(typeof(VersusControl), "get_playersLeftToPlace"); 76 | } 77 | 78 | static IEnumerable Transpiler(IEnumerable e) 79 | { 80 | foreach (var inst in e) 81 | { 82 | if (inst.opcode == OpCodes.Ldc_I4_4) 83 | { 84 | inst.opcode = OpCodes.Ldc_I4; 85 | inst.operand = PlayerManager.maxPlayers; 86 | } 87 | yield return inst; 88 | } 89 | } 90 | } 91 | 92 | [HarmonyPatch] 93 | static class SwitchFirst4ForMaxNumPatch 94 | { 95 | static IEnumerable TargetMethods() 96 | { 97 | yield return AccessTools.Method(typeof(PartyBox), nameof(PartyBox.AddPlayer)); 98 | yield return AccessTools.Method(typeof(LobbySkillTracker), nameof(LobbySkillTracker.RecalculateScores)); 99 | yield return AccessTools.Method(typeof(PickableNetworkButton), nameof(PickableNetworkButton.Update)); 100 | } 101 | 102 | static IEnumerable Transpiler(IEnumerable e) 103 | { 104 | var count = 0; 105 | 106 | foreach (var inst in e) 107 | { 108 | if (inst.opcode == OpCodes.Ldc_I4_4 && count == 0) 109 | { 110 | inst.opcode = OpCodes.Ldc_I4; 111 | inst.operand = PlayerManager.maxPlayers; 112 | count += 1; 113 | } 114 | yield return inst; 115 | } 116 | } 117 | } 118 | 119 | [HarmonyPatch] 120 | static class SwitchSecond4ForMaxNumPatch 121 | { 122 | static IEnumerable TargetMethods() 123 | { 124 | yield return AccessTools.Method(typeof(LobbySkillTracker), nameof(LobbySkillTracker.UpdateLobbyInfo)); 125 | yield return AccessTools.Method(typeof(UnityMatchmaker), nameof(UnityMatchmaker.onLobbyJoined)); 126 | } 127 | 128 | static IEnumerable Transpiler(IEnumerable e) 129 | { 130 | var count = 0; 131 | 132 | foreach (var inst in e) 133 | { 134 | if (inst.opcode == OpCodes.Ldc_I4_4) 135 | { 136 | if (count == 1) 137 | { 138 | inst.opcode = OpCodes.Ldc_I4; 139 | inst.operand = PlayerManager.maxPlayers; 140 | } 141 | count += 1; 142 | } 143 | yield return inst; 144 | } 145 | } 146 | } 147 | 148 | [HarmonyPatch] 149 | static class Switch5ForNumPlusOnePatch 150 | { 151 | static IEnumerable TargetMethods() 152 | { 153 | yield return AccessTools.Method(typeof(SteamMatchmaker), nameof(SteamMatchmaker.OnSteamLobbyJoinRequested)); 154 | yield return AccessTools.Method(typeof(LobbyManager), nameof(LobbyManager.OnLobbyClientAddPlayerFailed)); 155 | yield return AccessTools.Method(typeof(Matchmaker), nameof(Matchmaker.CleanUpPlayers)); 156 | } 157 | 158 | static IEnumerable Transpiler(IEnumerable e) 159 | { 160 | foreach (var inst in e) 161 | { 162 | if (inst.opcode == OpCodes.Ldc_I4_4) 163 | { 164 | inst.opcode = OpCodes.Ldc_I4; 165 | inst.operand = PlayerManager.maxPlayers + 1; 166 | } 167 | yield return inst; 168 | } 169 | } 170 | } 171 | 172 | [HarmonyPatch] 173 | static class Switch3ForNumMinusOnePatch 174 | { 175 | static IEnumerable TargetMethods() 176 | { 177 | yield return AccessTools.Method(typeof(Controller), nameof(Controller.GetLastPlayerNumber)); 178 | yield return AccessTools.Method(typeof(Controller), nameof(Controller.GetLastPlayerNumberAfter)); 179 | 180 | yield return AccessTools.Method(typeof(GraphScoreBoard), nameof(GraphScoreBoard.SetPlayerCharacter)); 181 | } 182 | 183 | static IEnumerable Transpiler(IEnumerable e) 184 | { 185 | foreach (var inst in e) 186 | { 187 | if (inst.opcode == OpCodes.Ldc_I4_3) 188 | { 189 | inst.opcode = OpCodes.Ldc_I4; 190 | inst.operand = PlayerManager.maxPlayers - 1; 191 | } 192 | yield return inst; 193 | } 194 | } 195 | } 196 | 197 | [HarmonyPatch(typeof(ChallengeScoreboard), MethodType.Constructor)] 198 | static class ChallengeScoreboardCtorPatch 199 | { 200 | static void Postfix(ChallengeScoreboard __instance) 201 | { 202 | __instance.players = new ChallengeScoreboard.ChallengePlayer[PlayerManager.maxPlayers]; 203 | } 204 | } 205 | 206 | [HarmonyPatch(typeof(Tablet), MethodType.Constructor)] 207 | static class TabletCtorPatch 208 | { 209 | static void Postfix(Tablet __instance) 210 | { 211 | __instance.untrackedCursors = new List(PlayerManager.maxPlayers); 212 | } 213 | } 214 | 215 | [HarmonyPatch(typeof(Controller), MethodType.Constructor)] 216 | static class ControllerCtorPatch 217 | { 218 | static void Postfix(Controller __instance) 219 | { 220 | __instance.associatedChars = new Character.Animals[PlayerManager.maxPlayers]; 221 | } 222 | } 223 | 224 | [HarmonyPatch(typeof(Controller), nameof(Controller.ClearPlayers))] 225 | static class ControllerClearPlayersPatch 226 | { 227 | static void Postfix(Controller __instance) 228 | { 229 | __instance.associatedChars = new Character.Animals[PlayerManager.maxPlayers]; 230 | } 231 | } 232 | 233 | [HarmonyPatch(typeof(Controller), nameof(Controller.RemovePlayer))] 234 | static class ControllerRemovePlayerPatch 235 | { 236 | static bool Prefix(Controller __instance, int player) 237 | { 238 | // remove player bit from bitmask 239 | var num = ~(1 << (player - 1)); 240 | __instance.Player &= num; 241 | __instance.associatedChars[player - 1] = Character.Animals.NONE; 242 | if (__instance.Player == 0) 243 | { 244 | __instance.PossibleNetWorkNumber = 0; 245 | } 246 | return false; 247 | } 248 | } 249 | 250 | [HarmonyPatch(typeof(GameState), MethodType.Constructor)] 251 | static class GameStateCtorPatch 252 | { 253 | static void Postfix(GameState __instance) 254 | { 255 | __instance.PlayerScores = new int[PlayerManager.maxPlayers]; 256 | } 257 | } 258 | 259 | [HarmonyPatch(typeof(GraphScoreBoard), nameof(GraphScoreBoard.SetPlayerCount))] 260 | static class GraphScoreBoardCtorPatch 261 | { 262 | static bool Prefix(GraphScoreBoard __instance, int numberPlayers) 263 | { 264 | Array.Resize(ref __instance.ScorePositions, PlayerManager.maxPlayers); 265 | 266 | __instance.playerScoreLines = new ScoreLine[numberPlayers]; 267 | 268 | Debug.Log("GraphScoreBoard.SetPlayerCount"); 269 | Vector3 vector = __instance.ScorePositions[0].position + new Vector3(0f, 1.25f, 0f); 270 | for (int num = 0; num != numberPlayers; num++) 271 | { 272 | /* add ScorePositions for additional players */ 273 | GameObject gameObject = UnityEngine.Object.Instantiate(__instance.scoreLinePrefab.gameObject, vector - new Vector3(0f, (float)num * 1.25f, 0f), Quaternion.identity); 274 | gameObject.transform.SetParent(__instance.mainParent); 275 | gameObject.transform.localScale = new Vector3(1f, 0.5f, 1f); 276 | __instance.playerScoreLines[num] = gameObject.GetComponent(); 277 | __instance.playerScoreLines[num].scoreBoardParent = __instance; 278 | } 279 | 280 | // skip function 281 | return false; 282 | } 283 | } 284 | 285 | [HarmonyPatch(typeof(LevelSelectController), nameof(LevelSelectController.Awake))] 286 | static class LevelSelectControllerCtorPatch 287 | { 288 | static void Postfix(LevelSelectController __instance) 289 | { 290 | Debug.Log("fixed LevelSelectController"); 291 | __instance.JoinedPlayers = new LobbyPlayer[PlayerManager.maxPlayers]; 292 | 293 | if (__instance.PlayerJoinIndicators.Length < PlayerManager.maxPlayers) 294 | { 295 | int num = __instance.PlayerJoinIndicators.Length; 296 | Array.Resize(ref __instance.PlayerJoinIndicators, PlayerManager.maxPlayers); 297 | for (int i = num; i < __instance.PlayerJoinIndicators.Length; i++) 298 | { 299 | /* map PlayerJoinIndicators to 0..3, we reuse existing 4 indicators for multiple players */ 300 | __instance.PlayerJoinIndicators[i] = __instance.PlayerJoinIndicators[i % num]; 301 | } 302 | } 303 | 304 | Debug.Log("CursorSpawnPoint.Length " + __instance.CursorSpawnPoint.Length); 305 | if (__instance.CursorSpawnPoint.Length < PlayerManager.maxPlayers) 306 | { 307 | int num = __instance.CursorSpawnPoint.Length; 308 | Array.Resize(ref __instance.CursorSpawnPoint, PlayerManager.maxPlayers); 309 | for (int i = num; i < __instance.CursorSpawnPoint.Length; i++) 310 | { 311 | /* map CursorSpawnPoint to 0..3 312 | TODO: add new positions, so cursors aren't hidden behind each other on spawn 313 | */ 314 | __instance.CursorSpawnPoint[i] = __instance.CursorSpawnPoint[i % num]; 315 | } 316 | } 317 | 318 | Debug.Log("UndergroundCharacterPosition.Length " + __instance.UndergroundCharacterPosition.Length); 319 | if (__instance.UndergroundCharacterPosition.Length < PlayerManager.maxPlayers) 320 | { 321 | int num = __instance.UndergroundCharacterPosition.Length; 322 | Array.Resize(ref __instance.UndergroundCharacterPosition, PlayerManager.maxPlayers); 323 | for (int i = num; i < __instance.UndergroundCharacterPosition.Length; i++) 324 | { 325 | /* map UndergroundCharacterPosition to 0..3 326 | TODO: add new positions 327 | */ 328 | __instance.UndergroundCharacterPosition[i] = __instance.UndergroundCharacterPosition[i % num]; 329 | } 330 | } 331 | } 332 | } 333 | 334 | [HarmonyPatch(typeof(LobbyPointCounter), MethodType.Constructor)] 335 | static class LobbyPointCounterCtorPatch 336 | { 337 | static void Postfix(LobbyPointCounter __instance) 338 | { 339 | __instance.playerJoinedGame = new bool[PlayerManager.maxPlayers]; 340 | __instance.playerPlayedGame = new bool[PlayerManager.maxPlayers]; 341 | __instance.playerAFK = new bool[PlayerManager.maxPlayers]; 342 | } 343 | } 344 | 345 | [HarmonyPatch(typeof(LobbyPointCounter), nameof(LobbyPointCounter.Reset))] 346 | static class LobbyPointCounterResetCtorPatch 347 | { 348 | static void Postfix(LobbyPointCounter __instance) 349 | { 350 | __instance.playerPlayedGame = new bool[PlayerManager.maxPlayers]; 351 | } 352 | } 353 | 354 | [HarmonyPatch(typeof(NetworkLobbyManager), MethodType.Constructor)] 355 | static class NetworkLobbyManagerCtorPatch 356 | { 357 | static void Postfix(NetworkLobbyManager __instance) 358 | { 359 | __instance.maxPlayers = PlayerManager.maxPlayers; 360 | } 361 | } 362 | 363 | [HarmonyPatch(typeof(LobbySkillTracker), nameof(LobbySkillTracker.Start))] 364 | static class LobbySkillTrackerCtorPatch 365 | { 366 | static void Postfix(LobbySkillTracker __instance) 367 | { 368 | Debug.Log("LobbySkillTracker patch " + __instance.ratings.Length); 369 | __instance.ratings = new Moserware.Skills.Rating[PlayerManager.maxPlayers]; 370 | } 371 | } 372 | 373 | [HarmonyPatch(typeof(VersusControl), MethodType.Constructor)] 374 | static class VersusControlCtorPatch 375 | { 376 | static void Postfix(VersusControl __instance) 377 | { 378 | Debug.Log("patch VersusControl " + PlayerManager.maxPlayers); 379 | __instance.winOrder = new GamePlayer[PlayerManager.maxPlayers]; 380 | __instance.RemainingPlacements = new int[PlayerManager.maxPlayers]; 381 | } 382 | } 383 | 384 | [HarmonyPatch(typeof(VersusControl), nameof(VersusControl.ShuffleStartPosition))] 385 | public class VersusControlShuffleStartPositionCtorPatch 386 | { 387 | static bool Prefix(VersusControl __instance) 388 | { 389 | List list = new List(); 390 | for (int i = 0; i < __instance.PlayerQueue.Count; i++) 391 | { 392 | list.Add((i % 4) + 1); 393 | } 394 | var randr = ""; 395 | for (int j = 0; j < __instance.PlayerQueue.Count; j++) 396 | { 397 | int index = UnityEngine.Random.Range(0, list.Count); 398 | int num2 = list[index]; 399 | randr += num2.ToString(); 400 | list.RemoveAt(index); 401 | } 402 | 403 | __instance.NetworkRandomStartPositionString = randr; 404 | 405 | return false; 406 | } 407 | } 408 | 409 | [HarmonyPatch(typeof(LobbyManager), nameof(LobbyManager.Awake))] 410 | static class LobbyManagerCtorPatch 411 | { 412 | static void Postfix(LobbyManager __instance) 413 | { 414 | Debug.Log("LobbyManager.instance.lobbySlots " + __instance.lobbySlots.Length); 415 | 416 | __instance.maxPlayers = PlayerManager.maxPlayers; 417 | __instance.maxPlayersPerConnection = PlayerManager.maxPlayers; 418 | 419 | if (__instance.lobbySlots.Length < PlayerManager.maxPlayers) 420 | { 421 | Array.Resize(ref __instance.lobbySlots, PlayerManager.maxPlayers); 422 | } 423 | } 424 | } 425 | 426 | [HarmonyPatch(typeof(GameSettings), nameof(GameSettings.GetInstance))] 427 | static class GameSettingsCtorPatch 428 | { 429 | static void Postfix(GameSettings __result) 430 | { 431 | __result.MaxPlayers = PlayerManager.maxPlayers; 432 | 433 | if (__result.PlayerColors.Length < PlayerManager.maxPlayers) 434 | { 435 | int num2 = __result.PlayerColors.Length; 436 | Array.Resize(ref __result.PlayerColors, PlayerManager.maxPlayers); 437 | Color[] playerColors = __result.PlayerColors; 438 | for (int j = num2; j < playerColors.Length; j++) 439 | { 440 | /* Random generate colors for new players */ 441 | __result.PlayerColors[j] = new Color(UnityEngine.Random.Range(0f, 1f), UnityEngine.Random.Range(0f, 1f), UnityEngine.Random.Range(0f, 1f)); 442 | } 443 | } 444 | 445 | } 446 | } 447 | 448 | [HarmonyPatch(typeof(InventoryBook), nameof(InventoryBook.AddPlayer))] 449 | static class InventoryBookCtorPatch 450 | { 451 | static void Prefix(InventoryBook __instance) 452 | { 453 | if (__instance.cursorSpawnLocation.Length < PlayerManager.maxPlayers) 454 | { 455 | int num2 = __instance.cursorSpawnLocation.Length; 456 | Array.Resize(ref __instance.cursorSpawnLocation, PlayerManager.maxPlayers); 457 | for (int j = num2; j < __instance.cursorSpawnLocation.Length; j++) 458 | { 459 | __instance.cursorSpawnLocation[j] = __instance.cursorSpawnLocation[0]; 460 | } 461 | 462 | } 463 | Debug.Log("InventoryBook cursorSpawnLocation patched"); 464 | } 465 | } 466 | 467 | [HarmonyPatch(typeof(ControllerDisconnect), nameof(ControllerDisconnect.Start))] 468 | static class ControllerDisconnectCtorPatch 469 | { 470 | static void Prefix(ControllerDisconnect __instance) 471 | { 472 | var oglen = __instance.ConnectPrompts.Length; 473 | Array.Resize(ref __instance.ConnectPrompts, PlayerManager.maxPlayers); 474 | 475 | for (var i = oglen; i < PlayerManager.maxPlayers; i++) 476 | { 477 | __instance.ConnectPrompts[i] = __instance.ConnectPrompts[0]; 478 | } 479 | 480 | if (__instance.orphanedReceivers.Length != PlayerManager.maxPlayers) 481 | { 482 | var og_len = __instance.orphanedReceivers.Length; 483 | Array.Resize(ref __instance.orphanedReceivers, PlayerManager.maxPlayers); 484 | for (var i = og_len; i < __instance.orphanedReceivers.Length; i++) 485 | { 486 | __instance.orphanedReceivers[i] = new List(); 487 | } 488 | } 489 | ControllerDisconnect.showingPrompts = new bool[PlayerManager.maxPlayers]; 490 | __instance.orphanedCharacters = new Character.Animals[PlayerManager.maxPlayers][]; 491 | 492 | Debug.Log("ControllerDisconnect patched"); 493 | } 494 | } 495 | 496 | [HarmonyPatch(typeof(InputManager), "get_EnableNativeInput")] 497 | static class InputManagerCtorPatch 498 | { 499 | static void Postfix(ref bool __result) 500 | { 501 | Debug.Log("InputManager.EnableNativeInput"); 502 | __result = true; 503 | } 504 | } 505 | 506 | [HarmonyPatch(typeof(InputManager), "get_NativeInputEnableXInput")] 507 | static class NativeInputEnableXInputInputManagerCtorPatch 508 | { 509 | static void Postfix(ref bool __result) 510 | { 511 | Debug.Log("InputManager.NativeInputEnableXInput"); 512 | __result = false; 513 | } 514 | } 515 | 516 | [HarmonyPatch(typeof(LevelPortal), nameof(LevelPortal.Awake))] 517 | static class LevelPortalCtorPatch 518 | { 519 | static void Prefix(LevelPortal __instance) 520 | { 521 | VoteArrow[] componentsInChildren = __instance.GetComponentsInChildren(); 522 | if (componentsInChildren.Length != PlayerManager.maxPlayers) 523 | { 524 | int num = componentsInChildren.Length; 525 | 526 | for (int j = num; j < PlayerManager.maxPlayers; j++) 527 | { 528 | Type type = componentsInChildren[3].GetType(); 529 | VoteArrow voteArrow2 = componentsInChildren[3].gameObject.AddComponent(type) as VoteArrow; 530 | foreach (FieldInfo fieldInfo in type.GetFields()) 531 | { 532 | fieldInfo.SetValue(voteArrow2, fieldInfo.GetValue(componentsInChildren[3])); 533 | } 534 | } 535 | } 536 | } 537 | } 538 | 539 | [HarmonyPatch(typeof(StatTracker), MethodType.Constructor)] 540 | static class StatTrackerCtorPatch 541 | { 542 | static void Postfix(StatTracker __instance) 543 | { 544 | Debug.Log("patch StatTracker " + PlayerManager.maxPlayers); 545 | __instance.saveFiles = new SaveFileData[PlayerManager.maxPlayers]; 546 | __instance.saveStatuses = new StatTracker.SaveFileStatus[PlayerManager.maxPlayers]; 547 | } 548 | } 549 | 550 | [HarmonyPatch(typeof(GameSparksQuery), nameof(GameSparksQuery.DoGetLobbyData))] 551 | static class GameSparksQueryLobbyCtorPatch 552 | { 553 | static void Prefix(ref bool reserveSlot) 554 | { 555 | Debug.Log("reserveSlot " + reserveSlot); 556 | reserveSlot = false; 557 | } 558 | } 559 | 560 | [HarmonyPatch(typeof(LivesDisplayController), nameof(LivesDisplayController.Initialize))] 561 | static class LivesDisplayControllerCtorPatch 562 | { 563 | static void Prefix(LivesDisplayController __instance) 564 | { 565 | Debug.Log("LivesDisplayController patch " + __instance.livesDisplayBoxes.Count); 566 | if (__instance.livesDisplayBoxes.Count < PlayerManager.maxPlayers) 567 | { 568 | var og_len = __instance.livesDisplayBoxes.Count; 569 | for (var i = og_len; i <= PlayerManager.maxPlayers; i++) 570 | { 571 | __instance.livesDisplayBoxes.Add(__instance.livesDisplayBoxes[0]); 572 | } 573 | 574 | } 575 | } 576 | } 577 | 578 | [HarmonyPatch(typeof(PlayerStatusDisplay), nameof(PlayerStatusDisplay.SetupSlot))] 579 | [HarmonyPatch(typeof(PlayerStatusDisplay), nameof(PlayerStatusDisplay.SetSlot))] 580 | [HarmonyPatch(typeof(PlayerStatusDisplay), nameof(PlayerStatusDisplay.SetSlotCount))] 581 | static class PlayerStatusDisplaySetSlotCountCtorPatch 582 | { 583 | static void Prefix(PlayerStatusDisplay __instance) 584 | { 585 | Debug.Log("PlayerStatusDisplay patch " + __instance.Slots.Length); 586 | if (__instance.Slots.Length < PlayerManager.maxPlayers) 587 | { 588 | var og_len = __instance.Slots.Length; 589 | Array.Resize(ref __instance.Slots, PlayerManager.maxPlayers); 590 | for (var i = og_len; i < PlayerManager.maxPlayers; i++) 591 | { 592 | __instance.Slots[i] = __instance.Slots[0]; 593 | } 594 | 595 | } 596 | } 597 | } 598 | 599 | [Obsolete] 600 | [HarmonyPatch(typeof(NetworkManager), nameof(NetworkManager.StartServer), new Type[] { typeof(ConnectionConfig), typeof(int) })] 601 | static class NetworkManagerCtorPatch 602 | { 603 | static void Prefix(NetworkManager __instance, int maxConnections) 604 | { 605 | Debug.Log("NetworkManager StartServer " + __instance.maxConnections + " param maxConnections " + maxConnections); 606 | __instance.maxConnections = PlayerManager.maxPlayers; 607 | } 608 | } 609 | 610 | [HarmonyPatch(typeof(PickableNetworkButton), nameof(PickableNetworkButton.SetSearchResultInfo))] 611 | static class PickableNetworkButtonCtorPatch 612 | { 613 | static void Postfix(PickableNetworkButton __instance, Matchmaker.LobbyListInfo lobbyInfo) 614 | { 615 | __instance.NumPlayersText.text = lobbyInfo.Players.ToString() + "/?"; 616 | } 617 | } 618 | 619 | [HarmonyPatch(typeof(GameControl), nameof(GameControl.Awake))] 620 | static class GameControlCtorPatch 621 | { 622 | static void Postfix(GameControl __instance) 623 | { 624 | if (__instance.showScoreButtons.Length < PlayerManager.maxPlayers) 625 | { 626 | Array.Resize(ref __instance.showScoreButtons, PlayerManager.maxPlayers); 627 | } 628 | } 629 | } 630 | 631 | [HarmonyPatch(typeof(GameControl), nameof(GameControl.ReceiveEvent))] 632 | static class GameControlReceiveEventCtorPatch 633 | { 634 | static void Prefix(GameControl __instance, InputEvent e) 635 | { 636 | 637 | __instance.inputPlayerNumber = 0; 638 | //TODO: Fix overflow int for more than 94 players 639 | for (var i = 0; i < PlayerManager.maxPlayers && i < 95; i++) 640 | { 641 | if ((e.PlayerBitMask & (1 << i)) == (1 << i)) 642 | { 643 | __instance.inputPlayerNumber = i + 1; 644 | break; 645 | } 646 | } 647 | } 648 | } 649 | 650 | [HarmonyPatch] 651 | static class GameControlReceiveEventDropInputPlayerNumber0Patch 652 | { 653 | static IEnumerable TargetMethods() 654 | { 655 | yield return AccessTools.Method(typeof(GameControl), nameof(GameControl.ReceiveEvent)); 656 | } 657 | 658 | static IEnumerable Transpiler(IEnumerable e) 659 | { 660 | var done = false; 661 | 662 | foreach (var inst in e) 663 | { 664 | if (!done && inst.opcode != OpCodes.Ldarg_1) 665 | { 666 | // NOP out this.inputPlayerNumber = 0; 667 | inst.opcode = OpCodes.Nop; 668 | } 669 | else 670 | { 671 | done = true; 672 | } 673 | yield return inst; 674 | } 675 | } 676 | } 677 | 678 | } --------------------------------------------------------------------------------