diff --git a/README.md b/README.md index 427a283..54108eb 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,65 @@ -[Our Discord](https://discord.townofus.pl) +
# AleLuduMod -This mod unlocks the possibility to show more than 15 players on screen during an emergency meeting, on vitals, and on the Transporter/Glitch screen. + +[![Discord](https://img.shields.io/badge/Discord-Join%20Server-5662f6?style=for-the-badge&logo=discord&logoColor=white)](https://discord.townofus.pl) +[![Download](https://img.shields.io/badge/Download-Latest-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/raspberrygitq/AleLuduMod/releases/latest) + +![GitHub Downloads](https://img.shields.io/github/downloads/raspberrygitq/AleLuduMod/total.svg?style=flat-square) +![GitHub Release](https://img.shields.io/github/release/raspberrygitq/AleLuduMod.svg?style=flat-square) + +
+ +# +This mod unlocks the possibility to show more than 15 players on screen during an meeting hud, vitals and shapeshifter menu. Works on **latest** Among Us Steam version. +> [!Warning] +> AleLuduMod **doesn't** work with official servers from Innersloth. It is recommended to use modded servers. + ## Presentation ![AleLuduModEmergencyMeeting](https://github.com/townofus-pl/AleLuduMod/blob/main/aleludumeeting.png) ## Versions -| Mod version | Downloads | Notes | -|-------------|---------------------------------------------------------------------------------------------|-------| -| v1.0.6 | [GitHub](https://github.com/townofus-pl/AleLuduMod/releases/download/v1.0.6/AleLuduMod.dll) | | -| v1.0.5.beta | [GitHub](https://github.com/townofus-pl/AleLuduMod/releases/download/v1.0.5.beta/AleLuduMod.dll) | Doesn't include creating 15+ players lobby. | -| v1.0.4 | [GitHub](https://github.com/townofus-pl/AleLuduMod/releases/download/v1.0.4/AleLuduMod.dll) | | -| v1.0.3 | [GitHub](https://github.com/townofus-pl/AleLuduMod/releases/download/v1.0.3/AleLuduMod.dll) | | -| v1.0.2 | [GitHub](https://github.com/townofus-pl/AleLuduMod/releases/download/v1.0.2/AleLuduMod.dll) | | -| v1.0.1 | [GitHub](https://github.com/townofus-pl/AleLuduMod/releases/download/v1.0.1/AleLuduMod.dll) | | -| v1.0.0 | [GitHub](https://github.com/townofus-pl/AleLuduMod/releases/download/v1.0.0/AleLuduMod.dll) | | + +| Mod Version | Among Us - Version | Downloads | +|-------------|--------------------|-------------------------------------------------------------------------------------------------| +| v1.1.2 | 17.0.0 - 17.2.2 | [Download](https://github.com/raspberrygitq/AleLuduMod/releases/download/v1.1.2/AleLuduMod.dll) | +| v1.1.1 | 17.0.0 - 17.2.1 | [Download](https://github.com/raspberrygitq/AleLuduMod/releases/download/v1.1.1/AleLuduMod.dll) | +| v1.1.0 | 16.0.0 - 16.1.0 | [Download](https://github.com/raspberrygitq/AleLuduMod/releases/download/v1.1.0/AleLuduMod.dll) | + +
+ Archive + +| Mod version | Among Us - Version | Downloads | +|-------------|--------------------|---------------------------------------------------------------------------------------------| +| v1.0.6 | 16.0.2 - 16.1.0 | [Download](https://github.com/townofus-pl/AleLuduMod/releases/download/v1.0.6/AleLuduMod.dll) | +| v1.0.5.beta | 16.0.0 - 16.0.2 | [Download](https://github.com/townofus-pl/AleLuduMod/releases/download/v1.0.5.beta/AleLuduMod.dll) | +| v1.0.4 | 2024.11.26 | [Download](https://github.com/townofus-pl/AleLuduMod/releases/download/v1.0.4/AleLuduMod.dll) | +| v1.0.3 | 2024.11.26 | [Download](https://github.com/townofus-pl/AleLuduMod/releases/download/v1.0.3/AleLuduMod.dll) | +| v1.0.2 | 2024.11.26 | [Download](https://github.com/townofus-pl/AleLuduMod/releases/download/v1.0.2/AleLuduMod.dll) | +| v1.0.1 | 2024.11.26 | [Download](https://github.com/townofus-pl/AleLuduMod/releases/download/v1.0.1/AleLuduMod.dll) | +| v1.0.0 | 2024.11.26 | [Download](https://github.com/townofus-pl/AleLuduMod/releases/download/v1.0.0/AleLuduMod.dll) | +
## Installation -Drop it into `\BepInEx\plugins\` folder inside your Among Us + TownOfUs folder installation. +Drop `AleLuduMod.dll` it the **`BepInEx\plugins`** folder with other mods or download the zip package. +> [!Warning] +> The mod needs [BepInEx](https://builds.bepinex.dev/projects/bepinex_be) and [Reactor](https://github.com/nuclearpowered/reactor) to work properly. While **BepInEx** is used by every mod and shouldn't be a problem, without **Reactor** you can't launch the mod. + +## Mod Compatibility + +AleLuduMod should be compatible with most mods that don't significantly alter interface in the meetings / vitals / shapeshifter menus. +> [!Caution] +> Host-only mods that use code from CrowdedMod are incompatible. CrowdedMod itself is **incompatible** with AleLuduMod because it modifies the interface in the same way as AleLuduMod. ## Commands - `!limit ` - Set the maximum number of players in lobby (from 4 to 35). ## Authors -- [TownOfUs.pl](http://townofus.pl/) +- [TownOfUs.pl](https://townofus.pl/) - OrzechMC - Bubeu - gitq @@ -35,4 +67,7 @@ Drop it into `\BepInEx\plugins\` folder inside your Among Us + TownOfUs folder i ## Credits - [CrowdedMods](https://github.com/CrowdedMods/CrowdedMod) - current authors of CrowdedMod that we used as a base for our mod - [andry08/100-player-mod](https://github.com/andry08/100-player-mod) - original author of the CrowdedMod plugin (andry08) - +--- +> This mod is not affiliated with Among Us or Innersloth LLC, and the content contained therein is not endorsed or otherwise sponsored by Innersloth LLC. Portions of the materials contained herein are property of Innersloth LLC.\ +> © Innersloth LLC.\ +> [License](./LICENSE) diff --git a/src/AleLuduMod/AleLuduMod.csproj b/src/AleLuduMod/AleLuduMod.csproj index 854a6e8..89a74b3 100644 --- a/src/AleLuduMod/AleLuduMod.csproj +++ b/src/AleLuduMod/AleLuduMod.csproj @@ -2,7 +2,7 @@ net6.0 - 1.1.0 + 1.1.2 townofus.pl, OrzechMC, Bubeu, gitq latest @@ -11,16 +11,21 @@ Steam - 2025.3.25 + 2025.9.9 - + - + + + + + + diff --git a/src/AleLuduMod/AleLuduModPlugin.cs b/src/AleLuduMod/AleLuduModPlugin.cs index 1502d84..e091921 100644 --- a/src/AleLuduMod/AleLuduModPlugin.cs +++ b/src/AleLuduMod/AleLuduModPlugin.cs @@ -1,10 +1,9 @@ -using AmongUs.GameOptions; -using BepInEx; +using BepInEx; +using BepInEx.Configuration; using BepInEx.Unity.IL2CPP; using HarmonyLib; using Reactor; -using BepInEx.Configuration; -using System.Linq; +using Reactor.Utilities; namespace AleLuduMod; @@ -12,19 +11,18 @@ namespace AleLuduMod; [BepInProcess("Among Us.exe")] [BepInDependency(ReactorPlugin.Id)] [BepInDependency("gg.reactor.debugger", BepInDependency.DependencyFlags.SoftDependency)] // fix debugger overwriting MinPlayers -[BepInDependency("com.slushiegoose.townofus", BepInDependency.DependencyFlags.SoftDependency)] // load after town of us public partial class AleLuduModPlugin : BasePlugin { - public const int MaxPlayers = 36; - public const int MaxImpostors = 36 / 2; + public const int MaxPlayers = 35; + public const int MaxImpostors = MaxPlayers / 2; public static ConfigEntry Force4Columns { get; set; } private Harmony Harmony { get; } = new(Id); public override void Load() - { - NormalGameOptionsV09.RecommendedImpostors = NormalGameOptionsV09.MaxImpostors = Enumerable.Repeat(36, 36).ToArray(); - NormalGameOptionsV09.MinPlayers = Enumerable.Repeat(4, 36).ToArray(); - HideNSeekGameOptionsV09.MinPlayers = Enumerable.Repeat(4, 36).ToArray(); + { + ReactorCredits.Register(ReactorCredits.AlwaysShow); + + IL2CPPChainloader.Instance.Finished += ModCompatibility.Initialize; Force4Columns = Config.Bind("Settings", "Force 4 columns", true, "Always display 4 columns in meeting, vitals, etc."); diff --git a/src/AleLuduMod/Components/MeetingHudBehaviour.cs b/src/AleLuduMod/Components/MeetingHudBehaviour.cs index ec3d4ce..1f6b037 100644 --- a/src/AleLuduMod/Components/MeetingHudBehaviour.cs +++ b/src/AleLuduMod/Components/MeetingHudBehaviour.cs @@ -10,10 +10,7 @@ namespace AleLuduMod.Components; [RegisterInIl2Cpp] public class MeetingHudBehaviour : MonoBehaviour { - public MeetingHudBehaviour(IntPtr ptr) : base(ptr) - { - } - + public MeetingHudBehaviour(IntPtr ptr) : base(ptr) { } internal MeetingHud meetingHud = null!; [HideFromIl2Cpp] @@ -21,31 +18,21 @@ public MeetingHudBehaviour(IntPtr ptr) : base(ptr) public void Start() { - - if (Targets.Count() < 16 && !AleLuduModPlugin.Force4Columns.Value) - { - // dont change layout if players count is below 16 - return; - } - + if (Targets.Count() < 16 && !AleLuduModPlugin.Force4Columns.Value) return; // dont change layout if players count is below 16 var i = 0; foreach (var button in Targets) { - button.gameObject.SetActive(true); - var row = i / 4; var col = i % 4; var buttonTransform = button.transform; buttonTransform.localScale *= 0.75f; - buttonTransform.localPosition = meetingHud.VoteOrigin + - new Vector3( - meetingHud.VoteButtonOffsets.x * col * 0.75f - 0.375f, - meetingHud.VoteButtonOffsets.y * row * 0.75f, - buttonTransform.localPosition.z - ); + buttonTransform.localPosition = meetingHud.VoteOrigin + new Vector3( + meetingHud.VoteButtonOffsets.x * col * 0.75f - 0.375f, + meetingHud.VoteButtonOffsets.y * row * 0.75f, + buttonTransform.localPosition.z + ); i++; } } - -} +} \ No newline at end of file diff --git a/src/AleLuduMod/Components/ShapeshifterMenuBehaviour.cs b/src/AleLuduMod/Components/ShapeshifterMenuBehaviour.cs index b52a698..aa7cf73 100644 --- a/src/AleLuduMod/Components/ShapeshifterMenuBehaviour.cs +++ b/src/AleLuduMod/Components/ShapeshifterMenuBehaviour.cs @@ -1,8 +1,8 @@ -using System; +using Il2CppInterop.Runtime.Attributes; +using Reactor.Utilities.Attributes; +using System; using System.Collections.Generic; using System.Linq; -using Il2CppInterop.Runtime.Attributes; -using Reactor.Utilities.Attributes; using UnityEngine; namespace AleLuduMod.Components; @@ -10,27 +10,19 @@ namespace AleLuduMod.Components; [RegisterInIl2Cpp] public class ShapeshifterBehaviour : MonoBehaviour { - public ShapeshifterBehaviour(IntPtr ptr) : base(ptr) - { - } + public ShapeshifterBehaviour(IntPtr ptr) : base(ptr) { } + internal ShapeshifterMinigame shapeshifterMinigame = null!; - public ShapeshifterMinigame shapeshifterMinigame = null!; - [HideFromIl2Cpp] public IEnumerable Targets => shapeshifterMinigame.potentialVictims.ToArray(); + [HideFromIl2Cpp] + public IEnumerable Targets => shapeshifterMinigame.potentialVictims.ToArray(); public void Start() { - if (Targets.Count() < 16 && !AleLuduModPlugin.Force4Columns.Value) - { - // dont change layout if players count is below 16 - return; - } - + if (Targets.Count() < 16 && !AleLuduModPlugin.Force4Columns.Value) return; // dont change layout if players count is below 16 var i = 0; - foreach (var panel in Targets) { panel.gameObject.SetActive(true); - var row = i / 4; var col = i % 4; var buttonTransform = panel.transform; @@ -39,7 +31,7 @@ public void Start() shapeshifterMinigame.XStart + shapeshifterMinigame.XOffset * col * 0.75f - 0.375f, shapeshifterMinigame.YStart + shapeshifterMinigame.YOffset * row * 0.75f, buttonTransform.localPosition.z - ); + ); i++; } } diff --git a/src/AleLuduMod/Components/VitalsHudBehaviour.cs b/src/AleLuduMod/Components/VitalsHudBehaviour.cs index c0e4d46..8329d9c 100644 --- a/src/AleLuduMod/Components/VitalsHudBehaviour.cs +++ b/src/AleLuduMod/Components/VitalsHudBehaviour.cs @@ -10,8 +10,7 @@ namespace AleLuduMod.Components; [RegisterInIl2Cpp] public class VitalsHudBehaviour : MonoBehaviour { - public VitalsHudBehaviour(IntPtr ptr) : base(ptr) {} - + public VitalsHudBehaviour(IntPtr ptr) : base(ptr) { } internal VitalsMinigame vitalsMinigame = null!; [HideFromIl2Cpp] @@ -19,13 +18,7 @@ public VitalsHudBehaviour(IntPtr ptr) : base(ptr) {} public void Start() { - - if (Targets.Count() < 16 && !AleLuduModPlugin.Force4Columns.Value) - { - // dont change layout if players count is below 16 - return; - } - + if (Targets.Count() < 16 && !AleLuduModPlugin.Force4Columns.Value) return; // dont change layout if players count is below 16 var i = 0; foreach (var panel in Targets) { @@ -35,11 +28,11 @@ public void Start() var panelTransform = panel.transform; panelTransform.localScale *= 0.75f; panelTransform.localPosition = new Vector3( - vitalsMinigame.XStart + vitalsMinigame.XOffset * (col * 0.75f - 0.25f), - vitalsMinigame.YStart + vitalsMinigame.YOffset * row * 0.75f, - panelTransform.localPosition.z - ); + vitalsMinigame.XStart + vitalsMinigame.XOffset * (col * 0.75f - 0.25f), + vitalsMinigame.YStart + vitalsMinigame.YOffset * row * 0.75f, + panelTransform.localPosition.z + ); i++; } } -} +} \ No newline at end of file diff --git a/src/AleLuduMod/ModCompatibility.cs b/src/AleLuduMod/ModCompatibility.cs new file mode 100644 index 0000000..7504571 --- /dev/null +++ b/src/AleLuduMod/ModCompatibility.cs @@ -0,0 +1,75 @@ +using BepInEx.Unity.IL2CPP; +using HarmonyLib; +using Reactor.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace AleLuduMod; + +public static class ModCompatibility +{ + public const string MiraApiGuid = "mira.api"; + public static bool MiraApiLoaded { get; private set; } + private static BasePlugin MiraApiPlugin { get; set; } + private static Assembly MiraApiAssembly { get; set; } + private static Type[] MiraApiTypes { get; set; } + public static float XStart = -0.8f; + public static float YStart = 2.15f; + public static float XOffset = 1.95f; + public static float YOffset = -0.65f; + + public static void Initialize() + { + InitMiraApi(); + } + + private static void InitMiraApi() + { + if (!IL2CPPChainloader.Instance.Plugins.TryGetValue(MiraApiGuid, out var value)) + { + return; + } + + MiraApiPlugin = (value.Instance as BasePlugin)!; + MiraApiAssembly = MiraApiPlugin.GetType().Assembly; + + MiraApiTypes = AccessTools.GetTypesFromAssembly(MiraApiAssembly); + + var playerMenu = MiraApiTypes.First(t => t.Name == "CustomPlayerMenu"); + var menuBegin = AccessTools.Method(playerMenu, "Begin", new[] { typeof(Func), typeof(Action) }); + + var compatType = typeof(ModCompatibility); + var harmony = new Harmony("aleludu.miraapi.patch"); + harmony.Patch(menuBegin, null, + new HarmonyMethod(AccessTools.Method(compatType, nameof(BeginPostfix)))); + + MiraApiLoaded = true; + Logger.Message("MiraAPI was detected and patched"); + } + public static void BeginPostfix(dynamic __instance, Func playerMatch, Action onClick) + { + var targets = __instance.potentialVictims as List; + if (targets == null || targets.Count() < 16 && !AleLuduModPlugin.Force4Columns.Value) return; + + var i = 0; + + foreach (var panel in targets) + { + panel.gameObject.SetActive(true); + + var row = i / 4; + var col = i % 4; + var buttonTransform = panel.transform; + buttonTransform.localScale *= 0.75f; + buttonTransform.localPosition = new Vector3( + XStart + XOffset * col * 1.1f - 2.4f, + YStart + YOffset * row * 0.9255f - 0.65f, + buttonTransform.localPosition.z + ); + i++; + } + } +} \ No newline at end of file diff --git a/src/AleLuduMod/Patches/ChatCommands.cs b/src/AleLuduMod/Patches/ChatCommands.cs index 1f2c481..80a5265 100644 --- a/src/AleLuduMod/Patches/ChatCommands.cs +++ b/src/AleLuduMod/Patches/ChatCommands.cs @@ -12,15 +12,23 @@ public class ChatCommands [HarmonyPatch(typeof(ChatController), nameof(ChatController.AddChat))] public class Commands { - public static bool Prefix(ChatController __instance, [HarmonyArgument(0)] PlayerControl sourcePlayer , ref string chatText) + public static bool Prefix(ChatController __instance, [HarmonyArgument(0)] PlayerControl sourcePlayer, ref string chatText) { - if (__instance != HudManager.Instance.Chat) - return true; + if (__instance != HudManager.Instance.Chat) return true; // After entering the command, when you try to join the lobby it will show "X/15". Only after the game is over will there be a larger lobby. if (chatText.StartsWith("!limit ")) { - if (GameData.Instance.GetHost() == sourcePlayer.Data) + if ((AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started || AmongUsClient.Instance.NetworkMode == NetworkModes.FreePlay) && sourcePlayer.PlayerId == PlayerControl.LocalPlayer.PlayerId) + { + if (sourcePlayer.PlayerId == PlayerControl.LocalPlayer.PlayerId) + { + chatText = "You cannot use this command during the game!"; + error = true; + } + return sourcePlayer.PlayerId == PlayerControl.LocalPlayer.PlayerId; + } + if (GameData.Instance.GetHost() == sourcePlayer.Data && sourcePlayer.PlayerId == PlayerControl.LocalPlayer.PlayerId) { string[] args = chatText.Split(' '); if (args.Length > 1 && int.TryParse(args[1], out int newLimit)) @@ -32,10 +40,9 @@ public static bool Prefix(ChatController __instance, [HarmonyArgument(0)] Player GameOptionsManager.Instance.CurrentGameOptions.SetInt(Int32OptionNames.MaxPlayers, newLimit); if (sourcePlayer.PlayerId == PlayerControl.LocalPlayer.PlayerId) { + chatText = $"Player limit has been set for: {newLimit}"; system = true; - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"A player limit has been set for: {newLimit}"); } - return false; } catch { } } @@ -43,30 +50,27 @@ public static bool Prefix(ChatController __instance, [HarmonyArgument(0)] Player { if (sourcePlayer.PlayerId == PlayerControl.LocalPlayer.PlayerId) { + chatText = "The !limit command has a range of 4 - 35!"; error = true; - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, "The limit must be between 4 and 35."); } - return false; } } else { if (sourcePlayer.PlayerId == PlayerControl.LocalPlayer.PlayerId) { + chatText = "Use !limit [number]. Example: !limit 20"; error = true; - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, "Use !limit [number]. Example: !limit 20"); } - return false; } } else if (sourcePlayer.PlayerId == PlayerControl.LocalPlayer.PlayerId) { if (sourcePlayer.PlayerId == PlayerControl.LocalPlayer.PlayerId) { + chatText = "You don't have access to this command!"; noaccess = true; - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, "You don't have access to this command!"); } - return false; } return sourcePlayer.PlayerId == PlayerControl.LocalPlayer.PlayerId; } @@ -113,4 +117,4 @@ public static bool Prefix(ChatBubble __instance) } } } -} +} \ No newline at end of file diff --git a/src/AleLuduMod/Patches/CreateGameOptionsPatches.cs b/src/AleLuduMod/Patches/CreateGameOptionsPatches.cs deleted file mode 100644 index 722f751..0000000 --- a/src/AleLuduMod/Patches/CreateGameOptionsPatches.cs +++ /dev/null @@ -1,178 +0,0 @@ -using AmongUs.GameOptions; -using HarmonyLib; -using Reactor.Utilities.Extensions; -using System; -using TMPro; -using UnityEngine; - -namespace AleLuduMod.Patches; - -internal static class CreateGameOptionsPatches -{ - [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.Awake))] - public static class CreateOptionsPicker_Awake - { - public static void Postfix(CreateOptionsPicker __instance) - { - if (__instance.mode != SettingsMode.Host) return; - - { - var firstButtonRenderer = __instance.MaxPlayerButtons[0]; - firstButtonRenderer.GetComponentInChildren().text = "-"; - firstButtonRenderer.enabled = false; - - var firstButtonButton = firstButtonRenderer.GetComponent(); - firstButtonButton.OnClick.RemoveAllListeners(); - firstButtonButton.OnClick.AddListener((Action)(() => - { - for (var i = 1; i < 11; i++) - { - var playerButton = __instance.MaxPlayerButtons[i]; - - var tmp = playerButton.GetComponentInChildren(); - var newValue = Mathf.Max(byte.Parse(tmp.text) - 10, byte.Parse(playerButton.name) - 2); - tmp.text = newValue.ToString(); - } - - __instance.UpdateMaxPlayersButtons(__instance.GetTargetOptions()); - })); - firstButtonRenderer.Destroy(); - - var lastButtonRenderer = __instance.MaxPlayerButtons[^1]; - lastButtonRenderer.GetComponentInChildren().text = "+"; - lastButtonRenderer.enabled = false; - - var lastButtonButton = lastButtonRenderer.GetComponent(); - lastButtonButton.OnClick.RemoveAllListeners(); - lastButtonButton.OnClick.AddListener((Action)(() => - { - for (var i = 1; i < 11; i++) - { - var playerButton = __instance.MaxPlayerButtons[i]; - - var tmp = playerButton.GetComponentInChildren(); - var newValue = Mathf.Min(byte.Parse(tmp.text) + 10, - AleLuduModPlugin.MaxPlayers - 14 + byte.Parse(playerButton.name)); - tmp.text = newValue.ToString(); - } - - __instance.UpdateMaxPlayersButtons(__instance.GetTargetOptions()); - })); - lastButtonRenderer.Destroy(); - - for (var i = 1; i < 11; i++) - { - var playerButton = __instance.MaxPlayerButtons[i].GetComponent(); - var text = playerButton.GetComponentInChildren(); - - playerButton.OnClick.RemoveAllListeners(); - playerButton.OnClick.AddListener((Action)(() => - { - var maxPlayers = byte.Parse(text.text); - var maxImp = Mathf.Min(__instance.GetTargetOptions().NumImpostors, maxPlayers / 2); - __instance.GetTargetOptions().SetInt(Int32OptionNames.NumImpostors, maxImp); - __instance.ImpostorButtons[1].TextMesh.text = maxImp.ToString(); - __instance.SetMaxPlayersButtons(maxPlayers); - })); - } - - foreach (var button in __instance.MaxPlayerButtons) - { - button.enabled = button.GetComponentInChildren().text == __instance.GetTargetOptions().MaxPlayers.ToString(); - } - } - - if (__instance.GetTargetOptions().GameMode is GameModes.Normal or GameModes.NormalFools) - { - var secondButton = __instance.ImpostorButtons[1]; - secondButton.SpriteRenderer.enabled = false; - secondButton.transform.FindChild("ConsoleHighlight").gameObject.Destroy(); - secondButton.PassiveButton.Destroy(); - secondButton.BoxCollider.Destroy(); - - var secondButtonText = secondButton.TextMesh; - secondButtonText.text = __instance.GetTargetOptions().NumImpostors.ToString(); - - var firstButton = __instance.ImpostorButtons[0]; - firstButton.SpriteRenderer.enabled = false; - firstButton.TextMesh.text = "-"; - - var firstPassiveButton = firstButton.PassiveButton; - firstPassiveButton.OnClick.RemoveAllListeners(); - firstPassiveButton.OnClick.AddListener((Action)(() => - { - var newVal = Mathf.Clamp( - byte.Parse(secondButtonText.text) - 1, - 1, - __instance.GetTargetOptions().MaxPlayers / 2 - ); - __instance.SetImpostorButtons(newVal); - secondButtonText.text = newVal.ToString(); - })); - - var thirdButton = __instance.ImpostorButtons[2]; - thirdButton.SpriteRenderer.enabled = false; - thirdButton.TextMesh.text = "+"; - - var thirdPassiveButton = thirdButton.PassiveButton; - thirdPassiveButton.OnClick.RemoveAllListeners(); - thirdPassiveButton.OnClick.AddListener((Action)(() => - { - var newVal = Mathf.Clamp( - byte.Parse(secondButtonText.text) + 1, - 1, - __instance.GetTargetOptions().MaxPlayers / 2 - ); - __instance.SetImpostorButtons(newVal); - secondButtonText.text = newVal.ToString(); - })); - } - } - } - - [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.UpdateMaxPlayersButtons))] - public static class CreateOptionsPicker_UpdateMaxPlayersButtons - { - public static bool Prefix(CreateOptionsPicker __instance, [HarmonyArgument(0)] IGameOptions opts) - { - if (__instance.mode != SettingsMode.Host) return true; - if (__instance.CrewArea) - { - __instance.CrewArea.SetCrewSize(opts.MaxPlayers, opts.NumImpostors); - } - - var selectedAsString = opts.MaxPlayers.ToString(); - for (var i = 1; i < __instance.MaxPlayerButtons.Count - 1; i++) - { - __instance.MaxPlayerButtons[i].enabled = __instance.MaxPlayerButtons[i].GetComponentInChildren().text == selectedAsString; - } - - return false; - } - } - - [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.UpdateImpostorsButtons))] - public static class CreateOptionsPicker_UpdateImpostorsButtons - { - public static bool Prefix(CreateOptionsPicker __instance) - { - if (__instance.mode == SettingsMode.Host) return false; - return true; - } - } - - [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.Refresh))] - public static class CreateOptionsPicker_Refresh - { - public static bool Prefix(CreateOptionsPicker __instance) - { - IGameOptions targetOptions = __instance.GetTargetOptions(); - __instance.UpdateImpostorsButtons(targetOptions.NumImpostors); - __instance.UpdateMaxPlayersButtons(targetOptions); - __instance.UpdateLanguageButton((uint)targetOptions.Keywords); - __instance.MapMenu.UpdateMapButtons((int)targetOptions.MapId); - __instance.GameModeText.text = DestroyableSingleton.Instance.GetString(GameModesHelpers.ModeToName[GameOptionsManager.Instance.CurrentGameOptions.GameMode]); - return false; - } - } -} diff --git a/src/AleLuduMod/Patches/GenericPatches.cs b/src/AleLuduMod/Patches/GenericPatches.cs index a18c20b..c322f34 100644 --- a/src/AleLuduMod/Patches/GenericPatches.cs +++ b/src/AleLuduMod/Patches/GenericPatches.cs @@ -1,13 +1,13 @@ using AmongUs.GameOptions; using HarmonyLib; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using Il2CppSystem.Reflection; using System.Linq; namespace AleLuduMod.Patches; internal static class GenericPatches { - - // I did not find a use of this method, but still patching for future updates // maxExpectedPlayers is unknown, looks like server code tbh [HarmonyPatch(typeof(LegacyGameOptions), nameof(LegacyGameOptions.AreInvalid))] @@ -23,30 +23,78 @@ __instance.KillDistance is < 0 or > 2 || } } - - [HarmonyPatch(typeof(PingTracker), nameof(PingTracker.Update))] - public static class PingShowerPatch + [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))] + public static class GameStartManagerUpdatePatch { - public static void Postfix(PingTracker __instance) + [HarmonyPostfix] + [HarmonyPatch(typeof(CreateGameOptions), nameof(CreateGameOptions.Show))] + public static void CreateGameOptionsShowPostfix(CreateGameOptions __instance) { - __instance.text.text += "\ntownofus.pl"; + var numberOption = __instance.gameObject.GetComponentInChildren(true); + if (numberOption != null) + { + numberOption.ValidRange.max = AleLuduModPlugin.MaxPlayers; + } } } + private static void TryAdjustOptionsRecommendations(GameOptionsManager manager) + { + const int MaxPlayers = AleLuduModPlugin.MaxPlayers; + var type = manager.GetGameOptions(); + var options = manager.GameHostOptions.Cast(); + + var maxRecommendation = ((Il2CppStructArray)Enumerable.Repeat(MaxPlayers, MaxPlayers + 1).ToArray()) + .Cast(); + var minRecommendation = ((Il2CppStructArray)Enumerable.Repeat(4, MaxPlayers + 1).ToArray()) + .Cast(); + var killRecommendation = ((Il2CppStructArray)Enumerable.Repeat(0, MaxPlayers + 1).ToArray()) + .Cast(); + + + const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; + // all these fields are currently static, but we're doing a forward compat + // static fields ignore object param so non-null instance is ok + type.GetField("RecommendedImpostors", flags)?.SetValue(options, maxRecommendation); + type.GetField("MaxImpostors", flags)?.SetValue(options, maxRecommendation); + type.GetField("RecommendedKillCooldown", flags)?.SetValue(options, killRecommendation); + type.GetField("MinPlayers", flags)?.SetValue(options, minRecommendation); + } + + [HarmonyPatch(typeof(GameOptionsManager), nameof(GameOptionsManager.GameHostOptions), MethodType.Setter)] + public static class GameOptionsManager_set_GameHostOptions + { + public static void Postfix(GameOptionsManager __instance) + { + TryAdjustOptionsRecommendations(__instance); + } + } - [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Initialize))] - public static class GameOptionsMenu_Initialize + [HarmonyPatch(typeof(GameOptionsManager), nameof(GameOptionsManager.SwitchGameMode))] + public static class GameOptionsManager_SwitchGameMode { - public static void Postfix(GameOptionsMenu __instance) + public static void Postfix(GameOptionsManager __instance) { - var numberOptions = __instance.GetComponentsInChildren(); + TryAdjustOptionsRecommendations(__instance); + } + } - var impostorsOption = numberOptions.FirstOrDefault(o => o.Title == StringNames.GameNumImpostors); - if (impostorsOption != null) + [HarmonyPatch(typeof(GameManager), nameof(GameManager.Awake))] + public static class GameManager_Awake + { + public static void Postfix(GameManager __instance) + { + foreach (var category in __instance.GameSettingsList.AllCategories) { - impostorsOption.ValidRange = new FloatRange(1, AleLuduModPlugin.MaxImpostors); + foreach (var option in category.AllGameSettings) + { + if (option is IntGameSetting intOption && option.Title == StringNames.GameNumImpostors) + { + intOption.ValidRange = new IntRange(1, AleLuduModPlugin.MaxImpostors); + return; + } + } } - } } -} +} \ No newline at end of file diff --git a/src/AleLuduMod/Patches/SecurityLoggerPatches.cs b/src/AleLuduMod/Patches/SecurityLoggerPatches.cs new file mode 100644 index 0000000..727feeb --- /dev/null +++ b/src/AleLuduMod/Patches/SecurityLoggerPatches.cs @@ -0,0 +1,16 @@ +using HarmonyLib; +using Il2CppInterop.Runtime.InteropTypes.Arrays; + +namespace AleLuduMod.Patches; + +internal static class SecurityLoggerPatches +{ + [HarmonyPatch(typeof(SecurityLogger), nameof(SecurityLogger.Awake))] + public static class SecurityLoggerPatch + { + public static void Postfix(SecurityLogger __instance) + { + __instance.Timers = new Il2CppStructArray(AleLuduModPlugin.MaxPlayers); + } + } +} \ No newline at end of file diff --git a/src/AleLuduMod/Patches/ServerDropdownPatch.cs b/src/AleLuduMod/Patches/ServerDropdownPatch.cs new file mode 100644 index 0000000..7785ec3 --- /dev/null +++ b/src/AleLuduMod/Patches/ServerDropdownPatch.cs @@ -0,0 +1,69 @@ +using HarmonyLib; +using System.Linq; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.SceneManagement; + +namespace AleLuduMod.Patches; + +[HarmonyPatch(typeof(ServerDropdown), nameof(ServerDropdown.FillServerOptions))] +public static class ServerDropdownPatch +{ + public static bool RegionEquals(this IRegionInfo region, IRegionInfo other) + { + return region.Name == other.Name && + region.TranslateName == other.TranslateName && + region.PingServer == other.PingServer && + region.TargetServer == other.TargetServer && + region.Servers.All(s => other.Servers.Any(x => x.Equals(s))); + } + + public static bool Prefix(ServerDropdown __instance) + { + var num = 0; + __instance.background.size = new Vector2(8.4f, 4.8f); + + foreach (var regionInfo in ServerManager.Instance.AvailableRegions) + { + var findingGame = SceneManager.GetActiveScene().name is "FindAGame"; + + if (ServerManager.Instance.CurrentRegion.RegionEquals(regionInfo)) + { + __instance.defaultButtonSelected = __instance.firstOption; + __instance.firstOption.ChangeButtonText( + TranslationController.Instance.GetStringWithDefault( + regionInfo.TranslateName, + regionInfo.Name)); + } + else + { + var region = regionInfo; + var serverListButton = __instance.ButtonPool.Get(); + var x = num % 2 == 0 ? -2 : 2; + if (findingGame) + { + x += 2; + } + var y = -0.55f * (num / 2f); + serverListButton.transform.localPosition = new Vector3(x, __instance.y_posButton + y, -1f); + serverListButton.transform.localScale = Vector3.one; + serverListButton.Text.text = + TranslationController.Instance.GetStringWithDefault( + regionInfo.TranslateName, + regionInfo.Name); + serverListButton.Text.ForceMeshUpdate(); + serverListButton.Button.OnClick.RemoveAllListeners(); + serverListButton.Button.OnClick.AddListener((UnityAction)(() => { __instance.ChooseOption(region); })); + __instance.controllerSelectable.Add(serverListButton.Button); + __instance.background.transform.localPosition = new Vector3( + findingGame ? 2f : 0f, + __instance.initialYPos + -0.3f * (num / 2f), + 0f); + __instance.background.size = new Vector2(__instance.background.size.x, 1.2f + 0.6f * (num / 2f)); + num++; + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/AleLuduMod/Resources/Icon.png b/src/AleLuduMod/Resources/Icon.png new file mode 100644 index 0000000..5236af9 Binary files /dev/null and b/src/AleLuduMod/Resources/Icon.png differ diff --git a/src/AleLuduMod/modinfo.json b/src/AleLuduMod/modinfo.json new file mode 100644 index 0000000..0591b36 --- /dev/null +++ b/src/AleLuduMod/modinfo.json @@ -0,0 +1,19 @@ +{ + "Name": "AleLuduMod", + "Description": "This mod unlocks the possibility to show more than 15 players on screen during an meeting hud, vitals and shapeshifter menu.", + "Tags": [ + "hud", + "mods" + ], + "Icon": "AleLuduMod.Resources.Icon.png", + "License": "MIT", + "Authors": [ + "TownOfUs.pl", + "OrzechMC", + "Bubeu", + "gitq" + ], + "Links": { + "Source": "https://github.com/raspberrygitq/AleLuduMod" + } +} \ No newline at end of file