From 99fb35725f2a9db6bcf0f1a4eeb7004b6edbae0a Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Thu, 16 Apr 2026 10:27:02 +0200 Subject: [PATCH] Add Tar --- CustomRoles.cs | 1 + Main.cs | 1 + Modules/CustomRolesHelper.cs | 3 + Modules/ExtendedPlayerControl.cs | 19 +--- Modules/Extensions/CountdownTimer.cs | 2 +- Resources/Lang/en_US.jsonc | 3 + Roles/Standard/Crewmate/Miscellaneous/Tar.cs | 99 ++++++++++++++++++++ 7 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 Roles/Standard/Crewmate/Miscellaneous/Tar.cs diff --git a/CustomRoles.cs b/CustomRoles.cs index 2182b8bdd..0fe5d9d62 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -270,6 +270,7 @@ public enum CustomRoles Spy, SuperStar, Swapper, + Tar, TaskManager, Telekinetic, Tether, diff --git a/Main.cs b/Main.cs index 8369af047..32abb3202 100644 --- a/Main.cs +++ b/Main.cs @@ -433,6 +433,7 @@ public class Main : BasePlugin { CustomRoles.Vacuum, "#E44CD6" }, { CustomRoles.Carrier, "#5DE2E7" }, { CustomRoles.Transmitter, "#c9a11e" }, + { CustomRoles.Tar, "#8C796B" }, { CustomRoles.Sensor, "#a3f7ff" }, { CustomRoles.Doorjammer, "#FFECA1" }, { CustomRoles.Captain, "#53B3EF" }, diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index a6a8ff2f2..0ac320955 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -40,6 +40,7 @@ internal static class CustomRolesHelper CustomRoles.Doorjammer, CustomRoles.Carrier, CustomRoles.Empress, + CustomRoles.Tar, // Add-ons CustomRoles.Energetic, @@ -245,6 +246,7 @@ internal static class CustomRolesHelper CustomRoles.Farmer => CustomRoles.Crewmate, CustomRoles.Captain => CustomRoles.Crewmate, CustomRoles.Transmitter => CustomRoles.Crewmate, + CustomRoles.Tar => CustomRoles.Crewmate, CustomRoles.Sensor => CustomRoles.Crewmate, CustomRoles.Doorjammer => CustomRoles.Crewmate, CustomRoles.Tree => CustomRoles.Crewmate, @@ -1342,6 +1344,7 @@ internal static class CustomRolesHelper CustomRoles.Ricochet => RoleOptionType.Crewmate_Miscellaneous, CustomRoles.Safeguard => RoleOptionType.Crewmate_Miscellaneous, CustomRoles.SuperStar => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Tar => RoleOptionType.Crewmate_Miscellaneous, CustomRoles.Tether => RoleOptionType.Crewmate_Miscellaneous, CustomRoles.Transmitter => RoleOptionType.Crewmate_Miscellaneous, CustomRoles.Tracefinder => RoleOptionType.Crewmate_Miscellaneous, diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index b04959946..1110b1140 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -92,6 +92,8 @@ internal static class ExtendedPlayerControl return (player.CanUseImpostorVentButton() || player.GetRoleTypes() == RoleTypes.Engineer) && Main.PlayerStates.Values.All(x => x.Role.CanUseVent(player, ventId)); } + // Next 5: From MoreGamemodes by Rabek009 (https://github.com/Rabek009/MoreGamemodes) + public Vent GetClosestVent() { if (ShipStatus.Instance?.AllVents == null) return null; @@ -324,6 +326,7 @@ internal static class ExtendedPlayerControl Main.PlayerStates[player.PlayerId].SetDead(); } + // Saves some RPC calls for vanilla servers to make innersloth's rate limit happy public void ReviveFromTemporaryExile() // Only used in game modes { if (!AmongUsClient.Instance.AmHost) return; @@ -1441,6 +1444,8 @@ internal static class ExtendedPlayerControl return player.transform.position; } + // Next 5: https://github.com/Rabek009/MoreGamemodes/blob/master/Modules/ExtendedPlayerControl.cs + public void MakeInvisible() { player.invisibilityAlpha = player.AmOwner ? 0.5f : PlayerControl.LocalPlayer.Data.Role.IsDead ? 0.5f : 0f; @@ -2339,20 +2344,8 @@ internal static class ExtendedPlayerControl } } - // Next 2: https://github.com/Rabek009/MoreGamemodes/blob/master/Modules/ExtendedPlayerControl.cs - - // Next 2 from https://github.com/Rabek009/MoreGamemodes/blob/master/Roles/Impostor/Concealing/Droner.cs - - // From: https://github.com/Rabek009/MoreGamemodes/blob/master/Modules/ExtendedPlayerControl.cs - coded by Rabek009 - - // Saves some RPC calls for vanilla servers to make innersloth's rate limit happy - // If you use vanilla RpcSetRole, it will block further SetRole calls until the next game starts. - // https://github.com/Ultradragon005/TownofHost-Enhanced/blob/ea5f1e8ea87e6c19466231c305d6d36d511d5b2d/Modules/ExtendedPlayerControl.cs - - // https://github.com/Ultradragon005/TownofHost-Enhanced/blob/ea5f1e8ea87e6c19466231c305d6d36d511d5b2d/Modules/ExtendedPlayerControl.cs - /* public static void RpcShowScanAnimationDesync(this PlayerControl target, PlayerControl seer, bool on) { @@ -2387,8 +2380,6 @@ internal static class ExtendedPlayerControl } */ - // Next 6: From MoreGamemodes by Rabek009 (https://github.com/Rabek009/MoreGamemodes) - extension(byte playerId) { public float GetAbilityUseLimit() diff --git a/Modules/Extensions/CountdownTimer.cs b/Modules/Extensions/CountdownTimer.cs index bfb776262..c69287d5a 100644 --- a/Modules/Extensions/CountdownTimer.cs +++ b/Modules/Extensions/CountdownTimer.cs @@ -112,7 +112,7 @@ public sealed class CountdownTimer : IDisposable return _cancelOnGameEnd && (GameStates.IsEnded || GameStates.IsLobby || !GameStates.InGame); } - private void Complete() + public void Complete() { if (_completed) return; diff --git a/Resources/Lang/en_US.jsonc b/Resources/Lang/en_US.jsonc index d026d2863..248ad135a 100644 --- a/Resources/Lang/en_US.jsonc +++ b/Resources/Lang/en_US.jsonc @@ -1445,6 +1445,9 @@ "SwapperManipulatedEjection": "Thanks to the {0}", "SwapperMax": "Initial Number of Swaps", "SwapperTrialMax": "Ability use limit reached", + "Tar": "Tar", + "TarInfo": "Avoid Kills", + "TarInfoLong": "(Add-ons):\\nAs the Tar, you can pet to activate your ability. This will freeze and blind you, and set your skin color to black for a set time. If someone tries to kill you during this time, it gets blocked, the killer gets teleported away, and your ability ends instantly.", "TaskManager": "Task Manager", "TaskManagerInfo": "See the total tasks completed in real-time", "TaskManagerInfoLong": "(Crewmates):\\nYou see the total number of tasks completed (by everyone all together) next to your role name, which updates in real-time.", diff --git a/Roles/Standard/Crewmate/Miscellaneous/Tar.cs b/Roles/Standard/Crewmate/Miscellaneous/Tar.cs new file mode 100644 index 000000000..3bc7af15f --- /dev/null +++ b/Roles/Standard/Crewmate/Miscellaneous/Tar.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Linq; +using AmongUs.GameOptions; +using EHR.Modules.Extensions; + +namespace EHR.Roles; + +public class Tar : RoleBase +{ + public static bool On; + + public override bool IsEnable => On; + + private static OptionItem AbilityCooldown; + private static OptionItem AbilityDuration; + private static OptionItem AbilityUseLimit; + private static OptionItem AbilityUseGainWithEachTaskCompleted; + private static OptionItem AbilityChargesWhenFinishedTasks; + + private CountdownTimer Timer; + private byte TarId; + private int ColorId; + + public override void SetupCustomOption() + { + StartSetup(658100) + .AutoSetupOption(ref AbilityCooldown, 15, new IntegerValueRule(0, 120, 1), OptionFormat.Seconds) + .AutoSetupOption(ref AbilityDuration, 5, new IntegerValueRule(0, 60, 1), OptionFormat.Seconds) + .AutoSetupOption(ref AbilityUseLimit, 1f, new FloatValueRule(0, 20, 0.05f), OptionFormat.Times) + .AutoSetupOption(ref AbilityUseGainWithEachTaskCompleted, 0.5f, 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; + Timer = null; + TarId = playerId; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat()); + ColorId = Camouflage.PlayerSkins.GetValueOrDefault(playerId)?.ColorId ?? 0; + } + + public override void OnPet(PlayerControl pc) + { + if (pc.GetAbilityUseLimit() < 1) return; + pc.RpcRemoveAbilityUse(); + + Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; + Main.PlayerStates[pc.PlayerId].IsBlackOut = true; + pc.RpcSetColor(6); + pc.MarkDirtySettings(); + + Timer = new CountdownTimer(AbilityDuration.GetFloat(), () => + { + Timer = null; + if (!pc || !pc.IsAlive()) return; + Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + Main.PlayerStates[pc.PlayerId].IsBlackOut = false; + pc.RpcSetColor((byte)ColorId); + pc.MarkDirtySettings(); + }, cancelOnMeeting: false, onCanceled: () => + { + Timer = null; + Main.AllPlayerSpeed[TarId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + Main.PlayerStates[TarId].IsBlackOut = false; + }); + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (Timer != null) + { + Vector2 pos = target.Pos(); + killer.TP(Main.LIMap ? ShipStatus.Instance.AllVents.MaxBy(x => Vector2.Distance(pos, x.transform.position)).transform.position : RandomSpawn.SpawnMap.GetSpawnMap().Positions.Values.MaxBy(x => Vector2.Distance(pos, x))); + Timer.Complete(); + Timer = null; + return false; + } + + return true; + } + + public override void OnReportDeadBody() + { + if (Timer != null) + { + Timer.Dispose(); + Timer = null; + Main.AllPlayerSpeed[TarId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + Main.PlayerStates[TarId].IsBlackOut = false; + TarId.GetPlayer()?.RpcSetColor((byte)ColorId); + } + } +} \ No newline at end of file