From 372177f2c42ace86cdcaa67edc37bd0346023c0d Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Sat, 19 Oct 2024 12:22:02 +0200 Subject: [PATCH] Add Wasp, Trainee, Vortex --- CustomRoles.cs | 5 +- Modules/CustomRolesHelper.cs | 6 + Modules/ExtendedPlayerControl.cs | 7 + Modules/GameState.cs | 1 + Modules/NameColorManager.cs | 1 + Modules/Utils.cs | 3 + Patches/MeetingHudPatch.cs | 2 + Patches/PetActionsPatch.cs | 6 + Patches/PlayerControlPatch.cs | 10 +- Resources/Lang/en_US.json | 24 ++++ Roles/AddOns/Common/Trainee.cs | 12 ++ Roles/Impostor/Wasp.cs | 226 +++++++++++++++++++++++++++++++ Roles/Neutral/Vortex.cs | 58 ++++++++ main.cs | 3 + 14 files changed, 362 insertions(+), 2 deletions(-) create mode 100644 Roles/AddOns/Common/Trainee.cs create mode 100644 Roles/Impostor/Wasp.cs create mode 100644 Roles/Neutral/Vortex.cs diff --git a/CustomRoles.cs b/CustomRoles.cs index ebbcbf2d7..47e274d6b 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -105,6 +105,7 @@ public enum CustomRoles Vindicator, Visionary, Warlock, + Wasp, Wildling, Witch, YinYanger, @@ -341,6 +342,7 @@ public enum CustomRoles Vengeance, VengefulRomantic, Virus, + Vortex, Vulture, Wraith, Werewolf, @@ -381,7 +383,7 @@ public enum CustomRoles // Natural Disasters NDPlayer, - + // Room Rush RRPlayer, @@ -457,6 +459,7 @@ public enum CustomRoles Reach, Recruit, Rookie, + Trainee, DualPersonality, // Schizophrenic Seer, Sleuth, diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 551550a89..178502b70 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -121,6 +121,7 @@ internal static class CustomRolesHelper CustomRoles.Dictator => CustomRoles.Crewmate, CustomRoles.Inhibitor => CustomRoles.Impostor, CustomRoles.Kidnapper => CustomRoles.Shapeshifter, + CustomRoles.Wasp => CustomRoles.Impostor, CustomRoles.Assumer => CustomRoles.Impostor, CustomRoles.Augmenter => CustomRoles.Shapeshifter, CustomRoles.Ventriloquist => CustomRoles.Impostor, @@ -419,6 +420,7 @@ internal static class CustomRolesHelper CustomRoles.BloodKnight => RoleTypes.Impostor, CustomRoles.Poisoner => RoleTypes.Impostor, CustomRoles.NSerialKiller => RoleTypes.Impostor, + CustomRoles.Vortex => RoleTypes.Impostor, CustomRoles.Beehive => RoleTypes.Impostor, CustomRoles.RouleteGrandeur => RoleTypes.Impostor, CustomRoles.Nonplus => RoleTypes.Impostor, @@ -510,6 +512,7 @@ internal static class CustomRolesHelper CustomRoles.Rogue or CustomRoles.Parasite or CustomRoles.NSerialKiller or + CustomRoles.Vortex or CustomRoles.Beehive or CustomRoles.RouleteGrandeur or CustomRoles.Nonplus or @@ -583,6 +586,7 @@ internal static class CustomRolesHelper CustomRoles.Augmenter or CustomRoles.Inhibitor or CustomRoles.Kidnapper or + CustomRoles.Wasp or CustomRoles.Hypnotist or CustomRoles.Assumer or CustomRoles.Generator or @@ -1237,6 +1241,7 @@ internal static class CustomRolesHelper CustomRoles.Sniper => RoleOptionType.Impostor_Killing, CustomRoles.BoobyTrap => RoleOptionType.Impostor_Killing, CustomRoles.Underdog => RoleOptionType.Impostor_Killing, + CustomRoles.Wasp => RoleOptionType.Impostor_Killing, CustomRoles.Witch => RoleOptionType.Impostor_Killing, CustomRoles.Zombie => RoleOptionType.Impostor_Killing, CustomRoles.Bard => RoleOptionType.Impostor_Support, @@ -1493,6 +1498,7 @@ public enum CountTypes HexMaster, Wraith, NSerialKiller, + Vortex, Beehive, RouleteGrandeur, Nonplus, diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 94c54ebf5..3fdf9d6b8 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -67,6 +67,7 @@ static class ExtendedPlayerControl public static bool CanUseVent(this PlayerControl player, int ventId) { + if (player.Is(CustomRoles.Trainee) && MeetingStates.FirstMeeting) return false; return GameStates.IsInTask && ((player.inVent && player.GetClosestVent()?.Id == ventId) || (player.CanUseImpostorVentButton() || player.GetRoleTypes() == RoleTypes.Engineer) && Main.PlayerStates.Values.All(x => x.Role.CanUseVent(player, ventId))); } @@ -1169,6 +1170,12 @@ static class ExtendedPlayerControl public static bool CanUseSabotage(this PlayerControl pc) { if (!pc.IsAlive() || pc.Data.Role.Role == RoleTypes.GuardianAngel) return false; + if (pc.Is(CustomRoles.Trainee) && MeetingStates.FirstMeeting) + { + pc.Notify(GetString("TraineeNotify")); + return false; + } + return Main.PlayerStates.TryGetValue(pc.PlayerId, out var state) && state.Role.CanUseSabotage(pc); } diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 7d35b7689..bfed9d744 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -57,6 +57,7 @@ public class PlayerState(byte playerId) Assumed, Negotiation, Trapped, + Stung, // Natural Disasters Meteor, diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index e595bb05c..356def357 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -160,6 +160,7 @@ public static class NameColorManager CustomRoles.Socialite when ((Socialite)seerRoleClass).MarkedPlayerId == target.PlayerId => Main.RoleColors[seerRole], CustomRoles.Beehive when ((Beehive)seerRoleClass).StungPlayers.ContainsKey(target.PlayerId) => "000000", CustomRoles.Dad when ((Dad)seerRoleClass).DrunkPlayers.Contains(target.PlayerId) => "000000", + CustomRoles.Wasp when seerRoleClass is Wasp wasp && (wasp.DelayedKills.ContainsKey(target.PlayerId) || wasp.MeetingKills.Contains(target.PlayerId)) => "000000", CustomRoles.God when God.KnowInfo.GetValue() == 1 => target.GetTeam() switch { Team.Impostor => Main.ImpostorColor, diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 756af3c6a..8593c8efe 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -745,6 +745,7 @@ public static class Utils case CustomRoles.Eclipse: case CustomRoles.Pyromaniac: case CustomRoles.NSerialKiller: + case CustomRoles.Vortex: case CustomRoles.Beehive: case CustomRoles.RouleteGrandeur: case CustomRoles.Nonplus: @@ -2020,6 +2021,7 @@ public static class Utils else { SelfMark.Append(Witch.GetSpelledMark(seer.PlayerId, isForMeeting)); + if (isForMeeting) SelfMark.Append(Wasp.GetStungMark(seer.PlayerId)); } GameMode: @@ -2176,6 +2178,7 @@ public static class Utils if (Options.CurrentGameMode != CustomGameMode.Standard || GameStates.IsLobby) goto BeforeEnd2; TargetMark.Append(Witch.GetSpelledMark(target.PlayerId, isForMeeting)); + if (isForMeeting) TargetMark.Append(Wasp.GetStungMark(target.PlayerId)); if (target.Is(CustomRoles.SuperStar) && Options.EveryOneKnowSuperStar.GetBool()) TargetMark.Append(ColorString(GetRoleColor(CustomRoles.SuperStar), "★")); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 6475f5a3b..2ef02051a 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -511,6 +511,7 @@ static class CheckForEndVotingPatch if (Witch.PlayerIdList.Count > 0) Witch.OnCheckForEndVoting(deathReason, playerIds); if (Virus.PlayerIdList.Count > 0) Virus.OnCheckForEndVoting(deathReason, playerIds); if (deathReason == PlayerState.DeathReason.Vote) Gaslighter.OnExile(playerIds); + if (Wasp.On && deathReason == PlayerState.DeathReason.Vote) Wasp.OnExile(playerIds); foreach (var playerId in playerIds) { try @@ -960,6 +961,7 @@ static class MeetingHudStartPatch } sb.Append(Witch.GetSpelledMark(target.PlayerId, true)); + sb.Append(Wasp.GetStungMark(target.PlayerId)); if (target.Is(CustomRoles.SuperStar) && Options.EveryOneKnowSuperStar.GetBool()) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.SuperStar), "★")); diff --git a/Patches/PetActionsPatch.cs b/Patches/PetActionsPatch.cs index d64ec763b..b6460cc96 100644 --- a/Patches/PetActionsPatch.cs +++ b/Patches/PetActionsPatch.cs @@ -122,6 +122,12 @@ static class ExternalRpcPetPatch return; } + if (pc.Is(CustomRoles.Trainee) && MeetingStates.FirstMeeting) + { + pc.Notify(Translator.GetString("TraineeNotify")); + return; + } + if (pc.HasAbilityCD()) { if (!pc.IsHost()) pc.Notify(Translator.GetString("AbilityOnCooldown")); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 91241be01..a539220a2 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -760,7 +760,15 @@ static class ShapeshiftPatch var shapeshifting = shapeshifter.PlayerId != target.PlayerId; - if (AmongUsClient.Instance.AmHost && shapeshifting && !Rhapsode.CheckAbilityUse(shapeshifter)) return false; + if (AmongUsClient.Instance.AmHost && shapeshifting) + { + if (!Rhapsode.CheckAbilityUse(shapeshifter)) return false; + if (shapeshifter.Is(CustomRoles.Trainee) && MeetingStates.FirstMeeting) + { + shapeshifter.Notify(GetString("TraineeNotify")); + return false; + } + } Main.CheckShapeshift[shapeshifter.PlayerId] = shapeshifting; Main.ShapeshiftTarget[shapeshifter.PlayerId] = target.PlayerId; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 4828d1388..ec1c757e1 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1780,6 +1780,7 @@ "DeathReason.Assumed": "Assumed", "DeathReason.Negotiation": "Negotiation", "DeathReason.Trapped": "Trapped", + "DeathReason.Stung": "Stung", "DeathReason.Meteor": "Meteor Strike", "DeathReason.Lava": "Lava", "DeathReason.Tornado": "Tornado", @@ -2854,6 +2855,18 @@ "pet_D2WormPet": "Worm", "pet_RANDOM_FOR_EVERYONE": "Random", "PlayerExemptedFromAFK": "{0} was exempted from AFK detection for 1 game", + "Wasp": "Wasp", + "WaspInfo": "Sting Players", + "WaspInfoLong": "(Impostors):\\nAs the Wasp, you can sting players by using your kill button on them. If the player has a kill button they will die after a few seconds. Otherwise, they will have a ◀ next to their name in the next meeting. They will die after the meeting if the wasp isn't ejected.\\n\\nWhen someone tries to kill you, you may evade the kill based on the host's settings. If so, 2 things can happen:\\n1. You become aggressive and go into swarm mode with no kill cooldown for some time.\\n2. You get pest controlled. This will daze you, Stunting your vision, speed and ability to sting for some time.\\nNote: If you evade a kill, you cannot evade kills, sabotage or vent for the rest of that round.", + "Wasp.StingCooldown": "Sting Cooldown", + "Wasp.KillDelay": "Sting Kill Delay", + "Wasp.EvadeKills": "Evade Kills", + "Wasp.SwarmModeDuration": "Swarm Mode Duration", + "Wasp.WaspDiesAfterSwarmEnd": "Suicide After Swarm Mode Ends", + "Wasp.PestControlDuration": "Pest Control Duration", + "Wasp.PestControlSpeed": "Speed During Pest Control", + "Wasp.PestControlVision": "Vision During Pest Control", + "Wasp.SwarmModeSuffix": "<#ffff00>You've been attacked and entered swarm mode for {0} seconds!", "Swapster": "Swapster", "SwapsterInfo": "Swap Players", "SwapsterInfoLong": "(Impostors):\\nAs the Swapster, you can select 2 players using your shapeshift menu to swap them.", @@ -3290,6 +3303,12 @@ "VentguardNotifyOnBlockedVentUse": "Notify When Someone Tries to Use a Blocked Vent", "VentguardBlocksResetOnMeeting": "Reset Blocked Vents Every Meeting", "VentBlockSuccess": "This vent is now blocked", + "Vortex": "Vortex", + "VortexInfo": "Your victims' bodies teleport to a random vent", + "VortexInfoLong": "(Neutrals):\\nAs the Vortex, when you kill someone, their body teleports to a random vent. The body will be reported in the vent.", + "Vortex.IfTargetCannotBeTeleported": "If the target cannot be teleported to a random vent", + "Vortex.NoTPMode.Block": "Cancel Kill", + "Vortex.NoTPMode.DoWithoutTP": "Kill Without TP", "Enigma": "Enigma", "EnigmaInfo": "Get Clues about Killers", "EnigmaInfoLong": "(Crewmates):\\nAs the Enigma, you get a random clue about the killer each meeting, depending on the setting, you may have to report the body to receive a clue. The more tasks you complete the preciser the clues get.\\n\\nLight colors: Red, Pink, Orange, Yellow, White, Cyan, Lime, Rose, Banana, Coral\\nDark colors: Blue, Green, Black, Purple, Brown, Maroon, Gray, Tan", @@ -4438,6 +4457,11 @@ "RookieInfo": "You Can't Kill on the First Round", "RookieInfoLong": "(Add-ons):\\nYou can't kill on the first round.", "Prefix.Rookie": "Rookie", + "Trainee": "Trainee", + "TraineeInfo": "You Can't Vent/Sabotage/SS/Pet Round 1", + "TraineeInfoLong": "(Add-ons):\\nYou can't vent, sabotage, shapeshift, or pet on the first round.", + "Prefix.Trainee": "Trainee", + "TraineeNotify": "<#ffff00>As the <#4287f5>Trainee, you can't vent, sabotage, shapeshift, or pet on the first round!", "Soothsayer": "Soothsayer", "SoothsayerInfo": "Predict Who Kills You", "SoothsayerInfoLong": "(Crewmates):\\nAs the Soothsayer, you can predict who kills you. If you're correct, when you die, everyone will be notified of the killer's identity.", diff --git a/Roles/AddOns/Common/Trainee.cs b/Roles/AddOns/Common/Trainee.cs new file mode 100644 index 000000000..ea239716e --- /dev/null +++ b/Roles/AddOns/Common/Trainee.cs @@ -0,0 +1,12 @@ +namespace EHR.AddOns.Common +{ + public class Trainee : IAddon + { + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(647844, CustomRoles.Trainee, canSetNum: true, teamSpawnOptions: true); + } + } +} \ No newline at end of file diff --git a/Roles/Impostor/Wasp.cs b/Roles/Impostor/Wasp.cs new file mode 100644 index 000000000..7d2b44f06 --- /dev/null +++ b/Roles/Impostor/Wasp.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AmongUs.GameOptions; +using EHR.Modules; +using EHR.Patches; +using Hazel; + +namespace EHR.Impostor +{ + public class Wasp : RoleBase + { + public static bool On; + private static List Instances = []; + + private static OptionItem StingCooldown; + private static OptionItem KillDelay; + private static OptionItem EvadeKills; + private static OptionItem SwarmModeDuration; + private static OptionItem WaspDiesAfterSwarmEnd; + private static OptionItem PestControlDuration; + private static OptionItem PestControlSpeed; + private static OptionItem PestControlVision; + public Dictionary DelayedKills; + private bool EvadedKillThisRound; + + private long LastUpdate; + public HashSet MeetingKills; + private long PestControlEnd; + private long SwarmModeEnd; + private PlayerControl WaspPC; + + public override bool IsEnable => On; + + public override void SetupCustomOption() + { + StartSetup(645350) + .AutoSetupOption(ref StingCooldown, 30, new IntegerValueRule(1, 120, 1), OptionFormat.Seconds) + .AutoSetupOption(ref KillDelay, 5, new IntegerValueRule(1, 30, 1), OptionFormat.Seconds) + .AutoSetupOption(ref EvadeKills, true) + .AutoSetupOption(ref SwarmModeDuration, 15, new IntegerValueRule(1, 60, 1), OptionFormat.Seconds, overrideParent: EvadeKills) + .AutoSetupOption(ref WaspDiesAfterSwarmEnd, true, overrideParent: EvadeKills) + .AutoSetupOption(ref PestControlDuration, 10, new IntegerValueRule(1, 60, 1), OptionFormat.Seconds, overrideParent: EvadeKills) + .AutoSetupOption(ref PestControlSpeed, 0.5f, new FloatValueRule(0.05f, 2f, 0.05f), OptionFormat.Multiplier, overrideParent: EvadeKills) + .AutoSetupOption(ref PestControlVision, 0.2f, new FloatValueRule(0f, 1f, 0.05f), OptionFormat.Multiplier, overrideParent: EvadeKills); + } + + public override void Init() + { + On = false; + Instances = []; + } + + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + WaspPC = playerId.GetPlayer(); + DelayedKills = []; + MeetingKills = []; + SwarmModeEnd = 0; + PestControlEnd = 0; + EvadedKillThisRound = false; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = SwarmModeEnd == 0 ? StingCooldown.GetInt() : 0.01f; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (PestControlEnd == 0) return; + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, PestControlVision.GetFloat()); + opt.SetFloat(FloatOptionNames.CrewLightMod, PestControlVision.GetFloat()); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (DelayedKills.ContainsKey(target.PlayerId)) return false; + + if (target.HasKillButton()) DelayedKills[target.PlayerId] = Utils.TimeStamp + KillDelay.GetInt(); + else MeetingKills.Add(target.PlayerId); + + killer.SetKillCooldown(StingCooldown.GetInt()); + return false; + } + + public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + { + if (lowLoad || !GameStates.IsInTask || ExileController.Instance || !DelayedKills.TryGetValue(pc.PlayerId, out var ts) || ts > Utils.TimeStamp) return; + pc.Suicide(PlayerState.DeathReason.Stung, WaspPC); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (SwarmModeEnd == 0 && PestControlEnd == 0) return; + + var now = Utils.TimeStamp; + if (LastUpdate == now) return; + LastUpdate = now; + + if (SwarmModeEnd != 0) + { + if (SwarmModeEnd <= now) + { + SwarmModeEnd = 0; + Utils.SendRPC(CustomRPC.SyncRoleData, WaspPC.PlayerId, SwarmModeEnd); + + if (WaspDiesAfterSwarmEnd.GetBool()) + { + pc.Suicide(); + } + else + { + pc.SyncSettings(); + pc.SetKillCooldown(StingCooldown.GetInt()); + } + } + + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + if (PestControlEnd != 0 && PestControlEnd <= now) + { + PestControlEnd = 0; + Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + pc.MarkDirtySettings(); + } + } + + public override void OnReportDeadBody() + { + foreach (var id in DelayedKills.Keys) + { + var player = id.GetPlayer(); + if (player == null || !player.IsAlive()) continue; + player.Suicide(PlayerState.DeathReason.Stung, WaspPC); + } + + DelayedKills.Clear(); + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (!EvadeKills.GetBool() || EvadedKillThisRound) return true; + + if (IRandom.Instance.Next(2) == 0) + { + SwarmModeEnd = Utils.TimeStamp + SwarmModeDuration.GetInt(); + Utils.SendRPC(CustomRPC.SyncRoleData, WaspPC.PlayerId, SwarmModeEnd); + target.SyncSettings(); + target.SetKillCooldown(0.01f); + } + else + { + PestControlEnd = Utils.TimeStamp + PestControlDuration.GetInt(); + Main.AllPlayerSpeed[target.PlayerId] = PestControlSpeed.GetFloat(); + target.MarkDirtySettings(); + } + + EvadedKillThisRound = true; + return false; + } + + public override bool CanUseKillButton(PlayerControl pc) => !EvadedKillThisRound || SwarmModeEnd != 0; + public override bool CanUseSabotage(PlayerControl pc) => !EvadedKillThisRound; + public override bool CanUseImpostorVentButton(PlayerControl pc) => !EvadedKillThisRound; + + public override void AfterMeetingTasks() + { + EvadedKillThisRound = false; + MeetingKills.Clear(); + } + + public static void OnExile(byte[] exileIds) + { + try + { + foreach (Wasp instance in Instances) + { + foreach (byte id in exileIds) + { + if (id == instance.WaspPC.PlayerId) + instance.MeetingKills.Clear(); + } + } + + List waspDeathList = []; + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + foreach (Wasp instance in Instances) + { + if (Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) continue; + + if (instance.MeetingKills.Contains(pc.PlayerId) && instance.WaspPC != null && instance.WaspPC.IsAlive()) + { + pc.SetRealKiller(instance.WaspPC); + waspDeathList.Add(pc.PlayerId); + } + } + } + + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Stung, [.. waspDeathList]); + } + catch (Exception e) + { + Utils.ThrowException(e); + } + } + + public static string GetStungMark(byte target) + { + return Instances.Any(x => x.MeetingKills.Contains(target)) ? Utils.ColorString(Palette.ImpostorRed, "\u25c0") : string.Empty; + } + + public void ReceiveRPC(MessageReader reader) => SwarmModeEnd = long.Parse(reader.ReadString()); + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != WaspPC.PlayerId || seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud) || meeting || SwarmModeEnd == 0) return string.Empty; + return string.Format(Translator.GetString("Wasp.SwarmModeSuffix"), SwarmModeEnd - Utils.TimeStamp); + } + } +} \ No newline at end of file diff --git a/Roles/Neutral/Vortex.cs b/Roles/Neutral/Vortex.cs new file mode 100644 index 000000000..3127c8cee --- /dev/null +++ b/Roles/Neutral/Vortex.cs @@ -0,0 +1,58 @@ +using AmongUs.GameOptions; + +namespace EHR.Neutral +{ + public class Vortex : RoleBase + { + private const int Id = 645650; + public static bool On; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + private static OptionItem IfTargetCannotBeTeleported; + + public override bool IsEnable => On; + + public override void SetupCustomOption() + { + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Vortex); + KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vortex]) + .SetValueFormat(OptionFormat.Seconds); + CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vortex]); + HasImpostorVision = new BooleanOptionItem(Id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vortex]); + IfTargetCannotBeTeleported = new StringOptionItem(Id + 5, "Vortex.IfTargetCannotBeTeleported", ["Vortex.NoTPMode.Block", "Vortex.NoTPMode.DoWithoutTP"], 0, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vortex]); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); + public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!killer.RpcCheckAndMurder(target, check: true)) return false; + if (!target.TPtoRndVent()) + { + killer.Notify(Translator.GetString("TargetCannotBeTeleported")); + return IfTargetCannotBeTeleported.GetValue() == 1; + } + + LateTask.New(() => target.Suicide(PlayerState.DeathReason.Kill, killer), 0.2f, log: false); + return false; + } + } +} \ No newline at end of file diff --git a/main.cs b/main.cs index 9fce68023..6a5f849a5 100644 --- a/main.cs +++ b/main.cs @@ -475,6 +475,7 @@ public class Main : BasePlugin { CustomRoles.HexMaster, "#ff00ff" }, { CustomRoles.Wraith, "#4B0082" }, { CustomRoles.NSerialKiller, "#233fcc" }, + { CustomRoles.Vortex, "#a83293" }, { CustomRoles.Beehive, "#ffff00" }, { CustomRoles.RouleteGrandeur, "#a88332" }, { CustomRoles.Nonplus, "#09632f" }, @@ -559,6 +560,7 @@ public class Main : BasePlugin { CustomRoles.AntiTP, "#fcba03" }, { CustomRoles.Deadlined, "#ffa500" }, { CustomRoles.Rookie, "#bf671f" }, + { CustomRoles.Trainee, "#4287f5" }, { CustomRoles.Taskcounter, "#ff1919" }, { CustomRoles.Stained, "#e6bf91" }, { CustomRoles.Clumsy, "#b8b8b8" }, @@ -804,6 +806,7 @@ public enum CustomWinner Necromancer = CustomRoles.Necromancer, Wraith = CustomRoles.Wraith, SerialKiller = CustomRoles.NSerialKiller, + Vortex = CustomRoles.Vortex, Beehive = CustomRoles.Beehive, RouleteGrandeur = CustomRoles.RouleteGrandeur, Nonplus = CustomRoles.Nonplus,