diff --git a/Modules/GameOptionsSender/GameOptionsSender.cs b/Modules/GameOptionsSender/GameOptionsSender.cs index b310154bc..358cb9eb2 100644 --- a/Modules/GameOptionsSender/GameOptionsSender.cs +++ b/Modules/GameOptionsSender/GameOptionsSender.cs @@ -3,23 +3,24 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; using AmongUs.GameOptions; -using Hazel; -using Il2CppInterop.Runtime.InteropTypes.Arrays; -using InnerNet; -using UnityEngine; +using Hazel; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using InnerNet; +using UnityEngine; +using static EHR.GameStates; namespace EHR.Modules; public abstract class GameOptionsSender { - protected abstract bool IsDirty { get; set; } + protected abstract bool IsDirty { get; set; } protected virtual int TargetClientId => -1; - private Il2CppStructArray BuildOptionArray() - { - IGameOptions opt = BuildGameOptions(); - var currentGameMode = AprilFoolsMode.IsAprilFoolsModeToggledOn ? opt.AprilFoolsOnMode : opt.GameMode; + private Il2CppStructArray BuildOptionArray() + { + IGameOptions opt = BuildSendableGameOptions(); + var currentGameMode = AprilFoolsMode.IsAprilFoolsModeToggledOn ? opt.AprilFoolsOnMode : opt.GameMode; // option => byte[] MessageWriter writer = MessageWriter.Get(); @@ -107,7 +108,60 @@ public abstract class GameOptionsSender catch (Exception ex) { Logger.Fatal(ex.ToString(), "GameOptionsSender.SendOptionsArray"); } } - public abstract IGameOptions BuildGameOptions(); + public abstract IGameOptions BuildGameOptions(); + + protected IGameOptions BuildSendableGameOptions() + { + return SanitizeForOfficialServer(BuildGameOptions()); + } + + protected static IGameOptions SanitizeForOfficialServer(IGameOptions opt) + { + if (CurrentServerType != ServerType.Vanilla || opt == null || !opt.TryCast(out NormalGameOptionsV10 normalOpt)) + return opt; + + int originalMaxPlayers = normalOpt.MaxPlayers; + int originalImpostors = normalOpt.NumImpostors; + int originalKillDistance = normalOpt.KillDistance; + float originalPlayerSpeed = normalOpt.PlayerSpeedMod; + bool changed = false; + + if (normalOpt.MaxPlayers > 15) + { + normalOpt.SetInt(Int32OptionNames.MaxPlayers, 15); + changed = true; + } + + int impostors = Mathf.Clamp(normalOpt.NumImpostors, 1, 3); + if (impostors != normalOpt.NumImpostors) + { + normalOpt.SetInt(Int32OptionNames.NumImpostors, impostors); + changed = true; + } + + int killDistance = Mathf.Clamp(normalOpt.KillDistance, 0, 2); + if (killDistance != normalOpt.KillDistance) + { + normalOpt.SetInt(Int32OptionNames.KillDistance, killDistance); + changed = true; + } + + float playerSpeed = Mathf.Clamp(normalOpt.PlayerSpeedMod, Main.MinSpeed, 3f); + if (!Mathf.Approximately(playerSpeed, normalOpt.PlayerSpeedMod)) + { + normalOpt.SetFloat(FloatOptionNames.PlayerSpeedMod, playerSpeed); + changed = true; + } + + if (changed) + { + Logger.Warn( + $"Clamped outgoing official game options: MaxPlayers={originalMaxPlayers}->{normalOpt.MaxPlayers}, NumImpostors={originalImpostors}->{normalOpt.NumImpostors}, KillDistance={originalKillDistance}->{normalOpt.KillDistance}, PlayerSpeedMod={originalPlayerSpeed:0.###}->{normalOpt.PlayerSpeedMod:0.###}", + nameof(GameOptionsSender)); + } + + return normalOpt.CastFast(); + } protected virtual bool AmValid() { @@ -171,4 +225,4 @@ public abstract class GameOptionsSender protected static bool ForceWaitFrame; #endregion -} \ No newline at end of file +} diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index 1924c4c6d..dca0d6453 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -106,11 +106,11 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS IsDirty = true; } - protected override void SendGameOptions() - { - if (player.AmOwner) - { - IGameOptions opt = BuildGameOptions(); + protected override void SendGameOptions() + { + if (player.AmOwner) + { + IGameOptions opt = BuildSendableGameOptions(); if (GameManager.Instance?.LogicComponents != null) { @@ -127,11 +127,11 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS base.SendGameOptions(); } - protected override IEnumerator SendGameOptionsAsync() - { - if (player.AmOwner) - { - IGameOptions opt = BuildGameOptions(); + protected override IEnumerator SendGameOptionsAsync() + { + if (player.AmOwner) + { + IGameOptions opt = BuildSendableGameOptions(); if (GameManager.Instance?.LogicComponents != null) { diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index e8b1cbd57..aed36237f 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -633,13 +633,15 @@ public static class ToggleOptionPatch } [HarmonyPatch(typeof(NumberOption))] -public static class NumberOptionPatch -{ - private static int IncrementMultiplier - { - get - { - if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) return 5; +public static class NumberOptionPatch +{ + private static bool IsVanillaServer => GameStates.CurrentServerType == GameStates.ServerType.Vanilla; + + private static int IncrementMultiplier + { + get + { + if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) return 5; if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) return 10; return 1; } @@ -661,27 +663,34 @@ public static class NumberOptionPatch __instance.ValidRange = new(0, 90); __instance.Value = (float)Math.Round(__instance.Value, 2); break; - case StringNames.GameKillCooldown: - __instance.ValidRange = new(0, 180); - __instance.Increment = 0.5f; - __instance.Value = (float)Math.Round(__instance.Value, 2); - break; - case StringNames.GamePlayerSpeed: - case StringNames.GameCrewLight: - case StringNames.GameImpostorLight: - __instance.Increment = 0.05f; - __instance.Value = (float)Math.Round(__instance.Value, 2); - break; + case StringNames.GameKillCooldown: + __instance.ValidRange = new(0, 180); + __instance.Increment = 0.5f; + __instance.Value = (float)Math.Round(__instance.Value, 2); + break; + case StringNames.GamePlayerSpeed: + if (IsVanillaServer) + __instance.ValidRange = new(Main.MinSpeed, 3f); + + __instance.Increment = 0.05f; + __instance.Value = Mathf.Clamp((float)Math.Round(__instance.Value, 2), __instance.ValidRange.min, __instance.ValidRange.max); + break; + case StringNames.GameCrewLight: + case StringNames.GameImpostorLight: + __instance.Increment = 0.05f; + __instance.Value = (float)Math.Round(__instance.Value, 2); + break; case StringNames.GameNumImpostors: - __instance.ValidRange = GameStates.CurrentServerType != GameStates.ServerType.Vanilla - ? new(0, Crowded.MaxImpostors) : new(1, 3); - __instance.Value = (float)Math.Round(__instance.Value, 2); - if (DebugModeManager.AmDebugger) __instance.ValidRange.min = 0; - break; - case StringNames.CapacityLabel: - __instance.ValidRange = new(4, 127); - break; - } + __instance.ValidRange = GameStates.CurrentServerType != GameStates.ServerType.Vanilla + ? new(0, Crowded.MaxImpostors) : new(1, 3); + __instance.Value = Mathf.Clamp((float)Math.Round(__instance.Value, 2), __instance.ValidRange.min, __instance.ValidRange.max); + if (DebugModeManager.AmDebugger) __instance.ValidRange.min = 0; + break; + case StringNames.CapacityLabel: + __instance.ValidRange = IsVanillaServer ? new(4, 15) : new(4, 127); + __instance.Value = Mathf.Clamp(__instance.Value, __instance.ValidRange.min, __instance.ValidRange.max); + break; + } if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) { @@ -803,18 +812,33 @@ public static class NumberOptionPatch } [HarmonyPatch(typeof(StringOption))] -public static class StringOptionPatch -{ - private static long HelpShowEndTS; - - [HarmonyPatch(nameof(StringOption.Initialize))] - [HarmonyPrefix] - private static bool InitializePrefix(StringOption __instance) - { - if (__instance.name.StartsWith(nameof(OnlinePresetsManager))) return true; - - if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) - { +public static class StringOptionPatch +{ + private static long HelpShowEndTS; + + private static bool IsVanillaServer => GameStates.CurrentServerType == GameStates.ServerType.Vanilla; + + private static void ClampOfficialStringOption(StringOption option) + { + if (!IsVanillaServer) return; + + switch (option.Title) + { + case StringNames.GameKillDistance: + option.Value = Mathf.Clamp(option.Value, 0, Math.Min(2, option.Values.Length - 1)); + break; + } + } + + [HarmonyPatch(nameof(StringOption.Initialize))] + [HarmonyPrefix] + private static bool InitializePrefix(StringOption __instance) + { + if (__instance.name.StartsWith(nameof(OnlinePresetsManager))) return true; + ClampOfficialStringOption(__instance); + + if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) + { OptionItem item = OptionItem.AllOptions[index]; string name = item.GetName(); item.OptionBehaviour = __instance; @@ -1006,18 +1030,20 @@ public static class StringOptionPatch return true; } - [HarmonyPatch(nameof(StringOption.FixedUpdate))] - [HarmonyPrefix] - private static bool FixedUpdatePrefix(StringOption __instance) - { - if (__instance.name.StartsWith(nameof(OnlinePresetsManager))) + [HarmonyPatch(nameof(StringOption.FixedUpdate))] + [HarmonyPrefix] + private static bool FixedUpdatePrefix(StringOption __instance) + { + if (__instance.name.StartsWith(nameof(OnlinePresetsManager))) { __instance.TitleText.SetText(__instance.name.Split(';')[1]); - return false; - } - - if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) - { + return false; + } + + ClampOfficialStringOption(__instance); + + if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) + { __instance.MinusBtn.SetInteractable(true); __instance.PlusBtn.SetInteractable(true); @@ -1700,4 +1726,4 @@ public static class RpcSyncSettingsPatch { OptionItem.SyncAllOptions(); } -} \ No newline at end of file +}