diff --git a/CTA/CustomTeamAssigner/CustomRoles.cs b/CTA/CustomTeamAssigner/CustomRoles.cs index 1688796da..180eee4a2 100644 --- a/CTA/CustomTeamAssigner/CustomRoles.cs +++ b/CTA/CustomTeamAssigner/CustomRoles.cs @@ -121,6 +121,7 @@ public enum CustomRoles Trapster, Trickster, Twister, + Tyrant, Undertaker, Vampire, Venerer, @@ -538,6 +539,7 @@ public enum CustomRoles Blocked, Bloodlust, Bloodmoon, // Ghost role + Bluetooth, Busy, Charmed, Circumvent, @@ -552,6 +554,7 @@ public enum CustomRoles Damocles, Deadlined, DeadlyQuota, + Degraded, Disco, Diseased, Disregarded, @@ -605,6 +608,7 @@ public enum CustomRoles Sleep, Phantasm, // Ghost role Physicist, + Priority, Rascal, Reach, Rookie, diff --git a/CustomRoles.cs b/CustomRoles.cs index 1688796da..180eee4a2 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -121,6 +121,7 @@ public enum CustomRoles Trapster, Trickster, Twister, + Tyrant, Undertaker, Vampire, Venerer, @@ -538,6 +539,7 @@ public enum CustomRoles Blocked, Bloodlust, Bloodmoon, // Ghost role + Bluetooth, Busy, Charmed, Circumvent, @@ -552,6 +554,7 @@ public enum CustomRoles Damocles, Deadlined, DeadlyQuota, + Degraded, Disco, Diseased, Disregarded, @@ -605,6 +608,7 @@ public enum CustomRoles Sleep, Phantasm, // Ghost role Physicist, + Priority, Rascal, Reach, Rookie, diff --git a/Main.cs b/Main.cs index d9ba9e422..a6a33e981 100644 --- a/Main.cs +++ b/Main.cs @@ -782,6 +782,9 @@ public class Main : BasePlugin { CustomRoles.Shy, "#9582f5" }, { CustomRoles.Blocked, "#B7A627" }, { CustomRoles.Aide, "#ff1919" }, + { CustomRoles.Bluetooth, "#ff1919" }, + { CustomRoles.Priority, CovenColor }, + { CustomRoles.Degraded,"#b14a0e" }, { CustomRoles.Anchor, "#6B4CE4" }, { CustomRoles.Fragile, "#debe66" }, { CustomRoles.Absorber, "#29f5a0" }, diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 5facbcdd0..bd06fe8dc 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -217,6 +217,7 @@ internal static class CustomRolesHelper CustomRoles.Psychic => CustomRoles.Crewmate, CustomRoles.LazyGuy => CustomRoles.Crewmate, CustomRoles.Twister => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.Tyrant => CustomRoles.Impostor, CustomRoles.SuperStar => CustomRoles.Crewmate, CustomRoles.Survivor => CustomRoles.Engineer, CustomRoles.Anonymous => CustomRoles.Shapeshifter, @@ -856,6 +857,7 @@ internal static class CustomRolesHelper CustomRoles.Devourer or CustomRoles.Camouflager or CustomRoles.Twister or + CustomRoles.Tyrant or CustomRoles.Lurker or CustomRoles.Fakeshifter or CustomRoles.Perplexer; @@ -1282,6 +1284,7 @@ internal static class CustomRolesHelper CustomRoles.Swapster => RoleOptionType.Impostor_Support, CustomRoles.TimeThief => RoleOptionType.Impostor_Support, CustomRoles.Twister => RoleOptionType.Impostor_Support, + CustomRoles.Tyrant => RoleOptionType.Impostor_Support, CustomRoles.Ventriloquist => RoleOptionType.Impostor_Support, CustomRoles.Vindicator => RoleOptionType.Impostor_Support, CustomRoles.YinYanger => RoleOptionType.Impostor_Support, @@ -1932,6 +1935,7 @@ internal static class CustomRolesHelper CustomRoles.Rascal when !pc.IsCrewmate() => false, CustomRoles.LazyGuy when pc.GetCustomRole().IsAdditionRole() => false, CustomRoles.Stealer when pc.Is(CustomRoles.Vindicator) => false, + CustomRoles.Priority when Main.PlayerStates[pc.PlayerId].Role is not CovenBase coven || coven.CanNeverGetNecronomicon => false, CustomRoles.Bloodlust when !pc.GetCustomRole().IsCrewmate() || pc.GetCustomRole().IsTaskBasedCrewmate() || pc.GetCustomRole() is CustomRoles.Medic => false, CustomRoles.Mare when pc.GetCustomRole() is CustomRoles.Inhibitor or CustomRoles.Saboteur or CustomRoles.Swift or CustomRoles.Nemesis or CustomRoles.Sniper or CustomRoles.Fireworker or CustomRoles.Swooper or CustomRoles.Vampire => false, CustomRoles.Torch when pc.GetCustomRole() is CustomRoles.Lighter or CustomRoles.Ignitor or CustomRoles.Investigator or CustomRoles.Eclipse or CustomRoles.Decryptor => false, diff --git a/Modules/GameState.cs b/Modules/GameState.cs index add42edbd..96fb1476f 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -39,7 +39,6 @@ public class PlayerState(byte playerId) Quantization, Overtired, Ashamed, - PissedOff, Dismembered, LossOfHead, Trialed, diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index b0c940e37..66f54e026 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -143,6 +143,7 @@ public static class NameColorManager { CustomRoles.Transporter when target.IsImpostor() => "ffffff", CustomRoles.Silencer when Silencer.ForSilencer.Contains(target.PlayerId) => "000000", + CustomRoles.Tyrant when Tyrant.IsDegraded(target) => "000000", CustomRoles.PlagueBearer when PlagueBearer.IsPlagued(seer.PlayerId, target.PlayerId) => "000000", CustomRoles.Executioner when Executioner.Target.TryGetValue(seer.PlayerId, out byte exeTarget) && exeTarget == target.PlayerId => "000000", CustomRoles.Lawyer when Lawyer.Target.TryGetValue(seer.PlayerId, out byte lawyerTarget) && lawyerTarget == target.PlayerId => "000000", diff --git a/Modules/Utils.cs b/Modules/Utils.cs index e3a5e319c..e80427b05 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -352,7 +352,7 @@ public static class Utils foreach (PlayerControl seer in Main.CachedAllPlayerControls()) { - if (seer.Is(CustomRoles.GM) || seer.Is(CustomRoles.Seer)) + if (seer.Is(CustomRoles.GM) || seer.Is(CustomRoles.Seer) || (seer.Is(CustomRoles.Bluetooth) && target.IsImpostor())) { seer.KillFlash(); continue; @@ -4302,6 +4302,14 @@ public static class Utils addons.ForEach(state.RemoveSubRole); } + break; + case CustomRoles.Tyrant: + foreach (byte id in Tyrant.Degraded[target.PlayerId]) + { + if (!Main.PlayerStates.TryGetValue(id, out var state)) continue; + state.RemoveSubRole(CustomRoles.Degraded); + } + Tyrant.Degraded[target.PlayerId] = []; break; case CustomRoles.Swapper when disconnect: Swapper.SwapTargets = (byte.MaxValue, byte.MaxValue); diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index fd1afc095..1b7faf34b 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -10,6 +10,7 @@ using HarmonyLib; using Il2CppSystem.Collections.Generic; using TMPro; using UnityEngine; +using Priority = HarmonyLib.Priority; // ReSharper disable PossibleLossOfFraction diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 2fba1a0f9..66b61b29a 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -12,6 +12,7 @@ using HarmonyLib; using TMPro; using UnityEngine; using static EHR.Translator; +using Priority = HarmonyLib.Priority; namespace EHR.Patches; diff --git a/Patches/MainMenuManagerPatch.cs b/Patches/MainMenuManagerPatch.cs index ba53d3f7e..635eea2c4 100644 --- a/Patches/MainMenuManagerPatch.cs +++ b/Patches/MainMenuManagerPatch.cs @@ -3,6 +3,7 @@ using BepInEx.Unity.IL2CPP; using HarmonyLib; using TMPro; using UnityEngine; +using Priority = HarmonyLib.Priority; namespace EHR; diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 17f3b1e0e..bebaf3536 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -11,6 +11,7 @@ using Hazel; using TMPro; using UnityEngine; using static EHR.Translator; +using Priority = HarmonyLib.Priority; namespace EHR.Patches; @@ -182,6 +183,7 @@ internal static class CheckForEndVotingPatch bool canVote = !(CheckRole(ps.TargetPlayerId, CustomRoles.Glitch) && !Glitch.CanVote.GetBool()); if (CheckRole(ps.TargetPlayerId, CustomRoles.Shifter) && !Shifter.CanVote.GetBool()) canVote = false; if (ps.VotedFor.GetPlayer() && CheckRole(ps.VotedFor, CustomRoles.Zombie)) canVote = false; + if (CheckRole(ps.TargetPlayerId, CustomRoles.Degraded)) canVote = false; if (Poache.PoachedPlayers != null && Poache.PoachedPlayers.Contains(ps.TargetPlayerId)) canVote = false; if (Silencer.ForSilencer.Contains(ps.TargetPlayerId) && Main.AllAlivePlayerControlsCount > Silencer.MaxPlayersAliveForSilencedToVote.GetInt()) canVote = false; @@ -199,6 +201,9 @@ internal static class CheckForEndVotingPatch case Mayor mayor when !Mayor.MayorHideVote.GetBool(): Loop.Times(Mayor.MayorAdditionalVote.GetInt() + mayor.TaskVotes, _ => AddVote()); break; + case Survivor when Main.AllAlivePlayerControlsCount <= Survivor.ThirdAbility.GetInt(): + Loop.Times(Survivor.AdditionalVote.GetInt(), _ => AddVote()); + break; } if (canVote) AddVote(); @@ -645,6 +650,7 @@ internal static class ExtendedMeetingHud if (Silencer.ForSilencer.Contains(ps.TargetPlayerId) && Main.AllAlivePlayerControlsCount > Silencer.MaxPlayersAliveForSilencedToVote.GetInt()) voteNum = 0; if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Knighted)) voteNum += 1; + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Degraded)) voteNum -= 1; if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Schizophrenic) && Options.DualVotes.GetBool()) voteNum += voteNum; if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Stealer)) voteNum += (int)(Main.EnumeratePlayerControls().Count(x => x.GetRealKiller()?.PlayerId == ps.TargetPlayerId) * Options.VotesPerKill.GetFloat()); diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs index c35e6424b..5f5bd61f0 100644 --- a/Patches/OnGameStartedPatch.cs +++ b/Patches/OnGameStartedPatch.cs @@ -17,6 +17,7 @@ using static EHR.Modules.CustomRoleSelector; using static EHR.Translator; using DateTime = Il2CppSystem.DateTime; using Exception = System.Exception; +using Priority = HarmonyLib.Priority; namespace EHR; diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index fa07d8b7e..d58fe5d8f 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -559,7 +559,7 @@ internal static class CheckMurderPatch killer.Kill(player); Crusader.ForCrusade.Remove(target.PlayerId); target.RpcGuardAndKill(killer); - Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.PissedOff; + Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.Destroyed; return false; } } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index bf003d844..7121f372f 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -123,6 +123,7 @@ "Prefix.Blocked": "Blocked", "Prefix.Bloodlust": "Bloodlust", "Prefix.Bloodmoon": "Bloodmoon", + "Prefix.Bluetooth": "Bluetooth", "Prefix.Busy": "Busy", "Prefix.Circumvent": "Circumvent", "Prefix.Cleansed": "Cleansed", @@ -135,6 +136,7 @@ "Prefix.Damocles": "Damocles", "Prefix.Deadlined": "Deadlined", "Prefix.DeadlyQuota": "Deadly Quota", + "Prefix.Degraded": "Degraded", "Prefix.Disco": "Disco", "Prefix.Diseased": "Diseased", "Prefix.Disregarded": "Disregarded", @@ -182,6 +184,7 @@ "Prefix.Onbound": "Onbound", "Prefix.Phantasm": "Phantasm", "Prefix.Physicist": "Physicist", + "Prefix.Priority": "Priority", "Prefix.Rascal": "Rascal", "Prefix.Reach": "Reach", "Prefix.Reroll": "Reroll", @@ -2291,6 +2294,10 @@ "TwisterInfoLong": "(Impostors):\\nAs the Twister, you can use vanishing/petting to swap the position of all players randomly.\\n\\nThe Twister itself will not swap places with anyone, and players who are in vents cannot be teleported.", "TwisterButtonText": "Twist", "TwisterCooldown": "Twist Cooldown", + "Tyrant": "Tyrant", + "TyrantInfo": "Degrade players", + "TyrantInfoLong": "(Impostors):\\nAs the Tyrant, you can mark a player with your Kill button, and when a meeting is called, the player will get the Degraded addon, which subtracts 1 from their total amount of votes. The Tyrant sees all Degraded players in black names. When the Tyrant dies, all players lose the Degraded addon. Double-click to kill normally.", + "TyrantButtonText": "Degrade", "Undertaker": "Undertaker", "UndertakerInfo": "Take bodies wherever you want to", "UndertakerInfoLong": "(Impostors):\\nAs the Undertaker, you can mark your target using your kill button, then vanish/pet to kill them. However, the person you assassinate will teleport to your location when you shapeshift, so their body will be wherever you are! Use this well to hide bodies!", @@ -3616,6 +3623,9 @@ "Bloodlust": "Bloodlust", "BloodlustInfo": "You can kill", "BloodlustInfoLong": "(Add-ons):\\nYou can kill. You are a neutral killer while keeping your role's ability (as much as host-only limitations allow).\\nIf your role has an ability to be used through the kill button, you can single-click to use your ability and double-click the kill button to kill.", + "Bluetooth": "Bluetooth", + "BluetoothInfo": "You know when your teammate dies", + "BluetoothInfoLong": "(Add-ons):\\nWhen your teammate dies, you will see a red flash paired with a Reactor Sabotage alarm sound.", "Busy": "Busy", "BusyInfo": "You have more tasks", "BusyInfoLong": "(Add-ons):\\nYou have more tasks.", @@ -3687,6 +3697,9 @@ "DeadlyQuota": "Deadly Quota", "DeadlyQuotaInfo": "You don't win if you didn't kill enough", "DeadlyQuotaInfoLong": "(Add-ons):\\nDeadly Quota is an add-on that only impostors can get. The holder of this add-on will need to get a specified number of kills (or more) to be able to win. If their win condition triggers before getting a required amount of kills, the player loses even if they were supposed to win.", + "Degraded": "Degraded", + "DegradedInfo": "You lost one of your votes", + "DegradedInfoLong": "(Add-ons):\\nDegraded players were degraded by the Tyrant. When you vote, your vote count will be subtracted by 1. If the Tyrant who degraded you dies, you lose this add-on.", "Disco": "Disco", "DiscoInfo": "You continuously change colors", "DiscoInfoLong": "(Add-ons):\\nYou change your skin color every few seconds.", @@ -3861,6 +3874,9 @@ "Physicist": "Physicist", "PhysicistInfo": "Grants Portable Vitals Access", "PhysicistInfoLong": "(Add-ons):\\nGrants portable vitals access for crewmate Roles.", + "Priority": "Priority", + "PriorityInfo": "Gets Necronomicon First", + "PriorityInfoLong": "(Add-ons):\\nCan only be assigned to Coven roles who can receive Necronomicon. As the Priority, you will always receive the Necronomicon first, even if there is a Coven Leader. If multiple Priorities are present, one will be picked at random to get the Necronomicon first.", "Rascal": "Rascal", "RascalInfo": "You appear evil in some cases", "RascalInfoLong": "(Add-ons):\\nAs the Rascal, you can die to the Sheriff, and Snitch can find you if Snitch can find madmates.\\n\\nOnly assigned to Crewmates, cannot be assigned by the Merchant.", @@ -3891,7 +3907,7 @@ "SchizophrenicInfoLong": "(Add-ons):\\nNeutrals and Madmates can't become Schizophrenic. The Schizophrenic will be considered as two players in the game. Having at least one player as Schizophrenic won't end the game if there are only them and the Impostor left.", "Seer": "Seer", "SeerInfo": "You see when players die", - "SeerInfoLong": "(Add-ons):\\nWhen a player is killed, you will see a red flash paired with a Reactor Sabotage alarm sound..", + "SeerInfoLong": "(Add-ons):\\nWhen a player is killed, you will see a red flash paired with a Reactor Sabotage alarm sound.", "Shy": "Shy", "ShyInfo": "You don't hear whispers", "ShyInfoLong": "(Add-ons):\\nAs the Shy, you can't hear whispers. You can still send them, but you won't be able to hear them.", @@ -9778,7 +9794,6 @@ "DeathReason.OutOfOxygen": "Out Of Oxygen", "DeathReason.Overtired": "Overtired", "DeathReason.Patrolled": "Patrolled", - "DeathReason.PissedOff": "Destroyed", "DeathReason.Poison": "Poisoned", "DeathReason.Quantization": "Quantization", "DeathReason.RNG": "RNG", diff --git a/Roles/Standard/Addons/Common/Priority.cs b/Roles/Standard/Addons/Common/Priority.cs new file mode 100644 index 000000000..0906fe877 --- /dev/null +++ b/Roles/Standard/Addons/Common/Priority.cs @@ -0,0 +1,11 @@ +namespace EHR.Roles; + +public class Priority : IAddon +{ + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(659550, CustomRoles.Priority, canSetNum: true); + } +} diff --git a/Roles/Standard/Addons/Impostor/Bluetooth.cs b/Roles/Standard/Addons/Impostor/Bluetooth.cs new file mode 100644 index 000000000..f1ae693c9 --- /dev/null +++ b/Roles/Standard/Addons/Impostor/Bluetooth.cs @@ -0,0 +1,11 @@ +namespace EHR.Roles; + +public class Bluetooth : IAddon +{ + public AddonTypes Type => AddonTypes.ImpOnly; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(659500, CustomRoles.Bluetooth, canSetNum: true); + } +} \ No newline at end of file diff --git a/Roles/Standard/Coven/CovenBase.cs b/Roles/Standard/Coven/CovenBase.cs index 4ad668df1..bceb89d6c 100644 --- a/Roles/Standard/Coven/CovenBase.cs +++ b/Roles/Standard/Coven/CovenBase.cs @@ -17,14 +17,22 @@ public abstract class CovenBase : RoleBase public bool HasNecronomicon { get; set; } + public bool CanNeverGetNecronomicon => NecronomiconReceivePriority == NecronomiconReceivePriorities.Never; + public virtual void OnReceiveNecronomicon() { } private static void GiveNecronomicon() { - Dictionary psDict = Main.PlayerStates.Where(x => x.Value.Role is CovenBase { HasNecronomicon: false } coven && coven.NecronomiconReceivePriority != NecronomiconReceivePriorities.Never).ToDictionary(x => x.Key, x => x.Value); + Dictionary psDict = Main.PlayerStates + .Where(x => x.Value.Role is CovenBase { HasNecronomicon: false } coven && !coven.CanNeverGetNecronomicon) + .ToDictionary(x => x.Key, x => x.Value); if (psDict.Count == 0) return; - KeyValuePair receiver = psDict.Shuffle().OrderByDescending(x => !x.Value.IsDead).ThenBy(x => ((CovenBase)x.Value.Role).NecronomiconReceivePriority).First(); + KeyValuePair receiver = psDict.Shuffle() + .OrderByDescending(x => !x.Value.IsDead) + .ThenByDescending(x => x.Value.SubRoles.Contains(CustomRoles.Priority)) + .ThenBy(x => ((CovenBase)x.Value.Role).NecronomiconReceivePriority) + .First(); var covenRole = (CovenBase)receiver.Value.Role; covenRole.HasNecronomicon = true; diff --git a/Roles/Standard/Coven/Goddess.cs b/Roles/Standard/Coven/Goddess.cs index ce3b03900..83e778f02 100644 --- a/Roles/Standard/Coven/Goddess.cs +++ b/Roles/Standard/Coven/Goddess.cs @@ -83,7 +83,8 @@ public class Goddess : CovenBase public override void OnMurder(PlayerControl killer, PlayerControl target) { - Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Stoned; + if (killer.PlayerId != target.PlayerId) + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Stoned; } public void ReceiveRPC(MessageReader reader) diff --git a/Roles/Standard/Crewmate/Killing/Sheriff.cs b/Roles/Standard/Crewmate/Killing/Sheriff.cs index 09ddb927b..4ee8a9255 100644 --- a/Roles/Standard/Crewmate/Killing/Sheriff.cs +++ b/Roles/Standard/Crewmate/Killing/Sheriff.cs @@ -139,7 +139,8 @@ public class Sheriff : RoleBase { killer.RpcRemoveAbilityUse(); Logger.Info($"{killer.GetNameWithRole()} : Number of kills left: {killer.GetAbilityUseLimit()}", "Sheriff"); - Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Shot; + if (killer.PlayerId != target.PlayerId) + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Shot; if (killer.AmOwner && Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS < 25) Achievements.Type.ItsGamblingTime.Complete(); diff --git a/Roles/Standard/Impostor/Support/Tyrant.cs b/Roles/Standard/Impostor/Support/Tyrant.cs new file mode 100644 index 000000000..0cbd234c2 --- /dev/null +++ b/Roles/Standard/Impostor/Support/Tyrant.cs @@ -0,0 +1,129 @@ +using System.Collections.Generic; +using System.Linq; +using EHR.Modules; +using Hazel; + +namespace EHR.Roles; + +public class Tyrant : RoleBase +{ + public static bool On; + public static Dictionary> Degraded = []; + private List ScheduledAssigns = []; + private byte TyrantId; + + private static OptionItem KillCooldown; + private static OptionItem AbilityUseLimit; + public static OptionItem AbilityUseGainWithEachKill; + + public override bool IsEnable => On; + + public override void SetupCustomOption() + { + StartSetup(659450) + .AutoSetupOption(ref KillCooldown, 30f, new FloatValueRule(1, 120, 1), OptionFormat.Seconds) + .AutoSetupOption(ref AbilityUseLimit, 3f, new FloatValueRule(0, 20, 0.05f), OptionFormat.Times) + .AutoSetupOption(ref AbilityUseGainWithEachKill, 0.5f, new FloatValueRule(0f, 5f, 0.25f), OptionFormat.Times); + } + + public override void Init() + { + On = false; + Degraded = []; + } + + public override void Add(byte playerId) + { + On = true; + Degraded[playerId] = []; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat()); + TyrantId = playerId; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.GetAbilityUseLimit() < 1f || IsDegraded(target)) return true; + + return killer.CheckDoubleTrigger(target, () => + { + ScheduledAssigns.Add(target.PlayerId); + Degraded[killer.PlayerId].Add(target.PlayerId); + killer.RpcRemoveAbilityUse(); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); + Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 1, target.PlayerId); + }); + } + + public override void OnReportDeadBody() + { + var tyrant = TyrantId.GetPlayer(); + if (!tyrant || !tyrant.IsAlive()) return; + foreach (byte id in ScheduledAssigns) + { + var pc = id.GetPlayer(); + if (!pc) continue; + + pc.RpcSetCustomRole(CustomRoles.Degraded); + } + + ScheduledAssigns.Clear(); + } + + // this method is used in case target's Degraded addon was removed, e.g. Cleansed + public override void AfterMeetingTasks() + { + List removeList = []; + + foreach (byte id in Degraded[TyrantId]) + { + var pc = id.GetPlayer(); + + if (!pc || !pc.IsAlive()) + continue; + + if (!pc.Is(CustomRoles.Degraded)) + removeList.Add(id); + } + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleData, SendOption.Reliable); + writer.Write(TyrantId); + writer.WritePacked(2); + writer.Write(removeList.Count); + foreach (byte id in removeList) + { + Degraded[TyrantId].Remove(id); + writer.Write(id); + } + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + Degraded[TyrantId].Add(reader.ReadByte()); + break; + case 2: + int length = reader.ReadInt32(); + for (var i = 0; i < length; i++) Degraded[TyrantId].Remove(reader.ReadByte()); + break; + } + } + + public static bool IsDegraded(PlayerControl target) + { + return Degraded.Values.Any(x => x.Contains(target.PlayerId)) || target.Is(CustomRoles.Degraded); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("TyrantButtonText")); + } +} diff --git a/Roles/Standard/Neutral/Benign/Provocateur.cs b/Roles/Standard/Neutral/Benign/Provocateur.cs index f5aefb0a9..3f5990165 100644 --- a/Roles/Standard/Neutral/Benign/Provocateur.cs +++ b/Roles/Standard/Neutral/Benign/Provocateur.cs @@ -60,7 +60,7 @@ internal class Provocateur : RoleBase public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) { - Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.PissedOff; + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Destroyed; killer.Kill(target); Provoked.TryAdd(killer.PlayerId, target.PlayerId); return false;