diff --git a/CustomRoles.cs b/CustomRoles.cs index 5a52d7157..780ef16d9 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -243,6 +243,7 @@ public enum CustomRoles Nightmare, Negotiator, Observer, + Operative, Oracle, Oxyman, Pacifist, diff --git a/Main.cs b/Main.cs index cb53e33f9..87601b8ba 100644 --- a/Main.cs +++ b/Main.cs @@ -439,6 +439,7 @@ public class Main : BasePlugin { CustomRoles.Vacuum, "#E44CD6" }, { CustomRoles.Carrier, "#5DE2E7" }, { CustomRoles.Transmitter, "#c9a11e" }, + { CustomRoles.Operative, "#47f5d2" }, { CustomRoles.Tar, "#8C796B" }, { CustomRoles.Sensor, "#a3f7ff" }, { CustomRoles.Doorjammer, "#FFECA1" }, diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 92a6cd163..ce65e1af8 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -239,6 +239,7 @@ internal static class CustomRolesHelper CustomRoles.Tornado => CustomRoles.Crewmate, CustomRoles.Druid => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, CustomRoles.Catcher => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Operative => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, CustomRoles.Insight => CustomRoles.Crewmate, CustomRoles.Tunneler => CustomRoles.Crewmate, CustomRoles.Detour => CustomRoles.Crewmate, @@ -1445,6 +1446,7 @@ internal static class CustomRolesHelper CustomRoles.Merchant => RoleOptionType.Crewmate_Support, CustomRoles.Monarch => RoleOptionType.Crewmate_Support, CustomRoles.Negotiator => RoleOptionType.Crewmate_Support, + CustomRoles.Operative => RoleOptionType.Crewmate_Support, CustomRoles.Rhapsode => RoleOptionType.Crewmate_Support, CustomRoles.Scanner => RoleOptionType.Crewmate_Support, CustomRoles.SecurityGuard => RoleOptionType.Crewmate_Support, diff --git a/Modules/CustomSabotage.cs b/Modules/CustomSabotage.cs index 810f610e8..bd2960077 100644 --- a/Modules/CustomSabotage.cs +++ b/Modules/CustomSabotage.cs @@ -20,7 +20,7 @@ public abstract class CustomSabotage protected virtual void Fix() { Instances.Remove(this); - SabotageSystemTypeRepairDamagePatch.Instance.IsDirty = true; + ShipStatus.Instance.Systems[SystemTypes.Sabotage].CastFast().IsDirty = true; } protected virtual string GetSuffix(PlayerControl seer, PlayerControl target, bool hud, bool meeting) @@ -93,9 +93,11 @@ public abstract class CustomSabotage if (Instances.Count > 0) { - SabotageSystemTypeRepairDamagePatch.Instance.Timer = SabotageSystemTypeRepairDamagePatch.IsCooldownModificationEnabled + SabotageSystemType sabotageSystemType = ShipStatus.Instance.Systems[SystemTypes.Sabotage].CastFast(); + sabotageSystemType.Timer = SabotageSystemTypeRepairDamagePatch.IsCooldownModificationEnabled ? SabotageSystemTypeRepairDamagePatch.ModifiedCooldownSec : 30f; + sabotageSystemType.IsDirty = true; } } diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index e9688fcd2..127b811e6 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1405,11 +1405,13 @@ internal static class ExtendedPlayerControl }; } +/* public bool CanUseSabotage() { if (!player.IsAlive() || player.Data.Role.Role == RoleTypes.GuardianAngel) return false; return Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) && state.Role.CanUseSabotage(player); } +*/ public RoleTypes GetGhostRoleBasis() { diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 39e324038..1bd036820 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -3679,6 +3679,7 @@ public static class Utils CustomRoles.Thanos => 5, CustomRoles.Blockade => 5, CustomRoles.Mole => Mole.CD.GetInt(), + CustomRoles.Operative => Operative.AbilityCooldown.GetInt(), CustomRoles.PortalMaker => PortalMaker.AbilityCooldown.GetInt(), CustomRoles.Telecommunication => Telecommunication.VentCooldown.GetInt(), CustomRoles.Tether => Tether.VentCooldown.GetInt(), diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index e3d3f2817..4f7ec0b58 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using AmongUs.GameOptions; using EHR.Modules; using EHR.Roles; @@ -405,7 +404,7 @@ public static class SabotageSystemTypeRepairDamagePatch public static bool CheckSabotage(SabotageSystemType __instance, PlayerControl player, SystemTypes systemTypes) { - if (__instance is { Timer: > 0f }) return false; + if (__instance.Timer > 0f) return false; if (Options.DisableSabotage.GetBool()) { @@ -450,7 +449,7 @@ public static class SabotageSystemTypeRepairDamagePatch if (Pelican.IsEaten(player.PlayerId)) return false; - if (__instance != null && systemTypes == SystemTypes.Electrical && Main.PlayerStates.Values.FindFirst(x => !x.IsDead && x.MainRole == CustomRoles.Battery && x.Player != null && x.Player.GetAbilityUseLimit() >= 1f, out var batteryState)) + if (systemTypes == SystemTypes.Electrical && Main.PlayerStates.Values.FindFirst(x => !x.IsDead && x.MainRole == CustomRoles.Battery && x.Player != null && x.Player.GetAbilityUseLimit() >= 1f, out var batteryState)) { batteryState.Player.RpcRemoveAbilityUse(); player.Notify(string.Format(Translator.GetString("BatteryNotify"), CustomRoles.Battery.ToColoredString()), 10f); @@ -465,7 +464,7 @@ public static class SabotageSystemTypeRepairDamagePatch { var room = player.GetPlainShipRoom(); - if (room != null && Doorjammer.JammedRooms.Contains(room.RoomId)) + if (room && Doorjammer.JammedRooms.Contains(room.RoomId)) { player.Notify(Translator.GetString("DoorjammerBlockSabo")); return false; diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 9a6357b4c..5612c17dc 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -18,7 +18,7 @@ namespace EHR; [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.UpdateSystem), typeof(SystemTypes), typeof(PlayerControl), typeof(MessageReader))] public static class MessageReaderUpdateSystemPatch { - public static bool Prefix([HarmonyArgument(0)] SystemTypes systemType, [HarmonyArgument(1)] PlayerControl player, [HarmonyArgument(2)] MessageReader reader) + public static bool Prefix(ShipStatus __instance, [HarmonyArgument(0)] SystemTypes systemType, [HarmonyArgument(1)] PlayerControl player, [HarmonyArgument(2)] MessageReader reader) { try { @@ -38,7 +38,7 @@ public static class MessageReaderUpdateSystemPatch return false; } - return RepairSystemPatch.Prefix(systemType, player, amount); + return RepairSystemPatch.Prefix(__instance, systemType, player, amount); } catch { } @@ -60,7 +60,7 @@ public static class MessageReaderUpdateSystemPatch [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.UpdateSystem), typeof(SystemTypes), typeof(PlayerControl), typeof(byte))] internal static class RepairSystemPatch { - public static bool Prefix( /*ShipStatus __instance,*/ + public static bool Prefix(ShipStatus __instance, [HarmonyArgument(0)] SystemTypes systemType, [HarmonyArgument(1)] PlayerControl player, [HarmonyArgument(2)] byte amount) @@ -156,7 +156,7 @@ internal static class RepairSystemPatch break; } case SystemTypes.Sabotage when AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay: - return SabotageSystemTypeRepairDamagePatch.CheckSabotage(null, player, systemType); + return SabotageSystemTypeRepairDamagePatch.CheckSabotage(__instance.Systems[SystemTypes.Sabotage].CastFast(), player, systemType); case SystemTypes.Security when amount == 1: { bool camerasDisabled = Main.CurrentMap switch @@ -233,14 +233,14 @@ internal static class RepairSystemPatch [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CloseDoorsOfType))] internal static class CloseDoorsPatch { - public static bool Prefix([HarmonyArgument(0)] SystemTypes room) + public static bool Prefix(ShipStatus __instance, [HarmonyArgument(0)] SystemTypes room) { bool allow = !Options.DisableSabotage.GetBool() && !AntiBlackout.SkipTasks && Options.CurrentGameMode is not CustomGameMode.SoloPVP and not CustomGameMode.FFA and not CustomGameMode.StopAndGo and not CustomGameMode.HotPotato and not CustomGameMode.Speedrun and not CustomGameMode.CaptureTheFlag and not CustomGameMode.NaturalDisasters and not CustomGameMode.RoomRush and not CustomGameMode.KingOfTheZones and not CustomGameMode.Quiz and not CustomGameMode.TheMindGame and not CustomGameMode.BedWars and not CustomGameMode.Deathrace and not CustomGameMode.Mingle and not CustomGameMode.Snowdown; if (Doorjammer.JammedRooms.Contains(room)) allow = false; if (SecurityGuard.BlockSabo.Count > 0) allow = false; if (Options.DisableCloseDoor.GetBool()) allow = false; - if (Main.CurrentMap != MapNames.Polus && SabotageSystemTypeRepairDamagePatch.Instance != null && SabotageSystemTypeRepairDamagePatch.Instance.AnyActive) allow = false; + if (Main.CurrentMap != MapNames.Polus && __instance.Systems[SystemTypes.Sabotage].CastFast().AnyActive) allow = false; Logger.Info($"({room}) => {(allow ? "Allowed" : "Blocked")}", "DoorClose"); return allow; diff --git a/Resources/Lang/en_US.jsonc b/Resources/Lang/en_US.jsonc index afb1d771c..7758c124b 100644 --- a/Resources/Lang/en_US.jsonc +++ b/Resources/Lang/en_US.jsonc @@ -1191,6 +1191,9 @@ "Observer": "Observer", "ObserverInfo": "You can see all shield-animations", "ObserverInfoLong": "(Crewmates):\\nThe Observer can see all shield animations caused by other players after the first meeting.", + "Operative": "Operative", + "OperativeInfo": "Reset Sabotage Cooldowns", + "OperativeInfoLong": "(Crewmates):\\nAs the Operative, you can vent/pet to reset all critical sabotage cooldowns to 30s.", "Oracle": "Oracle", "OracleInfo": "Vote a player to see their alignment", "OracleInfoLong": "(Crewmates):\\nAs the Oracle, you may vote for a player during a meeting. You'll see if they are a crewmate, neutral, or Impostor.\\n\\nBut be careful!\\nThis has a chance of giving you an incorrect result!\\n(The host sets the chance)", diff --git a/Roles/RoleBase.cs b/Roles/RoleBase.cs index 92916f710..8d0a287f6 100644 --- a/Roles/RoleBase.cs +++ b/Roles/RoleBase.cs @@ -116,12 +116,12 @@ public abstract class RoleBase : IComparable public virtual bool OnCheckMurder(PlayerControl killer, PlayerControl target) { - return target != null && killer != null; + return target && killer; } public virtual bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { - return target != null && killer != null; + return target && killer; } public virtual void OnMurder(PlayerControl killer, PlayerControl target) { } diff --git a/Roles/Standard/Crewmate/Support/Operative.cs b/Roles/Standard/Crewmate/Support/Operative.cs new file mode 100644 index 000000000..2b251a1f4 --- /dev/null +++ b/Roles/Standard/Crewmate/Support/Operative.cs @@ -0,0 +1,62 @@ +using AmongUs.GameOptions; + +namespace EHR.Roles; + +public class Operative : RoleBase +{ + public static bool On; + + public static OptionItem AbilityCooldown; + private static OptionItem AbilityUseLimit; + private static OptionItem AbilityUseGainWithEachTaskCompleted; + private static OptionItem AbilityChargesWhenFinishedTasks; + + public override bool IsEnable => On; + + public override void SetupCustomOption() + { + StartSetup(658800) + .AutoSetupOption(ref AbilityCooldown, 15, new IntegerValueRule(0, 120, 1), OptionFormat.Seconds) + .AutoSetupOption(ref AbilityUseLimit, 1f, new FloatValueRule(0, 20, 0.05f), OptionFormat.Times) + .AutoSetupOption(ref AbilityUseGainWithEachTaskCompleted, 0.4f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times) + .AutoSetupOption(ref AbilityChargesWhenFinishedTasks, 0.2f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat()); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (!Options.UsePets.GetBool()) + { + AURoleOptions.EngineerInVentMaxTime = 1f; + AURoleOptions.EngineerCooldown = AbilityCooldown.GetFloat(); + } + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (Options.UsePets.GetBool()) return; + UseAbility(); + } + + public override void OnPet(PlayerControl pc) + { + UseAbility(); + } + + static void UseAbility() + { + SabotageSystemType sabotageSystemType = ShipStatus.Instance.Systems[SystemTypes.Sabotage].CastFast(); + sabotageSystemType.Timer = 30f; + sabotageSystemType.IsDirty = true; + } +} \ No newline at end of file