diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 8c17330c1..45ec558d3 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -756,6 +756,7 @@ internal static class CustomRolesHelper public static bool IsTaskBasedCrewmate(this CustomRoles role) => role is CustomRoles.Snitch or + CustomRoles.Speedrunner or CustomRoles.Marshall or CustomRoles.TimeManager or CustomRoles.Ignitor or @@ -775,6 +776,17 @@ internal static class CustomRolesHelper CustomRoles.Pickpocket or CustomRoles.TicketsStealer; + public static bool IsNotAssignableMidGame(this CustomRoles role) => role is + CustomRoles.Egoist or + CustomRoles.Workhorse or + CustomRoles.Cleansed or + CustomRoles.Busy or + CustomRoles.Lovers or + CustomRoles.Stressed or + CustomRoles.Lazy or + CustomRoles.Rascal or + CustomRoles.LastImpostor; + public static bool ForceCancelShapeshift(this CustomRoles role) => role is CustomRoles.Echo or CustomRoles.Hangman or diff --git a/Modules/MeetingTimeManager.cs b/Modules/MeetingTimeManager.cs index 613360500..468677f9a 100644 --- a/Modules/MeetingTimeManager.cs +++ b/Modules/MeetingTimeManager.cs @@ -2,16 +2,19 @@ using AmongUs.GameOptions; using EHR.Crewmate; using EHR.Impostor; +using HarmonyLib; namespace EHR.Modules; -public class MeetingTimeManager +public static class MeetingTimeManager { private static int DiscussionTime; private static int VotingTime; private static int DefaultDiscussionTime; private static int DefaultVotingTime; + public static int VotingTimeLeft; + public static void Init() { DefaultDiscussionTime = Main.RealOptionsData.GetInt(Int32OptionNames.DiscussionTime); @@ -83,4 +86,10 @@ public class MeetingTimeManager Logger.Info($"Discussion Time: {DiscussionTime}s, Voting Time: {VotingTime}s", "MeetingTimeManager.OnReportDeadBody"); } +} + +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.UpdateTimerText))] +static class MeetingHudUpdateTimerTextPatch +{ + public static void Postfix([HarmonyArgument(1)] int value) => MeetingTimeManager.VotingTimeLeft = value; } \ No newline at end of file diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 4aee0d5d0..e694b3b99 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1840,7 +1840,7 @@ public static class Utils public static float GetSettingNameAndValueForRole(CustomRoles role, string settingName) { - const BindingFlags flags = BindingFlags.Public | BindingFlags.Static; + const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; var types = Assembly.GetExecutingAssembly().GetTypes(); var field = types.SelectMany(x => x.GetFields(flags)).FirstOrDefault(x => x.Name == $"{role}{settingName}"); if (field == null) diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index d9b213f29..93a9a3cc3 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -120,16 +120,16 @@ static class GameEndChecker { switch (pc.GetCustomRole()) { - case CustomRoles.DarkHide when !pc.Data.IsDead && ((WinnerTeam == CustomWinner.Impostor && !reason.Equals(GameOverReason.ImpostorBySabotage)) || WinnerTeam == CustomWinner.DarkHide || (WinnerTeam == CustomWinner.Crewmate && !reason.Equals(GameOverReason.HumansByTask) && Main.PlayerStates[pc.PlayerId].Role is DarkHide { IsWinKill: true } && DarkHide.SnatchesWin.GetBool())): + case CustomRoles.DarkHide when pc.IsAlive() && ((WinnerTeam == CustomWinner.Impostor && !reason.Equals(GameOverReason.ImpostorBySabotage)) || WinnerTeam == CustomWinner.DarkHide || (WinnerTeam == CustomWinner.Crewmate && !reason.Equals(GameOverReason.HumansByTask) && Main.PlayerStates[pc.PlayerId].Role is DarkHide { IsWinKill: true } && DarkHide.SnatchesWin.GetBool())): ResetAndSetWinner(CustomWinner.DarkHide); WinnerIds.Add(pc.PlayerId); break; - case CustomRoles.Phantasm when pc.GetTaskState().IsTaskFinished && pc.Data.IsDead && Options.PhantomSnatchesWin.GetBool(): + case CustomRoles.Phantasm when pc.GetTaskState().RemainingTasksCount <= 0 && !pc.IsAlive() && Options.PhantomSnatchesWin.GetBool(): reason = GameOverReason.ImpostorByKill; ResetAndSetWinner(CustomWinner.Phantom); WinnerIds.Add(pc.PlayerId); break; - case CustomRoles.Phantasm when !Options.PhantomSnatchesWin.GetBool() && !pc.IsAlive() && pc.GetTaskState().IsTaskFinished: + case CustomRoles.Phantasm when pc.GetTaskState().RemainingTasksCount <= 0 && !pc.IsAlive() && !Options.PhantomSnatchesWin.GetBool(): WinnerIds.Add(pc.PlayerId); AdditionalWinnerTeams.Add(AdditionalWinners.Phantom); break; @@ -316,7 +316,7 @@ static class GameEndChecker Main.AllPlayerControls.DoIf( x => x.GetClient() != null && !x.Data.Disconnected, x => ChatUpdatePatch.SendMessage(PlayerControl.LocalPlayer, "\n", x.PlayerId, msg)); - + SetEverythingUpPatch.LastWinsReason = WinnerTeam is CustomWinner.Crewmate or CustomWinner.Impostor ? GetString($"GameOverReason.{reason}") : string.Empty; AmongUsClient self = AmongUsClient.Instance; self.StartCoroutine(CoEndGame(self, reason).WrapToIl2Cpp()); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 9279f4d0a..ab356d08a 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -29,7 +29,6 @@ static class CheckForEndVotingPatch //Meeting Skip with vote counting on keystroke (m + delete) bool shouldSkip = Input.GetKeyDown(KeyCode.F6); - // var voteLog = Logger.Handler("Vote"); try { @@ -78,13 +77,14 @@ static class CheckForEndVotingPatch return true; } - if (pva.DidVote && pva.VotedFor < 253 && pc.Data?.IsDead == false) + if (pva.DidVote && pva.VotedFor < 253 && pc.IsAlive()) { var voteTarget = Utils.GetPlayerById(pva.VotedFor); if (voteTarget == null || !voteTarget.IsAlive() || voteTarget.Data == null || voteTarget.Data.Disconnected) { - __instance.UpdateButtons(); + pva.UnsetVote(); __instance.RpcClearVote(pc.GetClientId()); + __instance.UpdateButtons(); } else if (voteTarget != null && !pc.GetCustomRole().CancelsVote()) Main.PlayerStates[pc.PlayerId].Role.OnVote(pc, voteTarget); @@ -603,7 +603,7 @@ static class MeetingHudStartPatch private static void NotifyRoleSkillOnMeetingStart() { if (!AmongUsClient.Instance.AmHost) return; - + CheckForEndVotingPatch.RunRoleCode = true; List<(string Message, byte TargetID, string Title)> msgToSend = []; @@ -1132,17 +1132,17 @@ static class MeetingHudCastVotePatch isSkip = true; } - bool isVoteCanceled = false; + bool voteCanceled = false; if (!Main.DontCancelVoteList.Contains(srcPlayerId) && !isSkip && pc_src.GetCustomRole().CancelsVote() && Main.PlayerStates[srcPlayerId].Role.OnVote(pc_src, pc_target)) { ShouldCancelVoteList.TryAdd(srcPlayerId, (__instance, pva_src, pc_src)); - isVoteCanceled = true; + voteCanceled = true; } - Logger.Info($"{pc_src.GetNameWithRole().RemoveHtmlTags()} => {(isSkip ? "Skip" : pc_target.GetNameWithRole().RemoveHtmlTags())}{(isVoteCanceled ? " (Canceled)" : string.Empty)}", "Vote"); + Logger.Info($"{pc_src.GetNameWithRole().RemoveHtmlTags()} => {(isSkip ? "Skip" : pc_target.GetNameWithRole().RemoveHtmlTags())}{(voteCanceled ? " (Canceled)" : string.Empty)}", "Vote"); - return isSkip || !isVoteCanceled; // return false to use the vote as a trigger; skips and invalid votes are never canceled + return isSkip || !voteCanceled; // return false to use the vote as a trigger; skips and invalid votes are never canceled } public static void Postfix([HarmonyArgument(0)] byte srcPlayerId) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 9aef45ce0..ddcda5f27 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1306,14 +1306,18 @@ static class FixedUpdatePatch LevelKickBufferTime = 20; if (player.GetClient().ProductUserId != "") { - if (!BanManager.TempBanWhiteList.Contains(player.GetClient().GetHashedPuid())) - BanManager.TempBanWhiteList.Add(player.GetClient().GetHashedPuid()); + var hashedPuid = player.GetClient().GetHashedPuid(); + if (!BanManager.TempBanWhiteList.Contains(hashedPuid)) + BanManager.TempBanWhiteList.Add(hashedPuid); } - string msg = string.Format(GetString("KickBecauseLowLevel"), player.GetRealName().RemoveHtmlTags()); - Logger.SendInGame(msg); - AmongUsClient.Instance.KickPlayer(player.GetClientId(), true); - Logger.Info(msg, "Low Level Temp Ban"); + if (!Main.AllPlayerControls.All(x => x.Data.PlayerLevel <= 1)) + { + string msg = string.Format(GetString("KickBecauseLowLevel"), player.GetRealName().RemoveHtmlTags()); + Logger.SendInGame(msg); + AmongUsClient.Instance.KickPlayer(player.GetClientId(), true); + Logger.Info(msg, "Low Level Temp Ban"); + } } } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 137890ace..0a918ad41 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -565,6 +565,7 @@ "TrapperInfo": "Freeze your killer for a few seconds", "OnboundInfo": "You can't be guessed", "AsthmaticInfo": "Stop And Go", + "KnightedInfo": "You have an extra vote (from the Monarch)", "MagnetInfo": "Pull your victims to you", "SleuthInfo": "Know the roles of the bodies you report", "AntiTPInfo": "You can't be teleported", diff --git a/Roles/Crewmate/Merchant.cs b/Roles/Crewmate/Merchant.cs index 4a8921ef2..0505aa3f0 100644 --- a/Roles/Crewmate/Merchant.cs +++ b/Roles/Crewmate/Merchant.cs @@ -75,7 +75,8 @@ namespace EHR.Crewmate if (OptionSellOnlyEnabledAddons.GetBool()) Addons.RemoveAll(x => x.GetMode() == 0); - Addons.RemoveAll(x => x is CustomRoles.Nimble or CustomRoles.Physicist or CustomRoles.Bloodlust or CustomRoles.Finder or CustomRoles.Noisy); + Addons.RemoveAll(StartGameHostPatch.BasisChangingAddons.ContainsKey); + Addons.RemoveAll(x => x.IsNotAssignableMidGame()); } public override void Add(byte playerId) @@ -101,33 +102,33 @@ namespace EHR.Crewmate CustomRoles addon = Addons.RandomElement(); - var AllAlivePlayer = + var availableTargets = Main.AllAlivePlayerControls.Where(x => x.PlayerId != player.PlayerId && !Pelican.IsEaten(x.PlayerId) && !x.Is(addon) - && !CustomRolesHelper.CheckAddonConflict(addon, x) + && CustomRolesHelper.CheckAddonConflict(addon, x) && (Cleanser.CleansedCanGetAddon.GetBool() || (!Cleanser.CleansedCanGetAddon.GetBool() && !x.Is(CustomRoles.Cleansed))) && ((OptionCanTargetCrew.GetBool() && x.IsCrewmate()) || (OptionCanTargetImpostor.GetBool() && x.GetCustomRole().IsImpostor()) || (OptionCanTargetNeutral.GetBool() && (x.GetCustomRole().IsNeutral() || x.IsNeutralKiller()))) ).ToList(); - if (AllAlivePlayer.Count <= 0) return; + if (availableTargets.Count <= 0) return; bool helpfulAddon = GroupedAddons.TryGetValue(AddonTypes.Helpful, out var helpful) && helpful.Contains(addon); bool harmfulAddon = GroupedAddons.TryGetValue(AddonTypes.Harmful, out var harmful) && harmful.Contains(addon); - if (helpfulAddon && OptionSellOnlyHarmfulToEvil.GetBool()) AllAlivePlayer.RemoveAll(x => !x.Is(Team.Crewmate)); - if (harmfulAddon && OptionSellOnlyHelpfulToCrew.GetBool()) AllAlivePlayer.RemoveAll(x => x.Is(Team.Crewmate)); + if (helpfulAddon && OptionSellOnlyHarmfulToEvil.GetBool()) availableTargets.RemoveAll(x => !x.Is(Team.Crewmate)); + if (harmfulAddon && OptionSellOnlyHelpfulToCrew.GetBool()) availableTargets.RemoveAll(x => x.Is(Team.Crewmate)); - if (AllAlivePlayer.Count == 0) + if (availableTargets.Count == 0) { player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantAddonSellFail"))); return; } - PlayerControl target = AllAlivePlayer.RandomElement(); + PlayerControl target = availableTargets.RandomElement(); target.RpcSetCustomRole(addon); target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantAddonSell"))); diff --git a/Roles/Crewmate/Negotiator.cs b/Roles/Crewmate/Negotiator.cs index 7e44d5ac6..d3f016012 100644 --- a/Roles/Crewmate/Negotiator.cs +++ b/Roles/Crewmate/Negotiator.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; +using EHR.Modules; namespace EHR.Crewmate { @@ -10,11 +11,12 @@ namespace EHR.Crewmate public static bool On; private static List Instances = []; + private static OptionItem MinVotingTimeLeftToNegotiate; private static OptionItem LowVision; private static OptionItem LowSpeed; private static OptionItem AbilityUseLimit; - public static OptionItem AbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + private static OptionItem AbilityUseGainWithEachTaskCompleted; + private static OptionItem AbilityChargesWhenFinishedTasks; private byte NegotiatorId; private NegotiationType Penalty; private Dictionary> PermanentPenalties; @@ -25,6 +27,7 @@ namespace EHR.Crewmate public override void SetupCustomOption() { StartSetup(647950) + .AutoSetupOption(ref MinVotingTimeLeftToNegotiate, 10, new IntegerValueRule(0, 30, 1), OptionFormat.Seconds) .AutoSetupOption(ref LowVision, 0.4f, new FloatValueRule(0f, 1f, 0.05f), OptionFormat.Multiplier) .AutoSetupOption(ref LowSpeed, 0.9f, new FloatValueRule(0.1f, 2f, 0.1f), OptionFormat.Multiplier) .AutoSetupOption(ref AbilityUseLimit, 0, new IntegerValueRule(0, 20, 1), OptionFormat.Times) @@ -85,7 +88,7 @@ namespace EHR.Crewmate switch (Penalty) { case NegotiationType.Suicide: - target.Suicide(PlayerState.DeathReason.Negotiation, negotiator); + LateTask.New(() => target.Suicide(PlayerState.DeathReason.Negotiation, negotiator), 1f, log: false); break; case NegotiationType.HarmfulAddon: var addon = Options.GroupedAddons[AddonTypes.Harmful].Shuffle().FirstOrDefault(x => CustomRolesHelper.CheckAddonConflict(x, target)); @@ -104,7 +107,7 @@ namespace EHR.Crewmate public override bool OnVote(PlayerControl voter, PlayerControl target) { - if (target == null || voter == null || voter.PlayerId == target.PlayerId || TargetId != byte.MaxValue || voter.GetAbilityUseLimit() < 1f || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; + if (target == null || voter == null || voter.PlayerId == target.PlayerId || TargetId != byte.MaxValue || voter.GetAbilityUseLimit() < 1f || MinVotingTimeLeftToNegotiate.GetInt() > MeetingTimeManager.VotingTimeLeft || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; TargetId = target.PlayerId; Penalty = NegotiationType.Suicide; diff --git a/Roles/Neutral/Bandit.cs b/Roles/Neutral/Bandit.cs index fa14a03ea..3769dfd62 100644 --- a/Roles/Neutral/Bandit.cs +++ b/Roles/Neutral/Bandit.cs @@ -111,16 +111,7 @@ public class Bandit : RoleBase return addon; } - private static bool IsBlacklistedAddon(CustomRoles role) => (role.IsImpOnlyAddon() && !CanStealImpOnlyAddon.GetBool()) || (role.IsBetrayalAddon() && !CanStealBetrayalAddon.GetBool()) || StartGameHostPatch.BasisChangingAddons.ContainsKey(role) || role is - CustomRoles.Egoist or - CustomRoles.Workhorse or - CustomRoles.Cleansed or - CustomRoles.Busy or - CustomRoles.Lovers or - CustomRoles.Stressed or - CustomRoles.Lazy or - CustomRoles.Rascal or - CustomRoles.LastImpostor; + private static bool IsBlacklistedAddon(CustomRoles role) => (role.IsImpOnlyAddon() && !CanStealImpOnlyAddon.GetBool()) || (role.IsBetrayalAddon() && !CanStealBetrayalAddon.GetBool()) || StartGameHostPatch.BasisChangingAddons.ContainsKey(role) || role.IsNotAssignableMidGame(); public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) {