diff --git a/EHR.sln.DotSettings b/EHR.sln.DotSettings
index e377f4788..929413dec 100644
--- a/EHR.sln.DotSettings
+++ b/EHR.sln.DotSettings
@@ -2,6 +2,7 @@
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
SOLUTION
+ AFK
ATK
AU
CD
@@ -12,6 +13,7 @@
FFA
FFF
FPS
+ GA
GM
HP
HUD
diff --git a/Modules/AFKDetector.cs b/Modules/AFKDetector.cs
new file mode 100644
index 000000000..5c49633b3
--- /dev/null
+++ b/Modules/AFKDetector.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace EHR.Modules
+{
+ public static class AFKDetector
+ {
+ private static OptionItem EnableDetector;
+ private static OptionItem ConsequenceOption;
+
+ private static readonly Dictionary PlayerData = [];
+ public static readonly HashSet ExemptedPlayers = [];
+ public static readonly HashSet ShieldedPlayers = [];
+ public static int NumAFK;
+
+ public static void SetupCustomOption()
+ {
+ new TextOptionItem(100080, "MenuTitle.AFKDetection", TabGroup.GameSettings)
+ .SetColor(new Color32(0, 255, 160, 255))
+ .SetHeader(true);
+ EnableDetector = new BooleanOptionItem(90, "EnableAFKDetector", true, TabGroup.GameSettings)
+ .SetColor(new Color32(0, 255, 160, 255));
+ ConsequenceOption = new StringOptionItem(91, "AFKConsequence", Enum.GetNames().Select(x => $"AFKConsequence.{x}").ToArray(), 0, TabGroup.GameSettings)
+ .SetParent(EnableDetector)
+ .SetColor(new Color32(0, 255, 160, 255));
+ }
+
+ public static void RecordPosition(PlayerControl pc)
+ {
+ if (!EnableDetector.GetBool() || ExemptedPlayers.Contains(pc.PlayerId)) return;
+
+ PlayerData[pc.PlayerId] = new()
+ {
+ LastPosition = pc.Pos(),
+ Timer = 10f + (pc.Is(CustomRoles.Truant) ? Options.TruantWaitingTime.GetFloat() : 0f)
+ };
+ }
+
+ public static void OnFixedUpdate(PlayerControl pc)
+ {
+ if (!EnableDetector.GetBool() || !PlayerData.TryGetValue(pc.PlayerId, out var data)) return;
+
+ if (Vector2.Distance(pc.Pos(), data.LastPosition) > 0.1f)
+ {
+ PlayerData.Remove(pc.PlayerId);
+ ShieldedPlayers.Remove(pc.PlayerId);
+ return;
+ }
+
+ int lastTimer = (int)Math.Round(data.Timer);
+ data.Timer -= Time.fixedDeltaTime;
+ int currentTimer = (int)Math.Round(data.Timer);
+
+ if (data.Timer <= 0f)
+ {
+ switch (data.CurrentPhase)
+ {
+ case Data.Phase.Detection:
+ if (!pc.IsModClient()) NumAFK++;
+ data.CurrentPhase = Data.Phase.Warning;
+ data.Timer = 15f;
+ break;
+ case Data.Phase.Warning:
+ data.CurrentPhase = Data.Phase.Consequence;
+ HandleConsequence(pc, (Consequence)ConsequenceOption.GetInt());
+ break;
+ }
+
+ if (Main.AllAlivePlayerControls.Length / 2 <= NumAFK)
+ {
+ Logger.SendInGame(Translator.GetString("AFKTooMany"));
+ PlayerData.Clear();
+ }
+ }
+
+ if (data.CurrentPhase == Data.Phase.Warning && lastTimer != currentTimer)
+ {
+ Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
+ }
+ }
+
+ public static string GetSuffix(PlayerControl seer, PlayerControl target)
+ {
+ if (seer.IsAlive() && PlayerData.TryGetValue(seer.PlayerId, out var seerData) && seerData.CurrentPhase > Data.Phase.Detection)
+ return seerData.CurrentPhase == Data.Phase.Warning ? string.Format(Translator.GetString("AFKWarning"), (int)Math.Round(seerData.Timer)) : Translator.GetString("AFKSuffix");
+
+ if (target.IsAlive() && PlayerData.TryGetValue(target.PlayerId, out var targetData) && targetData.CurrentPhase > Data.Phase.Detection)
+ return Translator.GetString("AFKSuffix");
+
+ return string.Empty;
+ }
+
+ private static void HandleConsequence(PlayerControl pc, Consequence consequence)
+ {
+ switch (consequence)
+ {
+ case Consequence.Shield:
+ ShieldedPlayers.Add(pc.PlayerId);
+ Utils.NotifyRoles(SpecifyTarget: pc);
+ break;
+ case Consequence.Kick:
+ AmongUsClient.Instance.KickPlayer(pc.GetClientId(), false);
+ Logger.SendInGame(string.Format(Translator.GetString("AFKKick"), pc.PlayerId.ColoredPlayerName()));
+ break;
+ case Consequence.Suicide:
+ pc.Suicide(PlayerState.DeathReason.AFK);
+ break;
+ case Consequence.Nothing:
+ Utils.NotifyRoles(SpecifyTarget: pc);
+ break;
+ }
+
+ PlayerData.Remove(pc.PlayerId);
+ }
+
+ enum Consequence
+ {
+ Nothing,
+ Shield,
+ Suicide,
+ Kick
+ }
+
+ class Data
+ {
+ public enum Phase
+ {
+ Detection,
+ Warning,
+ Consequence
+ }
+
+ public Vector2 LastPosition { get; init; }
+ public float Timer { get; set; }
+ public Phase CurrentPhase { get; set; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Modules/CustomNetObject.cs b/Modules/CustomNetObject.cs
index 79cad3818..78f8290c9 100644
--- a/Modules/CustomNetObject.cs
+++ b/Modules/CustomNetObject.cs
@@ -348,7 +348,7 @@ namespace EHR
}
}
- public static void FixedUpdate() => AllObjects.ForEach(x => x.OnFixedUpdate());
+ public static void FixedUpdate() => AllObjects.ToArray().Do(x => x.OnFixedUpdate());
public static CustomNetObject Get(int id) => AllObjects.FirstOrDefault(x => x.Id == id);
public static void Reset()
@@ -395,6 +395,8 @@ namespace EHR
protected override void OnFixedUpdate()
{
+ base.OnFixedUpdate();
+
Timer += Time.deltaTime;
if (Timer >= Duration / 5f && Frame == 0)
{
@@ -448,6 +450,8 @@ namespace EHR
protected override void OnFixedUpdate()
{
+ base.OnFixedUpdate();
+
Timer += Time.deltaTime;
if (Timer >= WaitDuration * 0.75f && State == 0)
{
@@ -477,6 +481,8 @@ namespace EHR
protected override void OnFixedUpdate()
{
+ base.OnFixedUpdate();
+
try
{
if (Gone) return;
diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs
index 84e1a9950..b9178947c 100644
--- a/Modules/CustomRolesHelper.cs
+++ b/Modules/CustomRolesHelper.cs
@@ -785,17 +785,7 @@ internal static class CustomRolesHelper
CustomRoles.Undead or
CustomRoles.Egoist;
- public static bool IsImpOnlyAddon(this CustomRoles role) => role is
- CustomRoles.Mare or
- CustomRoles.LastImpostor or
- CustomRoles.Mare or
- CustomRoles.Mimic or
- CustomRoles.Damocles or
- CustomRoles.DeadlyQuota or
- CustomRoles.Circumvent or
- CustomRoles.TicketsStealer or
- CustomRoles.Taskcounter or
- CustomRoles.Swift;
+ public static bool IsImpOnlyAddon(this CustomRoles role) => Options.GroupedAddons[AddonTypes.ImpOnly].Contains(role);
public static bool IsTaskBasedCrewmate(this CustomRoles role) => role is
CustomRoles.Snitch or
@@ -825,7 +815,7 @@ internal static class CustomRolesHelper
public static bool IsNoAnimationShifter(this CustomRoles role) => role is
CustomRoles.Echo;
- public static bool CheckAddonConflict(CustomRoles role, PlayerControl pc) => role.IsAdditionRole() && (!Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out var neverList) || !neverList.TryGetValue(pc.GetCustomRole(), out var bannedAddonList) || !bannedAddonList.Contains(role)) && pc.GetCustomRole() is not CustomRoles.GuardianAngelEHR and not CustomRoles.God && !pc.Is(CustomRoles.Madmate) && !pc.Is(CustomRoles.GM) && role is not CustomRoles.Lovers && !pc.Is(CustomRoles.Needy) && (!pc.HasSubRole() || pc.GetCustomSubRoles().Count < Options.NoLimitAddonsNumMax.GetInt()) && (!Options.AddonCanBeSettings.TryGetValue(role, out var o) || ((o.Imp.GetBool() || !pc.GetCustomRole().IsImpostor()) && (o.Neutral.GetBool() || !pc.GetCustomRole().IsNeutral()) && (o.Crew.GetBool() || !pc.IsCrewmate()))) && role switch
+ public static bool CheckAddonConflict(CustomRoles role, PlayerControl pc) => role.IsAdditionRole() && (!Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out var neverList) || !neverList.TryGetValue(pc.GetCustomRole(), out var bannedAddonList) || !bannedAddonList.Contains(role)) && pc.GetCustomRole() is not CustomRoles.GuardianAngelEHR and not CustomRoles.God && !pc.Is(CustomRoles.Madmate) && !pc.Is(CustomRoles.GM) && role is not CustomRoles.Lovers && !pc.Is(CustomRoles.Needy) && (!pc.HasSubRole() || pc.GetCustomSubRoles().Count < Options.NoLimitAddonsNumMax.GetInt()) && (!Options.AddonCanBeSettings.TryGetValue(role, out var o) || ((o.Imp.GetBool() || !pc.GetCustomRole().IsImpostor()) && (o.Neutral.GetBool() || !pc.GetCustomRole().IsNeutral()) && (o.Crew.GetBool() || !pc.IsCrewmate()))) && (!role.IsImpOnlyAddon() || pc.IsImpostor()) && role switch
{
CustomRoles.Rookie when !pc.CanUseKillButton() => false,
CustomRoles.Energetic when !Options.UsePets.GetBool() => false,
@@ -840,7 +830,6 @@ internal static class CustomRolesHelper
CustomRoles.Stressed when !pc.IsCrewmate() || pc.GetCustomRole().IsTasklessCrewmate() => false,
CustomRoles.Lazy when pc.GetCustomRole().IsNeutral() || pc.IsImpostor() || (pc.GetCustomRole().IsTasklessCrewmate() && !Options.TasklessCrewCanBeLazy.GetBool()) || (pc.GetCustomRole().IsTaskBasedCrewmate() && !Options.TaskBasedCrewCanBeLazy.GetBool()) => false,
CustomRoles.TicketsStealer or CustomRoles.Swift or CustomRoles.DeadlyQuota or CustomRoles.Damocles or CustomRoles.Mare when pc.Is(CustomRoles.Bomber) || pc.Is(CustomRoles.Nuker) || pc.Is(CustomRoles.BoobyTrap) || pc.Is(CustomRoles.Capitalism) => false,
- CustomRoles.TicketsStealer or CustomRoles.Mimic or CustomRoles.Swift or CustomRoles.DeadlyQuota or CustomRoles.Circumvent or CustomRoles.Damocles or CustomRoles.Taskcounter or CustomRoles.Mare when !pc.IsImpostor() => false,
CustomRoles.Torch when !pc.IsCrewmate() || pc.Is(CustomRoles.Bewilder) || pc.Is(CustomRoles.Sunglasses) || pc.Is(CustomRoles.GuardianAngelEHR) => false,
CustomRoles.Bewilder when pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.GuardianAngelEHR) || pc.Is(CustomRoles.Sunglasses) => false,
CustomRoles.Sunglasses when pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.GuardianAngelEHR) || pc.Is(CustomRoles.Bewilder) => false,
@@ -968,7 +957,6 @@ internal static class CustomRolesHelper
CustomRoles.Charmed or
CustomRoles.Recruit or
CustomRoles.Contagious or
- CustomRoles.Sidekick or
CustomRoles.Undead or
CustomRoles.Lovers;
diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs
index cb7ee1261..9c6887686 100644
--- a/Modules/ExtendedPlayerControl.cs
+++ b/Modules/ExtendedPlayerControl.cs
@@ -153,17 +153,16 @@ static class ExtendedPlayerControl
Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] = name;
Logger.Info($"Set:{player.Data?.PlayerName}:{name} for {seer.GetNameWithRole().RemoveHtmlTags()}", "RpcSetNamePrivate");
+ if (seer == null || player == null) return;
+
var clientId = seer.GetClientId();
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetName, SendOption.Reliable, clientId);
- try
- {
- writer.Write(player.Data.NetId);
- writer.Write(name);
- }
- finally
- {
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- }
+
+ var sender = CustomRpcSender.Create(name: "SetNamePrivate");
+ sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetName, clientId)
+ .Write(seer.Data.NetId)
+ .Write(name)
+ .EndRpc();
+ sender.SendMessage();
}
public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role, bool canOverride, int clientId)
diff --git a/Modules/Extensions/CollectionExtensions.cs b/Modules/Extensions/CollectionExtensions.cs
index d6c199bf0..f28ec355f 100644
--- a/Modules/Extensions/CollectionExtensions.cs
+++ b/Modules/Extensions/CollectionExtensions.cs
@@ -60,7 +60,7 @@ namespace EHR
/// The collection to iterate over
/// The action to execute for each element
/// The type of the elements in the collection
- public static IEnumerable Do(this IEnumerable collection, Action action)
+ public static void Do(this IEnumerable collection, Action action)
{
if (collection is List list)
{
@@ -69,15 +69,13 @@ namespace EHR
action(list[i]);
}
- return collection;
+ return;
}
foreach (T element in collection)
{
action(element);
}
-
- return collection;
}
///
diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs
index bce6ea830..6232d998a 100644
--- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs
+++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs
@@ -214,6 +214,10 @@ public class PlayerGameOptionsSender(PlayerControl player) : GameOptionsSender
break;
}
+ // When impostor alert is off, and the player is a desync crewmate, set impostor alert as true
+ if (role.IsDesyncRole() && role.IsCrewmate() && !CrewmateVanillaRoles.NoiseMakerImpostorAlert.GetBool()) AURoleOptions.NoisemakerImpostorAlert = true;
+ else AURoleOptions.NoisemakerImpostorAlert = CrewmateVanillaRoles.NoiseMakerImpostorAlert.GetBool();
+
if (Shifter.WasShifter.Contains(player.PlayerId) && role.IsImpostor()) opt.SetVision(true);
Main.PlayerStates[player.PlayerId].Role.ApplyGameOptions(opt, player.PlayerId);
diff --git a/Modules/GameState.cs b/Modules/GameState.cs
index 74f691cb9..93c7037b7 100644
--- a/Modules/GameState.cs
+++ b/Modules/GameState.cs
@@ -32,6 +32,7 @@ public class PlayerState(byte playerId)
Execution,
Disconnected,
Fall,
+ AFK,
// EHR
Gambled,
diff --git a/Modules/GhostRolesManager.cs b/Modules/GhostRolesManager.cs
index 5236bf1e3..3eaad0886 100644
--- a/Modules/GhostRolesManager.cs
+++ b/Modules/GhostRolesManager.cs
@@ -31,15 +31,16 @@ namespace EHR.Modules
var suitableRole = GetSuitableGhostRole(pc);
Logger.Warn($"Assigning Ghost Role: {pc.GetNameWithRole()} => {suitableRole}", "GhostRolesManager");
- pc.RpcSetCustomRole(suitableRole);
- pc.RpcSetRole(RoleTypes.GuardianAngel);
IGhostRole instance = CreateGhostRoleInstance(suitableRole);
+ pc.RpcSetCustomRole(suitableRole);
+ if (instance.ChangeToGA) pc.RpcSetRole(RoleTypes.GuardianAngel);
instance.OnAssign(pc);
+ Main.ResetCamPlayerList.Add(pc.PlayerId);
AssignedGhostRoles[pc.PlayerId] = (suitableRole, instance);
if (suitableRole == CustomRoles.Haunter) GhostRoles.Remove(suitableRole);
- NotifyAboutGhostRole(pc);
+ NotifyAboutGhostRole(pc, true);
}
public static void SpecificAssignGhostRole(byte id, CustomRoles role, bool set)
@@ -47,18 +48,23 @@ namespace EHR.Modules
if (AssignedGhostRoles.Any(x => x.Key == id || x.Value.Role == role)) return;
var pc = Utils.GetPlayerById(id);
- if (set) pc.RpcSetRole(RoleTypes.GuardianAngel);
IGhostRole instance = CreateGhostRoleInstance(role);
+ if (set && instance.ChangeToGA) pc.RpcSetRole(RoleTypes.GuardianAngel);
instance.OnAssign(pc);
+ Main.ResetCamPlayerList.Add(pc.PlayerId);
AssignedGhostRoles[id] = (role, instance);
}
- public static void NotifyAboutGhostRole(PlayerControl pc)
+ public static void NotifyAboutGhostRole(PlayerControl pc, bool first = false)
{
if (!AssignedGhostRoles.TryGetValue(pc.PlayerId, out var ghostRole)) return;
+ if (!first && pc.IsModClient()) return;
+
CustomRoles role = ghostRole.Role;
- pc.Notify($"{Translator.GetString("GotGhostRoleNotify")}\n{GetMessage(Translator.GetString($"{role}InfoLong").Split("\n")[1..].Join(delimiter: "\n"))}", 300f);
+ var text = $"{Translator.GetString("GotGhostRoleNotify")}\n{GetMessage(Translator.GetString($"{role}InfoLong").Split("\n")[1..].Join(delimiter: "\n"))}";
+ Utils.SendMessage(title: text, sendTo: pc.PlayerId, text: "\n");
+ pc.Notify(text, 5 * text.Count(x => x == '\n'));
return;
string GetMessage(string baseMessage)
diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs
index 76ca54da4..e7a37dfec 100644
--- a/Modules/GuessManager.cs
+++ b/Modules/GuessManager.cs
@@ -578,8 +578,6 @@ public static class GuessManager
return true;
}
- private static TextMeshPro NameText(this PlayerControl p) => p.cosmetics.nameText;
-
public static void RpcGuesserMurderPlayer(this PlayerControl pc /*, float delay = 0f*/)
{
// DEATH STUFF //
@@ -597,7 +595,7 @@ public static class GuessManager
if (amOwner)
{
hudManager.ShadowQuad.gameObject.SetActive(false);
- pc.NameText().GetComponent().material.SetInt(Mask, 0);
+ pc.cosmetics.nameText.GetComponent().material.SetInt(Mask, 0);
pc.RpcSetScanner(false);
ImportantTextTask importantTextTask = new GameObject("_Player").AddComponent();
importantTextTask.transform.SetParent(AmongUsClient.Instance.transform, false);
@@ -642,7 +640,7 @@ public static class GuessManager
if (amOwner)
{
hudManager.ShadowQuad.gameObject.SetActive(false);
- pc.NameText().GetComponent().material.SetInt(Mask, 0);
+ pc.cosmetics.nameText.GetComponent().material.SetInt(Mask, 0);
pc.RpcSetScanner(false);
ImportantTextTask importantTextTask = new GameObject("_Player").AddComponent();
importantTextTask.transform.SetParent(AmongUsClient.Instance.transform, false);
@@ -733,6 +731,28 @@ public static class GuessManager
}
}
+ private static void CreateIDLabels(MeetingHud __instance)
+ {
+ foreach (var pva in __instance.playerStates)
+ {
+ var levelDisplay = pva.transform.FindChild("PlayerLevel").gameObject;
+ var panel = Object.Instantiate(levelDisplay, pva.transform, true);
+ var panelTransform = panel.transform;
+ var background = panel.GetComponent();
+ background.color = Palette.Purple;
+ background.sortingOrder = 99;
+ panelTransform.SetAsFirstSibling();
+ panelTransform.localPosition = new(-1.2f, -0.6f, 0f);
+ var levelLabel = panelTransform.FindChild("LevelLabel").GetComponents()[0];
+ levelLabel.DestroyTranslator();
+ levelLabel.text = "ID";
+ levelLabel.sortingOrder = 100;
+ var levelNumber = panelTransform.FindChild("LevelNumber").GetComponent();
+ levelNumber.text = pva.TargetPlayerId.ToString();
+ levelNumber.sortingOrder = 100;
+ }
+ }
+
static void GuesserSelectRole(CustomRoleTypes Role, bool SetPage = true)
{
CurrentTeamType = Role;
@@ -1154,6 +1174,8 @@ public static class GuessManager
if (alive && lp.Is(CustomRoles.Guesser))
CreateGuesserButton(__instance);
}
+
+ CreateIDLabels(__instance);
}
}
diff --git a/Modules/RPC.cs b/Modules/RPC.cs
index 2e335b5ae..0e4ee3704 100644
--- a/Modules/RPC.cs
+++ b/Modules/RPC.cs
@@ -270,6 +270,12 @@ internal class RPCHandlerPatch
Version version = Version.Parse(reader.ReadString());
string tag = reader.ReadString();
string forkId = reader.ReadString();
+
+ if (!Main.PlayerVersion.ContainsKey(__instance.PlayerId))
+ {
+ RPC.RpcVersionCheck();
+ }
+
Main.PlayerVersion[__instance.PlayerId] = new(version, tag, forkId);
if (Main.VersionCheat.Value && __instance.IsHost()) RPC.RpcVersionCheck();
@@ -278,7 +284,7 @@ internal class RPCHandlerPatch
Main.PlayerVersion[__instance.PlayerId] = Main.PlayerVersion[0];
// Kick Unmached Player Start
- if (AmongUsClient.Instance.AmHost && tag != $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})" && forkId != Main.ForkId)
+ if (AmongUsClient.Instance.AmHost && tag != $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})" && forkId != Main.ForkId && !Main.VersionCheat.Value)
{
LateTask.New(() =>
{
diff --git a/Modules/Utils.cs b/Modules/Utils.cs
index e6c04b25d..141217167 100644
--- a/Modules/Utils.cs
+++ b/Modules/Utils.cs
@@ -317,7 +317,7 @@ public static class Utils
{
try
{
- var data = new Il2CppSystem.Collections.Generic.Dictionary>>();
+ var data = new Il2CppSystem.Collections.Generic.Dictionary>>();
var dict = path.Contains("Always") ? Main.AlwaysSpawnTogetherCombos : Main.NeverSpawnTogetherCombos;
dict.Do(kvp =>
{
@@ -326,7 +326,7 @@ public static class Utils
{
var key = pair.Key.ToString();
data[kvp.Key][key] = new();
- pair.Value.Do(x => data[kvp.Key][key].Add((int)x));
+ pair.Value.Do(x => data[kvp.Key][key].Add(x.ToString()));
});
});
File.WriteAllText(path, JsonConvert.SerializeObject(data, Formatting.Indented));
@@ -350,7 +350,7 @@ public static class Utils
try
{
if (!File.Exists(path)) return;
- var data = JsonConvert.DeserializeObject>>>(File.ReadAllText(path));
+ var data = JsonConvert.DeserializeObject>>>(File.ReadAllText(path));
var dict = path.Contains("Always") ? Main.AlwaysSpawnTogetherCombos : Main.NeverSpawnTogetherCombos;
dict.Clear();
foreach (var kvp in data)
@@ -362,7 +362,7 @@ public static class Utils
dict[kvp.Key][key] = [];
foreach (var n in pair.Value)
{
- dict[kvp.Key][key].Add((CustomRoles)n);
+ dict[kvp.Key][key].Add(Enum.Parse(n));
}
}
}
@@ -733,6 +733,7 @@ public static class Utils
case CustomRoles.Gaulois when !Options.UsePets.GetBool() || !Gaulois.UsePet.GetBool():
case CustomRoles.Analyst when !Options.UsePets.GetBool() || !Analyst.UsePet.GetBool():
case CustomRoles.Witness when !Options.UsePets.GetBool() || !Options.WitnessUsePet.GetBool():
+ case CustomRoles.Goose:
case CustomRoles.Pursuer:
case CustomRoles.Spiritcaller:
case CustomRoles.PlagueBearer:
@@ -1905,6 +1906,8 @@ public static class Utils
return add;
}
+ public static string ColoredPlayerName(this byte id) => ColorString(Main.PlayerColors.GetValueOrDefault(id, Color.white), Main.AllPlayerNames.GetValueOrDefault(id, $"Someone (ID {id})"));
+
public static PlayerControl GetPlayerById(int PlayerId, bool fast = true)
{
if (PlayerId is > byte.MaxValue or < byte.MinValue) return null;
@@ -1919,7 +1922,7 @@ public static class Utils
//if (Options.DeepLowLoad.GetBool()) await Task.Run(() => { DoNotifyRoles(isForMeeting, SpecifySeer, NoCache, ForceLoop, CamouflageIsForMeeting, GuesserIsForMeeting); });
/*else */
- if (!SetUpRoleTextPatch.IsInIntro && ((SpecifySeer != null && SpecifySeer.IsModClient()) || !AmongUsClient.Instance.AmHost || Main.AllPlayerControls == null || GameStates.IsMeeting || GameStates.IsLobby)) return;
+ if (!SetUpRoleTextPatch.IsInIntro && ((SpecifySeer != null && SpecifySeer.IsModClient()) || !AmongUsClient.Instance.AmHost || Main.AllPlayerControls == null || (GameStates.IsMeeting && !isForMeeting) || GameStates.IsLobby)) return;
await DoNotifyRoles(isForMeeting, SpecifySeer, SpecifyTarget, NoCache, ForceLoop, CamouflageIsForMeeting, GuesserIsForMeeting, MushroomMixup);
}
@@ -2423,6 +2426,8 @@ public static class Utils
if (MeetingStates.FirstMeeting && Main.FirstDied != string.Empty && Main.FirstDied == target.FriendCode && Main.ShieldPlayer != string.Empty && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.SoloKombat or CustomGameMode.FFA)
TargetSuffix.Append(GetString("DiedR1Warning"));
+
+ TargetSuffix.Append(AFKDetector.GetSuffix(seer, target));
}
string TargetDeathReason = string.Empty;
@@ -2548,8 +2553,8 @@ public static class Utils
}
Lovers.IsChatActivated = false;
-
Main.ProcessShapeshifts = true;
+ AFKDetector.NumAFK = 0;
foreach (var pc in Main.AllPlayerControls)
{
@@ -2571,11 +2576,14 @@ public static class Utils
if (Options.UsePets.GetBool()) pc.AddAbilityCD(includeDuration: false);
+ AFKDetector.RecordPosition(pc);
+
Main.PlayerStates[pc.PlayerId].Role.AfterMeetingTasks();
}
else
{
- if (pc.IsCrewmate() && !pc.GetTaskState().IsTaskFinished)
+ TaskState taskState = pc.GetTaskState();
+ if (pc.IsCrewmate() && !taskState.IsTaskFinished && taskState.hasTasks)
pc.Notify(GetString("DoYourTasksPlease"), 10f);
GhostRolesManager.NotifyAboutGhostRole(pc);
diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs
index 2c1dfd61f..0742f55c4 100644
--- a/Patches/ChatCommandPatch.cs
+++ b/Patches/ChatCommandPatch.cs
@@ -778,6 +778,13 @@ internal class ChatCommands
break;
+ case "/afkexempt":
+ canceled = true;
+ if (args.Length < 2 || !byte.TryParse(args[1], out var afkId)) break;
+ AFKDetector.ExemptedPlayers.Add(afkId);
+ Utils.SendMessage("\n", localPlayerId, string.Format(GetString("PlayerExemptedFromAFK"), afkId.ColoredPlayerName()));
+ break;
+
case "/m":
case "/myrole":
canceled = true;
@@ -806,7 +813,7 @@ internal class ChatCommands
sb.Replace(subRole.ToString().ToLower(), subRole.ToColoredString());
}
- Utils.SendMessage("\n", localPlayerId, settings.ToString());
+ if (settings.Length > 0) Utils.SendMessage("\n", localPlayerId, settings.ToString());
Utils.SendMessage(sb.Append("").ToString(), localPlayerId, titleSb.ToString());
}
else Utils.SendMessage((lp.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + GetString("Message.CanNotUseInLobby"), localPlayerId);
@@ -1440,7 +1447,7 @@ internal class ChatCommands
sb.Clear().Append(txt);
if (rl.PetActivatedAbility()) sb.Append($"{GetString("SupportsPetMessage")}");
- Utils.SendMessage(text: "\n", sendTo: playerId, title: settings.ToString());
+ if (settings.Length > 0) Utils.SendMessage(text: "\n", sendTo: playerId, title: settings.ToString());
Utils.SendMessage(text: sb.ToString(), sendTo: playerId, title: title);
return;
@@ -1557,7 +1564,7 @@ internal class ChatCommands
sb.Replace(subRole.ToString().ToLower(), subRole.ToColoredString());
}
- Utils.SendMessage("\n", player.PlayerId, settings.ToString());
+ if (settings.Length > 0) Utils.SendMessage("\n", player.PlayerId, settings.ToString());
Utils.SendMessage(sb.Append("").ToString(), player.PlayerId, titleSb.ToString());
}
else Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId);
diff --git a/Patches/CreateGameOptionsPatches.cs b/Patches/CreateGameOptionsPatches.cs
deleted file mode 100644
index 29c719773..000000000
--- a/Patches/CreateGameOptionsPatches.cs
+++ /dev/null
@@ -1,153 +0,0 @@
-using System;
-using AmongUs.GameOptions;
-using HarmonyLib;
-using TMPro;
-using UnityEngine;
-
-// All errors in this file are fake, the build succeeds
-// https://github.com/CrowdedMods/CrowdedMod/blob/master/src/CrowdedMod/Patches/CreateGameOptionsPatches.cs
-namespace EHR.Patches
-{
- internal static class CreateGameOptionsPatches
- {
- [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.Awake))]
- public static class CreateOptionsPickerAwakePatch
- {
- 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());
- }));
-
- 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,
- 127 - 14 + byte.Parse(playerButton.name));
- tmp.text = newValue.ToString();
- }
-
- __instance.UpdateMaxPlayersButtons(__instance.GetTargetOptions());
- }));
-
- 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;
-
- 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 CreateOptionsPickerUpdateMaxPlayersButtonsPatch
- {
- public static bool Prefix(CreateOptionsPicker __instance, [HarmonyArgument(0)] IGameOptions opts)
- {
- 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 CreateOptionsPickerUpdateImpostorsButtonsPatch
- {
- public static bool Prefix()
- {
- return false;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Patches/Crowded.cs b/Patches/Crowded.cs
new file mode 100644
index 000000000..546e24633
--- /dev/null
+++ b/Patches/Crowded.cs
@@ -0,0 +1,187 @@
+using System;
+using AmongUs.GameOptions;
+using HarmonyLib;
+using TMPro;
+using UnityEngine;
+
+// All errors in this file are fake, the build succeeds
+// https://github.com/CrowdedMods/CrowdedMod/blob/master/src/CrowdedMod/Patches/CreateGameOptionsPatches.cs
+namespace EHR.Patches
+{
+ internal static class Crowded
+ {
+ public const int MaxPlayers = 127;
+ public const int MaxImpostors = 127 / 2;
+
+ [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.Awake))]
+ public static class CreateOptionsPickerAwake
+ {
+ 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());
+ }));
+ Object.Destroy(firstButtonRenderer);
+
+ 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,
+ MaxPlayers - 14 + byte.Parse(playerButton.name));
+ tmp.text = newValue.ToString();
+ }
+
+ __instance.UpdateMaxPlayersButtons(__instance.GetTargetOptions());
+ }));
+ Object.Destroy(lastButtonRenderer);
+
+ 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(CreateOptionsPicker), nameof(CreateOptionsPicker.UpdateMaxPlayersButtons))]
+ public static class CreateOptionsPickerUpdateMaxPlayersButtons
+ {
+ public static bool Prefix(CreateOptionsPicker __instance, [HarmonyArgument(0)] IGameOptions opts)
+ {
+ 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 CreateOptionsPickerUpdateImpostorsButtons
+ {
+ public static bool Prefix()
+ {
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(GameOptionsData), nameof(GameOptionsData.AreInvalid))]
+ public static class InvalidOptionsPatches
+ {
+ 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
+ {
+ public static void Postfix(ref SecurityLogger __instance)
+ {
+ __instance.Timers = new float[127];
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs
index 8a7ae2741..e6272d6b2 100644
--- a/Patches/GameOptionsMenuPatch.cs
+++ b/Patches/GameOptionsMenuPatch.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using BepInEx.Unity.IL2CPP.Utils.Collections;
+using EHR.Patches;
using HarmonyLib;
using Il2CppSystem.Collections.Generic;
using TMPro;
@@ -300,52 +301,58 @@ public static class GameOptionsMenuPatch
private static BaseGameSetting GetSetting(OptionItem item)
{
- // ReSharper disable Unity.IncorrectScriptableObjectInstantiation
- BaseGameSetting baseGameSetting = item switch
+ BaseGameSetting baseGameSetting;
+ switch (item)
{
- BooleanOptionItem => new CheckboxGameSetting
- {
- Type = OptionTypes.Checkbox
- },
- IntegerOptionItem integerOptionItem => new IntGameSetting
- {
- Type = OptionTypes.Int,
- Value = integerOptionItem.GetInt(),
- Increment = integerOptionItem.Rule.Step,
- ValidRange = new(integerOptionItem.Rule.MinValue, integerOptionItem.Rule.MaxValue),
- ZeroIsInfinity = false,
- SuffixType = NumberSuffixes.Multiplier,
- FormatString = string.Empty
- },
- FloatOptionItem floatOptionItem => new FloatGameSetting
- {
- Type = OptionTypes.Float,
- Value = floatOptionItem.GetFloat(),
- Increment = floatOptionItem.Rule.Step,
- ValidRange = new(floatOptionItem.Rule.MinValue, floatOptionItem.Rule.MaxValue),
- ZeroIsInfinity = false,
- SuffixType = NumberSuffixes.Multiplier,
- FormatString = string.Empty
- },
- StringOptionItem stringOptionItem => new StringGameSetting
- {
- Type = OptionTypes.String,
- Values = new StringNames[stringOptionItem.Selections.Count],
- Index = stringOptionItem.GetInt()
- },
- PresetOptionItem presetOptionItem => new IntGameSetting
- {
- Type = OptionTypes.Int,
- Value = presetOptionItem.GetInt(),
- Increment = presetOptionItem.Rule.Step,
- ValidRange = new(presetOptionItem.Rule.MinValue, presetOptionItem.Rule.MaxValue),
- ZeroIsInfinity = false,
- SuffixType = NumberSuffixes.Multiplier,
- FormatString = string.Empty
- },
- _ => null
- };
- // ReSharper restore Unity.IncorrectScriptableObjectInstantiation
+ case BooleanOptionItem:
+ var checkboxGameSetting = ScriptableObject.CreateInstance();
+ checkboxGameSetting.Type = OptionTypes.Checkbox;
+ baseGameSetting = checkboxGameSetting;
+ break;
+ case IntegerOptionItem integerOptionItem:
+ var intGameSetting = ScriptableObject.CreateInstance();
+ intGameSetting.Type = OptionTypes.Int;
+ intGameSetting.Value = integerOptionItem.GetInt();
+ intGameSetting.Increment = integerOptionItem.Rule.Step;
+ intGameSetting.ValidRange = new(integerOptionItem.Rule.MinValue, integerOptionItem.Rule.MaxValue);
+ intGameSetting.ZeroIsInfinity = false;
+ intGameSetting.SuffixType = NumberSuffixes.Multiplier;
+ intGameSetting.FormatString = string.Empty;
+ baseGameSetting = intGameSetting;
+ break;
+ case FloatOptionItem floatOptionItem:
+ var floatGameSetting = ScriptableObject.CreateInstance();
+ floatGameSetting.Type = OptionTypes.Float;
+ floatGameSetting.Value = floatOptionItem.GetFloat();
+ floatGameSetting.Increment = floatOptionItem.Rule.Step;
+ floatGameSetting.ValidRange = new(floatOptionItem.Rule.MinValue, floatOptionItem.Rule.MaxValue);
+ floatGameSetting.ZeroIsInfinity = false;
+ floatGameSetting.SuffixType = NumberSuffixes.Multiplier;
+ floatGameSetting.FormatString = string.Empty;
+ baseGameSetting = floatGameSetting;
+ break;
+ case StringOptionItem stringOptionItem:
+ var stringGameSetting = ScriptableObject.CreateInstance();
+ stringGameSetting.Type = OptionTypes.String;
+ stringGameSetting.Values = new StringNames[stringOptionItem.Selections.Count];
+ stringGameSetting.Index = stringOptionItem.GetInt();
+ baseGameSetting = stringGameSetting;
+ break;
+ case PresetOptionItem presetOptionItem:
+ var presetIntGameSetting = ScriptableObject.CreateInstance();
+ presetIntGameSetting.Type = OptionTypes.Int;
+ presetIntGameSetting.Value = presetOptionItem.GetInt();
+ presetIntGameSetting.Increment = presetOptionItem.Rule.Step;
+ presetIntGameSetting.ValidRange = new(presetOptionItem.Rule.MinValue, presetOptionItem.Rule.MaxValue);
+ presetIntGameSetting.ZeroIsInfinity = false;
+ presetIntGameSetting.SuffixType = NumberSuffixes.Multiplier;
+ presetIntGameSetting.FormatString = string.Empty;
+ baseGameSetting = presetIntGameSetting;
+ break;
+ default:
+ baseGameSetting = null;
+ break;
+ }
if (baseGameSetting != null)
{
@@ -426,8 +433,10 @@ public static class NumberOptionPatch
__instance.Increment = 0.05f;
__instance.Value = (float)Math.Round(__instance.Value, 2);
break;
- case StringNames.GameNumImpostors when DebugModeManager.IsDebugMode:
- __instance.ValidRange.min = 0;
+ case StringNames.GameNumImpostors:
+ __instance.ValidRange = new(1, Crowded.MaxImpostors);
+ __instance.Value = (float)Math.Round(__instance.Value, 2);
+ if (DebugModeManager.AmDebugger) __instance.ValidRange.min = 0;
break;
}
diff --git a/Patches/GameStartManagerPatch.cs b/Patches/GameStartManagerPatch.cs
index e069e2ede..e5424a693 100644
--- a/Patches/GameStartManagerPatch.cs
+++ b/Patches/GameStartManagerPatch.cs
@@ -136,7 +136,7 @@ public class GameStartManagerPatch
}
}
- if (AmongUsClient.Instance == null || GameData.Instance == null || !AmongUsClient.Instance.AmHost || !GameData.Instance) return false;
+ if (AmongUsClient.Instance == null || GameData.Instance == null || !AmongUsClient.Instance.AmHost || !GameData.Instance) return true;
if (Main.AutoStart != null && Main.AutoStart.Value)
{
diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs
index 6c69a11a8..7acb6bbc8 100644
--- a/Patches/IntroPatch.cs
+++ b/Patches/IntroPatch.cs
@@ -418,24 +418,28 @@ class BeginCrewmatePatch
CustomRoles.Disperser
=> ShipStatus.Instance.VentMoveSounds.FirstOrDefault(),
- _ => PlayerControl.LocalPlayer.GetCustomRoleTypes() switch
- {
- CustomRoleTypes.Impostor => GetIntroSound(RoleTypes.Impostor),
- CustomRoleTypes.Crewmate => GetIntroSound(RoleTypes.Crewmate),
- CustomRoleTypes.Neutral => GetIntroSound(RoleTypes.Shapeshifter),
- _ => GetIntroSound(RoleTypes.Crewmate)
- }
+ CustomRoles.Tracker
+ or CustomRoles.EvilTracker
+ => GetIntroSound(RoleTypes.Tracker),
+
+ CustomRoles.Noisemaker
+ or CustomRoles.NoisemakerEHR
+ => GetIntroSound(RoleTypes.Noisemaker),
+
+ CustomRoles.Phantom
+ or CustomRoles.PhantomEHR
+ => GetIntroSound(RoleTypes.Phantom),
+
+ CustomRoles.Shapeshifter
+ or CustomRoles.ShapeshifterEHR
+ => GetIntroSound(RoleTypes.Shapeshifter),
+
+ _ => GetAudioClipFromCustomRoleType()
};
}
catch (Exception ex)
{
- PlayerControl.LocalPlayer.Data.Role.IntroSound = PlayerControl.LocalPlayer.GetCustomRoleTypes() switch
- {
- CustomRoleTypes.Impostor => GetIntroSound(RoleTypes.Impostor),
- CustomRoleTypes.Crewmate => GetIntroSound(RoleTypes.Crewmate),
- CustomRoleTypes.Neutral => GetIntroSound(RoleTypes.Shapeshifter),
- _ => GetIntroSound(RoleTypes.Crewmate)
- };
+ PlayerControl.LocalPlayer.Data.Role.IntroSound = GetAudioClipFromCustomRoleType();
Logger.Warn($"Could not set intro sound\n{ex}", "IntroSound");
}
@@ -555,6 +559,17 @@ class BeginCrewmatePatch
__instance.TeamTitle.color = Color.magenta;
StartFadeIntro(__instance, Color.magenta, Color.magenta);
}
+
+ AudioClip GetAudioClipFromCustomRoleType()
+ {
+ return PlayerControl.LocalPlayer.GetCustomRoleTypes() switch
+ {
+ CustomRoleTypes.Impostor => GetIntroSound(RoleTypes.Impostor),
+ CustomRoleTypes.Crewmate => GetIntroSound(RoleTypes.Crewmate),
+ CustomRoleTypes.Neutral => GetIntroSound(RoleTypes.Shapeshifter),
+ _ => GetIntroSound(RoleTypes.Crewmate)
+ };
+ }
}
public static AudioClip GetIntroSound(RoleTypes roleType)
@@ -743,7 +758,7 @@ class IntroCutsceneDestroyPatch
5 => new RandomSpawn.FungleSpawnMap(),
_ => null
};
- if (map != null) Main.AllAlivePlayerControls.Do(map.RandomTeleport);
+ if (map != null && AmongUsClient.Instance.AmHost) Main.AllAlivePlayerControls.Do(map.RandomTeleport);
}
if (Main.ResetCamPlayerList.Contains(PlayerControl.LocalPlayer.PlayerId))
diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs
index c5b4df97d..70951d24e 100644
--- a/Patches/MeetingHudPatch.cs
+++ b/Patches/MeetingHudPatch.cs
@@ -892,34 +892,8 @@ class MeetingHudStartPatch
// Name Color Manager
pva.NameText.text = pva.NameText.text.ApplyNameColorData(seer, target, true);
-
var seerRole = seer.GetCustomRole();
- // Guesser Mode //
- if (((Options.GuesserMode.GetBool() && !seer.Data.IsDead && !target.Data.IsDead &&
- ((Options.CrewmatesCanGuess.GetBool() && seer.IsCrewmate() && !seer.Is(CustomRoles.Judge) && !seer.Is(CustomRoles.NiceSwapper) && !seer.Is(CustomRoles.Lookout) && !seer.Is(CustomRoles.ParityCop)) ||
- (Options.ImpostorsCanGuess.GetBool() && seerRole.IsImpostor() && !seer.Is(CustomRoles.Councillor)) ||
- (Options.NeutralKillersCanGuess.GetBool() && seer.IsNeutralKiller()) ||
- (Options.PassiveNeutralsCanGuess.GetBool() && seerRole.IsNonNK() && !seer.Is(CustomRoles.Doomsayer)))) ||
-
- // Other Roles and Add-ons that can see player IDs //
- (seer.Data.IsDead && !target.Data.IsDead && seerRole is CustomRoles.Mafia) ||
- (!seer.Data.IsDead && !target.Data.IsDead &&
- (seer.Is(CustomRoles.Guesser) ||
- seerRole is
- CustomRoles.Judge or
- CustomRoles.NiceSwapper or
- CustomRoles.Lookout or
- CustomRoles.ParityCop or
- CustomRoles.Councillor or
- CustomRoles.Doomsayer or
- CustomRoles.EvilGuesser or
- CustomRoles.NiceGuesser
- ))))
- {
- pva.NameText.text = $"{Utils.ColorString(Utils.GetRoleColor(seerRole), target.PlayerId.ToString())} {pva.NameText.text}";
- }
-
if (seer.KnowDeathReason(target))
sb.Append($"({Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doctor), Utils.GetVitalText(target.PlayerId))})");
diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs
index 5da8e3057..1c36552e7 100644
--- a/Patches/OnGameStartedPatch.cs
+++ b/Patches/OnGameStartedPatch.cs
@@ -209,8 +209,9 @@ internal class ChangeRoleSettings
Provocateur.Provoked = [];
Crusader.ForCrusade = [];
Godfather.GodfatherTarget = byte.MaxValue;
- ChatManager.ResetHistory();
- CustomNetObject.Reset();
+ Crewpostor.TasksDone = [];
+ Express.SpeedNormal = [];
+ Express.SpeedUp = [];
ReportDeadBodyPatch.CanReport = [];
SabotageMapPatch.TimerTexts = [];
@@ -225,6 +226,8 @@ internal class ChangeRoleSettings
RandomSpawn.CustomNetworkTransformPatch.NumOfTP = [];
+ AFKDetector.ShieldedPlayers.Clear();
+
MeetingTimeManager.Init();
Main.DefaultCrewmateVision = Main.RealOptionsData.GetFloat(FloatOptionNames.CrewLightMod);
Main.DefaultImpostorVision = Main.RealOptionsData.GetFloat(FloatOptionNames.ImpostorLightMod);
@@ -305,10 +308,6 @@ internal class ChangeRoleSettings
Logger.Exception(ex, "Init Roles");
}
- Crewpostor.TasksDone = [];
- Express.SpeedNormal = [];
- Express.SpeedUp = [];
-
Main.ChangedRole = false;
try
@@ -332,6 +331,8 @@ internal class ChangeRoleSettings
DoorsReset.Initialize();
GhostRolesManager.Initialize();
RoleBlockManager.Reset();
+ ChatManager.ResetHistory();
+ CustomNetObject.Reset();
IRandom.SetInstanceById(Options.RoleAssigningAlgorithm.GetValue());
@@ -406,18 +407,26 @@ internal class SelectRolesPatch
}
- var rd = IRandom.Instance;
- BasisChangingAddons.Remove(CustomRoles.Bloodlust);
- bool bloodlustSpawn = rd.Next(1, 100) <= (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Bloodlust, out var option3) ? option3.GetFloat() : 0) && CustomRoles.Bloodlust.IsEnable();
- HashSet bloodlustList = RoleResult.Where(x => x.Value.IsCrewmate() && !x.Value.IsTaskBasedCrewmate()).Select(x => x.Key.PlayerId).ToHashSet();
- if (bloodlustList.Count == 0) bloodlustSpawn = false;
- if (Main.SetAddOns.Values.Any(x => x.Contains(CustomRoles.Bloodlust)))
- {
- bloodlustSpawn = true;
- bloodlustList = Main.SetAddOns.Where(x => x.Value.Contains(CustomRoles.Bloodlust)).Select(x => x.Key).ToHashSet();
- }
+ BasisChangingAddons.Clear();
- if (bloodlustSpawn) BasisChangingAddons[CustomRoles.Bloodlust] = bloodlustList.Shuffle().Take(CustomRoles.Bloodlust.GetCount()).ToList();
+ try
+ {
+ var rd = IRandom.Instance;
+ bool bloodlustSpawn = rd.Next(1, 100) <= (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Bloodlust, out var option3) ? option3.GetFloat() : 0) && CustomRoles.Bloodlust.IsEnable();
+ HashSet bloodlustList = RoleResult.Where(x => x.Value.IsCrewmate() && !x.Value.IsTaskBasedCrewmate() && (Options.UsePets.GetBool() || x.Value.GetRoleTypes() != RoleTypes.Impostor)).Select(x => x.Key.PlayerId).ToHashSet();
+ if (bloodlustList.Count == 0) bloodlustSpawn = false;
+ if (Main.SetAddOns.Values.Any(x => x.Contains(CustomRoles.Bloodlust)))
+ {
+ bloodlustSpawn = true;
+ bloodlustList = Main.SetAddOns.Where(x => x.Value.Contains(CustomRoles.Bloodlust)).Select(x => x.Key).ToHashSet();
+ }
+
+ if (bloodlustSpawn) BasisChangingAddons[CustomRoles.Bloodlust] = bloodlustList.Shuffle().Take(CustomRoles.Bloodlust.GetCount()).ToList();
+ }
+ catch (Exception e)
+ {
+ Utils.ThrowException(e);
+ }
Dictionary<(byte, byte), RoleTypes> rolesMap = [];
@@ -450,8 +459,6 @@ internal class SelectRolesPatch
{
var rd = IRandom.Instance;
- new[] { CustomRoles.Nimble, CustomRoles.Physicist, CustomRoles.Finder, CustomRoles.Noisy }.Do(x => BasisChangingAddons.Remove(x));
-
bool physicistSpawn = rd.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Physicist, out var option1) ? option1.GetFloat() : 0) && CustomRoles.Physicist.IsEnable();
bool nimbleSpawn = rd.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Nimble, out var option2) ? option2.GetFloat() : 0) && CustomRoles.Nimble.IsEnable();
bool finderSpawn = rd.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Finder, out var option3) ? option3.GetFloat() : 0) && CustomRoles.Finder.IsEnable();
@@ -664,19 +671,19 @@ internal class SelectRolesPatch
var aapc = Main.AllAlivePlayerControls;
var addonNum = aapc.ToDictionary(x => x, _ => 0);
AddonRolesList
+ .Except(BasisChangingAddons.Keys)
.Where(x => x.IsEnable())
.SelectMany(x => Enumerable.Repeat(x, Math.Clamp(x.GetCount(), 0, aapc.Length)))
.Where(x => IRandom.Instance.Next(1, 100) <= (Options.CustomAdtRoleSpawnRate.TryGetValue(x, out var sc) ? sc.GetFloat() : 0))
.Shuffle()
- .Chunk(aapc.Length)
- .SelectMany(a => a.Select(x =>
+ .Select(x =>
{
var suitablePlayer = aapc
.OrderBy(p => addonNum[p])
.FirstOrDefault(p => CustomRolesHelper.CheckAddonConflict(x, p));
if (suitablePlayer != null) addonNum[suitablePlayer]++;
return (Role: x, SuitablePlayer: suitablePlayer);
- }))
+ })
.DoIf(x => x.SuitablePlayer != null, x => Main.PlayerStates[x.SuitablePlayer.PlayerId].SetSubRole(x.Role));
@@ -863,12 +870,25 @@ internal class SelectRolesPatch
{
foreach (PlayerControl seer in Main.AllPlayerControls)
{
- var sender = senders[seer.PlayerId];
foreach (PlayerControl target in Main.AllPlayerControls)
{
- if (rolesMap.TryGetValue((seer.PlayerId, target.PlayerId), out var role))
+ try
+ {
+ if (rolesMap.TryGetValue((seer.PlayerId, target.PlayerId), out var role))
+ {
+ // Change Scientist to Noisemaker when the role is desync and target have the Noisemaker role << Thanks: TommyXL
+ if (role is RoleTypes.Scientist && RoleResult.Any(x => x.Key.PlayerId == seer.PlayerId && x.Value is CustomRoles.NoisemakerEHR or CustomRoles.Noisemaker))
+ {
+ Logger.Info($"seer: {seer.PlayerId}, target: {target.PlayerId}, {role} => {RoleTypes.Noisemaker}", "OverrideRoleForDesync");
+ role = RoleTypes.Noisemaker;
+ }
+
+ var sender = senders[seer.PlayerId];
+ sender.RpcSetRole(seer, role, target.GetClientId());
+ }
+ }
+ catch
{
- sender.RpcSetRole(seer, role, target.GetClientId());
}
}
}
diff --git a/Patches/OutroPatch.cs b/Patches/OutroPatch.cs
index 1e3a2cf70..5f31dd520 100644
--- a/Patches/OutroPatch.cs
+++ b/Patches/OutroPatch.cs
@@ -48,7 +48,7 @@ class EndGamePatch
SummaryText[id] = Utils.SummaryTexts(id, disableColor: false);
if (state.SubRoles.Count == 0) continue;
- Main.LastAddOns[id] = $"{Utils.ColorString(Main.PlayerColors.GetValueOrDefault(id, Color.white), Main.AllPlayerNames.GetValueOrDefault(id, $"ID {id}"))}: {state.SubRoles.Join(x => x.ToColoredString())}";
+ Main.LastAddOns[id] = $"{id.ColoredPlayerName()}: {state.SubRoles.Join(x => x.ToColoredString())}";
}
if (Options.DumpLogAfterGameEnd.GetBool())
@@ -110,6 +110,7 @@ class EndGamePatch
CustomNetObject.Reset();
Main.LoversPlayers.Clear();
Bloodmoon.OnMeetingStart();
+ AFKDetector.ExemptedPlayers.Clear();
foreach (var state in Main.PlayerStates.Values)
{
diff --git a/Patches/PetActionsPatch.cs b/Patches/PetActionsPatch.cs
index f14c2572e..15986359b 100644
--- a/Patches/PetActionsPatch.cs
+++ b/Patches/PetActionsPatch.cs
@@ -75,7 +75,7 @@ class ExternalRpcPetPatch
Logger.Info($"Player {pc.GetNameWithRole().RemoveHtmlTags()} petted their pet", "PetActionTrigger");
- LateTask.New(() => { OnPetUse(pc); }, 0.2f, $"OnPetUse: {pc.GetNameWithRole().RemoveHtmlTags()}", false);
+ LateTask.New(() => OnPetUse(pc), 0.2f, $"OnPetUse: {pc.GetNameWithRole().RemoveHtmlTags()}", false);
}
public static void OnPetUse(PlayerControl pc)
@@ -109,7 +109,7 @@ class ExternalRpcPetPatch
if (target != null) hasKillTarget = true;
if (!pc.CanUseKillButton()) hasKillTarget = false;
- if (pc.GetCustomRole().UsesPetInsteadOfKill() && hasKillTarget && pc.Data.RoleType != RoleTypes.Impostor)
+ if (pc.GetCustomRole().UsesPetInsteadOfKill() && hasKillTarget && (pc.Data.RoleType != RoleTypes.Impostor || pc.GetCustomRole() is CustomRoles.Necromancer or CustomRoles.Deathknight))
{
pc.AddKCDAsAbilityCD();
if (Main.PlayerStates[pc.PlayerId].Role.OnCheckMurder(pc, target))
diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs
index 0b3da15d2..668125b76 100644
--- a/Patches/PlayerControlPatch.cs
+++ b/Patches/PlayerControlPatch.cs
@@ -150,7 +150,6 @@ class CheckMurderPatch
if (target.Data == null
|| target.inVent
- || target.walkingToVent
|| target.inMovingPlat
|| target.MyPhysics.Animations.IsPlayingEnterVentAnimation()
|| target.MyPhysics.Animations.IsPlayingAnyLadderAnimation()
@@ -312,6 +311,12 @@ class CheckMurderPatch
return false;
}
+ if (AFKDetector.ShieldedPlayers.Contains(target.PlayerId))
+ {
+ Notify("AFKShielded");
+ return false;
+ }
+
if ((killer.Is(CustomRoles.Jackal) && target.Is(CustomRoles.Sidekick) && !Options.JackalCanKillSidekick.GetBool()) ||
(killer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Jackal) && !Options.SidekickCanKillJackal.GetBool()) ||
(killer.Is(CustomRoles.Jackal) && target.Is(CustomRoles.Recruit) && !Options.JackalCanKillSidekick.GetBool()) ||
@@ -1040,7 +1045,7 @@ class ReportDeadBodyPatch
Camouflage.RpcSetSkin(pc, RevertToDefault: true);
}
- if (Main.CurrentMap == MapNames.Fungle && pc.IsMushroomMixupActive())
+ if (Main.CurrentMap == MapNames.Fungle && (pc.IsMushroomMixupActive() || IsActive(SystemTypes.MushroomMixupSabotage)))
{
pc.FixMixedUpOutfit();
}
@@ -1159,7 +1164,12 @@ class FixedUpdatePatch
NameNotifyManager.OnFixedUpdate(player);
TargetArrow.OnFixedUpdate(player);
LocateArrow.OnFixedUpdate(player);
- Camouflage.OnFixedUpdate(player);
+
+ if (AmongUsClient.Instance.AmHost)
+ {
+ Camouflage.OnFixedUpdate(player);
+ AFKDetector.OnFixedUpdate(player);
+ }
if (RPCHandlerPatch.ReportDeadBodyRPCs.Remove(playerId))
Logger.Info($"Cleared ReportDeadBodyRPC Count for {player.GetRealName().RemoveHtmlTags()}", "FixedUpdatePatch");
@@ -1428,6 +1438,8 @@ class FixedUpdatePatch
if (self) Suffix.Append(CustomTeamManager.GetSuffix(seer));
+ Suffix.Append(AFKDetector.GetSuffix(seer, target));
+
switch (target.GetCustomRole())
{
case CustomRoles.Snitch when seer.IsImpostor() && target.Is(CustomRoles.Madmate) && target.GetTaskState().IsTaskFinished:
@@ -2141,4 +2153,19 @@ class CmdCheckAppearPatch
return false;
}
+}
+
+[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.AssertWithTimeout))]
+class AssertWithTimeoutPatch
+{
+ public static bool Prefix() => false;
+}
+
+[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckName))]
+class CmdCheckNameVersionCheckPatch
+{
+ public static void Postfix(PlayerControl __instance)
+ {
+ RPC.RpcVersionCheck();
+ }
}
\ No newline at end of file
diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs
index 96ebf88f4..1bdb5c704 100644
--- a/Patches/PlayerJoinAndLeftPatch.cs
+++ b/Patches/PlayerJoinAndLeftPatch.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AmongUs.Data;
@@ -7,6 +8,7 @@ using EHR.Crewmate;
using EHR.Modules;
using EHR.Neutral;
using HarmonyLib;
+using Hazel;
using InnerNet;
using static EHR.Translator;
@@ -84,6 +86,33 @@ class OnPlayerJoinedPatch
public static void Postfix( /*AmongUsClient __instance,*/ [HarmonyArgument(0)] ClientData client)
{
Logger.Info($"{client.PlayerName} (ClientID: {client.Id} / FriendCode: {client.FriendCode}) joined the lobby", "Session");
+
+ LateTask.New(() =>
+ {
+ try
+ {
+ if (AmongUsClient.Instance.AmHost)
+ {
+ if (!client.Character.Data.Disconnected && client.Character.Data.IsIncomplete)
+ {
+ Logger.SendInGame(GetString("Error.InvalidColor") + $" {client.Id}/{client.PlayerName}");
+ AmongUsClient.Instance.KickPlayer(client.Id, false);
+ Logger.Info($"Kicked client {client.Id}/{client.PlayerName} because PlayerControl is not spawned in time.", "OnPlayerJoinedPatchPostfix");
+ return;
+ }
+
+ if (!Main.PlayerVersion.TryGetValue(client.Character.PlayerId, out _))
+ {
+ var retry = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestRetryVersionCheck, SendOption.None, client.Id);
+ AmongUsClient.Instance.FinishRpcImmediately(retry);
+ }
+ }
+ }
+ catch
+ {
+ }
+ }, 3f, "green bean kick late task", false);
+
if (AmongUsClient.Instance.AmHost && client.FriendCode == "" && Options.KickPlayerFriendCodeNotExist.GetBool() && !GameStates.IsLocalGame)
{
if (!BanManager.TempBanWhiteList.Contains(client.GetHashedPuid()))
@@ -101,7 +130,7 @@ class OnPlayerJoinedPatch
Logger.Info(msg, "Android Kick");
}
- if (AmongUsClient.Instance.AmHost && client.PlayerName.Contains("Silasticm", StringComparison.OrdinalIgnoreCase))
+ if (AmongUsClient.Instance.AmHost && (client.PlayerName.EndsWith("cm", StringComparison.OrdinalIgnoreCase) || client.PlayerName.EndsWith("sm", StringComparison.OrdinalIgnoreCase)) && (client.PlayerName.Length == 4 || client.PlayerName.Count(x => x is 'i' or 'I') >= 2))
{
AmongUsClient.Instance.KickPlayer(client.Id, false);
Logger.SendInGame("They were probably hacking tbh");
@@ -176,7 +205,7 @@ class OnPlayerLeftPatch
if (Main.HostClientId == __instance.ClientId)
{
- var clientId = -1;
+ const int clientId = -1;
var player = PlayerControl.LocalPlayer;
var title = "" + GetString("DefaultSystemMessageTitle") + "";
var name = player?.Data?.PlayerName;
@@ -238,6 +267,22 @@ class OnPlayerLeftPatch
Main.SayStartTimes.Remove(__instance.ClientId);
Main.SayBanwordsTimes.Remove(__instance.ClientId);
Main.PlayerVersion.Remove(data?.Character?.PlayerId ?? byte.MaxValue);
+ Main.MessagesToSend.RemoveAll(x => x.RECEIVER_ID == data?.Character.PlayerId);
+
+ if (data != null && data.Character != null)
+ {
+ var netid = data.Character.NetId;
+ LateTask.New(() =>
+ {
+ if (GameStates.IsOnlineGame)
+ {
+ MessageWriter messageWriter = AmongUsClient.Instance.Streams[1];
+ messageWriter.StartMessage(5);
+ messageWriter.WritePacked(netid);
+ messageWriter.EndMessage();
+ }
+ }, 2.5f, "Repeat Despawn", false);
+ }
}
Utils.CountAlivePlayers(true);
diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json
index 262234a42..d70951468 100644
--- a/Resources/Lang/en_US.json
+++ b/Resources/Lang/en_US.json
@@ -1143,6 +1143,7 @@
"MenuTitle.Guessers": "★ Guesser Mode ★",
"MenuTitle.GuesserModeRoles": "★ Roles and Add-ons for Guesser Mode ★",
"MenuTitle.CTA": "★ Custom Team Assignment ★",
+ "MenuTitle.AFKDetection": "★ AFK Detection ★",
"ShieldPersonDiedFirst": "Shield the person who got killed first in the last game",
"SidekickKnowOtherSidekick": "Sidekicks know each other",
"SidekickKnowOtherSidekickRole": "Sidekicks know the roles of other Sidekicks",
@@ -1639,6 +1640,7 @@
"DoppelgangerWarning": "<#ffff00>The Doppelganger swapped skins with you!",
"CheckMurderFail": "<#ff0000>The murder was canceled due to the following reason:\\n",
"SameCTATeam": "The player you attacked is in the same custom team as you",
+ "AFKShielded": "According to the Host's settings, AFK players cannot be killed",
"JackalSidekick": "This Jackal/Sidekick/Recruit interaction is not allowed",
"ContagiousPlayers": "Contagious players cannot kill each other",
"MadmateKillImpostor": "Madmates cannot kill Impostors",
@@ -1680,6 +1682,14 @@
"KickCommandKicked": "was kicked from the game.",
"KickCommandKickedRole": "Their role was",
"ModeratorTag": "<#8000ff>Moderator",
+ "EnableAFKDetector": "Enable AFK Detector",
+ "AFKWarning": "<#ffff00>Please move to avoid being marked as AFK <#ffffff>({0})",
+ "AFKSuffix": "<#ffa500>(AFK)",
+ "AFKConsequence": "Consequence for AFK players",
+ "AFKConsequence.Nothing": "Nothing",
+ "AFKConsequence.Shield": "Shield",
+ "AFKConsequence.Suicide": "Suicide",
+ "AFKConsequence.Kick": "Kick",
"DeathReason.Kill": "Kill",
"DeathReason.Vote": "Ejected",
"DeathReason.Suicide": "Suicide",
@@ -1697,6 +1707,7 @@
"DeathReason.Execution": "Executed",
"DeathReason.Disconnected": "Disconnected",
"DeathReason.Fall": "Fall",
+ "DeathReason.AFK": "AFK",
"DeathReason.Revenge": "Revenge",
"DeathReason.Eaten": "Eaten",
"DeathReason.Sacrifice": "Victim",
@@ -2226,27 +2237,27 @@
"RoleRateNoColor": "Random",
"RoleOnNoColor": "Always",
"RoleOffNoColor": "Disabled",
- "Rate0": "<#444444>0%",
- "Rate5": "<#808F8F>5%",
- "Rate10": "<#799595>10%",
- "Rate15": "<#719C9C>15%",
- "Rate20": "<#6AA2A2>20%",
- "Rate25": "<#62A9A9>25%",
- "Rate30": "<#5BB0B0>30%",
- "Rate35": "<#53B6B6>35%",
- "Rate40": "<#4CBDBD>40%",
- "Rate45": "<#44C4C4>45%",
- "Rate50": "<#3CCACA>50%",
- "Rate55": "<#35D1D1>55%",
- "Rate60": "<#2DD7D7>60%",
- "Rate65": "<#26DEDE>65%",
- "Rate70": "<#1EE5E5>70%",
- "Rate75": "<#17EBEB>75%",
- "Rate80": "<#0FF2F2>80%",
- "Rate85": "<#08F8F8>85%",
- "Rate90": "<#07F9F9>90%",
- "Rate95": "<#00FFFF>95%",
- "Rate100": "<#00FFA5>100%",
+ "Rate0": "<#440000>0%",
+ "Rate5": "<#00ffff>5%",
+ "Rate10": "<#00fffb>10%",
+ "Rate15": "<#00fff8>15%",
+ "Rate20": "<#00fff4>20%",
+ "Rate25": "<#00fff0>25%",
+ "Rate30": "<#00ffec>30%",
+ "Rate35": "<#00ffe7>35%",
+ "Rate40": "<#00ffe3>40%",
+ "Rate45": "<#00ffde>45%",
+ "Rate50": "<#00ffd9>50%",
+ "Rate55": "<#00ffd4>55%",
+ "Rate60": "<#00ffcf>60%",
+ "Rate65": "<#00ffc9>65%",
+ "Rate70": "<#00ffc4>70%",
+ "Rate75": "<#00ffbe>75%",
+ "Rate80": "<#00ffb8>80%",
+ "Rate85": "<#00ffb2>85%",
+ "Rate90": "<#00ffac>90%",
+ "Rate95": "<#00ffa6>95%",
+ "Rate100": "<#ffff44>100%",
"Preset": "Preset",
"Preset_1": "Preset 1",
"Preset_2": "Preset 2",
@@ -2395,6 +2406,8 @@
"KickXboxPlayer": "{0} was kicked due to playing on Xbox.",
"KickPlayStationPlayer": "{0} was kicked due to playing on PlayStation.",
"KickNintendoPlayer": "{0} was kicked due to playing on Nintendo Switch.",
+ "AFKKick": "{0} was kicked because they went AFK",
+ "AFKTooMany": "AFK detection was disabled for this round since there are too many AFK players",
"KBDisplayScore": "Rank: {0} Kills: {1}",
"KBTimeRemain": "Time Remaining: {0}s",
"ResetDoorsEveryTurns": "Reset All Doors After Meetings",
@@ -2547,6 +2560,7 @@
"pet_UFO": "UFO",
"pet_D2WormPet": "Worm",
"pet_RANDOM_FOR_EVERYONE": "Random",
+ "PlayerExemptedFromAFK": "{0} was exempted from AFK detection for 1 game",
"Swapster": "Swapster",
"SwapsterInfo": "Swap Players",
"SwapsterInfoLong": "(Impostors):\\nAs the Swapster, you can select 2 players using your shapeshift menu to swap them.",
diff --git a/Roles/AddOns/GhostRoles/Bloodmoon.cs b/Roles/AddOns/GhostRoles/Bloodmoon.cs
index b6a635006..ff50bb56a 100644
--- a/Roles/AddOns/GhostRoles/Bloodmoon.cs
+++ b/Roles/AddOns/GhostRoles/Bloodmoon.cs
@@ -11,6 +11,7 @@ namespace EHR.AddOns.GhostRoles
private static readonly Dictionary ScheduledDeaths = [];
public Team Team => Team.Impostor | Team.Neutral;
public int Cooldown => Duration.GetInt() + CD.GetInt();
+ public bool ChangeToGA => true;
public void OnAssign(PlayerControl pc)
{
diff --git a/Roles/AddOns/GhostRoles/GA.cs b/Roles/AddOns/GhostRoles/GA.cs
index c42d6807d..dfa73bc90 100644
--- a/Roles/AddOns/GhostRoles/GA.cs
+++ b/Roles/AddOns/GhostRoles/GA.cs
@@ -11,6 +11,7 @@ namespace EHR.AddOns.GhostRoles
public Team Team => Team.Crewmate;
public int Cooldown => CD.GetInt();
+ public bool ChangeToGA => true;
public void OnProtect(PlayerControl pc, PlayerControl target)
{
diff --git a/Roles/AddOns/GhostRoles/Haunter.cs b/Roles/AddOns/GhostRoles/Haunter.cs
index 191c13cc3..44a691890 100644
--- a/Roles/AddOns/GhostRoles/Haunter.cs
+++ b/Roles/AddOns/GhostRoles/Haunter.cs
@@ -21,6 +21,7 @@ namespace EHR.AddOns.GhostRoles
private long WarnTimeStamp = 0;
public Team Team => Team.Crewmate | Team.Neutral;
public int Cooldown => 900;
+ public bool ChangeToGA => false;
public void OnProtect(PlayerControl pc, PlayerControl target)
{
diff --git a/Roles/AddOns/GhostRoles/IGhostRole.cs b/Roles/AddOns/GhostRoles/IGhostRole.cs
index 87818b0c6..37e459e07 100644
--- a/Roles/AddOns/GhostRoles/IGhostRole.cs
+++ b/Roles/AddOns/GhostRoles/IGhostRole.cs
@@ -4,6 +4,7 @@
{
public Team Team { get; }
public int Cooldown { get; }
+ public bool ChangeToGA { get; }
public void OnProtect(PlayerControl pc, PlayerControl target);
public void OnAssign(PlayerControl pc);
}
diff --git a/Roles/AddOns/GhostRoles/Minion.cs b/Roles/AddOns/GhostRoles/Minion.cs
index 51169c644..d463ea75f 100644
--- a/Roles/AddOns/GhostRoles/Minion.cs
+++ b/Roles/AddOns/GhostRoles/Minion.cs
@@ -11,6 +11,7 @@ namespace EHR.AddOns.GhostRoles
public Team Team => Team.Impostor;
public int Cooldown => CD.GetInt();
+ public bool ChangeToGA => true;
public void OnProtect(PlayerControl pc, PlayerControl target)
{
diff --git a/Roles/AddOns/GhostRoles/Specter.cs b/Roles/AddOns/GhostRoles/Specter.cs
index 1960a32d2..341980566 100644
--- a/Roles/AddOns/GhostRoles/Specter.cs
+++ b/Roles/AddOns/GhostRoles/Specter.cs
@@ -11,6 +11,7 @@ namespace EHR.AddOns.GhostRoles
public bool IsWon;
public Team Team => Team.Neutral;
public int Cooldown => 900;
+ public bool ChangeToGA => false;
public void OnAssign(PlayerControl pc)
{
diff --git a/Roles/AddOns/GhostRoles/Warden.cs b/Roles/AddOns/GhostRoles/Warden.cs
index 72533e23d..0af66a112 100644
--- a/Roles/AddOns/GhostRoles/Warden.cs
+++ b/Roles/AddOns/GhostRoles/Warden.cs
@@ -14,6 +14,7 @@ namespace EHR.AddOns.GhostRoles
public Team Team => Team.Crewmate;
public int Cooldown => CD.GetInt();
+ public bool ChangeToGA => true;
public void OnProtect(PlayerControl pc, PlayerControl target)
{
diff --git a/Roles/Crewmate/Aid.cs b/Roles/Crewmate/Aid.cs
index e137c7c22..b6d423691 100644
--- a/Roles/Crewmate/Aid.cs
+++ b/Roles/Crewmate/Aid.cs
@@ -3,7 +3,6 @@ using System.Linq;
using AmongUs.GameOptions;
using EHR.Modules;
using Hazel;
-using UnityEngine;
namespace EHR.Crewmate
{
@@ -132,7 +131,7 @@ namespace EHR.Crewmate
{
var duration = AidDur.GetInt();
var now = Utils.TimeStamp;
- var formatted = ShieldedPlayers.Select(x => string.Format(Translator.GetString("AidCounterTarget"), Utils.ColorString(Main.PlayerColors.GetValueOrDefault(x.Key, Color.white), Main.AllPlayerNames.GetValueOrDefault(x.Key, "Someone")), duration - (now - x.Value)));
+ var formatted = ShieldedPlayers.Select(x => string.Format(Translator.GetString("AidCounterTarget"), x.Key.ColoredPlayerName(), duration - (now - x.Value)));
return string.Join("\n", formatted);
}
diff --git a/Roles/Crewmate/Divinator.cs b/Roles/Crewmate/Divinator.cs
index 2920c848c..f6ca18b54 100644
--- a/Roles/Crewmate/Divinator.cs
+++ b/Roles/Crewmate/Divinator.cs
@@ -61,14 +61,15 @@ public class Divinator : RoleBase
var players = Main.AllAlivePlayerControls;
int rolesNeeded = players.Length * (RolesPerCategory - 1);
- AllPlayerRoleList = Enum.GetValues()
+ var roleList = Enum.GetValues()
.Where(x => !x.IsVanilla() && !x.IsAdditionRole() && x is not CustomRoles.GM and not CustomRoles.Convict && !x.IsForOtherGameMode())
.OrderBy(x => x.IsEnable() ? IRandom.Instance.Next(10) : IRandom.Instance.Next(10, 100))
.Take(rolesNeeded)
.Chunk(RolesPerCategory - 1)
.Zip(players, (Array, Player) => (RoleList: Array.ToList(), Player))
- .Do(x => x.RoleList.Insert(IRandom.Instance.Next(x.RoleList.Count), x.Player.GetCustomRole()))
- .ToDictionary(x => x.Player.PlayerId, x => x.RoleList);
+ .ToArray();
+ roleList.Do(x => x.RoleList.Insert(IRandom.Instance.Next(x.RoleList.Count), x.Player.GetCustomRole()));
+ AllPlayerRoleList = roleList.ToDictionary(x => x.Player.PlayerId, x => x.RoleList);
Logger.Info(string.Join(" ---- ", AllPlayerRoleList.Select(x => $"ID {x.Key}: {string.Join(", ", x.Value)}")), "Divinator Roles");
}
diff --git a/Roles/Crewmate/Goose.cs b/Roles/Crewmate/Goose.cs
index 0cf571ac0..ae9b5641b 100644
--- a/Roles/Crewmate/Goose.cs
+++ b/Roles/Crewmate/Goose.cs
@@ -8,6 +8,7 @@
public static OptionItem OptionSpeedDuringDrag;
public static OptionItem OptionVictimCanUseAbilities;
public static OptionItem CanBeGuessed;
+ public static OptionItem Cooldown;
public void SetupCustomOption()
{
@@ -22,6 +23,9 @@
.SetParent(Options.CustomRoleSpawnChances[CustomRoles.Goose]);
CanBeGuessed = new BooleanOptionItem(Id + 15, "CanBeGuessed", true, TabGroup.CrewmateRoles)
.SetParent(Options.CustomRoleSpawnChances[CustomRoles.Goose]);
+ Cooldown = new FloatOptionItem(Id + 16, "AbilityCooldown", new(0f, 180f, 0.5f), 15f, TabGroup.CrewmateRoles)
+ .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Goose])
+ .SetValueFormat(OptionFormat.Seconds);
}
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Mediumshiper.cs b/Roles/Crewmate/Mediumshiper.cs
index f0dd503a8..cb8b105ee 100644
--- a/Roles/Crewmate/Mediumshiper.cs
+++ b/Roles/Crewmate/Mediumshiper.cs
@@ -47,7 +47,7 @@ public class Mediumshiper : RoleBase
public static void OnReportDeadBody(NetworkedPlayerInfo target)
{
ContactPlayer = [];
- if (target == null) return;
+ if (target == null || target.Object == null) return;
foreach (var pc in Main.AllAlivePlayerControls.Where(x => playerIdList.Contains(x.PlayerId) && x.PlayerId != target.PlayerId).ToArray())
{
if (pc.GetAbilityUseLimit() < 1) continue;
diff --git a/Roles/Impostor/Penguin.cs b/Roles/Impostor/Penguin.cs
index 56689c7f5..940ed1a35 100644
--- a/Roles/Impostor/Penguin.cs
+++ b/Roles/Impostor/Penguin.cs
@@ -1,4 +1,5 @@
-using EHR.Crewmate;
+using AmongUs.GameOptions;
+using EHR.Crewmate;
using EHR.Modules;
using Hazel;
using UnityEngine;
@@ -14,21 +15,24 @@ namespace EHR.Impostor
private static OptionItem OptionMeetingKill;
private static OptionItem OptionSpeedDuringDrag;
private static OptionItem OptionVictimCanUseAbilities;
+
private float AbductTimer;
private float AbductTimerLimit;
private PlayerControl AbductVictim;
+ private float Cooldown;
- int Count = 0;
+ private int Count;
private float DefaultSpeed;
private bool IsGoose;
private long LastNotify;
private bool MeetingKill;
- private PlayerControl Penguin_;
+ private PlayerControl Penguin_;
private byte PenguinId = byte.MaxValue;
private float SpeedDuringDrag;
+
private bool stopCount;
private bool VictimCanUseAbilities;
@@ -77,6 +81,7 @@ namespace EHR.Impostor
MeetingKill = OptionMeetingKill.GetBool();
SpeedDuringDrag = OptionSpeedDuringDrag.GetFloat();
VictimCanUseAbilities = OptionVictimCanUseAbilities.GetBool();
+ Cooldown = Options.DefaultKillCooldown;
}
else
{
@@ -84,9 +89,10 @@ namespace EHR.Impostor
MeetingKill = false;
SpeedDuringDrag = Goose.OptionSpeedDuringDrag.GetFloat();
VictimCanUseAbilities = Goose.OptionVictimCanUseAbilities.GetBool();
+ Cooldown = Goose.Cooldown.GetFloat();
}
- LateTask.New(() => { DefaultSpeed = Main.AllPlayerSpeed[playerId]; }, 9f, log: false);
+ LateTask.New(() => DefaultSpeed = Main.AllPlayerSpeed[playerId], 9f, log: false);
PenguinId = playerId;
Penguin_ = Utils.GetPlayerById(playerId);
@@ -96,9 +102,15 @@ namespace EHR.Impostor
LastNotify = 0;
}
- public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Options.DefaultKillCooldown;
- public override bool CanUseImpostorVentButton(PlayerControl pc) => AbductVictim == null;
+ public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Cooldown;
+ public override bool CanUseImpostorVentButton(PlayerControl pc) => AbductVictim == null && !IsGoose;
public override bool CanUseKillButton(PlayerControl pc) => pc.IsAlive();
+ public override bool CanUseSabotage(PlayerControl pc) => !IsGoose && pc.IsAlive();
+
+ public override void ApplyGameOptions(IGameOptions opt, byte playerId)
+ {
+ if (IsGoose) opt.SetVision(false);
+ }
void SendRPC()
{
@@ -190,7 +202,7 @@ namespace EHR.Impostor
}
else
{
- if (!killer.RpcCheckAndMurder(target, check: true)) return false;
+ if (!IsGoose && !killer.RpcCheckAndMurder(target, check: true)) return false;
doKill = false;
AddVictim(target);
}
diff --git a/Roles/Impostor/Stealth.cs b/Roles/Impostor/Stealth.cs
index abfc03b15..cc293907b 100644
--- a/Roles/Impostor/Stealth.cs
+++ b/Roles/Impostor/Stealth.cs
@@ -9,41 +9,44 @@ namespace EHR.Impostor
public class Stealth : RoleBase
{
private const int Id = 641900;
- private static List playerIdList = [];
+ private static List PlayerIdList = [];
- private static OptionItem optionExcludeImpostors;
- private static OptionItem optionDarkenDuration;
+ private static OptionItem OptionExcludeImpostors;
+ private static OptionItem OptionDarkenDuration;
- private static bool excludeImpostors;
- private static float darkenDuration;
- private static float darkenTimer;
- private static PlayerControl[] darkenedPlayers = [];
- private static SystemTypes? darkenedRoom;
+ private static bool ExcludeImpostors;
+ private static float DarkenDuration;
+ private PlayerControl[] DarkenedPlayers = [];
+ private SystemTypes? DarkenedRoom;
+ private float DarkenTimer;
- public override bool IsEnable => playerIdList.Count > 0;
+ private PlayerControl StealthPC;
+
+ public override bool IsEnable => PlayerIdList.Count > 0;
public static void SetupCustomOption()
{
Options.SetupSingleRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Stealth);
- optionExcludeImpostors = new BooleanOptionItem(Id + 10, "StealthExcludeImpostors", false, TabGroup.ImpostorRoles)
+ OptionExcludeImpostors = new BooleanOptionItem(Id + 10, "StealthExcludeImpostors", false, TabGroup.ImpostorRoles)
.SetParent(Options.CustomRoleSpawnChances[CustomRoles.Stealth]);
- optionDarkenDuration = new FloatOptionItem(Id + 20, "StealthDarkenDuration", new(0.5f, 10f, 0.5f), 3f, TabGroup.ImpostorRoles)
+ OptionDarkenDuration = new FloatOptionItem(Id + 20, "StealthDarkenDuration", new(0.5f, 10f, 0.5f), 3f, TabGroup.ImpostorRoles)
.SetParent(Options.CustomRoleSpawnChances[CustomRoles.Stealth])
.SetValueFormat(OptionFormat.Seconds);
}
public override void Init()
{
- playerIdList = [];
+ PlayerIdList = [];
+ ExcludeImpostors = OptionExcludeImpostors.GetBool();
+ DarkenDuration = DarkenTimer = OptionDarkenDuration.GetFloat();
}
public override void Add(byte playerId)
{
- excludeImpostors = optionExcludeImpostors.GetBool();
- darkenDuration = darkenTimer = optionDarkenDuration.GetFloat();
- darkenedPlayers = null;
+ StealthPC = Utils.GetPlayerById(playerId);
+ DarkenedPlayers = null;
- playerIdList.Add(playerId);
+ PlayerIdList.Add(playerId);
}
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Options.DefaultKillCooldown;
@@ -51,10 +54,6 @@ namespace EHR.Impostor
public override void OnMurder(PlayerControl killer, PlayerControl target)
{
if (!IsEnable) return;
- if (!killer.CanUseKillButton() || killer == null || target == null)
- {
- return;
- }
var playersToDarken = FindPlayersInSameRoom(target);
if (playersToDarken == null)
@@ -63,7 +62,7 @@ namespace EHR.Impostor
return;
}
- if (excludeImpostors)
+ if (ExcludeImpostors)
{
playersToDarken = playersToDarken.Where(player => !player.Is(CustomRoleTypes.Impostor)).ToArray();
}
@@ -75,21 +74,16 @@ namespace EHR.Impostor
PlayerControl[] FindPlayersInSameRoom(PlayerControl killedPlayer)
{
var room = killedPlayer.GetPlainShipRoom();
- if (room == null)
- {
- return null;
- }
+ if (room == null) return null;
- var roomArea = room.roomArea;
- var roomName = room.RoomId;
- RpcDarken(roomName);
- return Main.AllAlivePlayerControls.Where(player => player != Utils.GetPlayerById(playerIdList[0]) && player.Collider.IsTouching(roomArea)).ToArray();
+ RpcDarken(room.RoomId);
+ return Main.AllAlivePlayerControls.Where(p => p.PlayerId != StealthPC.PlayerId && p.GetPlainShipRoom() == room).ToArray();
}
- /// Give the given player zero visibility for seconds.
- static void DarkenPlayers(PlayerControl[] playersToDarken)
+ /// Give the given player zero visibility for seconds.
+ void DarkenPlayers(PlayerControl[] playersToDarken)
{
- darkenedPlayers = [.. playersToDarken];
+ DarkenedPlayers = [.. playersToDarken];
foreach (PlayerControl player in playersToDarken)
{
Main.PlayerStates[player.PlayerId].IsBlackOut = true;
@@ -106,12 +100,12 @@ namespace EHR.Impostor
}
// when you're darkening someone
- if (darkenedPlayers != null)
+ if (DarkenedPlayers != null)
{
// reduce timer
- darkenTimer -= Time.fixedDeltaTime;
+ DarkenTimer -= Time.fixedDeltaTime;
// When the timer reaches 0, return everyone's vision and reset the timer and darkening player.
- if (darkenTimer <= 0)
+ if (DarkenTimer <= 0)
{
ResetDarkenState();
}
@@ -131,14 +125,15 @@ namespace EHR.Impostor
{
if (!IsEnable) return;
Logger.Info($"Set the darkened room to {roomType?.ToString() ?? "null"}", "Stealth");
- darkenedRoom = roomType;
+ DarkenedRoom = roomType;
SendRPC(roomType);
}
void SendRPC(SystemTypes? roomType)
{
if (!IsEnable) return;
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PenguinSync, SendOption.Reliable);
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.StealthDarken, SendOption.Reliable);
+ writer.Write(StealthPC.PlayerId);
writer.Write((byte?)roomType ?? byte.MaxValue);
AmongUsClient.Instance.FinishRpcImmediately(writer);
}
@@ -146,27 +141,28 @@ namespace EHR.Impostor
public static void ReceiveRPC(MessageReader reader)
{
var roomId = reader.ReadByte();
- darkenedRoom = roomId == byte.MaxValue ? null : (SystemTypes)roomId;
+ var stealthId = reader.ReadByte();
+ ((Stealth)Main.PlayerStates[stealthId].Role).DarkenedRoom = roomId == byte.MaxValue ? null : (SystemTypes)roomId;
}
/// Removes the darkening effect that has occurred.
void ResetDarkenState()
{
if (!IsEnable) return;
- if (darkenedPlayers != null)
+ if (DarkenedPlayers != null)
{
- foreach (PlayerControl player in darkenedPlayers)
+ foreach (PlayerControl player in DarkenedPlayers)
{
Main.PlayerStates[player.PlayerId].IsBlackOut = false;
player.MarkDirtySettings();
}
- darkenedPlayers = null;
+ DarkenedPlayers = null;
}
- darkenTimer = darkenDuration;
+ DarkenTimer = DarkenDuration;
RpcDarken(null);
- Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(playerIdList[0]), SpecifyTarget: Utils.GetPlayerById(playerIdList[0]));
+ Utils.NotifyRoles(SpecifySeer: StealthPC, SpecifyTarget: StealthPC);
}
public override string GetSuffix(PlayerControl seer, PlayerControl seen, bool hud = false, bool meeting = false)
@@ -174,9 +170,9 @@ namespace EHR.Impostor
if (Main.PlayerStates[seer.PlayerId].Role is not Stealth { IsEnable: true }) return string.Empty;
seen ??= seer;
// During the meeting, unless it's my suffix, or it's dark everywhere, I won't show anything.
- return meeting || seen != seer || !darkenedRoom.HasValue || (seer.IsModClient() && !hud)
+ return meeting || seen != seer || !DarkenedRoom.HasValue || (seer.IsModClient() && !hud)
? string.Empty
- : string.Format(Translator.GetString("StealthDarkened"), DestroyableSingleton.Instance.GetString(darkenedRoom.Value));
+ : string.Format(Translator.GetString("StealthDarkened"), DestroyableSingleton.Instance.GetString(DarkenedRoom.Value));
}
}
}
\ No newline at end of file
diff --git a/Roles/Impostor/Swooper.cs b/Roles/Impostor/Swooper.cs
index e07a3068b..86298fceb 100644
--- a/Roles/Impostor/Swooper.cs
+++ b/Roles/Impostor/Swooper.cs
@@ -205,8 +205,8 @@ public class Swooper : RoleBase
LateTask.New(() =>
{
float limit = pc.GetAbilityUseLimit();
- bool naN = float.IsNaN(limit);
- if (CanGoInvis && (naN || limit >= 1))
+ bool wraith = UsedRole == CustomRoles.Wraith;
+ if (CanGoInvis && (wraith || limit >= 1))
{
ventedId = ventId;
@@ -215,7 +215,7 @@ public class Swooper : RoleBase
AmongUsClient.Instance.FinishRpcImmediately(writer);
InvisTime = Utils.TimeStamp;
- if (!naN) pc.RpcRemoveAbilityUse();
+ if (!wraith) pc.RpcRemoveAbilityUse();
SendRPC();
pc.Notify(GetString("SwooperInvisState"), Duration);
}
@@ -229,7 +229,7 @@ public class Swooper : RoleBase
public override void OnEnterVent(PlayerControl pc, Vent vent)
{
- if (!IsInvis) return;
+ if (!IsInvis || InvisTime == Utils.TimeStamp) return;
InvisTime = -10;
lastTime = Utils.TimeStamp;
diff --git a/Roles/Neutral/Glitch.cs b/Roles/Neutral/Glitch.cs
index fb57f36f1..a1037d9b7 100644
--- a/Roles/Neutral/Glitch.cs
+++ b/Roles/Neutral/Glitch.cs
@@ -86,7 +86,7 @@ public class Glitch : RoleBase
public override void SetButtonTexts(HudManager hud, byte id)
{
- hud.SabotageButton?.ToggleVisible(true);
+ hud.SabotageButton?.ToggleVisible(!Main.PlayerStates[id].IsDead);
hud.SabotageButton?.OverrideText(Translator.GetString("HackButtonText"));
}
diff --git a/Roles/Neutral/Vulture.cs b/Roles/Neutral/Vulture.cs
index a1075a426..1aed9be13 100644
--- a/Roles/Neutral/Vulture.cs
+++ b/Roles/Neutral/Vulture.cs
@@ -173,7 +173,7 @@ public class Vulture : RoleBase
return;
}
- if (ChangeRoleWhenCantWin.GetBool() && Main.AllAlivePlayerControls.Length <= (NumberOfReportsToWin.GetInt() - BodyReportCount))
+ if (ChangeRoleWhenCantWin.GetBool() && Main.AllAlivePlayerControls.Length - 1 <= (NumberOfReportsToWin.GetInt() - BodyReportCount))
{
var role = ChangeRoles[ChangeRole.GetValue()];
pc.RpcSetCustomRole(role);
diff --git a/main.cs b/main.cs
index a3a48fab1..1e21b6ee9 100644
--- a/main.cs
+++ b/main.cs
@@ -29,8 +29,8 @@ public class Main : BasePlugin
private const string DebugKeyHash = "c0fd562955ba56af3ae20d7ec9e64c664f0facecef4b3e366e109306adeae29d";
private const string DebugKeySalt = "59687b";
private const string PluginGuid = "com.gurge44.endlesshostroles";
- public const string PluginVersion = "4.0.0";
- public const string PluginDisplayVersion = "4.0.0";
+ public const string PluginVersion = "4.1.0";
+ public const string PluginDisplayVersion = "4.1.0";
public const string NeutralColor = "#ffab1b";
public const string ImpostorColor = "#ff1919";
public const string CrewmateColor = "#8cffff";