diff --git a/.editorconfig b/.editorconfig
index 1f3fff443..39bb8427d 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -8,7 +8,7 @@ dotnet_diagnostic.ide0060.severity = none
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:silent
-csharp_style_namespace_declarations = block_scoped:silent
+csharp_style_namespace_declarations = file_scoped:none
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = true:suggestion
diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs
index e857a8ef0..def3c16c8 100644
--- a/Modules/AntiBlackout.cs
+++ b/Modules/AntiBlackout.cs
@@ -107,7 +107,19 @@ namespace EHR
IsDeadCache.Clear();
IsCached = false;
- if (doSend) SendGameData();
+
+ if (doSend)
+ {
+ SendGameData();
+ LateTask.New(RestoreIsDeadByExile, 0.3f, "AntiBlackOut.RestoreIsDeadByExile");
+ }
+ }
+
+ private static void RestoreIsDeadByExile()
+ {
+ var sender = CustomRpcSender.Create("AntiBlackout.RestoreIsDeadByExile", SendOption.Reliable);
+ Main.AllPlayerControls.DoIf(x => x.Data.IsDead && !x.Data.Disconnected, x => sender.AutoStartRpc(x.NetId, (byte)RpcCalls.Exiled).EndRpc());
+ sender.SendMessage();
}
public static void SendGameData([CallerMemberName] string callerMethodName = "")
diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs
index 9842adeaa..1e716f55a 100644
--- a/Modules/ExtendedPlayerControl.cs
+++ b/Modules/ExtendedPlayerControl.cs
@@ -1485,7 +1485,7 @@ namespace EHR
public static void RpcExileV2(this PlayerControl player)
{
player.Exiled();
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.None);
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.Reliable);
AmongUsClient.Instance.FinishRpcImmediately(writer);
FixedUpdatePatch.LoversSuicide(player.PlayerId);
}
diff --git a/Modules/GameState.cs b/Modules/GameState.cs
index d4480aa8c..b73b41f7c 100644
--- a/Modules/GameState.cs
+++ b/Modules/GameState.cs
@@ -533,7 +533,7 @@ namespace EHR
{
get
{
- if (!IsOnlineGame) return false;
+ if (IsLocalGame && !IsNotJoined) return true;
const string domain = "among.us";
diff --git a/Modules/Utils.cs b/Modules/Utils.cs
index 01d46d1d4..9b511a77e 100644
--- a/Modules/Utils.cs
+++ b/Modules/Utils.cs
@@ -148,10 +148,14 @@ other: ∟ ⌠ ⌡ ╬ ╨ ▓ ▒ ░ « » █ ▄ ▌▀▐│ ┤ ╡ ╢
}
}
- if (AmongUsClient.Instance.AmHost)
+ switch (AmongUsClient.Instance.AmHost)
{
- nt.SnapTo(location, (ushort)(nt.lastSequenceId + 328));
- nt.SetDirtyBit(uint.MaxValue);
+ case true:
+ nt.SnapTo(location, (ushort)(nt.lastSequenceId + 328));
+ nt.SetDirtyBit(uint.MaxValue);
+ break;
+ case false when !nt.AmOwner:
+ return false;
}
var newSid = (ushort)(nt.lastSequenceId + 8);
@@ -3090,88 +3094,94 @@ other: ∟ ⌠ ⌡ ╬ ╨ ▓ ▒ ░ « » █ ▄ ▌▀▐│ ┤ ╡ ╢
public static string SummaryTexts(byte id, bool disableColor = true, bool check = false)
{
- //var RolePos = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.English or SupportedLangs.Russian ? 37 : 34;
- //var KillsPos = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.English or SupportedLangs.Russian ? 14 : 12;
- string name = Main.AllPlayerNames[id].RemoveHtmlTags().Replace("\r\n", string.Empty);
-
- if (id == PlayerControl.LocalPlayer.PlayerId)
- name = DataManager.player.Customization.Name;
- else
- name = GetPlayerById(id)?.Data.PlayerName ?? name;
-
- TaskState taskState = Main.PlayerStates[id].TaskState;
- string TaskCount;
-
- if (taskState.HasTasks)
+ try
{
- NetworkedPlayerInfo info = GetPlayerInfoById(id);
- Color TaskCompleteColor = HasTasks(info) ? Color.green : Color.cyan;
- Color NonCompleteColor = HasTasks(info) ? Color.yellow : Color.white;
+ string name = Main.AllPlayerNames[id].RemoveHtmlTags().Replace("\r\n", string.Empty);
- if (Workhorse.IsThisRole(id)) NonCompleteColor = Workhorse.RoleColor;
+ if (id == PlayerControl.LocalPlayer.PlayerId)
+ name = DataManager.player.Customization.Name;
+ else
+ name = GetPlayerById(id)?.Data.PlayerName ?? name;
- Color NormalColor = taskState.IsTaskFinished ? TaskCompleteColor : NonCompleteColor;
+ TaskState taskState = Main.PlayerStates[id].TaskState;
+ string TaskCount;
- if (Main.PlayerStates.TryGetValue(id, out PlayerState ps))
+ if (taskState.HasTasks)
{
- NormalColor = ps.MainRole switch
+ NetworkedPlayerInfo info = GetPlayerInfoById(id);
+ Color TaskCompleteColor = HasTasks(info) ? Color.green : Color.cyan;
+ Color NonCompleteColor = HasTasks(info) ? Color.yellow : Color.white;
+
+ if (Workhorse.IsThisRole(id)) NonCompleteColor = Workhorse.RoleColor;
+
+ Color NormalColor = taskState.IsTaskFinished ? TaskCompleteColor : NonCompleteColor;
+
+ if (Main.PlayerStates.TryGetValue(id, out PlayerState ps))
{
- CustomRoles.Crewpostor => Color.red,
- CustomRoles.Cherokious => GetRoleColor(CustomRoles.Cherokious),
- _ => NormalColor
- };
+ NormalColor = ps.MainRole switch
+ {
+ CustomRoles.Crewpostor => Color.red,
+ CustomRoles.Cherokious => GetRoleColor(CustomRoles.Cherokious),
+ _ => NormalColor
+ };
+ }
+
+ Color TextColor = NormalColor;
+ var Completed = $"{taskState.CompletedTasksCount}";
+ TaskCount = ColorString(TextColor, $" ({Completed}/{taskState.AllTasksCount})");
+ }
+ else
+ TaskCount = string.Empty;
+
+ var summary = $"{ColorString(Main.PlayerColors[id], name)} - {GetDisplayRoleName(id, true)}{TaskCount}{GetKillCountText(id)} ({GetVitalText(id, true)})";
+
+ switch (Options.CurrentGameMode)
+ {
+ case CustomGameMode.SoloKombat:
+ summary = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.SChinese or SupportedLangs.TChinese ? $"{GetProgressText(id)}\t{ColorString(Main.PlayerColors[id], name)}" : $"{ColorString(Main.PlayerColors[id], name)}{GetProgressText(id)}";
+ if (GetProgressText(id).Trim() == string.Empty) return string.Empty;
+
+ break;
+ case CustomGameMode.FFA:
+ summary = $"{ColorString(Main.PlayerColors[id], name)} {GetKillCountText(id, true)}";
+ break;
+ case CustomGameMode.Speedrun:
+ case CustomGameMode.MoveAndStop:
+ summary = $"{ColorString(Main.PlayerColors[id], name)} -{TaskCount.Replace("(", string.Empty).Replace(")", string.Empty)} ({GetVitalText(id, true)})";
+ break;
+ case CustomGameMode.HotPotato:
+ int time = HotPotato.GetSurvivalTime(id);
+ summary = $"{ColorString(Main.PlayerColors[id], name)} - <#e8cd46>{GetString("SurvivedTimePrefix")}: <#ffffff>{(time == 0 ? $"{GetString("SurvivedUntilTheEnd")}" : $"{time}s")} ({GetVitalText(id, true)})";
+ break;
+ case CustomGameMode.NaturalDisasters:
+ int time2 = NaturalDisasters.SurvivalTime(id);
+ summary = $"{ColorString(Main.PlayerColors[id], name)} - <#e8cd46>{GetString("SurvivedTimePrefix")}: <#ffffff>{(time2 == 0 ? $"{GetString("SurvivedUntilTheEnd")}" : $"{time2}s")} ({GetVitalText(id, true)})";
+ break;
+ case CustomGameMode.RoomRush:
+ int time3 = RoomRush.GetSurvivalTime(id);
+ summary = $"{ColorString(Main.PlayerColors[id], name)} - <#e8cd46>{GetString("SurvivedTimePrefix")}: <#ffffff>{(time3 == 0 ? $"{GetString("SurvivedUntilTheEnd")}" : $"{time3}s")} ({GetVitalText(id, true)})";
+ break;
+ case CustomGameMode.CaptureTheFlag:
+ summary = $"{ColorString(Main.PlayerColors[id], name)}: {CTFManager.GetStatistics(id)}";
+ if (CTFManager.IsDeathPossible) summary += $" ({GetVitalText(id, true)})";
+ break;
+ case CustomGameMode.AllInOne:
+ string survivalTimeText = !Main.PlayerStates[id].IsDead ? string.Empty : $" ({GetString("SurvivedTimePrefix")}: <#f542ad>{RoomRush.GetSurvivalTime(id)}s)";
+ summary = $"{ColorString(Main.PlayerColors[id], name)} -{TaskCount}{GetKillCountText(id, true)} ({GetVitalText(id, true)}){survivalTimeText}";
+ break;
}
- Color TextColor = NormalColor;
- var Completed = $"{taskState.CompletedTasksCount}";
- TaskCount = ColorString(TextColor, $" ({Completed}/{taskState.AllTasksCount})");
+ return check && GetDisplayRoleName(id, true).RemoveHtmlTags().Contains("INVALID:NotAssigned")
+ ? "INVALID"
+ : disableColor
+ ? summary.RemoveHtmlTags()
+ : summary;
}
- else
- TaskCount = string.Empty;
-
- var summary = $"{ColorString(Main.PlayerColors[id], name)} - {GetDisplayRoleName(id, true)}{TaskCount}{GetKillCountText(id)} ({GetVitalText(id, true)})";
-
- switch (Options.CurrentGameMode)
+ catch (Exception e)
{
- case CustomGameMode.SoloKombat:
- summary = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.SChinese or SupportedLangs.TChinese ? $"{GetProgressText(id)}\t{ColorString(Main.PlayerColors[id], name)}" : $"{ColorString(Main.PlayerColors[id], name)}{GetProgressText(id)}";
- if (GetProgressText(id).Trim() == string.Empty) return string.Empty;
-
- break;
- case CustomGameMode.FFA:
- summary = $"{ColorString(Main.PlayerColors[id], name)} {GetKillCountText(id, true)}";
- break;
- case CustomGameMode.Speedrun:
- case CustomGameMode.MoveAndStop:
- summary = $"{ColorString(Main.PlayerColors[id], name)} -{TaskCount.Replace("(", string.Empty).Replace(")", string.Empty)} ({GetVitalText(id, true)})";
- break;
- case CustomGameMode.HotPotato:
- int time = HotPotato.GetSurvivalTime(id);
- summary = $"{ColorString(Main.PlayerColors[id], name)} - <#e8cd46>{GetString("SurvivedTimePrefix")}: <#ffffff>{(time == 0 ? $"{GetString("SurvivedUntilTheEnd")}" : $"{time}s")} ({GetVitalText(id, true)})";
- break;
- case CustomGameMode.NaturalDisasters:
- int time2 = NaturalDisasters.SurvivalTime(id);
- summary = $"{ColorString(Main.PlayerColors[id], name)} - <#e8cd46>{GetString("SurvivedTimePrefix")}: <#ffffff>{(time2 == 0 ? $"{GetString("SurvivedUntilTheEnd")}" : $"{time2}s")} ({GetVitalText(id, true)})";
- break;
- case CustomGameMode.RoomRush:
- int time3 = RoomRush.GetSurvivalTime(id);
- summary = $"{ColorString(Main.PlayerColors[id], name)} - <#e8cd46>{GetString("SurvivedTimePrefix")}: <#ffffff>{(time3 == 0 ? $"{GetString("SurvivedUntilTheEnd")}" : $"{time3}s")} ({GetVitalText(id, true)})";
- break;
- case CustomGameMode.CaptureTheFlag:
- summary = $"{ColorString(Main.PlayerColors[id], name)}: {CTFManager.GetStatistics(id)}";
- if (CTFManager.IsDeathPossible) summary += $" ({GetVitalText(id, true)})";
- break;
- case CustomGameMode.AllInOne:
- string survivalTimeText = !Main.PlayerStates[id].IsDead ? string.Empty : $" ({GetString("SurvivedTimePrefix")}: <#f542ad>{RoomRush.GetSurvivalTime(id)}s)";
- summary = $"{ColorString(Main.PlayerColors[id], name)} -{TaskCount}{GetKillCountText(id, true)} ({GetVitalText(id, true)}){survivalTimeText}";
- break;
+ ThrowException(e);
+ return $"{id.ColoredPlayerName()} - ERROR";
}
-
- return check && GetDisplayRoleName(id, true).RemoveHtmlTags().Contains("INVALID:NotAssigned")
- ? "INVALID"
- : disableColor
- ? summary.RemoveHtmlTags()
- : summary;
}
public static string GetRemainingKillers(bool notify = false, bool president = false)
diff --git a/Patches/ClientPatch.cs b/Patches/ClientPatch.cs
index 2849fe7ed..12d7e9d06 100644
--- a/Patches/ClientPatch.cs
+++ b/Patches/ClientPatch.cs
@@ -1,355 +1,443 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using EHR.Modules;
using HarmonyLib;
using Hazel;
-using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem.Collections.Generic;
using InnerNet;
using TMPro;
using UnityEngine;
using static EHR.Translator;
-namespace EHR
+namespace EHR;
+
+[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.MakePublic))]
+internal static class MakePublicPatch
{
- [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.MakePublic))]
- internal static class MakePublicPatch
+ public static bool Prefix()
{
- public static bool Prefix()
+ if (ModUpdater.IsBroken || (ModUpdater.HasUpdate && ModUpdater.ForceUpdate) || !VersionChecker.IsSupported)
{
- if (ModUpdater.IsBroken || (ModUpdater.HasUpdate && ModUpdater.ForceUpdate) || !VersionChecker.IsSupported)
- {
- var message = string.Empty;
- if (!VersionChecker.IsSupported) message = GetString("UnsupportedVersion");
+ var message = string.Empty;
+ if (!VersionChecker.IsSupported) message = GetString("UnsupportedVersion");
- if (ModUpdater.IsBroken) message = GetString("ModBrokenMessage");
+ if (ModUpdater.IsBroken) message = GetString("ModBrokenMessage");
- if (ModUpdater.HasUpdate) message = GetString("CanNotJoinPublicRoomNoLatest");
+ if (ModUpdater.HasUpdate) message = GetString("CanNotJoinPublicRoomNoLatest");
- Logger.Info(message, "MakePublicPatch");
- Logger.SendInGame(message);
- return false;
- }
-
- return true;
+ Logger.Info(message, "MakePublicPatch");
+ Logger.SendInGame(message);
+ return false;
}
- }
- [HarmonyPatch(typeof(MMOnlineManager), nameof(MMOnlineManager.Start))]
+ return true;
+ }
+}
+
+[HarmonyPatch(typeof(MMOnlineManager), nameof(MMOnlineManager.Start))]
// ReSharper disable once InconsistentNaming
- internal static class MMOnlineManagerStartPatch
+internal static class MMOnlineManagerStartPatch
+{
+ public static void Postfix()
{
- public static void Postfix()
+ if (!((ModUpdater.HasUpdate && ModUpdater.ForceUpdate) || ModUpdater.IsBroken)) return;
+
+ GameObject obj = GameObject.Find("FindGameButton");
+
+ if (obj)
{
- if (!((ModUpdater.HasUpdate && ModUpdater.ForceUpdate) || ModUpdater.IsBroken)) return;
+ obj.SetActive(false);
+ TextMeshPro textObj = Object.Instantiate(obj.transform.FindChild("Text_TMP").GetComponent());
+ textObj.transform.position = new(1f, -0.3f, 0);
+ textObj.name = "CanNotJoinPublic";
- GameObject obj = GameObject.Find("FindGameButton");
+ string message = ModUpdater.IsBroken
+ ? $"{Utils.ColorString(Color.red, GetString("ModBrokenMessage"))}"
+ : $"{Utils.ColorString(Color.red, GetString("CanNotJoinPublicRoomNoLatest"))}";
- if (obj)
- {
- obj.SetActive(false);
- TextMeshPro textObj = Object.Instantiate(obj.transform.FindChild("Text_TMP").GetComponent());
- textObj.transform.position = new(1f, -0.3f, 0);
- textObj.name = "CanNotJoinPublic";
-
- string message = ModUpdater.IsBroken
- ? $"{Utils.ColorString(Color.red, GetString("ModBrokenMessage"))}"
- : $"{Utils.ColorString(Color.red, GetString("CanNotJoinPublicRoomNoLatest"))}";
-
- LateTask.New(() => { textObj.text = message; }, 0.01f, "CanNotJoinPublic");
- }
+ LateTask.New(() => { textObj.text = message; }, 0.01f, "CanNotJoinPublic");
}
}
+}
- [HarmonyPatch(typeof(SplashManager), nameof(SplashManager.Update))]
- internal static class SplashLogoAnimatorPatch
+[HarmonyPatch(typeof(SplashManager), nameof(SplashManager.Update))]
+internal static class SplashLogoAnimatorPatch
+{
+ public static void Prefix(SplashManager __instance)
{
- public static void Prefix(SplashManager __instance)
- {
- __instance.sceneChanger.AllowFinishLoadingScene();
- __instance.startedSceneLoad = true;
- }
+ __instance.sceneChanger.AllowFinishLoadingScene();
+ __instance.startedSceneLoad = true;
}
+}
- [HarmonyPatch(typeof(EOSManager), nameof(EOSManager.IsAllowedOnline))]
- internal static class RunLoginPatch
+[HarmonyPatch(typeof(EOSManager), nameof(EOSManager.IsAllowedOnline))]
+internal static class RunLoginPatch
+{
+ public const int ClickCount = 0;
+
+ public static void Prefix(ref bool canOnline)
{
- public const int ClickCount = 0;
-
- public static void Prefix(ref bool canOnline)
- {
- if (DebugModeManager.AmDebugger) canOnline = true;
- }
+ if (DebugModeManager.AmDebugger) canOnline = true;
}
+}
- [HarmonyPatch(typeof(BanMenu), nameof(BanMenu.SetVisible))]
- internal static class BanMenuSetVisiblePatch
+[HarmonyPatch(typeof(BanMenu), nameof(BanMenu.SetVisible))]
+internal static class BanMenuSetVisiblePatch
+{
+ public static bool Prefix(BanMenu __instance, bool show)
{
- public static bool Prefix(BanMenu __instance, bool show)
- {
- if (!AmongUsClient.Instance.AmHost) return true;
+ if (!AmongUsClient.Instance.AmHost) return true;
- show &= PlayerControl.LocalPlayer && PlayerControl.LocalPlayer.Data != null;
- __instance.BanButton.gameObject.SetActive(AmongUsClient.Instance.CanBan());
- __instance.KickButton.gameObject.SetActive(AmongUsClient.Instance.CanKick());
- __instance.MenuButton.gameObject.SetActive(show);
+ show &= PlayerControl.LocalPlayer && PlayerControl.LocalPlayer.Data != null;
+ __instance.BanButton.gameObject.SetActive(AmongUsClient.Instance.CanBan());
+ __instance.KickButton.gameObject.SetActive(AmongUsClient.Instance.CanKick());
+ __instance.MenuButton.gameObject.SetActive(show);
+ return false;
+ }
+}
+
+[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.CanBan))]
+internal static class InnerNetClientCanBanPatch
+{
+ public static bool Prefix(InnerNetClient __instance, ref bool __result)
+ {
+ __result = __instance.AmHost;
+ return false;
+ }
+}
+
+[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.KickPlayer))]
+internal static class KickPlayerPatch
+{
+ public static bool Prefix( /*InnerNetClient __instance,*/ int clientId, bool ban)
+ {
+ if (!AmongUsClient.Instance.AmHost && !OnGameJoinedPatch.JoiningGame) return true;
+
+ if (AmongUsClient.Instance.ClientId == clientId)
+ {
+ Logger.SendInGame($"Game Attempting to {(ban ? "Ban" : "Kick")} Host, Blocked the attempt.");
+ Logger.Info("Game attempted to kick/ban host....", "KickPlayerPatch");
return false;
}
+
+ if (ban) BanManager.AddBanPlayer(AmongUsClient.Instance.GetRecentClient(clientId));
+
+ return true;
}
+}
- [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.CanBan))]
- internal static class InnerNetClientCanBanPatch
+[HarmonyPatch(typeof(ResolutionManager), nameof(ResolutionManager.SetResolution))]
+internal static class SetResolutionManager
+{
+ public static void Postfix()
{
- public static bool Prefix(InnerNetClient __instance, ref bool __result)
- {
- __result = __instance.AmHost;
- return false;
- }
+ if (MainMenuManagerPatch.UpdateButton != null) MainMenuManagerPatch.UpdateButton.transform.localPosition = MainMenuManagerPatch.Template.transform.localPosition + new Vector3(0.25f, 0.75f);
}
+}
- [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.KickPlayer))]
- internal static class KickPlayerPatch
+[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendAllStreamedObjects))]
+internal static class InnerNetObjectSerializePatch
+{
+ public static void Prefix()
{
- public static bool Prefix( /*InnerNetClient __instance,*/ int clientId, bool ban)
- {
- if (!AmongUsClient.Instance.AmHost && !OnGameJoinedPatch.JoiningGame) return true;
+ if (AmongUsClient.Instance.AmHost) Main.Instance.StartCoroutine(GameOptionsSender.SendAllGameOptionsAsync());
+ }
+}
- if (AmongUsClient.Instance.ClientId == clientId)
+[HarmonyPatch(typeof(InnerNetClient))]
+public class InnerNetClientPatch
+{
+ private static byte Timer;
+
+ [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendInitialData))]
+ [HarmonyPrefix]
+ public static bool SendInitialDataPrefix(InnerNetClient __instance, int clientId)
+ {
+ if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return true;
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(6);
+ messageWriter.Write(__instance.GameId);
+ messageWriter.WritePacked(clientId);
+ List obj = __instance.allObjects;
+
+ lock (obj)
+ {
+ System.Collections.Generic.HashSet hashSet = [];
+
+ for (int i = 0; i < __instance.allObjects.Count; i++)
{
- Logger.SendInGame($"Game Attempting to {(ban ? "Ban" : "Kick")} Host, Blocked the attempt.");
- Logger.Info("Game attempted to kick/ban host....", "KickPlayerPatch");
- return false;
+ InnerNetObject innerNetObject = __instance.allObjects[i]; // False error
+
+ if (innerNetObject && (innerNetObject.OwnerId != -4 || __instance.AmModdedHost) && hashSet.Add(innerNetObject.gameObject))
+ {
+ GameManager gameManager = innerNetObject as GameManager;
+
+ if (gameManager != null)
+ {
+ __instance.SendGameManager(clientId, gameManager);
+ }
+ else
+ {
+ if (innerNetObject is not NetworkedPlayerInfo)
+ __instance.WriteSpawnMessage(innerNetObject, innerNetObject.OwnerId, innerNetObject.SpawnFlags, messageWriter);
+ }
+ }
}
- if (ban) BanManager.AddBanPlayer(AmongUsClient.Instance.GetRecentClient(clientId));
-
- return true;
+ messageWriter.EndMessage();
+ __instance.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
}
+
+ DelaySpawnPlayerInfo(__instance, clientId);
+ return false;
}
- [HarmonyPatch(typeof(ResolutionManager), nameof(ResolutionManager.SetResolution))]
- internal static class SetResolutionManager
+ private static void DelaySpawnPlayerInfo(InnerNetClient __instance, int clientId)
{
- public static void Postfix()
+ var players = GameData.Instance.AllPlayers.ToArray().ToList();
+
+ while (players.Count > 0)
{
- if (MainMenuManagerPatch.UpdateButton != null) MainMenuManagerPatch.UpdateButton.transform.localPosition = MainMenuManagerPatch.Template.transform.localPosition + new Vector3(0.25f, 0.75f);
- }
- }
+ var batch = players.Take(5).ToList();
- [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendAllStreamedObjects))]
- internal static class InnerNetObjectSerializePatch
- {
- public static void Prefix()
- {
- if (AmongUsClient.Instance.AmHost) Main.Instance.StartCoroutine(GameOptionsSender.SendAllGameOptionsAsync());
- }
- }
-
- internal static class InnerNetClientPatch
- {
- private static byte Timer;
-
- [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendInitialData))]
- [HarmonyPrefix]
- public static bool SendInitialDataPrefix(InnerNetClient __instance, int clientId)
- {
- if (!Constants.IsVersionModded() || GameStates.IsInGame || __instance.NetworkMode != NetworkModes.OnlineGame) return true;
-
- // We make sure other stuff like PlayerControl and LobbyBehavior is spawned properly
- // Then we spawn the networked data for new clients
MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
messageWriter.StartMessage(6);
messageWriter.Write(__instance.GameId);
messageWriter.WritePacked(clientId);
- List obj = __instance.allObjects;
- lock (obj)
+ foreach (var player in batch)
{
- System.Collections.Generic.HashSet hashSet = [];
+ if (messageWriter.Length > 500) break;
- for (var i = 0; i < __instance.allObjects.Count; i++)
+ if (player != null && player.ClientId != clientId && !player.Disconnected)
{
- InnerNetObject innerNetObject = __instance.allObjects[i]; // False error
-
- if (innerNetObject && (innerNetObject.OwnerId != -4 || __instance.AmModdedHost) && hashSet.Add(innerNetObject.gameObject))
- {
- var gameManager = innerNetObject as GameManager;
-
- if (gameManager != null)
- __instance.SendGameManager(clientId, gameManager);
- else
- {
- if (innerNetObject is not NetworkedPlayerInfo) __instance.WriteSpawnMessage(innerNetObject, innerNetObject.OwnerId, innerNetObject.SpawnFlags, messageWriter);
- }
- }
+ __instance.WriteSpawnMessage(player, player.OwnerId, player.SpawnFlags, messageWriter);
}
- messageWriter.EndMessage();
- __instance.SendOrDisconnect(messageWriter);
- messageWriter.Recycle();
+ players.Remove(player);
}
- DelaySpawnPlayerInfo(__instance, clientId);
- return false;
- }
-
- private static void DelaySpawnPlayerInfo(InnerNetClient __instance, int clientId)
- {
- Il2CppArrayBase players = GameData.Instance.AllPlayers.ToArray();
-
- // We send 5 players at a time to prevent too large packets
- foreach (NetworkedPlayerInfo[] batch in players.Chunk(5))
- {
- MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
- messageWriter.StartMessage(6);
- messageWriter.Write(__instance.GameId);
- messageWriter.WritePacked(clientId);
-
- batch.DoIf(p => p != null && p.ClientId != clientId && !p.Disconnected, p => __instance.WriteSpawnMessage(p, p.OwnerId, p.SpawnFlags, messageWriter));
-
- messageWriter.EndMessage();
- __instance.SendOrDisconnect(messageWriter);
- messageWriter.Recycle();
- }
- }
-
- [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendAllStreamedObjects))]
- [HarmonyPrefix]
- public static bool SendAllStreamedObjectsPrefix(InnerNetClient __instance, ref bool __result)
- {
- if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return true;
-
- // Bypass all NetworkedData here.
- __result = false;
- List obj = __instance.allObjects;
-
- lock (obj)
- {
- for (var i = 0; i < __instance.allObjects.Count; i++)
- {
- InnerNetObject innerNetObject = __instance.allObjects[i]; // False error
-
- if (innerNetObject && innerNetObject is not NetworkedPlayerInfo && innerNetObject.IsDirty && (innerNetObject.AmOwner || (innerNetObject.OwnerId == -2 && __instance.AmHost)))
- {
- MessageWriter messageWriter = __instance.Streams[(int)innerNetObject.sendMode];
- messageWriter.StartMessage(1);
- messageWriter.WritePacked(innerNetObject.NetId);
-
- try
- {
- if (innerNetObject.Serialize(messageWriter, false))
- messageWriter.EndMessage();
- else
- messageWriter.CancelMessage();
-
- if (innerNetObject.Chunked && innerNetObject.IsDirty) __result = true;
- }
- catch (Exception ex)
- {
- Logger.Exception(ex, "SendAllStreamedObjectsPrefix");
- messageWriter.CancelMessage();
- }
- }
- }
- }
-
- for (var j = 0; j < __instance.Streams.Length; j++)
- {
- MessageWriter messageWriter2 = __instance.Streams[j];
-
- if (messageWriter2.HasBytes(7))
- {
- messageWriter2.EndMessage();
- __instance.SendOrDisconnect(messageWriter2);
- messageWriter2.Clear((SendOption)j);
- messageWriter2.StartMessage(5);
- messageWriter2.Write(__instance.GameId);
- }
- }
-
- return false;
- }
-
- [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.FixedUpdate))]
- [HarmonyPostfix]
- public static void FixedUpdatePostfix(InnerNetClient __instance)
- {
- if (!Constants.IsVersionModded() || GameStates.IsInGame || !__instance.AmHost || __instance.Streams == null || __instance.NetworkMode != NetworkModes.OnlineGame) return;
-
- if (Timer == 0)
- {
- Timer = 1;
- return;
- }
-
- NetworkedPlayerInfo player = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(x => x.IsDirty);
-
- if (player != null)
- {
- Timer = 0;
- MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
- messageWriter.StartMessage(5);
- messageWriter.Write(__instance.GameId);
- messageWriter.StartMessage(1);
- messageWriter.WritePacked(player.NetId);
-
- try
- {
- if (player.Serialize(messageWriter, false))
- messageWriter.EndMessage();
- else
- {
- messageWriter.CancelMessage();
- player.ClearDirtyBits();
- return;
- }
-
- messageWriter.EndMessage();
- __instance.SendOrDisconnect(messageWriter);
- messageWriter.Recycle();
- }
- catch (Exception ex)
- {
- Logger.Exception(ex, "FixedUpdatePostfix");
- messageWriter.CancelMessage();
- player.ClearDirtyBits();
- }
- }
+ messageWriter.EndMessage();
+ __instance.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
}
}
- [HarmonyPatch]
- internal static class AuthTimeoutPatch
+ [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendAllStreamedObjects))]
+ [HarmonyPrefix]
+ public static bool SendAllStreamedObjectsPrefix(InnerNetClient __instance, ref bool __result)
{
- [HarmonyPatch(typeof(AuthManager._CoConnect_d__4), nameof(AuthManager._CoConnect_d__4.MoveNext))]
- [HarmonyPatch(typeof(AuthManager._CoWaitForNonce_d__6), nameof(AuthManager._CoWaitForNonce_d__6.MoveNext))]
- [HarmonyPrefix]
- // From Reactor.gg
- // https://github.com/NuclearPowered/Reactor/blob/master/Reactor/Patches/Miscellaneous/CustomServersPatch.cs
- public static bool CoWaitforNoncePrefix(ref bool __result)
- {
- if (GameStates.IsVanillaServer) return true;
+ if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return true;
+ __result = false;
+ List obj = __instance.allObjects;
- __result = false;
- return false;
+ lock (obj)
+ {
+ for (int i = 0; i < __instance.allObjects.Count; i++)
+ {
+ InnerNetObject innerNetObject = __instance.allObjects[i]; // False error
+
+ if (innerNetObject && innerNetObject is not NetworkedPlayerInfo && innerNetObject.IsDirty && (innerNetObject.AmOwner || (innerNetObject.OwnerId == -2 && __instance.AmHost)))
+ {
+ MessageWriter messageWriter = __instance.Streams[(int)innerNetObject.sendMode];
+ messageWriter.StartMessage(1);
+ messageWriter.WritePacked(innerNetObject.NetId);
+
+ try
+ {
+ if (innerNetObject.Serialize(messageWriter, false))
+ {
+ messageWriter.EndMessage();
+ }
+ else
+ {
+ messageWriter.CancelMessage();
+ }
+
+ if (innerNetObject.Chunked && innerNetObject.IsDirty)
+ {
+ __result = true;
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Exception(ex, "SendAllStreamedObjectsPrefix");
+ messageWriter.CancelMessage();
+ }
+ }
+ }
}
- // If you don't patch this, you still need to wait for 5 s.
- // I have no idea why this is happening
- [HarmonyPatch(typeof(AmongUsClient._CoJoinOnlinePublicGame_d__1), nameof(AmongUsClient._CoJoinOnlinePublicGame_d__1.MoveNext))]
- [HarmonyPrefix]
- public static void EnableUdpMatchmakingPrefix(AmongUsClient._CoJoinOnlinePublicGame_d__1 __instance)
+ for (int j = 0; j < __instance.Streams.Length; j++)
{
- // Skip to state 1, which just calls CoJoinOnlineGameDirect
- if (__instance.__1__state == 0 && !ServerManager.Instance.IsHttp)
- {
- __instance.__1__state = 1;
+ MessageWriter messageWriter2 = __instance.Streams[j];
- __instance.__8__1 = new()
- {
- matchmakerToken = string.Empty
- };
+ if (messageWriter2.HasBytes(7))
+ {
+ messageWriter2.EndMessage();
+ __instance.SendOrDisconnect(messageWriter2);
+ messageWriter2.Clear((SendOption)j);
+ messageWriter2.StartMessage(5);
+ messageWriter2.Write(__instance.GameId);
}
}
+
+ return false;
+ }
+
+ [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Spawn))]
+ [HarmonyPostfix]
+ public static void Spawn_Postfix(InnerNetClient __instance, InnerNetObject netObjParent, int ownerId = -2, SpawnFlags flags = SpawnFlags.None)
+ {
+ if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return;
+
+ if (__instance.AmHost)
+ {
+ switch (netObjParent)
+ {
+ case NetworkedPlayerInfo playerinfo:
+ LateTask.New(() =>
+ {
+ if (playerinfo != null && AmongUsClient.Instance.AmConnected)
+ {
+ var client = AmongUsClient.Instance.GetClient(playerinfo.ClientId);
+
+ if (client != null && !client.IsDisconnected())
+ {
+ if (playerinfo.IsIncomplete)
+ {
+ Logger.Info($"Disconnecting Client [{client.Id}]{client.PlayerName} {client.FriendCode} for playerinfo timeout", "DelayedNetworkedData");
+ AmongUsClient.Instance.SendLateRejection(client.Id, DisconnectReasons.ClientTimeout);
+ __instance.OnPlayerLeft(client, DisconnectReasons.ClientTimeout);
+ }
+ }
+ }
+ }, 5f, "PlayerInfo Green Bean Kick", false);
+
+ break;
+ case PlayerControl player:
+ LateTask.New(() =>
+ {
+ if (player != null && !player.notRealPlayer && !player.isDummy && AmongUsClient.Instance.AmConnected)
+ {
+ var client = AmongUsClient.Instance.GetClient(player.OwnerId);
+
+ if (client != null && !client.IsDisconnected())
+ {
+ if (player.Data == null || player.Data.IsIncomplete)
+ {
+ Logger.Info($"Disconnecting Client [{client.Id}]{client.PlayerName} {client.FriendCode} for playercontrol timeout", "DelayedNetworkedData");
+ AmongUsClient.Instance.SendLateRejection(client.Id, DisconnectReasons.ClientTimeout);
+ __instance.OnPlayerLeft(client, DisconnectReasons.ClientTimeout);
+ }
+ }
+ }
+ }, 5.5f, "PlayerControl Green Bean Kick", false);
+
+ break;
+ }
+ }
+
+ if (!__instance.AmHost)
+ {
+ Debug.LogError("Tried to spawn while not host:" + (netObjParent?.ToString()));
+ }
+ }
+
+ [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.FixedUpdate))]
+ [HarmonyPostfix]
+ public static void FixedUpdatePostfix(InnerNetClient __instance)
+ {
+ if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return;
+ if (!__instance.AmHost || __instance.Streams == null) return;
+
+ if (Timer == 0)
+ {
+ Timer = 1;
+ return;
+ }
+
+ var player = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(x => x.IsDirty);
+
+ if (player != null)
+ {
+ Timer = 0;
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(5);
+ messageWriter.Write(__instance.GameId);
+ messageWriter.StartMessage(1);
+ messageWriter.WritePacked(player.NetId);
+
+ try
+ {
+ if (player.Serialize(messageWriter, false))
+ {
+ messageWriter.EndMessage();
+ }
+ else
+ {
+ messageWriter.CancelMessage();
+ player.ClearDirtyBits();
+ return;
+ }
+
+ messageWriter.EndMessage();
+ __instance.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
+ }
+ catch (Exception ex)
+ {
+ Logger.Exception(ex, "FixedUpdatePostfix");
+ messageWriter.CancelMessage();
+ player.ClearDirtyBits();
+ }
+ }
+ }
+}
+
+[HarmonyPatch(typeof(GameData), nameof(GameData.DirtyAllData))]
+internal class DirtyAllDataPatch
+{
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public static bool Prefix()
+ {
+ return false;
+ }
+}
+
+[HarmonyPatch]
+internal static class AuthTimeoutPatch
+{
+ [HarmonyPatch(typeof(AuthManager._CoConnect_d__4), nameof(AuthManager._CoConnect_d__4.MoveNext))]
+ [HarmonyPatch(typeof(AuthManager._CoWaitForNonce_d__6), nameof(AuthManager._CoWaitForNonce_d__6.MoveNext))]
+ [HarmonyPrefix]
+ // From Reactor.gg
+ // https://github.com/NuclearPowered/Reactor/blob/master/Reactor/Patches/Miscellaneous/CustomServersPatch.cs
+ public static bool CoWaitforNoncePrefix(ref bool __result)
+ {
+ if (GameStates.IsVanillaServer) return true;
+
+ __result = false;
+ return false;
+ }
+
+ // If you don't patch this, you still need to wait for 5 s.
+ // I have no idea why this is happening
+ [HarmonyPatch(typeof(AmongUsClient._CoJoinOnlinePublicGame_d__1), nameof(AmongUsClient._CoJoinOnlinePublicGame_d__1.MoveNext))]
+ [HarmonyPrefix]
+ public static void EnableUdpMatchmakingPrefix(AmongUsClient._CoJoinOnlinePublicGame_d__1 __instance)
+ {
+ // Skip to state 1, which just calls CoJoinOnlineGameDirect
+ if (__instance.__1__state == 0 && !ServerManager.Instance.IsHttp)
+ {
+ __instance.__1__state = 1;
+
+ __instance.__8__1 = new()
+ {
+ matchmakerToken = string.Empty
+ };
+ }
}
}
\ No newline at end of file
diff --git a/Patches/Crowded.cs b/Patches/Crowded.cs
index 35ff62843..edb484cc7 100644
--- a/Patches/Crowded.cs
+++ b/Patches/Crowded.cs
@@ -1,33 +1,506 @@
-using AmongUs.GameOptions;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using AmongUs.GameOptions;
using HarmonyLib;
+using Il2CppInterop.Runtime.Attributes;
+using TMPro;
+using UnityEngine;
+
+namespace EHR.Patches;
// https://github.com/CrowdedMods/CrowdedMod/blob/master/src/CrowdedMod
-namespace EHR.Patches
-{
- internal static class Crowded
- {
- public const int MaxImpostors = 127 / 2;
+// Niko adjusted mono behavior patches to fit into non-reactor mods
- [HarmonyPatch(typeof(GameOptionsData), nameof(GameOptionsData.AreInvalid))]
- public static class InvalidOptionsPatches
+internal static class Crowded
+{
+ private static CreateOptionsPicker Instance;
+ public static readonly int MaxImpostors = GameOptionsManager.Instance.currentHostOptions.MaxPlayers / 2;
+ private static int MaxPlayers => GameStates.IsVanillaServer ? 15 : 127;
+
+ [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.Awake))]
+ public static class CreateOptionsPickerAwake
+ {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public static void Prefix(CreateOptionsPicker __instance)
{
- public static bool Prefix(GameOptionsData __instance, [HarmonyArgument(0)] int maxExpectedPlayers)
+ Instance = __instance;
+
+ if (GameStates.IsVanillaServer)
{
- return __instance.MaxPlayers > maxExpectedPlayers ||
- __instance.NumImpostors < 1 ||
- __instance.NumImpostors + 1 > maxExpectedPlayers / 2 ||
- __instance.KillDistance is < 0 or > 2 ||
- __instance.PlayerSpeedMod is <= 0f or > 3f;
+ if (GameOptionsManager.Instance.GameHostOptions != null)
+ {
+ if (GameOptionsManager.Instance.GameHostOptions.MaxPlayers > 15)
+ {
+ GameOptionsManager.Instance.GameHostOptions.SetInt(Int32OptionNames.MaxPlayers, 15);
+ }
+ }
}
}
- [HarmonyPatch(typeof(SecurityLogger), nameof(SecurityLogger.Awake))]
- public static class SecurityLoggerPatch
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public static void Postfix(CreateOptionsPicker __instance)
{
- public static void Postfix(ref SecurityLogger __instance)
+ if (__instance.mode != SettingsMode.Host) return;
+
{
- __instance.Timers = new float[127];
+ 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());
+ }));
+
+ Object.Destroy(firstButtonRenderer);
+ var lastButtonRenderer = __instance.MaxPlayerButtons[^1];
+ lastButtonRenderer.GetComponentInChildren().text = "+"; // False error
+ lastButtonRenderer.enabled = false; // False error
+ var lastButtonButton = lastButtonRenderer.GetComponent(); // False error
+ 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,
+ MaxPlayers - 14 + byte.Parse(playerButton.name));
+
+ tmp.text = newValue.ToString();
+ }
+
+ __instance.UpdateMaxPlayersButtons(__instance.GetTargetOptions());
+ }));
+
+ Object.Destroy(lastButtonRenderer); // False error
+
+ 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();
+ }
+ }
+
+ {
+ var secondButton = __instance.ImpostorButtons[1];
+ secondButton.SpriteRenderer.enabled = false;
+ Object.Destroy(secondButton.transform.FindChild("ConsoleHighlight").gameObject);
+ Object.Destroy(secondButton.PassiveButton);
+ Object.Destroy(secondButton.BoxCollider);
+ 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(ServerManager), nameof(ServerManager.SetRegion))]
+ public static class ServerManager_SetRegion
+ {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public static void Postfix()
+ {
+ if (GameStates.IsVanillaServer)
+ {
+ if (GameOptionsManager.Instance.GameHostOptions != null && GameOptionsManager.Instance.GameHostOptions.MaxPlayers > 15)
+ GameOptionsManager.Instance.GameHostOptions.SetInt(Int32OptionNames.MaxPlayers, 15);
+
+ if (Instance)
+ {
+ 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,
+ MaxPlayers - 14 + byte.Parse(playerButton.name));
+
+ tmp.text = newValue.ToString();
+ }
+
+ Instance.UpdateMaxPlayersButtons(Instance.GetTargetOptions());
+ }
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.UpdateMaxPlayersButtons))]
+ public static class CreateOptionsPickerUpdateMaxPlayersButtons
+ {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ 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; // False errors
+ }
+
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.UpdateImpostorsButtons))]
+ public static class CreateOptionsPickerUpdateImpostorsButtons
+ {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public static bool Prefix(CreateOptionsPicker __instance)
+ {
+ return __instance.mode != SettingsMode.Host;
+ }
+ }
+
+ [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.SetImpostorButtons))]
+ public static class CreateOptionsPickerSetImpostorButtons
+ {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public static bool Prefix(CreateOptionsPicker __instance, int numImpostors)
+ {
+ if (__instance.mode != SettingsMode.Host) return true;
+ IGameOptions targetOptions = __instance.GetTargetOptions();
+ targetOptions.SetInt(Int32OptionNames.NumImpostors, numImpostors);
+ __instance.SetTargetOptions(targetOptions);
+ __instance.UpdateImpostorsButtons(numImpostors);
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.SetMaxPlayersButtons))]
+ public static class CreateOptionsPickerSetMaxPlayersButtons
+ {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public static bool Prefix(CreateOptionsPicker __instance, int maxPlayers)
+ {
+ if (DestroyableSingleton.InstanceExists || __instance.mode != SettingsMode.Host)
+ {
+ return true;
+ }
+
+ IGameOptions targetOptions = __instance.GetTargetOptions();
+ targetOptions.SetInt(Int32OptionNames.MaxPlayers, maxPlayers);
+ __instance.SetTargetOptions(targetOptions);
+ __instance.UpdateMaxPlayersButtons(targetOptions);
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(GameOptionsData), nameof(GameOptionsData.AreInvalid))]
+ public static class InvalidOptionsPatches
+ {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public static bool Prefix(GameOptionsData __instance, [HarmonyArgument(0)] int maxExpectedPlayers)
+ {
+ return __instance.MaxPlayers > maxExpectedPlayers ||
+ __instance.NumImpostors < 1 ||
+ __instance.NumImpostors + 1 > maxExpectedPlayers / 2 ||
+ __instance.KillDistance is < 0 or > 2 ||
+ __instance.PlayerSpeedMod is <= 0f or > 3f;
+ }
+ }
+
+ [HarmonyPatch(typeof(SecurityLogger), nameof(SecurityLogger.Awake))]
+ public static class SecurityLoggerPatch
+ {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public static void Postfix(ref SecurityLogger __instance)
+ {
+ __instance.Timers = new float[127];
+ }
+ }
+
+ [HarmonyPatch(typeof(PlayerTab), nameof(PlayerTab.Update))]
+ public static class PlayerTabIsSelectedItemEquippedPatch
+ {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public static void Postfix(PlayerTab __instance)
+ {
+ if (GameOptionsManager.Instance.CurrentGameOptions.MaxPlayers > 15)
+ {
+ __instance.currentColorIsEquipped = false;
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(PlayerTab), nameof(PlayerTab.UpdateAvailableColors))]
+ public static class PlayerTabUpdateAvailableColorsPatch
+ {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public static bool Prefix(PlayerTab __instance)
+ {
+ if (GameOptionsManager.Instance.CurrentGameOptions.MaxPlayers <= 15)
+ {
+ return true;
+ }
+
+ __instance.AvailableColors.Clear();
+
+ for (var i = 0; i < Palette.PlayerColors.Count; i++)
+ {
+ if (!PlayerControl.LocalPlayer || PlayerControl.LocalPlayer.CurrentOutfit.ColorId != i)
+ {
+ __instance.AvailableColors.Add(i);
+ }
+ }
+
+ return false;
+ }
+ }
+}
+
+public class AbstractPagingBehaviour(IntPtr ptr) : MonoBehaviour(ptr)
+{
+ protected const string PageIndexGameObjectName = "CrowdedMod_PageIndex";
+
+ // ReSharper disable once ReplaceWithFieldKeyword
+ private int _page;
+
+ protected static int MaxPerPage => 15;
+
+ public virtual int PageIndex
+ {
+ get => _page;
+ set
+ {
+ _page = value;
+ OnPageChanged();
+ }
+ }
+
+ protected virtual int MaxPageIndex => throw new("MaxPageIndex must be overridden");
+ public virtual void Start() => OnPageChanged();
+
+ public virtual void Update()
+ {
+ if (Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.LeftArrow) || Input.mouseScrollDelta.y > 0f)
+ Cycle(false);
+ else if (Input.GetKeyDown(KeyCode.DownArrow) || Input.GetKeyDown(KeyCode.RightArrow) || Input.mouseScrollDelta.y < 0f)
+ Cycle(true);
+ }
+
+ public virtual void OnPageChanged() => throw new("OnPageChanged must be overridden");
+
+ ///
+ /// Loops around if you go over the limits.
+ /// Attempting to go up a page while on the first page will take you to the last page and vice versa.
+ ///
+ public virtual void Cycle(bool increment)
+ {
+ var change = increment ? 1 : -1;
+ PageIndex = Mathf.Clamp(PageIndex + change, 0, MaxPageIndex);
+ }
+}
+
+public class MeetingHudPagingBehaviour(IntPtr ptr) : AbstractPagingBehaviour(ptr)
+{
+ private readonly MeetingHud meetingHud = null!;
+ [HideFromIl2Cpp] private IEnumerable Targets => meetingHud.playerStates.OrderBy(p => p.AmDead);
+
+ protected override int MaxPageIndex => (Targets.Count() - 1) / MaxPerPage;
+ public override void Start() => OnPageChanged();
+
+ public override void Update()
+ {
+ base.Update();
+ // Sometimes the timer text is spammed with the page counter for some eccentric reason, so this is just a Band-Aid fix for it
+ if (meetingHud.state is MeetingHud.VoteStates.Animating or MeetingHud.VoteStates.Proceeding || meetingHud.TimerText.text.Contains($" ({PageIndex + 1}/{MaxPageIndex + 1})")) return; // TimerText does not update there
+ meetingHud.TimerText.text += $" ({PageIndex + 1}/{MaxPageIndex + 1})";
+ }
+
+ public override void OnPageChanged()
+ {
+ var i = 0;
+
+ foreach (var button in Targets)
+ {
+ if (i >= PageIndex * MaxPerPage && i < (PageIndex + 1) * MaxPerPage)
+ {
+ button.gameObject.SetActive(true);
+ var relativeIndex = i % MaxPerPage;
+ var row = relativeIndex / 3;
+ var col = relativeIndex % 3;
+ var buttonTransform = button.transform;
+
+ buttonTransform.localPosition = meetingHud.VoteOrigin +
+ new Vector3(
+ meetingHud.VoteButtonOffsets.x * col,
+ meetingHud.VoteButtonOffsets.y * row,
+ buttonTransform.localPosition.z
+ );
+ }
+ else button.gameObject.SetActive(false);
+
+ i++;
+ }
+ }
+}
+
+public class ShapeShifterPagingBehaviour(IntPtr ptr) : AbstractPagingBehaviour(ptr)
+{
+ public ShapeshifterMinigame shapeshifterMinigame = null!;
+ private TextMeshPro PageText = null!;
+ [HideFromIl2Cpp] private IEnumerable Targets => shapeshifterMinigame.potentialVictims.ToArray();
+
+ protected override int MaxPageIndex => (Targets.Count() - 1) / MaxPerPage;
+
+ public override void Start()
+ {
+ PageText = Instantiate(HudManager.Instance.KillButton.cooldownTimerText, shapeshifterMinigame.transform);
+ PageText.name = PageIndexGameObjectName;
+ PageText.enableWordWrapping = false;
+ PageText.gameObject.SetActive(true);
+ PageText.transform.localPosition = new(4.1f, -2.36f, -1f);
+ PageText.transform.localScale *= 0.5f;
+ OnPageChanged();
+ }
+
+ public override void OnPageChanged()
+ {
+ PageText.text = $"({PageIndex + 1}/{MaxPageIndex + 1})";
+ var i = 0;
+
+ foreach (var panel in Targets)
+ {
+ if (i >= PageIndex * MaxPerPage && i < (PageIndex + 1) * MaxPerPage)
+ {
+ panel.gameObject.SetActive(true);
+ var relativeIndex = i % MaxPerPage;
+ var row = relativeIndex / 3;
+ var col = relativeIndex % 3;
+ var buttonTransform = panel.transform;
+
+ buttonTransform.localPosition =
+ new(
+ shapeshifterMinigame.XStart + shapeshifterMinigame.XOffset * col,
+ shapeshifterMinigame.YStart + shapeshifterMinigame.YOffset * row,
+ buttonTransform.localPosition.z
+ );
+ }
+ else panel.gameObject.SetActive(false);
+
+ i++;
+ }
+ }
+}
+
+public class VitalsPagingBehaviour(IntPtr ptr) : AbstractPagingBehaviour(ptr)
+{
+ public VitalsMinigame vitalsMinigame = null!;
+ private TextMeshPro PageText = null!;
+ [HideFromIl2Cpp] private IEnumerable Targets => vitalsMinigame.vitals.ToArray();
+
+ protected override int MaxPageIndex => (Targets.Count() - 1) / MaxPerPage;
+
+ public override void Start()
+ {
+ PageText = Instantiate(HudManager.Instance.KillButton.cooldownTimerText, vitalsMinigame.transform);
+ PageText.name = PageIndexGameObjectName;
+ PageText.enableWordWrapping = false;
+ PageText.gameObject.SetActive(true);
+ PageText.transform.localPosition = new(2.7f, -2f, -1f);
+ PageText.transform.localScale *= 0.5f;
+ OnPageChanged();
+ }
+
+ public override void OnPageChanged()
+ {
+ if (PlayerTask.PlayerHasTaskOfType(PlayerControl.LocalPlayer))
+ return;
+
+ PageText.text = $"({PageIndex + 1}/{MaxPageIndex + 1})";
+ var i = 0;
+
+ foreach (var panel in Targets)
+ {
+ if (i >= PageIndex * MaxPerPage && i < (PageIndex + 1) * MaxPerPage)
+ {
+ panel.gameObject.SetActive(true);
+ var relativeIndex = i % MaxPerPage;
+ var row = relativeIndex / 3;
+ var col = relativeIndex % 3;
+ var panelTransform = panel.transform;
+
+ panelTransform.localPosition =
+ new(
+ vitalsMinigame.XStart + vitalsMinigame.XOffset * col,
+ vitalsMinigame.YStart + vitalsMinigame.YOffset * row,
+ panelTransform.localPosition.z
+ );
+ }
+ else panel.gameObject.SetActive(false);
+
+ i++;
+ }
+ }
}
\ No newline at end of file
diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs
index 84145bd7c..c247f1bc0 100644
--- a/Patches/GameOptionsMenuPatch.cs
+++ b/Patches/GameOptionsMenuPatch.cs
@@ -511,10 +511,9 @@ namespace EHR
__instance.Value = (float)Math.Round(__instance.Value, 2);
break;
case StringNames.GameNumImpostors:
- __instance.ValidRange = new(1, Crowded.MaxImpostors);
+ __instance.ValidRange = new(0, Crowded.MaxImpostors);
__instance.Value = (float)Math.Round(__instance.Value, 2);
if (DebugModeManager.AmDebugger) __instance.ValidRange.min = 0;
-
break;
}
diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs
index 25a934c8c..a97fd4c8d 100644
--- a/Patches/IntroPatch.cs
+++ b/Patches/IntroPatch.cs
@@ -506,10 +506,7 @@ namespace EHR
CustomRoles.NiceGuesser
=> PlayerControl.LocalPlayer.KillSfx,
- CustomRoles.Swooper or
- CustomRoles.Wraith or
- CustomRoles.Chameleon or
- CustomRoles.Drainer
+ CustomRoles.Drainer
=> PlayerControl.LocalPlayer.MyPhysics.ImpostorDiscoveredSound,
CustomRoles.Addict or
@@ -534,10 +531,15 @@ namespace EHR
CustomRoles.Noisemaker
or CustomRoles.NoisemakerEHR
+ or CustomRoles.DarkHide
+ or CustomRoles.Specter
=> GetIntroSound(RoleTypes.Noisemaker),
CustomRoles.Phantom
or CustomRoles.PhantomEHR
+ or CustomRoles.Swooper
+ or CustomRoles.Wraith
+ or CustomRoles.Chameleon
=> GetIntroSound(RoleTypes.Phantom),
CustomRoles.Shapeshifter
diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs
index b5dcc22c3..8ccfba959 100644
--- a/Patches/PlayerJoinAndLeftPatch.cs
+++ b/Patches/PlayerJoinAndLeftPatch.cs
@@ -109,7 +109,7 @@ namespace EHR
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerJoined))]
internal static class OnPlayerJoinedPatch
{
- private static bool IsDisconnected(this ClientData client)
+ public static bool IsDisconnected(this ClientData client)
{
var __instance = AmongUsClient.Instance;
diff --git a/README.md b/README.md
index 9b04e9c24..bbe77d60e 100644
--- a/README.md
+++ b/README.md
@@ -379,6 +379,11 @@ Source language: English
> - Reference: Custom Region Installer (adding modded regions)
>
+### :star: [Crowded](https://github.com/CrowdedMods/CrowdedMod)
+
+>
+> - Crowded is integrated into EHR
+
### Endless Host Roles (EHR), with Endless Possibilities, brought to you by the Endless Network.
##### Developers:
diff --git a/Roles/Crewmate/Addict.cs b/Roles/Crewmate/Addict.cs
index c604fa650..a87b8812d 100644
--- a/Roles/Crewmate/Addict.cs
+++ b/Roles/Crewmate/Addict.cs
@@ -147,5 +147,10 @@ namespace EHR.Crewmate
{
hud.AbilityButton?.OverrideText(Translator.GetString("AddictVentButtonText"));
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Alchemist.cs b/Roles/Crewmate/Alchemist.cs
index 3affa27fe..5ceb2ca2c 100644
--- a/Roles/Crewmate/Alchemist.cs
+++ b/Roles/Crewmate/Alchemist.cs
@@ -459,5 +459,10 @@ namespace EHR.Crewmate
else
hud.AbilityButton?.OverrideText(GetString("AlchemistVentButtonText"));
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs
index 420dbdf67..5728a934d 100644
--- a/Roles/Crewmate/Altruist.cs
+++ b/Roles/Crewmate/Altruist.cs
@@ -160,5 +160,10 @@ namespace EHR.Crewmate
if (ReviveStartTS != 0) return string.Format(Translator.GetString("AltruistSuffixRevive"), ReviveTime.GetInt() - (Utils.TimeStamp - ReviveStartTS));
return string.Format(Translator.GetString("AltruistSuffix"), Translator.GetString(RevivingMode ? "AltruistReviveMode" : "AltruistReportMode"));
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/CameraMan.cs b/Roles/Crewmate/CameraMan.cs
index 0b6e057f7..fe901fd3f 100644
--- a/Roles/Crewmate/CameraMan.cs
+++ b/Roles/Crewmate/CameraMan.cs
@@ -121,5 +121,10 @@ namespace EHR.Crewmate
{
IsTeleported = false;
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Catcher.cs b/Roles/Crewmate/Catcher.cs
index 8dd7a9720..262e3d421 100644
--- a/Roles/Crewmate/Catcher.cs
+++ b/Roles/Crewmate/Catcher.cs
@@ -180,5 +180,10 @@ namespace EHR.Crewmate
return string.Format(Translator.GetString("Catcher.Suffix"), TrapPlaceDelay.GetInt() - (Utils.TimeStamp - DelayStartTS));
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Clairvoyant.cs b/Roles/Crewmate/Clairvoyant.cs
index 52bc676d5..55acb3636 100644
--- a/Roles/Crewmate/Clairvoyant.cs
+++ b/Roles/Crewmate/Clairvoyant.cs
@@ -58,5 +58,10 @@ namespace EHR.Crewmate
{
UseAbility(pc);
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Convener.cs b/Roles/Crewmate/Convener.cs
index ea03fa051..b96d59abb 100644
--- a/Roles/Crewmate/Convener.cs
+++ b/Roles/Crewmate/Convener.cs
@@ -74,5 +74,10 @@ namespace EHR.Crewmate
pc.RpcRemoveAbilityUse();
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Dad.cs b/Roles/Crewmate/Dad.cs
index 5f3b1fae3..ea96389f1 100644
--- a/Roles/Crewmate/Dad.cs
+++ b/Roles/Crewmate/Dad.cs
@@ -459,5 +459,10 @@ namespace EHR.Crewmate
return sb.ToString();
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Doormaster.cs b/Roles/Crewmate/Doormaster.cs
index 94e9db859..bdd8be3d4 100644
--- a/Roles/Crewmate/Doormaster.cs
+++ b/Roles/Crewmate/Doormaster.cs
@@ -80,5 +80,10 @@ namespace EHR.Crewmate
else
pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks"));
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/DovesOfNeace.cs b/Roles/Crewmate/DovesOfNeace.cs
index 3efa89f47..5a01717b0 100644
--- a/Roles/Crewmate/DovesOfNeace.cs
+++ b/Roles/Crewmate/DovesOfNeace.cs
@@ -96,5 +96,10 @@ namespace EHR.Crewmate
pc.RPCPlayCustomSound("Dove");
pc.Notify(string.Format(Translator.GetString("DovesOfNeaceOnGuard"), pc.GetAbilityUseLimit()));
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Drainer.cs b/Roles/Crewmate/Drainer.cs
index 3ce21533e..33461a16d 100644
--- a/Roles/Crewmate/Drainer.cs
+++ b/Roles/Crewmate/Drainer.cs
@@ -113,5 +113,10 @@ namespace EHR.Crewmate
PlayersInVents.Clear();
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Druid.cs b/Roles/Crewmate/Druid.cs
index cbc073fc0..d2a332939 100644
--- a/Roles/Crewmate/Druid.cs
+++ b/Roles/Crewmate/Druid.cs
@@ -213,5 +213,10 @@ namespace EHR.Crewmate
return sb.ToString();
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Grenadier.cs b/Roles/Crewmate/Grenadier.cs
index a996843d9..b8e1c223b 100644
--- a/Roles/Crewmate/Grenadier.cs
+++ b/Roles/Crewmate/Grenadier.cs
@@ -144,5 +144,10 @@ namespace EHR.Crewmate
else
pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks"));
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Lighter.cs b/Roles/Crewmate/Lighter.cs
index 10d9546eb..266742ebb 100644
--- a/Roles/Crewmate/Lighter.cs
+++ b/Roles/Crewmate/Lighter.cs
@@ -140,5 +140,10 @@ namespace EHR.Crewmate
player.MarkDirtySettings();
}
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Mayor.cs b/Roles/Crewmate/Mayor.cs
index 1332e5df0..83227affb 100644
--- a/Roles/Crewmate/Mayor.cs
+++ b/Roles/Crewmate/Mayor.cs
@@ -95,5 +95,10 @@ namespace EHR.Crewmate
OverrideTasksData.Create(9516, TabGroup.CrewmateRoles, CustomRoles.Mayor);
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Mole.cs b/Roles/Crewmate/Mole.cs
index fa9d9fa74..d1fd1bc41 100644
--- a/Roles/Crewmate/Mole.cs
+++ b/Roles/Crewmate/Mole.cs
@@ -50,5 +50,10 @@ namespace EHR.Crewmate
{
pc.TPToRandomVent();
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/NiceHacker.cs b/Roles/Crewmate/NiceHacker.cs
index f7346de57..02cd9e981 100644
--- a/Roles/Crewmate/NiceHacker.cs
+++ b/Roles/Crewmate/NiceHacker.cs
@@ -208,5 +208,10 @@ namespace EHR.Crewmate
return sb.ToString();
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Oxyman.cs b/Roles/Crewmate/Oxyman.cs
index d21155751..b699e4299 100644
--- a/Roles/Crewmate/Oxyman.cs
+++ b/Roles/Crewmate/Oxyman.cs
@@ -187,6 +187,11 @@ namespace EHR.Crewmate
return $"<#ff0000>O2: {Utils.ColorString(GetLevelColor(), OxygenLevel.ToString())}%";
}
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
+
private enum Level
{
Blind,
diff --git a/Roles/Crewmate/Paranoia.cs b/Roles/Crewmate/Paranoia.cs
index a5d5eadad..6978bfba7 100644
--- a/Roles/Crewmate/Paranoia.cs
+++ b/Roles/Crewmate/Paranoia.cs
@@ -76,5 +76,10 @@ namespace EHR.Crewmate
pc.NoCheckStartMeeting(pc.Data);
}
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Perceiver.cs b/Roles/Crewmate/Perceiver.cs
index f43fdadb3..118312ca1 100644
--- a/Roles/Crewmate/Perceiver.cs
+++ b/Roles/Crewmate/Perceiver.cs
@@ -77,5 +77,10 @@ namespace EHR.Crewmate
pc.RpcRemoveAbilityUse();
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Rhapsode.cs b/Roles/Crewmate/Rhapsode.cs
index 5fc2fa4c1..643173ba7 100644
--- a/Roles/Crewmate/Rhapsode.cs
+++ b/Roles/Crewmate/Rhapsode.cs
@@ -131,5 +131,10 @@ namespace EHR.Crewmate
return $"\u25b6 ({AbilityDuration.GetInt() - (Utils.TimeStamp - ActivateTimeStamp)}s)";
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/SecurityGuard.cs b/Roles/Crewmate/SecurityGuard.cs
index 904bc1108..57ba4523a 100644
--- a/Roles/Crewmate/SecurityGuard.cs
+++ b/Roles/Crewmate/SecurityGuard.cs
@@ -109,5 +109,10 @@ namespace EHR.Crewmate
player.Notify(Translator.GetString("SecurityGuardSkillStop"));
}
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Sentinel.cs b/Roles/Crewmate/Sentinel.cs
index 4b987f411..ce8a1eb6f 100644
--- a/Roles/Crewmate/Sentinel.cs
+++ b/Roles/Crewmate/Sentinel.cs
@@ -201,5 +201,10 @@ namespace EHR.Crewmate
state.FinishPatrolling();
}, 0.1f, "SentinelFinishPatrol");
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Soothsayer.cs b/Roles/Crewmate/Soothsayer.cs
index 8f31bc346..0bcaf9380 100644
--- a/Roles/Crewmate/Soothsayer.cs
+++ b/Roles/Crewmate/Soothsayer.cs
@@ -49,7 +49,7 @@ namespace EHR.Crewmate
public static void OnAnyoneDeath(PlayerControl killer, PlayerControl target)
{
- Instances.DoIf(x => x.Target == target.PlayerId, _ => Main.AllAlivePlayerControls.Do(p => p.Notify(string.Format(Translator.GetString("SoothsayerDiedNotify"), Utils.ColorString(Main.PlayerColors.GetValueOrDefault(killer.PlayerId), killer.GetRealName())), 10f)));
+ Instances.DoIf(x => x.Target == killer.PlayerId, _ => Main.AllAlivePlayerControls.Do(p => p.Notify(string.Format(Translator.GetString("SoothsayerDiedNotify"), Utils.ColorString(Main.PlayerColors.GetValueOrDefault(killer.PlayerId), killer.GetRealName())), 10f)));
}
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Telekinetic.cs b/Roles/Crewmate/Telekinetic.cs
index 585022623..9ad60b9a6 100644
--- a/Roles/Crewmate/Telekinetic.cs
+++ b/Roles/Crewmate/Telekinetic.cs
@@ -211,6 +211,11 @@ namespace EHR.Crewmate
return $"<#ffffff>{Timer}{base.GetProgressText(playerId, comms)}";
}
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
+
private enum Mode
{
TeleportTarget,
diff --git a/Roles/Crewmate/Tether.cs b/Roles/Crewmate/Tether.cs
index b58480ef0..67231b154 100644
--- a/Roles/Crewmate/Tether.cs
+++ b/Roles/Crewmate/Tether.cs
@@ -145,5 +145,10 @@ namespace EHR.Crewmate
{
return Target != byte.MaxValue && seer.PlayerId == target.PlayerId && seer.PlayerId == TetherId ? $"Target: {Utils.GetPlayerById(Target).GetRealName()}" : string.Empty;
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/TimeMaster.cs b/Roles/Crewmate/TimeMaster.cs
index 7d7c065bc..5688a229f 100644
--- a/Roles/Crewmate/TimeMaster.cs
+++ b/Roles/Crewmate/TimeMaster.cs
@@ -141,5 +141,10 @@ namespace EHR.Crewmate
player.Notify(Translator.GetString("TimeMasterSkillStop"), (int)player.GetAbilityUseLimit());
}
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/ToiletMaster.cs b/Roles/Crewmate/ToiletMaster.cs
index a2edfbb3f..7d0a2cd31 100644
--- a/Roles/Crewmate/ToiletMaster.cs
+++ b/Roles/Crewmate/ToiletMaster.cs
@@ -304,6 +304,11 @@ namespace EHR.Crewmate
return true;
}
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
+
private enum ToiletVisibilityOptions
{
Instant,
diff --git a/Roles/Crewmate/Veteran.cs b/Roles/Crewmate/Veteran.cs
index 68a3e371e..35fd87f07 100644
--- a/Roles/Crewmate/Veteran.cs
+++ b/Roles/Crewmate/Veteran.cs
@@ -139,5 +139,10 @@ namespace EHR.Crewmate
player.Notify(string.Format(Translator.GetString("VeteranOffGuard"), (int)player.GetAbilityUseLimit()));
}
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Impostor/AntiAdminer.cs b/Roles/Impostor/AntiAdminer.cs
index 06097c8a2..f643c2a40 100644
--- a/Roles/Impostor/AntiAdminer.cs
+++ b/Roles/Impostor/AntiAdminer.cs
@@ -315,5 +315,10 @@ namespace EHR.Impostor
return aa == null ? string.Empty : $"<#ffff00>\u26a0 {Delay.GetInt() - (Utils.TimeStamp - aa.ExtraAbilityStartTimeStamp):N0}";
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return !IsMonitor || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/Impostor/Swooper.cs b/Roles/Impostor/Swooper.cs
index 067a16be5..b84abb103 100644
--- a/Roles/Impostor/Swooper.cs
+++ b/Roles/Impostor/Swooper.cs
@@ -291,5 +291,10 @@ namespace EHR.Impostor
target.RpcCheckAndMurder(target);
return false;
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return UsedRole != CustomRoles.Chameleon || pc.GetClosestVent()?.Id == ventId;
+ }
}
}
\ No newline at end of file
diff --git a/Roles/RoleBase.cs b/Roles/RoleBase.cs
index f7aba2960..b88892f00 100644
--- a/Roles/RoleBase.cs
+++ b/Roles/RoleBase.cs
@@ -167,6 +167,17 @@ namespace EHR
return seerRole.IsNK() && seerRole == target.GetCustomRole() && seer.GetTeam() == target.GetTeam();
}
+ protected bool IsThisRole(PlayerControl pc)
+ {
+ return pc.GetCustomRole() == Enum.Parse(GetType().Name, true);
+ }
+
+ protected bool IsThisRole(byte id)
+ {
+ var role = Main.PlayerStates.TryGetValue(id, out var state) ? state.MainRole : CustomRoles.NotAssigned;
+ return role == Enum.Parse(GetType().Name, true);
+ }
+
// Option setup simplifier
protected OptionSetupHandler StartSetup(int id, bool single = false)
{
@@ -179,6 +190,7 @@ namespace EHR
if (single) Options.SetupSingleRoleOptions(id++, tab, role, hideMaxSetting: true);
else Options.SetupRoleOptions(id++, tab, role);
+
return new(id, tab, role);
}
}
diff --git a/main.cs b/main.cs
index 04337ad49..41b26ba3f 100644
--- a/main.cs
+++ b/main.cs
@@ -12,6 +12,7 @@ using BepInEx.Unity.IL2CPP.Utils.Collections;
using EHR;
using EHR.Modules;
using EHR.Neutral;
+using EHR.Patches;
using HarmonyLib;
using Il2CppInterop.Runtime.Injection;
using UnityEngine;
@@ -25,6 +26,8 @@ namespace EHR
[BepInPlugin(PluginGuid, "EHR", PluginVersion)]
[BepInIncompatibility("jp.ykundesu.supernewroles")]
[BepInIncompatibility("MalumMenu")]
+ [BepInIncompatibility("com.ten.thebetterroles")]
+ [BepInIncompatibility("xyz.crowdedmods.crowdedmod")]
[BepInProcess("Among Us.exe")]
public class Main : BasePlugin
{
@@ -709,6 +712,13 @@ namespace EHR
handler.Info($"{nameof(ThisAssembly.Git.Tag)}: {ThisAssembly.Git.Tag}");
ClassInjector.RegisterTypeInIl2Cpp();
+ ClassInjector.RegisterTypeInIl2Cpp();
+ ClassInjector.RegisterTypeInIl2Cpp();
+ ClassInjector.RegisterTypeInIl2Cpp();
+
+ NormalGameOptionsV08.RecommendedImpostors = NormalGameOptionsV08.MaxImpostors = Enumerable.Repeat(127, 127).ToArray();
+ NormalGameOptionsV08.MinPlayers = Enumerable.Repeat(4, 127).ToArray();
+ HideNSeekGameOptionsV08.MinPlayers = Enumerable.Repeat(4, 127).ToArray();
CustomLogger.ClearLog();
LogMonitor.StartMonitoring();