diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index c285046f65..b1c47c4333 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -633,7 +633,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 30100 last id for roles/add-ons (Next use 30200) + // 30900 last id for roles/add-ons (Next use 31000) // Limit id for roles/add-ons --- "59999" //####################################### diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index b72b636492..01b9017065 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -399,6 +399,7 @@ "Sloth": "Sloth", "Prohibited": "Prohibited", "Eavesdropper": "Eavesdropper", + "HeartBreaker": "Heart Breaker", "BracketAddons": "Add Brackets To Add-ons", "EngineerTOHEInfo": "Use the vents to catch the Impostors", "ScientistTOHEInfo": "Access portable vitals from anywhere", @@ -710,6 +711,7 @@ "SlothInfo": "You're slower", "ProhibitedInfo": "Certain vents are blocked", "EavesdropperInfo": "Listen in on other roles", + "HeartBreakerInfo": "Steal a lover", "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the vents while Comms Sabotaged is inactive.", "ScientistTOHEInfoLong": "(Crewmates):\nAs the Scientist, you can see vitals at any time, showing you who is alive and dead.", "NoisemakerTOHEInfoLong": "(Crewmates):\nAs the Noisemaker, whenever you die, you will make a noise, and a visual indicator of your death appears on the screen so the Crewmates can run to catch the person who killed you red-handed (even if it’s not Red).", @@ -935,6 +937,7 @@ "JinxInfoLong": "(Neutrals):\nAs the Jinx, whenever you get attacked, you jinx them, resulting in them dying to a jinx.\nThis has limited uses.\n\nKill off everyone to win.", "PotionMasterInfoLong": "(Neutrals):\nAs the Potion Master, you have three different potions assigned to three different actions.\n\nSingle click: Reveal role\nDouble click: Kill\nMap: Sabotage\n\nThe reveal potion has a limit.\nWhen you run out, the kill buttons default to killing.", "NecromancerInfoLong": "(Neutrals):\nAs the Necromancer, you win when you're the last one standing.\nAdditionally when someone tries to kill you, you will block the kill, and you will teleport to a random vent. You will have a limited time to kill your killer. If you succeed in doing so, you live. If the time runs out before you kill your killer, you die permanently. If you try to kill someone else other than your killer, you will die.", + "HeartBreakerInfoLong": "(Neutrals):\nAs the Heart Breaker, you can steal a lover. Depending on the settings, you will be able to kill after finding your lover. You may also die if you don't find one", "LastImpostorInfoLong": "(Add-ons):\nThis special effect is given to the last surviving Impostor. It significantly reduces their kill cooldown.", "OverclockedInfoLong": "(Add-ons):\nAs the Overclocked, your kill cooldown is reduced by a percentage.\n\nThis feature is only assigned to roles with a kill button.", "LoversInfoLong": "(Add-ons):\nLovers are a combination of two players. The Lovers win when they are the last ones standing, and their victory is shared. When one of the Lovers wins, the other also wins together. Lovers can see the 「♥」 next to each other's name. If one of the Lovers dies, the other will die in love (may not die in love according to the Host's settings). When one of the Lovers is exiled in the meeting, the other will die and become a dead body that cannot be reported.", @@ -3792,6 +3795,13 @@ "Evader_ChanceNotExiled": "Chance not be exiled", + "HeartBreakerNewLoverText": "You have a new lover", + "HeartBreakerCooldown": "Break Cooldown", + "HeartBreakerKillOtherLover": "Kill Other Lover", + "HeartBreakerTriesMax": "Break Limit", + "HeartBreakerSuicideIfNoLover": "Suicide If No Lover", + "HeartBreakerBreakText" : "Break", + "EavesdropperMsgTitle": "You found a secret", "EavesdropPercentChance": "Chance to eavesdrop" } diff --git a/Resources/roleColor.json b/Resources/roleColor.json index 92684cb179..18cdbc4c2d 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -247,5 +247,6 @@ "Radar": "#1eff1e", "Rebirth": "#f08c22", "Sloth": "#376db8", - "Eavesdropper": "#ffe6bf" + "Eavesdropper": "#ffe6bf", + "HeartBreaker": "#269c84" } diff --git a/Roles/Core/AssignManager/AddonAssign.cs b/Roles/Core/AssignManager/AddonAssign.cs index 1d58f36b2f..b667fbe885 100644 --- a/Roles/Core/AssignManager/AddonAssign.cs +++ b/Roles/Core/AssignManager/AddonAssign.cs @@ -174,6 +174,7 @@ private static void AssignLovers(int RawCount = -1) || pc.Is(CustomRoles.Mini) || pc.Is(CustomRoles.NiceMini) || pc.Is(CustomRoles.EvilMini) + || pc.Is(CustomRoles.HeartBreaker) || (pc.GetCustomRole().IsCrewmate() && !Options.CrewCanBeInLove.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Options.NeutralCanBeInLove.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Options.ImpCanBeInLove.GetBool())) diff --git a/Roles/Core/AssignManager/RoleAssign.cs b/Roles/Core/AssignManager/RoleAssign.cs index db1c51e072..36e9440fd1 100644 --- a/Roles/Core/AssignManager/RoleAssign.cs +++ b/Roles/Core/AssignManager/RoleAssign.cs @@ -746,6 +746,11 @@ public static void StartSelect() if (FinalRolesList.Contains(CustomRoles.Romantic) && FinalRolesList.Contains(CustomRoles.Lovers)) FinalRolesList.Remove(CustomRoles.Lovers); } + if (HeartBreaker.HasEnabled) + { + if(!FinalRolesList.Contains(CustomRoles.Romantic) && !FinalRolesList.Contains(CustomRoles.Lovers) && FinalRolesList.Contains(CustomRoles.HeartBreaker)) + FinalRolesList.Remove(CustomRoles.HeartBreaker); + } // if roles are very few, add vanilla сrewmate roles if (AllPlayers.Count > FinalRolesList.Count) diff --git a/Roles/Neutral/HeartBreaker.cs b/Roles/Neutral/HeartBreaker.cs new file mode 100644 index 0000000000..4b94df4316 --- /dev/null +++ b/Roles/Neutral/HeartBreaker.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AmongUs.GameOptions; +using Rewired; +using TOHE.Roles.Core; +using UnityEngine; +using static TOHE.Options; +using static TOHE.Translator; + +namespace TOHE.Roles.Neutral +{ + internal class HeartBreaker : RoleBase + { + //===========================SETUP================================\\ + private const int Id = 30900; + public override bool IsDesyncRole => true; + public override bool IsExperimental => true; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.HeartBreaker); + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralChaos; + //==================================================================\\ + + private static OptionItem HeartBreakerCooldown; + private static OptionItem HeartBreakerKillOtherLover; + private static OptionItem HeartBreakerTriesMax; + private static OptionItem HeartBreakerSuicideIfNoLover; + + public override void SetupCustomOption() + { + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.HeartBreaker); + HeartBreakerCooldown = FloatOptionItem.Create(Id + 10, "HeartBreakerCooldown", new(0f, 180f, 2.5f), 20f, TabGroup.NeutralRoles, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.HeartBreaker]) + .SetValueFormat(OptionFormat.Seconds); + HeartBreakerTriesMax = IntegerOptionItem.Create(Id + 11, "HeartBreakerTriesMax", new(1, 10, 1), 3, TabGroup.NeutralRoles, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.HeartBreaker]); + HeartBreakerSuicideIfNoLover = BooleanOptionItem.Create(Id + 13, "HeartBreakerSuicideIfNoLover", true, TabGroup.NeutralRoles, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.HeartBreaker]); + HeartBreakerKillOtherLover = BooleanOptionItem.Create(Id + 14, "HeartBreakerKillOtherLover", true, TabGroup.NeutralRoles, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.HeartBreaker]); + } + public override void Add(byte playerId) + { + AbilityLimit = HeartBreakerTriesMax.GetInt(); + } + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = HeartBreakerCooldown.GetFloat(); + + public override bool CanUseKillButton(PlayerControl pc) => IsUseKillButton(pc); + public bool IsUseKillButton(PlayerControl pc) => pc.IsAlive() && AbilityLimit > 0; + public override bool CanUseImpostorVentButton(PlayerControl pc) => false; + public override string GetProgressText(byte playerId, bool comms) + => Utils.ColorString(IsUseKillButton(Utils.GetPlayerById(playerId)) ? Utils.GetRoleColor(CustomRoles.HeartBreaker).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); + public override void SetAbilityButtonText(HudManager hud, byte playerId) + { + if (!HasLover()) hud.KillButton.OverrideText(GetString("HeartBreakerBreakText")); + else hud.KillButton.OverrideText(GetString("TriggerKill")); + } + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + AbilityLimit--; + SendSkillRPC(); + if (HasLover()) return true; + else + { + if (target.GetCustomSubRoles().Contains(CustomRoles.Lovers)) + { + if (HasLover()) return false; + SetAbilityButtonText(HudManager.Instance, killer.PlayerId); + foreach (PlayerControl player in Main.LoversPlayers) + { + if (player.GetCustomSubRoles().Contains(CustomRoles.Lovers) && player != target && killer != target) + { + player.GetCustomSubRoles().Remove(CustomRoles.Lovers); + Main.LoversPlayers.Remove(player); + RPC.SyncLoversPlayers(); + if (HeartBreakerKillOtherLover.GetBool()) + { + player.SetDeathReason(PlayerState.DeathReason.FollowingSuicide); + player.RpcMurderPlayer(player); + player.SetRealKiller(killer); + } + killer.RpcSetCustomRole(CustomRoles.Lovers); + Main.LoversPlayers.Add(killer); + RPC.SyncLoversPlayers(); + // temp workaround i guess + Utils.DoNotifyRoles(killer, target); + Utils.DoNotifyRoles(target, killer); + target.Notify(GetString("HeartBreakerNewLoverText")); + if (!DisableShieldAnimations.GetBool()) killer.RpcGuardAndKill(target); + target.RpcGuardAndKill(killer); + target.RpcGuardAndKill(target); + break; + } + } + } + else if (target.Is(CustomRoles.Romantic) && !HasLover()) + { + Romantic.BetPlayer.Remove(target.PlayerId); + Romantic.BetPlayer.Add(target.PlayerId, killer.PlayerId); + Romantic romantic = (Romantic)target.GetRoleClass(); + romantic.SendRPC(target.PlayerId); + target.RpcSetCustomRole(CustomRoles.RuthlessRomantic); + Utils.DoNotifyRoles(killer, target); + Utils.DoNotifyRoles(target, killer); + target.Notify(GetString("HeartBreakerNewLoverText")); + if (!DisableShieldAnimations.GetBool()) killer.RpcGuardAndKill(target); + target.RpcGuardAndKill(killer); + target.RpcGuardAndKill(target); + } + else if (HeartBreakerSuicideIfNoLover.GetBool() && AbilityLimit < 1 && !HasLover()) + { + killer.SetDeathReason(PlayerState.DeathReason.Suicide); + killer.RpcMurderPlayer(killer); + } + killer.SetKillCooldown(); + return false; + } + } + public bool HasLover(PlayerControl player = null) + { + if (player == null) player = _Player; + if (Main.LoversPlayers.Contains(player)) + return true; + else + for (byte i = 0; i < Romantic.BetPlayer.Count; i++) + if (Romantic.BetPlayer[i] == player.PlayerId) return true; + return false; + } + } +} diff --git a/Roles/Neutral/Romantic.cs b/Roles/Neutral/Romantic.cs index 360c75d568..ada1852fd6 100644 --- a/Roles/Neutral/Romantic.cs +++ b/Roles/Neutral/Romantic.cs @@ -77,7 +77,7 @@ public override void Remove(byte playerId) CustomRoleManager.CheckDeadBodyOthers.Remove(OthersAfterPlayerDeathTask); } - private void SendRPC(byte playerId) + internal void SendRPC(byte playerId) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); writer.WriteNetObject(_Player); diff --git a/main.cs b/main.cs index 7d12229772..aac6dade1e 100644 --- a/main.cs +++ b/main.cs @@ -805,6 +805,7 @@ public enum CustomRoles Glitch, God, Hater, + HeartBreaker, HexMaster, Huntsman, Imitator,