From 095f94bb384cfecabe51bdd38cf2b635ef09b7f8 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 9 Sep 2024 21:57:43 -0400 Subject: [PATCH 001/101] work from the past 3 days --- Modules/CustomRolesHelper.cs | 38 +++---- Modules/ExtendedPlayerControl.cs | 2 + Modules/OptionHolder.cs | 58 ++++++++++ Modules/OptionItem/OptionItem.cs | 1 + Modules/RPC.cs | 1 + Patches/GameSettingMenuPatch.cs | 2 + Patches/IntroPatch.cs | 28 +++++ Patches/MeetingHudPatch.cs | 1 + Patches/PlayerControlPatch.cs | 1 + Resources/Lang/en_US.json | 40 +++++-- Resources/roleColor.json | 1 + Roles/Core/AssignManager/RoleAssign.cs | 128 +++++++++++++++++++++++ Roles/Coven/CovenLeader.cs | 81 ++++++++++++++ Roles/Coven/CovenManager.cs | 29 +++++ Roles/{Neutral => Coven}/HexMaster.cs | 15 +-- Roles/{Neutral => Coven}/Jinx.cs | 19 ++-- Roles/{Neutral => Coven}/Medusa.cs | 17 +-- Roles/{Neutral => Coven}/Necromancer.cs | 19 ++-- Roles/{Neutral => Coven}/Poisoner.cs | 17 +-- Roles/{Neutral => Coven}/PotionMaster.cs | 17 +-- main.cs | 24 +++-- 21 files changed, 458 insertions(+), 81 deletions(-) create mode 100644 Roles/Coven/CovenLeader.cs create mode 100644 Roles/Coven/CovenManager.cs rename Roles/{Neutral => Coven}/HexMaster.cs (94%) rename Roles/{Neutral => Coven}/Jinx.cs (80%) rename Roles/{Neutral => Coven}/Medusa.cs (80%) rename Roles/{Neutral => Coven}/Necromancer.cs (84%) rename Roles/{Neutral => Coven}/Poisoner.cs (87%) rename Roles/{Neutral => Coven}/PotionMaster.cs (86%) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 81e323557..fdf8af3f6 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -287,7 +287,14 @@ CustomRoles.Impostor or CustomRoles.Shapeshifter or CustomRoles.Phantom; } - + public static bool IsCoven(this CustomRoles role) + { + return role.GetStaticRoleClass().ThisRoleType is + Custom_RoleType.CovenKilling or + Custom_RoleType.CovenPower or + Custom_RoleType.CovenTrickery or + Custom_RoleType.CovenUtility; + } public static bool IsAbleToBeSidekicked(this CustomRoles role) => role.GetDYRole() == RoleTypes.Impostor && !role.IsImpostor() && !role.IsRecruitingRole(); @@ -1054,9 +1061,9 @@ public static RoleTypes GetRoleTypes(this CustomRoles role) /// public static bool IsImpostorTeam(this CustomRoles role) => role.IsImpostor() || role == CustomRoles.Madmate; /// - /// Role Is Not Impostor nor Madmate Nor Neutral. + /// Role Is Not Impostor nor Madmate Nor Neutral nor Coven. /// - public static bool IsCrewmate(this CustomRoles role) => !role.IsImpostor() && !role.IsNeutral() && !role.IsMadmate(); + public static bool IsCrewmate(this CustomRoles role) => !role.IsImpostor() && !role.IsNeutral() && !role.IsMadmate() && !role.IsCoven(); /// /// Role is Rascal or Madmate and not trickster. /// @@ -1091,6 +1098,7 @@ public static Custom_Team GetCustomRoleTeam(this CustomRoles role) Custom_Team team = Custom_Team.Crewmate; if (role.IsImpostor()) team = Custom_Team.Impostor; if (role.IsNeutral()) team = Custom_Team.Neutral; + if (role.IsCoven()) team = Custom_Team.Coven; if (role.IsAdditionRole()) team = Custom_Team.Addon; return team; } @@ -1164,29 +1172,25 @@ public static CountTypes GetCountTypes(this CustomRoles role) CustomRoles.Demon => CountTypes.Demon, CustomRoles.BloodKnight => CountTypes.BloodKnight, CustomRoles.Cultist => CountTypes.Cultist, - CustomRoles.HexMaster => CountTypes.HexMaster, - CustomRoles.Necromancer => CountTypes.Necromancer, CustomRoles.Stalker => !Stalker.SnatchesWin.GetBool() ? CountTypes.Stalker : CountTypes.Crew, CustomRoles.Arsonist => Arsonist.CanIgniteAnytime() ? CountTypes.Arsonist : CountTypes.Crew, CustomRoles.Shroud => CountTypes.Shroud, CustomRoles.Werewolf => CountTypes.Werewolf, CustomRoles.Wraith => CountTypes.Wraith, var r when r.IsNA() => CountTypes.Apocalypse, + var r when r.IsCoven() => CountTypes.Coven, CustomRoles.Agitater => CountTypes.Agitater, CustomRoles.Parasite => CountTypes.Impostor, CustomRoles.SerialKiller => CountTypes.SerialKiller, CustomRoles.Quizmaster => CountTypes.Quizmaster, CustomRoles.Juggernaut => CountTypes.Juggernaut, - CustomRoles.Jinx => CountTypes.Jinx, CustomRoles.Infectious or CustomRoles.Infected => CountTypes.Infectious, CustomRoles.Crewpostor => CountTypes.Impostor, CustomRoles.Pyromaniac => CountTypes.Pyromaniac, CustomRoles.PlagueDoctor => CountTypes.PlagueDoctor, CustomRoles.Virus => CountTypes.Virus, - CustomRoles.PotionMaster => CountTypes.PotionMaster, CustomRoles.Pickpocket => CountTypes.Pickpocket, CustomRoles.Traitor => CountTypes.Traitor, - CustomRoles.Medusa => CountTypes.Medusa, CustomRoles.Refugee => CountTypes.Impostor, CustomRoles.Huntsman => CountTypes.Huntsman, CustomRoles.Glitch => CountTypes.Glitch, @@ -1272,8 +1276,6 @@ var r when r.IsNA() => CountTypes.Apocalypse, CountTypes.Demon => CustomRoles.Demon, CountTypes.BloodKnight => CustomRoles.BloodKnight, CountTypes.Cultist => CustomRoles.Cultist, - CountTypes.HexMaster => CustomRoles.HexMaster, - CountTypes.Necromancer => CustomRoles.Necromancer, CountTypes.Shroud => CustomRoles.Shroud, CountTypes.Werewolf => CustomRoles.Werewolf, CountTypes.Wraith => CustomRoles.Wraith, @@ -1282,14 +1284,11 @@ var r when r.IsNA() => CountTypes.Apocalypse, CountTypes.SerialKiller => CustomRoles.SerialKiller, CountTypes.Quizmaster => CustomRoles.Quizmaster, CountTypes.Juggernaut => CustomRoles.Juggernaut, - CountTypes.Jinx => CustomRoles.Jinx, CountTypes.Infectious => CustomRoles.Infectious, CountTypes.Pyromaniac => CustomRoles.Pyromaniac, CountTypes.Virus => CustomRoles.Virus, - CountTypes.PotionMaster => CustomRoles.PotionMaster, CountTypes.Pickpocket => CustomRoles.Pickpocket, CountTypes.Traitor => CustomRoles.Traitor, - CountTypes.Medusa => CustomRoles.Medusa, CountTypes.Huntsman => CustomRoles.Huntsman, CountTypes.Glitch => CustomRoles.Glitch, CountTypes.Stalker => CustomRoles.Stalker, @@ -1305,6 +1304,7 @@ public enum Custom_Team Crewmate, Impostor, Neutral, + Coven, Addon, } public enum Custom_RoleType @@ -1335,6 +1335,12 @@ public enum Custom_RoleType NeutralKilling, NeutralApocalypse, + // Coven + CovenPower, + CovenKilling, + CovenTrickery, + CovenUtility, + None } public enum CountTypes @@ -1353,18 +1359,14 @@ public enum CountTypes Poisoner, Charmed, Cultist, - HexMaster, Wraith, SerialKiller, Juggernaut, Infectious, Virus, Stalker, - Jinx, - PotionMaster, Pickpocket, Traitor, - Medusa, Spiritcaller, Quizmaster, Apocalypse, @@ -1376,5 +1378,5 @@ public enum CountTypes Werewolf, Agitater, RuthlessRomantic, - Necromancer + Coven } diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 5b8fc782a..84e5d03a7 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -10,6 +10,7 @@ using TOHE.Roles.Crewmate; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; +using TOHE.Roles.Coven; using UnityEngine; using static TOHE.Translator; @@ -886,6 +887,7 @@ public static List GetPlayersInAbilityRangeSorted(this PlayerCont public static bool IsNeutralApocalypse(this PlayerControl player) => player.GetCustomRole().IsNA(); public static bool IsTransformedNeutralApocalypse(this PlayerControl player) => player.GetCustomRole().IsTNA(); public static bool IsNonNeutralKiller(this PlayerControl player) => player.GetCustomRole().IsNonNK(); + public static bool IsPlayerCoven(this PlayerControl player) => player.GetCustomRole().IsCoven(); public static bool KnowDeathReason(this PlayerControl seer, PlayerControl target) => (Options.EveryoneCanSeeDeathReason.GetBool() diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 640a7e663..3c931268b 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -473,10 +473,12 @@ private enum RatesZeroOne public static OptionItem NeutralKillersCanGuess; public static OptionItem NeutralApocalypseCanGuess; public static OptionItem PassiveNeutralsCanGuess; + public static OptionItem CovenCanGuess; public static OptionItem CanGuessAddons; public static OptionItem ImpCanGuessImp; public static OptionItem CrewCanGuessCrew; public static OptionItem ApocCanGuessApoc; + public static OptionItem CovenCanGuessCoven; public static OptionItem HideGuesserCommands; public static OptionItem ShowOnlyEnabledRolesInGuesserUI; @@ -502,6 +504,10 @@ private enum RatesZeroOne public static OptionItem NeutralRoleWinTogether; public static OptionItem NeutralWinTogether; + // Coven + public static OptionItem CovenRolesMinPlayer; + public static OptionItem CovenRolesMaxPlayer; + // Add-on public static OptionItem NameDisplayAddons; public static OptionItem AddBracketsToAddons; @@ -705,6 +711,14 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetParent(NeutralRoleWinTogether) .SetGameMode(CustomGameMode.Standard); + CovenRolesMinPlayer = IntegerOptionItem.Create(60025, "CovenRolesMinPlayer", new(0, 15, 1), 0, TabGroup.CovenRoles, false) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetValueFormat(OptionFormat.Players); + CovenRolesMaxPlayer = IntegerOptionItem.Create(60026, "CovenRolesMaxPlayer", new(0, 15, 1), 0, TabGroup.CovenRoles, false) + .SetGameMode(CustomGameMode.Standard) + .SetValueFormat(OptionFormat.Players); + NameDisplayAddons = BooleanOptionItem.Create(60019, "NameDisplayAddons", true, TabGroup.Addons, false) .SetGameMode(CustomGameMode.Standard) .SetHeader(true); @@ -920,6 +934,45 @@ private static System.Collections.IEnumerator CoLoadOptions() yield return null; + #region Coven Settings + if (CustomRoleManager.RoleClass.Where(x => x.Key.IsCoven()).Any(r => r.Value.IsExperimental)) + { + TextOptionItem.Create(10000023, "Experimental.Roles", TabGroup.NeutralRoles) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(141, 70, 49, byte.MaxValue)); + + CustomRoleManager.GetExperimentalOptions(Custom_Team.Coven).ForEach(r => r.SetupCustomOption()); + + + } + + TextOptionItem.Create(10000016, "RoleType.CovenPower", TabGroup.CovenRoles) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(172, 66, 242, byte.MaxValue)); + + CustomRoleManager.GetNormalOptions(Custom_RoleType.CovenPower).ForEach(r => r.SetupCustomOption()); + + TextOptionItem.Create(10000017, "RoleType.CovenKilling", TabGroup.CovenRoles) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(172, 66, 242, byte.MaxValue)); + + CustomRoleManager.GetNormalOptions(Custom_RoleType.CovenKilling).ForEach(r => r.SetupCustomOption()); + + TextOptionItem.Create(10000018, "RoleType.CovenTrickery", TabGroup.CovenRoles) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(172, 66, 242, byte.MaxValue)); + + CustomRoleManager.GetNormalOptions(Custom_RoleType.CovenTrickery).ForEach(r => r.SetupCustomOption()); + + TextOptionItem.Create(10000019, "RoleType.CovenUtility", TabGroup.CovenRoles) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(172, 66, 242, byte.MaxValue)); + + CustomRoleManager.GetNormalOptions(Custom_RoleType.CovenUtility).ForEach(r => r.SetupCustomOption()); + #endregion + + yield return null; + #region Add-Ons Settings int titleId = 100100; @@ -1673,6 +1726,8 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetParent(GuesserMode); PassiveNeutralsCanGuess = BooleanOptionItem.Create(60684, "PassiveNeutralsCanGuess", false, TabGroup.ModifierSettings, false) .SetParent(GuesserMode); + CovenCanGuess = BooleanOptionItem.Create(60693, "CovenCanGuess", false, TabGroup.ModifierSettings, false) + .SetParent(GuesserMode); CanGuessAddons = BooleanOptionItem.Create(60685, "CanGuessAddons", true, TabGroup.ModifierSettings, false) .SetParent(GuesserMode); CrewCanGuessCrew = BooleanOptionItem.Create(60686, "CrewCanGuessCrew", true, TabGroup.ModifierSettings, false) @@ -1684,6 +1739,9 @@ private static System.Collections.IEnumerator CoLoadOptions() ApocCanGuessApoc = BooleanOptionItem.Create(60691, "ApocCanGuessApoc", false, TabGroup.ModifierSettings, false) .SetHidden(true) .SetParent(GuesserMode); + CovenCanGuessCoven = BooleanOptionItem.Create(60692, "CovenCanGuessCoven", false, TabGroup.ModifierSettings, false) + .SetHidden(true) + .SetParent(GuesserMode); HideGuesserCommands = BooleanOptionItem.Create(60688, "GuesserTryHideMsg", true, TabGroup.ModifierSettings, false) .SetParent(GuesserMode) .SetColor(Color.green); diff --git a/Modules/OptionItem/OptionItem.cs b/Modules/OptionItem/OptionItem.cs index ff38d57a9..98356b5f0 100644 --- a/Modules/OptionItem/OptionItem.cs +++ b/Modules/OptionItem/OptionItem.cs @@ -306,6 +306,7 @@ public enum TabGroup ImpostorRoles, CrewmateRoles, NeutralRoles, + CovenRoles, Addons } public enum OptionFormat diff --git a/Modules/RPC.cs b/Modules/RPC.cs index e4a225e8f..aacfc5536 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -11,6 +11,7 @@ using TOHE.Roles.Crewmate; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; +using TOHE.Roles.Coven; using static TOHE.Translator; namespace TOHE; diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index cb7dce266..924c67037 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -53,6 +53,7 @@ public static void StartPostfix(GameSettingMenu __instance) TabGroup.ImpostorRoles => "#f74631", TabGroup.CrewmateRoles => "#8cffff", TabGroup.NeutralRoles => "#7f8c8d", + TabGroup.CovenRoles => "#ac42f2", TabGroup.Addons => "#ff9ace", _ => "#ffffff", }; @@ -355,6 +356,7 @@ public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [ case TabGroup.ImpostorRoles: case TabGroup.CrewmateRoles: case TabGroup.NeutralRoles: + case TabGroup.CovenRoles: case TabGroup.Addons: __instance.MenuDescriptionText.text = GetString("TabMenuDescription_Roles&AddOns"); break; diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 66ca0529f..dd5a81d03 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -272,6 +272,17 @@ public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections __instance.overlayHandle.color = Palette.ImpostorRed; return false; } + else if (PlayerControl.LocalPlayer.IsPlayerCoven()) + { + var covTeam = new Il2CppSystem.Collections.Generic.List(); + covTeam.Add(PlayerControl.LocalPlayer); + foreach (var pc in Main.AllAlivePlayerControls) + { + if (pc.IsPlayerCoven() && pc != PlayerControl.LocalPlayer) + covTeam.Add(pc); + } + teamToDisplay = covTeam; + } else if (PlayerControl.LocalPlayer.IsNeutralApocalypse()) { var apocTeam = new Il2CppSystem.Collections.Generic.List(); @@ -343,6 +354,13 @@ public static void Postfix(IntroCutscene __instance) __instance.ImpostorText.gameObject.SetActive(true); __instance.ImpostorText.text = GetString("SubText.Neutral"); break; + case Custom_Team.Coven: + __instance.TeamTitle.text = GetString("TeamCoven"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(172, 66, 242, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Phantom); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Coven"); + break; } switch (role) { @@ -509,6 +527,16 @@ public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections return false; } + if (role.IsCoven()) + { + yourTeam = new(); + yourTeam.Add(PlayerControl.LocalPlayer); + foreach (var pc in Main.AllPlayerControls.Where(x => !x.AmOwner)) yourTeam.Add(pc); + __instance.BeginCrewmate(yourTeam); + __instance.overlayHandle.color = new Color32(172, 66, 242, byte.MaxValue); + return false; + } + BeginCrewmatePatch.Prefix(__instance, ref yourTeam); return true; } diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index ab32a4c4d..21d58381a 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -8,6 +8,7 @@ using TOHE.Roles.Crewmate; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; +using TOHE.Roles.Coven; using UnityEngine; using static TOHE.Utils; using static TOHE.Translator; diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index a7c7f5450..cc28d54ec 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -15,6 +15,7 @@ using TOHE.Roles.Double; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; +using TOHE.Roles.Coven; using TOHE.Roles.Core; using static TOHE.Translator; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index d4f680c14..e52e9d571 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -22,22 +22,26 @@ "SubText.Impostor": "Sabotage and kill everyone", "SubText.Neutral": "Work alone to achieve your victory", "SubText.Apocalypse": "Become unstoppable with your team", + "SubText.Coven": "Bewitch and kill everyone", "SubText.Madmate": "Help the Impostors", "TypeImpostor": "Impostors", "TypeCrewmate": "Crewmates", "TypeNeutral": "Neutrals", + "TypeCoven": "Coven", "TypeAddon": "Add-ons", "GuesserMode": "Guesser Mode", "TeamImpostor": "Impostor", "TeamNeutral": "Neutral", "TeamCrewmate": "Crewmate", + "TeamCoven": "Coven", "TeamMadmate": "Madmate", "YouAreCrewmate": "You are a Crewmate", "YouAreImpostor": "You are an Impostor", "YouAreNeutral": "You are a Neutral", + "YouAreCoven": "You are a Coven member", "YouAreMadmate": "You are a Madmate", @@ -67,6 +71,7 @@ "ImpostorsCanGuess": "Impostors can guess", "NeutralKillersCanGuess": "Neutral Killers can guess", "NeutralApocalypseCanGuess": "Neutral Apocalypse can guess", + "CovenCanGuess": "Coven can guess", "PassiveNeutralsCanGuess": "Passive Neutrals can guess", "CanGuessAddons": "Can Guess Add-ons", @@ -74,6 +79,7 @@ "CrewCanGuessCrew": "Crewmates Can Guess Crewmate Roles", "ImpCanGuessImp": "Impostors Can Guess Impostor Roles", "ApocCanGuessApoc": "Neutral Apocalypse Can Guess Neutral Apocalypse Roles", + "CovenCanGuessCoven": "Coven Can Guess Coven Roles", "GuessImmune": "Sorry, but target is immune to being guessed!", @@ -292,7 +298,6 @@ "Vulture": "Vulture", "Taskinator": "Taskinator", "Benefactor": "Benefactor", - "Medusa": "Medusa", "Spiritcaller": "Spiritcaller", "Amnesiac": "Amnesiac", "Imitator": "Imitator", @@ -310,12 +315,14 @@ "Romantic": "Romantic", "VengefulRomantic": "Vengeful Romantic", "RuthlessRomantic": "Ruthless Romantic", + "Wraith": "Wraith", "Poisoner": "Poisoner", + "Medusa": "Medusa", "HexMaster": "Hex Master", - "Wraith": "Wraith", "Jinx": "Jinx", "PotionMaster": "Potion Master", "Necromancer": "Necromancer", + "CovenLeader": "Coven Leader", "Warden": "Warden", "Minion": "Minion", "Ghastly": "Ghastly", @@ -625,6 +632,7 @@ "JinxInfo": "Reflect attacks onto your attackers", "PotionMasterInfo": "Use your potions to your advantage", "NecromancerInfo": "Kill your killer to defy death", + "CovenLeaderInfo": "Help your teammates by retraining them", "WardenInfo": "(Ghost) Alert about danger", "MinionInfo": "(Ghost) Blind enemies", "LoversInfo": "Stay alive and win together", @@ -903,7 +911,7 @@ "VultureInfoLong": "(Neutrals):\nAs the Vulture, report bodies to win!\n\nWhen you report a body, if your eat cooldown is up, you'll eat the body (makes it unreportable).\nIf your eat ability is still on cooldown, then you'll report the body normally.\n\nAdditionally, you'll report bodies normally if the maximum bodies eaten per round is reached.", "TaskinatorInfoLong": "(Neutrals):\nAs the Taskinator, whenever you finish a task, the task will be bombed. When another player completes the bombed task, the bomb will detonate, and the player will die.\n\nYou win if you survive till the end and Crew doesn't win.\n\n Note: Taskinator bombs ignore all protection.", "BenefactorInfoLong": "(Crewmates):\nAs the Benefactor, whenever you finish a task, that task will be marked. When another player completes the marked task, they get a temporary shield.\n\n Note: Shield only protects from direct kill attacks.", - "MedusaInfoLong": "(Neutrals):\nAs the Medusa, you can stone bodies much like cleaning a body.\nStoned bodies cannot be reported.\n\nKill everyone to win.", + "MedusaInfoLong": "(Coven):\nAs the Medusa, you can stone bodies much like cleaning a body.\nStoned bodies cannot be reported.\n\nKill everyone to win.", "SpiritcallerInfoLong": "(Neutrals):\nAs the Spiritcaller, your victims become Evil Spirits after they die. These spirits can help you win by freezing other players briefly or blocking their vision. Alternatively, the spirits can give you a shield that protects you briefly from an attempted kill.", "AmnesiacInfoLong": "(Neutrals):\nAs the Amnesiac, use your report button to remember a role.\n\nIf the target was an Impostor, you'll become a Refugee.\nIf the target was a crewmate, you'll become the target role if compatible (otherwise you become an Engineer).\nIf the target was a passive neutral or a neutral killer not specified, you'll become the role defined in the settings.\nIf the target was a neutral killer of a select few, you'll become the role they are.", "ImitatorInfoLong": "(Neutrals):\nAs the Imitator, use your kill button to imitate a player.\n\nYou'll either become a Sheriff, a Refugee, or some Neutral.", @@ -920,12 +928,13 @@ "RomanticInfoLong": "(Neutrals):\nThe Romantic can pick their lover partner using their kill button (this can be done at any point of the game). Once they've picked their partner, they can use their kill button to give their partner a temporary shield that protects them from attacks. If their lover partner dies, the Romantic's role will change according to the following conditions:\n1. If their partner was an Impostor, the Romantic becomes the Refugee\n2. If their partner was a Neutral Killer, then they become Ruthless Romantic.\n3. If their partner was a Crewmate or a non-killing neutral, the Romantic becomes the Vengeful Romantic. \n\nThe Romantic wins with the winning team if their partner wins.\nNote: If your role changes, your win condition will be changed accordingly", "RuthlessRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A neutral killer) is killed. As a Ruthless Romantic, you win if you kill everyone and are the last one standing. If you win, your dead partner will also win with you.", "VengefulRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A crew or non-neutral killer) is killed. As a Vengeful Romantic, your goal is to avenge your partner, which means you must kill the killer of your partner. If you succeed, then you and your partner win with the winning team at the end. If you try to kill someone other than your partner's killer, then you will die by misfire.", - "PoisonerInfoLong": "(Neutrals):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", - "HexMasterInfoLong": "(Neutrals):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", + "PoisonerInfoLong": "(Coven):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", + "HexMasterInfoLong": "(Coven):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", - "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.", + "JinxInfoLong": "(Coven):\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": "(Coven):\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": "(Coven):\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.", + "CovenLeaderInfoLong": "(Coven): The Coven Leader can use their kill button on a fellow Coven member to retrain them into a random Coven role that isn’t currently in the game. During the next meeting, that Coven member will be notified that the Coven Leader wishes to retrain them. They can vote themselves to accept the retrain, or vote otherwise to deny it. Denying the retrain does not take away an ability usage.
With the Necronomicon, you cannot retrain, and can only kill other players.", "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.", @@ -1408,6 +1417,8 @@ "NeutralKillingRolesMaxPlayer": "Maximum amount of Neutral Killers", "NeutralApocalypseRolesMinPlayer": "Minimum amount of Neutral Apocalypse", "NeutralApocalypseRolesMaxPlayer": "Maximum amount of Neutral Apocalypse", + "CovenRolesMinPlayer": "Minimum amount of Coven", + "CovenRolesMaxPlayer": "Maximum amount of Coven", "TNACanBeGuessed": "Transformed Neutral Apocalypse Roles can be guessed", "ImpsCanSeeEachOthersRoles": "Impostors know the roles of other Impostors", "ImpsCanSeeEachOthersAddOns": "Impostors can see each other's Add-ons", @@ -1844,6 +1855,14 @@ "Troller_YouCausedSabotage": "You caused sabotage", "Troller_YouFixedSabotage": "You fixed sabotage", + "CovenLeaderMaxRetrains": "Max Retrains", + "CovenLeaderRetrainCooldown": "Retrain Cooldown", + "CovenLeaderAcceptRetrain": "The player you attempted to retrain has accepted your training and is now a [{0}]!", + "CovenLeaderDeclineRetrain": "The player you attempted to retrain has declined your offer to retrain them into a [{0}]...", + "RetrainNotification": "The Coven Leader has requested to retrain you into a [{0}].
Vote yourself to accept the offer, vote someone else to decline.", + "RetrainAcceptOffer": "You have accepted the Coven Leader's retraining and you are now a [{0}]!", + "RetrainDeclineOffer": "You declined the Coven Leader's retraining and your role has not been changed...", + "LuckyProbability": "Probability of surviving a kill", "ImpCanBeDoubleShot": "Impostors can have Double Shot", "CrewCanBeDoubleShot": "Crewmates can have Double Shot", @@ -2352,6 +2371,7 @@ "TabGroup.ModifierSettings": "Game Modifiers", "TabGroup.CrewmateRoles": "Crewmate Roles", "TabGroup.NeutralRoles": "Neutral Roles", + "TabGroup.CovenRoles": "Coven Roles", "TabGroup.ImpostorRoles": "Impostor Roles", "TabGroup.Addons": "Add-Ons", "TabMenuDescription_General": "Here you can configure the functions that are in the mod", @@ -3440,6 +3460,10 @@ "RoleType.NeutralChaos": "★ Neutral Chaos Roles", "RoleType.NeutralKilling": "★ Neutral Killing Roles", "RoleType.NeutralApocalypse": "★ Neutral Apocalypse Roles /apocalypseinfo", + "RoleType.CovenPower": "★ Coven Power Roles", + "RoleType.CovenKilling": "★ Coven Killing Roles", + "RoleType.CovenTrickery": "★ Coven Trickery Roles", + "RoleType.CovenUtility": "★ Coven Utility Roles", "RoleType.Harmful": "★ Harmful Add-ons", "RoleType.Support": "★ Supportive Add-ons", "RoleType.Helpful": "★ Helpful Add-ons", diff --git a/Resources/roleColor.json b/Resources/roleColor.json index bbeb4b157..1663d46e4 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -156,6 +156,7 @@ "Pixie": "#00FF00", "Imitator": "#B3D94C", "Doppelganger": "#f6f4a3", + "CovenLeader": "#ac42f2", "GM": "#ff5b70", "NotAssigned": "#ffffff", "LastImpostor": "#ff1919", diff --git a/Roles/Core/AssignManager/RoleAssign.cs b/Roles/Core/AssignManager/RoleAssign.cs index 395e3d0b3..1035f9b97 100644 --- a/Roles/Core/AssignManager/RoleAssign.cs +++ b/Roles/Core/AssignManager/RoleAssign.cs @@ -17,6 +17,7 @@ enum RoleAssignType NeutralKilling, NonKillingNeutral, NeutralApocalypse, + Coven, Crewmate } @@ -47,6 +48,15 @@ public static void GetNeutralCounts(int NKmaxOpt, int NKminOpt, int NNKmaxOpt, i ResultNAnum = rd.Next(NAminOpt, NAmaxOpt + 1); } } + public static void GetCovenCounts(int CVmaxOpt, int CVminOpt, ref int ResultCVnum) + { + var rd = IRandom.Instance; + + if (CVmaxOpt > 0 && CVmaxOpt >= CVminOpt) + { + ResultCVnum = rd.Next(CVminOpt, CVmaxOpt + 1); + } + } public static void StartSelect() { @@ -68,14 +78,17 @@ public static void StartSelect() int optNonNeutralKillingNum = 0; int optNeutralKillingNum = 0; int optNeutralApocalypseNum = 0; + int optCovenNum = 0; GetNeutralCounts(Options.NeutralKillingRolesMaxPlayer.GetInt(), Options.NeutralKillingRolesMinPlayer.GetInt(), Options.NonNeutralKillingRolesMaxPlayer.GetInt(), Options.NonNeutralKillingRolesMinPlayer.GetInt(), Options.NeutralApocalypseRolesMaxPlayer.GetInt(), Options.NeutralApocalypseRolesMinPlayer.GetInt(), ref optNeutralKillingNum, ref optNonNeutralKillingNum, ref optNeutralApocalypseNum); + GetCovenCounts(Options.CovenRolesMaxPlayer.GetInt(), Options.CovenRolesMinPlayer.GetInt(), ref optCovenNum); int readyRoleNum = 0; int readyImpNum = 0; int readyNonNeutralKillingNum = 0; int readyNeutralKillingNum = 0; int readyNeutralApocalypseNum = 0; + int readyCovenNum = 0; List FinalRolesList = []; @@ -85,6 +98,7 @@ public static void StartSelect() Roles[RoleAssignType.NeutralKilling] = []; Roles[RoleAssignType.NonKillingNeutral] = []; Roles[RoleAssignType.NeutralApocalypse] = []; + Roles[RoleAssignType.Coven] = []; Roles[RoleAssignType.Crewmate] = []; foreach (var id in SetRoles.Keys.Where(id => Utils.GetPlayerById(id) == null).ToArray()) SetRoles.Remove(id); @@ -129,6 +143,7 @@ public static void StartSelect() else if (role.IsNK()) Roles[RoleAssignType.NeutralKilling].Add(info); else if (role.IsNA()) Roles[RoleAssignType.NeutralApocalypse].Add(info); else if (role.IsNonNK()) Roles[RoleAssignType.NonKillingNeutral].Add(info); + else if (role.IsCoven()) Roles[RoleAssignType.Coven].Add(info); else Roles[RoleAssignType.Crewmate].Add(info); } @@ -144,6 +159,7 @@ public static void StartSelect() Logger.Info(string.Join(", ", Roles[RoleAssignType.NeutralKilling].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "NKRoles"); Logger.Info(string.Join(", ", Roles[RoleAssignType.NeutralApocalypse].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "NARoles"); Logger.Info(string.Join(", ", Roles[RoleAssignType.NonKillingNeutral].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "NonNKRoles"); + Logger.Info(string.Join(", ", Roles[RoleAssignType.Coven].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "CovenRoles"); Logger.Info(string.Join(", ", Roles[RoleAssignType.Crewmate].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "CrewRoles"); Logger.Msg("=====================================================", "AllActiveRoles"); @@ -151,6 +167,7 @@ public static void StartSelect() IEnumerable TempAlwaysNKRoles = Roles[RoleAssignType.NeutralKilling].Where(x => x.SpawnChance == 100); IEnumerable TempAlwaysNARoles = Roles[RoleAssignType.NeutralApocalypse].Where(x => x.SpawnChance == 100); IEnumerable TempAlwaysNNKRoles = Roles[RoleAssignType.NonKillingNeutral].Where(x => x.SpawnChance == 100); + IEnumerable TempAlwaysCovenRoles = Roles[RoleAssignType.Coven].Where(x => x.SpawnChance == 100); IEnumerable TempAlwaysCrewRoles = Roles[RoleAssignType.Crewmate].Where(x => x.SpawnChance == 100); // DistinctBy - Removes duplicate roles if there are any @@ -161,18 +178,21 @@ public static void StartSelect() Roles[RoleAssignType.NeutralKilling] = Roles[RoleAssignType.NeutralKilling].Shuffle(rd).Take(optNeutralKillingNum).ToList(); Roles[RoleAssignType.NeutralApocalypse] = Roles[RoleAssignType.NeutralApocalypse].Shuffle(rd).Take(optNeutralApocalypseNum).ToList(); Roles[RoleAssignType.NonKillingNeutral] = Roles[RoleAssignType.NonKillingNeutral].Shuffle(rd).Take(optNonNeutralKillingNum).ToList(); + Roles[RoleAssignType.Coven] = Roles[RoleAssignType.Coven].Shuffle(rd).Take(optCovenNum).ToList(); Roles[RoleAssignType.Crewmate] = Roles[RoleAssignType.Crewmate].Shuffle(rd).Take(playerCount).ToList(); Roles[RoleAssignType.Impostor].AddRange(TempAlwaysImpRoles); Roles[RoleAssignType.NeutralKilling].AddRange(TempAlwaysNKRoles); Roles[RoleAssignType.NeutralApocalypse].AddRange(TempAlwaysNARoles); Roles[RoleAssignType.NonKillingNeutral].AddRange(TempAlwaysNNKRoles); + Roles[RoleAssignType.Coven].AddRange(TempAlwaysCovenRoles); Roles[RoleAssignType.Crewmate].AddRange(TempAlwaysCrewRoles); Roles[RoleAssignType.Impostor] = Roles[RoleAssignType.Impostor].DistinctBy(x => x.Role).ToList(); Roles[RoleAssignType.NeutralKilling] = Roles[RoleAssignType.NeutralKilling].DistinctBy(x => x.Role).ToList(); Roles[RoleAssignType.NeutralApocalypse] = Roles[RoleAssignType.NeutralApocalypse].DistinctBy(x => x.Role).ToList(); Roles[RoleAssignType.NonKillingNeutral] = Roles[RoleAssignType.NonKillingNeutral].DistinctBy(x => x.Role).ToList(); + Roles[RoleAssignType.Coven] = Roles[RoleAssignType.Coven].DistinctBy(x => x.Role).ToList(); Roles[RoleAssignType.Crewmate] = Roles[RoleAssignType.Crewmate].DistinctBy(x => x.Role).ToList(); Logger.Msg("======================================================", "SelectedRoles"); @@ -180,6 +200,7 @@ public static void StartSelect() Logger.Info(string.Join(", ", Roles[RoleAssignType.NeutralKilling].Select(x => x.Role.ToString())), "Selected-NK-Roles"); Logger.Info(string.Join(", ", Roles[RoleAssignType.NeutralApocalypse].Select(x => x.Role.ToString())), "Selected-NA-Roles"); Logger.Info(string.Join(", ", Roles[RoleAssignType.NonKillingNeutral].Select(x => x.Role.ToString())), "Selected-NonNK-Roles"); + Logger.Info(string.Join(", ", Roles[RoleAssignType.Coven].Select(x => x.Role.ToString())), "Selected-Coven-Roles"); Logger.Info(string.Join(", ", Roles[RoleAssignType.Crewmate].Select(x => x.Role.ToString())), "Selected-Crew-Roles"); Logger.Msg("======================================================", "SelectedRoles"); @@ -222,6 +243,11 @@ public static void StartSelect() Roles[RoleAssignType.NeutralApocalypse].Where(x => x.Role == item.Value).Do(x => x.AssignedCount++); readyNeutralApocalypseNum++; } + else if (item.Value.IsCoven()) + { + Roles[RoleAssignType.Coven].Where(x => x.Role == item.Value).Do(x => x.AssignedCount++); + readyCovenNum++; + } else if (item.Value.IsNonNK()) { Roles[RoleAssignType.NonKillingNeutral].Where(x => x.Role == item.Value).Do(x => x.AssignedCount++); @@ -237,6 +263,7 @@ public static void StartSelect() RoleAssignInfo[] NNKs = []; RoleAssignInfo[] NKs = []; RoleAssignInfo[] NAs = []; + RoleAssignInfo[] Covs = []; RoleAssignInfo[] Crews = []; // Impostor Roles @@ -637,7 +664,106 @@ public static void StartSelect() } } } + // Coven Roles + { + { + List AlwaysCVRoles = []; + List ChanceCVRoles = []; + for (int i = 0; i < Roles[RoleAssignType.Coven].Count; i++) + { + RoleAssignInfo item = Roles[RoleAssignType.Coven][i]; + + if (item.SpawnChance == 100) + { + for (int j = 0; j < item.MaxCount; j++) + { + // Don't add if Host has assigned this role by using '/up' + if (SetRoles.ContainsValue(item.Role)) + { + var playerId = SetRoles.FirstOrDefault(x => x.Value == item.Role).Key; + SetRoles.Remove(playerId); + continue; + } + + AlwaysCVRoles.Add(item.Role); + } + } + else + { + // Add 'MaxCount' (1) times + for (int k = 0; k < item.MaxCount; k++) + { + // Don't add if Host has assigned this role by using '/up' + if (SetRoles.ContainsValue(item.Role)) + { + var playerId = SetRoles.FirstOrDefault(x => x.Value == item.Role).Key; + SetRoles.Remove(playerId); + continue; + } + // Make "Spawn Chance ÷ 5 = x" (Example: 65 ÷ 5 = 13) + for (int j = 0; j < item.SpawnChance / 5; j++) + { + // Add coven roles 'x' times (13) + ChanceCVRoles.Add(item.Role); + } + } + } + } + + RoleAssignInfo[] CVRoleCounts = AlwaysCVRoles.Distinct().Select(GetAssignInfo).ToArray().AddRangeToArray(ChanceCVRoles.Distinct().Select(GetAssignInfo).ToArray()); + Covs = CVRoleCounts; + + // Assign roles set to 100% + if (readyCovenNum < optCovenNum) + { + while (AlwaysCVRoles.Any() && optCovenNum > 0) + { + var selected = AlwaysCVRoles[rd.Next(0, AlwaysCVRoles.Count)]; + var info = CVRoleCounts.FirstOrDefault(x => x.Role == selected); + AlwaysCVRoles.Remove(selected); + if (info.AssignedCount >= info.MaxCount) continue; + + FinalRolesList.Add(selected); + info.AssignedCount++; + readyRoleNum++; + readyCovenNum++; + + Covs = CVRoleCounts; + + if (readyRoleNum >= playerCount) goto EndOfAssign; + if (readyCovenNum >= optCovenNum) break; + } + } + + // Assign other roles when needed + if (readyRoleNum < playerCount && readyCovenNum < optCovenNum) + { + while (ChanceCVRoles.Any() && optCovenNum > 0) + { + var selectesItem = rd.Next(0, ChanceCVRoles.Count); + var selected = ChanceCVRoles[selectesItem]; + var info = CVRoleCounts.FirstOrDefault(x => x.Role == selected); + + // Remove 'x' times + for (int j = 0; j < info.SpawnChance / 5; j++) + ChanceCVRoles.Remove(selected); + + FinalRolesList.Add(selected); + info.AssignedCount++; + readyRoleNum++; + readyCovenNum++; + + Covs = CVRoleCounts; + + if (info.AssignedCount >= info.MaxCount) while (ChanceCVRoles.Contains(selected)) ChanceCVRoles.Remove(selected); + + if (readyRoleNum >= playerCount) goto EndOfAssign; + if (readyCovenNum >= optCovenNum) break; + } + } + } + } // Crewmate Roles { List AlwaysCrewRoles = []; @@ -737,6 +863,8 @@ public static void StartSelect() if (Imps.Any()) Logger.Info(string.Join(", ", Imps.Select(x => $"{x.Role} - {x.AssignedCount}/{x.MaxCount} ({x.SpawnChance}%)")), "ImpRoleResult"); if (NNKs.Any()) Logger.Info(string.Join(", ", NNKs.Select(x => $"{x.Role} - {x.AssignedCount}/{x.MaxCount} ({x.SpawnChance}%)")), "NNKRoleResult"); if (NKs.Any()) Logger.Info(string.Join(", ", NKs.Select(x => $"{x.Role} - {x.AssignedCount}/{x.MaxCount} ({x.SpawnChance}%)")), "NKRoleResult"); + if (NAs.Any()) Logger.Info(string.Join(", ", NKs.Select(x => $"{x.Role} - {x.AssignedCount}/{x.MaxCount} ({x.SpawnChance}%)")), "NARoleResult"); + if (Covs.Any()) Logger.Info(string.Join(", ", Covs.Select(x => $"{x.Role} - {x.AssignedCount}/{x.MaxCount} ({x.SpawnChance}%)")), "CovRoleResult"); if (Crews.Any()) Logger.Info(string.Join(", ", Crews.Select(x => $"{x.Role} - {x.AssignedCount}/{x.MaxCount} ({x.SpawnChance}%)")), "CrewRoleResult"); if (Sunnyboy.CheckSpawn() && FinalRolesList.Remove(CustomRoles.Jester)) FinalRolesList.Add(CustomRoles.Sunnyboy); diff --git a/Roles/Coven/CovenLeader.cs b/Roles/Coven/CovenLeader.cs new file mode 100644 index 000000000..86d86b6f7 --- /dev/null +++ b/Roles/Coven/CovenLeader.cs @@ -0,0 +1,81 @@ +using AmongUs.GameOptions; +using Hazel; +using System.Diagnostics.Metrics; +using System.Text; +using TOHE.Roles.Core; +using TOHE.Roles.Crewmate; +using TOHE.Roles.Double; +using TOHE.Roles.Neutral; +using static TOHE.Options; +using static TOHE.Translator; +using static TOHE.Utils; + +namespace TOHE.Roles.Coven; + +internal class CovenLeader : CovenManager +{ + //===========================SETUP================================\\ + private const int Id = 29800; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.CovenLeader); + public override bool IsDesyncRole => true; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenPower; + //==================================================================\\ + + private static OptionItem RetrainCooldown; + public static OptionItem MaxRetrains; + + public static readonly Dictionary retrainPlayer = []; + + public override void SetupCustomOption() + { + SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.CovenLeader, 1, zeroOne: false); + MaxRetrains = IntegerOptionItem.Create(Id + 10, "CovenLeaderMaxRetrains", new(1, 15, 1), 2, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.CovenLeader]) + .SetValueFormat(OptionFormat.Times); + RetrainCooldown = FloatOptionItem.Create(Id + 11, "CovenLeaderRetrainCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.CovenLeader]) + .SetValueFormat(OptionFormat.Seconds); + } + public override void Init() + { + retrainPlayer.Clear(); + } + public override void Add(byte playerId) + { + AbilityLimit = MaxRetrains.GetInt(); + } + + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + if (HasNecronomicon(killer)) return true; + if (killer.IsPlayerCoven() && !target.IsPlayerCoven()) return false; + var roleList = CustomRolesHelper.AllRoles.Where(role => (role.IsCoven() && (role.IsEnable() && !role.RoleExist(countDead: true)))).ToList(); + retrainPlayer[target.PlayerId] = roleList.RandomElement(); + foreach (byte cov in retrainPlayer.Keys) + { + GetPlayerById(cov).SendMessage(string.Format(GetString("RetrainAcceptOffer"), retrainPlayer[cov].ToColoredString())); + } + return false; + } + public override bool CheckVote(PlayerControl voter, PlayerControl target) + { + PlayerControl CL = CustomRoles.CovenLeader.GetPlayerListByRole().First(); + if ((voter == target) && retrainPlayer[voter.PlayerId].IsCoven()) + { + voter.RpcSetCustomRole(retrainPlayer[voter.PlayerId]); + CL.SendMessage(string.Format(GetString("CovenLeaderAcceptRetrain"), retrainPlayer[voter.PlayerId].ToColoredString())); + voter.SendMessage(string.Format(GetString("RetrainAcceptOffer"), retrainPlayer[voter.PlayerId].ToColoredString())); + retrainPlayer[voter.PlayerId] = CustomRoles.Crewmate; + AbilityLimit--; + return true; + } + if ((voter != target) && retrainPlayer[voter.PlayerId].IsCoven()) + { + CL.SendMessage(string.Format(GetString("CovenLeaderDeclineRetrain"), retrainPlayer[voter.PlayerId].ToColoredString())); + voter.SendMessage(GetString("RetrainDeclineOffer")); + retrainPlayer[voter.PlayerId] = CustomRoles.Crewmate; + return true; + } + return false; + } +} diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs new file mode 100644 index 000000000..7023599e8 --- /dev/null +++ b/Roles/Coven/CovenManager.cs @@ -0,0 +1,29 @@ +using TOHE.Roles.Crewmate; +using TOHE.Roles.Double; +using TOHE.Roles.Neutral; +using static TOHE.Options; +using static UnityEngine.GraphicsBuffer; + +namespace TOHE; +public abstract class CovenManager : RoleBase +{ + public static PlayerControl necroHolder; + + private static + public override void SetupCustomOption() + { + base.SetupCustomOption(); + } + public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); + public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); + public static void GiveNecronomicon() + { + var pcList = Main.AllAlivePlayerControls.Where(pc => pc.IsPlayerCoven() && pc.IsAlive()).ToList(); + if (pcList.Any()) + { + PlayerControl rp = pcList.RandomElement(); + necroHolder = rp; + } + } + public static bool HasNecronomicon(PlayerControl pc) => necroHolder == pc; +} diff --git a/Roles/Neutral/HexMaster.cs b/Roles/Coven/HexMaster.cs similarity index 94% rename from Roles/Neutral/HexMaster.cs rename to Roles/Coven/HexMaster.cs index ed105bb1c..4676e153d 100644 --- a/Roles/Neutral/HexMaster.cs +++ b/Roles/Coven/HexMaster.cs @@ -7,9 +7,9 @@ using static TOHE.Translator; -namespace TOHE.Roles.Neutral; +namespace TOHE.Roles.Coven; -internal class HexMaster : RoleBase +internal class HexMaster : CovenManager { //===========================SETUP================================\\ private const int Id = 16400; @@ -17,7 +17,7 @@ internal class HexMaster : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; - public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenPower; //==================================================================\\ private static OptionItem ModeSwitchAction; @@ -40,10 +40,10 @@ private enum SwitchTriggerList public override void SetupCustomOption() { - SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.HexMaster, 1, zeroOne: false); - ModeSwitchAction = StringOptionItem.Create(Id + 10, GeneralOption.ModeSwitchAction, EnumHelper.GetAllNames(), 2, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); - HexesLookLikeSpells = BooleanOptionItem.Create(Id + 11, "HexesLookLikeSpells", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); - HasImpostorVision = BooleanOptionItem.Create(Id + 12, GeneralOption.ImpostorVision, true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); + SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.HexMaster, 1, zeroOne: false); + ModeSwitchAction = StringOptionItem.Create(Id + 10, GeneralOption.ModeSwitchAction, EnumHelper.GetAllNames(), 2, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); + HexesLookLikeSpells = BooleanOptionItem.Create(Id + 11, "HexesLookLikeSpells", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); + HasImpostorVision = BooleanOptionItem.Create(Id + 12, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); } public override void Init() { @@ -107,6 +107,7 @@ public static void ReceiveRPC(MessageReader reader, bool doHex) public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => true; + private static bool IsHexMode(byte playerId) { return HexMode.ContainsKey(playerId) && HexMode[playerId]; diff --git a/Roles/Neutral/Jinx.cs b/Roles/Coven/Jinx.cs similarity index 80% rename from Roles/Neutral/Jinx.cs rename to Roles/Coven/Jinx.cs index 2696e6827..e65fe1602 100644 --- a/Roles/Neutral/Jinx.cs +++ b/Roles/Coven/Jinx.cs @@ -3,16 +3,16 @@ using static TOHE.Options; using TOHE.Roles.Core; -namespace TOHE.Roles.Neutral; +namespace TOHE.Roles.Coven; -internal class Jinx : RoleBase +internal class Jinx : CovenManager { //===========================SETUP================================\\ private const int Id = 16800; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Jinx); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; - public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenKilling; //==================================================================\\ private static OptionItem KillCooldown; @@ -23,15 +23,15 @@ internal class Jinx : RoleBase public override void SetupCustomOption() { - SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Jinx, 1, zeroOne: false); - KillCooldown = FloatOptionItem.Create(Id + 10, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 20f, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]) + SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Jinx, 1, zeroOne: false); + KillCooldown = FloatOptionItem.Create(Id + 10, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 20f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]) .SetValueFormat(OptionFormat.Seconds); - CanVent = BooleanOptionItem.Create(Id + 11, GeneralOption.CanVent, true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); - HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); - JinxSpellTimes = IntegerOptionItem.Create(Id + 14, "JinxSpellTimes", new(1, 15, 1), 3, TabGroup.NeutralRoles, false) + CanVent = BooleanOptionItem.Create(Id + 11, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); + HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); + JinxSpellTimes = IntegerOptionItem.Create(Id + 14, "JinxSpellTimes", new(1, 15, 1), 3, TabGroup.CovenRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]) .SetValueFormat(OptionFormat.Times); - killAttacker = BooleanOptionItem.Create(Id + 15, GeneralOption.KillAttackerWhenAbilityRemaining, true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); + killAttacker = BooleanOptionItem.Create(Id + 15, GeneralOption.KillAttackerWhenAbilityRemaining, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); } public override void Add(byte playerId) @@ -65,6 +65,7 @@ public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl t public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl player) => CanVent.GetBool(); + public override string GetProgressText(byte playerId, bool comms) => Utils.ColorString(CanJinx(playerId) ? Utils.GetRoleColor(CustomRoles.Gangster).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); diff --git a/Roles/Neutral/Medusa.cs b/Roles/Coven/Medusa.cs similarity index 80% rename from Roles/Neutral/Medusa.cs rename to Roles/Coven/Medusa.cs index 2770da576..61fb47a7a 100644 --- a/Roles/Neutral/Medusa.cs +++ b/Roles/Coven/Medusa.cs @@ -2,9 +2,9 @@ using static TOHE.Translator; using static TOHE.Options; -namespace TOHE.Roles.Neutral; +namespace TOHE.Roles.Coven; -internal class Medusa : RoleBase +internal class Medusa : CovenManager { //===========================SETUP================================\\ private const int Id = 17000; @@ -12,7 +12,7 @@ internal class Medusa : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; - public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; //==================================================================\\ private static OptionItem KillCooldown; @@ -22,13 +22,13 @@ internal class Medusa : RoleBase public override void SetupCustomOption() { - SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Medusa, 1, zeroOne: false); - KillCooldown = FloatOptionItem.Create(Id + 12, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 20f, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]) + SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Medusa, 1, zeroOne: false); + KillCooldown = FloatOptionItem.Create(Id + 12, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 20f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]) .SetValueFormat(OptionFormat.Seconds); - KillCooldownAfterStoneGazing = FloatOptionItem.Create(Id + 15, "KillCooldownAfterStoneGazing", new(0f, 180f, 2.5f), 40f, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]) + KillCooldownAfterStoneGazing = FloatOptionItem.Create(Id + 15, "KillCooldownAfterStoneGazing", new(0f, 180f, 2.5f), 40f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]) .SetValueFormat(OptionFormat.Seconds); - CanVent = BooleanOptionItem.Create(Id + 11, GeneralOption.CanVent, true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); - HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); + CanVent = BooleanOptionItem.Create(Id + 11, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); + HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); } public override void Init() { @@ -43,6 +43,7 @@ public override void Add(byte playerId) public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); + public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) { if (reporter.Is(CustomRoles.Medusa)) diff --git a/Roles/Neutral/Necromancer.cs b/Roles/Coven/Necromancer.cs similarity index 84% rename from Roles/Neutral/Necromancer.cs rename to Roles/Coven/Necromancer.cs index 9cd89ea42..d105d7af0 100644 --- a/Roles/Neutral/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -2,9 +2,9 @@ using static TOHE.Options; using static TOHE.Translator; -namespace TOHE.Roles.Neutral; +namespace TOHE.Roles.Coven; -internal class Necromancer : RoleBase +internal class Necromancer : CovenManager { //===========================SETUP================================\\ private const int Id = 17100; @@ -12,7 +12,7 @@ internal class Necromancer : RoleBase public static bool HasEnabled => playerIdList.Any(); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; - public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; //==================================================================\\ private static OptionItem KillCooldown; @@ -28,13 +28,13 @@ internal class Necromancer : RoleBase public override void SetupCustomOption() { - SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Necromancer, 1, zeroOne: false); - KillCooldown = FloatOptionItem.Create(Id + 10, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 20f, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]) + SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Necromancer, 1, zeroOne: false); + KillCooldown = FloatOptionItem.Create(Id + 10, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 20f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]) .SetValueFormat(OptionFormat.Seconds); - RevengeTime = IntegerOptionItem.Create(Id + 11, "NecromancerRevengeTime", new(0, 60, 1), 30, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]) + RevengeTime = IntegerOptionItem.Create(Id + 11, "NecromancerRevengeTime", new(0, 60, 1), 30, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]) .SetValueFormat(OptionFormat.Seconds); - CanVent = BooleanOptionItem.Create(Id + 12, GeneralOption.CanVent, true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]); - HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]); + CanVent = BooleanOptionItem.Create(Id + 12, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]); + HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]); } public override void Init() { @@ -53,7 +53,8 @@ public override void Add(byte playerId) public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); - + public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { if (IsRevenge) return true; diff --git a/Roles/Neutral/Poisoner.cs b/Roles/Coven/Poisoner.cs similarity index 87% rename from Roles/Neutral/Poisoner.cs rename to Roles/Coven/Poisoner.cs index 8be8e2c5c..13c6ae441 100644 --- a/Roles/Neutral/Poisoner.cs +++ b/Roles/Coven/Poisoner.cs @@ -3,9 +3,9 @@ using TOHE.Roles.AddOns.Common; using static TOHE.Translator; -namespace TOHE.Roles.Neutral; +namespace TOHE.Roles.Coven; -internal class Poisoner : RoleBase +internal class Poisoner : CovenManager { private class PoisonedInfo(byte poisonerId, float killTimer) { @@ -19,7 +19,7 @@ private class PoisonedInfo(byte poisonerId, float killTimer) public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; - public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenTrickery; //==================================================================\\ private static OptionItem OptionKillDelay; @@ -33,13 +33,13 @@ private class PoisonedInfo(byte poisonerId, float killTimer) public override void SetupCustomOption() { - Options.SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Poisoner, 1, zeroOne: false); - KillCooldown = FloatOptionItem.Create(Id + 10, "PoisonCooldown", new(0f, 180f, 2.5f), 20f, TabGroup.NeutralRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]) + Options.SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Poisoner, 1, zeroOne: false); + KillCooldown = FloatOptionItem.Create(Id + 10, "PoisonCooldown", new(0f, 180f, 2.5f), 20f, TabGroup.CovenRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]) .SetValueFormat(OptionFormat.Seconds); - OptionKillDelay = FloatOptionItem.Create(Id + 11, "PoisonerKillDelay", new(1f, 60f, 1f), 10f, TabGroup.NeutralRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]) + OptionKillDelay = FloatOptionItem.Create(Id + 11, "PoisonerKillDelay", new(1f, 60f, 1f), 10f, TabGroup.CovenRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]) .SetValueFormat(OptionFormat.Seconds); - CanVent = BooleanOptionItem.Create(Id + 12, GeneralOption.CanVent, true, TabGroup.NeutralRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]); - HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.NeutralRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]); + CanVent = BooleanOptionItem.Create(Id + 12, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]); + HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]); } public override void Init() @@ -58,6 +58,7 @@ public override void Add(byte playerId) public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (target.Is(CustomRoles.Bait)) return true; diff --git a/Roles/Neutral/PotionMaster.cs b/Roles/Coven/PotionMaster.cs similarity index 86% rename from Roles/Neutral/PotionMaster.cs rename to Roles/Coven/PotionMaster.cs index db55f619a..122b731f2 100644 --- a/Roles/Neutral/PotionMaster.cs +++ b/Roles/Coven/PotionMaster.cs @@ -5,16 +5,16 @@ using UnityEngine; using static TOHE.Options; -namespace TOHE.Roles.Neutral; +namespace TOHE.Roles.Coven; -internal class PotionMaster : RoleBase +internal class PotionMaster : CovenManager { //===========================SETUP================================\\ private const int Id = 17700; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.PotionMaster); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; - public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; //==================================================================\\ private static OptionItem KillCooldown; @@ -26,13 +26,13 @@ internal class PotionMaster : RoleBase public override void SetupCustomOption() { - SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.PotionMaster, 1, zeroOne: false); - KillCooldown = FloatOptionItem.Create(Id + 14, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 20f, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]) + SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.PotionMaster, 1, zeroOne: false); + KillCooldown = FloatOptionItem.Create(Id + 14, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 20f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]) .SetValueFormat(OptionFormat.Seconds); - RitualMaxCount = IntegerOptionItem.Create(Id + 11, "RitualMaxCount", new(1, 15, 1), 5, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]) + RitualMaxCount = IntegerOptionItem.Create(Id + 11, "RitualMaxCount", new(1, 15, 1), 5, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]) .SetValueFormat(OptionFormat.Times); - CanVent = BooleanOptionItem.Create(Id + 12, GeneralOption.CanVent, true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]); - HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]); + CanVent = BooleanOptionItem.Create(Id + 12, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]); + HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]); } public override void Init() { @@ -70,6 +70,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); public override bool CanUseSabotage(PlayerControl pc) => true; + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (AbilityLimit > 0) diff --git a/main.cs b/main.cs index 2510b14d0..536ace846 100644 --- a/main.cs +++ b/main.cs @@ -797,19 +797,15 @@ public enum CustomRoles Glitch, God, Hater, - HexMaster, Huntsman, Imitator, Infectious, Innocent, Jackal, Jester, - Jinx, Juggernaut, Lawyer, Maverick, - Medusa, - Necromancer, Opportunist, Pelican, Pestilence, @@ -818,8 +814,6 @@ public enum CustomRoles Pixie, PlagueBearer, PlagueDoctor, - Poisoner, - PotionMaster, Provocateur, PunchingBag, Pursuer, @@ -853,6 +847,24 @@ public enum CustomRoles Workaholic, Wraith, + //Coven + Conjurer, + CovenLeader, + Dreamweaver, + HexMaster, + Illusionist, + Jinx, + Medusa, + MoonDancer, + Necromancer, + Poisoner, + PotionMaster, + Ritualist, + Sacrifist, + Spellcaster, + Sorceress, + VoodooMaster, + //two-way camp Mini, From 9b54799de9576c8c282f4208a5f7a0caf2d5f822 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Wed, 11 Sep 2024 19:02:55 -0400 Subject: [PATCH 002/101] more stuff cl should be finished now, rpcs unsure --- Modules/OptionHolder.cs | 17 ++++++ Patches/MeetingHudPatch.cs | 20 +++++++ Resources/Lang/en_US.json | 26 ++++++--- Roles/Coven/CovenLeader.cs | 55 +++++++++++------- Roles/Coven/CovenManager.cs | 111 ++++++++++++++++++++++++++++++++++-- Roles/Coven/HexMaster.cs | 8 +-- Roles/Coven/Jinx.cs | 12 ++-- Roles/Coven/Medusa.cs | 12 ++-- Roles/Coven/Poisoner.cs | 12 ++-- Roles/Coven/PotionMaster.cs | 14 ++--- 10 files changed, 224 insertions(+), 63 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 3c931268b..c2b917a4b 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -507,6 +507,10 @@ private enum RatesZeroOne // Coven public static OptionItem CovenRolesMinPlayer; public static OptionItem CovenRolesMaxPlayer; + public static OptionItem CovenHasImpVis; + public static OptionItem CovenImpVisMode; + public static OptionItem CovenCanVent; + public static OptionItem CovenVentMode; // Add-on public static OptionItem NameDisplayAddons; @@ -718,6 +722,19 @@ private static System.Collections.IEnumerator CoLoadOptions() CovenRolesMaxPlayer = IntegerOptionItem.Create(60026, "CovenRolesMaxPlayer", new(0, 15, 1), 0, TabGroup.CovenRoles, false) .SetGameMode(CustomGameMode.Standard) .SetValueFormat(OptionFormat.Players); + CovenHasImpVis = BooleanOptionItem.Create(60027, "CovenHasImpVis", true, TabGroup.CovenRoles, false) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true); + CovenImpVisMode = StringOptionItem.Create(60028, "CovenImpVisMode", EnumHelper.GetAllNames(), 0, TabGroup.CovenRoles, false) + .SetGameMode(CustomGameMode.Standard) + .SetParent(CovenHasImpVis); + CovenManager.RunSetUpImpVisOptions(160032); + CovenCanVent = BooleanOptionItem.Create(60029, "CovenCanVent", true, TabGroup.CovenRoles, false) + .SetGameMode(CustomGameMode.Standard); + CovenVentMode = StringOptionItem.Create(60030, "CovenVentMode", EnumHelper.GetAllNames(), 0, TabGroup.CovenRoles, false) + .SetGameMode(CustomGameMode.Standard) + .SetParent(CovenCanVent); + CovenManager.RunSetUpVentOptions(260032); NameDisplayAddons = BooleanOptionItem.Create(60019, "NameDisplayAddons", true, TabGroup.Addons, false) .SetGameMode(CustomGameMode.Standard) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 21d58381a..5c65be7f8 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -681,6 +681,26 @@ public static bool Prefix(MeetingHud __instance, byte srcPlayerId, byte suspectP } } + // Coven Leader Retraining + if (target == voter && CovenLeader.retrainPlayer[voter.PlayerId].IsCoven()) + { + PlayerControl CL = CustomRoles.CovenLeader.GetPlayerListByRole().First(); + voter.RpcSetCustomRole(CovenLeader.retrainPlayer[voter.PlayerId]); + SendMessage(string.Format(GetString("CovenLeaderAcceptRetrain"), CustomRoles.CovenLeader.ToColoredString(), CovenLeader.retrainPlayer[voter.PlayerId].ToColoredString()), CL.PlayerId); + SendMessage(string.Format(GetString("RetrainAcceptOffer"), CustomRoles.CovenLeader.ToColoredString(), CovenLeader.retrainPlayer[voter.PlayerId].ToColoredString()), voter.PlayerId); + CovenLeader.retrainPlayer.Clear(); + CustomRoles.CovenLeader.GetStaticRoleClass().AbilityLimit--; + __instance.RpcClearVoteDelay(voter.GetClientId()); + } + else if (target != voter && CovenLeader.retrainPlayer[voter.PlayerId].IsCoven()) + { + PlayerControl CL = CustomRoles.CovenLeader.GetPlayerListByRole().First(); + SendMessage(string.Format(GetString("CovenLeaderDeclineRetrain"), CovenLeader.retrainPlayer[voter.PlayerId].ToColoredString()), CL.PlayerId); + SendMessage(string.Format(GetString("RetrainDeclineOffer"), CustomRoles.CovenLeader.ToColoredString()), voter.PlayerId); + CovenLeader.retrainPlayer.Clear(); + __instance.RpcClearVoteDelay(voter.GetClientId()); + } + if (target != null && suspectPlayerId < 253) { if (!target.IsAlive() || target.Data.Disconnected) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index e52e9d571..2776dafd0 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1417,8 +1417,6 @@ "NeutralKillingRolesMaxPlayer": "Maximum amount of Neutral Killers", "NeutralApocalypseRolesMinPlayer": "Minimum amount of Neutral Apocalypse", "NeutralApocalypseRolesMaxPlayer": "Maximum amount of Neutral Apocalypse", - "CovenRolesMinPlayer": "Minimum amount of Coven", - "CovenRolesMaxPlayer": "Maximum amount of Coven", "TNACanBeGuessed": "Transformed Neutral Apocalypse Roles can be guessed", "ImpsCanSeeEachOthersRoles": "Impostors know the roles of other Impostors", "ImpsCanSeeEachOthersAddOns": "Impostors can see each other's Add-ons", @@ -1855,13 +1853,27 @@ "Troller_YouCausedSabotage": "You caused sabotage", "Troller_YouFixedSabotage": "You fixed sabotage", + "CovenRolesMinPlayer": "Minimum amount of Coven", + "CovenRolesMaxPlayer": "Maximum amount of Coven", + "%role%CanVent": "%role% Can Vent", + "%role%HasImpVis": "%role% Has Impostor Vision", + "CovenHasImpVis": "Coven Members have Impostor Vision", + "CovenImpVisMode": "Impostor Vision Configuration", + "CovenCanVent": "Coven Members Can Vent", + "CovenVentMode": "Vent Configuration", + "CovenPerRole": "Per Role", + "NecronomiconNotification": "You have recieved the Necronomicon!
Your powers are now enhanced!", + "CovenLeaderMaxRetrains": "Max Retrains", "CovenLeaderRetrainCooldown": "Retrain Cooldown", - "CovenLeaderAcceptRetrain": "The player you attempted to retrain has accepted your training and is now a [{0}]!", - "CovenLeaderDeclineRetrain": "The player you attempted to retrain has declined your offer to retrain them into a [{0}]...", - "RetrainNotification": "The Coven Leader has requested to retrain you into a [{0}].
Vote yourself to accept the offer, vote someone else to decline.", - "RetrainAcceptOffer": "You have accepted the Coven Leader's retraining and you are now a [{0}]!", - "RetrainDeclineOffer": "You declined the Coven Leader's retraining and your role has not been changed...", + "CovenLeaderRetrain": "Retrain offered", + "CovenLeaderNoRetrain": "You have ran out of Retrains!", + "CovenLeaderRetrainNonCoven": "You can't retrain a non-Coven!", + "CovenLeaderAcceptRetrain": "The player you attempted to retrain has accepted your training and is now a {0}!", + "CovenLeaderDeclineRetrain": "The player you attempted to retrain has declined your offer to retrain them into a {0}...", + "RetrainNotification": "The {0} has requested to retrain you into a {1}.
Vote yourself to accept the offer, vote someone else to decline.", + "RetrainAcceptOffer": "You have accepted the {0}'s retraining and you are now a {1}!", + "RetrainDeclineOffer": "You declined the {0}'s retraining and your role has not been changed...", "LuckyProbability": "Probability of surviving a kill", "ImpCanBeDoubleShot": "Impostors can have Double Shot", diff --git a/Roles/Coven/CovenLeader.cs b/Roles/Coven/CovenLeader.cs index 86d86b6f7..8ef8c8b11 100644 --- a/Roles/Coven/CovenLeader.cs +++ b/Roles/Coven/CovenLeader.cs @@ -1,11 +1,13 @@ using AmongUs.GameOptions; using Hazel; +using InnerNet; using System.Diagnostics.Metrics; using System.Text; using TOHE.Roles.Core; using TOHE.Roles.Crewmate; using TOHE.Roles.Double; using TOHE.Roles.Neutral; +using UnityEngine; using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; @@ -44,38 +46,47 @@ public override void Add(byte playerId) AbilityLimit = MaxRetrains.GetInt(); } + private void SendRPC(byte playerId, byte targetId) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + writer.WriteNetObject(_Player); + writer.Write(playerId); + writer.Write(AbilityLimit); + writer.Write(targetId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) + { + byte playerId = reader.ReadByte(); + AbilityLimit = reader.ReadSingle(); + } + public override bool CanUseKillButton(PlayerControl pc) => pc.IsAlive(); + public override string GetProgressText(byte playerId, bool comms) + => ColorString(AbilityLimit >= 1 ? GetRoleColor(CustomRoles.CovenLeader).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); + public override void SetKillCooldown(byte id) => RetrainCooldown.GetFloat(); public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (killer == null || target == null) return false; if (HasNecronomicon(killer)) return true; - if (killer.IsPlayerCoven() && !target.IsPlayerCoven()) return false; - var roleList = CustomRolesHelper.AllRoles.Where(role => (role.IsCoven() && (role.IsEnable() && !role.RoleExist(countDead: true)))).ToList(); - retrainPlayer[target.PlayerId] = roleList.RandomElement(); - foreach (byte cov in retrainPlayer.Keys) + if (AbilityLimit <= 0) { - GetPlayerById(cov).SendMessage(string.Format(GetString("RetrainAcceptOffer"), retrainPlayer[cov].ToColoredString())); + killer.Notify(GetString("CovenLeaderNoRetrain")); + return false; } - return false; - } - public override bool CheckVote(PlayerControl voter, PlayerControl target) - { - PlayerControl CL = CustomRoles.CovenLeader.GetPlayerListByRole().First(); - if ((voter == target) && retrainPlayer[voter.PlayerId].IsCoven()) + if (killer.IsPlayerCoven() && !target.IsPlayerCoven()) { - voter.RpcSetCustomRole(retrainPlayer[voter.PlayerId]); - CL.SendMessage(string.Format(GetString("CovenLeaderAcceptRetrain"), retrainPlayer[voter.PlayerId].ToColoredString())); - voter.SendMessage(string.Format(GetString("RetrainAcceptOffer"), retrainPlayer[voter.PlayerId].ToColoredString())); - retrainPlayer[voter.PlayerId] = CustomRoles.Crewmate; - AbilityLimit--; - return true; + killer.Notify(GetString("CovenLeaderRetrainNonCoven")); + return false; } - if ((voter != target) && retrainPlayer[voter.PlayerId].IsCoven()) + var roleList = CustomRolesHelper.AllRoles.Where(role => (role.IsCoven() && (role.IsEnable() && !role.RoleExist(countDead: true)))).ToList(); + retrainPlayer[target.PlayerId] = roleList.RandomElement(); + foreach (byte cov in retrainPlayer.Keys) { - CL.SendMessage(string.Format(GetString("CovenLeaderDeclineRetrain"), retrainPlayer[voter.PlayerId].ToColoredString())); - voter.SendMessage(GetString("RetrainDeclineOffer")); - retrainPlayer[voter.PlayerId] = CustomRoles.Crewmate; - return true; + SendMessage(string.Format(GetString("RetrainNotification"), CustomRoles.CovenLeader.ToColoredString(), retrainPlayer[cov].ToColoredString()), cov); } + killer.Notify(GetString("CovenLeaderRetrain")); + killer.ResetKillCooldown(); return false; } + } diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 7023599e8..3df9a2310 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -1,7 +1,12 @@ -using TOHE.Roles.Crewmate; +using AmongUs.GameOptions; +using Hazel; +using TOHE.Roles.Crewmate; using TOHE.Roles.Double; using TOHE.Roles.Neutral; +using InnerNet; using static TOHE.Options; +using static TOHE.Translator; +using static TOHE.Utils; using static UnityEngine.GraphicsBuffer; namespace TOHE; @@ -9,10 +14,94 @@ public abstract class CovenManager : RoleBase { public static PlayerControl necroHolder; - private static - public override void SetupCustomOption() + public enum VisOptionList { - base.SetupCustomOption(); + On, + CovenPerRole + } + public enum VentOptionList + { + On, + CovenPerRole + } + + private static readonly Dictionary CovenImpVisOptions = []; + private static readonly Dictionary CovenVentOptions = []; + public static void RunSetUpImpVisOptions(int Id) + { + foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven()).ToArray()) + { + SetUpImpVisOption(cov, Id, true, CovenImpVisMode); + Id++; + } + } + public static void RunSetUpVentOptions(int Id) + { + foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven() && (x is not CustomRoles.Medusa or CustomRoles.PotionMaster /* or CustomRoles.Sacrifist */)).ToArray()) + { + SetUpVentOption(cov, Id, true, CovenVentMode); + Id++; + } + } + private static void SetUpImpVisOption(CustomRoles role, int Id, bool defaultValue = true, OptionItem parent = null) + { + var roleName = GetRoleName(role); + Dictionary replacementDic = new() { { "%role%", ColorString(GetRoleColor(role), roleName) } }; + CovenImpVisOptions[role] = BooleanOptionItem.Create(Id, "%role%HasImpVis", defaultValue, TabGroup.CovenRoles, false).SetParent(CovenImpVisMode); + CovenImpVisOptions[role].ReplacementDictionary = replacementDic; + } + private static void SetUpVentOption(CustomRoles role, int Id, bool defaultValue = true, OptionItem parent = null) + { + var roleName = GetRoleName(role); + Dictionary replacementDic = new() { { "%role%", ColorString(GetRoleColor(role), roleName) } }; + CovenVentOptions[role] = BooleanOptionItem.Create(Id, "%role%CanVent", defaultValue, TabGroup.CovenRoles, false).SetParent(CovenVentMode); + CovenVentOptions[role].ReplacementDictionary = replacementDic; + } + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) + => HasNecronomicon(seen) ? ColorString(GetRoleColor(CustomRoles.CovenLeader), "♣") : string.Empty; + public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) + { + if (!(seer == target) && HasNecronomicon(target) && seer.IsPlayerCoven() && !HasNecronomicon(seer)) + { + return ColorString(GetRoleColor(CustomRoles.CovenLeader), "♣"); + } + return string.Empty; + } + private void SendRPC(byte playerId) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + writer.WriteNetObject(_Player); + writer.Write(necroHolder.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) + { + byte NecroId = reader.ReadByte(); + necroHolder = GetPlayerById(NecroId); + } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (!CovenHasImpVis.GetBool()) + opt.SetVision(false); + else if (CovenImpVisMode.GetValue() == 0) + opt.SetVision(true); + else + { + CovenImpVisOptions.TryGetValue(GetPlayerById(playerId).GetCustomRole(), out var option); + opt.SetVision(option.GetBool()); + } + } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + if (!CovenCanVent.GetBool()) + return false; + else if (CovenVentMode.GetValue() == 0) + return true; + else + { + CovenVentOptions.TryGetValue(pc.GetCustomRole(), out var option); + return option.GetBool(); + } } public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); @@ -23,7 +112,19 @@ public static void GiveNecronomicon() { PlayerControl rp = pcList.RandomElement(); necroHolder = rp; + necroHolder.Notify(GetString("NecronomiconNotification")); + } + } + public override void OnCoEndGame() + { + necroHolder = null; + } + public override void OnFixedUpdate(PlayerControl pc) + { + if (!necroHolder.IsAlive()) + { + GiveNecronomicon(); } } - public static bool HasNecronomicon(PlayerControl pc) => necroHolder == pc; + public static bool HasNecronomicon(PlayerControl pc) => necroHolder.Equals(pc); } diff --git a/Roles/Coven/HexMaster.cs b/Roles/Coven/HexMaster.cs index 4676e153d..3ae55eaff 100644 --- a/Roles/Coven/HexMaster.cs +++ b/Roles/Coven/HexMaster.cs @@ -17,12 +17,12 @@ internal class HexMaster : CovenManager public static bool HasEnabled => playerIdList.Any(); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; - public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenPower; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenKilling; //==================================================================\\ private static OptionItem ModeSwitchAction; private static OptionItem HexesLookLikeSpells; - private static OptionItem HasImpostorVision; + //private static OptionItem HasImpostorVision; private static readonly Dictionary HexMode = []; private static readonly Dictionary> HexedPlayer = []; @@ -43,7 +43,7 @@ public override void SetupCustomOption() SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.HexMaster, 1, zeroOne: false); ModeSwitchAction = StringOptionItem.Create(Id + 10, GeneralOption.ModeSwitchAction, EnumHelper.GetAllNames(), 2, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); HexesLookLikeSpells = BooleanOptionItem.Create(Id + 11, "HexesLookLikeSpells", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); - HasImpostorVision = BooleanOptionItem.Create(Id + 12, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); + //HasImpostorVision = BooleanOptionItem.Create(Id + 12, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); } public override void Init() { @@ -102,7 +102,7 @@ public static void ReceiveRPC(MessageReader reader, bool doHex) } } - public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); + //public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => true; diff --git a/Roles/Coven/Jinx.cs b/Roles/Coven/Jinx.cs index e65fe1602..f4acb5d34 100644 --- a/Roles/Coven/Jinx.cs +++ b/Roles/Coven/Jinx.cs @@ -16,8 +16,8 @@ internal class Jinx : CovenManager //==================================================================\\ private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; + //private static OptionItem CanVent; + //private static OptionItem HasImpostorVision; private static OptionItem JinxSpellTimes; private static OptionItem killAttacker; @@ -26,8 +26,8 @@ public override void SetupCustomOption() SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Jinx, 1, zeroOne: false); KillCooldown = FloatOptionItem.Create(Id + 10, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 20f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]) .SetValueFormat(OptionFormat.Seconds); - CanVent = BooleanOptionItem.Create(Id + 11, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); - HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); + //CanVent = BooleanOptionItem.Create(Id + 11, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); + //HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); JinxSpellTimes = IntegerOptionItem.Create(Id + 14, "JinxSpellTimes", new(1, 15, 1), 3, TabGroup.CovenRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]) .SetValueFormat(OptionFormat.Times); @@ -59,11 +59,11 @@ public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl t } return false; } - public override void ApplyGameOptions(IGameOptions opt, byte babushka) => opt.SetVision(HasImpostorVision.GetBool()); + //public override void ApplyGameOptions(IGameOptions opt, byte babushka) => opt.SetVision(HasImpostorVision.GetBool()); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; - public override bool CanUseImpostorVentButton(PlayerControl player) => CanVent.GetBool(); + //public override bool CanUseImpostorVentButton(PlayerControl player) => CanVent.GetBool(); public override string GetProgressText(byte playerId, bool comms) diff --git a/Roles/Coven/Medusa.cs b/Roles/Coven/Medusa.cs index 61fb47a7a..3362afb49 100644 --- a/Roles/Coven/Medusa.cs +++ b/Roles/Coven/Medusa.cs @@ -17,8 +17,8 @@ internal class Medusa : CovenManager private static OptionItem KillCooldown; private static OptionItem KillCooldownAfterStoneGazing; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; + //private static OptionItem CanVent; + //private static OptionItem HasImpostorVision; public override void SetupCustomOption() { @@ -27,8 +27,8 @@ public override void SetupCustomOption() .SetValueFormat(OptionFormat.Seconds); KillCooldownAfterStoneGazing = FloatOptionItem.Create(Id + 15, "KillCooldownAfterStoneGazing", new(0f, 180f, 2.5f), 40f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]) .SetValueFormat(OptionFormat.Seconds); - CanVent = BooleanOptionItem.Create(Id + 11, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); - HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); + //CanVent = BooleanOptionItem.Create(Id + 11, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); + //HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); } public override void Init() { @@ -39,9 +39,9 @@ public override void Add(byte playerId) playerIdList.Add(playerId); } 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 void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override bool CanUseKillButton(PlayerControl pc) => true; - public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); + //public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) diff --git a/Roles/Coven/Poisoner.cs b/Roles/Coven/Poisoner.cs index 13c6ae441..36d94992b 100644 --- a/Roles/Coven/Poisoner.cs +++ b/Roles/Coven/Poisoner.cs @@ -23,9 +23,9 @@ private class PoisonedInfo(byte poisonerId, float killTimer) //==================================================================\\ private static OptionItem OptionKillDelay; - private static OptionItem CanVent; + //private static OptionItem CanVent; public static OptionItem KillCooldown; - private static OptionItem HasImpostorVision; + //private static OptionItem HasImpostorVision; private static readonly Dictionary PoisonedPlayers = []; @@ -38,8 +38,8 @@ public override void SetupCustomOption() .SetValueFormat(OptionFormat.Seconds); OptionKillDelay = FloatOptionItem.Create(Id + 11, "PoisonerKillDelay", new(1f, 60f, 1f), 10f, TabGroup.CovenRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]) .SetValueFormat(OptionFormat.Seconds); - CanVent = BooleanOptionItem.Create(Id + 12, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]); - HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]); + //CanVent = BooleanOptionItem.Create(Id + 12, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]); + //HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]); } public override void Init() @@ -53,10 +53,10 @@ public override void Add(byte playerId) { playerIdList.Add(playerId); } - public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); + //public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; - public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); + //public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) diff --git a/Roles/Coven/PotionMaster.cs b/Roles/Coven/PotionMaster.cs index 122b731f2..082d4eaa7 100644 --- a/Roles/Coven/PotionMaster.cs +++ b/Roles/Coven/PotionMaster.cs @@ -19,8 +19,8 @@ internal class PotionMaster : CovenManager private static OptionItem KillCooldown; private static OptionItem RitualMaxCount; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; + //private static OptionItem CanVent; + //private static OptionItem HasImpostorVision; private static readonly Dictionary> RitualTarget = []; @@ -31,8 +31,8 @@ public override void SetupCustomOption() .SetValueFormat(OptionFormat.Seconds); RitualMaxCount = IntegerOptionItem.Create(Id + 11, "RitualMaxCount", new(1, 15, 1), 5, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]) .SetValueFormat(OptionFormat.Times); - CanVent = BooleanOptionItem.Create(Id + 12, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]); - HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]); + //CanVent = BooleanOptionItem.Create(Id + 12, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]); + //HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]); } public override void Init() { @@ -64,11 +64,11 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) RitualTarget[playerId].Add(reader.ReadByte()); } - public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); + //public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; - public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); - public override bool CanUseSabotage(PlayerControl pc) => true; + public override bool CanUseImpostorVentButton(PlayerControl pc) => true; + //public override bool CanUseSabotage(PlayerControl pc) => true; public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) From 85cf456158a89492f695329241d7e840a36d2170 Mon Sep 17 00:00:00 2001 From: MargaretTheFool Date: Thu, 12 Sep 2024 15:08:34 -0400 Subject: [PATCH 003/101] rit and role classes --- Modules/OptionHolder.cs | 2 +- Modules/RPC.cs | 4 ++ Resources/Lang/en_US.json | 15 +++- Roles/Coven/Conjurer.cs | 0 Roles/Coven/CovenLeader.cs | 2 - Roles/Coven/Dreamweaver.cs | 0 Roles/Coven/Illusionist.cs | 0 Roles/Coven/MoonDancer.cs | 0 Roles/Coven/Ritualist.cs | 136 ++++++++++++++++++++++++++++++++++++ Roles/Coven/Sacrifist.cs | 0 Roles/Coven/Sorceress.cs | 0 Roles/Coven/Spellcaster.cs | 0 Roles/Coven/VoodooMaster.cs | 0 main.cs | 1 + 14 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 Roles/Coven/Conjurer.cs create mode 100644 Roles/Coven/Dreamweaver.cs create mode 100644 Roles/Coven/Illusionist.cs create mode 100644 Roles/Coven/MoonDancer.cs create mode 100644 Roles/Coven/Ritualist.cs create mode 100644 Roles/Coven/Sacrifist.cs create mode 100644 Roles/Coven/Sorceress.cs create mode 100644 Roles/Coven/Spellcaster.cs create mode 100644 Roles/Coven/VoodooMaster.cs diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index c2b917a4b..0de45da4d 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -631,7 +631,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 29700 last id for roles/add-ons (Next use 29800) + // 29900 last id for roles/add-ons (Next use 30000) // Limit id for roles/add-ons --- "59999" //####################################### diff --git a/Modules/RPC.cs b/Modules/RPC.cs index aacfc5536..a1bb9e389 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -116,6 +116,7 @@ enum CustomRPC : byte // 197/255 USED SetVultureArrow, SetRadarArrow, SyncVultureBodyAmount, + BloodRitual, //SetTrackerTarget, SpyRedNameSync, SpyRedNameRemove, @@ -639,6 +640,9 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.SetChameleonTimer: Chameleon.ReceiveRPC_Custom(reader); break; + case CustomRPC.BloodRitual: + Ritualist.ReceiveRPC_Custom(reader, __instance); + break; case CustomRPC.SetAlchemistTimer: Alchemist.ReceiveRPC(reader); break; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 2776dafd0..fbff7cb12 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -323,6 +323,7 @@ "PotionMaster": "Potion Master", "Necromancer": "Necromancer", "CovenLeader": "Coven Leader", + "Ritualist": "Ritualist", "Warden": "Warden", "Minion": "Minion", "Ghastly": "Ghastly", @@ -633,6 +634,7 @@ "PotionMasterInfo": "Use your potions to your advantage", "NecromancerInfo": "Kill your killer to defy death", "CovenLeaderInfo": "Help your teammates by retraining them", + "RitualistInfo": "Perform Blood Rituals to Enchant other players!", "WardenInfo": "(Ghost) Alert about danger", "MinionInfo": "(Ghost) Blind enemies", "LoversInfo": "Stay alive and win together", @@ -911,7 +913,6 @@ "VultureInfoLong": "(Neutrals):\nAs the Vulture, report bodies to win!\n\nWhen you report a body, if your eat cooldown is up, you'll eat the body (makes it unreportable).\nIf your eat ability is still on cooldown, then you'll report the body normally.\n\nAdditionally, you'll report bodies normally if the maximum bodies eaten per round is reached.", "TaskinatorInfoLong": "(Neutrals):\nAs the Taskinator, whenever you finish a task, the task will be bombed. When another player completes the bombed task, the bomb will detonate, and the player will die.\n\nYou win if you survive till the end and Crew doesn't win.\n\n Note: Taskinator bombs ignore all protection.", "BenefactorInfoLong": "(Crewmates):\nAs the Benefactor, whenever you finish a task, that task will be marked. When another player completes the marked task, they get a temporary shield.\n\n Note: Shield only protects from direct kill attacks.", - "MedusaInfoLong": "(Coven):\nAs the Medusa, you can stone bodies much like cleaning a body.\nStoned bodies cannot be reported.\n\nKill everyone to win.", "SpiritcallerInfoLong": "(Neutrals):\nAs the Spiritcaller, your victims become Evil Spirits after they die. These spirits can help you win by freezing other players briefly or blocking their vision. Alternatively, the spirits can give you a shield that protects you briefly from an attempted kill.", "AmnesiacInfoLong": "(Neutrals):\nAs the Amnesiac, use your report button to remember a role.\n\nIf the target was an Impostor, you'll become a Refugee.\nIf the target was a crewmate, you'll become the target role if compatible (otherwise you become an Engineer).\nIf the target was a passive neutral or a neutral killer not specified, you'll become the role defined in the settings.\nIf the target was a neutral killer of a select few, you'll become the role they are.", "ImitatorInfoLong": "(Neutrals):\nAs the Imitator, use your kill button to imitate a player.\n\nYou'll either become a Sheriff, a Refugee, or some Neutral.", @@ -928,13 +929,15 @@ "RomanticInfoLong": "(Neutrals):\nThe Romantic can pick their lover partner using their kill button (this can be done at any point of the game). Once they've picked their partner, they can use their kill button to give their partner a temporary shield that protects them from attacks. If their lover partner dies, the Romantic's role will change according to the following conditions:\n1. If their partner was an Impostor, the Romantic becomes the Refugee\n2. If their partner was a Neutral Killer, then they become Ruthless Romantic.\n3. If their partner was a Crewmate or a non-killing neutral, the Romantic becomes the Vengeful Romantic. \n\nThe Romantic wins with the winning team if their partner wins.\nNote: If your role changes, your win condition will be changed accordingly", "RuthlessRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A neutral killer) is killed. As a Ruthless Romantic, you win if you kill everyone and are the last one standing. If you win, your dead partner will also win with you.", "VengefulRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A crew or non-neutral killer) is killed. As a Vengeful Romantic, your goal is to avenge your partner, which means you must kill the killer of your partner. If you succeed, then you and your partner win with the winning team at the end. If you try to kill someone other than your partner's killer, then you will die by misfire.", + "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", "PoisonerInfoLong": "(Coven):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", "HexMasterInfoLong": "(Coven):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", - "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", "JinxInfoLong": "(Coven):\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.", + "MedusaInfoLong": "(Coven):\nAs the Medusa, you can stone bodies much like cleaning a body.\nStoned bodies cannot be reported.\n\nKill everyone to win.", "PotionMasterInfoLong": "(Coven):\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": "(Coven):\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.", "CovenLeaderInfoLong": "(Coven): The Coven Leader can use their kill button on a fellow Coven member to retrain them into a random Coven role that isn’t currently in the game. During the next meeting, that Coven member will be notified that the Coven Leader wishes to retrain them. They can vote themselves to accept the retrain, or vote otherwise to deny it. Denying the retrain does not take away an ability usage.
With the Necronomicon, you cannot retrain, and can only kill other players.", + "RitualistInfoLong": "During a meeting, the Ritualist can perform a Blood Ritual to guess a player’s exact role. If the Ritualist is correct, that player will be converted to Coven. If the Ritualist is incorrect, they will not die but will be unable to Blood Ritual until the next meeting.
The command is /rt id role.
With the Necronomicon, the Ritualist can kill.", "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.", @@ -1875,6 +1878,14 @@ "RetrainAcceptOffer": "You have accepted the {0}'s retraining and you are now a {1}!", "RetrainDeclineOffer": "You declined the {0}'s retraining and your role has not been changed...", + "RitualistMaxRitsPerRound": "Max Blood Rituals per Round", + "RitualistRitualSuccess": "You preformed a successful blood ritual on {0} and they are now part of the Coven!", + "RitualistRitualFail": "You have failed your blood ritual and may no longer preform any blood rituals this meeting...", + "RitualistRitualImpossible": "Your Blood Ritual was successful, however, this player is unenchantable.", + "RitualistRitualMax": "You've already used the max amount of Blood Rituals for this meeting.", + "RitualistCommandHelp": "Instructions: /rt [Player ID] [Role Name] \nExample: /rt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs" + "RitualistConvertNotif": "Your role has been guessed by the {0} and you are now apart of the Coven!", + "LuckyProbability": "Probability of surviving a kill", "ImpCanBeDoubleShot": "Impostors can have Double Shot", "CrewCanBeDoubleShot": "Crewmates can have Double Shot", diff --git a/Roles/Coven/Conjurer.cs b/Roles/Coven/Conjurer.cs new file mode 100644 index 000000000..e69de29bb diff --git a/Roles/Coven/CovenLeader.cs b/Roles/Coven/CovenLeader.cs index 8ef8c8b11..87abe0122 100644 --- a/Roles/Coven/CovenLeader.cs +++ b/Roles/Coven/CovenLeader.cs @@ -4,8 +4,6 @@ using System.Diagnostics.Metrics; using System.Text; using TOHE.Roles.Core; -using TOHE.Roles.Crewmate; -using TOHE.Roles.Double; using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Options; diff --git a/Roles/Coven/Dreamweaver.cs b/Roles/Coven/Dreamweaver.cs new file mode 100644 index 000000000..e69de29bb diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs new file mode 100644 index 000000000..e69de29bb diff --git a/Roles/Coven/MoonDancer.cs b/Roles/Coven/MoonDancer.cs new file mode 100644 index 000000000..e69de29bb diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs new file mode 100644 index 000000000..aca9607af --- /dev/null +++ b/Roles/Coven/Ritualist.cs @@ -0,0 +1,136 @@ + +namespace TOHE.Roles.Coven; + +internal class Ritualist : CovenManager +{ + //===========================SETUP================================\\ + private const int Id = 29900; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Ritualist); + public override bool IsDesyncRole => true; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenPower; + //==================================================================\\ + + public static OptionItem MaxRitsPerRound; + + public override void SetupCustomOption() + { + SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Ritualist, 1, zeroOne: false); + MaxRitsPerRound = IntegerOptionItem.Create(Id + 10, "RitualistMaxRitsPerRound", new(1, 15, 1), 2, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Add(byte PlayerId) + { + AbilityLimit = MaxRitsPerRound.GetInt(); + } + + public static void ReceiveRPC_Custom(MessageReader reader, PlayerControl pc) + { + int PlayerId = reader.ReadByte(); + RitualistMsgCheck(pc, $"/rt {PlayerId}", true); + } + public override bool CanUseKillButton(PlayerControl pc) => HasNecronomicon(pc); + public override string GetProgressText(byte playerId, bool comms) + => ColorString(AbilityLimit >= 1 ? GetRoleColor(CustomRoles.CovenLeader).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); + public override void OnReportDeadBody(PlayerControl hatsune, NetworkedPlayerInfo miku) + { + AbilityLimit = MaxRitsPerRound.GetInt(); + } + public static bool RitualistMsgCheck(PlayerControl pc, string msg, bool isUI = false) + { + if (!AmongUsClient.Instance.AmHost) return false; + if (!GameStates.IsInGame || pc == null) return false; + if (!pc.Is(CustomRoles.Ritualist)) return false; + msg = msg.Trim().ToLower(); + if (msg.Length < 3 || msg[..4] != "/rt") return false; + + + if (msg == "/rt") + { + string text = GetString("PlayerIdList"); + foreach (var npc in Main.AllAlivePlayerControls) + text += "\n" + npc.PlayerId.ToString() + " → " + npc.GetRealName(); + SendMessage(text, pc.PlayerId); + return true; + } + + + if (AbilityLimit <= 0) + { + pc.ShowInfoMessage(isUI, GetString("RitualistRitualMax")); + return true; + } + + if (!MsgToPlayerAndRole(msg, out byte targetId, out CustomRoles role, out string error)) + { + pc.ShowInfoMessage(isUI, error); + return true; + } + var target = Utils.GetPlayerById(targetId); + + if (!target.Is(role)) + { + pc.ShowInfoMessage(isUI, GetString("RitualistRitualFail")); + AbilityLimit = 0; + return true; + } + if (target.IsTransformedNeutralApocalypse() || (target.Is(CustomRoles.NiceMini) || target.Is(CustomRoles.EvilMini) && Mini.Age < 18) || target.Is(CustomRoles.Solsticer) || !target.IsAlive() || target.Is(CustomRoles.Loyal) || !(target.GetCustomSubRoles().Contains(CustomRoles.Hurried) && !Hurried.CanBeConverted.GetBool()) + { + pc.ShowInfoMessage(isUI, GetString("RitualistRitualImpossible")); + return true; + } + + Logger.Info($"{pc.GetNameWithRole()} enchant {target.GetNameWithRole()}", "Ritualist"); + + string Name = target.GetRealName(); + + AbilityLimit--; + + target.RpcSetCustomRole(CustomRoles.Enchanted); + SendMessage(string.Format(GetString("RitualistConvertNotif"), CustomRoles.Ritualist.ToColoredString()), target.PlayerId); + SendMessage(string.Format(GetString("RitualistRitualSuccess"), target.GetRealName()), pc.PlayerId); + return true; + } + private static bool MsgToPlayerAndRole(string msg, out byte id, out CustomRoles role, out string error) + { + if (msg.StartsWith("/")) msg = msg.Replace("/", string.Empty); + + Regex r = new("\\d+"); + MatchCollection mc = r.Matches(msg); + string result = string.Empty; + for (int i = 0; i < mc.Count; i++) + { + result += mc[i];//匹配结果是完整的数字,此处可以不做拼接的 + } + + if (int.TryParse(result, out int num)) + { + id = Convert.ToByte(num); + } + else + { + id = byte.MaxValue; + error = GetString("RitualistCommandHelp"); + role = new(); + return false; + } + + PlayerControl target = Utils.GetPlayerById(id); + if (target == null || target.Data.IsDead) + { + error = GetString("GuessNull"); + role = new(); + return false; + } + + if (!ChatCommands.GetRoleByName(msg, out role)) + { + error = GetString("RitualistCommandHelp"); + return false; + } + + error = string.Empty; + return true; + } +} \ No newline at end of file diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs new file mode 100644 index 000000000..e69de29bb diff --git a/Roles/Coven/Sorceress.cs b/Roles/Coven/Sorceress.cs new file mode 100644 index 000000000..e69de29bb diff --git a/Roles/Coven/Spellcaster.cs b/Roles/Coven/Spellcaster.cs new file mode 100644 index 000000000..e69de29bb diff --git a/Roles/Coven/VoodooMaster.cs b/Roles/Coven/VoodooMaster.cs new file mode 100644 index 000000000..e69de29bb diff --git a/main.cs b/main.cs index 536ace846..fa9f4df6c 100644 --- a/main.cs +++ b/main.cs @@ -896,6 +896,7 @@ public enum CustomRoles Diseased, DoubleShot, Egoist, + Enchanted, Evader, EvilSpirit, Flash, From 897a1d8c7bbcc600787212e9b2fa35abf73145a6 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:58:17 -0400 Subject: [PATCH 004/101] h --- Modules/AntiBlackout.cs | 19 +++- Modules/CustomRolesHelper.cs | 12 +-- Modules/CustomWinnerHolder.cs | 5 +- Modules/ExtendedPlayerControl.cs | 10 ++ Modules/GuessManager.cs | 14 +++ Modules/NameColorManager.cs | 3 + Modules/OptionHolder.cs | 3 + Modules/Utils.cs | 6 ++ Patches/ChatCommandPatch.cs | 7 ++ Patches/CheckGameEndPatch.cs | 34 ++++++- Patches/MeetingHudPatch.cs | 4 + Patches/OutroPatch.cs | 4 + Patches/PlayerControlPatch.cs | 4 + Resources/Lang/en_US.json | 8 +- Roles/Coven/CovenLeader.cs | 9 -- Roles/Coven/CovenManager.cs | 4 +- Roles/Coven/Ritualist.cs | 158 ++++++++++++++++++++++--------- main.cs | 13 +++ 18 files changed, 245 insertions(+), 72 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 720981393..cda1c2721 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -9,7 +9,7 @@ namespace TOHE; public static class AntiBlackout { /// - /// Check num alive Impostors & Crewmates & NeutralKillers + /// Check num alive Impostors & Crewmates & NeutralKillers & Coven /// public static bool BlackOutIsActive => !Options.DisableAntiBlackoutProtects.GetBool() && CheckBlackOut(); @@ -21,6 +21,7 @@ public static bool CheckBlackOut() HashSet Impostors = []; HashSet Crewmates = []; HashSet NeutralKillers = []; + HashSet Coven = []; var lastExiled = ExileControllerWrapUpPatch.AntiBlackout_LastExiled; foreach (var pc in Main.AllAlivePlayerControls) @@ -36,6 +37,10 @@ public static bool CheckBlackOut() else if (pc.IsNeutralKiller() || pc.IsNeutralApocalypse()) NeutralKillers.Add(pc.PlayerId); + //Coven + if (pc.Is(Custom_Team.Coven)) + Coven.Add(pc.PlayerId); + // Crewmate else Crewmates.Add(pc.PlayerId); } @@ -43,10 +48,12 @@ public static bool CheckBlackOut() var numAliveImpostors = Impostors.Count; var numAliveCrewmates = Crewmates.Count; var numAliveNeutralKillers = NeutralKillers.Count; + var numAliveCoven = Coven.Count; Logger.Info($" {numAliveImpostors}", "AntiBlackout Num Alive Impostors"); Logger.Info($" {numAliveCrewmates}", "AntiBlackout Num Alive Crewmates"); Logger.Info($" {numAliveNeutralKillers}", "AntiBlackout Num Alive Neutral Killers"); + Logger.Info($" {numAliveCoven}", "AntiBlackout Num Alive Coven"); var BlackOutIsActive = false; @@ -56,12 +63,20 @@ public static bool CheckBlackOut() // Alive Impostors > or = others team count if (!BlackOutIsActive) - BlackOutIsActive = (numAliveNeutralKillers + numAliveCrewmates) <= numAliveImpostors; + BlackOutIsActive = (numAliveNeutralKillers + numAliveCrewmates + numAliveCoven) <= numAliveImpostors; // One Impostor and one Neutral Killer is alive, and living Crewmates very few if (!BlackOutIsActive) BlackOutIsActive = numAliveNeutralKillers == 1 && numAliveImpostors == 1 && numAliveCrewmates <= 2; + // One Neutral Killer and one Coven is alive, and living Crewmates very few + if (!BlackOutIsActive) + BlackOutIsActive = numAliveNeutralKillers == 1 && numAliveCoven == 1 && numAliveCrewmates <= 2; + + // One Coven and one Impostor is alive, and living Crewmates very few + if (!BlackOutIsActive) + BlackOutIsActive = numAliveCoven == 1 && numAliveImpostors == 1 && numAliveCrewmates <= 2; + Logger.Info($" {BlackOutIsActive}", "BlackOut Is Active"); return BlackOutIsActive; } diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index fdf8af3f6..314fe81e4 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -320,7 +320,8 @@ CustomRoles.Recruit or CustomRoles.Infected or CustomRoles.Contagious or CustomRoles.Soulless or - CustomRoles.Madmate; + CustomRoles.Madmate or + CustomRoles.Enchanted; public static bool IsNotKnightable(this CustomRoles role) { @@ -363,7 +364,8 @@ or CustomRoles.Recruit or CustomRoles.Infected or CustomRoles.Contagious or CustomRoles.Rascal - or CustomRoles.Soulless; + or CustomRoles.Soulless + or CustomRoles.Enchanted; } public static bool IsImpOnlyAddon(this CustomRoles role) @@ -1228,8 +1230,6 @@ var r when r.IsCoven() => CountTypes.Coven, CustomRoles.Solsticer => CustomWinner.Solsticer, CustomRoles.Collector => CustomWinner.Collector, CustomRoles.BloodKnight => CustomWinner.BloodKnight, - CustomRoles.Poisoner => CustomWinner.Poisoner, - CustomRoles.HexMaster => CustomWinner.HexMaster, CustomRoles.Cultist => CustomWinner.Cultist, CustomRoles.Wraith => CustomWinner.Wraith, CustomRoles.Bandit => CustomWinner.Bandit, @@ -1237,20 +1237,16 @@ var r when r.IsCoven() => CountTypes.Coven, CustomRoles.SerialKiller => CustomWinner.SerialKiller, CustomRoles.Quizmaster => CustomWinner.Quizmaster, CustomRoles.Werewolf => CustomWinner.Werewolf, - CustomRoles.Necromancer => CustomWinner.Necromancer, CustomRoles.Huntsman => CustomWinner.Huntsman, CustomRoles.Juggernaut => CustomWinner.Juggernaut, CustomRoles.Infectious => CustomWinner.Infectious, CustomRoles.Virus => CustomWinner.Virus, CustomRoles.Specter => CustomWinner.Specter, - CustomRoles.Jinx => CustomWinner.Jinx, CustomRoles.CursedSoul => CustomWinner.CursedSoul, - CustomRoles.PotionMaster => CustomWinner.PotionMaster, CustomRoles.Pickpocket => CustomWinner.Pickpocket, CustomRoles.Traitor => CustomWinner.Traitor, CustomRoles.Vulture => CustomWinner.Vulture, CustomRoles.Apocalypse => CustomWinner.Apocalypse, - CustomRoles.Medusa => CustomWinner.Medusa, CustomRoles.Spiritcaller => CustomWinner.Spiritcaller, CustomRoles.Glitch => CustomWinner.Glitch, CustomRoles.PunchingBag => CustomWinner.PunchingBag, diff --git a/Modules/CustomWinnerHolder.cs b/Modules/CustomWinnerHolder.cs index 771bde02d..b24429c1e 100644 --- a/Modules/CustomWinnerHolder.cs +++ b/Modules/CustomWinnerHolder.cs @@ -72,7 +72,10 @@ public static bool CheckForConvertedWinner(byte playerId) ResetAndSetWinner(CustomWinner.Infectious); return true; case CustomRoles.Contagious: - ResetAndSetWinner(CustomWinner.Virus); + ResetAndSetWinner(CustomWinner.Virus); + return true; + case CustomRoles.Enchanted: + ResetAndSetWinner(CustomWinner.Coven); return true; } } diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 84e5d03a7..ab6e03811 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -948,6 +948,12 @@ public static bool KnowRoleTarget(PlayerControl seer, PlayerControl target, bool logger.Info($"Imps Can See Each Others Roles"); return true; } + else if (seer.Is(Custom_Team.Coven) && target.Is(Custom_Team.Coven)) + { + if (isVanilla) + logger.Info($"Coven Can See Each Others Roles"); + return true; + } else if (Madmate.MadmateKnowWhosImp.GetBool() && seer.Is(CustomRoles.Madmate) && target.Is(Custom_Team.Impostor)) { if (isVanilla) @@ -1074,6 +1080,10 @@ public static bool KnowSubRoleTarget(PlayerControl seer, PlayerControl target) else if (seer.Is(CustomRoles.Egoist) && target.Is(CustomRoles.Egoist) && Egoist.ImpEgoistVisibalToAllies.GetBool()) return true; } + if (seer.Is(Custom_Team.Coven)) + { + if (target.Is(CustomRoles.Enchanted) || target.Is(Custom_Team.Coven)) return true; + } else if (Admirer.HasEnabled && Admirer.CheckKnowRoleTarget(seer, target)) return true; else if (Cultist.HasEnabled && Cultist.KnowRole(seer, target)) return true; else if (Infectious.HasEnabled && Infectious.KnowRole(seer, target)) return true; diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 1e4e4b27c..772148b41 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -145,6 +145,11 @@ public static bool GuesserMsg(PlayerControl pc, string msg, bool isUI = false) pc.ShowInfoMessage(isUI, GetString("GuessNotAllowed")); return true; } + if (pc.GetCustomRole().IsCoven() && !Options.CovenCanGuess.GetBool() && !pc.Is(CustomRoles.Guesser)) + { + pc.ShowInfoMessage(isUI, GetString("GuessNotAllowed")); + return true; + } if (operate == 1) @@ -273,6 +278,13 @@ public static bool GuesserMsg(PlayerControl pc, string msg, bool isUI = false) return true; } + // Coven Cant Guess Addons + if (Options.CovenCanGuess.GetBool() && pc.Is(Custom_Team.Coven) && !pc.Is(CustomRoles.Guesser)) + { + pc.ShowInfoMessage(isUI, GetString("GuessAdtRole")); + return true; + } + // Neutrals Cant Guess Addons if ((Options.NeutralKillersCanGuess.GetBool() || Options.PassiveNeutralsCanGuess.GetBool()) && pc.Is(Custom_Team.Neutral) && !(pc.Is(CustomRoles.Doomsayer) || pc.Is(CustomRoles.Guesser))) { @@ -581,6 +593,8 @@ public static void Postfix(MeetingHud __instance) CreateGuesserButton(__instance); if (PlayerControl.LocalPlayer.IsAlive() && PlayerControl.LocalPlayer.GetCustomRole().IsNonNK() && Options.PassiveNeutralsCanGuess.GetBool()) CreateGuesserButton(__instance); + if (PlayerControl.LocalPlayer.IsAlive() && PlayerControl.LocalPlayer.GetCustomRole().IsCoven() && Options.CovenCanGuess.GetBool()) + CreateGuesserButton(__instance); else if (PlayerControl.LocalPlayer.GetCustomRole() is CustomRoles.Doomsayer && !Options.PassiveNeutralsCanGuess.GetBool() && !Doomsayer.CheckCantGuess) CreateGuesserButton(__instance); } diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index b2d491c83..67a4b3785 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -42,6 +42,9 @@ private static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target if (seer.Is(Custom_Team.Impostor) && target.GetCustomRole().IsGhostRole() && target.GetCustomRole().IsImpostor()) color = Main.roleColors[CustomRoles.Madmate]; if (seer.Is(CustomRoles.Madmate) && target.Is(CustomRoles.Madmate) && Madmate.MadmateKnowWhosMadmate.GetBool()) color = Main.roleColors[CustomRoles.Madmate]; + // Coven + if ((seer.Is(Custom_Team.Coven) || seer.Is(CustomRoles.Enchanted)) && (target.Is(Custom_Team.Coven) || target.Is(CustomRoles.Enchanted))) color = Main.roleColors[CustomRoles.Coven]; + // Cultist if (Cultist.NameRoleColor(seer, target)) color = Main.roleColors[CustomRoles.Cultist]; diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 0de45da4d..eb0776524 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -139,6 +139,7 @@ private enum RatesZeroOne public static OptionItem EnableKillerLeftCommand; public static OptionItem ShowMadmatesInLeftCommand; public static OptionItem ShowApocalypseInLeftCommand; + public static OptionItem ShowCovenInLeftCommand; public static OptionItem SeeEjectedRolesInMeeting; public static OptionItem KickLowLevelPlayer; @@ -1062,6 +1063,8 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetParent(EnableKillerLeftCommand); ShowApocalypseInLeftCommand = BooleanOptionItem.Create(60043, "ShowApocalypseInLeftCommand", true, TabGroup.SystemSettings, false) .SetParent(EnableKillerLeftCommand); + ShowCovenInLeftCommand = BooleanOptionItem.Create(60044, "ShowCovenInLeftCommand", true, TabGroup.SystemSettings, false) + .SetParent(EnableKillerLeftCommand); SeeEjectedRolesInMeeting = BooleanOptionItem.Create(60041, "SeeEjectedRolesInMeeting", true, TabGroup.SystemSettings, false) .HideInHnS(); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index d18885f87..803968097 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -425,6 +425,9 @@ public static Color GetTeamColor(PlayerControl player) case Custom_Team.Neutral: hexColor = "#7f8c8d"; break; + case Custom_Team.Coven: + hexColor = "#ac42f2"; + break; } _ = ColorUtility.TryParseHtmlString(hexColor, out Color c); @@ -2090,6 +2093,9 @@ static int GetInfoSize(string RoleInfo) if (Options.PassiveNeutralsCanGuess.GetBool() && seer.GetCustomRole().IsNonNK() && !seer.Is(CustomRoles.Doomsayer)) TargetPlayerName = GetTragetId; + + if (Options.CovenCanGuess.GetBool() && seer.GetCustomRole().IsCoven()) + TargetPlayerName = GetTragetId; } } else // Guesser Mode is Off ID diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index d8d06c4eb..14bd5ca28 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -8,6 +8,7 @@ using TOHE.Modules.ChatManager; using TOHE.Roles.Core; using TOHE.Roles.Core.AssignManager; +using TOHE.Roles.Coven; using TOHE.Roles.Crewmate; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; @@ -66,6 +67,7 @@ public static bool Prefix(ChatController __instance) if (PlayerControl.LocalPlayer.GetRoleClass() is Councillor cl && cl.MurderMsg(PlayerControl.LocalPlayer, text)) goto Canceled; if (Nemesis.NemesisMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; if (Retributionist.RetributionistMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Ritualist.RitualistMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; if (Medium.MsMsg(PlayerControl.LocalPlayer, text)) goto Canceled; if (PlayerControl.LocalPlayer.GetRoleClass() is Swapper sw && sw.SwapMsg(PlayerControl.LocalPlayer, text)) goto Canceled; Directory.CreateDirectory(modTagsFiles); @@ -358,6 +360,7 @@ public static bool Prefix(ChatController __instance) int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); + int covnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Coven)); var sub = new StringBuilder(); sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); @@ -368,6 +371,9 @@ public static bool Prefix(ChatController __instance) if (Options.ShowApocalypseInLeftCommand.GetBool()) sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); + if (Options.ShowCovenInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.CovenCount"), covnum)); + sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); Utils.SendMessage(sub.ToString(), PlayerControl.LocalPlayer.PlayerId); @@ -1888,6 +1894,7 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can if (Medium.MsMsg(player, text)) { Logger.Info($"Is Medium command", "OnReceiveChat"); return; } if (Nemesis.NemesisMsgCheck(player, text)) { Logger.Info($"Is Nemesis Revenge command", "OnReceiveChat"); return; } if (Retributionist.RetributionistMsgCheck(player, text)) { Logger.Info($"Is Retributionist Revenge command", "OnReceiveChat"); return; } + if (Ritualist.RitualistMsgCheck(player, text)) { Logger.Info($"Is Ritualist command", "OnReceiveChat"); return; } Directory.CreateDirectory(modTagsFiles); Directory.CreateDirectory(vipTagsFiles); diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index e7cc8d9a4..91f3ee0bb 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -101,6 +101,13 @@ public static bool Prefix() WinnerIds.Add(pc.PlayerId); } break; + case CustomWinner.Coven: + if (((pc.Is(Custom_Team.Coven) || pc.Is(CustomRoles.Enchanted)) && (countType == CountTypes.Coven || pc.Is(CustomRoles.Soulless))) + && !WinnerIds.Contains(pc.PlayerId)) + { + WinnerIds.Add(pc.PlayerId); + } + break; case CustomWinner.Apocalypse: if ((pc.IsNeutralApocalypse()) && (countType == CountTypes.Apocalypse || pc.Is(CustomRoles.Soulless)) && !WinnerIds.Contains(pc.PlayerId)) @@ -356,6 +363,14 @@ public static bool Prefix() WinnerIds.Add(pc.PlayerId); } } + if (Main.AllAlivePlayerControls.All(p => p.IsPlayerCoven())) + { + foreach (var pc in Main.AllPlayerControls.Where(x => x.IsPlayerCoven())) + { + if (!WinnerIds.Contains(pc.PlayerId)) + WinnerIds.Add(pc.PlayerId); + } + } if (WinnerTeam is CustomWinner.Youtuber) { var youTuber = Main.AllPlayerControls.FirstOrDefault(x => x.Is(CustomRoles.Youtuber) && WinnerIds.Contains(x.PlayerId)); @@ -538,7 +553,7 @@ public static bool CheckGameEndByLivingPlayers(out GameOverReason reason) if (Sunnyboy.HasEnabled && Sunnyboy.CheckGameEnd()) return false; var neutralRoleCounts = new Dictionary(); var allAlivePlayerList = Main.AllAlivePlayerControls.ToArray(); - int dual = 0, impCount = 0, crewCount = 0; + int dual = 0, impCount = 0, crewCount = 0, covenCount = 0; foreach (var pc in allAlivePlayerList) { @@ -559,6 +574,10 @@ public static bool CheckGameEndByLivingPlayers(out GameOverReason reason) crewCount++; crewCount += dual; break; + case CountTypes.Coven: + covenCount++; + covenCount += dual; + break; default: if (neutralRoleCounts.ContainsKey(countType)) neutralRoleCounts[countType]++; @@ -571,7 +590,7 @@ public static bool CheckGameEndByLivingPlayers(out GameOverReason reason) int totalNKAlive = neutralRoleCounts.Sum(kvp => kvp.Value); - if (crewCount == 0 && impCount == 0 && totalNKAlive == 0) // Everyone is dead + if (crewCount == 0 && impCount == 0 && totalNKAlive == 0 && covenCount == 0) // Everyone is dead { reason = GameOverReason.ImpostorByKill; ResetAndSetWinner(CustomWinner.None); @@ -585,7 +604,8 @@ public static bool CheckGameEndByLivingPlayers(out GameOverReason reason) return true; } - else if (totalNKAlive == 0) // total number of nks alive 0 + + else if (totalNKAlive == 0 && covenCount == 0) // total number of nks alive 0 { if (crewCount <= impCount) // Crew less than or equal to Imps, Imp wins { @@ -605,7 +625,15 @@ public static bool CheckGameEndByLivingPlayers(out GameOverReason reason) else { if (impCount >= 1) return false; // Both Imp and NK are alive, the game must continue + if (covenCount >= 1) return false; // Both Coven and NK are alive, the game must continue if (crewCount > totalNKAlive) return false; // Imps are dead, but Crew still outnumbers NK (the game must continue) + if (crewCount > totalNKAlive) return false; // Imps are dead, but Crew still outnumbers Coven (the game must continue) + if (crewCount <= covenCount && totalNKAlive == 0) // Imps dead, NK dead, Coven <= NK, Coven wins + { + reason = GameOverReason.ImpostorByKill; + ResetAndSetWinner(CustomWinner.Coven); + return true; + } else // Imps dead, Crew <= NK, Checking if All nk alive are in 1 team { var winners = neutralRoleCounts.Where(kvp => kvp.Value == totalNKAlive).ToArray(); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 5c65be7f8..195dcbff4 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -690,6 +690,7 @@ public static bool Prefix(MeetingHud __instance, byte srcPlayerId, byte suspectP SendMessage(string.Format(GetString("RetrainAcceptOffer"), CustomRoles.CovenLeader.ToColoredString(), CovenLeader.retrainPlayer[voter.PlayerId].ToColoredString()), voter.PlayerId); CovenLeader.retrainPlayer.Clear(); CustomRoles.CovenLeader.GetStaticRoleClass().AbilityLimit--; + CustomRoles.CovenLeader.GetStaticRoleClass().SendSkillRPC(); __instance.RpcClearVoteDelay(voter.GetClientId()); } else if (target != voter && CovenLeader.retrainPlayer[voter.PlayerId].IsCoven()) @@ -1152,6 +1153,9 @@ public static void Postfix(MeetingHud __instance) if (Options.PassiveNeutralsCanGuess.GetBool() && seer.GetCustomRole().IsNonNK() && !seer.Is(CustomRoles.Doomsayer)) if (!seer.Data.IsDead && !target.Data.IsDead) pva.NameText.text = ColorString(GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; + if (Options.CovenCanGuess.GetBool() && (seer.GetCustomRole().IsCoven() || seer.Is(CustomRoles.Enchanted))) + if (!seer.Data.IsDead && !target.Data.IsDead) + pva.NameText.text = ColorString(GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; } diff --git a/Patches/OutroPatch.cs b/Patches/OutroPatch.cs index 921de6f90..5b328a458 100644 --- a/Patches/OutroPatch.cs +++ b/Patches/OutroPatch.cs @@ -223,6 +223,10 @@ public static void Postfix(EndGameManager __instance) CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Egoist); __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Egoist); break; + case CustomWinner.Coven: + CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.CovenLeader); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.CovenLeader); + break; //特殊勝利 case CustomWinner.Terrorist: __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Terrorist); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index cc28d54ec..adb61e543 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -18,6 +18,7 @@ using TOHE.Roles.Coven; using TOHE.Roles.Core; using static TOHE.Translator; +using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE; @@ -266,6 +267,9 @@ public static bool RpcCheckAndMurder(PlayerControl killer, PlayerControl target, if (killer.Is(Custom_Team.Impostor) && !Madmate.ImpCanKillMadmate.GetBool() && target.Is(CustomRoles.Madmate)) return false; + // Coven CAN'T kill Coven/Enchanted + if ((killer.Is(Custom_Team.Coven) || killer.Is(CustomRoles.Enchanted)) && (target.Is(Custom_Team.Coven) || target.Is(CustomRoles.Enchanted))) return false; + Logger.Info($"Start", "OnCheckMurderAsTargetOnOthers"); // Check murder on others targets diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index fbff7cb12..9556808fa 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -376,6 +376,7 @@ "EvilSpirit": "Evil Spirit", "Recruit": "Recruit", "Admired": "Admired", + "Enchanted": "Enchanted", "Glow": "Glow", "Radar": "Radar", "Diseased": "Diseased", @@ -676,6 +677,7 @@ "LoyalInfo": "You cannot be recruited", "EvilSpiritInfo": "You are an evil Spirit", "RecruitInfo": "Help the Jackal", + "EnchantedInfo": "Help the Coven", "AdmiredInfo": "The Admirer chose you as their love", "GlowInfo": "You glow in the dark", "RadarInfo": "Arrow's hue, closest to you!", @@ -982,6 +984,7 @@ "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to neutrals.", "EvilSpiritInfoLong": "(Add-ons):\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to give the Spiritcaller a shield against a kill attempt temporarily.", "RecruitInfoLong": "(Betrayal Add-ons):\nAs a recruit, you are on the Jackal's team and help out the Jackal and their Sidekicks.\n\nYou cannot win with your original team.", + "EnchantedInfoLong": "(Betrayal Add-ons):\nThe Enchanted add-on can only be obtained through being converted by the Ritualist.\nOnce Enchanted, you are apart of the Coven team and are no longer apart of your original team.", "AdmiredInfoLong": "(Betrayal Add-ons):\nAs an admired player, you win with the crew and not your original team.\n\nYou can see the Admirer.", "GlowInfoLong": "(Add-ons):\nDuring lights out, you and players nearby you will receive a vision boost.", "RadarInfoLong": "(Add-ons):\nAs Radar, you have arrows pointing towards the closest person at all times.", @@ -1879,11 +1882,12 @@ "RetrainDeclineOffer": "You declined the {0}'s retraining and your role has not been changed...", "RitualistMaxRitsPerRound": "Max Blood Rituals per Round", + "RitualistTryHideMsg": "Try to hide Ritualist's commands", "RitualistRitualSuccess": "You preformed a successful blood ritual on {0} and they are now part of the Coven!", "RitualistRitualFail": "You have failed your blood ritual and may no longer preform any blood rituals this meeting...", "RitualistRitualImpossible": "Your Blood Ritual was successful, however, this player is unenchantable.", "RitualistRitualMax": "You've already used the max amount of Blood Rituals for this meeting.", - "RitualistCommandHelp": "Instructions: /rt [Player ID] [Role Name] \nExample: /rt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs" + "RitualistCommandHelp": "Instructions: /rt [Player ID] [Role Name] \nExample: /rt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", "RitualistConvertNotif": "Your role has been guessed by the {0} and you are now apart of the Coven!", "LuckyProbability": "Probability of surviving a kill", @@ -2061,9 +2065,11 @@ "Remaining.MadmateCount": "Madmates left: {0}", "Remaining.NeutralCount": "Neutral Killers left: {0}", "Remaining.ApocalypseCount": "Neutral Apocalypse left: {0}", + "Remaining.CovenCount": "Coven left: {0}", "EnableKillerLeftCommand": "Enable use of /kcount command", "ShowMadmatesInLeftCommand": "Show Madmates (including add-ons)", "ShowApocalypseInLeftCommand": "Show Neutral Apocalypse", + "ShowCovenInLeftCommand": "Show Coven", "SeeEjectedRolesInMeeting": "See ejected roles in meetings", "SkillUsedLeft": "You have activated your skill to call a meeting. \nRemaining amount of uses left:", diff --git a/Roles/Coven/CovenLeader.cs b/Roles/Coven/CovenLeader.cs index 87abe0122..9b27bea5f 100644 --- a/Roles/Coven/CovenLeader.cs +++ b/Roles/Coven/CovenLeader.cs @@ -44,15 +44,6 @@ public override void Add(byte playerId) AbilityLimit = MaxRetrains.GetInt(); } - private void SendRPC(byte playerId, byte targetId) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); - writer.WriteNetObject(_Player); - writer.Write(playerId); - writer.Write(AbilityLimit); - writer.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) { byte playerId = reader.ReadByte(); diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 3df9a2310..32f7d2c1a 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -103,8 +103,8 @@ public override bool CanUseImpostorVentButton(PlayerControl pc) return option.GetBool(); } } - public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); - public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); + //public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); + //public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public static void GiveNecronomicon() { var pcList = Main.AllAlivePlayerControls.Where(pc => pc.IsPlayerCoven() && pc.IsAlive()).ToList(); diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index aca9607af..3bdb2d780 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -1,4 +1,18 @@ - +using Hazel; +using TOHE.Roles.Core; +using TOHE.Roles.Double; +using TOHE.Roles.AddOns.Crewmate; +using TOHE.Modules; +using InnerNet; +using static TOHE.Options; +using static TOHE.Translator; +using static TOHE.Utils; +using System.Text.RegularExpressions; +using System; +using TOHE.Modules.ChatManager; +using UnityEngine; +using static UnityEngine.GraphicsBuffer; + namespace TOHE.Roles.Coven; internal class Ritualist : CovenManager @@ -11,86 +25,112 @@ internal class Ritualist : CovenManager public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenPower; //==================================================================\\ - public static OptionItem MaxRitsPerRound; + private static OptionItem MaxRitsPerRound; + public static OptionItem TryHideMsg; + private static readonly Dictionary RitualLimit = []; public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Ritualist, 1, zeroOne: false); MaxRitsPerRound = IntegerOptionItem.Create(Id + 10, "RitualistMaxRitsPerRound", new(1, 15, 1), 2, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]) .SetValueFormat(OptionFormat.Times); + TryHideMsg = BooleanOptionItem.Create(Id + 11, "RitualistTryHideMsg", true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]) + .SetColor(Color.green); + } + public override void Init() + { + RitualLimit.Clear(); } - public override void Add(byte PlayerId) { - AbilityLimit = MaxRitsPerRound.GetInt(); + RitualLimit.Add(PlayerId, MaxRitsPerRound.GetInt()); + } + public override void Remove(byte playerId) + { + RitualLimit.Remove(playerId); } - public static void ReceiveRPC_Custom(MessageReader reader, PlayerControl pc) { int PlayerId = reader.ReadByte(); RitualistMsgCheck(pc, $"/rt {PlayerId}", true); } public override bool CanUseKillButton(PlayerControl pc) => HasNecronomicon(pc); - public override string GetProgressText(byte playerId, bool comms) - => ColorString(AbilityLimit >= 1 ? GetRoleColor(CustomRoles.CovenLeader).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); public override void OnReportDeadBody(PlayerControl hatsune, NetworkedPlayerInfo miku) { - AbilityLimit = MaxRitsPerRound.GetInt(); + foreach (var pid in RitualLimit.Keys) + { + RitualLimit[pid] = MaxRitsPerRound.GetInt(); + } } + public override string PVANameText(PlayerVoteArea pva, PlayerControl seer, PlayerControl target) + => seer.IsAlive() && target.IsAlive() ? ColorString(GetRoleColor(CustomRoles.Ritualist), target.PlayerId.ToString()) + " " + pva.NameText.text : string.Empty; public static bool RitualistMsgCheck(PlayerControl pc, string msg, bool isUI = false) { if (!AmongUsClient.Instance.AmHost) return false; - if (!GameStates.IsInGame || pc == null) return false; + if (!GameStates.IsMeeting || pc == null || GameStates.IsExilling) return false; if (!pc.Is(CustomRoles.Ritualist)) return false; - msg = msg.Trim().ToLower(); - if (msg.Length < 3 || msg[..4] != "/rt") return false; - - - if (msg == "/rt") - { - string text = GetString("PlayerIdList"); - foreach (var npc in Main.AllAlivePlayerControls) - text += "\n" + npc.PlayerId.ToString() + " → " + npc.GetRealName(); - SendMessage(text, pc.PlayerId); - return true; - } - + int operate = 0; // 1:ID 2:猜测 + msg = msg.ToLower().TrimStart().TrimEnd(); + if (CheckCommond(ref msg, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id||編號|玩家編號")) operate = 1; + else if (CheckCommond(ref msg, "rt|rit|ritual|bloodritual", false)) operate = 2; + else return false; - if (AbilityLimit <= 0) + if (!pc.IsAlive()) { - pc.ShowInfoMessage(isUI, GetString("RitualistRitualMax")); + pc.ShowInfoMessage(isUI, GetString("GuessDead")); return true; } - if (!MsgToPlayerAndRole(msg, out byte targetId, out CustomRoles role, out string error)) + if (operate == 1) { - pc.ShowInfoMessage(isUI, error); + SendMessage(GuessManager.GetFormatString(), pc.PlayerId); return true; } - var target = Utils.GetPlayerById(targetId); - if (!target.Is(role)) + else if (operate == 2) { - pc.ShowInfoMessage(isUI, GetString("RitualistRitualFail")); - AbilityLimit = 0; + if (TryHideMsg.GetBool()) + { + //if (Options.NewHideMsg.GetBool()) ChatManager.SendPreviousMessagesToAll(); + //else GuessManager.TryHideMsg(); + GuessManager.TryHideMsg(); + ChatManager.SendPreviousMessagesToAll(); + } + if (RitualLimit[pc.PlayerId] <= 0) + { + pc.ShowInfoMessage(isUI, GetString("RitualistRitualMax")); + return true; + } + + if (!MsgToPlayerAndRole(msg, out byte targetId, out CustomRoles role, out string error)) + { + pc.ShowInfoMessage(isUI, error); + return true; + } + var target = GetPlayerById(targetId); + + if (!target.Is(role)) + { + pc.ShowInfoMessage(isUI, GetString("RitualistRitualFail")); + RitualLimit[pc.PlayerId] = 0; + return true; + } + if (!CanBeConverted(target)) + { + pc.ShowInfoMessage(isUI, GetString("RitualistRitualImpossible")); + return true; + } + + Logger.Info($"{pc.GetNameWithRole()} enchant {target.GetNameWithRole()}", "Ritualist"); + + RitualLimit[pc.PlayerId]--; + + target.RpcSetCustomRole(CustomRoles.Enchanted); + SendMessage(string.Format(GetString("RitualistConvertNotif"), CustomRoles.Ritualist.ToColoredString()), target.PlayerId); + SendMessage(string.Format(GetString("RitualistRitualSuccess"), target.GetRealName()), pc.PlayerId); return true; } - if (target.IsTransformedNeutralApocalypse() || (target.Is(CustomRoles.NiceMini) || target.Is(CustomRoles.EvilMini) && Mini.Age < 18) || target.Is(CustomRoles.Solsticer) || !target.IsAlive() || target.Is(CustomRoles.Loyal) || !(target.GetCustomSubRoles().Contains(CustomRoles.Hurried) && !Hurried.CanBeConverted.GetBool()) - { - pc.ShowInfoMessage(isUI, GetString("RitualistRitualImpossible")); - return true; - } - - Logger.Info($"{pc.GetNameWithRole()} enchant {target.GetNameWithRole()}", "Ritualist"); - - string Name = target.GetRealName(); - - AbilityLimit--; - - target.RpcSetCustomRole(CustomRoles.Enchanted); - SendMessage(string.Format(GetString("RitualistConvertNotif"), CustomRoles.Ritualist.ToColoredString()), target.PlayerId); - SendMessage(string.Format(GetString("RitualistRitualSuccess"), target.GetRealName()), pc.PlayerId); - return true; + return false; } private static bool MsgToPlayerAndRole(string msg, out byte id, out CustomRoles role, out string error) { @@ -133,4 +173,30 @@ private static bool MsgToPlayerAndRole(string msg, out byte id, out CustomRoles error = string.Empty; return true; } + public static bool CheckCommond(ref string msg, string command, bool exact = true) + { + var comList = command.Split('|'); + foreach (var comm in comList) + { + if (exact) + { + if (msg == "/" + comm) return true; + } + else + { + if (msg.StartsWith("/" + comm)) + { + msg = msg.Replace("/" + comm, string.Empty); + return true; + } + } + } + return false; + } + private static bool CanBeConverted(PlayerControl pc) + { + return pc != null && (!pc.IsPlayerCoven() && !pc.Is(CustomRoles.Enchanted) && !pc.IsTransformedNeutralApocalypse()) && !pc.Is(CustomRoles.Soulless) && !pc.Is(CustomRoles.Lovers) && !pc.Is(CustomRoles.Loyal) + && !((pc.Is(CustomRoles.NiceMini) || pc.Is(CustomRoles.EvilMini)) && Mini.Age < 18) + && !(pc.GetCustomSubRoles().Contains(CustomRoles.Hurried) && !Hurried.CanBeConverted.GetBool()); + } } \ No newline at end of file diff --git a/main.cs b/main.cs index fa9f4df6c..83de97593 100644 --- a/main.cs +++ b/main.cs @@ -352,6 +352,17 @@ public static void LoadRoleColors() break; } } + foreach (var role in EnumHelper.GetAllValues()) + { + switch (role.GetCustomRoleTeam()) + { + case Custom_Team.Coven: + roleColors.TryAdd(role, "#ac42f2"); + break; + default: + break; + } + } if (!Directory.Exists(LANGUAGE_FOLDER_NAME)) Directory.CreateDirectory(LANGUAGE_FOLDER_NAME); CreateTemplateRoleColorFile(); if (File.Exists(@$"./{LANGUAGE_FOLDER_NAME}/RoleColor.dat")) @@ -848,6 +859,7 @@ public enum CustomRoles Wraith, //Coven + Coven, Conjurer, CovenLeader, Dreamweaver, @@ -1021,6 +1033,7 @@ public enum CustomWinner Doppelganger = CustomRoles.Doppelganger, Solsticer = CustomRoles.Solsticer, Apocalypse = CustomRoles.Apocalypse, + Coven = CustomRoles.Coven, } public enum AdditionalWinners { From f2c5e726c2e7deacb7cac3189132336d28130061 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Tue, 17 Sep 2024 17:56:03 -0400 Subject: [PATCH 005/101] i am really good at commit names --- Modules/ExtendedPlayerControl.cs | 8 +- Modules/NameColorManager.cs | 2 +- Resources/Lang/en_US.json | 35 ++++++++- Resources/roleColor.json | 3 +- Roles/AddOns/Common/Necroview.cs | 4 + Roles/Coven/Conjurer.cs | 125 +++++++++++++++++++++++++++++++ Roles/Coven/CovenManager.cs | 13 ++-- Roles/Coven/Necromancer.cs | 12 +-- main.cs | 8 -- 9 files changed, 182 insertions(+), 28 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index ab6e03811..d6bce22db 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1080,10 +1080,10 @@ public static bool KnowSubRoleTarget(PlayerControl seer, PlayerControl target) else if (seer.Is(CustomRoles.Egoist) && target.Is(CustomRoles.Egoist) && Egoist.ImpEgoistVisibalToAllies.GetBool()) return true; } - if (seer.Is(Custom_Team.Coven)) - { - if (target.Is(CustomRoles.Enchanted) || target.Is(Custom_Team.Coven)) return true; - } + //if (seer.Is(Custom_Team.Coven)) + //{ + // if (target.Is(CustomRoles.Enchanted) || target.Is(Custom_Team.Coven)) return true; + //} else if (Admirer.HasEnabled && Admirer.CheckKnowRoleTarget(seer, target)) return true; else if (Cultist.HasEnabled && Cultist.KnowRole(seer, target)) return true; else if (Infectious.HasEnabled && Infectious.KnowRole(seer, target)) return true; diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index 67a4b3785..3e64c8fec 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -43,7 +43,7 @@ private static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target if (seer.Is(CustomRoles.Madmate) && target.Is(CustomRoles.Madmate) && Madmate.MadmateKnowWhosMadmate.GetBool()) color = Main.roleColors[CustomRoles.Madmate]; // Coven - if ((seer.Is(Custom_Team.Coven) || seer.Is(CustomRoles.Enchanted)) && (target.Is(Custom_Team.Coven) || target.Is(CustomRoles.Enchanted))) color = Main.roleColors[CustomRoles.Coven]; + //if ((seer.Is(Custom_Team.Coven) || seer.Is(CustomRoles.Enchanted)) && (target.Is(Custom_Team.Coven) || target.Is(CustomRoles.Enchanted))) color = Main.roleColors[CustomRoles.Coven]; // Cultist if (Cultist.NameRoleColor(seer, target)) color = Main.roleColors[CustomRoles.Cultist]; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 9556808fa..ec0b4f40f 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -324,6 +324,13 @@ "Necromancer": "Necromancer", "CovenLeader": "Coven Leader", "Ritualist": "Ritualist", + "Spellcaster": "Spellcaster", + "Conjurer": "Conjurer", + "Dreamweaver": "Dreamweaver", + "Illusionist": "Illusionist", + "VoodooMaster": "Voodoo Master", + "Sacrifist": "Sacrifist", + "MoonDancer": "Moon Dancer", "Warden": "Warden", "Minion": "Minion", "Ghastly": "Ghastly", @@ -636,6 +643,13 @@ "NecromancerInfo": "Kill your killer to defy death", "CovenLeaderInfo": "Help your teammates by retraining them", "RitualistInfo": "Perform Blood Rituals to Enchant other players!", + "SpellcasterInfo": "Control other players against eachother!", + "ConjurerInfo": "Blast away your enemies!", + "DreamweaverInfo": "Drive other players to insomnia!", + "IllusionistInfo": "Place Illusions on players to spread confusion!", + "VoodooMasterInfo": "Craft Voodoo Dolls of other players!", + "SacrifistInfo": "Help your team and harm everyone else at your own cost", + "MoonDancerInfo": "Use Baton Pass to give out add-ons!", "WardenInfo": "(Ghost) Alert about danger", "MinionInfo": "(Ghost) Blind enemies", "LoversInfo": "Stay alive and win together", @@ -938,10 +952,17 @@ "MedusaInfoLong": "(Coven):\nAs the Medusa, you can stone bodies much like cleaning a body.\nStoned bodies cannot be reported.\n\nKill everyone to win.", "PotionMasterInfoLong": "(Coven):\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": "(Coven):\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.", - "CovenLeaderInfoLong": "(Coven): The Coven Leader can use their kill button on a fellow Coven member to retrain them into a random Coven role that isn’t currently in the game. During the next meeting, that Coven member will be notified that the Coven Leader wishes to retrain them. They can vote themselves to accept the retrain, or vote otherwise to deny it. Denying the retrain does not take away an ability usage.
With the Necronomicon, you cannot retrain, and can only kill other players.", - "RitualistInfoLong": "During a meeting, the Ritualist can perform a Blood Ritual to guess a player’s exact role. If the Ritualist is correct, that player will be converted to Coven. If the Ritualist is incorrect, they will not die but will be unable to Blood Ritual until the next meeting.
The command is /rt id role.
With the Necronomicon, the Ritualist can kill.", - "LastImpostorInfoLong": "(Add-ons):\nThis special effect is given to the last surviving Impostor. It significantly reduces their kill cooldown.", + "CovenLeaderInfoLong": "(Coven):\nThe Coven Leader can use their kill button on a fellow Coven member to retrain them into a random Coven role that isn’t currently in the game. During the next meeting, that Coven member will be notified that the Coven Leader wishes to retrain them. They can vote themselves to accept the retrain, or vote otherwise to deny it. Denying the retrain does not take away an ability usage.
With the Necronomicon, you cannot retrain, and can only kill other players.", + "RitualistInfoLong": "(Coven):\nDuring a meeting, the Ritualist can perform a Blood Ritual to guess a player’s exact role. If the Ritualist is correct, that player will be converted to Coven. If the Ritualist is incorrect, they will not die but will be unable to Blood Ritual until the next meeting.
The command is /rt id role.
With the Necronomicon, the Ritualist can kill.", + "SpellcasterInfoLong": "(Coven):\nThe Spellcaster can use their kill button on two players to control the first player into the other. If the first player has a kill button, they will be forced to use their kill button on the second person.\nWith the Necronomicon, you will kill your first target after controlling them.", + "ConjurerInfoLong": "(Coven):\nShapeshift once to mark a location.\nShapeshift again to conjure a meteor at the place you marked, killing everyone in the radius.\nWith the Necronomicon, you can kill. You can also mark a player using the Shapeshift menu. When the Conjurer unshapeshifts, all the players in the radius of the marked player will die, including themselves.", + "DreamweaverInfoLong": "(Coven):\nThe Dreamweaver can Dreamweave a player. The dreamweaved players will be notified of this during the next meeting. If the Dreamweaver is not voted out, these players will be unable to use their abilities until the Dreamweaver dies.\nWith the Necronomicon, the Dreamweaver can double-click to kill.", + "IllusionistInfoLong": "(Coven):\nThe Illusionist can use their kill button on a player to reverse the results of any investigative role. For example, if someone with a kill button is Illusioned, they will appear not to have a kill button to the Investigator, and vice versa.\nWith the Necronomicon, you may double-click to kill. Every kill you make appears as a random death reason.", + "VoodooMasterInfoLong": "(Coven):\nThe Voodoo Master can craft a voodoo doll of a player by using their kill button, similar to the Shaman. All the interactions with you using kill button will be deflected to the voodoo doll and the voodoo doll will destroy. Unlike the Shaman, this voodoo will last during the meeting (eg. If the Voodoo Master is judged, then the voodoo'd player will be judged instead).\nWith the Necronomicon, you can double-click to kill. Additionally, the voodoo’d player will be unable to report. The next person to interact with them will die.", + "SacrifistInfoLong": "(Coven):\nThe Sacrifist can vent to cause a random debuff to a non-coven member, however, the Sacrifist will also receive this effect.\nDepending on the host’s settings, if the Sacrifist is voted out, some random non-coven who voted the Sacrifist will die too.\nWith the Necronomicon, when you vent, you commit suicide. However, the entire Coven for the rest of the game receives a lowered kill cooldown.", + "MoonDancerInfoLong": "(Coven):\nThe Moon Dancer can use their kill button to use their ability, Baton Pass.\nIf used on a Coven member: Gives a helpful add-on at the next meeting.\nIf used on a non-Coven member: Gives a harmful add-on at the next meeting.\nWith the Necronomicon, the Moon Dancer can double-click their kill button to kill. When killing, the player is teleported off the map. They will appear alive on vitals and will not show up in tracefinder's arrows etc. They die when a meeting/body report with the death reason Blasted Off.", "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.", + "LastImpostorInfoLong": "(Add-ons):\nThis special effect is given to the last surviving Impostor. It significantly reduces their kill cooldown.", "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.", "MadmateInfoLong": "(Add-ons):\nOnly Crewmates can become Madmate. Madmate's task is to help the Impostors win the game. Madmate will lose if all Impostors are killed/ejected. Madmates may know who are Impostors, and Impostors may know who are Madmates (host settings).\n\nLazy Guy, Celebrity can't become Madmate. Sheriff, Snitch, Nice Guesser, Mayor, and Judge may become Madmate (host settings). Skill changes when the following roles are converted into Madmates:\n\nTime Manager => Doing tasks will reduce meeting time.\nBodyguard => Skill won't activate if the killer is an Impostor.\nGrenadier => Flash bomb will work on Crewmates and Neutrals instead of the Impostors.\nSheriff => Can kill anyone, including Impostors (host settings).\nNice Guesser => Can guess Crewmates and Neutrals\nPsychic => All evil Neutrals and Crewmates' names with the ability to kill will be displayed in Red.\nJudge => Can judge anyone\nPacifist => Their ability only works on Crewmates.", "WatcherInfoLong": "(Add-ons):\nDuring the meeting, Watcher can see everyone's votes.", @@ -1890,6 +1911,14 @@ "RitualistCommandHelp": "Instructions: /rt [Player ID] [Role Name] \nExample: /rt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", "RitualistConvertNotif": "Your role has been guessed by the {0} and you are now apart of the Coven!", + "ConjurerCooldown": "Conjure Cooldown", + "ConjurerRadius": "Blast Radius", + "ConjurerNecroRadius": "Necronomicon Blast Radius", + "ConjurerCovenDies": "Coven can die in blast", + "ConjurerMark": "Location marked", + "ConjurerMeteor": "Meteor summoned", + "ConjurerNecroMark": "Player marked", + "LuckyProbability": "Probability of surviving a kill", "ImpCanBeDoubleShot": "Impostors can have Double Shot", "CrewCanBeDoubleShot": "Crewmates can have Double Shot", diff --git a/Resources/roleColor.json b/Resources/roleColor.json index 1663d46e4..5dfbf1ffe 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -156,7 +156,8 @@ "Pixie": "#00FF00", "Imitator": "#B3D94C", "Doppelganger": "#f6f4a3", - "CovenLeader": "#ac42f2", + "Coven": "#ac42f2", + "Enchanted": "#ac42f2", "GM": "#ff5b70", "NotAssigned": "#ffffff", "LastImpostor": "#ff1919", diff --git a/Roles/AddOns/Common/Necroview.cs b/Roles/AddOns/Common/Necroview.cs index f64e8460f..e14a93a55 100644 --- a/Roles/AddOns/Common/Necroview.cs +++ b/Roles/AddOns/Common/Necroview.cs @@ -37,6 +37,10 @@ or CustomRoles.Recruit return Main.roleColors[CustomRoles.Bait]; } + if (customRole.IsCoven() || customRole.Equals(CustomRoles.Enchanted)) + { + return Main.roleColors[CustomRoles.Coven]; + } return Main.roleColors[CustomRoles.Knight]; } } diff --git a/Roles/Coven/Conjurer.cs b/Roles/Coven/Conjurer.cs index e69de29bb..3e04823f5 100644 --- a/Roles/Coven/Conjurer.cs +++ b/Roles/Coven/Conjurer.cs @@ -0,0 +1,125 @@ +using Hazel; +using TOHE.Roles.Core; +using TOHE.Roles.Double; +using TOHE.Roles.AddOns.Crewmate; +using TOHE.Modules; +using InnerNet; +using static TOHE.Options; +using static TOHE.Translator; +using static TOHE.Utils; +using System.Text.RegularExpressions; +using System; +using TOHE.Modules.ChatManager; +using UnityEngine; +using static UnityEngine.GraphicsBuffer; + +namespace TOHE.Roles.Coven; + +internal class Conjurer : CovenManager +{ + private enum ConjState + { + NormalMark, + NormalBomb, + NecroMark, + NecroBomb + } + //===========================SETUP================================\\ + private const int Id = 30000; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Conjurer); + public override bool IsDesyncRole => true; + public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenKilling; + //==================================================================\\ + private static OptionItem ConjureCooldown; + private static OptionItem ConjureRadius; + private static OptionItem NecroRadius; + private static OptionItem CovenDiesInBlast; + + public static byte NecroBombHolder = byte.MaxValue; + private static readonly Dictionary> ConjPosition = []; + private static readonly Dictionary state = []; + + + public override void SetupCustomOption() + { + SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Conjurer, 1, zeroOne: false); + ConjureCooldown = FloatOptionItem.Create(Id + 10, "ConjurerCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Conjurer]) + .SetValueFormat(OptionFormat.Seconds); + ConjureRadius = FloatOptionItem.Create(Id + 11, "ConjurerRadius", new(0.5f, 100f, 0.5f), 2f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Conjurer]) + .SetValueFormat(OptionFormat.Multiplier); + NecroRadius = FloatOptionItem.Create(Id + 12, "ConjurerNecroRadius", new(0.5f, 100f, 0.5f), 3f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Conjurer]) + .SetValueFormat(OptionFormat.Multiplier); + CovenDiesInBlast = BooleanOptionItem.Create(Id + 13, "ConjurerCovenDies", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Conjurer]); + } + public override void Init() + { + NecroBombHolder = byte.MaxValue; + ConjPosition.Clear(); + } + public override void Add(byte playerId) + { + ConjPosition[playerId] = []; + state.TryAdd(playerId, ConjState.NormalMark); + } + public override bool CanUseKillButton(PlayerControl pc) => HasNecronomicon(pc); + + public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl target, ref bool resetCooldown, ref bool shouldAnimate) + { + Logger.Info($"Conjurer ShapeShift", "Conjurer"); + if (shapeshifter.PlayerId == target.PlayerId) return false; + state[shapeshifter.PlayerId] = HasNecronomicon(shapeshifter) ? ConjState.NecroMark : ConjState.NormalMark; + switch (state[shapeshifter.PlayerId]) + { + case ConjState.NormalMark: + ConjPosition[shapeshifter.PlayerId].Add(shapeshifter.transform.position); + state[shapeshifter.PlayerId] = ConjState.NormalBomb; + shapeshifter.Notify(GetString("ConjurerMark")); + break; + + case ConjState.NormalBomb: + foreach (var player in Main.AllAlivePlayerControls) + { + foreach (var pos in ConjPosition[shapeshifter.PlayerId].ToArray()) + { + var dis = GetDistance(pos, player.transform.position); + if (dis > ConjureRadius.GetFloat()) continue; + if (player.IsPlayerCoven() && !CovenDiesInBlast.GetBool()) continue; + else + { + player.SetDeathReason(PlayerState.DeathReason.Bombed); + player.RpcMurderPlayer(player); + player.SetRealKiller(shapeshifter); + } + } + } + shapeshifter.Notify(GetString("ConjurerMeteor")); + state[shapeshifter.PlayerId] = ConjState.NormalMark; + break; + case ConjState.NecroMark: + NecroBombHolder = target.PlayerId; + state[shapeshifter.PlayerId] = ConjState.NecroBomb; + shapeshifter.Notify(GetString("ConjurerNecroMark")); + break; + case ConjState.NecroBomb: + foreach (var player in Main.AllAlivePlayerControls) + { + var dis = GetDistance(GetPlayerById(NecroBombHolder).transform.position, player.transform.position); + if (dis > NecroRadius.GetFloat()) continue; + if (player.IsPlayerCoven() && !CovenDiesInBlast.GetBool()) continue; + else + { + player.SetDeathReason(PlayerState.DeathReason.Bombed); + player.RpcMurderPlayer(player); + player.SetRealKiller(shapeshifter); + } + + } + shapeshifter.Notify(GetString("ConjurerMeteor")); + state[shapeshifter.PlayerId] = ConjState.NecroMark; + NecroBombHolder = byte.MaxValue; + break; + } + return false; + } +} \ No newline at end of file diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 32f7d2c1a..5319f8cdf 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -67,11 +67,11 @@ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, b } return string.Empty; } - private void SendRPC(byte playerId) + private static void SendRPC(byte playerId) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); - writer.WriteNetObject(_Player); - writer.Write(necroHolder.PlayerId); + writer.WriteNetObject(GetPlayerById(playerId)); + writer.Write(playerId); AmongUsClient.Instance.FinishRpcImmediately(writer); } public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) @@ -103,17 +103,20 @@ public override bool CanUseImpostorVentButton(PlayerControl pc) return option.GetBool(); } } - //public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); - //public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); + public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); + public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public static void GiveNecronomicon() { + var pcList = Main.AllAlivePlayerControls.Where(pc => pc.IsPlayerCoven() && pc.IsAlive()).ToList(); if (pcList.Any()) { PlayerControl rp = pcList.RandomElement(); necroHolder = rp; necroHolder.Notify(GetString("NecronomiconNotification")); + SendRPC(necroHolder.PlayerId); } + } public override void OnCoEndGame() { diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index d105d7af0..38d9b01f5 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -16,8 +16,8 @@ internal class Necromancer : CovenManager //==================================================================\\ private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; + //private static OptionItem CanVent; + //private static OptionItem HasImpostorVision; private static OptionItem RevengeTime; public static PlayerControl Killer = null; @@ -33,8 +33,8 @@ public override void SetupCustomOption() .SetValueFormat(OptionFormat.Seconds); RevengeTime = IntegerOptionItem.Create(Id + 11, "NecromancerRevengeTime", new(0, 60, 1), 30, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]) .SetValueFormat(OptionFormat.Seconds); - CanVent = BooleanOptionItem.Create(Id + 12, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]); - HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]); + //CanVent = BooleanOptionItem.Create(Id + 12, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]); + //HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]); } public override void Init() { @@ -49,10 +49,10 @@ public override void Add(byte playerId) playerIdList.Add(playerId); Timer = RevengeTime.GetInt(); } - public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); + //public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; - public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); + //public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) diff --git a/main.cs b/main.cs index 83de97593..f9561924e 100644 --- a/main.cs +++ b/main.cs @@ -348,14 +348,6 @@ public static void LoadRoleColors() case Custom_Team.Impostor: roleColors.TryAdd(role, "#ff1919"); break; - default: - break; - } - } - foreach (var role in EnumHelper.GetAllValues()) - { - switch (role.GetCustomRoleTeam()) - { case Custom_Team.Coven: roleColors.TryAdd(role, "#ac42f2"); break; From 79af27ed910b3824d6b5a720a936e36827f22fc5 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:47:01 -0400 Subject: [PATCH 006/101] conj fix --- Roles/Coven/Conjurer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Roles/Coven/Conjurer.cs b/Roles/Coven/Conjurer.cs index 3e04823f5..beea25724 100644 --- a/Roles/Coven/Conjurer.cs +++ b/Roles/Coven/Conjurer.cs @@ -68,7 +68,8 @@ public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl { Logger.Info($"Conjurer ShapeShift", "Conjurer"); if (shapeshifter.PlayerId == target.PlayerId) return false; - state[shapeshifter.PlayerId] = HasNecronomicon(shapeshifter) ? ConjState.NecroMark : ConjState.NormalMark; + if (state[shapeshifter.PlayerId] != ConjState.NecroBomb && state[shapeshifter.PlayerId] != ConjState.NormalBomb) + state[shapeshifter.PlayerId] = HasNecronomicon(shapeshifter) ? ConjState.NecroMark : ConjState.NormalMark; switch (state[shapeshifter.PlayerId]) { case ConjState.NormalMark: From 71b55908e2490eeb63ab5fbcd32959b67d809046 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:47:37 -0400 Subject: [PATCH 007/101] push so i can merge and bug fix --- Roles/Coven/Illusionist.cs | 97 ++++++++++++++++++++++++++++++++++ Roles/Crewmate/Investigator.cs | 4 +- 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index e69de29bb..eea65a246 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -0,0 +1,97 @@ + +using Hazel; +using TOHE.Roles.Core; +using TOHE.Roles.AddOns.Impostor; +using TOHE.Modules; +using InnerNet; +using static TOHE.Options; +using static TOHE.Translator; +using static TOHE.Utils; +using System.Text.RegularExpressions; +using System; +using TOHE.Modules.ChatManager; +using UnityEngine; +using static UnityEngine.GraphicsBuffer; + +namespace TOHE.Roles.Coven; + +internal class Illusionist : CovenManager +{ + //===========================SETUP================================\\ + private const int Id = 30100; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Illusionist); + public override bool IsDesyncRole => true; + public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenTrickery; + //==================================================================\\ + + private static OptionItem IllusionCooldown; + private static OptionItem MaxIllusions; + private static OptionItem SnitchCanIllusioned; + + private static readonly Dictionary> IllusionedPlayers = []; + + + public override void SetupCustomOption() + { + SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Conjurer, 1, zeroOne: false); + IllusionCooldown = FloatOptionItem.Create(Id + 10, "IllusionCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Conjurer]) + .SetValueFormat(OptionFormat.Seconds); + MaxIllusions = IntegerOptionItem.Create(Id + 11, "IllusionistMaxIllusions", new(1, 15, 1), 5, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]) + .SetValueFormat(OptionFormat.Times); + SnitchCanIllusioned = BooleanOptionItem.Create(Id + 12, "IllusionistSnitchAffected", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Conjurer]); + } + + public override void Init() + { + IllusionedPlayers.Clear(); + } + public override void Add(byte playerId) + { + IllusionedPlayers[playerId] = []; + GetPlayerById(playerId)?.AddDoubleTrigger(); + } + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (killer.CheckDoubleTrigger(target, () => + { + killer.SetKillCooldown(); + + if (!IllusionedPlayers[killer.PlayerId].Contains(target.PlayerId)) + { + IllusionedPlayers[killer.PlayerId].Add(target.PlayerId); + } + }) + ) //this looks so ugly + { + var randomDeathReason = ChangeRandomDeath(); + Main.PlayerStates[target.PlayerId].deathReason = randomDeathReason; + Main.PlayerStates[target.PlayerId].SetDead(); + return true; + } + return false; + } + private static PlayerState.DeathReason ChangeRandomDeath() + { + PlayerState.DeathReason[] deathReasons = EnumHelper.GetAllValues().ToArray(); + if (deathReasons.Length == 0 || !deathReasons.Contains(PlayerState.DeathReason.Kill)) deathReasons.AddItem(PlayerState.DeathReason.Kill); + var random = IRandom.Instance; + int randomIndex = random.Next(deathReasons.Length); + return deathReasons[randomIndex]; + } + public static bool IsNonCovIllusioned(byte target) + { + byte pc = Utils.GetPlayerListByRole(CustomRoles.Illusionist).First().PlayerId; + return IllusionedPlayers[pc].Contains(target) && !GetPlayerById(target).IsPlayerCoven(); + } + public static bool IsCovIllusioned(byte target) + { + byte pc = Utils.GetPlayerListByRole(CustomRoles.Illusionist).First().PlayerId; + return IllusionedPlayers[pc].Contains(target) && GetPlayerById(target).IsPlayerCoven(); + } + public override void AfterMeetingTasks() + { + IllusionedPlayers.Clear(); + } + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => IllusionedPlayers[seer.PlayerId].Contains(seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.Illusionist), "") : string.Empty; +} \ No newline at end of file diff --git a/Roles/Crewmate/Investigator.cs b/Roles/Crewmate/Investigator.cs index a2845fe44..fdf5d0a31 100644 --- a/Roles/Crewmate/Investigator.cs +++ b/Roles/Crewmate/Investigator.cs @@ -1,6 +1,7 @@ using AmongUs.GameOptions; using Hazel; using UnityEngine; +using TOHE.Roles.Coven; using static TOHE.Options; namespace TOHE.Roles.Crewmate; @@ -142,7 +143,8 @@ public override string PlayerKnowTargetColor(PlayerControl seer, PlayerControl t if (!InvestigatedList.TryGetValue(seer.PlayerId, out var targetList)) return string.Empty; if (!targetList.Contains(target.PlayerId)) return string.Empty; - if (target.HasKillButton() || CopyCat.playerIdList.Contains(target.PlayerId)) return "#FF1919"; + if (Illusionist.IsCovIllusioned(target.PlayerId)) return "#8CFFFF"; + if (Illusionist.IsNonCovIllusioned(target.PlayerId) || target.HasKillButton() || CopyCat.playerIdList.Contains(target.PlayerId)) return "#FF1919"; else return "#8CFFFF"; } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) From fbbead2c834b8a8d5439d1fe782882356538dc11 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Tue, 24 Sep 2024 19:11:19 -0400 Subject: [PATCH 008/101] a --- Modules/ExtendedPlayerControl.cs | 3 +++ Modules/NameColorManager.cs | 2 +- Resources/Lang/en_US.json | 4 ++++ Roles/Coven/Conjurer.cs | 9 +++++++-- Roles/Coven/CovenLeader.cs | 2 +- Roles/Coven/CovenManager.cs | 8 +++++--- Roles/Coven/Illusionist.cs | 13 ++++++++----- 7 files changed, 29 insertions(+), 12 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 8fa697f14..7405382a9 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1241,6 +1241,9 @@ public static bool KnowRoleTarget(PlayerControl seer, PlayerControl target) else if (Madmate.MadmateKnowWhosImp.GetBool() && seer.Is(CustomRoles.Madmate) && target.Is(Custom_Team.Impostor)) return true; else if (Madmate.ImpKnowWhosMadmate.GetBool() && target.Is(CustomRoles.Madmate) && seer.Is(Custom_Team.Impostor)) return true; else if (seer.Is(Custom_Team.Impostor) && target.GetCustomRole().IsGhostRole() && target.GetCustomRole().IsImpostor()) return true; + else if (target.Is(CustomRoles.Enchanted) && seer.Is(Custom_Team.Coven)) return true; + else if (seer.Is(CustomRoles.Enchanted) && target.Is(Custom_Team.Coven)) return true; + else if (target.Is(Custom_Team.Coven) && seer.Is(Custom_Team.Coven)) return true; else if (target.GetRoleClass().KnowRoleTarget(seer, target)) return true; else if (seer.GetRoleClass().KnowRoleTarget(seer, target)) return true; else if (Solsticer.OtherKnowSolsticer(target)) return true; diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index 6d6d79917..8bc5ec004 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -50,7 +50,7 @@ private static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target if (seer.Is(CustomRoles.Madmate) && target.Is(CustomRoles.Madmate) && Madmate.MadmateKnowWhosMadmate.GetBool()) color = Main.roleColors[CustomRoles.Madmate]; // Coven - //if ((seer.Is(Custom_Team.Coven) || seer.Is(CustomRoles.Enchanted)) && (target.Is(Custom_Team.Coven) || target.Is(CustomRoles.Enchanted))) color = Main.roleColors[CustomRoles.Coven]; + if ((seer.Is(Custom_Team.Coven) || seer.Is(CustomRoles.Enchanted)) && (target.Is(Custom_Team.Coven) || target.Is(CustomRoles.Enchanted))) color = Main.roleColors[CustomRoles.Coven]; // Cultist if (Cultist.NameRoleColor(seer, target)) color = Main.roleColors[CustomRoles.Cultist]; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index b705acc78..3e3ab19a9 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1971,6 +1971,10 @@ "ConjurerMeteor": "Meteor summoned", "ConjurerNecroMark": "Player marked", + "IllusionCooldown": "Illusion Cooldown", + "IllusionistMaxIllusions": "Max Illusions", + "IllusionistSnitchAffected": "Snitch is Affected by Illusions", + "LuckyProbability": "Probability of surviving a kill", "ImpCanBeDoubleShot": "Impostors can have Double Shot", "CrewCanBeDoubleShot": "Crewmates can have Double Shot", diff --git a/Roles/Coven/Conjurer.cs b/Roles/Coven/Conjurer.cs index beea25724..5817014ec 100644 --- a/Roles/Coven/Conjurer.cs +++ b/Roles/Coven/Conjurer.cs @@ -12,6 +12,7 @@ using TOHE.Modules.ChatManager; using UnityEngine; using static UnityEngine.GraphicsBuffer; +using AmongUs.GameOptions; namespace TOHE.Roles.Coven; @@ -25,7 +26,7 @@ private enum ConjState NecroBomb } //===========================SETUP================================\\ - private const int Id = 30000; + private const int Id = 30300; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Conjurer); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; @@ -63,9 +64,13 @@ public override void Add(byte playerId) state.TryAdd(playerId, ConjState.NormalMark); } public override bool CanUseKillButton(PlayerControl pc) => HasNecronomicon(pc); - + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ShapeshifterCooldown = ConjureCooldown.GetFloat(); + } public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl target, ref bool resetCooldown, ref bool shouldAnimate) { + resetCooldown = true; Logger.Info($"Conjurer ShapeShift", "Conjurer"); if (shapeshifter.PlayerId == target.PlayerId) return false; if (state[shapeshifter.PlayerId] != ConjState.NecroBomb && state[shapeshifter.PlayerId] != ConjState.NormalBomb) diff --git a/Roles/Coven/CovenLeader.cs b/Roles/Coven/CovenLeader.cs index 9b27bea5f..2bd49e328 100644 --- a/Roles/Coven/CovenLeader.cs +++ b/Roles/Coven/CovenLeader.cs @@ -15,7 +15,7 @@ namespace TOHE.Roles.Coven; internal class CovenLeader : CovenManager { //===========================SETUP================================\\ - private const int Id = 29800; + private const int Id = 30200; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.CovenLeader); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 5319f8cdf..a71faa99c 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -47,14 +47,14 @@ private static void SetUpImpVisOption(CustomRoles role, int Id, bool defaultValu { var roleName = GetRoleName(role); Dictionary replacementDic = new() { { "%role%", ColorString(GetRoleColor(role), roleName) } }; - CovenImpVisOptions[role] = BooleanOptionItem.Create(Id, "%role%HasImpVis", defaultValue, TabGroup.CovenRoles, false).SetParent(CovenImpVisMode); + CovenImpVisOptions[role] = BooleanOptionItem.Create(Id, "%role%HasImpVis", defaultValue, TabGroup.CovenRoles, false).SetParent(parent); CovenImpVisOptions[role].ReplacementDictionary = replacementDic; } private static void SetUpVentOption(CustomRoles role, int Id, bool defaultValue = true, OptionItem parent = null) { var roleName = GetRoleName(role); Dictionary replacementDic = new() { { "%role%", ColorString(GetRoleColor(role), roleName) } }; - CovenVentOptions[role] = BooleanOptionItem.Create(Id, "%role%CanVent", defaultValue, TabGroup.CovenRoles, false).SetParent(CovenVentMode); + CovenVentOptions[role] = BooleanOptionItem.Create(Id, "%role%CanVent", defaultValue, TabGroup.CovenRoles, false).SetParent(parent); CovenVentOptions[role].ReplacementDictionary = replacementDic; } public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) @@ -103,8 +103,10 @@ public override bool CanUseImpostorVentButton(PlayerControl pc) return option.GetBool(); } } + /* public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); + */ public static void GiveNecronomicon() { @@ -122,7 +124,7 @@ public override void OnCoEndGame() { necroHolder = null; } - public override void OnFixedUpdate(PlayerControl pc) + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { if (!necroHolder.IsAlive()) { diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index eea65a246..64590a7f5 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -12,13 +12,14 @@ using TOHE.Modules.ChatManager; using UnityEngine; using static UnityEngine.GraphicsBuffer; +using AmongUs.GameOptions; namespace TOHE.Roles.Coven; internal class Illusionist : CovenManager { //===========================SETUP================================\\ - private const int Id = 30100; + private const int Id = 30400; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Illusionist); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; @@ -34,12 +35,12 @@ internal class Illusionist : CovenManager public override void SetupCustomOption() { - SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Conjurer, 1, zeroOne: false); - IllusionCooldown = FloatOptionItem.Create(Id + 10, "IllusionCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Conjurer]) + SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Illusionist, 1, zeroOne: false); + IllusionCooldown = FloatOptionItem.Create(Id + 10, "IllusionCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Illusionist]) .SetValueFormat(OptionFormat.Seconds); - MaxIllusions = IntegerOptionItem.Create(Id + 11, "IllusionistMaxIllusions", new(1, 15, 1), 5, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]) + MaxIllusions = IntegerOptionItem.Create(Id + 11, "IllusionistMaxIllusions", new(1, 15, 1), 5, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Illusionist]) .SetValueFormat(OptionFormat.Times); - SnitchCanIllusioned = BooleanOptionItem.Create(Id + 12, "IllusionistSnitchAffected", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Conjurer]); + SnitchCanIllusioned = BooleanOptionItem.Create(Id + 12, "IllusionistSnitchAffected", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Illusionist]); } public override void Init() @@ -48,6 +49,7 @@ public override void Init() } public override void Add(byte playerId) { + AbilityLimit = MaxIllusions.GetInt(); IllusionedPlayers[playerId] = []; GetPlayerById(playerId)?.AddDoubleTrigger(); } @@ -71,6 +73,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } return false; } + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = IllusionCooldown.GetFloat(); private static PlayerState.DeathReason ChangeRandomDeath() { PlayerState.DeathReason[] deathReasons = EnumHelper.GetAllValues().ToArray(); From 6b541bf89717188809930a925e9f7a759e1062a7 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 29 Sep 2024 12:47:30 -0400 Subject: [PATCH 009/101] push then merge because alpha 15 is bugged as hell --- Modules/ExtendedPlayerControl.cs | 4 +- Modules/NameColorManager.cs | 3 +- Patches/CheckGameEndPatch.cs | 8 +-- Patches/PlayerControlPatch.cs | 2 +- Resources/Lang/en_US.json | 11 ++- Roles/Core/CustomRoleManager.cs | 2 + Roles/Coven/Conjurer.cs | 6 -- Roles/Coven/CovenLeader.cs | 3 - Roles/Coven/CovenManager.cs | 34 ++++----- Roles/Coven/Illusionist.cs | 63 ++++++++++------- Roles/Coven/Medusa.cs | 114 +++++++++++++++++++++++++++---- Roles/Coven/Ritualist.cs | 27 ++++++-- Roles/Crewmate/Inspector.cs | 18 +++-- Roles/Crewmate/Oracle.cs | 4 ++ Roles/Crewmate/Psychic.cs | 3 +- Roles/Crewmate/Snitch.cs | 3 +- Roles/Crewmate/Witness.cs | 4 +- 17 files changed, 221 insertions(+), 88 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 7405382a9..607f11138 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1241,8 +1241,8 @@ public static bool KnowRoleTarget(PlayerControl seer, PlayerControl target) else if (Madmate.MadmateKnowWhosImp.GetBool() && seer.Is(CustomRoles.Madmate) && target.Is(Custom_Team.Impostor)) return true; else if (Madmate.ImpKnowWhosMadmate.GetBool() && target.Is(CustomRoles.Madmate) && seer.Is(Custom_Team.Impostor)) return true; else if (seer.Is(Custom_Team.Impostor) && target.GetCustomRole().IsGhostRole() && target.GetCustomRole().IsImpostor()) return true; - else if (target.Is(CustomRoles.Enchanted) && seer.Is(Custom_Team.Coven)) return true; - else if (seer.Is(CustomRoles.Enchanted) && target.Is(Custom_Team.Coven)) return true; + //else if (target.Is(CustomRoles.Enchanted) && seer.Is(Custom_Team.Coven)) return true; + //else if (seer.Is(CustomRoles.Enchanted) && target.Is(Custom_Team.Coven)) return true; else if (target.Is(Custom_Team.Coven) && seer.Is(Custom_Team.Coven)) return true; else if (target.GetRoleClass().KnowRoleTarget(seer, target)) return true; else if (seer.GetRoleClass().KnowRoleTarget(seer, target)) return true; diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index 8bc5ec004..7b34a7e8b 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -49,8 +49,9 @@ private static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target if (seer.Is(Custom_Team.Impostor) && target.GetCustomRole().IsGhostRole() && target.GetCustomRole().IsImpostor()) color = Main.roleColors[CustomRoles.Madmate]; if (seer.Is(CustomRoles.Madmate) && target.Is(CustomRoles.Madmate) && Madmate.MadmateKnowWhosMadmate.GetBool()) color = Main.roleColors[CustomRoles.Madmate]; - // Coven + /* Coven if ((seer.Is(Custom_Team.Coven) || seer.Is(CustomRoles.Enchanted)) && (target.Is(Custom_Team.Coven) || target.Is(CustomRoles.Enchanted))) color = Main.roleColors[CustomRoles.Coven]; + */ // Cultist if (Cultist.NameRoleColor(seer, target)) color = Main.roleColors[CustomRoles.Cultist]; diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 6ab1a90f2..c6653bb36 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -643,15 +643,15 @@ public static bool CheckGameEndByLivingPlayers(out GameOverReason reason) else { if (impCount >= 1) return false; // Both Imp and NK are alive, the game must continue - if (covenCount >= 1) return false; // Both Coven and NK are alive, the game must continue - if (crewCount > totalNKAlive) return false; // Imps are dead, but Crew still outnumbers NK (the game must continue) - if (crewCount > totalNKAlive) return false; // Imps are dead, but Crew still outnumbers Coven (the game must continue) - if (crewCount <= covenCount && totalNKAlive == 0) // Imps dead, NK dead, Coven <= NK, Coven wins + if (crewCount <= covenCount && totalNKAlive == 0) // Imps dead, NK dead, Crew <= Coven, Coven wins { reason = GameOverReason.ImpostorByKill; ResetAndSetWinner(CustomWinner.Coven); return true; } + if (covenCount >= 1) return false; // Both Coven and NK are alive, the game must continue + if (crewCount > totalNKAlive) return false; // Imps are dead, but Crew still outnumbers NK (the game must continue) + if (crewCount > covenCount) return false; // Imps are dead, but Crew still outnumbers Coven (the game must continue) else // Imps dead, Crew <= NK, Checking if All nk alive are in 1 team { var winners = neutralRoleCounts.Where(kvp => kvp.Value == totalNKAlive).ToArray(); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index a1b78e6ae..db4a043dd 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -276,7 +276,7 @@ public static bool RpcCheckAndMurder(PlayerControl killer, PlayerControl target, return false; // Coven CAN'T kill Coven/Enchanted - if ((killer.Is(Custom_Team.Coven) || killer.Is(CustomRoles.Enchanted)) && (target.Is(Custom_Team.Coven) || target.Is(CustomRoles.Enchanted))) return false; + // if ((killer.Is(Custom_Team.Coven) || killer.Is(CustomRoles.Enchanted)) && (target.Is(Custom_Team.Coven) || target.Is(CustomRoles.Enchanted))) return false; Logger.Info($"Start", "OnCheckMurderAsTargetOnOthers"); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 3e3ab19a9..b086c68fb 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -621,7 +621,7 @@ "VultureInfo": "Eat bodies by reporting to win", "TaskinatorInfo": "Silent tasks, deadly blasts", "BenefactorInfo": "Task complete, shield elite!", - "MedusaInfo": "Stone bodies by reporting them", + "MedusaInfo": "Stop players in their tracks by Stoning them", "SpiritcallerInfo": "Turn Players into Evil Spirits", "AmnesiacInfo": "Remember the role of a dead body", "ImitatorInfo": "Imitate a player's role", @@ -958,7 +958,7 @@ "PoisonerInfoLong": "(Coven):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", "HexMasterInfoLong": "(Coven):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", "JinxInfoLong": "(Coven):\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.", - "MedusaInfoLong": "(Coven):\nAs the Medusa, you can stone bodies much like cleaning a body.\nStoned bodies cannot be reported.\n\nKill everyone to win.", + "MedusaInfoLong": "(Coven):\nThe Medusa can use their kill button on players to mark them as Stoned. When the Medusa vents, all Stoned players will be unable to move and will have reduced vision for a configurable amount of time. With the Necronomicon, killed players will be unreportable.", "PotionMasterInfoLong": "(Coven):\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": "(Coven):\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.", "CovenLeaderInfoLong": "(Coven):\nThe Coven Leader can use their kill button on a fellow Coven member to retrain them into a random Coven role that isn’t currently in the game. During the next meeting, that Coven member will be notified that the Coven Leader wishes to retrain them. They can vote themselves to accept the retrain, or vote otherwise to deny it. Denying the retrain does not take away an ability usage.
With the Necronomicon, you cannot retrain, and can only kill other players.", @@ -966,7 +966,7 @@ "SpellcasterInfoLong": "(Coven):\nThe Spellcaster can use their kill button on two players to control the first player into the other. If the first player has a kill button, they will be forced to use their kill button on the second person.\nWith the Necronomicon, you will kill your first target after controlling them.", "ConjurerInfoLong": "(Coven):\nShapeshift once to mark a location.\nShapeshift again to conjure a meteor at the place you marked, killing everyone in the radius.\nWith the Necronomicon, you can kill. You can also mark a player using the Shapeshift menu. When the Conjurer unshapeshifts, all the players in the radius of the marked player will die, including themselves.", "DreamweaverInfoLong": "(Coven):\nThe Dreamweaver can Dreamweave a player. The dreamweaved players will be notified of this during the next meeting. If the Dreamweaver is not voted out, these players will be unable to use their abilities until the Dreamweaver dies.\nWith the Necronomicon, the Dreamweaver can double-click to kill.", - "IllusionistInfoLong": "(Coven):\nThe Illusionist can use their kill button on a player to reverse the results of any investigative role. For example, if someone with a kill button is Illusioned, they will appear not to have a kill button to the Investigator, and vice versa.\nWith the Necronomicon, you may double-click to kill. Every kill you make appears as a random death reason.", + "IllusionistInfoLong": "(Coven):\nThe Illusionist can use their kill button on a player to reverse the results of any investigative role. For example, if someone with a kill button is Illusioned, they will appear not to have a kill button to the Investigator, and vice versa.\nIllusions wear off after meetings.\nWith the Necronomicon, you may double-click to kill. Every kill you make appears as a random death reason.", "VoodooMasterInfoLong": "(Coven):\nThe Voodoo Master can craft a voodoo doll of a player by using their kill button, similar to the Shaman. All the interactions with you using kill button will be deflected to the voodoo doll and the voodoo doll will destroy. Unlike the Shaman, this voodoo will last during the meeting (eg. If the Voodoo Master is judged, then the voodoo'd player will be judged instead).\nWith the Necronomicon, you can double-click to kill. Additionally, the voodoo’d player will be unable to report. The next person to interact with them will die.", "SacrifistInfoLong": "(Coven):\nThe Sacrifist can vent to cause a random debuff to a non-coven member, however, the Sacrifist will also receive this effect.\nDepending on the host’s settings, if the Sacrifist is voted out, some random non-coven who voted the Sacrifist will die too.\nWith the Necronomicon, when you vent, you commit suicide. However, the entire Coven for the rest of the game receives a lowered kill cooldown.", "MoonDancerInfoLong": "(Coven):\nThe Moon Dancer can use their kill button to use their ability, Baton Pass.\nIf used on a Coven member: Gives a helpful add-on at the next meeting.\nIf used on a non-Coven member: Gives a harmful add-on at the next meeting.\nWith the Necronomicon, the Moon Dancer can double-click their kill button to kill. When killing, the player is teleported off the map. They will appear alive on vitals and will not show up in tracefinder's arrows etc. They die when a meeting/body report with the death reason Blasted Off.", @@ -1975,6 +1975,10 @@ "IllusionistMaxIllusions": "Max Illusions", "IllusionistSnitchAffected": "Snitch is Affected by Illusions", + "MedusaStoneCooldown": "Stone Cooldown", + "MedusaStoneDuration": "Stone Duration", + "MedusaStoneVision": "Stoned Vision", + "LuckyProbability": "Probability of surviving a kill", "ImpCanBeDoubleShot": "Impostors can have Double Shot", "CrewCanBeDoubleShot": "Crewmates can have Double Shot", @@ -3599,6 +3603,7 @@ "WinnerRoleText.Impostor": "Impostors Win!", "WinnerRoleText.Crewmate": "Crewmates Win!", "WinnerRoleText.Apocalypse": "Apocalypse Wins!", + "WinnerRoleText.Coven": "Coven Wins!", "WinnerRoleText.Terrorist": "Terrorist Wins!", "WinnerRoleText.Jester": "Jester Wins!", "WinnerRoleText.Lovers": "Lovers Win!", diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 4385eafe3..c116b3f2a 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -8,6 +8,7 @@ using TOHE.Roles.Crewmate; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; +using TOHE.Roles.Coven; using TOHE.Roles.Vanilla; namespace TOHE.Roles.Core; @@ -104,6 +105,7 @@ public static void BuildCustomGameOptions(this PlayerControl player, ref IGameOp if (Deathpact.HasEnabled) Deathpact.SetDeathpactVision(player, opt); if (Spiritcaller.HasEnabled) Spiritcaller.ReduceVision(opt, player); if (Pitfall.HasEnabled) Pitfall.SetPitfallTrapVision(opt, player); + if (Medusa.HasEnabled) Medusa.SetStoned(player, opt); var playerSubRoles = player.GetCustomSubRoles(); diff --git a/Roles/Coven/Conjurer.cs b/Roles/Coven/Conjurer.cs index 5817014ec..04326371f 100644 --- a/Roles/Coven/Conjurer.cs +++ b/Roles/Coven/Conjurer.cs @@ -1,17 +1,11 @@ using Hazel; using TOHE.Roles.Core; -using TOHE.Roles.Double; -using TOHE.Roles.AddOns.Crewmate; -using TOHE.Modules; using InnerNet; using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; -using System.Text.RegularExpressions; using System; -using TOHE.Modules.ChatManager; using UnityEngine; -using static UnityEngine.GraphicsBuffer; using AmongUs.GameOptions; namespace TOHE.Roles.Coven; diff --git a/Roles/Coven/CovenLeader.cs b/Roles/Coven/CovenLeader.cs index 2bd49e328..60120aa70 100644 --- a/Roles/Coven/CovenLeader.cs +++ b/Roles/Coven/CovenLeader.cs @@ -1,10 +1,7 @@ using AmongUs.GameOptions; using Hazel; using InnerNet; -using System.Diagnostics.Metrics; -using System.Text; using TOHE.Roles.Core; -using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Options; using static TOHE.Translator; diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index a71faa99c..08dc5017e 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -1,18 +1,14 @@ using AmongUs.GameOptions; using Hazel; -using TOHE.Roles.Crewmate; -using TOHE.Roles.Double; -using TOHE.Roles.Neutral; using InnerNet; using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; -using static UnityEngine.GraphicsBuffer; namespace TOHE; public abstract class CovenManager : RoleBase { - public static PlayerControl necroHolder; + public static byte necroHolder; public enum VisOptionList { @@ -61,7 +57,7 @@ public override string GetMark(PlayerControl seer, PlayerControl seen = null, bo => HasNecronomicon(seen) ? ColorString(GetRoleColor(CustomRoles.CovenLeader), "♣") : string.Empty; public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if (!(seer == target) && HasNecronomicon(target) && seer.IsPlayerCoven() && !HasNecronomicon(seer)) + if ((seer != target) && HasNecronomicon(target) && seer.IsPlayerCoven()) { return ColorString(GetRoleColor(CustomRoles.CovenLeader), "♣"); } @@ -77,7 +73,7 @@ private static void SendRPC(byte playerId) public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) { byte NecroId = reader.ReadByte(); - necroHolder = GetPlayerById(NecroId); + necroHolder = NecroId; } public override void ApplyGameOptions(IGameOptions opt, byte playerId) { @@ -103,33 +99,37 @@ public override bool CanUseImpostorVentButton(PlayerControl pc) return option.GetBool(); } } - /* + public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); - */ + public static void GiveNecronomicon() { - var pcList = Main.AllAlivePlayerControls.Where(pc => pc.IsPlayerCoven() && pc.IsAlive()).ToList(); if (pcList.Any()) { - PlayerControl rp = pcList.RandomElement(); + byte rp = pcList.RandomElement().PlayerId; necroHolder = rp; - necroHolder.Notify(GetString("NecronomiconNotification")); - SendRPC(necroHolder.PlayerId); + GetPlayerById(necroHolder).Notify(GetString("NecronomiconNotification")); + SendRPC(necroHolder); } - + } + public static void GiveNecronomicon(byte target) + { + necroHolder = target; + GetPlayerById(necroHolder).Notify(GetString("NecronomiconNotification")); + SendRPC(necroHolder); } public override void OnCoEndGame() { - necroHolder = null; + necroHolder = byte.MaxValue; } public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (!necroHolder.IsAlive()) + if (necroHolder == byte.MaxValue || !GetPlayerById(necroHolder).IsAlive()) { GiveNecronomicon(); } } - public static bool HasNecronomicon(PlayerControl pc) => necroHolder.Equals(pc); + public static bool HasNecronomicon(PlayerControl pc) => necroHolder == pc.PlayerId; } diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index 64590a7f5..7afa4ba08 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -1,18 +1,12 @@ using Hazel; using TOHE.Roles.Core; -using TOHE.Roles.AddOns.Impostor; -using TOHE.Modules; using InnerNet; using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; -using System.Text.RegularExpressions; using System; -using TOHE.Modules.ChatManager; using UnityEngine; -using static UnityEngine.GraphicsBuffer; -using AmongUs.GameOptions; namespace TOHE.Roles.Coven; @@ -22,13 +16,13 @@ internal class Illusionist : CovenManager private const int Id = 30400; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Illusionist); public override bool IsDesyncRole => true; - public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenTrickery; //==================================================================\\ private static OptionItem IllusionCooldown; private static OptionItem MaxIllusions; - private static OptionItem SnitchCanIllusioned; + public static OptionItem SnitchCanIllusioned; private static readonly Dictionary> IllusionedPlayers = []; @@ -38,7 +32,7 @@ public override void SetupCustomOption() SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Illusionist, 1, zeroOne: false); IllusionCooldown = FloatOptionItem.Create(Id + 10, "IllusionCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Illusionist]) .SetValueFormat(OptionFormat.Seconds); - MaxIllusions = IntegerOptionItem.Create(Id + 11, "IllusionistMaxIllusions", new(1, 15, 1), 5, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Illusionist]) + MaxIllusions = IntegerOptionItem.Create(Id + 11, "IllusionistMaxIllusions", new(1, 100, 1), 5, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Illusionist]) .SetValueFormat(OptionFormat.Times); SnitchCanIllusioned = BooleanOptionItem.Create(Id + 12, "IllusionistSnitchAffected", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Illusionist]); } @@ -53,27 +47,47 @@ public override void Add(byte playerId) IllusionedPlayers[playerId] = []; GetPlayerById(playerId)?.AddDoubleTrigger(); } - public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + public void SendRPC(PlayerControl player, PlayerControl target) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + writer.WriteNetObject(_Player); + writer.Write(player.PlayerId); + writer.Write(AbilityLimit); + writer.Write(target.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) { - if (killer.CheckDoubleTrigger(target, () => - { - killer.SetKillCooldown(); + byte playerId = reader.ReadByte(); - if (!IllusionedPlayers[killer.PlayerId].Contains(target.PlayerId)) - { - IllusionedPlayers[killer.PlayerId].Add(target.PlayerId); - } - }) - ) //this looks so ugly + AbilityLimit = reader.ReadSingle(); + IllusionedPlayers[playerId].Add(reader.ReadByte()); + } + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (killer.CheckDoubleTrigger(target, () => { IllusionedPlayers[killer.PlayerId].Add(target.PlayerId); })) + { + if (HasNecronomicon(killer)) { + var randomDeathReason = ChangeRandomDeath(); + Main.PlayerStates[target.PlayerId].deathReason = randomDeathReason; + Main.PlayerStates[target.PlayerId].SetDead(); + return true; + } + return false; + } + else { - var randomDeathReason = ChangeRandomDeath(); - Main.PlayerStates[target.PlayerId].deathReason = randomDeathReason; - Main.PlayerStates[target.PlayerId].SetDead(); - return true; + AbilityLimit--; + SendRPC(killer, target); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); + return false; } - return false; } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = IllusionCooldown.GetFloat(); + public override bool CanUseKillButton(PlayerControl pc) => true; + public override string GetProgressText(byte playerId, bool comms) + => ColorString(AbilityLimit >= 1 ? GetRoleColor(CustomRoles.Ritualist).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); private static PlayerState.DeathReason ChangeRandomDeath() { PlayerState.DeathReason[] deathReasons = EnumHelper.GetAllValues().ToArray(); @@ -82,6 +96,7 @@ private static PlayerState.DeathReason ChangeRandomDeath() int randomIndex = random.Next(deathReasons.Length); return deathReasons[randomIndex]; } + // Affects the following roles: Snitch, Witness, Psychic, Inspector, Oracle, Investigator public static bool IsNonCovIllusioned(byte target) { byte pc = Utils.GetPlayerListByRole(CustomRoles.Illusionist).First().PlayerId; diff --git a/Roles/Coven/Medusa.cs b/Roles/Coven/Medusa.cs index 3362afb49..c994e32dd 100644 --- a/Roles/Coven/Medusa.cs +++ b/Roles/Coven/Medusa.cs @@ -1,6 +1,11 @@ -using AmongUs.GameOptions; +using AmongUs.GameOptions; using static TOHE.Translator; using static TOHE.Options; +using static TOHE.Utils; +using TOHE.Roles.Core; +using static UnityEngine.GraphicsBuffer; +using Hazel; +using InnerNet; namespace TOHE.Roles.Coven; @@ -8,42 +13,76 @@ internal class Medusa : CovenManager { //===========================SETUP================================\\ private const int Id = 17000; - private static readonly HashSet playerIdList = []; - public static bool HasEnabled => playerIdList.Any(); + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Medusa); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; //==================================================================\\ - private static OptionItem KillCooldown; - private static OptionItem KillCooldownAfterStoneGazing; + private static OptionItem StoneCooldown; + private static OptionItem StoneDuration; + private static OptionItem StoneVision; + //private static OptionItem KillCooldownAfterStoneGazing; //private static OptionItem CanVent; //private static OptionItem HasImpostorVision; + private static readonly Dictionary> StonedPlayers = []; + private static readonly Dictionary> PreStonedPlayers = []; + private static readonly Dictionary originalSpeed = []; + + + public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Medusa, 1, zeroOne: false); - KillCooldown = FloatOptionItem.Create(Id + 12, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 20f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]) + StoneCooldown = FloatOptionItem.Create(Id + 12, "MedusaStoneCooldown", new(0f, 180f, 2.5f), 20f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]) + .SetValueFormat(OptionFormat.Seconds); + StoneDuration = FloatOptionItem.Create(Id + 14, "MedusaStoneDuration", new(0f, 180f, 2.5f), 15f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]) .SetValueFormat(OptionFormat.Seconds); + StoneVision = FloatOptionItem.Create(Id + 16, "MedusaStoneVision", new(0f, 5f, 0.25f), 0.5f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]) + .SetValueFormat(OptionFormat.Multiplier); + /* KillCooldownAfterStoneGazing = FloatOptionItem.Create(Id + 15, "KillCooldownAfterStoneGazing", new(0f, 180f, 2.5f), 40f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]) .SetValueFormat(OptionFormat.Seconds); - //CanVent = BooleanOptionItem.Create(Id + 11, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); - //HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); + CanVent = BooleanOptionItem.Create(Id + 11, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); + HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); + */ } public override void Init() { - playerIdList.Clear(); + StonedPlayers.Clear(); + originalSpeed.Clear(); + PreStonedPlayers.Clear(); } public override void Add(byte playerId) { - playerIdList.Add(playerId); + StonedPlayers[playerId] = []; + PreStonedPlayers[playerId] = []; + } + + public void SendRPC(PlayerControl player, PlayerControl target) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + writer.WriteNetObject(_Player); + writer.Write(player.PlayerId); + writer.Write(AbilityLimit); + writer.Write(target.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); } - public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) + { + byte playerId = reader.ReadByte(); + + AbilityLimit = reader.ReadSingle(); + PreStonedPlayers[playerId].Add(reader.ReadByte()); + } + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = StoneCooldown.GetFloat(); //public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override bool CanUseKillButton(PlayerControl pc) => true; + public override bool CanUseImpostorVentButton(PlayerControl pc) => true; //public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); - + /* public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) { if (reporter.Is(CustomRoles.Medusa)) @@ -57,6 +96,57 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay } return true; } + */ + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + if (HasNecronomicon(killer) || !target.IsPlayerCoven()) { + killer.RpcMurderPlayer(target); + killer.ResetKillCooldown(); + Main.UnreportableBodies.Add(target.PlayerId); + return false; + } + else + { + PreStonedPlayers[killer.PlayerId].Add(target.PlayerId); + killer.Notify(GetString("MedusaStonedPlayer")); + return false; + } + } + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + foreach (var player in PreStonedPlayers[pc.PlayerId]) + { + StonedPlayers[pc.PlayerId].Add(player); + PreStonedPlayers[pc.PlayerId].Remove(player); + } + foreach (var player in StonedPlayers[pc.PlayerId]) + { + originalSpeed.Remove(player); + originalSpeed.Add(player, Main.AllPlayerSpeed[player]); + Main.AllPlayerSpeed[player] = 0f; + ReportDeadBodyPatch.CanReport[player] = false; + GetPlayerById(player).MarkDirtySettings(); + _ = new LateTask(() => + { + Main.AllPlayerSpeed[player] = originalSpeed[player]; + GetPlayerById(player).SyncSettings(); + originalSpeed.Remove(player); + StonedPlayers[pc.PlayerId].Remove(player); + }, StoneDuration.GetFloat(), "Medusa Revert Stone"); + } + } + public static void SetStoned(PlayerControl player, IGameOptions opt) + { + if (StonedPlayers.Any(a => a.Value.Contains(player.PlayerId) && + Main.AllAlivePlayerControls.Any(b => b.PlayerId == a.Key))) + { + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, StoneDuration.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, StoneDuration.GetFloat()); + } + } + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => PreStonedPlayers[seer.PlayerId].Contains(seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.Medusa), "♻") : string.Empty; public override void SetAbilityButtonText(HudManager hud, byte playerId) { hud.ReportButton.OverrideText(GetString("MedusaReportButtonText")); diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index 3bdb2d780..ea94bde0c 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -2,7 +2,6 @@ using TOHE.Roles.Core; using TOHE.Roles.Double; using TOHE.Roles.AddOns.Crewmate; -using TOHE.Modules; using InnerNet; using static TOHE.Options; using static TOHE.Translator; @@ -11,7 +10,6 @@ using System; using TOHE.Modules.ChatManager; using UnityEngine; -using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Coven; @@ -29,6 +27,8 @@ internal class Ritualist : CovenManager public static OptionItem TryHideMsg; private static readonly Dictionary RitualLimit = []; + private static readonly Dictionary> EnchantedPlayers = []; + public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Ritualist, 1, zeroOne: false); @@ -40,14 +40,18 @@ public override void SetupCustomOption() public override void Init() { RitualLimit.Clear(); + EnchantedPlayers.Clear(); } public override void Add(byte PlayerId) { + EnchantedPlayers[PlayerId] = []; RitualLimit.Add(PlayerId, MaxRitsPerRound.GetInt()); } - public override void Remove(byte playerId) + private static void SendRPC(byte playerId) { - RitualLimit.Remove(playerId); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.BloodRitual, SendOption.Reliable, -1); + writer.Write(playerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); } public static void ReceiveRPC_Custom(MessageReader reader, PlayerControl pc) { @@ -62,8 +66,10 @@ public override void OnReportDeadBody(PlayerControl hatsune, NetworkedPlayerInfo RitualLimit[pid] = MaxRitsPerRound.GetInt(); } } + public override string NotifyPlayerName(PlayerControl seer, PlayerControl target, string TargetPlayerName = "", bool IsForMeeting = false) + => IsForMeeting && seer.IsAlive() && target.IsAlive() ? ColorString(GetRoleColor(CustomRoles.Ritualist), target.PlayerId.ToString()) + " " + TargetPlayerName : ""; public override string PVANameText(PlayerVoteArea pva, PlayerControl seer, PlayerControl target) - => seer.IsAlive() && target.IsAlive() ? ColorString(GetRoleColor(CustomRoles.Ritualist), target.PlayerId.ToString()) + " " + pva.NameText.text : string.Empty; + => seer.IsAlive() && target.IsAlive() ? ColorString(GetRoleColor(CustomRoles.Ritualist), target.PlayerId.ToString()) + " " + pva.NameText.text : ""; public static bool RitualistMsgCheck(PlayerControl pc, string msg, bool isUI = false) { if (!AmongUsClient.Instance.AmHost) return false; @@ -125,13 +131,22 @@ public static bool RitualistMsgCheck(PlayerControl pc, string msg, bool isUI = f RitualLimit[pc.PlayerId]--; - target.RpcSetCustomRole(CustomRoles.Enchanted); + EnchantedPlayers[pc.PlayerId].Add(target.PlayerId); SendMessage(string.Format(GetString("RitualistConvertNotif"), CustomRoles.Ritualist.ToColoredString()), target.PlayerId); SendMessage(string.Format(GetString("RitualistRitualSuccess"), target.GetRealName()), pc.PlayerId); return true; } return false; } + public override void AfterMeetingTasks() + { + var rit = Utils.GetPlayerListByRole(CustomRoles.Ritualist).First(); + foreach (var pc in EnchantedPlayers[rit.PlayerId]) + { + GetPlayerById(pc).RpcSetCustomRole(CustomRoles.Enchanted); + } + EnchantedPlayers[rit.PlayerId].Clear(); + } private static bool MsgToPlayerAndRole(string msg, out byte id, out CustomRoles role, out string error) { if (msg.StartsWith("/")) msg = msg.Replace("/", string.Empty); diff --git a/Roles/Crewmate/Inspector.cs b/Roles/Crewmate/Inspector.cs index 6094cf138..b85f5f6ed 100644 --- a/Roles/Crewmate/Inspector.cs +++ b/Roles/Crewmate/Inspector.cs @@ -4,6 +4,7 @@ using TOHE.Modules.ChatManager; using TOHE.Roles.AddOns.Common; using TOHE.Roles.Core; +using TOHE.Roles.Coven; using UnityEngine; using static TOHE.Options; using static TOHE.Translator; @@ -216,21 +217,26 @@ public static bool InspectCheckMsg(PlayerControl pc, string msg, bool isUI = fal if ( ( - (target1.GetCustomRole().IsImpostorTeamV2() || target1.IsAnySubRole(role => role.IsImpostorTeamV2())) && !target1.Is(CustomRoles.Admired) + ((target1.IsPlayerCoven() || target1.Is(CustomRoles.Enchanted) || Illusionist.IsNonCovIllusioned(target1.PlayerId))) + && (target2.IsPlayerCoven() || target2.Is(CustomRoles.Enchanted) || Illusionist.IsNonCovIllusioned(target2.PlayerId)) + ) + || + ( + (Illusionist.IsCovIllusioned(target1.PlayerId) || (target1.GetCustomRole().IsCrewmateTeamV2() && (target1.GetCustomSubRoles().All(role => role.IsCrewmateTeamV2()) || target1.GetCustomSubRoles().Count == 0)) || target1.Is(CustomRoles.Admired)) && - (target2.GetCustomRole().IsImpostorTeamV2() || target2.IsAnySubRole(role => role.IsImpostorTeamV2()) && !target2.Is(CustomRoles.Admired)) + (Illusionist.IsCovIllusioned(target2.PlayerId) || (target2.GetCustomRole().IsCrewmateTeamV2() && (target2.GetCustomSubRoles().All(role => role.IsCrewmateTeamV2()) || target2.GetCustomSubRoles().Count == 0)) || target2.Is(CustomRoles.Admired)) ) || ( - (target1.GetCustomRole().IsNeutralTeamV2() || target1.IsAnySubRole(role => role.IsNeutralTeamV2())) && !target1.Is(CustomRoles.Admired) + (target1.GetCustomRole().IsImpostorTeamV2() || target1.IsAnySubRole(role => role.IsImpostorTeamV2())) && !target1.Is(CustomRoles.Admired) && - (target2.GetCustomRole().IsNeutralTeamV2() || target2.IsAnySubRole(role => role.IsNeutralTeamV2())) && !target2.Is(CustomRoles.Admired) + (target2.GetCustomRole().IsImpostorTeamV2() || target2.IsAnySubRole(role => role.IsImpostorTeamV2()) && !target2.Is(CustomRoles.Admired)) ) || ( - ((target1.GetCustomRole().IsCrewmateTeamV2() && (target1.GetCustomSubRoles().All(role => role.IsCrewmateTeamV2()) || target1.GetCustomSubRoles().Count == 0)) || target1.Is(CustomRoles.Admired)) + (target1.GetCustomRole().IsNeutralTeamV2() || target1.IsAnySubRole(role => role.IsNeutralTeamV2())) && !target1.Is(CustomRoles.Admired) && - ((target2.GetCustomRole().IsCrewmateTeamV2() && (target2.GetCustomSubRoles().All(role => role.IsCrewmateTeamV2()) || target2.GetCustomSubRoles().Count == 0)) || target2.Is(CustomRoles.Admired)) + (target2.GetCustomRole().IsNeutralTeamV2() || target2.IsAnySubRole(role => role.IsNeutralTeamV2())) && !target2.Is(CustomRoles.Admired) ) ) { diff --git a/Roles/Crewmate/Oracle.cs b/Roles/Crewmate/Oracle.cs index 76990d760..e2ca1000d 100644 --- a/Roles/Crewmate/Oracle.cs +++ b/Roles/Crewmate/Oracle.cs @@ -7,6 +7,7 @@ using static TOHE.Translator; using static TOHE.Utils; using InnerNet; +using TOHE.Roles.Coven; namespace TOHE.Roles.Crewmate; @@ -106,8 +107,11 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) if (ChangeRecruitTeam.GetBool()) { if (target.Is(CustomRoles.Admired)) text = "Crewmate"; + else if (Illusionist.IsCovIllusioned(target.PlayerId)) text = "Crewmate"; + else if (Illusionist.IsNonCovIllusioned(target.PlayerId)) text = "Coven"; else if (target.GetCustomRole().IsImpostorTeamV2() || target.GetCustomSubRoles().Any(role => role.IsImpostorTeamV2())) text = "Impostor"; else if (target.GetCustomRole().IsNeutralTeamV2() || target.GetCustomSubRoles().Any(role => role.IsNeutralTeamV2())) text = "Neutral"; + else if (target.IsPlayerCoven() || target.Is(CustomRoles.Enchanted)) text = "Coven"; else if (target.GetCustomRole().IsCrewmateTeamV2() && (target.GetCustomSubRoles().Any(role => role.IsCrewmateTeamV2()) || (target.GetCustomSubRoles().Count == 0))) text = "Crewmate"; } else diff --git a/Roles/Crewmate/Psychic.cs b/Roles/Crewmate/Psychic.cs index be3f25e46..864460b09 100644 --- a/Roles/Crewmate/Psychic.cs +++ b/Roles/Crewmate/Psychic.cs @@ -1,6 +1,7 @@ using Hazel; using InnerNet; using TOHE.Roles.Core; +using TOHE.Roles.Coven; using static TOHE.Options; using static TOHE.Utils; @@ -84,7 +85,7 @@ private void GetRedName() { if (!_Player.IsAlive() || !AmongUsClient.Instance.AmHost) return; - List BadListPc = Main.AllAlivePlayerControls.Where(x => + List BadListPc = Main.AllAlivePlayerControls.Where(x => Illusionist.IsNonCovIllusioned(x.PlayerId) || (x.Is(Custom_Team.Impostor) && !x.Is(CustomRoles.Trickster) && !x.Is(CustomRoles.Admired)) || x.IsAnySubRole(x => x.IsConverted()) || (x.GetCustomRole().IsCrewKiller() && CkshowEvil.GetBool()) || diff --git a/Roles/Crewmate/Snitch.cs b/Roles/Crewmate/Snitch.cs index e8343a780..3a620841c 100644 --- a/Roles/Crewmate/Snitch.cs +++ b/Roles/Crewmate/Snitch.cs @@ -3,6 +3,7 @@ using static TOHE.Translator; using static TOHE.Options; using InnerNet; +using TOHE.Roles.Coven; namespace TOHE.Roles.Crewmate; @@ -174,7 +175,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl pc) { foreach (var target in Main.AllAlivePlayerControls) { - if (!IsSnitchTarget(target)) continue; + if (!IsSnitchTarget(target) || (!Illusionist.IsNonCovIllusioned(target.PlayerId) && Illusionist.SnitchCanIllusioned.GetBool())) continue; var targetId = target.PlayerId; diff --git a/Roles/Crewmate/Witness.cs b/Roles/Crewmate/Witness.cs index d47ed8396..fa1133782 100644 --- a/Roles/Crewmate/Witness.cs +++ b/Roles/Crewmate/Witness.cs @@ -1,8 +1,10 @@ using AmongUs.GameOptions; using TOHE.Roles.Core; +using TOHE.Roles.Coven; using UnityEngine; using static TOHE.Options; using static TOHE.Translator; +using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Crewmate; @@ -54,7 +56,7 @@ public override void SetAbilityButtonText(HudManager hud, byte id) public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { killer.SetKillCooldown(); - if (Main.AllKillers.ContainsKey(target.PlayerId)) + if (Illusionist.IsNonCovIllusioned(target.PlayerId) || (Main.AllKillers.ContainsKey(target.PlayerId) && !Illusionist.IsCovIllusioned(target.PlayerId))) killer.Notify(GetString("WitnessFoundKiller")); else killer.Notify(GetString("WitnessFoundInnocent")); From 94b2aee14b235764e0205b0084546bc6527ede9f Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 29 Sep 2024 12:51:32 -0400 Subject: [PATCH 010/101] update strings --- Resources/Lang/en_US.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index b5f647ad8..572876a5d 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -957,14 +957,14 @@ "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", "PoisonerInfoLong": "(Coven):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", "HexMasterInfoLong": "(Coven):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", - "JinxInfoLong": "(Coven):\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.", - "MedusaInfoLong": "(Coven):\nThe Medusa can use their kill button on players to mark them as Stoned. When the Medusa vents, all Stoned players will be unable to move and will have reduced vision for a configurable amount of time. With the Necronomicon, killed players will be unreportable.", + "JinxInfoLong": "(Coven):\nAs the Jinx, whenever you get attacked, you jinx them, resulting in them dying to a jinx.\nThis has limited uses.", + "MedusaInfoLong": "(Coven):\nThe Medusa can use their kill button on players to mark them as Stoned. When the Medusa vents, all Stoned players will be unable to move and will have reduced vision for a configurable amount of time.\nWith the Necronomicon, killed players will be unreportable.", "PotionMasterInfoLong": "(Coven):\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": "(Coven):\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.", - "CovenLeaderInfoLong": "(Coven):\nThe Coven Leader can use their kill button on a fellow Coven member to retrain them into a random Coven role that isn’t currently in the game. During the next meeting, that Coven member will be notified that the Coven Leader wishes to retrain them. They can vote themselves to accept the retrain, or vote otherwise to deny it. Denying the retrain does not take away an ability usage.
With the Necronomicon, you cannot retrain, and can only kill other players.", - "RitualistInfoLong": "(Coven):\nDuring a meeting, the Ritualist can perform a Blood Ritual to guess a player’s exact role. If the Ritualist is correct, that player will be converted to Coven. If the Ritualist is incorrect, they will not die but will be unable to Blood Ritual until the next meeting.
The command is /rt id role.
With the Necronomicon, the Ritualist can kill.", + "NecromancerInfoLong": "(Coven):\nAs the Necromancer, 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.", + "CovenLeaderInfoLong": "(Coven):\nThe Coven Leader can use their kill button on a fellow Coven member to retrain them into a random Coven role that isn’t currently in the game. During the next meeting, that Coven member will be notified that the Coven Leader wishes to retrain them. They can vote themselves to accept the retrain, or vote otherwise to deny it. Denying the retrain does not take away an ability usage.\nWith the Necronomicon, you cannot retrain, and can only kill other players.", + "RitualistInfoLong": "(Coven):\nDuring a meeting, the Ritualist can perform a Blood Ritual to guess a player’s exact role. If the Ritualist is correct, that player will be converted to Coven. If the Ritualist is incorrect, they will not die but will be unable to Blood Ritual until the next meeting.\nhe command is /rt id role.\nWith the Necronomicon, the Ritualist can kill.", "SpellcasterInfoLong": "(Coven):\nThe Spellcaster can use their kill button on two players to control the first player into the other. If the first player has a kill button, they will be forced to use their kill button on the second person.\nWith the Necronomicon, you will kill your first target after controlling them.", - "ConjurerInfoLong": "(Coven):\nShapeshift once to mark a location.\nShapeshift again to conjure a meteor at the place you marked, killing everyone in the radius.\nWith the Necronomicon, you can kill. You can also mark a player using the Shapeshift menu. When the Conjurer unshapeshifts, all the players in the radius of the marked player will die, including themselves.", + "ConjurerInfoLong": "(Coven):\nShapeshift once to mark a location.\nShapeshift again to conjure a meteor at the place you marked, killing everyone in the radius.\nWith the Necronomicon, you can kill. You can also mark a player using the Shapeshift menu. When the Conjurer unshapeshifts, all the players in the radius of the marked player will die, including the marked player.", "DreamweaverInfoLong": "(Coven):\nThe Dreamweaver can Dreamweave a player. The dreamweaved players will be notified of this during the next meeting. If the Dreamweaver is not voted out, these players will be unable to use their abilities until the Dreamweaver dies.\nWith the Necronomicon, the Dreamweaver can double-click to kill.", "IllusionistInfoLong": "(Coven):\nThe Illusionist can use their kill button on a player to reverse the results of any investigative role. For example, if someone with a kill button is Illusioned, they will appear not to have a kill button to the Investigator, and vice versa.\nIllusions wear off after meetings.\nWith the Necronomicon, you may double-click to kill. Every kill you make appears as a random death reason.", "VoodooMasterInfoLong": "(Coven):\nThe Voodoo Master can craft a voodoo doll of a player by using their kill button, similar to the Shaman. All the interactions with you using kill button will be deflected to the voodoo doll and the voodoo doll will destroy. Unlike the Shaman, this voodoo will last during the meeting (eg. If the Voodoo Master is judged, then the voodoo'd player will be judged instead).\nWith the Necronomicon, you can double-click to kill. Additionally, the voodoo’d player will be unable to report. The next person to interact with them will die.", From 752a4d09e672e2b8b8f3f53c638399ad4801fabf Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 5 Oct 2024 17:39:46 -0400 Subject: [PATCH 011/101] medusa fix i think i broke necronomicon's rpc with this but im hoping that was an alpha 16 issue cuz i heard a lot of rpc issues about alpha 16 --- Modules/RPC.cs | 4 ++++ Resources/Lang/en_US.json | 4 ++++ Roles/Coven/CovenManager.cs | 6 +++--- Roles/Coven/Medusa.cs | 40 +++++++++++++++++-------------------- 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index bc31b63a9..c9c5ae6a9 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -115,6 +115,7 @@ enum CustomRPC : byte // 185/255 USED //FFA SyncFFAPlayer, SyncFFANameNotify, + Necronomicon, } public enum Sounds { @@ -697,6 +698,9 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c Main.FirstDied = reader.ReadString(); Main.FirstDiedPrevious = reader.ReadString(); break; + case CustomRPC.Necronomicon: + CovenManager.ReceiveNecroRPC(reader); + break; } } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 572876a5d..e00a75a1b 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1980,6 +1980,10 @@ "MedusaStoneCooldown": "Stone Cooldown", "MedusaStoneDuration": "Stone Duration", "MedusaStoneVision": "Stoned Vision", + "MedusaStonedPlayer": "{0} has been Stoned", + "MedusaStoningStart": "Stoning in progress", + "MedusaStoningEnd": "Stoning has ended", + "LuckyProbability": "Probability of surviving a kill", "ImpCanBeDoubleShot": "Impostors can have Double Shot", diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 08dc5017e..263deea5d 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -65,12 +65,12 @@ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, b } private static void SendRPC(byte playerId) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Necronomicon, SendOption.Reliable, -1); writer.WriteNetObject(GetPlayerById(playerId)); writer.Write(playerId); AmongUsClient.Instance.FinishRpcImmediately(writer); } - public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) + public static void ReceiveNecroRPC(MessageReader reader) { byte NecroId = reader.ReadByte(); necroHolder = NecroId; @@ -126,7 +126,7 @@ public override void OnCoEndGame() } public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (necroHolder == byte.MaxValue || !GetPlayerById(necroHolder).IsAlive()) + if (necroHolder == byte.MaxValue || !GetPlayerById(necroHolder).IsAlive() || !GetPlayerById(necroHolder).IsPlayerCoven()) { GiveNecronomicon(); } diff --git a/Roles/Coven/Medusa.cs b/Roles/Coven/Medusa.cs index c994e32dd..aa9fea1ee 100644 --- a/Roles/Coven/Medusa.cs +++ b/Roles/Coven/Medusa.cs @@ -27,8 +27,8 @@ internal class Medusa : CovenManager //private static OptionItem HasImpostorVision; private static readonly Dictionary> StonedPlayers = []; - private static readonly Dictionary> PreStonedPlayers = []; private static readonly Dictionary originalSpeed = []; + private static bool isStoning; @@ -52,12 +52,11 @@ public override void Init() { StonedPlayers.Clear(); originalSpeed.Clear(); - PreStonedPlayers.Clear(); } public override void Add(byte playerId) { StonedPlayers[playerId] = []; - PreStonedPlayers[playerId] = []; + isStoning = false; } public void SendRPC(PlayerControl player, PlayerControl target) @@ -65,16 +64,13 @@ public void SendRPC(PlayerControl player, PlayerControl target) MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); writer.WriteNetObject(_Player); writer.Write(player.PlayerId); - writer.Write(AbilityLimit); writer.Write(target.PlayerId); AmongUsClient.Instance.FinishRpcImmediately(writer); } public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) { byte playerId = reader.ReadByte(); - - AbilityLimit = reader.ReadSingle(); - PreStonedPlayers[playerId].Add(reader.ReadByte()); + StonedPlayers[playerId].Add(reader.ReadByte()); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = StoneCooldown.GetFloat(); //public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); @@ -100,7 +96,7 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (killer == null || target == null) return false; - if (HasNecronomicon(killer) || !target.IsPlayerCoven()) { + if (HasNecronomicon(killer) && !target.IsPlayerCoven()) { killer.RpcMurderPlayer(target); killer.ResetKillCooldown(); Main.UnreportableBodies.Add(target.PlayerId); @@ -108,20 +104,18 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } else { - PreStonedPlayers[killer.PlayerId].Add(target.PlayerId); - killer.Notify(GetString("MedusaStonedPlayer")); + StonedPlayers[killer.PlayerId].Add(target.PlayerId); + killer.Notify(string.Format(GetString("MedusaStonedPlayer"), target.GetRealName())); return false; } } - public override void OnEnterVent(PlayerControl pc, Vent vent) + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) { - foreach (var player in PreStonedPlayers[pc.PlayerId]) - { - StonedPlayers[pc.PlayerId].Add(player); - PreStonedPlayers[pc.PlayerId].Remove(player); - } - foreach (var player in StonedPlayers[pc.PlayerId]) + var dusa = physics.myPlayer; + foreach (var player in StonedPlayers[dusa.PlayerId]) { + dusa.Notify(GetString("MedusaStoningStart"), StoneDuration.GetFloat()); + isStoning = true; originalSpeed.Remove(player); originalSpeed.Add(player, Main.AllPlayerSpeed[player]); Main.AllPlayerSpeed[player] = 0f; @@ -129,24 +123,26 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) GetPlayerById(player).MarkDirtySettings(); _ = new LateTask(() => { + dusa.Notify(GetString("MedusaStoningEnd")); + isStoning = false; Main.AllPlayerSpeed[player] = originalSpeed[player]; GetPlayerById(player).SyncSettings(); originalSpeed.Remove(player); - StonedPlayers[pc.PlayerId].Remove(player); + StonedPlayers[dusa.PlayerId].Remove(player); }, StoneDuration.GetFloat(), "Medusa Revert Stone"); } } public static void SetStoned(PlayerControl player, IGameOptions opt) { if (StonedPlayers.Any(a => a.Value.Contains(player.PlayerId) && - Main.AllAlivePlayerControls.Any(b => b.PlayerId == a.Key))) + Main.AllAlivePlayerControls.Any(b => b.PlayerId == a.Key)) && isStoning) { opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, StoneDuration.GetFloat()); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, StoneDuration.GetFloat()); + opt.SetFloat(FloatOptionNames.CrewLightMod, StoneVision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, StoneVision.GetFloat()); } } - public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => PreStonedPlayers[seer.PlayerId].Contains(seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.Medusa), "♻") : string.Empty; + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => StonedPlayers[seer.PlayerId].Contains(seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.Medusa), "♻") : string.Empty; public override void SetAbilityButtonText(HudManager hud, byte playerId) { hud.ReportButton.OverrideText(GetString("MedusaReportButtonText")); From 696b0ef978b999423770c05bc3b6965650e904d2 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:44:09 -0400 Subject: [PATCH 012/101] moon dancer --- Modules/GameState.cs | 1 + Modules/Utils.cs | 1 + Resources/Lang/en_US.json | 10 ++ Resources/Sounds/BlastOff.wav | Bin 0 -> 299446 bytes Roles/Coven/CovenManager.cs | 2 +- Roles/Coven/MoonDancer.cs | 268 ++++++++++++++++++++++++++++++++++ 6 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 Resources/Sounds/BlastOff.wav diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 15cf691d8..c8c7017ca 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -306,6 +306,7 @@ public enum DeathReason Starved, Armageddon, Sacrificed, + BlastedOff, //Please add all new roles with deathreason & new deathreason in Utils.DeathReasonIsEnable(); etc = -1, diff --git a/Modules/Utils.cs b/Modules/Utils.cs index b4964e35b..d773a5246 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2356,6 +2356,7 @@ var Breason when BannedReason(Breason) => false, PlayerState.DeathReason.BloodLet => CustomRoles.Bloodmoon.IsEnable(), PlayerState.DeathReason.Starved => CustomRoles.Baker.IsEnable(), PlayerState.DeathReason.Sacrificed => CustomRoles.Altruist.IsEnable(), + PlayerState.DeathReason.BlastedOff => CustomRoles.MoonDancer.IsEnable(), PlayerState.DeathReason.Kill => true, _ => true, }; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index e00a75a1b..99f268a43 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1984,6 +1984,15 @@ "MedusaStoningStart": "Stoning in progress", "MedusaStoningEnd": "Stoning has ended", + "MoonDancerBatonPassCooldown": "Baton Pass Cooldown", + "MoonDancerBlastOffChance": "Chance to Blast Off", + "MoonDancerPassEnabledAddons": "Can only Baton Pass enabled Add-ons", + "MoonDancerCantBlastOff": "Target can not be Blasted Off", + "MoonDancerNormalKill": "Killed normally", + "MoonDancerGiveHelpfulAddon": "Target will be given Helpful Add-on during meeting", + "MoonDancerGiveHarmfulAddon": "Target will be given Harmful Add-on during meeting", + "MoonDancerNoAddons": "No Add-ons to Baton Pass", + "LuckyProbability": "Probability of surviving a kill", "ImpCanBeDoubleShot": "Impostors can have Double Shot", @@ -2080,6 +2089,7 @@ "DeathReason.Starved": "Starved", "DeathReason.Equilibrium": "Equilibrium", "DeathReason.Sacrificed": "Sacrificed", + "DeathReason.BlastedOff": "Blasted Off", "OnlyEnabledDeathReasons": "Only Enabled Death Reasons", "Alive": "Alive", "Disconnected": "Disconnected", diff --git a/Resources/Sounds/BlastOff.wav b/Resources/Sounds/BlastOff.wav new file mode 100644 index 0000000000000000000000000000000000000000..4418f82e6ebc8cfbaf545f08a1fec0e9126817ad GIT binary patch literal 299446 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 K?l;3f%l`{}wVH(h literal 0 HcmV?d00001 diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 263deea5d..85cf51e1e 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -8,7 +8,7 @@ namespace TOHE; public abstract class CovenManager : RoleBase { - public static byte necroHolder; + public static byte necroHolder = byte.MaxValue; public enum VisOptionList { diff --git a/Roles/Coven/MoonDancer.cs b/Roles/Coven/MoonDancer.cs index e69de29bb..436d127b1 100644 --- a/Roles/Coven/MoonDancer.cs +++ b/Roles/Coven/MoonDancer.cs @@ -0,0 +1,268 @@ +using Hazel; +using TOHE.Roles.Core; +using InnerNet; +using static TOHE.Options; +using static TOHE.Translator; +using static TOHE.Utils; +using TOHE.Roles.AddOns; +using TOHE.Roles.Double; +using TOHE.Roles.Neutral; +using TOHE.Roles.Crewmate; +using TOHE.Roles.Impostor; +using TOHE.Modules; + +namespace TOHE.Roles.Coven; + +internal class MoonDancer : CovenManager +{ + //===========================SETUP================================\\ + private const int Id = 30500; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.MoonDancer); + public override bool IsDesyncRole => true; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; + //==================================================================\\ + + private static OptionItem BatonPassCooldown; + private static OptionItem BlastOffChance; + private static OptionItem BatonPassEnabledAddons; + + private static List addons = []; + private static readonly Dictionary> BatonPassList = []; + private static readonly Dictionary> BlastedOffList = []; + private static readonly Dictionary originalSpeed = []; + + public override void SetupCustomOption() + { + SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.MoonDancer, 1, zeroOne: false); + BatonPassCooldown = FloatOptionItem.Create(Id + 10, "MoonDancerBatonPassCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.MoonDancer]) + .SetValueFormat(OptionFormat.Seconds); + BlastOffChance = IntegerOptionItem.Create(Id + 11, "MoonDancerBlastOffChance", new(0, 100, 1), 50, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.MoonDancer]) + .SetValueFormat(OptionFormat.Percent); + BatonPassEnabledAddons = BooleanOptionItem.Create(Id + 12, "MoonDancerPassEnabledAddons", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.MoonDancer]); + } + public override void Init() + { + BatonPassList.Clear(); + addons.Clear(); + BlastedOffList.Clear(); + originalSpeed.Clear(); + + addons.AddRange(GroupedAddons[AddonTypes.Helpful]); + addons.AddRange(GroupedAddons[AddonTypes.Harmful]); + if (BatonPassEnabledAddons.GetBool()) + { + addons = addons.Where(role => role.GetMode() != 0).ToList(); + } + } + public override void Add(byte playerId) + { + BatonPassList.Add(playerId, []); + BlastedOffList.Add(playerId, []); + } + private void SyncBlastList() + { + SendRPC(byte.MaxValue); + foreach (var bl in BlastedOffList) + SendRPC(bl.Key); + } + private void SendRPC(byte playerId) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + writer.WriteNetObject(_Player); + writer.Write(playerId); + if (playerId != byte.MaxValue) + { + writer.Write(BlastedOffList[playerId].Count); + foreach (var bl in BlastedOffList[playerId]) + writer.Write(bl); + } + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) + { + byte playerId = reader.ReadByte(); + if (playerId == byte.MaxValue) + { + BlastedOffList.Clear(); + } + else + { + int blastNum = reader.ReadInt32(); + BlastedOffList.Remove(playerId); + HashSet list = []; + for (int i = 0; i < blastNum; i++) + list.Add(reader.ReadByte()); + BlastedOffList.Add(playerId, list); + } + } + public override bool CanUseKillButton(PlayerControl pc) => true; + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = BatonPassCooldown.GetFloat(); + + public static bool CanBlast(PlayerControl pc, byte id) + { + if (!pc.Is(CustomRoles.MoonDancer) || GameStates.IsMeeting) return false; + + var target = GetPlayerById(id); + + var penguins = GetRoleBasesByType()?.ToList(); + if (penguins != null) + { + if (penguins.Any(pg => target.PlayerId == pg.AbductVictim?.PlayerId)) + { + return false; + } + } + + return target != null && target.CanBeTeleported() && !target.IsTransformedNeutralApocalypse() && !Medic.IsProtected(target.PlayerId) && !target.Is(CustomRoles.GM) && !IsBlasted(pc, id) && !IsBlasted(id); + } + private static bool IsBlasted(PlayerControl pc, byte id) => BlastedOffList.ContainsKey(pc.PlayerId) && BlastedOffList[pc.PlayerId].Contains(id); + public static bool IsBlasted(byte id) + { + foreach (var bl in BlastedOffList) + if (bl.Value.Contains(id)) + return true; + return false; + } + private void BlastPlayer(PlayerControl pc, PlayerControl target) + { + if (pc == null || target == null || !target.CanBeTeleported()) return; + if (Mini.Age < 18 && (target.Is(CustomRoles.NiceMini) || target.Is(CustomRoles.EvilMini))) + { + pc.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceMini), GetString("CantEat"))); + return; + } + + if (!BlastedOffList.ContainsKey(pc.PlayerId)) BlastedOffList.Add(pc.PlayerId, []); + BlastedOffList[pc.PlayerId].Add(target.PlayerId); + + SyncBlastList(); + + originalSpeed.Remove(target.PlayerId); + originalSpeed.Add(target.PlayerId, Main.AllPlayerSpeed[target.PlayerId]); + + target.RpcTeleport(Pelican.GetBlackRoomPSForPelican()); + Main.AllPlayerSpeed[target.PlayerId] = 0.5f; + ReportDeadBodyPatch.CanReport[target.PlayerId] = false; + target.MarkDirtySettings(); + + NotifyRoles(SpecifySeer: pc); + NotifyRoles(SpecifySeer: target); + + Logger.Info($"{pc.GetRealName()} Blasted Off {target.GetRealName()}", "MoonDancer"); + } + + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + if (HasNecronomicon(killer)) + { + var rd = IRandom.Instance; + if (target.IsPlayerCoven()) + { + killer.Notify(GetString("MoonDancerCantBlastOff")); + return false; + } + if (rd.Next(0, 101) < BlastOffChance.GetInt()) + { + if (CanBlast(killer, target.PlayerId)) + { + BlastPlayer(killer, target); + if (!DisableShieldAnimations.GetBool()) killer.RpcGuardAndKill(killer); + killer.SetKillCooldown(); + killer.RPCPlayCustomSound("BlastOff"); + target.RPCPlayCustomSound("BlastOff"); + } + else + { + killer.SetKillCooldown(); + killer.Notify(GetString("MoonDancerCantBlastOff")); + } + return false; + } + else + { + killer.Notify(GetString("MoonDancerNormalKill")); + return true; + } + } + if (target.IsPlayerCoven() || target.Is(CustomRoles.Enchanted)) + { + BatonPassList[killer.PlayerId].Add(target.PlayerId); + killer.Notify(GetString("MoonDancerGiveHelpfulAddon")); + } + else + { + BatonPassList[killer.PlayerId].Add(target.PlayerId); + killer.Notify(GetString("MoonDancerGiveHarmfulAddon")); + } + return false; + } + + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) + { + KillBlastedOff(); + var md = Utils.GetPlayerListByRole(CustomRoles.MoonDancer).First(); + DistributeAddOns(md); + } + private void DistributeAddOns(PlayerControl md) + { + if (addons.Count == 0) + { + md.Notify(ColorString(GetRoleColor(CustomRoles.MoonDancer), GetString("MoonDancerNoAddons"))); + Logger.Info("No addons to pass.", "MoonDancer"); + return; + } + var rd = IRandom.Instance; + foreach (var pc in BatonPassList[md.PlayerId]) + { + var player = GetPlayerById(pc); + var addon = addons.RandomElement(); + if (player.IsPlayerCoven() || player.Is(CustomRoles.Enchanted)) { + addon = GroupedAddons[AddonTypes.Helpful].Where(x => addons.Contains(x)).ToList().RandomElement(); + } + else + { + addon = GroupedAddons[AddonTypes.Harmful].Where(x => addons.Contains(x)).ToList().RandomElement(); + + } + player.RpcSetCustomRole(addon); + player.AddInSwitchAddons(player, addon); + } + BatonPassList[md.PlayerId].Clear(); + } + private void KillBlastedOff() + { + foreach (var pc in BlastedOffList) + { + foreach (var tar in pc.Value) + { + var target = GetPlayerById(tar); + var killer = GetPlayerById(pc.Key); + if (killer == null || target == null) continue; + Main.AllPlayerSpeed[tar] = Main.AllPlayerSpeed[tar] - 0.5f + originalSpeed[tar]; + ReportDeadBodyPatch.CanReport[tar] = true; + target.RpcExileV2(); + target.SetRealKiller(killer); + tar.SetDeathReason(PlayerState.DeathReason.BlastedOff); + Main.PlayerStates[tar].SetDead(); + MurderPlayerPatch.AfterPlayerDeathTasks(killer, target, true); + Logger.Info($"{killer.GetRealName()} Blasted Off {target.GetRealName()}", "MoonDancer"); + } + } + BlastedOffList.Clear(); + SyncBlastList(); + } + public override void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl moonDancer, bool inMeeting, bool isSuicide) + { + if (inMeeting) return; + + foreach (var bl in BlastedOffList[moonDancer.PlayerId]) + { + var pc = GetPlayerById(bl); + pc.SetRealKiller(moonDancer); + pc.RpcExileV2(); + pc.SetDeathReason(PlayerState.DeathReason.BlastedOff); + } + } +} \ No newline at end of file From 76f8c95d7f17399714726a7ef48c89c83d6581fb Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 27 Oct 2024 14:18:00 -0400 Subject: [PATCH 013/101] yada yada --- Modules/CustomRolesHelper.cs | 22 +- Modules/ExtendedPlayerControl.cs | 16 +- Modules/GuessManager.cs | 8 + Modules/NameColorManager.cs | 9 +- Modules/OptionHolder.cs | 34 +++- Patches/CheckGameEndPatch.cs | 8 +- Resources/Lang/en_US.json | 42 +++- Roles/AddOns/Common/Cyber.cs | 3 + Roles/AddOns/Common/DoubleShot.cs | 3 + Roles/AddOns/Common/Fragile.cs | 2 + Roles/AddOns/Common/Guesser.cs | 2 + Roles/AddOns/Common/Loyal.cs | 3 + Roles/AddOns/Common/Oiiai.cs | 12 ++ Roles/AddOns/Common/Paranoia.cs | 2 + Roles/Core/CustomRoleManager.cs | 2 + Roles/Coven/CovenManager.cs | 9 +- Roles/Coven/Illusionist.cs | 2 +- Roles/Coven/MoonDancer.cs | 27 ++- Roles/Coven/Necromancer.cs | 1 + Roles/Coven/Ritualist.cs | 6 + Roles/Coven/Sacrifist.cs | 327 ++++++++++++++++++++++++++++++ 21 files changed, 493 insertions(+), 47 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 1447d0229..30e642518 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -391,7 +391,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c // Only add-ons if (!role.IsAdditionRole() || pc == null) return false; - if (Options.AddonCanBeSettings.TryGetValue(role, out var o) && ((!o.Imp.GetBool() && pc.GetCustomRole().IsImpostor()) || (!o.Neutral.GetBool() && pc.GetCustomRole().IsNeutral()) || (!o.Crew.GetBool() && pc.GetCustomRole().IsCrewmate()))) + if (Options.AddonCanBeSettings.TryGetValue(role, out var o) && ((!o.Imp.GetBool() && pc.GetCustomRole().IsImpostor()) || (!o.Neutral.GetBool() && pc.GetCustomRole().IsNeutral()) || (!o.Crew.GetBool() && pc.GetCustomRole().IsCrewmate()) || (!o.Coven.GetBool() && pc.GetCustomRole().IsCoven()))) return false; // if player already has this addon @@ -447,7 +447,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c break; case CustomRoles.Guesser: - if (Options.GuesserMode.GetBool() && ((pc.GetCustomRole().IsCrewmate() && !Guesser.CrewCanBeGuesser.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Guesser.NeutralCanBeGuesser.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Guesser.ImpCanBeGuesser.GetBool()))) + if (Options.GuesserMode.GetBool() && ((pc.GetCustomRole().IsCrewmate() && !Guesser.CrewCanBeGuesser.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Guesser.NeutralCanBeGuesser.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Guesser.ImpCanBeGuesser.GetBool())|| (pc.GetCustomRole().IsCoven() && !Guesser.CovenCanBeGuesser.GetBool()))) return false; if (pc.Is(CustomRoles.EvilGuesser) || pc.Is(CustomRoles.NiceGuesser) @@ -555,8 +555,10 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c return false; if (DoubleShot.NeutralCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.Doomsayer) && ((pc.GetCustomRole().IsNonNK() && !Options.PassiveNeutralsCanGuess.GetBool()) || (pc.GetCustomRole().IsNK() && !Options.NeutralKillersCanGuess.GetBool()))) return false; + if (DoubleShot.CovenCanBeDoubleShot.GetBool() && !pc.Is(CustomRoles.Guesser) && (pc.Is(Custom_Team.Coven) && !Options.CovenCanGuess.GetBool())) + return false; } - if ((pc.Is(Custom_Team.Impostor) && !DoubleShot.ImpCanBeDoubleShot.GetBool()) || (pc.Is(Custom_Team.Crewmate) && !DoubleShot.CrewCanBeDoubleShot.GetBool()) || (pc.Is(Custom_Team.Neutral) && !DoubleShot.NeutralCanBeDoubleShot.GetBool())) + if ((pc.Is(Custom_Team.Impostor) && !DoubleShot.ImpCanBeDoubleShot.GetBool()) || (pc.Is(Custom_Team.Crewmate) && !DoubleShot.CrewCanBeDoubleShot.GetBool()) || (pc.Is(Custom_Team.Neutral) && !DoubleShot.NeutralCanBeDoubleShot.GetBool()) || (pc.Is(Custom_Team.Coven) && !DoubleShot.CovenCanBeDoubleShot.GetBool())) return false; break; @@ -585,7 +587,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.LazyGuy) || pc.Is(CustomRoles.Mundane)) return false; - if (pc.GetCustomRole().IsNeutral() || pc.GetCustomRole().IsImpostor() || pc.GetCustomRole().IsTasklessCrewmate() || pc.GetCustomRole().IsTaskBasedCrewmate()) + if (pc.GetCustomRole().IsNeutral() || pc.GetCustomRole().IsImpostor() || pc.GetCustomRole().IsCoven() || pc.GetCustomRole().IsTasklessCrewmate() || pc.GetCustomRole().IsTaskBasedCrewmate()) return false; break; @@ -937,9 +939,9 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Madmate) || pc.Is(CustomRoles.GuardianAngelTOHE)) return false; - if (!pc.GetCustomRole().IsImpostor() && !pc.GetCustomRole().IsCrewmate()) + if (!pc.GetCustomRole().IsImpostor() && !pc.GetCustomRole().IsCrewmate() && !pc.GetCustomRole().IsCoven()) return false; - if ((pc.GetCustomRole().IsImpostor() && !Paranoia.CanBeImp.GetBool()) || (pc.GetCustomRole().IsCrewmate() && !Paranoia.CanBeCrew.GetBool())) + if ((pc.GetCustomRole().IsImpostor() && !Paranoia.CanBeImp.GetBool()) || (pc.GetCustomRole().IsCrewmate() && !Paranoia.CanBeCrew.GetBool()) || (pc.GetCustomRole().IsCoven() && !Paranoia.CanBeCov.GetBool())) return false; if (pc.GetCustomRole().IsNotKnightable() && Paranoia.DualVotes.GetBool()) return false; @@ -955,9 +957,9 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.EvilMini) || (pc.Is(CustomRoles.CopyCat) && CopyCat.CanCopyTeamChangingAddon())) return false; - if (!pc.GetCustomRole().IsImpostor() && !pc.GetCustomRole().IsCrewmate()) + if (!pc.GetCustomRole().IsImpostor() && !pc.GetCustomRole().IsCrewmate() && !pc.GetCustomRole().IsCoven()) return false; - if ((pc.GetCustomRole().IsImpostor() && !Loyal.ImpCanBeLoyal.GetBool()) || (pc.GetCustomRole().IsCrewmate() && !Loyal.CrewCanBeLoyal.GetBool())) + if ((pc.GetCustomRole().IsImpostor() && !Loyal.ImpCanBeLoyal.GetBool()) || (pc.GetCustomRole().IsCrewmate() && !Loyal.CrewCanBeLoyal.GetBool()) || (pc.GetCustomRole().IsCoven() && !Loyal.CovenCanBeLoyal.GetBool())) return false; break; @@ -982,7 +984,9 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c return false; if (pc.Is(CustomRoles.Ventguard) || pc.Is(CustomRoles.Circumvent) - || pc.Is(CustomRoles.Jester) && Jester.CantMoveInVents.GetBool()) + || pc.Is(CustomRoles.Jester) && Jester.CantMoveInVents.GetBool() + || pc.Is(CustomRoles.Medusa) // Medusa needs to be able to vent to use ability + ) return false; break; diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 0b1ebb1e8..9cb992c01 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1274,8 +1274,8 @@ public static bool KnowRoleTarget(PlayerControl seer, PlayerControl target) else if (Madmate.MadmateKnowWhosImp.GetBool() && seer.Is(CustomRoles.Madmate) && target.Is(Custom_Team.Impostor)) return true; else if (Madmate.ImpKnowWhosMadmate.GetBool() && target.Is(CustomRoles.Madmate) && seer.Is(Custom_Team.Impostor)) return true; else if (seer.Is(Custom_Team.Impostor) && target.GetCustomRole().IsGhostRole() && target.GetCustomRole().IsImpostor()) return true; - //else if (target.Is(CustomRoles.Enchanted) && seer.Is(Custom_Team.Coven)) return true; - //else if (seer.Is(CustomRoles.Enchanted) && target.Is(Custom_Team.Coven)) return true; + else if (Ritualist.EnchantedKnowsCoven.GetBool() && seer.Is(CustomRoles.Enchanted) && target.Is(Custom_Team.Coven)) return true; + else if (target.Is(CustomRoles.Enchanted) && seer.Is(Custom_Team.Coven)) return true; else if (target.Is(Custom_Team.Coven) && seer.Is(Custom_Team.Coven)) return true; else if (target.GetRoleClass().KnowRoleTarget(seer, target)) return true; else if (seer.GetRoleClass().KnowRoleTarget(seer, target)) return true; @@ -1301,6 +1301,7 @@ public static bool ShowSubRoleTarget(this PlayerControl seer, PlayerControl targ else if (seer.Is(CustomRoles.GM) || target.Is(CustomRoles.GM) || seer.Is(CustomRoles.God) || (seer.IsHost() && Main.GodMode.Value)) return true; else if (Main.VisibleTasksCount && !seer.IsAlive() && Options.GhostCanSeeOtherRoles.GetBool()) return true; else if (Options.ImpsCanSeeEachOthersAddOns.GetBool() && seer.Is(Custom_Team.Impostor) && target.Is(Custom_Team.Impostor) && !subRole.IsBetrayalAddon()) return true; + else if (Options.CovenCanSeeEachOthersAddOns.GetBool() && seer.Is(Custom_Team.Coven) && target.Is(Custom_Team.Coven) && !subRole.IsBetrayalAddon()) return true; else if (Options.ApocCanSeeEachOthersAddOns.GetBool() && seer.IsNeutralApocalypse() && target.IsNeutralApocalypse() && !subRole.IsBetrayalAddon()) return true; else if ((subRole is CustomRoles.Madmate @@ -1310,7 +1311,8 @@ or CustomRoles.Admired or CustomRoles.Charmed or CustomRoles.Infected or CustomRoles.Contagious - or CustomRoles.Egoist) + or CustomRoles.Egoist + or CustomRoles.Enchanted) && KnowSubRoleTarget(seer, target)) return true; @@ -1331,10 +1333,10 @@ public static bool KnowSubRoleTarget(PlayerControl seer, PlayerControl target) else if (seer.Is(CustomRoles.Egoist) && target.Is(CustomRoles.Egoist) && Egoist.ImpEgoistVisibalToAllies.GetBool()) return true; } - //if (seer.Is(Custom_Team.Coven)) - //{ - // if (target.Is(CustomRoles.Enchanted) || target.Is(Custom_Team.Coven)) return true; - //} + if (seer.Is(Custom_Team.Coven)) + { + if (target.Is(CustomRoles.Enchanted) && Ritualist.EnchantedKnowsCoven.GetBool()) return true; + } else if (Admirer.HasEnabled && Admirer.CheckKnowRoleTarget(seer, target)) return true; else if (Cultist.HasEnabled && Cultist.KnowRole(seer, target)) return true; else if (Infectious.HasEnabled && Infectious.KnowRole(seer, target)) return true; diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index ddb435492..e6b5a0292 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -304,6 +304,14 @@ public static bool GuesserMsg(PlayerControl pc, string msg, bool isUI = false) return true; } } + if (role.IsCoven() && !Options.CovenCanGuessCoven.GetBool()) + { + if (Options.CovenCanGuess.GetBool() && (pc.Is(Custom_Team.Coven) || pc.Is(CustomRoles.Enchanted)) && !pc.Is(CustomRoles.Guesser)) + { + pc.ShowInfoMessage(isUI, GetString("GuessCovenRole")); + return true; + } + } if (role.IsCrewmate() && !Options.CrewCanGuessCrew.GetBool()) { if (Options.CrewmatesCanGuess.GetBool() && pc.Is(Custom_Team.Crewmate) && !(pc.Is(CustomRoles.NiceGuesser) || pc.Is(CustomRoles.Guesser))) diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index 7b34a7e8b..43fb8c751 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -2,6 +2,7 @@ using TOHE.Roles.AddOns.Common; using TOHE.Roles.AddOns.Impostor; using TOHE.Roles.Core; +using TOHE.Roles.Coven; using TOHE.Roles.Crewmate; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; @@ -49,9 +50,11 @@ private static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target if (seer.Is(Custom_Team.Impostor) && target.GetCustomRole().IsGhostRole() && target.GetCustomRole().IsImpostor()) color = Main.roleColors[CustomRoles.Madmate]; if (seer.Is(CustomRoles.Madmate) && target.Is(CustomRoles.Madmate) && Madmate.MadmateKnowWhosMadmate.GetBool()) color = Main.roleColors[CustomRoles.Madmate]; - /* Coven - if ((seer.Is(Custom_Team.Coven) || seer.Is(CustomRoles.Enchanted)) && (target.Is(Custom_Team.Coven) || target.Is(CustomRoles.Enchanted))) color = Main.roleColors[CustomRoles.Coven]; - */ + // Coven + if (seer.Is(Custom_Team.Coven) && target.Is(Custom_Team.Coven)) color = Main.roleColors[CustomRoles.Coven]; + if (seer.Is(CustomRoles.Enchanted) && target.Is(Custom_Team.Coven) && Ritualist.EnchantedKnowsCoven.GetBool()) color = Main.roleColors[CustomRoles.Coven]; + if (seer.Is(Custom_Team.Coven) && target.Is(CustomRoles.Enchanted)) color = Main.roleColors[CustomRoles.Enchanted]; + if (seer.Is(CustomRoles.Enchanted) && target.Is(CustomRoles.Enchanted) && Ritualist.EnchantedKnowsEnchanted.GetBool()) color = Main.roleColors[CustomRoles.Enchanted]; // Cultist if (Cultist.NameRoleColor(seer, target)) color = Main.roleColors[CustomRoles.Cultist]; diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 87661cdc0..e85247350 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -71,7 +71,7 @@ public static CustomGameMode CurrentGameMode public static Dictionary CustomRoleSpawnChances; public static Dictionary CustomAdtRoleSpawnRate; - public static readonly Dictionary AddonCanBeSettings = []; + public static readonly Dictionary AddonCanBeSettings = []; public enum SpawnChance { Chance0, @@ -342,6 +342,7 @@ private enum RatesZeroOne public static OptionItem DisableDevicesIgnoreConditions; public static OptionItem DisableDevicesIgnoreImpostors; public static OptionItem DisableDevicesIgnoreNeutrals; + public static OptionItem DisableDevicesIgnoreCoven; public static OptionItem DisableDevicesIgnoreCrewmates; public static OptionItem DisableDevicesIgnoreAfterAnyoneDied; @@ -520,6 +521,7 @@ private enum RatesZeroOne // Coven public static OptionItem CovenRolesMinPlayer; public static OptionItem CovenRolesMaxPlayer; + public static OptionItem CovenCanSeeEachOthersAddOns; public static OptionItem CovenHasImpVis; public static OptionItem CovenImpVisMode; public static OptionItem CovenCanVent; @@ -537,6 +539,7 @@ private enum RatesZeroOne public static OptionItem ImpCanBeInLove; public static OptionItem CrewCanBeInLove; public static OptionItem NeutralCanBeInLove; + public static OptionItem CovenCanBeInLove; // Experimental Roles @@ -728,26 +731,28 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetParent(NeutralRoleWinTogether) .SetGameMode(CustomGameMode.Standard); - CovenRolesMinPlayer = IntegerOptionItem.Create(60025, "CovenRolesMinPlayer", new(0, 15, 1), 0, TabGroup.CovenRoles, false) + CovenRolesMinPlayer = IntegerOptionItem.Create(60026, "CovenRolesMinPlayer", new(0, 15, 1), 0, TabGroup.CovenRoles, false) .SetGameMode(CustomGameMode.Standard) .SetHeader(true) .SetValueFormat(OptionFormat.Players); - CovenRolesMaxPlayer = IntegerOptionItem.Create(60026, "CovenRolesMaxPlayer", new(0, 15, 1), 0, TabGroup.CovenRoles, false) + CovenRolesMaxPlayer = IntegerOptionItem.Create(60027, "CovenRolesMaxPlayer", new(0, 15, 1), 0, TabGroup.CovenRoles, false) .SetGameMode(CustomGameMode.Standard) .SetValueFormat(OptionFormat.Players); - CovenHasImpVis = BooleanOptionItem.Create(60027, "CovenHasImpVis", true, TabGroup.CovenRoles, false) + CovenHasImpVis = BooleanOptionItem.Create(60028, "CovenHasImpVis", true, TabGroup.CovenRoles, false) .SetGameMode(CustomGameMode.Standard) .SetHeader(true); - CovenImpVisMode = StringOptionItem.Create(60028, "CovenImpVisMode", EnumHelper.GetAllNames(), 0, TabGroup.CovenRoles, false) + CovenImpVisMode = StringOptionItem.Create(60029, "CovenImpVisMode", EnumHelper.GetAllNames(), 0, TabGroup.CovenRoles, false) .SetGameMode(CustomGameMode.Standard) .SetParent(CovenHasImpVis); CovenManager.RunSetUpImpVisOptions(160032); - CovenCanVent = BooleanOptionItem.Create(60029, "CovenCanVent", true, TabGroup.CovenRoles, false) + CovenCanVent = BooleanOptionItem.Create(60030, "CovenCanVent", true, TabGroup.CovenRoles, false) .SetGameMode(CustomGameMode.Standard); - CovenVentMode = StringOptionItem.Create(60030, "CovenVentMode", EnumHelper.GetAllNames(), 0, TabGroup.CovenRoles, false) + CovenVentMode = StringOptionItem.Create(60031, "CovenVentMode", EnumHelper.GetAllNames(), 0, TabGroup.CovenRoles, false) .SetGameMode(CustomGameMode.Standard) .SetParent(CovenCanVent); CovenManager.RunSetUpVentOptions(260032); + CovenCanSeeEachOthersAddOns = BooleanOptionItem.Create(60032, "CovenCanSeeEachOthersAddOns", true, TabGroup.CovenRoles, false) + .SetGameMode(CustomGameMode.Standard); NameDisplayAddons = BooleanOptionItem.Create(60019, "NameDisplayAddons", true, TabGroup.Addons, false) .SetGameMode(CustomGameMode.Standard) @@ -1579,6 +1584,8 @@ private static System.Collections.IEnumerator CoLoadOptions() DisableDevicesIgnoreNeutrals = BooleanOptionItem.Create(60591, "IgnoreNeutrals", false, TabGroup.ModSettings, false) .SetParent(DisableDevicesIgnoreConditions); //.SetGameMode(CustomGameMode.Standard); + DisableDevicesIgnoreCoven = BooleanOptionItem.Create(60694, "IgnoreCoven", false, TabGroup.ModSettings, false) + .SetParent(DisableDevicesIgnoreConditions); DisableDevicesIgnoreCrewmates = BooleanOptionItem.Create(60592, "IgnoreCrewmates", false, TabGroup.ModSettings, false) .SetParent(DisableDevicesIgnoreConditions); //.SetGameMode(CustomGameMode.Standard); @@ -1785,6 +1792,8 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetParent(GuesserMode); CovenCanGuess = BooleanOptionItem.Create(60693, "CovenCanGuess", false, TabGroup.ModifierSettings, false) .SetParent(GuesserMode); + CovenCanGuessCoven = BooleanOptionItem.Create(60692, "CovenCanGuessCoven", false, TabGroup.ModifierSettings, false) + .SetParent(CovenCanGuess); CanGuessAddons = BooleanOptionItem.Create(60685, "CanGuessAddons", true, TabGroup.ModifierSettings, false) .SetParent(GuesserMode); HideGuesserCommands = BooleanOptionItem.Create(60688, "GuesserTryHideMsg", true, TabGroup.ModifierSettings, false) @@ -1999,6 +2008,10 @@ private static void SetupLoversRoleOptionsToggle(int id, CustomGameMode customGa .SetParent(spawnOption) .SetGameMode(customGameMode); + CovenCanBeInLove = BooleanOptionItem.Create(id + 8, "CovenCanBeInLove", true, TabGroup.Addons, false) + .SetParent(spawnOption) + .SetGameMode(customGameMode); + var countOption = IntegerOptionItem.Create(id + 1, "NumberOfLovers", new(2, 2, 1), 2, TabGroup.Addons, false) .SetParent(spawnOption) @@ -2044,7 +2057,12 @@ public static void SetupAdtRoleOptions(int id, CustomRoles role, CustomGameMode .SetGameMode(customGameMode) .AddReplacement(("{role}", role.ToColoredString())); - AddonCanBeSettings.Add(role, (impOption, neutralOption, crewOption)); + var covenOption = BooleanOptionItem.Create(id + 6, "CovenCanBeRole", true, tab, false) + .SetParent(spawnOption) + .SetGameMode(customGameMode) + .AddReplacement(("{role}", role.ToColoredString())); + + AddonCanBeSettings.Add(role, (impOption, neutralOption, crewOption, covenOption)); } diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 19be828b9..3e3c1e318 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -115,8 +115,8 @@ public static bool Prefix() } break; case CustomWinner.Coven: - if (((pc.Is(Custom_Team.Coven) || pc.Is(CustomRoles.Enchanted)) && (countType == CountTypes.Coven || pc.Is(CustomRoles.Soulless))) - && !WinnerIds.Contains(pc.PlayerId)) + if (((pc.Is(Custom_Team.Coven) || pc.Is(CustomRoles.Enchanted)) && (countType == CountTypes.Coven || pc.Is(CustomRoles.Soulless))) + || pc.Is(CustomRoles.Enchanted) && !WinnerIds.Contains(pc.PlayerId)) { WinnerIds.Add(pc.PlayerId); } @@ -383,9 +383,9 @@ public static bool Prefix() WinnerIds.Add(pc.PlayerId); } } - if (Main.AllAlivePlayerControls.All(p => p.IsPlayerCoven())) + if (Main.AllAlivePlayerControls.All(p => p.IsPlayerCoven() || p.Is(CustomRoles.Enchanted))) { - foreach (var pc in Main.AllPlayerControls.Where(x => x.IsPlayerCoven())) + foreach (var pc in Main.AllPlayerControls.Where(x => x.IsPlayerCoven() || x.Is(CustomRoles.Enchanted))) { if (!WinnerIds.Contains(pc.PlayerId)) WinnerIds.Add(pc.PlayerId); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index ab75b6232..f68738ebd 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -968,7 +968,7 @@ "DreamweaverInfoLong": "(Coven):\nThe Dreamweaver can Dreamweave a player. The dreamweaved players will be notified of this during the next meeting. If the Dreamweaver is not voted out, these players will be unable to use their abilities until the Dreamweaver dies.\nWith the Necronomicon, the Dreamweaver can double-click to kill.", "IllusionistInfoLong": "(Coven):\nThe Illusionist can use their kill button on a player to reverse the results of any investigative role. For example, if someone with a kill button is Illusioned, they will appear not to have a kill button to the Investigator, and vice versa.\nIllusions wear off after meetings.\nWith the Necronomicon, you may double-click to kill. Every kill you make appears as a random death reason.", "VoodooMasterInfoLong": "(Coven):\nThe Voodoo Master can craft a voodoo doll of a player by using their kill button, similar to the Shaman. All the interactions with you using kill button will be deflected to the voodoo doll and the voodoo doll will destroy. Unlike the Shaman, this voodoo will last during the meeting (eg. If the Voodoo Master is judged, then the voodoo'd player will be judged instead).\nWith the Necronomicon, you can double-click to kill. Additionally, the voodoo’d player will be unable to report. The next person to interact with them will die.", - "SacrifistInfoLong": "(Coven):\nThe Sacrifist can vent to cause a random debuff to a non-coven member, however, the Sacrifist will also receive this effect.\nDepending on the host’s settings, if the Sacrifist is voted out, some random non-coven who voted the Sacrifist will die too.\nWith the Necronomicon, when you vent, you commit suicide. However, the entire Coven for the rest of the game receives a lowered kill cooldown.", + "SacrifistInfoLong": "(Coven):\nThe Sacrifist can vent to cause a random debuff to a non-coven member, however, the Sacrifist will also receive this effect (when applicable).\nDepending on the host’s settings, if the Sacrifist is voted out, some random non-coven who voted the Sacrifist will die too.\nWith the Necronomicon, when you vent, you will use the Ultimate Sacrifice. When you do this, you die. However, the entire Coven for the rest of the game receives a lowered kill cooldown.", "MoonDancerInfoLong": "(Coven):\nThe Moon Dancer can use their kill button to use their ability, Baton Pass.\nIf used on a Coven member: Gives a helpful add-on at the next meeting.\nIf used on a non-Coven member: Gives a harmful add-on at the next meeting.\nWith the Necronomicon, the Moon Dancer can double-click their kill button to kill. When killing, the player is teleported off the map. They will appear alive on vitals and will not show up in tracefinder's arrows etc. They die when a meeting/body report with the death reason Blasted Off.", "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.", "LastImpostorInfoLong": "(Add-ons):\nThis special effect is given to the last surviving Impostor. It significantly reduces their kill cooldown.", @@ -1310,6 +1310,7 @@ "IgnoreConditions": "Ignore Conditions", "IgnoreImpostors": "Ignore Impostors", "IgnoreNeutrals": "Ignore Neutrals", + "IgnoreCoven": "Ignore Coven", "IgnoreCrewmates": "Ignore Crewmates", "IgnoreAfterAnyoneDied": "Ignore After First Death", "LightsOutSpecialSettings": "Fix Lights Special Settings", @@ -1702,6 +1703,7 @@ "NemesisCanKillNum": "Max number of revenges", "ImpKnowCelebrityDead": "Impostors know when the Celebrity dies", "NeutralKnowCelebrityDead": "Neutrals know when the Celebrity dies", + "CovenKnowCelebrityDead": "Coven know when the Celebrity dies", "VectorVentNumWin": "Number of Vents to win", "CanCheckCamera": "Can track camera usage", "DefaultKillCooldown": "Starting kill cooldown", @@ -1723,6 +1725,7 @@ "InnocentCanWinByImp": "If their target was an Impostor then they win with them", "ImpCanBeParanoia": "Impostors can become Paranoia", "CrewCanBeParanoia": "Crewmates can become Paranoia", + "CovenCanBeParanoia": "Coven can become Paranoia", "DualVotes": "Duplicate votes", "VeteranSkillCooldown": "Alert Cooldown", "VeteranSkillDuration": "Alert Duration", @@ -1952,6 +1955,7 @@ "CovenCanVent": "Coven Members Can Vent", "CovenVentMode": "Vent Configuration", "CovenPerRole": "Per Role", + "CovenCanSeeEachOthersAddOns": "Coven can see each other's Add-Ons", "NecronomiconNotification": "You have recieved the Necronomicon!
Your powers are now enhanced!", "CovenLeaderMaxRetrains": "Max Retrains", @@ -1971,6 +1975,8 @@ "RitualistRitualFail": "You have failed your blood ritual and may no longer preform any blood rituals this meeting...", "RitualistRitualImpossible": "Your Blood Ritual was successful, however, this player is unenchantable.", "RitualistRitualMax": "You've already used the max amount of Blood Rituals for this meeting.", + "RitualistEnchantedKnowsCoven": "Enchanted knows Coven", + "RitualistEnchantedKnowsEnchanted": "Enchanted knows Enchanted", "RitualistCommandHelp": "Instructions: /rt [Player ID] [Role Name] \nExample: /rt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", "RitualistConvertNotif": "Your role has been guessed by the {0} and you are now apart of the Coven!", @@ -2000,13 +2006,37 @@ "MoonDancerNormalKill": "Killed normally", "MoonDancerGiveHelpfulAddon": "Target will be given Helpful Add-on during meeting", "MoonDancerGiveHarmfulAddon": "Target will be given Harmful Add-on during meeting", - "MoonDancerNoAddons": "No Add-ons to Baton Pass", + "MoonDancerNoAddons": "No Add-ons to Baton Pass to {0}", + + "SacrifistDebuffCooldown": "Debuff Cooldown", + "SacrifistDeathsAfterVote": "Players Killed After Sacrifist is Exiled", + "SacrifistNecroReducedCooldown": "Coven Cooldown After Ultimate Sacrifices", + "SacrifistVision": "Sacrificed Vision", + "SacrifistSpeed": "Sacrificed Speed", + "SacrifistIncreasedCooldown": "Sacrificed Increase Cooldown", + "SacrifistSpeedDebuff": "Speed Debuffed", + "SacrifistVisionDebuff": "Vision Debuffed", + "SacrifistCooldownDebuff": "Cooldown Debuffed", + "SacrifistFoolDebuff": "Can't Fix Sabotages Debuff", + "SacrifistMeetingDebuff": "Forced Called Meeting", + "SacrifistReportDebuff": "Can't Report Bodies", + "SacrifistTasksDebuff": "Tasks Reset for Target", + "SacrifistInvertDebuff": "Inverted Speed", + "SacrifistDoxxDebuff": "Location Exposed", + "SacrifistSwapDebuff": "Swapped with Target", + + + + + + "LuckyProbability": "Probability of surviving a kill", "ImpCanBeDoubleShot": "Impostors can have Double Shot", "CrewCanBeDoubleShot": "Crewmates can have Double Shot", "NeutralCanBeDoubleShot": "Neutrals can have Double Shot", + "CovenCanBeDoubleShot": "Coven can have Double Shot", "MimicCanSeeDeadRoles": "Mimic can see the roles of dead players", "DisableReportWhenCamouflageIsActive": "Disable body reporting when camouflage is active", "CanUseCommsSabotage": "Can use comms sabotage", @@ -2213,6 +2243,7 @@ "GuessImpRole": "Unfortunately, the Host's settings do not allow Impostors to guess Impostor roles.", "GuessCrewRole": "Unfortunately, the Host's settings do not allow crewmates to guess crewmate roles.", "GuessApocRole": "Fortunately, the Host's settings does not allow Apocalypse to guess Apocalypse roles.", + "GuessCovenRole": "Fortunately, the Host's settings does not allow Coven to guess Coven roles.", "GuessKill": "{0} was guessed", "GuessNull": "Please select an ID of a living player to guess their role", "GuessHelp": "Instructions: /bt [Player ID] [Role Name] \nExample: /bt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", @@ -2524,6 +2555,7 @@ "ImpCanBeGuesser": "Impostors can become Guesser", "CrewCanBeGuesser": "Crewmates can become Guesser", "NeutralCanBeGuesser": "Neutrals can become Guesser", + "CovenCanBeGuesser": "Coven can become Guesser", "CrewCanBeMundane": "Crewmates can become Mundane", "NeutralCanBeMundane": "Neutrals can become Mundane", "GuessedAsMundane": "You're Mundane.\nYou can't guess until you finish all the tasks", @@ -2531,6 +2563,7 @@ "ImpCanBeInLove": "Impostors can be in love", "CrewCanBeInLove": "Crewmates can be in love", "NeutralCanBeInLove": "Neutrals can be in love", + "CovenCanBeInLove": "Coven can be in love", "updateButton": "Update", "updatePleaseWait": "Please Wait...", "updateManually": "Update failed.\nPlease try again or Update Manually.", @@ -3003,6 +3036,7 @@ "ImpCanKillFragile": "Impostors can force kill Fragile", "NeutralCanKillFragile": "Neutrals can force kill Fragile", "CrewCanKillFragile": "Crewmates can force kill Fragile", + "CovenCanKillFragile": "Coven can force kill Fragile", "FragileKillerLunge": "Killer lunges on kill", "CrusaderSkillLimit": "Maximum Crusades", "CrusaderSkillCooldown": "Crusade Cooldown", @@ -3171,16 +3205,19 @@ "ImpKnowCyberDead": "Impostors know if Cyber died", "CrewKnowCyberDead": "Crewmates know if Cyber died", "NeutralKnowCyberDead": "Neutrals know if Cyber died", + "CovenKnowCyberDead": "Coven know if Cyber died", "CyberKnown": "Everyone can see Cyber", "KillerGetBewilderVision": "Killer gets Bewilder's vision", "ImpCanBeOiiai": "Impostors can be OIIAI", "CrewCanBeOiiai": "Crewmates can be OIIAI", "NeutralCanBeOiiai": "Neutrals can be OIIAI", + "CovenCanBeOiiai": "Coven can be OIIAI", "OiiaiCanPassOn": "OIIAI can pass on to the killer", "NeutralChangeRolesForOiiai": "Neutrals turns to ", "LostRoleByOiiai": "You got erased by OIIAI!", "ImpCanBeLoyal": "Impostors can become Loyal", "CrewCanBeLoyal": "Crewmates can become Loyal", + "CovenCanBeLoyal": "Coven can become Loyal", "TasklessCrewCanBeLazy": "Crewmates without tasks can be Lazy", "TaskBasedCrewCanBeLazy": "Task based crewmates can be Lazy", "SheriffCanBeMadmate": "Sheriff can become Madmate", @@ -3419,6 +3456,7 @@ "ImpCanBeRole": "Impostors can become {role}", "CrewCanBeRole": "Crewmates can become {role}", "NeutralCanBeRole": "Neutrals can become {role}", + "CovenCanBeRole": "Coven can become {role}", "VotesPerKill": "Votes gained for each kill", "PickpocketGetVote": "You've got {0} votes", diff --git a/Roles/AddOns/Common/Cyber.cs b/Roles/AddOns/Common/Cyber.cs index 4afeda11a..f9b9ec9c9 100644 --- a/Roles/AddOns/Common/Cyber.cs +++ b/Roles/AddOns/Common/Cyber.cs @@ -12,6 +12,7 @@ public class Cyber : IAddon public static OptionItem ImpKnowCyberDead; public static OptionItem CrewKnowCyberDead; public static OptionItem NeutralKnowCyberDead; + public static OptionItem CovenKnowCyberDead; public static OptionItem CyberKnown; public void SetupCustomOption() @@ -20,6 +21,7 @@ public void SetupCustomOption() ImpKnowCyberDead = BooleanOptionItem.Create(Id + 13, "ImpKnowCyberDead", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cyber]); CrewKnowCyberDead = BooleanOptionItem.Create(Id + 14, "CrewKnowCyberDead", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cyber]); NeutralKnowCyberDead = BooleanOptionItem.Create(Id + 15, "NeutralKnowCyberDead", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cyber]); + CovenKnowCyberDead = BooleanOptionItem.Create(Id + 17, "CovenKnowCyberDead", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cyber]); CyberKnown = BooleanOptionItem.Create(Id + 16, "CyberKnown", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cyber]); } @@ -46,6 +48,7 @@ public static void AfterCyberDeadTask(PlayerControl target, bool inMeeting) if (!ImpKnowCyberDead.GetBool() && pc.GetCustomRole().IsImpostor()) continue; if (!NeutralKnowCyberDead.GetBool() && pc.GetCustomRole().IsNeutral()) continue; if (!CrewKnowCyberDead.GetBool() && pc.GetCustomRole().IsCrewmate()) continue; + if (!CovenKnowCyberDead.GetBool() && pc.GetCustomRole().IsCoven()) continue; if (inMeeting) { diff --git a/Roles/AddOns/Common/DoubleShot.cs b/Roles/AddOns/Common/DoubleShot.cs index de2155975..d06398842 100644 --- a/Roles/AddOns/Common/DoubleShot.cs +++ b/Roles/AddOns/Common/DoubleShot.cs @@ -11,6 +11,7 @@ public class DoubleShot : IAddon public static OptionItem ImpCanBeDoubleShot; public static OptionItem CrewCanBeDoubleShot; public static OptionItem NeutralCanBeDoubleShot; + public static OptionItem CovenCanBeDoubleShot; public void SetupCustomOption() { @@ -21,6 +22,8 @@ public void SetupCustomOption() .SetParent(CustomRoleSpawnChances[CustomRoles.DoubleShot]); NeutralCanBeDoubleShot = BooleanOptionItem.Create(19212, "NeutralCanBeDoubleShot", true, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.DoubleShot]); + CovenCanBeDoubleShot = BooleanOptionItem.Create(19213, "CovenCanBeDoubleShot", true, TabGroup.Addons, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.DoubleShot]); } public void Init() { diff --git a/Roles/AddOns/Common/Fragile.cs b/Roles/AddOns/Common/Fragile.cs index b667681db..9aa90b92a 100644 --- a/Roles/AddOns/Common/Fragile.cs +++ b/Roles/AddOns/Common/Fragile.cs @@ -10,6 +10,7 @@ public class Fragile : IAddon private static OptionItem ImpCanKillFragile; private static OptionItem CrewCanKillFragile; private static OptionItem NeutralCanKillFragile; + private static OptionItem CovenCanKillFragile; private static OptionItem FragileKillerLunge; public void SetupCustomOption() @@ -18,6 +19,7 @@ public void SetupCustomOption() ImpCanKillFragile = BooleanOptionItem.Create(Id + 13, "ImpCanKillFragile", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fragile]); CrewCanKillFragile = BooleanOptionItem.Create(Id + 14, "CrewCanKillFragile", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fragile]); NeutralCanKillFragile = BooleanOptionItem.Create(Id + 15, "NeutralCanKillFragile", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fragile]); + CovenCanKillFragile = BooleanOptionItem.Create(Id + 17, "CovenCanKillFragile", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fragile]); FragileKillerLunge = BooleanOptionItem.Create(Id + 16, "FragileKillerLunge", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Fragile]); } public void Init() diff --git a/Roles/AddOns/Common/Guesser.cs b/Roles/AddOns/Common/Guesser.cs index 1085fba42..1a140f0da 100644 --- a/Roles/AddOns/Common/Guesser.cs +++ b/Roles/AddOns/Common/Guesser.cs @@ -11,6 +11,7 @@ public class Guesser : IAddon public static OptionItem ImpCanBeGuesser; public static OptionItem CrewCanBeGuesser; public static OptionItem NeutralCanBeGuesser; + public static OptionItem CovenCanBeGuesser; public static OptionItem GCanGuessAdt; public static OptionItem GCanGuessTaskDoneSnitch; public static OptionItem GTryHideMsg; @@ -21,6 +22,7 @@ public void SetupCustomOption() ImpCanBeGuesser = BooleanOptionItem.Create(Id + 10, "ImpCanBeGuesser", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); CrewCanBeGuesser = BooleanOptionItem.Create(Id + 11, "CrewCanBeGuesser", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); NeutralCanBeGuesser = BooleanOptionItem.Create(Id + 12, "NeutralCanBeGuesser", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); + CovenCanBeGuesser = BooleanOptionItem.Create(Id + 16, "CovenCanBeGuesser", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); GCanGuessAdt = BooleanOptionItem.Create(Id+ 13, "GCanGuessAdt", false, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); GCanGuessTaskDoneSnitch = BooleanOptionItem.Create(Id + 14, "GCanGuessTaskDoneSnitch", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); GTryHideMsg = BooleanOptionItem.Create(Id + 15, "GuesserTryHideMsg", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]) diff --git a/Roles/AddOns/Common/Loyal.cs b/Roles/AddOns/Common/Loyal.cs index c01c8460c..ef9efbb2e 100644 --- a/Roles/AddOns/Common/Loyal.cs +++ b/Roles/AddOns/Common/Loyal.cs @@ -9,6 +9,7 @@ public class Loyal : IAddon public static OptionItem ImpCanBeLoyal; public static OptionItem CrewCanBeLoyal; + public static OptionItem CovenCanBeLoyal; public void SetupCustomOption() { SetupAdtRoleOptions(Id, CustomRoles.Loyal, canSetNum: true); @@ -16,6 +17,8 @@ public void SetupCustomOption() .SetParent(CustomRoleSpawnChances[CustomRoles.Loyal]); CrewCanBeLoyal = BooleanOptionItem.Create(Id + 11, "CrewCanBeLoyal", true, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Loyal]); + CovenCanBeLoyal = BooleanOptionItem.Create(Id + 12, "CovenCanBeLoyal", true, TabGroup.Addons, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Loyal]); } public void Init() { } diff --git a/Roles/AddOns/Common/Oiiai.cs b/Roles/AddOns/Common/Oiiai.cs index 1b73db716..b2bb0d345 100644 --- a/Roles/AddOns/Common/Oiiai.cs +++ b/Roles/AddOns/Common/Oiiai.cs @@ -91,6 +91,18 @@ public static void OnMurderPlayer(PlayerControl killer, PlayerControl target) Logger.Info($"Oiiai {killer.GetNameWithRole().RemoveHtmlTags()} cannot eraser crew imp-based role", "Oiiai"); return; } + else if (killer.GetCustomRole().IsCoven() || !CovenManager.HasNecronomicon(killer)) + { + killer.RpcSetCustomRole(CustomRoles.Amnesiac); + killer.RpcSetCustomRole(CustomRoles.Enchanted); + killer.AddInSwitchAddons(killer, CustomRoles.Enchanted); + Logger.Info($"Oiiai {killer.GetNameWithRole().RemoveHtmlTags()} with Coven without Necronomicon.", "Oiiai"); + } + else if (CovenManager.HasNecronomicon(killer)) + { + // Necronomicon holder immune to Oiiai + Logger.Info($"Oiiai {killer.GetNameWithRole().RemoveHtmlTags()} with Coven with Necronomicon.", "Oiiai"); + } else if (!killer.GetCustomRole().IsNeutral()) { //Use eraser here LOL diff --git a/Roles/AddOns/Common/Paranoia.cs b/Roles/AddOns/Common/Paranoia.cs index 7a5fb07a3..d57b68f38 100644 --- a/Roles/AddOns/Common/Paranoia.cs +++ b/Roles/AddOns/Common/Paranoia.cs @@ -8,6 +8,7 @@ public class Paranoia : IAddon public static OptionItem CanBeImp; public static OptionItem CanBeCrew; + public static OptionItem CanBeCov; public static OptionItem DualVotes; private static OptionItem HideAdditionalVotes; public AddonTypes Type => AddonTypes.Mixed; @@ -17,6 +18,7 @@ public void SetupCustomOption() SetupAdtRoleOptions(Id, CustomRoles.Paranoia, canSetNum: true); CanBeImp = BooleanOptionItem.Create(Id + 10, "ImpCanBeParanoia", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Paranoia]); CanBeCrew = BooleanOptionItem.Create(Id + 11, "CrewCanBeParanoia", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Paranoia]); + CanBeCov = BooleanOptionItem.Create(Id + 14, "CovenCanBeParanoia", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Paranoia]); DualVotes = BooleanOptionItem.Create(Id + 12, "DualVotes", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Paranoia]); HideAdditionalVotes = BooleanOptionItem.Create(Id + 13, "HideAdditionalVotes", false, TabGroup.Addons, false).SetParent(DualVotes); } diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 58aebb6f6..2eeae0312 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -112,6 +112,8 @@ public static void BuildCustomGameOptions(this PlayerControl player, ref IGameOp if (Spiritcaller.HasEnabled) Spiritcaller.ReduceVision(opt, player); if (Pitfall.HasEnabled) Pitfall.SetPitfallTrapVision(opt, player); if (Medusa.HasEnabled) Medusa.SetStoned(player, opt); + if (Sacrifist.HasEnabled) Sacrifist.SetVision(player, opt); + var playerSubRoles = player.GetCustomSubRoles(); diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 85cf51e1e..e852bfa43 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -33,7 +33,7 @@ public static void RunSetUpImpVisOptions(int Id) } public static void RunSetUpVentOptions(int Id) { - foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven() && (x is not CustomRoles.Medusa or CustomRoles.PotionMaster /* or CustomRoles.Sacrifist */)).ToArray()) + foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven() && (x is not CustomRoles.Medusa or CustomRoles.PotionMaster or CustomRoles.Sacrifist)).ToArray()) { SetUpVentOption(cov, Id, true, CovenVentMode); Id++; @@ -53,11 +53,13 @@ private static void SetUpVentOption(CustomRoles role, int Id, bool defaultValue CovenVentOptions[role] = BooleanOptionItem.Create(Id, "%role%CanVent", defaultValue, TabGroup.CovenRoles, false).SetParent(parent); CovenVentOptions[role].ReplacementDictionary = replacementDic; } + /* public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => HasNecronomicon(seen) ? ColorString(GetRoleColor(CustomRoles.CovenLeader), "♣") : string.Empty; + */ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if ((seer != target) && HasNecronomicon(target) && seer.IsPlayerCoven()) + if (HasNecronomicon(target) && seer.IsPlayerCoven()) { return ColorString(GetRoleColor(CustomRoles.CovenLeader), "♣"); } @@ -99,9 +101,10 @@ public override bool CanUseImpostorVentButton(PlayerControl pc) return option.GetBool(); } } - + /* public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); + */ public static void GiveNecronomicon() { diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index 7afa4ba08..39762f10c 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -67,7 +67,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t { if (killer.CheckDoubleTrigger(target, () => { IllusionedPlayers[killer.PlayerId].Add(target.PlayerId); })) { - if (HasNecronomicon(killer)) { + if (HasNecronomicon(killer) && !target.IsPlayerCoven()) { var randomDeathReason = ChangeRandomDeath(); Main.PlayerStates[target.PlayerId].deathReason = randomDeathReason; Main.PlayerStates[target.PlayerId].SetDead(); diff --git a/Roles/Coven/MoonDancer.cs b/Roles/Coven/MoonDancer.cs index 436d127b1..e3b815ea9 100644 --- a/Roles/Coven/MoonDancer.cs +++ b/Roles/Coven/MoonDancer.cs @@ -207,27 +207,34 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf } private void DistributeAddOns(PlayerControl md) { - if (addons.Count == 0) - { - md.Notify(ColorString(GetRoleColor(CustomRoles.MoonDancer), GetString("MoonDancerNoAddons"))); - Logger.Info("No addons to pass.", "MoonDancer"); - return; - } var rd = IRandom.Instance; foreach (var pc in BatonPassList[md.PlayerId]) { var player = GetPlayerById(pc); var addon = addons.RandomElement(); - if (player.IsPlayerCoven() || player.Is(CustomRoles.Enchanted)) { - addon = GroupedAddons[AddonTypes.Helpful].Where(x => addons.Contains(x)).ToList().RandomElement(); + var helpful = GroupedAddons[AddonTypes.Helpful].Where(x => addons.Contains(x)).ToList(); + var harmful = GroupedAddons[AddonTypes.Harmful].Where(x => addons.Contains(x)).ToList(); + if (player.IsPlayerCoven() || player.Is(CustomRoles.Enchanted) || (player.Is(CustomRoles.Lovers) && md.Is(CustomRoles.Lovers))) { + if (helpful.Count <= 0) { + SendMessage(string.Format(GetString("MoonDancerNoAddons"), player.GetRealName()), md.PlayerId); + Logger.Info("No addons to pass.", "MoonDancer"); + continue; + } + addon = helpful.RandomElement(); } else { - addon = GroupedAddons[AddonTypes.Harmful].Where(x => addons.Contains(x)).ToList().RandomElement(); - + if (harmful.Count <= 0) + { + SendMessage(string.Format(GetString("MoonDancerNoAddons"), player.GetRealName()), md.PlayerId); + Logger.Info("No addons to pass.", "MoonDancer"); + continue; + } + addon = harmful.RandomElement(); } player.RpcSetCustomRole(addon); player.AddInSwitchAddons(player, addon); + Logger.Info("Addon Passed.", "MoonDancer"); } BatonPassList[md.PlayerId].Clear(); } diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index 38d9b01f5..10c349a76 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -58,6 +58,7 @@ public override void Add(byte playerId) public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { if (IsRevenge) return true; + if (killer.IsPlayerCoven()) return false; if ((killer.Is(CustomRoles.Retributionist) || killer.Is(CustomRoles.Nemesis)) && !killer.IsAlive()) return true; _ = new LateTask(target.RpcRandomVentTeleport, 0.01f, "Random Vent Teleport - Necromancer"); diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index ea94bde0c..e41bc0804 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -25,6 +25,9 @@ internal class Ritualist : CovenManager private static OptionItem MaxRitsPerRound; public static OptionItem TryHideMsg; + public static OptionItem EnchantedKnowsCoven; + public static OptionItem EnchantedKnowsEnchanted; + private static readonly Dictionary RitualLimit = []; private static readonly Dictionary> EnchantedPlayers = []; @@ -36,6 +39,9 @@ public override void SetupCustomOption() .SetValueFormat(OptionFormat.Times); TryHideMsg = BooleanOptionItem.Create(Id + 11, "RitualistTryHideMsg", true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]) .SetColor(Color.green); + EnchantedKnowsCoven = BooleanOptionItem.Create(Id + 12, "RitualistEnchantedKnowsCoven", true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]); + EnchantedKnowsEnchanted = BooleanOptionItem.Create(Id + 13, "RitualistEnchantedKnowsEnchanted", true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]); + } public override void Init() { diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index e69de29bb..5d2fc99f4 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -0,0 +1,327 @@ +using TOHE.Roles.Core; +using static TOHE.Options; +using UnityEngine; +using static TOHE.Translator; +using static TOHE.Utils; +using TOHE.Modules; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using AmongUs.GameOptions; +using Hazel; +using MS.Internal.Xml.XPath; +using static UnityEngine.GraphicsBuffer; +using TOHE.Roles.Impostor; + +namespace TOHE.Roles.Coven; + +internal class Sacrifist : CovenManager +{ + //===========================SETUP================================\\ + private const int Id = 30600; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Sacrifist); + public override bool IsDesyncRole => true; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; + //==================================================================\\ + + private static OptionItem DebuffCooldown; + public static OptionItem DeathsAfterVote; + public static OptionItem NecroReducedCooldown; + private static OptionItem Vision; + private static OptionItem Speed; + private static OptionItem IncreasedCooldown; + + private static byte DebuffID = 10; + private static float debuffTimer; + private static float maxDebuffTimer; + private static byte randPlayer; + private static readonly Dictionary originalSpeed = []; + private static readonly Dictionary OriginalPlayerSkins = []; + + + + public override void SetupCustomOption() + { + SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Sacrifist, 1, zeroOne: false); + DebuffCooldown = FloatOptionItem.Create(Id + 10, "SacrifistDebuffCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) + .SetValueFormat(OptionFormat.Seconds); + Vision = FloatOptionItem.Create(Id + 13, "SacrifistVision", new(0f, 5f, 0.25f), 0.5f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) + .SetValueFormat(OptionFormat.Multiplier); + Speed = FloatOptionItem.Create(Id + 14, "SacrifistSpeed", new(0f, 5f, 0.25f), 0.5f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) + .SetValueFormat(OptionFormat.Multiplier); + IncreasedCooldown = FloatOptionItem.Create(Id + 15, "SacrifistIncreasedCooldown", new(0f, 100f, 2.5f), 50f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) + .SetValueFormat(OptionFormat.Percent); + DeathsAfterVote = IntegerOptionItem.Create(Id + 11, "SacrifistDeathsAfterVote", new(0, 15, 1), 0, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) + .SetValueFormat(OptionFormat.Players); + NecroReducedCooldown = FloatOptionItem.Create(Id + 12, "SacrifistNecroReducedCooldown", new(0f, 100f, 2.5f), 50f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) + .SetValueFormat(OptionFormat.Percent); + } + + public override void Init() + { + DebuffID = 10; + randPlayer = byte.MaxValue; + originalSpeed.Clear(); + OriginalPlayerSkins.Clear(); + } + public override void Add(byte playerId) + { + debuffTimer = 0; + maxDebuffTimer = DebuffCooldown.GetFloat(); + } + public void SendRPC(PlayerControl pc) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, pc.GetClientId()); + writer.Write(DebuffID); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) + { + DebuffID = reader.ReadByte(); + } + public override bool CanUseImpostorVentButton(PlayerControl pc) => true; + + // Sacrifist shouldn't be able to kill at all but if there's solo Sacrifist the game is unwinnable so they can kill when solo + public override bool CanUseKillButton(PlayerControl pc) => Main.AllAlivePlayerControls.Where(pc => pc.Is(Custom_Team.Coven)).Count() == 1; + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + var rand = IRandom.Instance; + DebuffID = (byte)rand.Next(0, 10); + if (randPlayer == byte.MaxValue) + { + randPlayer = Main.AllAlivePlayerControls.Where(x => !x.Is(Custom_Team.Coven) || !x.Is(CustomRoles.Enchanted)).ToList().RandomElement().PlayerId; + } + var randPlayerPC = GetPlayerById(randPlayer); + var sacrifist = pc.PlayerId; + if (debuffTimer >= maxDebuffTimer) + { + Logger.Info($"{pc.GetRealName()} Started Sacrifice", "Sacrifist"); + if (HasNecronomicon(pc)) + { + pc.RpcExileV2(); + pc.SetRealKiller(pc); + pc.SetDeathReason(PlayerState.DeathReason.Suicide); + Logger.Info($"{pc.GetRealName()} Ultimate Sacrifice", "Sacrifist"); + var covList = Main.AllAlivePlayerControls.Where(x => x.Is(Custom_Team.Coven) || x.Is(CustomRoles.Enchanted)); + foreach (var cov in covList) + { + Main.AllPlayerKillCooldown[cov.PlayerId] -= Main.AllPlayerKillCooldown[cov.PlayerId] * (NecroReducedCooldown.GetFloat() / 100); + } + return; + } + switch (DebuffID) + { + default: + break; + // Change Speed + case 0: + originalSpeed.Remove(randPlayer); + originalSpeed.Add(randPlayer, Main.AllPlayerSpeed[randPlayer]); + Main.AllPlayerSpeed[randPlayer] = Speed.GetFloat(); + originalSpeed.Remove(sacrifist); + originalSpeed.Add(sacrifist, Main.AllPlayerSpeed[sacrifist]); + Main.AllPlayerSpeed[sacrifist] = Speed.GetFloat(); + Logger.Info($"{pc.GetRealName()} Changed Speed for {randPlayerPC.GetRealName} and self", "Sacrifist"); + pc.Notify(GetString("SacrifistSpeedDebuff"), 15f); + break; + // Change Vision + case 1: + pc.Notify(GetString("SacrifistVisionDebuff"), 15f); + break; + // Change Cooldown + case 2: + Main.AllPlayerKillCooldown[randPlayer] += Main.AllPlayerKillCooldown[randPlayer] * (IncreasedCooldown.GetFloat() / 100); + Main.AllPlayerKillCooldown[sacrifist] += Main.AllPlayerKillCooldown[sacrifist] * (IncreasedCooldown.GetFloat() / 100); + maxDebuffTimer += maxDebuffTimer * (IncreasedCooldown.GetFloat() / 100); + Logger.Info($"{pc.GetRealName()} Changed Cooldown for {randPlayerPC.GetRealName} and self", "Sacrifist"); + pc.Notify(GetString("SacrifistCooldownDebuff"), 15f); + break; + // Cant Fix Sabotage (not coding allat, just give them Fool) + case 3: + GetPlayerById(sacrifist).RpcSetCustomRole(CustomRoles.Fool); + randPlayerPC.RpcSetCustomRole(CustomRoles.Fool); + Logger.Info($"{pc.GetRealName()} Gave Fool to {randPlayerPC.GetRealName} and self", "Sacrifist"); + pc.Notify(GetString("SacrifistFoolDebuff"), 15f); + break; + // Make one of them call a meeting + case 4: + pc.Notify(GetString("SacrifistMeetingDebuff"), 15f); + switch (rand.Next(0,2)) + { + case 0: + randPlayerPC.NoCheckStartMeeting(null); + Logger.Info($"{pc.GetRealName()} Made Self Call meeting", "Sacrifist"); + break; + case 1: + GetPlayerById(sacrifist).NoCheckStartMeeting(null); + Logger.Info($"{pc.GetRealName()} Made {randPlayerPC.GetRealName} call meeting", "Sacrifist"); + break; + } + break; + // Can't Report + case 5: + ReportDeadBodyPatch.CanReport[randPlayer] = false; + ReportDeadBodyPatch.CanReport[sacrifist] = false; + Logger.Info($"{pc.GetRealName()} Made {randPlayerPC.GetRealName} and self unable to report", "Sacrifist"); + pc.Notify(GetString("SacrifistReportDebuff"), 15f); + break; + // Reset Tasks (inapplicable to Sacrifist) + case 6: + var taskState = pc.GetPlayerTaskState(); + randPlayerPC.Data.RpcSetTasks(new Il2CppStructArray(0)); //Let taskassign patch decide the tasks + taskState.CompletedTasksCount = 0; + pc.Notify(GetString("SacrifistTasksDebuff"), 15f); + break; + // Swap Skins + case 7: + var temp = pc.CurrentOutfit; + OriginalPlayerSkins.Add(pc.PlayerId, Camouflage.PlayerSkins[pc.PlayerId]); + Camouflage.PlayerSkins[pc.PlayerId] = temp; + pc.SetNewOutfit(randPlayerPC.CurrentOutfit, setName: true, setNamePlate: true); + + OriginalPlayerSkins.Add(randPlayer, Camouflage.PlayerSkins[randPlayer]); + Camouflage.PlayerSkins[randPlayer] = randPlayerPC.CurrentOutfit; + randPlayerPC.SetNewOutfit(temp, setName: true, setNamePlate: true); + Logger.Info($"{pc.GetRealName()} swapped outfit with {randPlayerPC.GetRealName}", "Sacrifist"); + break; + // Fake Snitch Arrow to Sacrifist and Target (done in different method) + case 8: + Logger.Info($"{pc.GetRealName()} Caused Arrow to {randPlayerPC.GetRealName} and self for everyone", "Sacrifist"); + pc.Notify(GetString("SacrifistDoxxDebuff"), 15f); + break; + // Swap Sacrifist and Target (done in different method) + case 9: + Logger.Info($"{pc.GetRealName()} Will Swap with {randPlayerPC.GetRealName} 5s after exiting vent", "Sacrifist"); + pc.Notify(GetString("SacrifistSwapDebuff"), 15f); + break; + } + SendRPC(pc); + debuffTimer = 0; + } + } + public override void OnExitVent(PlayerControl pc, int ventId) + { + if (DebuffID == 9) + { + _ = new LateTask(() => + { + var randPlayerPC = GetPlayerById(randPlayer); + if (pc.CanBeTeleported() && randPlayerPC.CanBeTeleported()) + { + var originPs = randPlayerPC.GetCustomPosition(); + randPlayerPC.RpcTeleport(pc.GetCustomPosition()); + pc.RpcTeleport(originPs); + + pc.RPCPlayCustomSound("Teleport"); + randPlayerPC.RPCPlayCustomSound("Teleport"); + } + else + { + pc.Notify(ColorString(GetRoleColor(CustomRoles.Sacrifist), GetString("ErrorTeleport"))); + } + }, 5f, "Sacrifist Swap"); + } + } + public override string GetSuffixOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) + { + if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; + if (randPlayer == byte.MaxValue) return string.Empty; + if (randPlayer == seer.PlayerId) return string.Empty; + if (DebuffID != 8) return string.Empty; + + var warning = "⚠"; + warning += TargetArrow.GetArrows(seer, [_Player.PlayerId, randPlayer]); + + return ColorString(GetRoleColor(CustomRoles.Snitch), warning); + } + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) + { + + var sacrifist = _Player.PlayerId; + DebuffID = 10; + + Main.AllPlayerSpeed[randPlayer] = originalSpeed[randPlayer]; + GetPlayerById(randPlayer).SyncSettings(); + originalSpeed.Remove(randPlayer); + ReportDeadBodyPatch.CanReport[randPlayer] = true; + GetPlayerById(randPlayer).ResetKillCooldown(); + Camouflage.PlayerSkins[randPlayer] = OriginalPlayerSkins[randPlayer]; + + if (!Camouflage.IsCamouflage) + { + PlayerControl pc = + Main.AllAlivePlayerControls.FirstOrDefault(a => a.PlayerId == randPlayer); + + pc.SetNewOutfit(OriginalPlayerSkins[randPlayer], setName: true, setNamePlate: true); + } + randPlayer = byte.MaxValue; + Logger.Info($"Resetting Debuffs for Affected player", "Sacrifist"); + + + Main.AllPlayerSpeed[sacrifist] = originalSpeed[sacrifist]; + GetPlayerById(sacrifist).SyncSettings(); + originalSpeed.Remove(sacrifist); + ReportDeadBodyPatch.CanReport[sacrifist] = true; + _Player.ResetKillCooldown(); + maxDebuffTimer = DebuffCooldown.GetFloat(); + Camouflage.PlayerSkins[sacrifist] = OriginalPlayerSkins[sacrifist]; + + if (!Camouflage.IsCamouflage) + { + PlayerControl pc = + Main.AllAlivePlayerControls.FirstOrDefault(a => a.PlayerId == sacrifist); + + pc.SetNewOutfit(OriginalPlayerSkins[sacrifist], setName: true, setNamePlate: true); + } + Logger.Info($"Resetting Debuffs for Sacrifist", "Sacrifist"); + } + public static void SetVision(PlayerControl player, IGameOptions opt) + { + if ((player.PlayerId == randPlayer || player.PlayerId == Utils.GetPlayerListByRole(CustomRoles.Sacrifist).First().PlayerId) && DebuffID == 1) + { + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, Vision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Vision.GetFloat()); + } + } + public override string GetLowerText(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) + { + return debuffTimer.ToString() + "s / " + maxDebuffTimer.ToString() + "s"; + } + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) + { + if (debuffTimer < maxDebuffTimer) + { + debuffTimer += Time.fixedDeltaTime; + } + } + public override void OnPlayerExiled(PlayerControl player, NetworkedPlayerInfo exiled) + { + if (exiled == null) return; + if (exiled != _Player) return; + + List killPotentials = []; + var votedForExiled = MeetingHud.Instance.playerStates.Where(a => a.VotedFor == exiled.PlayerId && a.TargetPlayerId != exiled.PlayerId).ToArray(); + foreach (var playerVote in votedForExiled) + { + var crewPlayer = Main.AllPlayerControls.FirstOrDefault(a => a.PlayerId == playerVote.TargetPlayerId); + if (crewPlayer == null || crewPlayer.GetCustomRole().IsCoven() || crewPlayer.GetCustomRole().IsTNA()) return; + killPotentials.Add(crewPlayer); + } + if (killPotentials.Count == 0) return; + + List killPlayers = []; + + for (int i = 0; i < DeathsAfterVote.GetInt(); i++) + { + if (killPotentials.Count == 0) break; + + PlayerControl target = killPotentials.RandomElement(); + target.SetRealKiller(_Player); + killPlayers.Add(target.PlayerId); + killPotentials.Remove(target); + } + + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Retribution, [.. killPlayers]); + } +} \ No newline at end of file From ef2bc48592dc72cb0081abdea2b7f17248a77e87 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 3 Nov 2024 12:22:14 -0500 Subject: [PATCH 014/101] Voodoo Master and Coven interactions with other roles --- Modules/CustomRolesHelper.cs | 1 + Modules/GameState.cs | 4 + Modules/GuessManager.cs | 28 +++++- Modules/OptionHolder.cs | 5 + Patches/ChatCommandPatch.cs | 4 + Patches/MeetingHudPatch.cs | 12 +++ Resources/Lang/en_US.json | 68 ++++++++++---- Roles/AddOns/Common/Fragile.cs | 3 +- Roles/Core/CustomRoleManager.cs | 4 + Roles/Coven/CovenManager.cs | 2 +- Roles/Coven/Sacrifist.cs | 156 ++++++++++++++++++++++---------- Roles/Coven/VoodooMaster.cs | 122 +++++++++++++++++++++++++ Roles/Crewmate/Altruist.cs | 22 ++++- Roles/Crewmate/Captain.cs | 6 +- Roles/Crewmate/Cleanser.cs | 13 ++- Roles/Crewmate/CopyCat.cs | 5 + Roles/Crewmate/Enigma.cs | 2 + Roles/Crewmate/FortuneTeller.cs | 21 ++++- Roles/Crewmate/Inspector.cs | 37 ++++++-- Roles/Crewmate/Jailer.cs | 3 + Roles/Crewmate/Judge.cs | 12 +++ Roles/Crewmate/Merchant.cs | 8 +- Roles/Crewmate/Oracle.cs | 28 +++++- Roles/Crewmate/President.cs | 4 + Roles/Crewmate/Psychic.cs | 6 +- Roles/Crewmate/Sheriff.cs | 13 ++- Roles/Crewmate/Snitch.cs | 6 +- Roles/Crewmate/Swapper.cs | 16 +++- Roles/Impostor/Councillor.cs | 7 ++ Roles/Impostor/DoubleAgent.cs | 7 ++ Roles/Impostor/Eraser.cs | 2 +- Roles/Impostor/Visionary.cs | 5 + Roles/Neutral/Amnesiac.cs | 10 +- Roles/Neutral/Doomsayer.cs | 11 ++- Roles/Neutral/Executioner.cs | 3 + Roles/Neutral/Lawyer.cs | 3 + 36 files changed, 554 insertions(+), 105 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 30e642518..67e35ee29 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -1243,6 +1243,7 @@ public static CountTypes GetCountTypes(this CustomRoles role) CustomRoles.Wraith => CountTypes.Wraith, var r when r.IsNA() => CountTypes.Apocalypse, var r when r.IsCoven() => CountTypes.Coven, + CustomRoles.Enchanted => CountTypes.Coven, CustomRoles.Agitater => CountTypes.Agitater, CustomRoles.Parasite => CountTypes.Impostor, CustomRoles.SerialKiller => CountTypes.SerialKiller, diff --git a/Modules/GameState.cs b/Modules/GameState.cs index c8c7017ca..16c3bb382 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -111,6 +111,10 @@ public void SetMainRole(CustomRoles role) { countTypes = CountTypes.OutOfGame; } + if (pc.Is(CustomRoles.Enchanted)) + { + countTypes = CountTypes.Coven; + } } public void SetSubRole(CustomRoles role, PlayerControl pc = null) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index e6b5a0292..893d661aa 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -6,6 +6,7 @@ using TOHE.Modules.ChatManager; using TOHE.Roles.AddOns.Common; using TOHE.Roles.Core; +using TOHE.Roles.Coven; using TOHE.Roles.Crewmate; using TOHE.Roles.Double; using TOHE.Roles.Impostor; @@ -192,6 +193,15 @@ public static bool GuesserMsg(PlayerControl pc, string msg, bool isUI = false) if (target != null) { + + if (target.Is(CustomRoles.VoodooMaster) && VoodooMaster.Dolls[target.PlayerId].Count > 0) + { + target = Utils.GetPlayerById(VoodooMaster.Dolls[target.PlayerId].Where(x => Utils.GetPlayerById(x).IsAlive()).ToList().RandomElement()); + _ = new LateTask(() => + { + Utils.SendMessage(string.Format(GetString("VoodooMasterTargetInMeeting"), target.GetRealName()), Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().PlayerId); + }, 2f, "Voodoo Master Notify"); + } GuessMaster.OnGuess(role); bool guesserSuicide = false; @@ -758,11 +768,12 @@ static void GuesserOnClick(byte playerId, MeetingHud __instance) Crewmate = 0 Impostor = 1 Neutral = 2 - Add-ons = 3 + Coven = 3 + Add-ons = 4 */ int tabCount = 0; - for (int TabId = 0; TabId < 4; TabId++) + for (int TabId = 0; TabId < 5; TabId++) { if (PlayerControl.LocalPlayer.Is(CustomRoles.EvilGuesser)) { @@ -780,7 +791,7 @@ static void GuesserOnClick(byte playerId, MeetingHud __instance) { //if (!Options.GCanGuessCrew.GetBool() && TabId == 0) continue; //if (!Options.GCanGuessImp.GetBool() && TabId == 1) continue; - if (!Guesser.GCanGuessAdt.GetBool() && TabId == 3) continue; + if (!Guesser.GCanGuessAdt.GetBool() && TabId == 4) continue; } else if (Options.GuesserMode.GetBool() && !(PlayerControl.LocalPlayer.Is(CustomRoles.EvilGuesser) || @@ -790,7 +801,8 @@ static void GuesserOnClick(byte playerId, MeetingHud __instance) { if (!Options.CrewCanGuessCrew.GetBool() && PlayerControl.LocalPlayer.Is(Custom_Team.Crewmate) && TabId == 0) continue; if (!Options.ImpCanGuessImp.GetBool() && PlayerControl.LocalPlayer.Is(Custom_Team.Impostor) && TabId == 1) continue; - if (!Options.CanGuessAddons.GetBool() && TabId == 3) continue; + if (!Options.CovenCanGuessCoven.GetBool() && PlayerControl.LocalPlayer.Is(Custom_Team.Coven) && TabId == 3) continue; + if (!Options.CanGuessAddons.GetBool() && TabId == 4) continue; } Transform TeambuttonParent = new GameObject().transform; @@ -808,6 +820,7 @@ static void GuesserOnClick(byte playerId, MeetingHud __instance) Custom_Team.Crewmate => new Color32(140, 255, 255, byte.MaxValue), Custom_Team.Impostor => new Color32(255, 25, 25, byte.MaxValue), Custom_Team.Neutral => new Color32(127, 140, 141, byte.MaxValue), + Custom_Team.Coven => new Color32(172, 66, 242, byte.MaxValue), Custom_Team.Addon => new Color32(255, 154, 206, byte.MaxValue), _ => throw new NotImplementedException(), }; @@ -966,6 +979,12 @@ void ClickEvent() listOfRoles.Add(CustomRoles.War); } + if (CustomRoles.Ritualist.IsEnable()) + { + if (!listOfRoles.Contains(CustomRoles.Enchanted)) + listOfRoles.Add(CustomRoles.Enchanted); + } + arrayOfRoles = [.. listOfRoles]; } else @@ -1000,6 +1019,7 @@ or CustomRoles.Mare or CustomRoles.Cyber or CustomRoles.Sloth or CustomRoles.Apocalypse + or CustomRoles.Coven || (role.IsTNA() && !Options.TransformedNeutralApocalypseCanBeGuessed.GetBool())) continue; CreateRole(role); diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index e85247350..0a26c4f50 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -241,6 +241,7 @@ private enum RatesZeroOne public static OptionItem ShowImpRemainOnEject; public static OptionItem ShowNKRemainOnEject; public static OptionItem ShowNARemainOnEject; + public static OptionItem ShowCovenRemainOnEject; public static OptionItem ShowTeamNextToRoleNameOnEject; public static OptionItem ConfirmEgoistOnEject; public static OptionItem ConfirmLoversOnEject; @@ -1263,6 +1264,10 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetParent(ShowImpRemainOnEject) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(255, 238, 232, byte.MaxValue)); + ShowCovenRemainOnEject = BooleanOptionItem.Create(60447, "ShowCovenRemainOnEject", true, TabGroup.ModSettings, false) + .SetParent(ShowImpRemainOnEject) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 238, 232, byte.MaxValue)); ShowTeamNextToRoleNameOnEject = BooleanOptionItem.Create(60443, "ShowTeamNextToRoleNameOnEject", false, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(255, 238, 232, byte.MaxValue)); diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index cde652488..9b996ed55 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -2345,6 +2345,7 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); + int covnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Coven)); var sub = new StringBuilder(); sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); @@ -2355,6 +2356,9 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can if (Options.ShowApocalypseInLeftCommand.GetBool()) sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); + if (Options.ShowCovenInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.CovenCount"), covnum)); + sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); Utils.SendMessage(sub.ToString(), player.PlayerId); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index d59b18c75..da2acd380 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -439,6 +439,8 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti int impnum = 0; int neutralnum = 0; int apocnum = 0; + int covennum = 0; + if (CustomRoles.Bard.RoleExist()) { @@ -458,6 +460,8 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti neutralnum++; else if (pc_role.IsNA() && pc != exiledPlayer.Object) apocnum++; + else if (pc_role.IsCoven() && pc != exiledPlayer.Object) + covennum++; } switch (Options.CEMode.GetInt()) { @@ -474,6 +478,9 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti else if (player.GetCustomRole().IsNeutral() && !player.Is(CustomRoles.Parasite) && !player.Is(CustomRoles.Refugee) && !player.Is(CustomRoles.Crewpostor)) name = string.Format(GetString("BelongTo"), realName, ColorString(new Color32(127, 140, 141, byte.MaxValue), GetString("TeamNeutral"))); + else if (player.GetCustomRole().IsCoven()) + name = string.Format(GetString("BelongTo"), realName, ColorString(GetRoleColor(CustomRoles.Coven), GetString("TeamCoven"))); + break; case 2: name = string.Format(GetString("PlayerIsRole"), realName, coloredRole); @@ -486,6 +493,8 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti name += ColorString(new Color32(127, 140, 141, byte.MaxValue), GetString("TeamNeutral")); else if (player.GetCustomRole().IsCrewmate()) name += ColorString(new Color32(140, 255, 255, byte.MaxValue), GetString("TeamCrewmate")); + else if (player.GetCustomRole().IsCrewmate() || player.Is(CustomRoles.Enchanted)) + name += ColorString(new Color32(172, 66, 242, byte.MaxValue), GetString("TeamCoven")); name += ")"; } break; @@ -513,6 +522,8 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti name += string.Format(GetString("NeutralRemain"), neutralnum) + comma; if (Options.ShowNARemainOnEject.GetBool() && apocnum > 0) name += string.Format(GetString("ApocRemain"), apocnum) + comma; + if (Options.ShowCovenRemainOnEject.GetBool() && covennum > 0) + name += string.Format(GetString("CovenRemain"), covennum) + comma; } EndOfSession: @@ -955,6 +966,7 @@ public static void NotifyRoleSkillOnMeetingStart() if (!Cyber.ImpKnowCyberDead.GetBool() && pc.GetCustomRole().IsImpostor()) continue; if (!Cyber.NeutralKnowCyberDead.GetBool() && pc.GetCustomRole().IsNeutral()) continue; if (!Cyber.CrewKnowCyberDead.GetBool() && pc.GetCustomRole().IsCrewmate()) continue; + if (!Cyber.CovenKnowCyberDead.GetBool() && pc.GetCustomRole().IsCoven()) continue; AddMsg(string.Format(GetString("CyberDead"), Main.AllPlayerNames[csId]), pc.PlayerId, ColorString(GetRoleColor(CustomRoles.Cyber), GetString("CyberNewsTitle"))); } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index f68738ebd..19e069434 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -958,18 +958,18 @@ "PoisonerInfoLong": "(Coven):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", "HexMasterInfoLong": "(Coven):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", "JinxInfoLong": "(Coven):\nAs the Jinx, whenever you get attacked, you jinx them, resulting in them dying to a jinx.\nThis has limited uses.", - "MedusaInfoLong": "(Coven):\nThe Medusa can use their kill button on players to mark them as Stoned. When the Medusa vents, all Stoned players will be unable to move and will have reduced vision for a configurable amount of time.\nWith the Necronomicon, killed players will be unreportable.", + "MedusaInfoLong": "(Coven):\nThe Medusa can use their kill button on players to mark them as Stoned. When the Medusa vents, all Stoned players will be unable to move and will have reduced vision for a configurable amount of time.\nWith the Necronomicon, killed players will be unreportable.", "PotionMasterInfoLong": "(Coven):\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": "(Coven):\nAs the Necromancer, 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.", - "CovenLeaderInfoLong": "(Coven):\nThe Coven Leader can use their kill button on a fellow Coven member to retrain them into a random Coven role that isn’t currently in the game. During the next meeting, that Coven member will be notified that the Coven Leader wishes to retrain them. They can vote themselves to accept the retrain, or vote otherwise to deny it. Denying the retrain does not take away an ability usage.\nWith the Necronomicon, you cannot retrain, and can only kill other players.", - "RitualistInfoLong": "(Coven):\nDuring a meeting, the Ritualist can perform a Blood Ritual to guess a player’s exact role. If the Ritualist is correct, that player will be converted to Coven. If the Ritualist is incorrect, they will not die but will be unable to Blood Ritual until the next meeting.\nhe command is /rt id role.\nWith the Necronomicon, the Ritualist can kill.", - "SpellcasterInfoLong": "(Coven):\nThe Spellcaster can use their kill button on two players to control the first player into the other. If the first player has a kill button, they will be forced to use their kill button on the second person.\nWith the Necronomicon, you will kill your first target after controlling them.", - "ConjurerInfoLong": "(Coven):\nShapeshift once to mark a location.\nShapeshift again to conjure a meteor at the place you marked, killing everyone in the radius.\nWith the Necronomicon, you can kill. You can also mark a player using the Shapeshift menu. When the Conjurer unshapeshifts, all the players in the radius of the marked player will die, including the marked player.", - "DreamweaverInfoLong": "(Coven):\nThe Dreamweaver can Dreamweave a player. The dreamweaved players will be notified of this during the next meeting. If the Dreamweaver is not voted out, these players will be unable to use their abilities until the Dreamweaver dies.\nWith the Necronomicon, the Dreamweaver can double-click to kill.", - "IllusionistInfoLong": "(Coven):\nThe Illusionist can use their kill button on a player to reverse the results of any investigative role. For example, if someone with a kill button is Illusioned, they will appear not to have a kill button to the Investigator, and vice versa.\nIllusions wear off after meetings.\nWith the Necronomicon, you may double-click to kill. Every kill you make appears as a random death reason.", - "VoodooMasterInfoLong": "(Coven):\nThe Voodoo Master can craft a voodoo doll of a player by using their kill button, similar to the Shaman. All the interactions with you using kill button will be deflected to the voodoo doll and the voodoo doll will destroy. Unlike the Shaman, this voodoo will last during the meeting (eg. If the Voodoo Master is judged, then the voodoo'd player will be judged instead).\nWith the Necronomicon, you can double-click to kill. Additionally, the voodoo’d player will be unable to report. The next person to interact with them will die.", - "SacrifistInfoLong": "(Coven):\nThe Sacrifist can vent to cause a random debuff to a non-coven member, however, the Sacrifist will also receive this effect (when applicable).\nDepending on the host’s settings, if the Sacrifist is voted out, some random non-coven who voted the Sacrifist will die too.\nWith the Necronomicon, when you vent, you will use the Ultimate Sacrifice. When you do this, you die. However, the entire Coven for the rest of the game receives a lowered kill cooldown.", - "MoonDancerInfoLong": "(Coven):\nThe Moon Dancer can use their kill button to use their ability, Baton Pass.\nIf used on a Coven member: Gives a helpful add-on at the next meeting.\nIf used on a non-Coven member: Gives a harmful add-on at the next meeting.\nWith the Necronomicon, the Moon Dancer can double-click their kill button to kill. When killing, the player is teleported off the map. They will appear alive on vitals and will not show up in tracefinder's arrows etc. They die when a meeting/body report with the death reason Blasted Off.", + "CovenLeaderInfoLong": "(Coven):\nThe Coven Leader can use their kill button on a fellow Coven member to retrain them into a random Coven role that isn’t currently in the game. During the next meeting, that Coven member will be notified that the Coven Leader wishes to retrain them. They can vote themselves to accept the retrain, or vote otherwise to deny it. Denying the retrain does not take away an ability usage.\nWith the Necronomicon, you cannot retrain, and can only kill other players.", + "RitualistInfoLong": "(Coven):\nDuring a meeting, the Ritualist can perform a Blood Ritual to guess a player’s exact role. If the Ritualist is correct, that player will be converted to Coven. If the Ritualist is incorrect, they will not die but will be unable to Blood Ritual until the next meeting.\nhe command is /rt id role.\nWith the Necronomicon, the Ritualist can kill.", + "SpellcasterInfoLong": "(Coven):\nThe Spellcaster can use their kill button on two players to control the first player into the other. If the first player has a kill button, they will be forced to use their kill button on the second person.\nWith the Necronomicon, you will kill your first target after controlling them.", + "ConjurerInfoLong": "(Coven):\nShapeshift once to mark a location.\nShapeshift again to conjure a meteor at the place you marked, killing everyone in the radius.\nWith the Necronomicon, you can kill. You can also mark a player using the Shapeshift menu. When the Conjurer unshapeshifts, all the players in the radius of the marked player will die, including the marked player.", + "DreamweaverInfoLong": "(Coven):\nThe Dreamweaver can Dreamweave a player. The dreamweaved players will be notified of this during the next meeting. If the Dreamweaver is not voted out, these players will be unable to use their abilities until the Dreamweaver dies.\nWith the Necronomicon, the Dreamweaver can double-click to kill.", + "IllusionistInfoLong": "(Coven):\nThe Illusionist can use their kill button on a player to reverse the results of any investigative role. For example, if someone with a kill button is Illusioned, they will appear not to have a kill button to the Investigator, and vice versa.\nIllusions wear off after meetings.\nWith the Necronomicon, you may double-click to kill. Every kill you make appears as a random death reason.", + "VoodooMasterInfoLong": "(Coven):\nThe Voodoo Master can craft a voodoo doll of a player by using their kill button, similar to the Shaman. All the interactions with you using kill button will be deflected to the voodoo doll and the voodoo doll will destroy. Unlike the Shaman, this voodoo will last during the meeting (eg. If the Voodoo Master is judged, then the voodoo'd player will be judged instead).\nWith the Necronomicon, you can double-click to kill. Additionally, the voodoo’d player will be unable to report. The next person to interact with them will die.", + "SacrifistInfoLong": "(Coven):\nThe Sacrifist can vent to cause a random debuff to a non-coven member, however, the Sacrifist will also receive this effect (when applicable).\nThe random player will be the same player until the round ends.\nDepending on the host’s settings, if the Sacrifist is voted out, some random non-coven who voted the Sacrifist will die too.\nNormally, the Sacrifist can not kill, however, to prevent prolonging of the game, Sacrifist can kill if they are the last Coven member alive.\nWith the Necronomicon, when you vent, you will commit the Ultimate Sacrifice. When you do this, you die. However, the entire Coven for the rest of the game receives a lowered kill cooldown.", + "MoonDancerInfoLong": "(Coven):\nThe Moon Dancer can use their kill button to use their ability, Baton Pass.\nIf used on a Coven member: Gives a helpful add-on at the next meeting.\nIf used on a non-Coven member: Gives a harmful add-on at the next meeting.\nWith the Necronomicon, the Moon Dancer can double-click their kill button to kill. When killing, the player is teleported off the map. They will appear alive on vitals and will not show up in tracefinder's arrows etc. They die when a meeting/body report with the death reason Blasted Off.", "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.", "LastImpostorInfoLong": "(Add-ons):\nThis special effect is given to the last surviving Impostor. It significantly reduces their kill cooldown.", "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.", @@ -1375,6 +1375,7 @@ "ShowImpRemainOnEject": "Show remaining Impostors on ejects", "ShowNKRemainOnEject": "Show remaining Neutral Killers on ejects", "ShowNARemainOnEject": "Show remaining Neutral Apocalypse on ejects", + "ShowCovenRemainOnEject": "Show remaining Coven on ejects", "ConfirmEgoistOnEject": "Confirm Egoists on ejection", "ConfirmLoversOnEject": "Confirm Lovers on ejection", "ConfirmSidekickOnEject": "Confirm Sidekicks on ejection", @@ -1489,6 +1490,7 @@ "GuesserTryHideMsg": "Try to hide the guesser's command", "GCanGuessImp": "Impostor can guess Impostor roles", "GCanGuessCrew": "Crewmate can guess Crewmate roles", + "GCanGuessCoven": "Coven can guess Coven roles", "GCanGuessAdt": "Can guess Add-ons", "GCanGuessTaskDoneSnitch": "Can guess Snitch with All Tasks Done", "BountyTargetChangeTime": "Time Until Target Swaps", @@ -1567,10 +1569,13 @@ "SheriffCanKillMadmate": "Can Kill Madmates", "SheriffCanKillInfected": "Can Kill Infected players", "SheriffCanKillContagious": "Can Kill Contagious players", + "SheriffCanKillEnchanted": "Can Kill Enchanted players", + "SheriffCanKillCoven": "Can Kill Coven", "SheriffSetMadCanKill": "Non-Crew Sheriff Configuration", "SheriffMadCanKillImp": "Can kill Impostors", "SheriffMadCanKillNeutral": "Can kill Neutrals", "SheriffMadCanKillCrew": "Can kill Crewmates", + "SheriffMadCanKillCoven": "Can kill Coven", "RebirthUses": "Amount of Rebirths", "RebirthCountVotes": "Only rebirth to players who voted for them", @@ -1589,6 +1594,7 @@ "SnitchCanGetArrowColor": "See Colored Arrows based on Team Colors", "SnitchCanFindNeutralKiller": "Can Find Neutral Killers", "SnitchCanFindNeutralApoc": "Can Find Neutral Apocalypse", + "SnitchCanFindCoven": "Can Find Coven", "SnitchCanFindMadmate": "Can Find Madmates", "SnitchRemainingTaskFound": "Remaining tasks to be known", "MayorAdditionalVote": "Additional Votes Count", @@ -1607,11 +1613,13 @@ "ExecutionerCanTargetNeutralBenign": "Can Target Neutral Benign", "ExecutionerCanTargetNeutralEvil": "Can Target Neutral Evil", "ExecutionerCanTargetNeutralChaos": "Can Target Neutral Chaos", + "ExecutionerCanTargetCoven": "Can Target Coven", "Executioner_RevealTargetUponEject": "Reveal Target Upon Ejection", "SidekickSheriffCanGoBerserk": "Recruited Sheriff Can Go Nuts", "LawyerCanTargetImpostor": "Can Target Impostors", "LawyerCanTargetNeutralKiller": "Can Target Neutral Killers", "LawyerCanTargetNeutralApocalypse": "Can Target Neutral Apocalypse", + "LawyerCanTargetCoven": "Can Target Coven", "LawyerCanTargetCrewmate": "Can Target Crewmates", "LawyerCanTargetJester": "Can Target Jester", "LawyerChangeRolesAfterTargetKilled": "When Target Dies, Lawyer becomes", @@ -1693,6 +1701,7 @@ "Psychic_NCareRed": "Neutral Chaos can be red", "Psychic_NAareRed": "Neutral Apocalypse can be red", "Psychic_NKareRed": "Neutral Killers can be red", + "Psychic_CovareRed": "Coven can be red", "Psychic_CrewKillingRed": "Crewmate Killing can be red", "PsychicCanSeeNum": "Max number of red names", "PsychicFresh": "New red names every meeting", @@ -1789,6 +1798,8 @@ "Altruist_ImpostorsCanGetsArrow": "Impostors Can Get Arrow", "Altruist_NeutralKillersCanGetsAlert": "Neutral Killers Can Get Alert", "Altruist_NeutralKillersCanGetsArrow": "Neutral Killers Can Get Arrow", + "Altruist_CovenCanGetsAlert": "Coven Can Get Alert", + "Altruist_CovenCanGetsArrow": "Coven Can Get Arrow", "AltruistSuffix": "<#00ffa5>Mode: {0}", "AltruistReviveMode": "Revive", "AltruistReportMode": "Report", @@ -1832,6 +1843,7 @@ "JudgeCanTrialNeutralE": "Can trial Neutral Evil", "JudgeCanTrialNeutralC": "Can trial Neutral Chaos", "JudgeCanTrialNeutralA": "Can trial Neutral Apocalypse", + "JudgeCanTrialCoven": "Can trial Coven", "JudgeCanTrialSidekick": "Can trial Sidekick", "JudgeCanTrialInfected": "Can trial Infected", "JudgeCanTrialContagious": "Can trial Contagious", @@ -1840,6 +1852,7 @@ "JudgeTrialLimitPerGame": "Max Trials per Game", "JudgeCanTrialMadmate": "Can trial Madmates", "JudgeCanTrialCharmed": "Can trial Charmed players", + "JudgeCanTrialEnchanted": "Can trial Enchanted players", "JudgeDead": "Sorry, you can't trial players after death.", "JudgeTrialMaxMeetingMsg": "\nNo more meeting trials left!", "JudgeTrialMaxGameMsg": "\nNo more trials left!", @@ -1927,6 +1940,7 @@ "NeutralsSeePresident": "Neutrals can see revealed President", "MadmatesSeePresident": "Madmates can see revealed President", "ImpsSeePresident": "Impostors can see revealed President", + "CovenSeePresident": "Coven can see revealed President", "PresidentDead": "Sorry, you can't force end the meeting after death.", "PresidentEndMax": "No more force end meeting uses left!", "PresidentRevealMax": "You have already revealed yourself...", @@ -2010,10 +2024,13 @@ "SacrifistDebuffCooldown": "Debuff Cooldown", "SacrifistDeathsAfterVote": "Players Killed After Sacrifist is Exiled", - "SacrifistNecroReducedCooldown": "Coven Cooldown After Ultimate Sacrifices", + "SacrifistNecroReducedCooldown": "Coven Decreased Cooldown After Ultimate Sacrifice", "SacrifistVision": "Sacrificed Vision", + "SacrifistVisionDuration": "Vision Duration", "SacrifistSpeed": "Sacrificed Speed", + "SacrifistSpeedDuration": "Speed Duration", "SacrifistIncreasedCooldown": "Sacrificed Increase Cooldown", + "SacrifistFreezeDuration": "Random Freezing Duration", "SacrifistSpeedDebuff": "Speed Debuffed", "SacrifistVisionDebuff": "Vision Debuffed", "SacrifistCooldownDebuff": "Cooldown Debuffed", @@ -2021,15 +2038,19 @@ "SacrifistMeetingDebuff": "Forced Called Meeting", "SacrifistReportDebuff": "Can't Report Bodies", "SacrifistTasksDebuff": "Tasks Reset for Target", - "SacrifistInvertDebuff": "Inverted Speed", - "SacrifistDoxxDebuff": "Location Exposed", - "SacrifistSwapDebuff": "Swapped with Target", - - - - - + "SacrifistSwapSkinsDebuff": "Swapped Skins", + "SacrifistFreezeDebuff": "Random Freezing for {0} seconds", + "SacrifistSwapDebuff": "Swapping with target after 3 seconds", + "SacrifistVisionRevert": "Vision Reverted", + "SacrifistSpeedRevert": "Speed Reverted", + "VoodooMasterPerRound": "Voodoo Dolls per Round", + "VoodooMasterCanDollCoven": "Voodoo Master can Voodoo Coven", + "VoodooMasterNecroCanKillCov": "Necronomicon Ability can Kill Coven", + "VoodooMasterNoDollCoven": "You can't Voodoo other Coven members!", + "VoodooMasterTargetInMeeting": "Someone tried to target you with their role during this meeting, but it was deflected to one of your dolls, {0}, instead!", + "VoodooMasterNoDollsLeft": "You can't voodoo anyone else this round!", + "VoodooMasterDolledSomeone": "{0} has been turned into a Voodoo doll", "LuckyProbability": "Probability of surviving a kill", @@ -2386,6 +2407,7 @@ "MerchantNotifyBribery": "Inform Merchant when a killer gets bribed", "MerchantTargetCrew": "Can sell to Crewmates", "MerchantTargetImpostor": "Can sell to Impostors", + "MerchantTargetCoven": "Can sell to Coven", "MerchantTargetNeutral": "Can sell to Neutrals", "MerchantSellHelpful": "Can sell Helpful Add-ons", @@ -2789,7 +2811,8 @@ "NeutralRemain": "\n{0} Neutral Killers remain", "OneNeutralRemain": "\n{0} Neutral Killer remains", "ApocRemain": "\n{0} Neutral Apocalypse remains", - "GameOverReason.HumansByVote": "All Impostors and Neutral Killers were ejected or killed", + "CovenRemain": "\n{0} Coven remain", + "GameOverReason.HumansByVote": "All Impostors and Neutral Killers and Coven were ejected or killed", "GameOverReason.HumansByTask": "The Crewmates completed all tasks", "GameOverReason.HumansDisconnect": "Crewmates disconnected", "GameOverReason.ImpostorByVote": "The Crewmates were ejected", @@ -3137,6 +3160,7 @@ "CaptainCanTargetNC": "Captain can target Neutral Chaos", "CaptainCanTargetNA": "Captain can target Neutral Apocalypse", "CaptainCanTargetNK": "Captain can target Neutral Killer", + "CaptainCanTargetCoven": "Captain can target Coven", "CaptainSpeedReduced": "Captain reduced your speed", "CaptainRevealTaskRequired": "Number of tasks completed after which Captain is revealed", "CaptainSlowTaskRequired": "Number of tasks completed after which target speed is reduced", @@ -3171,6 +3195,7 @@ "JailerNECanBeExe": "Can execute Neutral Evil", "JailerNKCanBeExe": "Can execute Neutral Killing", "JailerNACanBeExe": "Can execute Neutral Apocalypse", + "JailerCovenCanBeExe": "Can execute Coven", "JailerCKCanBeExe": "Can execute Crew Killing", "JailerTargetAlreadySelected": "You have already selected a target", "SuccessfullyJailed": "Target successfully jailed", @@ -3360,6 +3385,7 @@ "OracleCheck.Crewmate": "Appears to be a crewmate", "OracleCheck.Neutral": "Appears to be a neutral", "OracleCheck.Impostor": "Appears to be an Impostor", + "OracleCheck.Coven": "Appears to be Coven", "OracleCheck": "Target Results:", "FailChance": "Chance of showing incorrect result", "OracleCheckAddons": "Oracle checks add-ons", @@ -3435,6 +3461,7 @@ "EnigmaClueRole2": "The Killer is a Neutral!", "EnigmaClueRole3": "The Killer is a Crewmate!", "EnigmaClueRole4": "The Killer's Role is {0}!", + "EnigmaClueRole5": "The Killer is a Coven member!", "EnigmaClueLevel1": "The Killer's Level is above 50!", "EnigmaClueLevel2": "The Killer's Level is below 50!", "EnigmaClueLevel3": "The Killer's Level is between {0} and {1}!", @@ -3563,6 +3590,7 @@ "DCanGuessImpostors": "Can Guess Impostors", "DCanGuessCrewmates": "Can Guess Crewmates", "DCanGuessNeutrals": "Can Guess Neutrals", + "DCanGuessCoven": "Can Guess Coven", "DCanGuessAdt": "Can Guess Add-Ons", "DoomsayerAdvancedSettings": "Advanced Settings", "DoomsayerMaxNumberOfGuessesPerMeeting": "Max number of guesses per meeting", diff --git a/Roles/AddOns/Common/Fragile.cs b/Roles/AddOns/Common/Fragile.cs index 9aa90b92a..3271640aa 100644 --- a/Roles/AddOns/Common/Fragile.cs +++ b/Roles/AddOns/Common/Fragile.cs @@ -34,7 +34,8 @@ public static bool KillFragile(PlayerControl killer, PlayerControl target) var killerRole = killer.GetCustomRole(); if ((killerRole.IsImpostorTeamV3() && ImpCanKillFragile.GetBool()) || (killerRole.IsNeutral() && NeutralCanKillFragile.GetBool()) - || (killerRole.IsCrewmate() && CrewCanKillFragile.GetBool())) + || (killerRole.IsCrewmate() && CrewCanKillFragile.GetBool()) + || (killerRole.IsCoven() && CovenCanKillFragile.GetBool())) { target.SetDeathReason(PlayerState.DeathReason.Shattered); if (FragileKillerLunge.GetBool()) diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 2eeae0312..65cdcb776 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -53,6 +53,10 @@ public static List GetExperimentalOptions(Custom_Team team) roles = RoleClass.Where(r => r.Value.IsExperimental && r.Key.IsNeutralTeamV2()).Select(r => r.Value).ToList(); break; + case Custom_Team.Coven: + roles = RoleClass.Where(r => r.Value.IsExperimental && r.Key.IsCoven()).Select(r => r.Value).ToList(); + break; + default: Logger.Info("Unsupported team was sent.", "GetExperimentalOptions"); break; diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index e852bfa43..db0438a21 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -33,7 +33,7 @@ public static void RunSetUpImpVisOptions(int Id) } public static void RunSetUpVentOptions(int Id) { - foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven() && (x is not CustomRoles.Medusa or CustomRoles.PotionMaster or CustomRoles.Sacrifist)).ToArray()) + foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven() && (x != CustomRoles.Medusa && x != CustomRoles.PotionMaster && x != CustomRoles.Sacrifist)).ToArray()) { SetUpVentOption(cov, Id, true, CovenVentMode); Id++; diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index 5d2fc99f4..aebcdb379 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -7,9 +7,6 @@ using Il2CppInterop.Runtime.InteropTypes.Arrays; using AmongUs.GameOptions; using Hazel; -using MS.Internal.Xml.XPath; -using static UnityEngine.GraphicsBuffer; -using TOHE.Roles.Impostor; namespace TOHE.Roles.Coven; @@ -24,16 +21,21 @@ internal class Sacrifist : CovenManager //==================================================================\\ private static OptionItem DebuffCooldown; - public static OptionItem DeathsAfterVote; - public static OptionItem NecroReducedCooldown; + private static OptionItem DeathsAfterVote; + private static OptionItem NecroReducedCooldown; private static OptionItem Vision; + private static OptionItem VisionDuration; private static OptionItem Speed; + private static OptionItem SpeedDuration; private static OptionItem IncreasedCooldown; + private static OptionItem RandomFreezeDuration; private static byte DebuffID = 10; private static float debuffTimer; private static float maxDebuffTimer; + private static float freezeTimer; private static byte randPlayer; + private static bool isFreezing; private static readonly Dictionary originalSpeed = []; private static readonly Dictionary OriginalPlayerSkins = []; @@ -46,10 +48,16 @@ public override void SetupCustomOption() .SetValueFormat(OptionFormat.Seconds); Vision = FloatOptionItem.Create(Id + 13, "SacrifistVision", new(0f, 5f, 0.25f), 0.5f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) .SetValueFormat(OptionFormat.Multiplier); + VisionDuration = FloatOptionItem.Create(Id + 17, "SacrifistVisionDuration", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) + .SetValueFormat(OptionFormat.Seconds); Speed = FloatOptionItem.Create(Id + 14, "SacrifistSpeed", new(0f, 5f, 0.25f), 0.5f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) .SetValueFormat(OptionFormat.Multiplier); + SpeedDuration = FloatOptionItem.Create(Id + 18, "SacrifistSpeedDuration", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) + .SetValueFormat(OptionFormat.Seconds); IncreasedCooldown = FloatOptionItem.Create(Id + 15, "SacrifistIncreasedCooldown", new(0f, 100f, 2.5f), 50f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) .SetValueFormat(OptionFormat.Percent); + RandomFreezeDuration = FloatOptionItem.Create(Id + 16, "SacrifistFreezeDuration", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) + .SetValueFormat(OptionFormat.Seconds); DeathsAfterVote = IntegerOptionItem.Create(Id + 11, "SacrifistDeathsAfterVote", new(0, 15, 1), 0, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) .SetValueFormat(OptionFormat.Players); NecroReducedCooldown = FloatOptionItem.Create(Id + 12, "SacrifistNecroReducedCooldown", new(0f, 100f, 2.5f), 50f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) @@ -67,6 +75,8 @@ public override void Add(byte playerId) { debuffTimer = 0; maxDebuffTimer = DebuffCooldown.GetFloat(); + freezeTimer = 0; + isFreezing = false; } public void SendRPC(PlayerControl pc) { @@ -98,7 +108,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) Logger.Info($"{pc.GetRealName()} Started Sacrifice", "Sacrifist"); if (HasNecronomicon(pc)) { - pc.RpcExileV2(); + pc.RpcMurderPlayer(pc); pc.SetRealKiller(pc); pc.SetDeathReason(PlayerState.DeathReason.Suicide); Logger.Info($"{pc.GetRealName()} Ultimate Sacrifice", "Sacrifist"); @@ -118,15 +128,32 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) originalSpeed.Remove(randPlayer); originalSpeed.Add(randPlayer, Main.AllPlayerSpeed[randPlayer]); Main.AllPlayerSpeed[randPlayer] = Speed.GetFloat(); + randPlayerPC.MarkDirtySettings(); originalSpeed.Remove(sacrifist); originalSpeed.Add(sacrifist, Main.AllPlayerSpeed[sacrifist]); Main.AllPlayerSpeed[sacrifist] = Speed.GetFloat(); + pc.MarkDirtySettings(); Logger.Info($"{pc.GetRealName()} Changed Speed for {randPlayerPC.GetRealName} and self", "Sacrifist"); - pc.Notify(GetString("SacrifistSpeedDebuff"), 15f); + pc.Notify(GetString("SacrifistSpeedDebuff"), SpeedDuration.GetFloat()); + _ = new LateTask(() => + { + pc.Notify(GetString("SacrifistSpeedRevert")); + Main.AllPlayerSpeed[randPlayer] = originalSpeed[randPlayer]; + GetPlayerById(randPlayer).SyncSettings(); + originalSpeed.Remove(randPlayer); + Main.AllPlayerSpeed[sacrifist] = originalSpeed[sacrifist]; + pc.SyncSettings(); + originalSpeed.Remove(sacrifist); + }, SpeedDuration.GetFloat(), "Sacrifist Revert Speed"); break; // Change Vision case 1: - pc.Notify(GetString("SacrifistVisionDebuff"), 15f); + pc.Notify(GetString("SacrifistVisionDebuff"), VisionDuration.GetFloat()); + _ = new LateTask(() => + { + DebuffID = 10; + pc.Notify(GetString("SacrifistVisionRevert"), 5f); + }, VisionDuration.GetFloat(), "Sacrifist Revert Vision"); break; // Change Cooldown case 2: @@ -134,47 +161,55 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) Main.AllPlayerKillCooldown[sacrifist] += Main.AllPlayerKillCooldown[sacrifist] * (IncreasedCooldown.GetFloat() / 100); maxDebuffTimer += maxDebuffTimer * (IncreasedCooldown.GetFloat() / 100); Logger.Info($"{pc.GetRealName()} Changed Cooldown for {randPlayerPC.GetRealName} and self", "Sacrifist"); - pc.Notify(GetString("SacrifistCooldownDebuff"), 15f); + pc.Notify(GetString("SacrifistCooldownDebuff"), 5f); break; // Cant Fix Sabotage (not coding allat, just give them Fool) case 3: GetPlayerById(sacrifist).RpcSetCustomRole(CustomRoles.Fool); randPlayerPC.RpcSetCustomRole(CustomRoles.Fool); Logger.Info($"{pc.GetRealName()} Gave Fool to {randPlayerPC.GetRealName} and self", "Sacrifist"); - pc.Notify(GetString("SacrifistFoolDebuff"), 15f); + pc.Notify(GetString("SacrifistFoolDebuff"), 5f); break; // Make one of them call a meeting case 4: pc.Notify(GetString("SacrifistMeetingDebuff"), 15f); - switch (rand.Next(0,2)) + _ = new LateTask(() => { - case 0: - randPlayerPC.NoCheckStartMeeting(null); - Logger.Info($"{pc.GetRealName()} Made Self Call meeting", "Sacrifist"); - break; - case 1: - GetPlayerById(sacrifist).NoCheckStartMeeting(null); - Logger.Info($"{pc.GetRealName()} Made {randPlayerPC.GetRealName} call meeting", "Sacrifist"); - break; - } + switch (rand.Next(0, 2)) + { + case 0: + randPlayerPC.NoCheckStartMeeting(null); + Logger.Info($"{pc.GetRealName()} Made Self Call meeting", "Sacrifist"); + break; + case 1: + GetPlayerById(sacrifist).NoCheckStartMeeting(null); + Logger.Info($"{pc.GetRealName()} Made {randPlayerPC.GetRealName} call meeting", "Sacrifist"); + break; + } + }, 2f, "Sacrifist Call Meeting"); break; // Can't Report case 5: ReportDeadBodyPatch.CanReport[randPlayer] = false; ReportDeadBodyPatch.CanReport[sacrifist] = false; Logger.Info($"{pc.GetRealName()} Made {randPlayerPC.GetRealName} and self unable to report", "Sacrifist"); - pc.Notify(GetString("SacrifistReportDebuff"), 15f); + pc.Notify(GetString("SacrifistReportDebuff"), 5f); break; - // Reset Tasks (inapplicable to Sacrifist) + // Reset Tasks case 6: - var taskState = pc.GetPlayerTaskState(); + var taskStateTarget = randPlayerPC.GetPlayerTaskState(); randPlayerPC.Data.RpcSetTasks(new Il2CppStructArray(0)); //Let taskassign patch decide the tasks - taskState.CompletedTasksCount = 0; - pc.Notify(GetString("SacrifistTasksDebuff"), 15f); + taskStateTarget.CompletedTasksCount = 0; + var taskStateSacrif = pc.GetPlayerTaskState(); + pc.Data.RpcSetTasks(new Il2CppStructArray(0)); //Let taskassign patch decide the tasks + taskStateSacrif.CompletedTasksCount = 0; + pc.Notify(GetString("SacrifistTasksDebuff"), 5f); + Logger.Info($"{pc.GetRealName()} Made {randPlayerPC.GetRealName} and self reset tasks", "Sacrifist"); break; // Swap Skins case 7: - var temp = pc.CurrentOutfit; + var temp = new NetworkedPlayerInfo.PlayerOutfit() + .Set(pc.GetRealName(), pc.CurrentOutfit.ColorId, pc.CurrentOutfit.HatId, pc.CurrentOutfit.SkinId, pc.CurrentOutfit.VisorId, pc.CurrentOutfit.PetId, pc.CurrentOutfit.NamePlateId); OriginalPlayerSkins.Add(pc.PlayerId, Camouflage.PlayerSkins[pc.PlayerId]); Camouflage.PlayerSkins[pc.PlayerId] = temp; pc.SetNewOutfit(randPlayerPC.CurrentOutfit, setName: true, setNamePlate: true); @@ -182,12 +217,14 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) OriginalPlayerSkins.Add(randPlayer, Camouflage.PlayerSkins[randPlayer]); Camouflage.PlayerSkins[randPlayer] = randPlayerPC.CurrentOutfit; randPlayerPC.SetNewOutfit(temp, setName: true, setNamePlate: true); + pc.Notify(GetString("SacrifistSwapSkinsDebuff"), 5f); Logger.Info($"{pc.GetRealName()} swapped outfit with {randPlayerPC.GetRealName}", "Sacrifist"); break; - // Fake Snitch Arrow to Sacrifist and Target (done in different method) + // Random Freezing (done in different method) case 8: - Logger.Info($"{pc.GetRealName()} Caused Arrow to {randPlayerPC.GetRealName} and self for everyone", "Sacrifist"); - pc.Notify(GetString("SacrifistDoxxDebuff"), 15f); + isFreezing = true; + Logger.Info($"{pc.GetRealName()} and {randPlayerPC.GetRealName} will randomly freeze for duration", "Sacrifist"); + pc.Notify(string.Format(GetString("SacrifistFreezeDebuff"), RandomFreezeDuration.GetFloat()), RandomFreezeDuration.GetFloat()); break; // Swap Sacrifist and Target (done in different method) case 9: @@ -219,30 +256,14 @@ public override void OnExitVent(PlayerControl pc, int ventId) { pc.Notify(ColorString(GetRoleColor(CustomRoles.Sacrifist), GetString("ErrorTeleport"))); } - }, 5f, "Sacrifist Swap"); + }, 3f, "Sacrifist Swap"); } } - public override string GetSuffixOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) - { - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; - if (randPlayer == byte.MaxValue) return string.Empty; - if (randPlayer == seer.PlayerId) return string.Empty; - if (DebuffID != 8) return string.Empty; - - var warning = "⚠"; - warning += TargetArrow.GetArrows(seer, [_Player.PlayerId, randPlayer]); - - return ColorString(GetRoleColor(CustomRoles.Snitch), warning); - } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { - var sacrifist = _Player.PlayerId; DebuffID = 10; - Main.AllPlayerSpeed[randPlayer] = originalSpeed[randPlayer]; - GetPlayerById(randPlayer).SyncSettings(); - originalSpeed.Remove(randPlayer); ReportDeadBodyPatch.CanReport[randPlayer] = true; GetPlayerById(randPlayer).ResetKillCooldown(); Camouflage.PlayerSkins[randPlayer] = OriginalPlayerSkins[randPlayer]; @@ -258,9 +279,6 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf Logger.Info($"Resetting Debuffs for Affected player", "Sacrifist"); - Main.AllPlayerSpeed[sacrifist] = originalSpeed[sacrifist]; - GetPlayerById(sacrifist).SyncSettings(); - originalSpeed.Remove(sacrifist); ReportDeadBodyPatch.CanReport[sacrifist] = true; _Player.ResetKillCooldown(); maxDebuffTimer = DebuffCooldown.GetFloat(); @@ -294,6 +312,46 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT { debuffTimer += Time.fixedDeltaTime; } + if (isFreezing) + { + if (freezeTimer < RandomFreezeDuration.GetFloat()) + { + var rand = IRandom.Instance; + var num = rand.Next(0, 10); + if (num == 0) + { + originalSpeed.Remove(randPlayer); + originalSpeed.Add(randPlayer, Main.AllPlayerSpeed[randPlayer]); + Main.AllPlayerSpeed[randPlayer] = 0f; + GetPlayerById(randPlayer).MarkDirtySettings(); + originalSpeed.Remove(player.PlayerId); + originalSpeed.Add(player.PlayerId, Main.AllPlayerSpeed[player.PlayerId]); + Main.AllPlayerSpeed[player.PlayerId] = 0f; + player.MarkDirtySettings(); + } + else + { + Main.AllPlayerSpeed[randPlayer] = originalSpeed[randPlayer]; + GetPlayerById(randPlayer).SyncSettings(); + originalSpeed.Remove(randPlayer); + Main.AllPlayerSpeed[player.PlayerId] = originalSpeed[player.PlayerId]; + player.SyncSettings(); + originalSpeed.Remove(player.PlayerId); + } + freezeTimer += Time.fixedDeltaTime; + } + if (freezeTimer >= RandomFreezeDuration.GetFloat()) + { + Main.AllPlayerSpeed[randPlayer] = originalSpeed[randPlayer]; + GetPlayerById(randPlayer).SyncSettings(); + originalSpeed.Remove(randPlayer); + Main.AllPlayerSpeed[player.PlayerId] = originalSpeed[player.PlayerId]; + player.SyncSettings(); + originalSpeed.Remove(player.PlayerId); + isFreezing = false; + freezeTimer = 0; + } + } } public override void OnPlayerExiled(PlayerControl player, NetworkedPlayerInfo exiled) { diff --git a/Roles/Coven/VoodooMaster.cs b/Roles/Coven/VoodooMaster.cs index e69de29bb..93b3a6719 100644 --- a/Roles/Coven/VoodooMaster.cs +++ b/Roles/Coven/VoodooMaster.cs @@ -0,0 +1,122 @@ +using TOHE.Roles.Core; +using static TOHE.Options; +using static TOHE.Translator; +using static TOHE.Utils; +using Hazel; +using InnerNet; +using UnityEngine; + +namespace TOHE.Roles.Coven; + +internal class VoodooMaster : CovenManager +{ + //===========================SETUP================================\\ + private const int Id = 30700; + public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.VoodooMaster); + public override bool IsDesyncRole => true; + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; + //==================================================================\\ + + private static OptionItem VoodooCooldown; + private static OptionItem VoodoosPerRound; + private static OptionItem CanDollCoven; + private static OptionItem NecroAbilityCanKillCov; + + public static readonly Dictionary> Dolls = []; + + public override void SetupCustomOption() + { + SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.VoodooMaster, 1, zeroOne: false); + VoodooCooldown = FloatOptionItem.Create(Id + 10, "VoodooCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.VoodooMaster]) + .SetValueFormat(OptionFormat.Seconds); + VoodoosPerRound = IntegerOptionItem.Create(Id + 11, "VoodooMasterPerRound", new(1, 15, 1), 1, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.VoodooMaster]) + .SetValueFormat(OptionFormat.Players); + CanDollCoven = BooleanOptionItem.Create(Id + 12, "VoodooMasterCanDollCoven", true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.VoodooMaster]); + NecroAbilityCanKillCov = BooleanOptionItem.Create(Id + 13, "VoodooMasterNecroCanKillCov", true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.VoodooMaster]); + } + public override void Init() + { + Dolls.Clear(); + } + public override void Add(byte PlayerId) + { + Dolls[PlayerId] = []; + AbilityLimit = VoodoosPerRound.GetInt(); + GetPlayerById(PlayerId)?.AddDoubleTrigger(); + } + + private void SendRPC(PlayerControl player, PlayerControl target) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + writer.WriteNetObject(_Player); + writer.Write(player.PlayerId); + writer.Write(AbilityLimit); + writer.Write(target.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) + { + byte VMId = reader.ReadByte(); + byte DollId = reader.ReadByte(); + Dolls[VMId].Add(DollId); + } + public override bool CanUseKillButton(PlayerControl pc) => true; + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = VoodooCooldown.GetFloat(); + public override void SetAbilityButtonText(HudManager hud, byte playerId) => + hud.KillButton.OverrideText(GetString("ShamanButtonText")); + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) + => Dolls[seer.PlayerId].Contains(seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.VoodooMaster), "✂") : string.Empty; + public override string GetProgressText(byte playerId, bool comms) + => ColorString(AbilityLimit >= 1 ? GetRoleColor(CustomRoles.VoodooMaster).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); + public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + return killer.CheckDoubleTrigger(target, () => { + if (AbilityLimit > 0 && (!target.IsPlayerCoven() || (target.IsPlayerCoven() && CanDollCoven.GetBool()))) + { + Dolls[killer.PlayerId].Add(target.PlayerId); + AbilityLimit--; + SendRPC(killer, target); + killer.RpcGuardAndKill(target); + killer.Notify(string.Format(GetString("VoodooMasterDolledSomeone"), target.GetRealName())); + if (HasNecronomicon(killer)) ReportDeadBodyPatch.CanReport[target.PlayerId] = false; + } + else if (target.IsPlayerCoven() && CanDollCoven.GetBool()) killer.Notify(GetString("VoodooMasterNoDollCoven")); + else killer.Notify(GetString("VoodooMasterNoDollsLeft")); + }); + } + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (Dolls[_Player.PlayerId].Count < 1) return true; + + PlayerControl ChoosenTarget = GetPlayerById(Dolls[target.PlayerId].Where(x => GetPlayerById(x).IsAlive()).ToList().RandomElement()); + + if (killer.CheckForInvalidMurdering(ChoosenTarget) && killer.RpcCheckAndMurder(ChoosenTarget, check: true)) + { + killer.RpcMurderPlayer(ChoosenTarget); + ChoosenTarget.SetRealKiller(_Player); + } + else + { + _Player.Notify(GetString("Shaman_KillerCannotMurderChosenTarget"), time: 10f); + } + Dolls[_Player.PlayerId].Remove(ChoosenTarget.PlayerId); + return false; + } + public override void AfterMeetingTasks() + { + Dolls[_Player.PlayerId].Clear(); + AbilityLimit = VoodoosPerRound.GetInt(); + } + public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) + { + if (!Dolls[_Player.PlayerId].Contains(target.PlayerId)) return false; + if (!HasNecronomicon(_Player)) return false; + if (!killer.IsPlayerCoven() || (killer.IsPlayerCoven() && NecroAbilityCanKillCov.GetBool())) { + killer.SetDeathReason(PlayerState.DeathReason.Sacrifice); + killer.RpcMurderPlayer(killer); + killer.SetRealKiller(target); + } + return false; + } +} diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 47592aff0..fd77ea6f1 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -20,6 +20,8 @@ internal class Altruist : RoleBase private static OptionItem ImpostorsCanGetsArrow; private static OptionItem NeutralKillersCanGetsAlert; private static OptionItem NeutralKillersCanGetsArrow; + private static OptionItem CovenCanGetsAlert; + private static OptionItem CovenCanGetsArrow; private bool IsRevivingMode = true; private byte RevivedPlayerId = byte.MaxValue; @@ -38,6 +40,10 @@ public override void SetupCustomOption() .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Altruist]); NeutralKillersCanGetsArrow = BooleanOptionItem.Create(Id + 14, "Altruist_NeutralKillersCanGetsArrow", true, TabGroup.CrewmateRoles, false) .SetParent(NeutralKillersCanGetsAlert); + CovenCanGetsAlert = BooleanOptionItem.Create(Id + 15, "Altruist_CovenCanGetsAlert", true, TabGroup.CrewmateRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Altruist]); + CovenCanGetsArrow = BooleanOptionItem.Create(Id + 16, "Altruist_CovenCanGetsArrow", true, TabGroup.CrewmateRoles, false) + .SetParent(CovenCanGetsAlert); } public override void Init() @@ -120,7 +126,14 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay if (NeutralKillersCanGetsArrow.GetBool()) getArrow = true; } - + if (CovenCanGetsAlert.GetBool() && pc.Is(Custom_Team.Coven) && pc.PlayerId != RevivedPlayerId) + { + getAlert = true; + + if (CovenCanGetsArrow.GetBool()) + getArrow = true; + } + if (getAlert) { pc.KillFlash(playKillSound: false); @@ -162,7 +175,7 @@ public override string GetSuffixOthers(PlayerControl seer, PlayerControl target, } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { - if (!(ImpostorsCanGetsArrow.GetBool() || NeutralKillersCanGetsArrow.GetBool()) || RevivedPlayerId == byte.MaxValue) return; + if (!(ImpostorsCanGetsArrow.GetBool() || NeutralKillersCanGetsArrow.GetBool() || CovenCanGetsArrow.GetBool()) || RevivedPlayerId == byte.MaxValue) return; foreach (var pc in Main.AllAlivePlayerControls) { @@ -176,6 +189,11 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf TargetArrow.Remove(pc.PlayerId, RevivedPlayerId); continue; } + if (CovenCanGetsArrow.GetBool() && pc.Is(Custom_Team.Coven)) + { + TargetArrow.Remove(pc.PlayerId, RevivedPlayerId); + continue; + } } } diff --git a/Roles/Crewmate/Captain.cs b/Roles/Crewmate/Captain.cs index e3480bcb8..2bc1141bf 100644 --- a/Roles/Crewmate/Captain.cs +++ b/Roles/Crewmate/Captain.cs @@ -27,6 +27,7 @@ internal class Captain : RoleBase private static OptionItem CaptainCanTargetNE; private static OptionItem CaptainCanTargetNK; private static OptionItem CaptainCanTargetNA; + private static OptionItem CaptainCanTargetCoven; private static readonly Dictionary OriginalSpeed = []; private static readonly Dictionary> CaptainVoteTargets = []; @@ -47,6 +48,7 @@ public override void SetupCustomOption() CaptainCanTargetNE = BooleanOptionItem.Create(Id + 19, "CaptainCanTargetNE", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); CaptainCanTargetNK = BooleanOptionItem.Create(Id + 20, "CaptainCanTargetNK", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); CaptainCanTargetNA = BooleanOptionItem.Create(Id + 21, "CaptainCanTargetNA", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); + CaptainCanTargetCoven = BooleanOptionItem.Create(Id + 23, "CaptainCanTargetCoven", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); OverrideTasksData.Create(Id + 22, TabGroup.CrewmateRoles, CustomRoles.Captain); } @@ -79,7 +81,9 @@ public override bool OnTaskComplete(PlayerControl pc, int completedTaskCount, in (CaptainCanTargetNE.GetBool() && x.GetCustomRole().IsNE()) || (CaptainCanTargetNC.GetBool() && x.GetCustomRole().IsNC()) || (CaptainCanTargetNK.GetBool() && x.GetCustomRole().IsNeutralKillerTeam()) - || (CaptainCanTargetNA.GetBool() && x.GetCustomRole().IsNA()))).ToList(); + || (CaptainCanTargetNA.GetBool() && x.GetCustomRole().IsNA()) || + (CaptainCanTargetCoven.GetBool() && x.GetCustomRole().IsCoven()) + )).ToList(); Logger.Info($"Total Number of Potential Target {allTargets.Count}", "Total Captain Target"); if (allTargets.Count == 0) return true; diff --git a/Roles/Crewmate/Cleanser.cs b/Roles/Crewmate/Cleanser.cs index c2d584690..cdae9c8ad 100644 --- a/Roles/Crewmate/Cleanser.cs +++ b/Roles/Crewmate/Cleanser.cs @@ -1,4 +1,5 @@ using TOHE.Roles.Core; +using TOHE.Roles.Coven; using UnityEngine; using static TOHE.Options; using static TOHE.Translator; @@ -63,11 +64,21 @@ public override bool CheckVote(PlayerControl voter, PlayerControl target) } if (CleanserTarget[voter.PlayerId] != byte.MaxValue) return true; + bool targetIsVM = false; + if (target.Is(CustomRoles.VoodooMaster) && VoodooMaster.Dolls[target.PlayerId].Count > 0) + { + target = Utils.GetPlayerById(VoodooMaster.Dolls[target.PlayerId].Where(x => Utils.GetPlayerById(x).IsAlive()).ToList().RandomElement()); + Utils.SendMessage(string.Format(GetString("VoodooMasterTargetInMeeting"), target.GetRealName()), Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().PlayerId); + targetIsVM = true; + } + var targetName = target.GetRealName(); + if (targetIsVM) targetName = Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().GetRealName(); + AbilityLimit--; CleanserTarget[voter.PlayerId] = target.PlayerId; Logger.Info($"{voter.GetNameWithRole()} cleansed {target.GetNameWithRole()}", "Cleansed"); CleansedPlayers.Add(target.PlayerId); - Utils.SendMessage(string.Format(GetString("CleanserRemovedRole"), target.GetRealName()), voter.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cleanser), GetString("CleanserTitle"))); + Utils.SendMessage(string.Format(GetString("CleanserRemovedRole"), targetName), voter.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cleanser), GetString("CleanserTitle"))); SendSkillRPC(); return false; } diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index 5aad19da9..47c265a4e 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -127,6 +127,10 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr CustomRoles.Baker when Baker.CurrentBread() is 0 => CustomRoles.Overseer, CustomRoles.Baker when Baker.CurrentBread() is 1 => CustomRoles.Deputy, CustomRoles.Baker when Baker.CurrentBread() is 2 => CustomRoles.Medic, + CustomRoles.Sacrifist => CustomRoles.Alchemist, + CustomRoles.MoonDancer => CustomRoles.Merchant, + CustomRoles.Ritualist => CustomRoles.Admirer, + CustomRoles.Illusionist => CustomRolesHelper.AllRoles.Where(role => role.IsEnable() && !role.IsAdditionRole() && role.IsCrewmate() && !BlackList(role)).ToList().RandomElement(), _ => role }; } @@ -148,6 +152,7 @@ CustomRoles.Baker when Baker.CurrentBread() is 2 => CustomRoles.Medic, if (target.Is(CustomRoles.Recruit)) killer.RpcSetCustomRole(CustomRoles.Recruit); if (target.Is(CustomRoles.Contagious)) killer.RpcSetCustomRole(CustomRoles.Contagious); if (target.Is(CustomRoles.Soulless)) killer.RpcSetCustomRole(CustomRoles.Soulless); + if (target.Is(CustomRoles.Enchanted)) killer.RpcSetCustomRole(CustomRoles.Enchanted); } killer.RpcGuardAndKill(killer); killer.Notify(string.Format(GetString("CopyCatRoleChange"), Utils.GetRoleName(role))); diff --git a/Roles/Crewmate/Enigma.cs b/Roles/Crewmate/Enigma.cs index f1225c728..bcf7e5e31 100644 --- a/Roles/Crewmate/Enigma.cs +++ b/Roles/Crewmate/Enigma.cs @@ -452,11 +452,13 @@ public override string GetMessage(PlayerControl killer, bool showStageClue) case 1: if (role.IsImpostor()) return GetString("EnigmaClueRole1"); if (role.IsNeutral()) return GetString("EnigmaClueRole2"); + if (role.IsCoven()) return GetString("EnigmaClueRole5"); return GetString("EnigmaClueRole3"); case 2: if (showStageClue) return string.Format(GetString("EnigmaClueRole4"), killer.GetDisplayRoleAndSubName(killer, false)); if (role.IsImpostor()) return GetString("EnigmaClueRole1"); if (role.IsNeutral()) return GetString("EnigmaClueRole2"); + if (role.IsCoven()) return GetString("EnigmaClueRole5"); return GetString("EnigmaClueRole3"); } diff --git a/Roles/Crewmate/FortuneTeller.cs b/Roles/Crewmate/FortuneTeller.cs index b1c4c1e3e..323114eef 100644 --- a/Roles/Crewmate/FortuneTeller.cs +++ b/Roles/Crewmate/FortuneTeller.cs @@ -3,6 +3,7 @@ using System; using System.Text; using TOHE.Roles.Core; +using TOHE.Roles.Coven; using UnityEngine; using static TOHE.Options; using static TOHE.Translator; @@ -132,10 +133,24 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) if ((player.AllTasksCompleted() || AccurateCheckMode.GetBool()) && ShowSpecificRole.GetBool()) { - msg = string.Format(GetString("FortuneTellerCheck.TaskDone"), target.GetRealName(), GetString(target.GetCustomRole().ToString())); + if (target.Is(CustomRoles.VoodooMaster) && VoodooMaster.Dolls[target.PlayerId].Count > 0) + { + var realTarget = GetPlayerById(VoodooMaster.Dolls[target.PlayerId].Where(x => GetPlayerById(x).IsAlive()).ToList().RandomElement()); + SendMessage(string.Format(GetString("VoodooMasterTargetInMeeting"), realTarget.GetRealName()), Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().PlayerId); + msg = string.Format(GetString("FortuneTellerCheck.TaskDone"), target.GetRealName(), GetString(realTarget.GetCustomRole().ToString())); + } + else + msg = string.Format(GetString("FortuneTellerCheck.TaskDone"), target.GetRealName(), GetString(target.GetCustomRole().ToString())); } else if (RandomActiveRoles.GetBool()) { + bool targetIsVM = false; + if (target.Is(CustomRoles.VoodooMaster) && VoodooMaster.Dolls[target.PlayerId].Count > 0) + { + target = GetPlayerById(VoodooMaster.Dolls[target.PlayerId].Where(x => GetPlayerById(x).IsAlive()).ToList().RandomElement()); + SendMessage(string.Format(GetString("VoodooMasterTargetInMeeting"), target.GetRealName()), Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().PlayerId); + targetIsVM = true; + } targetList.Add(target.PlayerId); var targetRole = target.GetCustomRole(); var activeRoleList = CustomRolesHelper.AllRoles.Where(role => (role.IsEnable() || role.RoleExist(countDead: true)) && role != targetRole && !role.IsAdditionRole()).ToList(); @@ -154,11 +169,13 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) (roleList[j], roleList[i]) = (roleList[i], roleList[j]); } var text = GetTargetRoleList([.. roleList]); + var targetName = target.GetRealName(); + if (targetIsVM) targetName = Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().GetRealName(); msg = string.Format(GetString("FortuneTellerCheck.Result"), target.GetRealName(), text); } else { - List completeRoleList = EnumHelper.Achunk(chunkSize: 6, shuffle: true, exclude: (x) => !x.IsGhostRole() && !x.IsAdditionRole() && !x.IsVanilla() && x is not CustomRoles.NotAssigned and not CustomRoles.ChiefOfPolice and not CustomRoles.Killer and not CustomRoles.GM); + List completeRoleList = EnumHelper.Achunk(chunkSize: 6, shuffle: true, exclude: (x) => !x.IsGhostRole() && !x.IsAdditionRole() && !x.IsVanilla() && x is not CustomRoles.NotAssigned and not CustomRoles.ChiefOfPolice and not CustomRoles.Killer and not CustomRoles.GM and not CustomRoles.Apocalypse and not CustomRoles.Coven); var targetRole = target.GetCustomRole(); string text = string.Empty; diff --git a/Roles/Crewmate/Inspector.cs b/Roles/Crewmate/Inspector.cs index b85f5f6ed..21c633090 100644 --- a/Roles/Crewmate/Inspector.cs +++ b/Roles/Crewmate/Inspector.cs @@ -9,6 +9,7 @@ using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; +using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Crewmate; internal class Inspector : RoleBase @@ -169,7 +170,29 @@ public static bool InspectCheckMsg(PlayerControl pc, string msg, bool isUI = fal return true; } var target1 = GetPlayerById(targetId1); + // Voodoo Master Check 1 + bool target1IsVM = false; + if (target1.Is(CustomRoles.VoodooMaster) && VoodooMaster.Dolls[target1.PlayerId].Count > 0) + { + target1 = Utils.GetPlayerById(VoodooMaster.Dolls[target1.PlayerId].Where(x => Utils.GetPlayerById(x).IsAlive()).ToList().RandomElement()); + Utils.SendMessage(string.Format(GetString("VoodooMasterTargetInMeeting"), target1.GetRealName()), Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().PlayerId); + target1IsVM = true; + } + var target1Name = target1.GetRealName(); + if (target1IsVM) target1Name = Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().GetRealName(); + var target2 = GetPlayerById(targetId2); + // Voodoo Master Check 1 + bool target2IsVM = false; + if (target2.Is(CustomRoles.VoodooMaster) && VoodooMaster.Dolls[target1.PlayerId].Count > 0) + { + target2 = Utils.GetPlayerById(VoodooMaster.Dolls[target2.PlayerId].Where(x => Utils.GetPlayerById(x).IsAlive()).ToList().RandomElement()); + Utils.SendMessage(string.Format(GetString("VoodooMasterTargetInMeeting"), target2.GetRealName()), Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().PlayerId); + target2IsVM = true; + } + var target2Name = target2.GetRealName(); + if (target2IsVM) target2Name = Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().GetRealName(); + if (target1 != null && target2 != null) { Logger.Info($"{pc.GetNameWithRole()} checked {target1.GetNameWithRole()} and {target2.GetNameWithRole()}", "Inspector"); @@ -250,21 +273,21 @@ public static bool InspectCheckMsg(PlayerControl pc, string msg, bool isUI = fal { _ = new LateTask(() => { - pc.ShowInfoMessage(isUI, string.Format(GetString("InspectCheckFalse"), target1.GetRealName(), target2.GetRealName()), ColorString(GetRoleColor(CustomRoles.Inspector), GetString("InspectCheckTitle"))); + pc.ShowInfoMessage(isUI, string.Format(GetString("InspectCheckFalse"), target1Name, target2Name), ColorString(GetRoleColor(CustomRoles.Inspector), GetString("InspectCheckTitle"))); Logger.Msg("Check attempt, result FALSE", "Inspector"); }, 0.2f, "Inspector Msg 6"); } if (InspectCheckTargetKnow.GetBool()) { - string textToSend = $"{target1.GetRealName()}"; + string textToSend = $"{target1Name}"; if (InspectCheckOtherTargetKnow.GetBool()) - textToSend += $" and {target2.GetRealName()}"; + textToSend += $" and {target2Name}"; textToSend += GetString("InspectCheckTargetMsg"); - string textToSend1 = $"{target2.GetRealName()}"; + string textToSend1 = $"{target2Name}"; if (InspectCheckOtherTargetKnow.GetBool()) - textToSend1 += $" and {target1.GetRealName()}"; + textToSend1 += $" and {target1Name}"; textToSend1 += GetString("InspectCheckTargetMsg"); _ = new LateTask(() => { @@ -289,8 +312,8 @@ public static bool InspectCheckMsg(PlayerControl pc, string msg, bool isUI = fal _ = new LateTask(() => { - SendMessage(string.Format(GetString("InspectorTargetReveal"), target2.GetRealName(), roleT2), target1.PlayerId, ColorString(GetRoleColor(CustomRoles.Inspector), GetString("InspectCheckTitle"))); - SendMessage(string.Format(GetString("InspectorTargetReveal"), target1.GetRealName(), roleT1), target2.PlayerId, ColorString(GetRoleColor(CustomRoles.Inspector), GetString("InspectCheckTitle"))); + SendMessage(string.Format(GetString("InspectorTargetReveal"), target2Name, roleT2), target1.PlayerId, ColorString(GetRoleColor(CustomRoles.Inspector), GetString("InspectCheckTitle"))); + SendMessage(string.Format(GetString("InspectorTargetReveal"), target1Name, roleT1), target2.PlayerId, ColorString(GetRoleColor(CustomRoles.Inspector), GetString("InspectCheckTitle"))); Logger.Msg($"check attempt, target1 notified target2 as {roleT2} and target2 notified target1 as {roleT1}", "Inspector"); }, 0.3f, "Inspector Msg 8"); } diff --git a/Roles/Crewmate/Jailer.cs b/Roles/Crewmate/Jailer.cs index 09c9f9e7a..e68facf45 100644 --- a/Roles/Crewmate/Jailer.cs +++ b/Roles/Crewmate/Jailer.cs @@ -24,6 +24,7 @@ internal class Jailer : RoleBase private static OptionItem NECanBeExe; private static OptionItem NKCanBeExe; private static OptionItem NACanBeExe; + private static OptionItem CovenCanBeExe; private static OptionItem CKCanBeExe; private static OptionItem NotifyJailedOnMeetingOpt; @@ -44,6 +45,7 @@ public override void SetupCustomOption() NECanBeExe = BooleanOptionItem.Create(Id + 14, "JailerNECanBeExe", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jailer]); NKCanBeExe = BooleanOptionItem.Create(Id + 15, "JailerNKCanBeExe", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jailer]); NACanBeExe = BooleanOptionItem.Create(Id + 17, "JailerNACanBeExe", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jailer]); + CovenCanBeExe = BooleanOptionItem.Create(Id + 19, "JailerCovenCanBeExe", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jailer]); CKCanBeExe = BooleanOptionItem.Create(Id + 16, "JailerCKCanBeExe", false, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jailer]); NotifyJailedOnMeetingOpt = BooleanOptionItem.Create(Id + 18, "notifyJailedOnMeeting", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jailer]); } @@ -185,6 +187,7 @@ private static bool CanBeExecuted(CustomRoles role) (role.IsNE() && NECanBeExe.GetBool()) || (role.IsNK() && NKCanBeExe.GetBool()) || (role.IsNA() && NACanBeExe.GetBool()) || + (role.IsCoven() && CovenCanBeExe.GetBool()) || (role.IsCrewKiller() && CKCanBeExe.GetBool()) || role.IsImpostorTeamV3(); } diff --git a/Roles/Crewmate/Judge.cs b/Roles/Crewmate/Judge.cs index 39ec8fc33..b23e12137 100644 --- a/Roles/Crewmate/Judge.cs +++ b/Roles/Crewmate/Judge.cs @@ -8,6 +8,7 @@ using static TOHE.Translator; using TOHE.Roles.Core; using System.Text; +using TOHE.Roles.Coven; namespace TOHE.Roles.Crewmate; @@ -30,12 +31,14 @@ internal class Judge : RoleBase private static OptionItem CanTrialSidekick; private static OptionItem CanTrialInfected; private static OptionItem CanTrialContagious; + private static OptionItem CanTrialEnchanted; private static OptionItem CanTrialCrewKilling; private static OptionItem CanTrialNeutralB; private static OptionItem CanTrialNeutralK; private static OptionItem CanTrialNeutralE; private static OptionItem CanTrialNeutralC; private static OptionItem CanTrialNeutralA; + private static OptionItem CanTrialCoven; private static readonly Dictionary TrialLimitMeeting = []; @@ -51,12 +54,14 @@ public override void SetupCustomOption() CanTrialSidekick = BooleanOptionItem.Create(Id + 19, "JudgeCanTrialSidekick", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); CanTrialInfected = BooleanOptionItem.Create(Id + 20, "JudgeCanTrialInfected", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); CanTrialContagious = BooleanOptionItem.Create(Id + 21, "JudgeCanTrialContagious", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); + CanTrialEnchanted = BooleanOptionItem.Create(Id + 24, "JudgeCanTrialEnchanted", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); CanTrialCrewKilling = BooleanOptionItem.Create(Id + 13, "JudgeCanTrialnCrewKilling", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); CanTrialNeutralB = BooleanOptionItem.Create(Id + 14, "JudgeCanTrialNeutralB", false, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); CanTrialNeutralE = BooleanOptionItem.Create(Id + 17, "JudgeCanTrialNeutralE", false, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); CanTrialNeutralC = BooleanOptionItem.Create(Id + 18, "JudgeCanTrialNeutralC", false, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); CanTrialNeutralK = BooleanOptionItem.Create(Id + 15, "JudgeCanTrialNeutralK", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); CanTrialNeutralA = BooleanOptionItem.Create(Id + 22, "JudgeCanTrialNeutralA", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); + CanTrialCoven = BooleanOptionItem.Create(Id + 23, "JudgeCanTrialCoven", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); TryHideMsg = BooleanOptionItem.Create(Id + 11, "JudgeTryHideMsg", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]) .SetColor(Color.green); } @@ -139,6 +144,11 @@ public bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) { pc.ShowInfoMessage(isUI, GetString("JudgeTrialMaxGameMsg")); } + if (target.Is(CustomRoles.VoodooMaster) && VoodooMaster.Dolls[target.PlayerId].Count > 0) + { + target = GetPlayerById(VoodooMaster.Dolls[target.PlayerId].Where(x => GetPlayerById(x).IsAlive()).ToList().RandomElement()); + SendMessage(string.Format(GetString("VoodooMasterTargetInMeeting"), target.GetRealName()), Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().PlayerId); + } if (Jailer.IsTarget(target.PlayerId)) { pc.ShowInfoMessage(isUI, GetString("CanNotTrialJailed"), ColorString(GetRoleColor(CustomRoles.Jailer), GetString("JailerTitle"))); @@ -189,12 +199,14 @@ public bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) else if (target.Is(CustomRoles.Infected) && CanTrialInfected.GetBool()) judgeSuicide = false; else if (target.Is(CustomRoles.Contagious) && CanTrialContagious.GetBool()) judgeSuicide = false; else if (target.Is(CustomRoles.Charmed) && CanTrialCharmed.GetBool()) judgeSuicide = false; + else if (target.Is(CustomRoles.Enchanted) && CanTrialEnchanted.GetBool()) judgeSuicide = false; else if (target.GetCustomRole().IsCrewKiller() && CanTrialCrewKilling.GetBool()) judgeSuicide = false; else if (target.GetCustomRole().IsNK() && CanTrialNeutralK.GetBool()) judgeSuicide = false; else if (target.GetCustomRole().IsNB() && CanTrialNeutralB.GetBool()) judgeSuicide = false; else if (target.GetCustomRole().IsNE() && CanTrialNeutralE.GetBool()) judgeSuicide = false; else if (target.GetCustomRole().IsNC() && CanTrialNeutralC.GetBool()) judgeSuicide = false; else if (target.GetCustomRole().IsNA() && CanTrialNeutralA.GetBool()) judgeSuicide = false; + else if (target.GetCustomRole().IsCoven() && CanTrialCoven.GetBool()) judgeSuicide = false; else if (target.GetCustomRole().IsImpostor()) judgeSuicide = false; else { diff --git a/Roles/Crewmate/Merchant.cs b/Roles/Crewmate/Merchant.cs index 9135cd144..72d95703e 100644 --- a/Roles/Crewmate/Merchant.cs +++ b/Roles/Crewmate/Merchant.cs @@ -26,6 +26,7 @@ internal class Merchant : RoleBase private static OptionItem OptionCanTargetCrew; private static OptionItem OptionCanTargetImpostor; private static OptionItem OptionCanTargetNeutral; + private static OptionItem OptionCanTargetCoven; private static OptionItem OptionCanSellHelpful; private static OptionItem OptionCanSellHarmful; private static OptionItem OptionCanSellNeutral; @@ -48,6 +49,7 @@ public override void SetupCustomOption() OptionCanTargetCrew = BooleanOptionItem.Create(Id + 6, "MerchantTargetCrew", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); OptionCanTargetImpostor = BooleanOptionItem.Create(Id + 7, "MerchantTargetImpostor", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); OptionCanTargetNeutral = BooleanOptionItem.Create(Id + 8, "MerchantTargetNeutral", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); + OptionCanTargetCoven = BooleanOptionItem.Create(Id + 16, "MerchantTargetCoven", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); OptionCanSellHelpful = BooleanOptionItem.Create(Id + 9, "MerchantSellHelpful", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); OptionCanSellHarmful = BooleanOptionItem.Create(Id + 10, "MerchantSellHarmful", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); OptionCanSellNeutral = BooleanOptionItem.Create(Id + 11, "MerchantSellMixed", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); @@ -138,6 +140,8 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount (OptionCanTargetImpostor.GetBool() && x.GetCustomRole().IsImpostor()) || (OptionCanTargetNeutral.GetBool() && x.GetCustomRole().IsNeutral()) + || + (OptionCanTargetCoven.GetBool() && x.GetCustomRole().IsCoven()) ) ).ToList(); @@ -157,7 +161,9 @@ public override bool OnTaskComplete(PlayerControl player, int completedTaskCount a.GetCustomRole().IsImpostor() || a.GetCustomRole().IsNeutral() - + || + a.GetCustomRole().IsCoven() + ).ToList(); } diff --git a/Roles/Crewmate/Oracle.cs b/Roles/Crewmate/Oracle.cs index e2ca1000d..2c882b608 100644 --- a/Roles/Crewmate/Oracle.cs +++ b/Roles/Crewmate/Oracle.cs @@ -102,7 +102,15 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) string msg; { - + bool targetIsVM = false; + if (target.Is(CustomRoles.VoodooMaster) && VoodooMaster.Dolls[target.PlayerId].Count > 0) + { + target = Utils.GetPlayerById(VoodooMaster.Dolls[target.PlayerId].Where(x => Utils.GetPlayerById(x).IsAlive()).ToList().RandomElement()); + Utils.SendMessage(string.Format(GetString("VoodooMasterTargetInMeeting"), target.GetRealName()), Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().PlayerId); + targetIsVM = true; + } + var targetName = target.GetRealName(); + if (targetIsVM) targetName = Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().GetRealName(); string text = "Crewmate"; if (ChangeRecruitTeam.GetBool()) { @@ -116,8 +124,11 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) } else { - if (target.Is(Custom_Team.Impostor) && !target.Is(CustomRoles.Trickster)) text = "Impostor"; + if (Illusionist.IsCovIllusioned(target.PlayerId)) text = "Crewmate"; + else if (Illusionist.IsNonCovIllusioned(target.PlayerId)) text = "Coven"; + else if (target.Is(Custom_Team.Impostor) && !target.Is(CustomRoles.Trickster)) text = "Impostor"; else if (target.GetCustomRole().IsNeutral()) text = "Neutral"; + else if (target.Is(Custom_Team.Coven)) text = "Coven"; else text = "Crewmate"; } @@ -126,25 +137,34 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) int random_number_1 = IRandom.Instance.Next(1, 100); if (random_number_1 <= FailChance.GetInt()) { - int random_number_2 = IRandom.Instance.Next(1, 3); + int random_number_2 = IRandom.Instance.Next(1, 4); if (text == "Crewmate") { if (random_number_2 == 1) text = "Neutral"; if (random_number_2 == 2) text = "Impostor"; + if (random_number_2 == 3) text = "Coven"; } if (text == "Neutral") { if (random_number_2 == 1) text = "Crewmate"; if (random_number_2 == 2) text = "Impostor"; + if (random_number_2 == 3) text = "Coven"; } if (text == "Impostor") { if (random_number_2 == 1) text = "Neutral"; if (random_number_2 == 2) text = "Crewmate"; + if (random_number_2 == 3) text = "Coven"; + } + if (text == "Coven") + { + if (random_number_2 == 1) text = "Crewmate"; + if (random_number_2 == 2) text = "Impostor"; + if (random_number_2 == 3) text = "Neutral"; } } } - msg = string.Format(GetString("OracleCheck." + text), target.GetRealName()); + msg = string.Format(GetString("OracleCheck." + text), targetName); } SendMessage(GetString("OracleCheck") + "\n" + msg + "\n\n" + string.Format(GetString("OracleCheckLimit"), AbilityLimit), player.PlayerId, ColorString(GetRoleColor(CustomRoles.Oracle), GetString("OracleCheckMsgTitle"))); diff --git a/Roles/Crewmate/President.cs b/Roles/Crewmate/President.cs index 502d089ec..01baf07a3 100644 --- a/Roles/Crewmate/President.cs +++ b/Roles/Crewmate/President.cs @@ -22,6 +22,7 @@ internal class President : RoleBase private static OptionItem NeutralsSeePresident; private static OptionItem MadmatesSeePresident; private static OptionItem ImpsSeePresident; + private static OptionItem CovenSeePresident; private static readonly Dictionary EndLimit = []; private static readonly Dictionary RevealLimit = []; @@ -36,6 +37,7 @@ public override void SetupCustomOption() NeutralsSeePresident = BooleanOptionItem.Create(Id + 12, "NeutralsSeePresident", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.President]); MadmatesSeePresident = BooleanOptionItem.Create(Id + 13, "MadmatesSeePresident", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.President]); ImpsSeePresident = BooleanOptionItem.Create(Id + 14, "ImpsSeePresident", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.President]); + CovenSeePresident = BooleanOptionItem.Create(Id + 16, "CovenSeePresident", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.President]); HidePresidentEndCommand = BooleanOptionItem.Create(Id + 15, "HidePresidentEndCommand", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.President]); } public override void Init() @@ -164,6 +166,7 @@ public static bool EndMsg(PlayerControl pc, string msg) if (!MadmatesSeePresident.GetBool() && tar.Is(CustomRoles.Madmate) && tar != pc) continue; if (!NeutralsSeePresident.GetBool() && tar.GetCustomRole().IsNeutral()) continue; if (!ImpsSeePresident.GetBool() && (tar.GetCustomRole().IsImpostor() || tar.Is(CustomRoles.Crewpostor))) continue; + if (!CovenSeePresident.GetBool() && tar.GetCustomRole().IsCoven()) continue; Utils.SendMessage(string.Format(GetString("PresidentRevealed"), pc.GetRealName()), tar.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.President), GetString("PresidentRevealTitle"))); } SendRPC(pc.PlayerId, isEnd: false); @@ -237,6 +240,7 @@ public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => (target.Is(CustomRoles.President) && seer.GetCustomRole().IsCrewmate() && !seer.Is(CustomRoles.Madmate) && CheckPresidentReveal[target.PlayerId] == true) || (target.Is(CustomRoles.President) && seer.Is(CustomRoles.Madmate) && MadmatesSeePresident.GetBool() && CheckPresidentReveal[target.PlayerId] == true) || (target.Is(CustomRoles.President) && seer.GetCustomRole().IsNeutral() && NeutralsSeePresident.GetBool() && CheckPresidentReveal[target.PlayerId] == true) || + (target.Is(CustomRoles.President) && seer.GetCustomRole().IsCoven() && CovenSeePresident.GetBool() && CheckPresidentReveal[target.PlayerId] == true) || (target.Is(CustomRoles.President) && seer.GetCustomRole().IsImpostor() && ImpsSeePresident.GetBool() && CheckPresidentReveal[target.PlayerId] == true); public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); diff --git a/Roles/Crewmate/Psychic.cs b/Roles/Crewmate/Psychic.cs index 864460b09..ff54176f3 100644 --- a/Roles/Crewmate/Psychic.cs +++ b/Roles/Crewmate/Psychic.cs @@ -25,6 +25,8 @@ internal class Psychic : RoleBase private static OptionItem NCshowEvil; private static OptionItem NAshowEvil; private static OptionItem NKshowEvil; + private static OptionItem CovshowEvil; + private readonly HashSet RedPlayer = []; @@ -40,6 +42,7 @@ public override void SetupCustomOption() NCshowEvil = BooleanOptionItem.Create(Id + 7, "Psychic_NCareRed", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); NAshowEvil = BooleanOptionItem.Create(Id + 8, "Psychic_NAareRed", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); NKshowEvil = BooleanOptionItem.Create(Id + 9, "Psychic_NKareRed", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); + CovshowEvil = BooleanOptionItem.Create(Id + 10, "Psychic_CovareRed", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); } public override void Init() { @@ -93,7 +96,8 @@ private void GetRedName() (x.GetCustomRole().IsNC() && NCshowEvil.GetBool()) || (x.GetCustomRole().IsNB() && NBshowEvil.GetBool()) || (x.GetCustomRole().IsNK() && NKshowEvil.GetBool()) || - (x.GetCustomRole().IsNA() && NAshowEvil.GetBool()) + (x.GetCustomRole().IsNA() && NAshowEvil.GetBool()) || + (x.GetCustomRole().IsCoven() && CovshowEvil.GetBool()) ).ToList(); var randomBadPlayer = BadListPc.RandomElement(); diff --git a/Roles/Crewmate/Sheriff.cs b/Roles/Crewmate/Sheriff.cs index 81b31c093..8627e7dd7 100644 --- a/Roles/Crewmate/Sheriff.cs +++ b/Roles/Crewmate/Sheriff.cs @@ -20,6 +20,7 @@ internal class Sheriff : RoleBase private static OptionItem ShotLimitOpt; private static OptionItem ShowShotLimit; private static OptionItem CanKillAllAlive; + private static OptionItem CanKillCoven; private static OptionItem CanKillNeutrals; private static OptionItem CanKillNeutralsMode; private static OptionItem CanKillMadmate; @@ -29,11 +30,13 @@ internal class Sheriff : RoleBase private static OptionItem CanKillEgoists; private static OptionItem CanKillInfected; private static OptionItem CanKillContagious; + private static OptionItem CanKillEnchanted; private static OptionItem SidekickSheriffCanGoBerserk; private static OptionItem SetNonCrewCanKill; private static OptionItem NonCrewCanKillCrew; private static OptionItem NonCrewCanKillImp; private static OptionItem NonCrewCanKillNeutral; + private static OptionItem NonCrewCanKillCoven; private float CurrentKillCooldown; @@ -62,14 +65,17 @@ public override void SetupCustomOption() CanKillEgoists = BooleanOptionItem.Create(Id + 25, "SheriffCanKillEgoist", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); CanKillInfected = BooleanOptionItem.Create(Id + 26, "SheriffCanKillInfected", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); CanKillContagious = BooleanOptionItem.Create(Id + 27, "SheriffCanKillContagious", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); + CanKillEnchanted = BooleanOptionItem.Create(Id + 30, "SheriffCanKillEnchanted", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); + CanKillCoven = BooleanOptionItem.Create(Id + 29, "SheriffCanKillCoven", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); CanKillNeutrals = BooleanOptionItem.Create(Id + 16, "SheriffCanKillNeutrals", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); CanKillNeutralsMode = StringOptionItem.Create(Id + 14, "SheriffCanKillNeutralsMode", EnumHelper.GetAllNames(), 0, TabGroup.CrewmateRoles, false).SetParent(CanKillNeutrals); - SetUpNeutralOptions(Id + 30); + SetUpNeutralOptions(Id + 32); SidekickSheriffCanGoBerserk = BooleanOptionItem.Create(Id + 28, "SidekickSheriffCanGoBerserk", true, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); SetNonCrewCanKill = BooleanOptionItem.Create(Id + 18, "SheriffSetMadCanKill", false, TabGroup.CrewmateRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); NonCrewCanKillImp = BooleanOptionItem.Create(Id + 19, "SheriffMadCanKillImp", true, TabGroup.CrewmateRoles, false).SetParent(SetNonCrewCanKill); NonCrewCanKillCrew = BooleanOptionItem.Create(Id + 21, "SheriffMadCanKillCrew", true, TabGroup.CrewmateRoles, false).SetParent(SetNonCrewCanKill); NonCrewCanKillNeutral = BooleanOptionItem.Create(Id + 20, "SheriffMadCanKillNeutral", true, TabGroup.CrewmateRoles, false).SetParent(SetNonCrewCanKill); + NonCrewCanKillCoven = BooleanOptionItem.Create(Id + 31, "SheriffMadCanKillCoven", true, TabGroup.CrewmateRoles, false).SetParent(SetNonCrewCanKill); } public override void Add(byte playerId) { @@ -111,7 +117,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t if ((CanBeKilledBySheriff(target) && !(SetNonCrewCanKill.GetBool() && killer.IsNonCrewSheriff() || SidekickSheriffCanGoBerserk.GetBool() && killer.Is(CustomRoles.Recruit))) || (SidekickSheriffCanGoBerserk.GetBool() && killer.Is(CustomRoles.Recruit)) || (SetNonCrewCanKill.GetBool() && killer.IsNonCrewSheriff() - && ((target.GetCustomRole().IsImpostor() && NonCrewCanKillImp.GetBool()) || (target.GetCustomRole().IsCrewmate() && NonCrewCanKillCrew.GetBool()) || (target.GetCustomRole().IsNeutral() && NonCrewCanKillNeutral.GetBool()))) + && ((target.GetCustomRole().IsImpostor() && NonCrewCanKillImp.GetBool()) || (target.GetCustomRole().IsCrewmate() && NonCrewCanKillCrew.GetBool()) || (target.GetCustomRole().IsNeutral() && NonCrewCanKillNeutral.GetBool()) || (target.GetCustomRole().IsCoven() && NonCrewCanKillCoven.GetBool()))) ) { killer.ResetKillCooldown(); @@ -148,6 +154,8 @@ public static bool CanBeKilledBySheriff(PlayerControl player) CanKill = CanKillInfected.GetBool(); if (SubRoleTarget == CustomRoles.Contagious) CanKill = CanKillContagious.GetBool(); + if (SubRoleTarget == CustomRoles.Enchanted) + CanKill = CanKillEnchanted.GetBool(); if (SubRoleTarget == CustomRoles.Rascal) CanKill = true; if (SubRoleTarget == CustomRoles.Admired) @@ -163,6 +171,7 @@ var r when cRole.IsTNA() => false, { Custom_Team.Impostor => true, Custom_Team.Neutral => CanKillNeutrals.GetBool() && (CanKillNeutralsMode.GetValue() == 0 || (!KillTargetOptions.TryGetValue(cRole, out var option) || option.GetBool())), + Custom_Team.Coven => CanKillCoven.GetBool(), _ => CanKill, } }; diff --git a/Roles/Crewmate/Snitch.cs b/Roles/Crewmate/Snitch.cs index 3a620841c..223b04a1f 100644 --- a/Roles/Crewmate/Snitch.cs +++ b/Roles/Crewmate/Snitch.cs @@ -24,6 +24,7 @@ internal class Snitch : RoleBase private static OptionItem OptionCanGetColoredArrow; private static OptionItem OptionCanFindNeutralKiller; private static OptionItem OptionCanFindNeutralApocalypse; + private static OptionItem OptionCanFindCoven; private static OptionItem OptionCanFindMadmate; private static OptionItem OptionRemainingTasks; @@ -31,6 +32,7 @@ internal class Snitch : RoleBase private static bool CanGetColoredArrow; private static bool CanFindNeutralKiller; private static bool CanFindNeutralApocalypse; + private static bool CanFindCoven; private static bool CanFindMadmate; private static int RemainingTasksToBeFound; @@ -47,6 +49,7 @@ public override void SetupCustomOption() OptionCanGetColoredArrow = BooleanOptionItem.Create(Id + 11, "SnitchCanGetArrowColor", true, TabGroup.CrewmateRoles, false).SetParent(OptionEnableTargetArrow); OptionCanFindNeutralKiller = BooleanOptionItem.Create(Id + 12, "SnitchCanFindNeutralKiller", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); OptionCanFindNeutralApocalypse = BooleanOptionItem.Create(Id + 15, "SnitchCanFindNeutralApoc", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); + OptionCanFindCoven = BooleanOptionItem.Create(Id + 16, "SnitchCanFindCoven", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); OptionCanFindMadmate = BooleanOptionItem.Create(Id + 14, "SnitchCanFindMadmate", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); OptionRemainingTasks = IntegerOptionItem.Create(Id + 13, "SnitchRemainingTaskFound", new(0, 10, 1), 1, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); OverrideTasksData.Create(Id + 20, TabGroup.CrewmateRoles, CustomRoles.Snitch); @@ -59,6 +62,7 @@ public override void Init() CanGetColoredArrow = OptionCanGetColoredArrow.GetBool(); CanFindNeutralKiller = OptionCanFindNeutralKiller.GetBool(); CanFindNeutralApocalypse = OptionCanFindNeutralApocalypse.GetBool(); + CanFindCoven = OptionCanFindCoven.GetBool(); CanFindMadmate = OptionCanFindMadmate.GetBool(); RemainingTasksToBeFound = OptionRemainingTasks.GetInt(); @@ -95,7 +99,7 @@ private static bool GetExpose(PlayerControl pc) } private static bool IsSnitchTarget(PlayerControl target) - => HasEnabled && (target.Is(Custom_Team.Impostor) && !target.Is(CustomRoles.Trickster) || (target.IsNeutralKiller() && CanFindNeutralKiller) || (target.IsNeutralApocalypse() && CanFindNeutralApocalypse)|| (target.Is(CustomRoles.Madmate) && CanFindMadmate) || (target.Is(CustomRoles.Rascal) && CanFindMadmate)); + => HasEnabled && (target.Is(Custom_Team.Impostor) && !target.Is(CustomRoles.Trickster) || (target.IsNeutralKiller() && CanFindNeutralKiller) || (target.IsNeutralApocalypse() && CanFindNeutralApocalypse) || (target.IsPlayerCoven() && CanFindCoven) || (target.Is(CustomRoles.Madmate) && CanFindMadmate) || (target.Is(CustomRoles.Rascal) && CanFindMadmate)); private void CheckTask(PlayerControl snitch) { diff --git a/Roles/Crewmate/Swapper.cs b/Roles/Crewmate/Swapper.cs index 54971de43..b1d19410f 100644 --- a/Roles/Crewmate/Swapper.cs +++ b/Roles/Crewmate/Swapper.cs @@ -8,6 +8,8 @@ using static TOHE.Translator; using static TOHE.CheckForEndVotingPatch; using static TOHE.Utils; +using static UnityEngine.GraphicsBuffer; +using TOHE.Roles.Coven; namespace TOHE.Roles.Crewmate; @@ -257,8 +259,18 @@ public void SwapVotes(MeetingHud __instance) if (!Vote.TryGetValue(pc.PlayerId, out var tid1) || !VoteTwo.TryGetValue(pc.PlayerId, out var tid2)) return; if (tid1 == 253 || tid2 == 253 || tid1 == tid2) return; - var target1 = Utils.GetPlayerById(tid1); - var target2 = Utils.GetPlayerById(tid2); + var target1 = GetPlayerById(tid1); + if (target1.Is(CustomRoles.VoodooMaster) && VoodooMaster.Dolls[target1.PlayerId].Count > 0) + { + target1 = GetPlayerById(VoodooMaster.Dolls[target1.PlayerId].Where(x => GetPlayerById(x).IsAlive()).ToList().RandomElement()); + SendMessage(string.Format(GetString("VoodooMasterTargetInMeeting"), target1.GetRealName()), Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().PlayerId); + } + var target2 = GetPlayerById(tid2); + if (target2.Is(CustomRoles.VoodooMaster) && VoodooMaster.Dolls[target2.PlayerId].Count > 0) + { + target2 = GetPlayerById(VoodooMaster.Dolls[target2.PlayerId].Where(x => GetPlayerById(x).IsAlive()).ToList().RandomElement()); + SendMessage(string.Format(GetString("VoodooMasterTargetInMeeting"), target2.GetRealName()), Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().PlayerId); + } if (target1 == null || target2 == null || !target1.IsAlive() || !target2.IsAlive()) return; diff --git a/Roles/Impostor/Councillor.cs b/Roles/Impostor/Councillor.cs index 50bca244d..66b2a8673 100644 --- a/Roles/Impostor/Councillor.cs +++ b/Roles/Impostor/Councillor.cs @@ -3,6 +3,7 @@ using System.Text.RegularExpressions; using TOHE.Modules.ChatManager; using TOHE.Roles.Core; +using TOHE.Roles.Coven; using TOHE.Roles.Crewmate; using TOHE.Roles.Double; using UnityEngine; @@ -120,6 +121,11 @@ public bool MurderMsg(PlayerControl pc, string msg, bool isUI = false) pc.ShowInfoMessage(isUI, GetString("CouncillorMurderMaxGame")); return true; } + if (target.Is(CustomRoles.VoodooMaster) && VoodooMaster.Dolls[target.PlayerId].Count > 0) + { + target = Utils.GetPlayerById(VoodooMaster.Dolls[target.PlayerId].Where(x => Utils.GetPlayerById(x).IsAlive()).ToList().RandomElement()); + Utils.SendMessage(string.Format(GetString("VoodooMasterTargetInMeeting"), target.GetRealName()), Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().PlayerId); + } if (Jailer.IsTarget(target.PlayerId)) { @@ -227,6 +233,7 @@ public bool MurderMsg(PlayerControl pc, string msg, bool isUI = false) } else if (target.GetCustomRole().IsCrewmate()) CouncillorSuicide = false; else if (target.GetCustomRole().IsNeutral()) CouncillorSuicide = false; + else if (target.GetCustomRole().IsCoven()) CouncillorSuicide = false; else { Logger.Warn("Impossibe to reach here!", "CouncillorTrial"); diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index 7388bb475..cb5022301 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -8,6 +8,7 @@ using static TOHE.Utils; using static TOHE.Options; using static TOHE.Translator; +using TOHE.Roles.Coven; namespace TOHE.Roles.Impostor; internal class DoubleAgent : RoleBase @@ -131,6 +132,12 @@ public override bool CheckVote(PlayerControl voter, PlayerControl target) if (target.Is(Custom_Team.Impostor)) return false; if (voter == target) return false; + if (target.Is(CustomRoles.VoodooMaster) && VoodooMaster.Dolls[target.PlayerId].Count > 0) + { + target = GetPlayerById(VoodooMaster.Dolls[target.PlayerId].Where(x => GetPlayerById(x).IsAlive()).ToList().RandomElement()); + SendMessage(string.Format(GetString("VoodooMasterTargetInMeeting"), target.GetRealName()), Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().PlayerId); + } + CurrentBombedTime = -1; CurrentBombedPlayers.Add(target.PlayerId); BombIsActive = true; diff --git a/Roles/Impostor/Eraser.cs b/Roles/Impostor/Eraser.cs index 0a19454d6..07d0bd009 100644 --- a/Roles/Impostor/Eraser.cs +++ b/Roles/Impostor/Eraser.cs @@ -61,7 +61,7 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) } var targetRole = target.GetCustomRole(); - if (targetRole.IsTasklessCrewmate() || targetRole.IsNeutral() || Main.TasklessCrewmate.Contains(target.PlayerId) || CopyCat.playerIdList.Contains(target.PlayerId) || target.Is(CustomRoles.Stubborn)) + if (targetRole.IsTasklessCrewmate() || targetRole.IsNeutral() || targetRole.IsCoven() || Main.TasklessCrewmate.Contains(target.PlayerId) || CopyCat.playerIdList.Contains(target.PlayerId) || target.Is(CustomRoles.Stubborn)) { Logger.Info($"Cannot erase role because is Impostor Based or Neutral or ect", "Eraser"); Utils.SendMessage(string.Format(GetString("EraserEraseBaseImpostorOrNeutralRoleNotice"), target.GetRealName()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Eraser), GetString("EraserEraseMsgTitle"))); diff --git a/Roles/Impostor/Visionary.cs b/Roles/Impostor/Visionary.cs index b1644b8e4..61127ddbd 100644 --- a/Roles/Impostor/Visionary.cs +++ b/Roles/Impostor/Visionary.cs @@ -53,6 +53,11 @@ or CustomRoles.Refugee return Main.roleColors[CustomRoles.Bait]; } + if (customRole.IsCoven() || customRole.Equals(CustomRoles.Enchanted)) + { + return Main.roleColors[CustomRoles.Coven]; + } + return Main.roleColors[CustomRoles.Knight]; } } diff --git a/Roles/Neutral/Amnesiac.cs b/Roles/Neutral/Amnesiac.cs index 4a0b25dd2..9c7980443 100644 --- a/Roles/Neutral/Amnesiac.cs +++ b/Roles/Neutral/Amnesiac.cs @@ -135,10 +135,11 @@ public override bool OnCheckReportDeadBody(PlayerControl __instance, NetworkedPl } if (tar.GetCustomRole().IsNA()) { - __instance.RpcSetCustomRole(tar.GetCustomRole()); - __instance.GetRoleClass().Add(__instance.PlayerId); - __instance.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("YouRememberedRole"))); - tar.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedYourRole"))); + tempRole = tar.GetCustomRole(); + } + if (tar.GetCustomRole().IsCoven()) + { + tempRole = tar.GetCustomRole(); } if (tar.GetCustomRole().IsAmneNK()) { @@ -168,6 +169,7 @@ public override bool OnCheckReportDeadBody(PlayerControl __instance, NetworkedPl if (tempRole != CustomRoles.Amnesiac) { __instance.GetRoleClass().OnRemove(__instance.PlayerId); + __instance.RpcChangeRoleBasis(tempRole); __instance.RpcSetCustomRole(tempRole); __instance.GetRoleClass().OnAdd(__instance.PlayerId); __instance.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("YouRememberedRole"))); diff --git a/Roles/Neutral/Doomsayer.cs b/Roles/Neutral/Doomsayer.cs index 3fb16a208..1c20518ac 100644 --- a/Roles/Neutral/Doomsayer.cs +++ b/Roles/Neutral/Doomsayer.cs @@ -28,6 +28,7 @@ internal class Doomsayer : RoleBase private static OptionItem DCanGuessImpostors; private static OptionItem DCanGuessCrewmates; private static OptionItem DCanGuessNeutrals; + private static OptionItem DCanGuessCoven; private static OptionItem DCanGuessAdt; private static OptionItem AdvancedSettings; private static OptionItem MaxNumberOfGuessesPerMeeting; @@ -49,6 +50,8 @@ public override void SetupCustomOption() .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); DCanGuessNeutrals = BooleanOptionItem.Create(Id + 14, "DCanGuessNeutrals", true, TabGroup.NeutralRoles, true) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + DCanGuessCoven = BooleanOptionItem.Create(Id + 26, "DCanGuessCoven", true, TabGroup.NeutralRoles, true) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); DCanGuessAdt = BooleanOptionItem.Create(Id + 15, "DCanGuessAdt", false, TabGroup.NeutralRoles, false) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); @@ -139,7 +142,8 @@ public static bool HideTabInGuesserUI(int TabId) if (!DCanGuessCrewmates.GetBool() && TabId == 0) return true; if (!DCanGuessImpostors.GetBool() && TabId == 1) return true; if (!DCanGuessNeutrals.GetBool() && TabId == 2) return true; - if (!DCanGuessAdt.GetBool() && TabId == 3) return true; + if (!DCanGuessCoven.GetBool() && TabId == 3) return true; + if (!DCanGuessAdt.GetBool() && TabId == 4) return true; return false; } @@ -167,6 +171,11 @@ public override bool GuessCheck(bool isUI, PlayerControl guesser, PlayerControl guesser.ShowInfoMessage(isUI, GetString("GuessNotAllowed")); return true; } + if (role.IsCoven() && !DCanGuessCoven.GetBool()) + { + guesser.ShowInfoMessage(isUI, GetString("GuessNotAllowed")); + return true; + } if (role.IsAdditionRole() && !DCanGuessAdt.GetBool()) { guesser.ShowInfoMessage(isUI, GetString("GuessAdtRole")); diff --git a/Roles/Neutral/Executioner.cs b/Roles/Neutral/Executioner.cs index 4d0d12b6c..6ce8028e8 100644 --- a/Roles/Neutral/Executioner.cs +++ b/Roles/Neutral/Executioner.cs @@ -22,6 +22,7 @@ internal class Executioner : RoleBase private static OptionItem CanTargetNeutralEvil; private static OptionItem CanTargetNeutralChaos; private static OptionItem CanTargetNeutralApocalypse; + private static OptionItem CanTargetCoven; private static OptionItem KnowTargetRole; private static OptionItem ChangeRolesAfterTargetKilled; private static OptionItem RevealExeTargetUponEjection; @@ -61,6 +62,7 @@ public override void SetupCustomOption() CanTargetNeutralEvil = BooleanOptionItem.Create(Id + 15, "ExecutionerCanTargetNeutralEvil", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); CanTargetNeutralChaos = BooleanOptionItem.Create(Id + 16, "ExecutionerCanTargetNeutralChaos", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); CanTargetNeutralApocalypse = BooleanOptionItem.Create(Id + 17, "ExecutionerCanTargetNeutralApocalypse", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); + CanTargetCoven = BooleanOptionItem.Create(Id + 18, "ExecutionerCanTargetCoven", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); KnowTargetRole = BooleanOptionItem.Create(Id + 13, "KnowTargetRole", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); ChangeRolesAfterTargetKilled = StringOptionItem.Create(Id + 11, "ExecutionerChangeRolesAfterTargetKilled", EnumHelper.GetAllNames(), 1, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); RevealExeTargetUponEjection = BooleanOptionItem.Create(Id + 18, "Executioner_RevealTargetUponEject", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); @@ -91,6 +93,7 @@ public override void Add(byte playerId) else if (!CanTargetNeutralBenign.GetBool() && target.GetCustomRole().IsNB()) continue; else if (!CanTargetNeutralEvil.GetBool() && target.GetCustomRole().IsNE()) continue; else if (!CanTargetNeutralChaos.GetBool() && target.GetCustomRole().IsNC()) continue; + else if (!CanTargetCoven.GetBool() && target.Is(Custom_Team.Coven)) continue; if (target.GetCustomRole() is CustomRoles.GM or CustomRoles.SuperStar or CustomRoles.NiceMini or CustomRoles.EvilMini) continue; if (executioner.Is(CustomRoles.Lovers) && target.Is(CustomRoles.Lovers)) continue; diff --git a/Roles/Neutral/Lawyer.cs b/Roles/Neutral/Lawyer.cs index 3023ed0f5..f8f4fe754 100644 --- a/Roles/Neutral/Lawyer.cs +++ b/Roles/Neutral/Lawyer.cs @@ -19,6 +19,7 @@ internal class Lawyer : RoleBase private static OptionItem CanTargetImpostor; private static OptionItem CanTargetNeutralKiller; private static OptionItem CanTargetNeutralApoc; + private static OptionItem CanTargetCoven; private static OptionItem CanTargetCrewmate; private static OptionItem CanTargetJester; private static OptionItem ShouldChangeRoleAfterTargetDeath; @@ -58,6 +59,7 @@ public override void SetupCustomOption() CanTargetImpostor = BooleanOptionItem.Create(Id + 10, "LawyerCanTargetImpostor", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); CanTargetNeutralKiller = BooleanOptionItem.Create(Id + 11, "LawyerCanTargetNeutralKiller", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); CanTargetNeutralApoc = BooleanOptionItem.Create(Id + 18, "LawyerCanTargetNeutralApocalypse", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); + CanTargetCoven = BooleanOptionItem.Create(Id + 19, "LawyerCanTargetCoven", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); CanTargetCrewmate = BooleanOptionItem.Create(Id + 12, "LawyerCanTargetCrewmate", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); CanTargetJester = BooleanOptionItem.Create(Id + 13, "LawyerCanTargetJester", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); KnowTargetRole = BooleanOptionItem.Create(Id + 14, "KnowTargetRole", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); @@ -86,6 +88,7 @@ public override void Add(byte playerId) else if (!CanTargetImpostor.GetBool() && target.Is(Custom_Team.Impostor)) continue; else if (!CanTargetNeutralApoc.GetBool() && target.IsNeutralApocalypse()) continue; else if (!CanTargetNeutralKiller.GetBool() && target.IsNeutralKiller()) continue; + else if (!CanTargetCoven.GetBool() && target.Is(Custom_Team.Coven)) continue; else if (!CanTargetCrewmate.GetBool() && target.Is(Custom_Team.Crewmate)) continue; else if (!CanTargetJester.GetBool() && target.Is(CustomRoles.Jester)) continue; else if (target.Is(Custom_Team.Neutral) && !target.IsNeutralKiller() && !target.Is(CustomRoles.Jester) && !target.IsNeutralApocalypse()) continue; From e38b06b31951ffc45b25d54201ffcc8c698d9bf1 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 4 Nov 2024 19:19:29 -0500 Subject: [PATCH 015/101] get rid of spellcaster, fix oiiai, make enchanted count as coven --- Modules/GameState.cs | 4 ++++ Resources/Lang/en_US.json | 7 ++----- Roles/AddOns/Common/Oiiai.cs | 2 +- Roles/Coven/Spellcaster.cs | 0 main.cs | 1 - 5 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 Roles/Coven/Spellcaster.cs diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 16c3bb382..504214e1e 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -215,6 +215,10 @@ public void SetSubRole(CustomRoles role, PlayerControl pc = null) case CustomRoles.Soulless: countTypes = CountTypes.OutOfGame; break; + + case CustomRoles.Enchanted: + countTypes = CountTypes.Coven; + break; } } public void RemoveSubRole(CustomRoles addOn) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 19e069434..9c9209f8f 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -325,7 +325,6 @@ "Necromancer": "Necromancer", "CovenLeader": "Coven Leader", "Ritualist": "Ritualist", - "Spellcaster": "Spellcaster", "Conjurer": "Conjurer", "Dreamweaver": "Dreamweaver", "Illusionist": "Illusionist", @@ -640,20 +639,19 @@ "RomanticInfo": "Protect your partner to win together", "VengefulRomanticInfo": "Revenge your partner to win together", "RuthlessRomanticInfo": "Kill everyone to win with your partner", + "WraithInfo": "Vent to go invisible temporarily", "PoisonerInfo": "Kill everyone with delayed kills", "HexMasterInfo": "Hex players to kill them in meetings", - "WraithInfo": "Vent to go invisible temporarily", "JinxInfo": "Reflect attacks onto your attackers", "PotionMasterInfo": "Use your potions to your advantage", "NecromancerInfo": "Kill your killer to defy death", "CovenLeaderInfo": "Help your teammates by retraining them", "RitualistInfo": "Perform Blood Rituals to Enchant other players!", - "SpellcasterInfo": "Control other players against eachother!", "ConjurerInfo": "Blast away your enemies!", "DreamweaverInfo": "Drive other players to insomnia!", "IllusionistInfo": "Place Illusions on players to spread confusion!", "VoodooMasterInfo": "Craft Voodoo Dolls of other players!", - "SacrifistInfo": "Help your team and harm everyone else at your own cost", + "SacrifistInfo": "Debuff your enemies at your own cost", "MoonDancerInfo": "Use Baton Pass to give out add-ons!", "WardenInfo": "(Ghost) Alert about danger", "MinionInfo": "(Ghost) Blind enemies", @@ -963,7 +961,6 @@ "NecromancerInfoLong": "(Coven):\nAs the Necromancer, 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.", "CovenLeaderInfoLong": "(Coven):\nThe Coven Leader can use their kill button on a fellow Coven member to retrain them into a random Coven role that isn’t currently in the game. During the next meeting, that Coven member will be notified that the Coven Leader wishes to retrain them. They can vote themselves to accept the retrain, or vote otherwise to deny it. Denying the retrain does not take away an ability usage.\nWith the Necronomicon, you cannot retrain, and can only kill other players.", "RitualistInfoLong": "(Coven):\nDuring a meeting, the Ritualist can perform a Blood Ritual to guess a player’s exact role. If the Ritualist is correct, that player will be converted to Coven. If the Ritualist is incorrect, they will not die but will be unable to Blood Ritual until the next meeting.\nhe command is /rt id role.\nWith the Necronomicon, the Ritualist can kill.", - "SpellcasterInfoLong": "(Coven):\nThe Spellcaster can use their kill button on two players to control the first player into the other. If the first player has a kill button, they will be forced to use their kill button on the second person.\nWith the Necronomicon, you will kill your first target after controlling them.", "ConjurerInfoLong": "(Coven):\nShapeshift once to mark a location.\nShapeshift again to conjure a meteor at the place you marked, killing everyone in the radius.\nWith the Necronomicon, you can kill. You can also mark a player using the Shapeshift menu. When the Conjurer unshapeshifts, all the players in the radius of the marked player will die, including the marked player.", "DreamweaverInfoLong": "(Coven):\nThe Dreamweaver can Dreamweave a player. The dreamweaved players will be notified of this during the next meeting. If the Dreamweaver is not voted out, these players will be unable to use their abilities until the Dreamweaver dies.\nWith the Necronomicon, the Dreamweaver can double-click to kill.", "IllusionistInfoLong": "(Coven):\nThe Illusionist can use their kill button on a player to reverse the results of any investigative role. For example, if someone with a kill button is Illusioned, they will appear not to have a kill button to the Investigator, and vice versa.\nIllusions wear off after meetings.\nWith the Necronomicon, you may double-click to kill. Every kill you make appears as a random death reason.", diff --git a/Roles/AddOns/Common/Oiiai.cs b/Roles/AddOns/Common/Oiiai.cs index b2bb0d345..cfaef2df1 100644 --- a/Roles/AddOns/Common/Oiiai.cs +++ b/Roles/AddOns/Common/Oiiai.cs @@ -91,7 +91,7 @@ public static void OnMurderPlayer(PlayerControl killer, PlayerControl target) Logger.Info($"Oiiai {killer.GetNameWithRole().RemoveHtmlTags()} cannot eraser crew imp-based role", "Oiiai"); return; } - else if (killer.GetCustomRole().IsCoven() || !CovenManager.HasNecronomicon(killer)) + else if (killer.GetCustomRole().IsCoven() && !CovenManager.HasNecronomicon(killer)) { killer.RpcSetCustomRole(CustomRoles.Amnesiac); killer.RpcSetCustomRole(CustomRoles.Enchanted); diff --git a/Roles/Coven/Spellcaster.cs b/Roles/Coven/Spellcaster.cs deleted file mode 100644 index e69de29bb..000000000 diff --git a/main.cs b/main.cs index b465465c3..b914a269f 100644 --- a/main.cs +++ b/main.cs @@ -873,7 +873,6 @@ public enum CustomRoles PotionMaster, Ritualist, Sacrifist, - Spellcaster, Sorceress, VoodooMaster, From bc8a36b260e3dc9bd5f267c007d58db4cc2dbdf9 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 4 Nov 2024 19:57:33 -0500 Subject: [PATCH 016/101] update illusionist cases --- Modules/Utils.cs | 17 +++++++++++++++++ Patches/MeetingHudPatch.cs | 15 +++++++++++++++ Patches/PlayerControlPatch.cs | 15 +++++++++++++++ Roles/Coven/Illusionist.cs | 2 +- Roles/Crewmate/FortuneTeller.cs | 13 +++++++++++++ Roles/Crewmate/Snitch.cs | 2 +- 6 files changed, 62 insertions(+), 2 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index de7811d9a..57b66453b 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -23,6 +23,8 @@ using TOHE.Roles.Core; using static TOHE.Translator; using TOHE.Patches; +using TOHE.Roles.Coven; +using Epic.OnlineServices; namespace TOHE; @@ -2147,6 +2149,21 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl TargetRoleText = Overseer.GetRandomRole(seer.PlayerId); // Random trickster role TargetRoleText += TaskState.GetTaskState(); // Random task count for revealed trickster } + // Same thing as Trickster but for Illusioned Coven + if (seer.IsAlive() && Overseer.IsRevealedPlayer(seer, target) && Illusionist.IsCovIllusioned(target.PlayerId)) + { + TargetRoleText = Overseer.GetRandomRole(seer.PlayerId); + TargetRoleText += TaskState.GetTaskState(); + } + if (seer.IsAlive() && Overseer.IsRevealedPlayer(seer, target) && Illusionist.IsNonCovIllusioned(target.PlayerId)) + { + var randomRole = CustomRolesHelper.AllRoles.Where(role => role.IsEnable() && !role.IsAdditionRole() && role.IsCoven()).ToList().RandomElement(); + TargetRoleText = ColorString(GetRoleColor(randomRole), GetString(randomRole.ToString())); + if (randomRole is CustomRoles.CovenLeader or CustomRoles.Jinx or CustomRoles.Illusionist or CustomRoles.VoodooMaster) // Roles with Ability Uses + { + TargetRoleText += randomRole.GetStaticRoleClass().GetProgressText(target.PlayerId, false); + } + } // ====== Target player name ====== diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index da2acd380..5db34e315 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -13,6 +13,7 @@ using UnityEngine; using static TOHE.Utils; using static TOHE.Translator; +using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE; @@ -1089,6 +1090,20 @@ public static void Postfix(MeetingHud __instance) roleTextMeeting.text += TaskState.GetTaskState(); // Random task count for revealed trickster //enable = false; } + if (!PlayerControl.LocalPlayer.Data.IsDead && Overseer.IsRevealedPlayer(PlayerControl.LocalPlayer, pc) && Illusionist.IsCovIllusioned(pc.PlayerId)) + { + roleTextMeeting.text = Overseer.GetRandomRole(PlayerControl.LocalPlayer.PlayerId); + roleTextMeeting.text += TaskState.GetTaskState(); + } + if (!PlayerControl.LocalPlayer.Data.IsDead && Overseer.IsRevealedPlayer(PlayerControl.LocalPlayer, pc) && Illusionist.IsNonCovIllusioned(pc.PlayerId)) + { + var randomRole = CustomRolesHelper.AllRoles.Where(role => role.IsEnable() && !role.IsAdditionRole() && role.IsCoven()).ToList().RandomElement(); + roleTextMeeting.text = ColorString(GetRoleColor(randomRole), GetString(randomRole.ToString())); + if (randomRole is CustomRoles.CovenLeader or CustomRoles.Jinx or CustomRoles.Illusionist or CustomRoles.VoodooMaster) // Roles with Ability Uses + { + roleTextMeeting.text += randomRole.GetStaticRoleClass().GetProgressText(PlayerControl.LocalPlayer.PlayerId, false); + } + } var suffixBuilder = new StringBuilder(32); if (myRole != null) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index db86b0c20..271929b8b 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1244,6 +1244,21 @@ public static Task DoPostfix(PlayerControl __instance) RoleText.text = Overseer.GetRandomRole(PlayerControl.LocalPlayer.PlayerId); // random role for revealed trickster RoleText.text += TaskState.GetTaskState(); // random task count for revealed trickster } + if (!PlayerControl.LocalPlayer.Data.IsDead && Overseer.IsRevealedPlayer(PlayerControl.LocalPlayer, __instance) && Illusionist.IsCovIllusioned(__instance.PlayerId)) + { + RoleText.text = Overseer.GetRandomRole(PlayerControl.LocalPlayer.PlayerId); + RoleText.text += TaskState.GetTaskState(); + } + if (!PlayerControl.LocalPlayer.Data.IsDead && Overseer.IsRevealedPlayer(PlayerControl.LocalPlayer, __instance) && Illusionist.IsNonCovIllusioned(__instance.PlayerId)) + { + var randomRole = CustomRolesHelper.AllRoles.Where(role => role.IsEnable() && !role.IsAdditionRole() && role.IsCoven()).ToList().RandomElement(); + RoleText.text = Utils.ColorString(Utils.GetRoleColor(randomRole), GetString(randomRole.ToString())); + if (randomRole is CustomRoles.CovenLeader or CustomRoles.Jinx or CustomRoles.Illusionist or CustomRoles.VoodooMaster) // Roles with Ability Uses + { + RoleText.text += randomRole.GetStaticRoleClass().GetProgressText(PlayerControl.LocalPlayer.PlayerId, false); + } + } + if (!AmongUsClient.Instance.IsGameStarted && AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay) { diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index 39762f10c..8d9d2d5cd 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -87,7 +87,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = IllusionCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; public override string GetProgressText(byte playerId, bool comms) - => ColorString(AbilityLimit >= 1 ? GetRoleColor(CustomRoles.Ritualist).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); + => ColorString(AbilityLimit >= 1 ? GetRoleColor(CustomRoles.Illusionist).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); private static PlayerState.DeathReason ChangeRandomDeath() { PlayerState.DeathReason[] deathReasons = EnumHelper.GetAllValues().ToArray(); diff --git a/Roles/Crewmate/FortuneTeller.cs b/Roles/Crewmate/FortuneTeller.cs index 323114eef..174ccb9be 100644 --- a/Roles/Crewmate/FortuneTeller.cs +++ b/Roles/Crewmate/FortuneTeller.cs @@ -139,6 +139,14 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) SendMessage(string.Format(GetString("VoodooMasterTargetInMeeting"), realTarget.GetRealName()), Utils.GetPlayerListByRole(CustomRoles.VoodooMaster).First().PlayerId); msg = string.Format(GetString("FortuneTellerCheck.TaskDone"), target.GetRealName(), GetString(realTarget.GetCustomRole().ToString())); } + else if (Illusionist.IsCovIllusioned(target.PlayerId)) + { + msg = string.Format(GetString("FortuneTellerCheck.TaskDone"), target.GetRealName(), GetString(CustomRolesHelper.AllRoles.Where(role => role.IsEnable() && !role.IsAdditionRole() && role.IsCrewmate()).ToList().RandomElement().ToString())); + } + else if (Illusionist.IsNonCovIllusioned(target.PlayerId)) + { + msg = string.Format(GetString("FortuneTellerCheck.TaskDone"), target.GetRealName(), GetString(CustomRolesHelper.AllRoles.Where(role => role.IsEnable() && !role.IsAdditionRole() && role.IsCoven()).ToList().RandomElement().ToString())); + } else msg = string.Format(GetString("FortuneTellerCheck.TaskDone"), target.GetRealName(), GetString(target.GetCustomRole().ToString())); } @@ -153,6 +161,8 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) } targetList.Add(target.PlayerId); var targetRole = target.GetCustomRole(); + if (Illusionist.IsCovIllusioned(target.PlayerId)) targetRole = CustomRolesHelper.AllRoles.Where(role => role.IsEnable() && !role.IsAdditionRole() && role.IsCrewmate()).ToList().RandomElement(); + else if (Illusionist.IsNonCovIllusioned(target.PlayerId)) targetRole = CustomRolesHelper.AllRoles.Where(role => role.IsEnable() && !role.IsAdditionRole() && role.IsCoven()).ToList().RandomElement(); var activeRoleList = CustomRolesHelper.AllRoles.Where(role => (role.IsEnable() || role.RoleExist(countDead: true)) && role != targetRole && !role.IsAdditionRole()).ToList(); var count = Math.Min(4, activeRoleList.Count); List roleList = [targetRole]; @@ -180,6 +190,9 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) var targetRole = target.GetCustomRole(); string text = string.Empty; + if (Illusionist.IsCovIllusioned(target.PlayerId)) targetRole = CustomRolesHelper.AllRoles.Where(role => role.IsEnable() && !role.IsAdditionRole() && role.IsCrewmate()).ToList().RandomElement(); + else if (Illusionist.IsNonCovIllusioned(target.PlayerId)) targetRole = CustomRolesHelper.AllRoles.Where(role => role.IsEnable() && !role.IsAdditionRole() && role.IsCoven()).ToList().RandomElement(); + text = GetTargetRoleList(completeRoleList.FirstOrDefault(x => x.Contains(targetRole))); if (text == string.Empty) diff --git a/Roles/Crewmate/Snitch.cs b/Roles/Crewmate/Snitch.cs index 223b04a1f..ac7f3a4ce 100644 --- a/Roles/Crewmate/Snitch.cs +++ b/Roles/Crewmate/Snitch.cs @@ -179,7 +179,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl pc) { foreach (var target in Main.AllAlivePlayerControls) { - if (!IsSnitchTarget(target) || (!Illusionist.IsNonCovIllusioned(target.PlayerId) && Illusionist.SnitchCanIllusioned.GetBool())) continue; + if (!IsSnitchTarget(target) || !(Illusionist.IsNonCovIllusioned(target.PlayerId) && Illusionist.SnitchCanIllusioned.GetBool())) continue; var targetId = target.PlayerId; From f2a5098b2d3da7bbb5b40718178a6af2291793fd Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 7 Nov 2024 16:30:39 -0500 Subject: [PATCH 017/101] potionmaster rework --- Resources/Lang/en_US.json | 13 ++- Roles/Coven/PotionMaster.cs | 188 ++++++++++++++++++++++++++++++------ Roles/Crewmate/CopyCat.cs | 4 +- 3 files changed, 175 insertions(+), 30 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 9c9209f8f..ebc3e5011 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1011,7 +1011,7 @@ "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to neutrals.", "EvilSpiritInfoLong": "(Add-ons):\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to give the Spiritcaller a shield against a kill attempt temporarily.", "RecruitInfoLong": "(Betrayal Add-ons):\nAs a recruit, you are on the Jackal's team and help out the Jackal and their Sidekicks.\n\nYou cannot win with your original team.", - "EnchantedInfoLong": "(Betrayal Add-ons):\nThe Enchanted add-on can only be obtained through being converted by the Ritualist.\nOnce Enchanted, you are apart of the Coven team and are no longer apart of your original team.", + "EnchantedInfoLong": "(Betrayal Add-ons):\nThe Enchanted add-on can only be obtained through being converted by the Ritualist or upon killing the Oiiai as a non-nerconomicon holder.\nOnce Enchanted, you are apart of the Coven team and are no longer apart of your original team.", "AdmiredInfoLong": "(Betrayal Add-ons):\nAs an admired player, you win with the crew and not your original team.\n\nYou can see the Admirer.", "GlowInfoLong": "(Add-ons):\nDuring lights out, you and players nearby you will receive a vision boost.", "RadarInfoLong": "(Add-ons):\nAs Radar, you have arrows pointing towards the closest person at all times.", @@ -2049,6 +2049,16 @@ "VoodooMasterNoDollsLeft": "You can't voodoo anyone else this round!", "VoodooMasterDolledSomeone": "{0} has been turned into a Voodoo doll", + "PotionMasterMaxReveals": "Maximum Reveals", + "PotionMasterMaxBarriers": "Maximum Barriers", + "PotionMasterNoPotions": "You're out of {0} Potions!", + "PotionMasterPotionSwitch": "Potion switched to: {0}", + "PotionMasterPotionCurrent": "Current Potion: ", + "PotionMasterRevealCoven": "You can already see roles of Coven!", + "PotionMasterReveal": "Reveal", + "PotionMasterBarrier": "Barrier", + "PotionMasterKillButtonText": "Use Potion", + "LuckyProbability": "Probability of surviving a kill", "ImpCanBeDoubleShot": "Impostors can have Double Shot", @@ -3370,7 +3380,6 @@ "PitfallTrapCauseVisionTime": "Trap caused vision time", "PitfallTrap": "You have fallen into a trap!", "ConsigliereDivinationMaxCount": "Maximum Reveals", - "RitualMaxCount": "Maximum Reveals", "CleanserHideVote": "Hide Cleanser's vote", "OracleSkillLimit": "Maximum Uses", "OracleHideVote": "Hide Oracle's vote", diff --git a/Roles/Coven/PotionMaster.cs b/Roles/Coven/PotionMaster.cs index 082d4eaa7..6d322627c 100644 --- a/Roles/Coven/PotionMaster.cs +++ b/Roles/Coven/PotionMaster.cs @@ -1,9 +1,13 @@ -using AmongUs.GameOptions; +using AmongUs.GameOptions; using Hazel; using InnerNet; using TOHE.Roles.Core; +using System.Text; using UnityEngine; using static TOHE.Options; +using static TOHE.Utils; +using static TOHE.Translator; +using MS.Internal.Xml.XPath; namespace TOHE.Roles.Coven; @@ -13,55 +17,81 @@ internal class PotionMaster : CovenManager private const int Id = 17700; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.PotionMaster); public override bool IsDesyncRole => true; - public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; //==================================================================\\ private static OptionItem KillCooldown; - private static OptionItem RitualMaxCount; + private static OptionItem RevealMaxCount; + private static OptionItem BarrierMaxCount; //private static OptionItem CanVent; //private static OptionItem HasImpostorVision; - private static readonly Dictionary> RitualTarget = []; + private static readonly Dictionary> RevealList = []; + private static readonly Dictionary> BarrierList = []; + private static readonly Dictionary RevealLimit = []; + private static readonly Dictionary BarrierLimit = []; + private static byte PotionMode = 0; + + public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.PotionMaster, 1, zeroOne: false); KillCooldown = FloatOptionItem.Create(Id + 14, GeneralOption.KillCooldown, new(0f, 180f, 2.5f), 20f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]) .SetValueFormat(OptionFormat.Seconds); - RitualMaxCount = IntegerOptionItem.Create(Id + 11, "RitualMaxCount", new(1, 15, 1), 5, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]) + RevealMaxCount = IntegerOptionItem.Create(Id + 11, "PotionMasterMaxReveals", new(1, 15, 1), 5, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]) + .SetValueFormat(OptionFormat.Times); + BarrierMaxCount = IntegerOptionItem.Create(Id + 15, "PotionMasterMaxBarriers", new(1, 100, 1), 5, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]) .SetValueFormat(OptionFormat.Times); //CanVent = BooleanOptionItem.Create(Id + 12, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]); //HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.PotionMaster]); } public override void Init() { - RitualTarget.Clear(); + RevealList.Clear(); + RevealLimit.Clear(); + BarrierLimit.Clear(); } public override void Add(byte playerId) { - AbilityLimit = RitualMaxCount.GetInt(); - RitualTarget.TryAdd(playerId, []); + RevealList[playerId] = []; + BarrierList[playerId] = []; + RevealLimit[playerId] = RevealMaxCount.GetInt(); + BarrierLimit[playerId] = RevealMaxCount.GetInt(); + PotionMode = 0; var pc = Utils.GetPlayerById(playerId); pc?.AddDoubleTrigger(); } - private void SendRPC(byte playerId, byte targetId) + private void SendRPC(byte playerId, byte targetId, int operate) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); writer.WriteNetObject(_Player); + writer.Write(operate); writer.Write(playerId); - writer.Write(AbilityLimit); writer.Write(targetId); + if (operate == 0) + writer.Write(RevealLimit[playerId]); + else + writer.Write(BarrierLimit[playerId]); AmongUsClient.Instance.FinishRpcImmediately(writer); } - public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) + public override void ReceiveRPC(MessageReader reader, PlayerControl pc) { + int operate = reader.ReadInt32(); byte playerId = reader.ReadByte(); - - AbilityLimit = reader.ReadSingle(); - RitualTarget[playerId].Add(reader.ReadByte()); + if (operate == 0) + { + RevealLimit[playerId] = reader.ReadInt32(); + RevealList[playerId].Add(reader.ReadByte()); + } + if (operate == 1) + { + BarrierLimit[playerId] = reader.ReadInt32(); + BarrierList[playerId].Add(reader.ReadByte()); + } } //public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); @@ -73,16 +103,20 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - if (AbilityLimit > 0) + if (HasNecronomicon(killer)) { return killer.CheckDoubleTrigger(target, () => { SetRitual(killer, target); }); } - else return true; + else + { + SetRitual(killer, target); + return false; + } } - public static bool IsRitual(byte seer, byte target) + public static bool IsReveal(byte seer, byte target) { - if (RitualTarget[seer].Contains(target)) + if (RevealList[seer].Contains(target)) { return true; } @@ -90,30 +124,130 @@ public static bool IsRitual(byte seer, byte target) } private void SetRitual(PlayerControl killer, PlayerControl target) { - if (!IsRitual(killer.PlayerId, target.PlayerId)) + switch (PotionMode) { - AbilityLimit--; - RitualTarget[killer.PlayerId].Add(target.PlayerId); - Logger.Info($"{killer.GetNameWithRole()}: Divined divination destination -> {target.GetNameWithRole()} || remaining {AbilityLimit} times", "PotionMaster"); + case 0: + if (target.IsPlayerCoven()) + { + killer.Notify(GetString("PotionMasterRevealCoven")); + } + else if (!IsReveal(killer.PlayerId, target.PlayerId) && RevealLimit[killer.PlayerId] > 0) + { + RevealLimit[killer.PlayerId]--; + RevealList[killer.PlayerId].Add(target.PlayerId); + Logger.Info($"{killer.GetNameWithRole()}: Divined divination destination -> {target.GetNameWithRole()} || remaining {RevealLimit[killer.PlayerId]} times", "PotionMaster"); + + Utils.NotifyRoles(SpecifySeer: killer); + SendRPC(killer.PlayerId, target.PlayerId, 0); + + killer.SetKillCooldown(); + } + else if (RevealLimit[killer.PlayerId] <= 0) + { + killer.Notify(string.Format(GetString("PotionMasterNoPotions"),GetString("PotionMasterReveal"))); + } + break; + case 1: + if (!BarrierList[killer.PlayerId].Contains(target.PlayerId) && BarrierLimit[killer.PlayerId] > 0) + { + BarrierLimit[killer.PlayerId]--; + BarrierList[killer.PlayerId].Add(target.PlayerId); + Logger.Info($"{killer.GetNameWithRole()}: Barrier destination -> {target.GetNameWithRole()} || remaining {BarrierLimit[killer.PlayerId]} times", "PotionMaster"); - Utils.NotifyRoles(SpecifySeer: killer); - SendRPC(killer.PlayerId, target.PlayerId); + SendRPC(killer.PlayerId, target.PlayerId, 1); - killer.SetKillCooldown(); + killer.SetKillCooldown(); + } + else if (BarrierLimit[killer.PlayerId] <= 0) + { + killer.Notify(string.Format(GetString("PotionMasterNoPotions"), GetString("PotionMasterBarrier"))); + } + break; + } + } + public override void UnShapeShiftButton(PlayerControl pm) + { + switch (PotionMode) { + case 0: + PotionMode = 1; + pm.Notify(string.Format(GetString("PotionMasterPotionSwitch"), GetString("PotionMasterBarrier"))); + break; + case 1: + PotionMode = 0; + pm.Notify(string.Format(GetString("PotionMasterPotionSwitch"), GetString("PotionMasterReveal"))); + break; + } + } + public static byte CurrentPotion() => PotionMode; + public override string GetLowerText(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) + { + if (seer == null || !seer.IsAlive() || isForMeeting || !isForHud) return string.Empty; + else + { + var sb = new StringBuilder(); + switch (PotionMode) + { + case 0: // Reveal + sb.Append(GetString("PotionMasterPotionCurrent") + GetString("PotionMasterReveal")); + break; + case 1: // Barrier + sb.Append(GetString("PotionMasterPotionCurrent") + GetString("PotionMasterBarrier")); + break; + } + return sb.ToString(); } } public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) { var IsWatch = false; - RitualTarget.Do(x => + RevealList.Do(x => { if (x.Value != null && seer.PlayerId == x.Key && x.Value.Contains(target.PlayerId) && Utils.GetPlayerById(x.Key).IsAlive()) IsWatch = true; }); return IsWatch; } + public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) + { + if (_Player == null || !_Player.IsAlive()) return false; + if (!BarrierList[_Player.PlayerId].Contains(target.PlayerId)) return false; + + killer.RpcGuardAndKill(target); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); + + NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); + NotifyRoles(SpecifySeer: target, SpecifyTarget: killer, ForceLoop: true); + return true; + } + public override void AfterMeetingTasks() + { + BarrierList[_Player.PlayerId].Clear(); + } + public override void SetAbilityButtonText(HudManager hud, byte playerId) + { + if (PotionMode == 0) + { + hud.AbilityButton.OverrideText(GetString("PotionMasterReveal")); + } + else if (PotionMode == 1) + { + hud.AbilityButton.OverrideText(GetString("PotionMasterBarrier")); + } + hud.KillButton.OverrideText(GetString("PotionMasterKillButtonText")); + } + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) + => BarrierList[seer.PlayerId].Contains(seen.PlayerId) ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.PotionMaster), "✚") : string.Empty; + public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) + { + if (BarrierList[_Player.PlayerId].Contains(target.PlayerId) && seer.IsPlayerCoven() && seer.PlayerId != _Player.PlayerId) + { + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.PotionMaster), "✚"); + } + return string.Empty; + } public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); - public override string GetProgressText(byte playerId, bool coooonms) => Utils.ColorString(AbilityLimit > 0 ? Utils.GetRoleColor(CustomRoles.PotionMaster).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); + public override string GetProgressText(byte playerId, bool coooonms) => Utils.ColorString(RevealLimit[playerId] > 0 ? Utils.GetRoleColor(CustomRoles.PotionMaster).ShadeColor(0.25f) : Color.gray, $"({RevealLimit[playerId]})")+ Utils.ColorString(BarrierLimit[playerId] > 0 ? Utils.GetRoleColor(CustomRoles.Medic).ShadeColor(0.25f) : Color.gray, $" ({BarrierLimit[playerId]})"); } \ No newline at end of file diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index 47c265a4e..17c25f3e4 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -1,4 +1,5 @@ using TOHE.Roles.Core; +using TOHE.Roles.Coven; using TOHE.Roles.Neutral; using static TOHE.Options; using static TOHE.Translator; @@ -108,7 +109,6 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr CustomRoles.Consigliere => CustomRoles.Overseer, CustomRoles.Mercenary => CustomRoles.Addict, CustomRoles.Miner => CustomRoles.Mole, - CustomRoles.PotionMaster => CustomRoles.Overseer, CustomRoles.Twister => CustomRoles.TimeMaster, CustomRoles.Disperser => CustomRoles.Transporter, CustomRoles.Eraser => CustomRoles.Cleanser, @@ -127,6 +127,8 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr CustomRoles.Baker when Baker.CurrentBread() is 0 => CustomRoles.Overseer, CustomRoles.Baker when Baker.CurrentBread() is 1 => CustomRoles.Deputy, CustomRoles.Baker when Baker.CurrentBread() is 2 => CustomRoles.Medic, + CustomRoles.PotionMaster when PotionMaster.CurrentPotion() is 0 => CustomRoles.Overseer, + CustomRoles.PotionMaster when PotionMaster.CurrentPotion() is 1 => CustomRoles.Medic, CustomRoles.Sacrifist => CustomRoles.Alchemist, CustomRoles.MoonDancer => CustomRoles.Merchant, CustomRoles.Ritualist => CustomRoles.Admirer, From 9f7f454b20a8677f3e374f24794d0345f72c7f63 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 9 Nov 2024 18:10:24 -0500 Subject: [PATCH 018/101] poisoner rework --- Roles/Coven/CovenManager.cs | 2 +- Roles/Coven/Poisoner.cs | 47 ++++++++++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index db0438a21..ef45aefb3 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -33,7 +33,7 @@ public static void RunSetUpImpVisOptions(int Id) } public static void RunSetUpVentOptions(int Id) { - foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven() && (x != CustomRoles.Medusa && x != CustomRoles.PotionMaster && x != CustomRoles.Sacrifist)).ToArray()) + foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven() && (x != CustomRoles.Medusa && x != CustomRoles.Sacrifist)).ToArray()) { SetUpVentOption(cov, Id, true, CovenVentMode); Id++; diff --git a/Roles/Coven/Poisoner.cs b/Roles/Coven/Poisoner.cs index 49a3d2b64..64fddb298 100644 --- a/Roles/Coven/Poisoner.cs +++ b/Roles/Coven/Poisoner.cs @@ -1,7 +1,8 @@ -using AmongUs.GameOptions; +using AmongUs.GameOptions; using UnityEngine; using TOHE.Roles.AddOns.Common; using static TOHE.Translator; +using static TOHE.Utils; namespace TOHE.Roles.Coven; @@ -28,6 +29,8 @@ private class PoisonedInfo(byte poisonerId, float killTimer) //private static OptionItem HasImpostorVision; private static readonly Dictionary PoisonedPlayers = []; + private static readonly Dictionary> RoleblockedPlayers = []; + private static float KillDelay; @@ -46,12 +49,16 @@ public override void Init() { playerIdList.Clear(); PoisonedPlayers.Clear(); + RoleblockedPlayers.Clear(); KillDelay = OptionKillDelay.GetFloat(); } public override void Add(byte playerId) { playerIdList.Add(playerId); + RoleblockedPlayers[playerId] = []; + GetPlayerById(playerId)?.AddDoubleTrigger(); + } //public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); @@ -61,15 +68,27 @@ public override void Add(byte playerId) public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - if (target.Is(CustomRoles.Bait)) return true; + if (killer.CheckDoubleTrigger(target, () => { RoleblockedPlayers[killer.PlayerId].Add(target.PlayerId); })) + { + if (HasNecronomicon(killer) && !target.IsPlayerCoven()) + { + if (target.Is(CustomRoles.Bait)) return true; - killer.SetKillCooldown(); + killer.SetKillCooldown(); - if (!PoisonedPlayers.ContainsKey(target.PlayerId)) + if (!PoisonedPlayers.ContainsKey(target.PlayerId)) + { + PoisonedPlayers.Add(target.PlayerId, new(killer.PlayerId, 0f)); + } + } + return false; + } + else { - PoisonedPlayers.Add(target.PlayerId, new(killer.PlayerId, 0f)); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); + return false; } - return false; } public override void OnFixedUpdate(PlayerControl poisoner, bool lowLoad, long nowTime) @@ -121,12 +140,24 @@ public override void OnReportDeadBody(PlayerControl sans, NetworkedPlayerInfo ba { foreach (var targetId in PoisonedPlayers.Keys) { - var target = Utils.GetPlayerById(targetId); - var poisoner = Utils.GetPlayerById(PoisonedPlayers[targetId].PoisonerId); + var target = GetPlayerById(targetId); + var poisoner = GetPlayerById(PoisonedPlayers[targetId].PoisonerId); KillPoisoned(poisoner, target); } PoisonedPlayers.Clear(); } + public bool IsRoleblocked(byte id) => RoleblockedPlayers[_Player.PlayerId].Contains(id); + public override bool CheckMurderOnOthersTarget(PlayerControl pc, PlayerControl _) // Target of Pursuer attempt to murder someone + { + if (!IsRoleblocked(pc.PlayerId)) return false; + if (pc == null) return false; + + pc.ResetKillCooldown(); + pc.SetKillCooldown(); + + Logger.Info($"{pc.GetRealName()} fail ability because roleblocked", "Poisoner"); + return true; + } public override void SetAbilityButtonText(HudManager hud, byte playerId) { hud.KillButton.OverrideText(GetString("PoisonerPoisonButtonText")); From 54b7779f26deec3f42f847f5f46413fce634997f Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 9 Nov 2024 18:20:51 -0500 Subject: [PATCH 019/101] change vent abilities to unshapeshift --- Resources/Lang/en_US.json | 10 +++++----- Roles/Coven/CovenManager.cs | 2 +- Roles/Coven/Medusa.cs | 6 ++---- Roles/Coven/PotionMaster.cs | 1 - Roles/Coven/Sacrifist.cs | 6 +++--- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index de4ac1d2e..22f81a788 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -953,19 +953,19 @@ "RuthlessRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A neutral killer) is killed. As a Ruthless Romantic, you win if you kill everyone and are the last one standing. If you win, your dead partner will also win with you.", "VengefulRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A crew or non-neutral killer) is killed. As a Vengeful Romantic, your goal is to avenge your partner, which means you must kill the killer of your partner. If you succeed, then you and your partner win with the winning team at the end. If you try to kill someone other than your partner's killer, then you will die by misfire.", "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", - "PoisonerInfoLong": "(Coven):\nAs the Poisoner, your kills are delayed.\nKill everyone to win.", + "PoisonerInfoLong": "(Coven):\nThe Poisoner can use their kill button on a player to roleblock them. The next time the roleblocked player tries to use their ability, it will do nothing, and their cooldown will be reset.\nWith the Necronomicon, you can double-click to kill. These kills will be delayed.", "HexMasterInfoLong": "(Coven):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", "JinxInfoLong": "(Coven):\nAs the Jinx, whenever you get attacked, you jinx them, resulting in them dying to a jinx.\nThis has limited uses.", - "MedusaInfoLong": "(Coven):\nThe Medusa can use their kill button on players to mark them as Stoned. When the Medusa vents, all Stoned players will be unable to move and will have reduced vision for a configurable amount of time.\nWith the Necronomicon, killed players will be unreportable.", - "PotionMasterInfoLong": "(Coven):\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.", + "MedusaInfoLong": "(Coven):\nThe Medusa can use their kill button on players to mark them as Stoned. When the Medusa clicks the Shapeshift button, all Stoned players will be unable to move and will have reduced vision for a configurable amount of time.\nWith the Necronomicon, killed players will be unreportable.", + "PotionMasterInfoLong": "(Coven):\nThe Potion Master has two potions available for their use. The Reveal potion reveals a player's role. The Barrier potion places a shield on a player for one round, the player will not be notified of this unless they are Coven as well. Click the Shapeshift button to change potions.\nWith the Necronomicon, the Potion Master can double-click their kill button to kill.", "NecromancerInfoLong": "(Coven):\nAs the Necromancer, 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.", "CovenLeaderInfoLong": "(Coven):\nThe Coven Leader can use their kill button on a fellow Coven member to retrain them into a random Coven role that isn’t currently in the game. During the next meeting, that Coven member will be notified that the Coven Leader wishes to retrain them. They can vote themselves to accept the retrain, or vote otherwise to deny it. Denying the retrain does not take away an ability usage.\nWith the Necronomicon, you cannot retrain, and can only kill other players.", "RitualistInfoLong": "(Coven):\nDuring a meeting, the Ritualist can perform a Blood Ritual to guess a player’s exact role. If the Ritualist is correct, that player will be converted to Coven. If the Ritualist is incorrect, they will not die but will be unable to Blood Ritual until the next meeting.\nhe command is /rt id role.\nWith the Necronomicon, the Ritualist can kill.", - "ConjurerInfoLong": "(Coven):\nShapeshift once to mark a location.\nShapeshift again to conjure a meteor at the place you marked, killing everyone in the radius.\nWith the Necronomicon, you can kill. You can also mark a player using the Shapeshift menu. When the Conjurer unshapeshifts, all the players in the radius of the marked player will die, including the marked player.", + "ConjurerInfoLong": "(Coven):\nShapeshift once to mark a location.\nShapeshift again to conjure a meteor at the place you marked, killing everyone in the radius.\nWith the Necronomicon, you can kill. You can also mark a player using the Shapeshift menu. When the Conjurer clicks the shapeshift button again, all the players in the radius of the marked player will die, including the marked player.", "DreamweaverInfoLong": "(Coven):\nThe Dreamweaver can Dreamweave a player. The dreamweaved players will be notified of this during the next meeting. If the Dreamweaver is not voted out, these players will be unable to use their abilities until the Dreamweaver dies.\nWith the Necronomicon, the Dreamweaver can double-click to kill.", "IllusionistInfoLong": "(Coven):\nThe Illusionist can use their kill button on a player to reverse the results of any investigative role. For example, if someone with a kill button is Illusioned, they will appear not to have a kill button to the Investigator, and vice versa.\nIllusions wear off after meetings.\nWith the Necronomicon, you may double-click to kill. Every kill you make appears as a random death reason.", "VoodooMasterInfoLong": "(Coven):\nThe Voodoo Master can craft a voodoo doll of a player by using their kill button, similar to the Shaman. All the interactions with you using kill button will be deflected to the voodoo doll and the voodoo doll will destroy. Unlike the Shaman, this voodoo will last during the meeting (eg. If the Voodoo Master is judged, then the voodoo'd player will be judged instead).\nWith the Necronomicon, you can double-click to kill. Additionally, the voodoo’d player will be unable to report. The next person to interact with them will die.", - "SacrifistInfoLong": "(Coven):\nThe Sacrifist can vent to cause a random debuff to a non-coven member, however, the Sacrifist will also receive this effect (when applicable).\nThe random player will be the same player until the round ends.\nDepending on the host’s settings, if the Sacrifist is voted out, some random non-coven who voted the Sacrifist will die too.\nNormally, the Sacrifist can not kill, however, to prevent prolonging of the game, Sacrifist can kill if they are the last Coven member alive.\nWith the Necronomicon, when you vent, you will commit the Ultimate Sacrifice. When you do this, you die. However, the entire Coven for the rest of the game receives a lowered kill cooldown.", + "SacrifistInfoLong": "(Coven):\nThe Sacrifist can shapeshift to cause a random debuff to a non-coven member, however, the Sacrifist will also receive this effect (when applicable).\nThe random player will be the same player until the round ends.\nDepending on the host’s settings, if the Sacrifist is voted out, some random non-coven who voted the Sacrifist will die too.\nNormally, the Sacrifist can not kill, however, to prevent prolonging of the game, Sacrifist can kill if they are the last Coven member alive.\nWith the Necronomicon, when you shapeshift, you will commit the Ultimate Sacrifice. When you do this, you die. However, the entire Coven for the rest of the game receives a lowered kill cooldown.", "MoonDancerInfoLong": "(Coven):\nThe Moon Dancer can use their kill button to use their ability, Baton Pass.\nIf used on a Coven member: Gives a helpful add-on at the next meeting.\nIf used on a non-Coven member: Gives a harmful add-on at the next meeting.\nWith the Necronomicon, the Moon Dancer can double-click their kill button to kill. When killing, the player is teleported off the map. They will appear alive on vitals and will not show up in tracefinder's arrows etc. They die when a meeting/body report with the death reason Blasted Off.", "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.", "LastImpostorInfoLong": "(Add-ons):\nThis special effect is given to the last surviving Impostor. It significantly reduces their kill cooldown.", diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index ef45aefb3..8873a475b 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -33,7 +33,7 @@ public static void RunSetUpImpVisOptions(int Id) } public static void RunSetUpVentOptions(int Id) { - foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven() && (x != CustomRoles.Medusa && x != CustomRoles.Sacrifist)).ToArray()) + foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven()).ToArray()) { SetUpVentOption(cov, Id, true, CovenVentMode); Id++; diff --git a/Roles/Coven/Medusa.cs b/Roles/Coven/Medusa.cs index aa9fea1ee..e82dfbe13 100644 --- a/Roles/Coven/Medusa.cs +++ b/Roles/Coven/Medusa.cs @@ -15,7 +15,7 @@ internal class Medusa : CovenManager private const int Id = 17000; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Medusa); public override bool IsDesyncRole => true; - public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; //==================================================================\\ @@ -75,7 +75,6 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = StoneCooldown.GetFloat(); //public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override bool CanUseKillButton(PlayerControl pc) => true; - public override bool CanUseImpostorVentButton(PlayerControl pc) => true; //public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); /* @@ -109,9 +108,8 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return false; } } - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + public override void UnShapeShiftButton(PlayerControl dusa) { - var dusa = physics.myPlayer; foreach (var player in StonedPlayers[dusa.PlayerId]) { dusa.Notify(GetString("MedusaStoningStart"), StoneDuration.GetFloat()); diff --git a/Roles/Coven/PotionMaster.cs b/Roles/Coven/PotionMaster.cs index 6d322627c..e8cf92331 100644 --- a/Roles/Coven/PotionMaster.cs +++ b/Roles/Coven/PotionMaster.cs @@ -97,7 +97,6 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl pc) //public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; - public override bool CanUseImpostorVentButton(PlayerControl pc) => true; //public override bool CanUseSabotage(PlayerControl pc) => true; diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index aebcdb379..8da186c0c 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -16,7 +16,7 @@ internal class Sacrifist : CovenManager private const int Id = 30600; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Sacrifist); public override bool IsDesyncRole => true; - public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; //==================================================================\\ @@ -93,13 +93,13 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) // Sacrifist shouldn't be able to kill at all but if there's solo Sacrifist the game is unwinnable so they can kill when solo public override bool CanUseKillButton(PlayerControl pc) => Main.AllAlivePlayerControls.Where(pc => pc.Is(Custom_Team.Coven)).Count() == 1; - public override void OnEnterVent(PlayerControl pc, Vent vent) + public override void UnShapeShiftButton(PlayerControl pc) { var rand = IRandom.Instance; DebuffID = (byte)rand.Next(0, 10); if (randPlayer == byte.MaxValue) { - randPlayer = Main.AllAlivePlayerControls.Where(x => !x.Is(Custom_Team.Coven) || !x.Is(CustomRoles.Enchanted)).ToList().RandomElement().PlayerId; + randPlayer = Main.AllAlivePlayerControls.Where(x => !x.Is(Custom_Team.Coven) && !x.Is(CustomRoles.Enchanted)).ToList().RandomElement().PlayerId; } var randPlayerPC = GetPlayerById(randPlayer); var sacrifist = pc.PlayerId; From ca3503a4bdb61ee4176924dc596a6cf0c83dfcd7 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 9 Nov 2024 18:22:33 -0500 Subject: [PATCH 020/101] revert amnesiac changes --- Roles/Neutral/Amnesiac.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Roles/Neutral/Amnesiac.cs b/Roles/Neutral/Amnesiac.cs index 9c7980443..63aadef1e 100644 --- a/Roles/Neutral/Amnesiac.cs +++ b/Roles/Neutral/Amnesiac.cs @@ -29,7 +29,7 @@ private enum AmnesiacIncompatibleNeutralModeSelectList Role_Maverick, Role_Imitator, } - + public override void SetupCustomOption() { SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Amnesiac); @@ -135,11 +135,10 @@ public override bool OnCheckReportDeadBody(PlayerControl __instance, NetworkedPl } if (tar.GetCustomRole().IsNA()) { - tempRole = tar.GetCustomRole(); - } - if (tar.GetCustomRole().IsCoven()) - { - tempRole = tar.GetCustomRole(); + __instance.RpcSetCustomRole(tar.GetCustomRole()); + __instance.GetRoleClass().Add(__instance.PlayerId); + __instance.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("YouRememberedRole"))); + tar.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedYourRole"))); } if (tar.GetCustomRole().IsAmneNK()) { @@ -169,7 +168,6 @@ public override bool OnCheckReportDeadBody(PlayerControl __instance, NetworkedPl if (tempRole != CustomRoles.Amnesiac) { __instance.GetRoleClass().OnRemove(__instance.PlayerId); - __instance.RpcChangeRoleBasis(tempRole); __instance.RpcSetCustomRole(tempRole); __instance.GetRoleClass().OnAdd(__instance.PlayerId); __instance.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("YouRememberedRole"))); From bb15328e609b4550e25b81d0cf5d07509bc66c68 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 9 Nov 2024 18:42:04 -0500 Subject: [PATCH 021/101] change some stuff for HM i have not added the necronomicon mechanic to this as i really dont want to right now --- Modules/CustomRolesHelper.cs | 2 -- Resources/Lang/en_US.json | 4 +++ Roles/Coven/HexMaster.cs | 52 +++++++++++++++++++++--------------- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index cdea7f693..dad828a54 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -181,8 +181,6 @@ public static bool IsAmneNK(this CustomRoles role) CustomRoles.Sidekick or CustomRoles.Infectious or CustomRoles.Pyromaniac or - CustomRoles.Medusa or - CustomRoles.Necromancer or CustomRoles.Wraith or CustomRoles.Shroud or CustomRoles.Pelican or diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 22f81a788..602cf7217 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2059,6 +2059,10 @@ "PotionMasterBarrier": "Barrier", "PotionMasterKillButtonText": "Use Potion", + "HexMasterHexCooldown": "Hex Cooldown", + "HexMasterCovenCanGetMovingHex": "Coven Can Get Moving Hex", + "HexMasterMovingHexCooldown": "Moving Hex Pass Cooldown", + "LuckyProbability": "Probability of surviving a kill", "ImpCanBeDoubleShot": "Impostors can have Double Shot", diff --git a/Roles/Coven/HexMaster.cs b/Roles/Coven/HexMaster.cs index 3e52c48bf..613c43aed 100644 --- a/Roles/Coven/HexMaster.cs +++ b/Roles/Coven/HexMaster.cs @@ -19,9 +19,12 @@ internal class HexMaster : CovenManager public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenKilling; //==================================================================\\ - private static OptionItem ModeSwitchAction; + //private static OptionItem ModeSwitchAction; private static OptionItem HexesLookLikeSpells; //private static OptionItem HasImpostorVision; + private static OptionItem HexCooldown; + private static OptionItem CovenCanGetMovingHex; + private static OptionItem MovingHexPassCooldown; private static readonly Dictionary HexMode = []; private static readonly Dictionary> HexedPlayer = []; @@ -29,6 +32,7 @@ internal class HexMaster : CovenManager private static readonly Color RoleColorHex = Utils.GetRoleColor(CustomRoles.HexMaster); private static readonly Color RoleColorSpell = Utils.GetRoleColor(CustomRoles.Impostor); + /* private enum SwitchTriggerList { TriggerKill, @@ -36,12 +40,18 @@ private enum SwitchTriggerList TriggerDouble, }; private static SwitchTriggerList NowSwitchTrigger; + */ public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.HexMaster, 1, zeroOne: false); - ModeSwitchAction = StringOptionItem.Create(Id + 10, GeneralOption.ModeSwitchAction, EnumHelper.GetAllNames(), 2, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); + //ModeSwitchAction = StringOptionItem.Create(Id + 10, GeneralOption.ModeSwitchAction, EnumHelper.GetAllNames(), 2, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); HexesLookLikeSpells = BooleanOptionItem.Create(Id + 11, "HexesLookLikeSpells", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); + HexCooldown = FloatOptionItem.Create(Id + 13, "HexMasterCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]) + .SetValueFormat(OptionFormat.Seconds); + CovenCanGetMovingHex = BooleanOptionItem.Create(Id + 14, "HexMasterCovenCanGetMovingHex", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); + MovingHexPassCooldown = FloatOptionItem.Create(Id + 15, "HexMasterMovingHexCooldown", new(0f, 5f, 0.25f), 1f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]) + .SetValueFormat(OptionFormat.Seconds); //HasImpostorVision = BooleanOptionItem.Create(Id + 12, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); } public override void Init() @@ -55,7 +65,7 @@ public override void Add(byte playerId) playerIdList.Add(playerId); HexMode.Add(playerId, false); HexedPlayer.Add(playerId, []); - NowSwitchTrigger = (SwitchTriggerList)ModeSwitchAction.GetValue(); + // NowSwitchTrigger = (SwitchTriggerList)ModeSwitchAction.GetValue(); var pc = Utils.GetPlayerById(playerId); pc.AddDoubleTrigger(); @@ -106,11 +116,12 @@ public static void ReceiveRPC(MessageReader reader, bool doHex) public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => true; - + /* private static bool IsHexMode(byte playerId) { return HexMode.ContainsKey(playerId) && HexMode[playerId]; } + private static void SwitchHexMode(byte playerId, bool kill) { bool needSwitch = false; @@ -130,6 +141,7 @@ private static void SwitchHexMode(byte playerId, bool kill) Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(playerId)); } } + */ private static bool IsHexed(byte target) { foreach (var hexmaster in playerIdList) @@ -160,6 +172,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t { if (target.IsTransformedNeutralApocalypse()) return false; + /* if (NowSwitchTrigger == SwitchTriggerList.TriggerDouble) { return killer.CheckDoubleTrigger(target, () => { SetHexed(killer, target); }); @@ -170,11 +183,14 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t //キルモードなら通常処理に戻る return true; } - SetHexed(killer, target); - - //スペルに失敗してもスイッチ判定 - SwitchHexMode(killer.PlayerId, true); - //キル処理終了させる + */ + if (killer.CheckDoubleTrigger(target, () => { SetHexed(killer, target); })) + { + if (HasNecronomicon(killer) && !target.IsPlayerCoven()) + { + return true; + } + } return false; } public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) @@ -219,6 +235,7 @@ private static void RemoveHexedPlayer() SendRPC(true, hexmaster); } } + /* public override void OnEnterVent(PlayerControl pc, Vent vent) { if (NowSwitchTrigger is SwitchTriggerList.TriggerVent) @@ -226,6 +243,7 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) SwitchHexMode(pc.PlayerId, false); } } + */ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { if (isForMeeting && IsHexed(target.PlayerId)) @@ -241,7 +259,7 @@ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, b } return string.Empty; } - + /* public override string GetLowerText(PlayerControl hexmaster, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) { if (!hexmaster.IsAlive() || isForMeeting || hexmaster != seen) return string.Empty; @@ -266,16 +284,8 @@ public override string GetLowerText(PlayerControl hexmaster, PlayerControl seen return str.ToString(); } + */ - public override void SetAbilityButtonText(HudManager hud, byte playerid) - { - if (IsHexMode(playerid) && NowSwitchTrigger != SwitchTriggerList.TriggerDouble) - { - hud.KillButton.OverrideText($"{GetString("HexButtonText")}"); - } - else - { - hud.KillButton.OverrideText($"{GetString("KillButtonText")}"); - } - } + public override void SetAbilityButtonText(HudManager hud, byte playerid) => hud.KillButton.OverrideText($"{GetString("HexButtonText")}"); + } \ No newline at end of file From 32db22a5393c4a1cc4da4a5a93a4cc2488b60d85 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 9 Nov 2024 20:24:27 -0500 Subject: [PATCH 022/101] necromancer rework --- Resources/Lang/en_US.json | 41 ++++++++++-------- Roles/Coven/Necromancer.cs | 85 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 20 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 602cf7217..0e5669009 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -955,10 +955,10 @@ "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", "PoisonerInfoLong": "(Coven):\nThe Poisoner can use their kill button on a player to roleblock them. The next time the roleblocked player tries to use their ability, it will do nothing, and their cooldown will be reset.\nWith the Necronomicon, you can double-click to kill. These kills will be delayed.", "HexMasterInfoLong": "(Coven):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", - "JinxInfoLong": "(Coven):\nAs the Jinx, whenever you get attacked, you jinx them, resulting in them dying to a jinx.\nThis has limited uses.", + "JinxInfoLong": "(Coven):\nThe Jinx can use their kill button to Jinx a player. Anyone who interacts with the Jinxed player will die with the death reason Jinxed.\nWith the Necronomicon the Jinx can double-kill to kill normally. Also, the Jinxed player and the player who interacted with the Jinxed person will die.", "MedusaInfoLong": "(Coven):\nThe Medusa can use their kill button on players to mark them as Stoned. When the Medusa clicks the Shapeshift button, all Stoned players will be unable to move and will have reduced vision for a configurable amount of time.\nWith the Necronomicon, killed players will be unreportable.", "PotionMasterInfoLong": "(Coven):\nThe Potion Master has two potions available for their use. The Reveal potion reveals a player's role. The Barrier potion places a shield on a player for one round, the player will not be notified of this unless they are Coven as well. Click the Shapeshift button to change potions.\nWith the Necronomicon, the Potion Master can double-click their kill button to kill.", - "NecromancerInfoLong": "(Coven):\nAs the Necromancer, 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.", + "NecromancerInfoLong": "(Coven):\nAs the Necromancer, you can shapeshift to become the role of a random dead person for a set duration.\nSome roles can not be used.\nWith the Necronomicon, when someone tries to kill you, you will block the kill and be teleported to a random vent. You have a limited time to kill your killer. If the time runs out or you try to kill someone else, you die.", "CovenLeaderInfoLong": "(Coven):\nThe Coven Leader can use their kill button on a fellow Coven member to retrain them into a random Coven role that isn’t currently in the game. During the next meeting, that Coven member will be notified that the Coven Leader wishes to retrain them. They can vote themselves to accept the retrain, or vote otherwise to deny it. Denying the retrain does not take away an ability usage.\nWith the Necronomicon, you cannot retrain, and can only kill other players.", "RitualistInfoLong": "(Coven):\nDuring a meeting, the Ritualist can perform a Blood Ritual to guess a player’s exact role. If the Ritualist is correct, that player will be converted to Coven. If the Ritualist is incorrect, they will not die but will be unable to Blood Ritual until the next meeting.\nhe command is /rt id role.\nWith the Necronomicon, the Ritualist can kill.", "ConjurerInfoLong": "(Coven):\nShapeshift once to mark a location.\nShapeshift again to conjure a meteor at the place you marked, killing everyone in the radius.\nWith the Necronomicon, you can kill. You can also mark a player using the Shapeshift menu. When the Conjurer clicks the shapeshift button again, all the players in the radius of the marked player will die, including the marked player.", @@ -1450,8 +1450,6 @@ "FPSGame": "FPS: ", "ControlCooldown": "Control Cooldown", - "PoisonCooldown": "Poison Cooldown", - "PoisonerKillDelay": "Poison Kill Delay", "WardenNotifyLimit": "Max number of alerts", "BombCooldown": "Bomb Cooldown", @@ -1822,7 +1820,6 @@ "DoctorVisibleToEveryone": "Everyone knows who the Doctor is", "CursedWolfGuardSpellTimes": "Amount of Cursed Shields", "KillAttackerWhenAbilityRemaining": "Kill attacker when ability is remaining", - "JinxSpellTimes": "Amount of Jinx Spells", "CollectorCollectAmount": "Required number of votes", "GlitchCanVote": "Can vote", "QuickShooterShapeshiftCooldown": "Shapeshift Cooldown", @@ -2060,8 +2057,29 @@ "PotionMasterKillButtonText": "Use Potion", "HexMasterHexCooldown": "Hex Cooldown", - "HexMasterCovenCanGetMovingHex": "Coven Can Get Moving Hex", + "HexMasterCovenCanGetMovingHex": "Coven Can Get Moving Hex", "HexMasterMovingHexCooldown": "Moving Hex Pass Cooldown", + "HexesLookLikeSpells": "Hexes appear as spells", + "HexButtonText": "Hex", + "HexMasterModeHex": "Hex", + "HexMasterModeKill": "Kill", + "HexMasterModeDouble": "Double Click = Kill, Single Click = Hex", + + "JinxSpellTimes": "Amount of Jinx Spells", + "JinxCooldown": "Jinx Cooldown", + "JinxCovenCanDieToJinx": "Coven Can Die To Jinx", + + "PoisonCooldown": "Poison Cooldown", + "PoisonerKillDelay": "Poison Kill Delay", + "PoisonerTargetDead": "Target died", + "PoisonerPoisonButtonText": "Poison", + + "NecromancerRevengeTime": "Necronomicon Ability time", + "NecromancerRevenge": "You have {0}s to kill {1}", + "NecromancerSuccess": "Necromancy complete! You live to see another day.", + "NecromancerHide": "Venting is disabled, hide from the Necromancer!", + "NecromancerAbilityDuration": "Necromancy Duration", + "NecromancerAbilityCooldown": "Necromancy Cooldown", "LuckyProbability": "Probability of surviving a kill", @@ -2328,9 +2346,6 @@ "BecomeMadmateCuzMadmateMode": "You became a Madmate because you died", "CleanerCleanBody": "The body has been cleaned", "QuickShooterStoraging": "Bullets stored successfully", - "PoisonerTargetDead": "Target died", - "HexesLookLikeSpells": "Hexes appear as spells", - "HexButtonText": "Hex", "BloodthirstAdded": "Your bloodthirst is now active!", "WarlockNoTarget": "Manipulation failed due to no target", "WarlockNoTargetYet": "You haven't marked a target.", @@ -2621,11 +2636,7 @@ "WitchCurrentMode": "Current Mode", "WitchModeKill": "Kill", "WitchModeSpell": "Spell", - "HexMasterModeHex": "Hex", - "HexMasterModeKill": "Kill", - "PoisonerPoisonButtonText": "Poison", "WitchModeDouble": "Double Click = Kill, Single Click = Spell", - "HexMasterModeDouble": "Double Click = Kill, Single Click = Hex", "BountyCurrentTarget": "Current Target", "Roles": "Roles", "Settings": "Settings", @@ -3139,10 +3150,6 @@ "DoppelMaxSteals": "Maximum Steals", "DoppelCurrentVictimCanSeeRolesAsDead": "Last victim can see role and add-on info of alive players as a ghost", - "NecromancerRevengeTime": "Necromancy time", - "NecromancerRevenge": "You have {0}s to kill {1}", - "NecromancerSuccess": "Necromancy complete! You live to see another day.", - "NecromancerHide": "Venting is disabled, hide from the Necromancer!", "RetributionistDeadMsg": "The death of the Retributionist means the beginning of retribution. \nPlease use /ret + [player ID] to kill the specified player \nYou can see player IDs in front of their names. \nOr type /ret to get a list of player IDs", "RetributionistAliveKill": "Retribution for the Retributionist may only begin after their death.", "RetributionistKillMax": "You've reached the maximum amount of kills. You can't kill anymore!", diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index 10c349a76..3dd61276e 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -1,6 +1,10 @@ using AmongUs.GameOptions; +using TOHE.Roles.Core; +using MS.Internal.Xml.XPath; using static TOHE.Options; using static TOHE.Translator; +using TOHE.Roles.AddOns; +using Rewired; namespace TOHE.Roles.Coven; @@ -11,7 +15,7 @@ internal class Necromancer : CovenManager private static readonly HashSet playerIdList = []; public static bool HasEnabled => playerIdList.Any(); public override bool IsDesyncRole => true; - public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; //==================================================================\\ @@ -19,6 +23,9 @@ internal class Necromancer : CovenManager //private static OptionItem CanVent; //private static OptionItem HasImpostorVision; private static OptionItem RevengeTime; + private static OptionItem AbilityDuration; + private static OptionItem AbilityCooldown; + public static PlayerControl Killer = null; private static bool IsRevenge = false; @@ -35,6 +42,10 @@ public override void SetupCustomOption() .SetValueFormat(OptionFormat.Seconds); //CanVent = BooleanOptionItem.Create(Id + 12, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]); //HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]); + AbilityDuration = FloatOptionItem.Create(Id + 14, "NecromancerAbilityDuration", new(0f, 300f, 2.5f), 60f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]) + .SetValueFormat(OptionFormat.Seconds); + AbilityCooldown = FloatOptionItem.Create(Id + 15, "NecromancerAbilityCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]) + .SetValueFormat(OptionFormat.Seconds); } public override void Init() { @@ -51,14 +62,20 @@ public override void Add(byte playerId) } //public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - public override bool CanUseKillButton(PlayerControl pc) => true; + public override bool CanUseKillButton(PlayerControl pc) => HasNecronomicon(pc); //public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ShapeshifterCooldown = AbilityCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = AbilityDuration.GetFloat(); + } public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { if (IsRevenge) return true; - if (killer.IsPlayerCoven()) return false; + if (killer.IsPlayerCoven()) return true; + if (!HasNecronomicon(target)) return true; if ((killer.Is(CustomRoles.Retributionist) || killer.Is(CustomRoles.Nemesis)) && !killer.IsAlive()) return true; _ = new LateTask(target.RpcRandomVentTeleport, 0.01f, "Random Vent Teleport - Necromancer"); @@ -93,6 +110,68 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return false; } } + public override bool OnCheckShapeshift(PlayerControl nm, PlayerControl target, ref bool resetCooldown, ref bool shouldAnimate) + { + resetCooldown = true; + var deadPlayers = Main.AllPlayerControls.Where(x => !x.IsAlive()); + CustomRoles[] deadRoles = new CustomRoles[deadPlayers.Count()]; + foreach (var deadPlayer in deadPlayers) { + if (BlackList(deadPlayer.GetCustomRole())) continue; + deadRoles.AddItem(deadPlayer.GetCustomRole()); + } + if (deadRoles.Length < 0) return false; + var role = deadRoles.RandomElement(); + nm.RpcChangeRoleBasis(role); + nm.RpcSetCustomRole(role); + nm.GetRoleClass()?.OnAdd(nm.PlayerId); + nm.RpcSetCustomRole(CustomRoles.Enchanted); + nm.AddInSwitchAddons(nm, CustomRoles.Enchanted); + nm.SyncSettings(); + Main.PlayerStates[nm.PlayerId].InitTask(nm); + nm.RpcGuardAndKill(nm); + nm.Notify(string.Format(GetString("CopyCatRoleChange"), Utils.GetRoleName(role))); + return false; + } + public override void OnShapeshift(PlayerControl pc, PlayerControl target, bool IsAnimate, bool shapeshifting) + { + IsAnimate = false; + if (!shapeshifting) + { + if (pc.GetCustomRole() != CustomRoles.Necromancer) + { + pc.GetRoleClass()?.OnRemove(pc.PlayerId); + } + Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Enchanted); + pc.RpcChangeRoleBasis(CustomRoles.Necromancer); + pc.RpcSetCustomRole(CustomRoles.Necromancer); + pc.ResetKillCooldown(); + pc.RpcGuardAndKill(pc); + pc.Notify(string.Format(GetString("CopyCatRoleChange"), Utils.GetRoleName(CustomRoles.Necromancer))); + } + } + private static bool BlackList(CustomRoles role) + { + return role.IsNA() || role.IsGhostRole() || role is + CustomRoles.Veteran or + CustomRoles.Solsticer or + CustomRoles.Lawyer or + CustomRoles.Amnesiac or + CustomRoles.Imitator or + CustomRoles.CopyCat or + CustomRoles.Executioner or + CustomRoles.Follower or + CustomRoles.Romantic or + CustomRoles.God or + CustomRoles.Innocent or + CustomRoles.Jackal or + CustomRoles.Marshall or + CustomRoles.Captain or + CustomRoles.Retributionist or + CustomRoles.Nemesis or + CustomRoles.NiceMini or + CustomRoles.Mini or + CustomRoles.EvilMini; + } private static void Countdown(int seconds, PlayerControl player) { var killer = Killer; From 1bfdba607339f68c9cd7069b62fef4391557703d Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 9 Nov 2024 20:24:37 -0500 Subject: [PATCH 023/101] jinx rework --- Roles/Coven/Jinx.cs | 105 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 5 deletions(-) diff --git a/Roles/Coven/Jinx.cs b/Roles/Coven/Jinx.cs index f4acb5d34..f98146724 100644 --- a/Roles/Coven/Jinx.cs +++ b/Roles/Coven/Jinx.cs @@ -1,7 +1,11 @@ -using AmongUs.GameOptions; +using AmongUs.GameOptions; using UnityEngine; using static TOHE.Options; +using static TOHE.Utils; +using static TOHE.Translator; +using InnerNet; using TOHE.Roles.Core; +using Hazel; namespace TOHE.Roles.Coven; @@ -19,7 +23,11 @@ internal class Jinx : CovenManager //private static OptionItem CanVent; //private static OptionItem HasImpostorVision; private static OptionItem JinxSpellTimes; - private static OptionItem killAttacker; + //private static OptionItem killAttacker; + private static OptionItem CovenCanDieToJinx; + + + private static readonly Dictionary> JinxedPlayers = []; public override void SetupCustomOption() { @@ -28,16 +36,24 @@ public override void SetupCustomOption() .SetValueFormat(OptionFormat.Seconds); //CanVent = BooleanOptionItem.Create(Id + 11, GeneralOption.CanVent, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); //HasImpostorVision = BooleanOptionItem.Create(Id + 13, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); - JinxSpellTimes = IntegerOptionItem.Create(Id + 14, "JinxSpellTimes", new(1, 15, 1), 3, TabGroup.CovenRoles, false) + JinxSpellTimes = IntegerOptionItem.Create(Id + 14, "JinxSpellTimes", new(1, 100, 1), 10, TabGroup.CovenRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]) .SetValueFormat(OptionFormat.Times); - killAttacker = BooleanOptionItem.Create(Id + 15, GeneralOption.KillAttackerWhenAbilityRemaining, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); + //killAttacker = BooleanOptionItem.Create(Id + 15, GeneralOption.KillAttackerWhenAbilityRemaining, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); + CovenCanDieToJinx = BooleanOptionItem.Create(Id + 16, "JinxCovenCanDieToJinx", true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); } + public override void Init() + { + JinxedPlayers.Clear(); + } public override void Add(byte playerId) { AbilityLimit = JinxSpellTimes.GetInt(); + JinxedPlayers[playerId] = []; + GetPlayerById(playerId)?.AddDoubleTrigger(); } + /* public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { if (AbilityLimit <= 0) return true; @@ -59,7 +75,86 @@ public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl t } return false; } + */ //public override void ApplyGameOptions(IGameOptions opt, byte babushka) => opt.SetVision(HasImpostorVision.GetBool()); + public bool IsJinxed(byte playerId) => JinxedPlayers[_Player.PlayerId].Contains(playerId); + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + return HasNecronomicon(killer) && killer.CheckDoubleTrigger(target, () => { + JinxPlayer(killer, target); + }); + } + private void JinxPlayer(PlayerControl jinx, PlayerControl target) + { + if (IsJinxed(target.PlayerId)) return; + if (CanJinx(jinx.PlayerId)) + { + JinxedPlayers[jinx.PlayerId].Add(target.PlayerId); + jinx.ResetKillCooldown(); + AbilityLimit--; + SendRPC(jinx, target); + } + } + public void SendRPC(PlayerControl player, PlayerControl target) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + writer.WriteNetObject(_Player); + writer.Write(AbilityLimit); + writer.Write(player.PlayerId); + writer.Write(target.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public override void ReceiveRPC(MessageReader reader, PlayerControl pc) + { + byte jinxID = reader.ReadByte(); + byte jinxedID = reader.ReadByte(); + JinxedPlayers[jinxID].Add(jinxedID); + } + public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) + { + if (!IsJinxed(target.PlayerId)) return false; + + var jinx = _Player; + if (!jinx.IsAlive() || jinx.PlayerId == target.PlayerId) return false; + + var killerRole = killer.GetCustomRole(); + // Not should kill + if (killerRole is CustomRoles.Taskinator + or CustomRoles.Bodyguard + or CustomRoles.Veteran + or CustomRoles.Deputy) + return false; + if (killer.IsPlayerCoven() && !CovenCanDieToJinx.GetBool()) return false; + + if (jinx.CheckForInvalidMurdering(killer) && jinx.RpcCheckAndMurder(killer, true)) + { + killer.RpcGuardAndKill(target); + killer.SetDeathReason(PlayerState.DeathReason.Jinx); + killer.RpcMurderPlayer(killer); + killer.SetRealKiller(jinx); + if (HasNecronomicon(jinx)) + { + target.SetDeathReason(PlayerState.DeathReason.Jinx); + target.RpcMurderPlayer(target); + target.SetRealKiller(jinx); + } + JinxedPlayers[jinx.PlayerId].Remove(target.PlayerId); + return true; + } + + if (killer.Is(CustomRoles.Pestilence)) + { + JinxedPlayers[jinx.PlayerId].Remove(target.PlayerId); + return false; + } + + + return false; + } + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) + => IsJinxed(seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.Jinx), "⌘") : string.Empty; + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; @@ -67,7 +162,7 @@ public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl t public override string GetProgressText(byte playerId, bool comms) - => Utils.ColorString(CanJinx(playerId) ? Utils.GetRoleColor(CustomRoles.Gangster).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); + => ColorString(CanJinx(playerId) ? GetRoleColor(CustomRoles.Jinx).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); private bool CanJinx(byte id) => AbilityLimit > 0; } From ef6a1740d9ccfc2097c20286ba75001c9a5eb036 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 9 Nov 2024 20:25:00 -0500 Subject: [PATCH 024/101] vm kill change --- Roles/Coven/VoodooMaster.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Coven/VoodooMaster.cs b/Roles/Coven/VoodooMaster.cs index 93b3a6719..c0454f4a3 100644 --- a/Roles/Coven/VoodooMaster.cs +++ b/Roles/Coven/VoodooMaster.cs @@ -71,7 +71,7 @@ public override string GetProgressText(byte playerId, bool comms) => ColorString(AbilityLimit >= 1 ? GetRoleColor(CustomRoles.VoodooMaster).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - return killer.CheckDoubleTrigger(target, () => { + return HasNecronomicon(killer) && killer.CheckDoubleTrigger(target, () => { if (AbilityLimit > 0 && (!target.IsPlayerCoven() || (target.IsPlayerCoven() && CanDollCoven.GetBool()))) { Dolls[killer.PlayerId].Add(target.PlayerId); From b61192db715fd5f1fc0e5180450f86c4347b4b1a Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 9 Nov 2024 20:25:15 -0500 Subject: [PATCH 025/101] add enchanted to admirer and gangster --- Roles/Coven/Ritualist.cs | 2 +- Roles/Crewmate/Admirer.cs | 10 +++++++++- Roles/Impostor/Gangster.cs | 8 ++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index e41bc0804..03d866a57 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -214,7 +214,7 @@ public static bool CheckCommond(ref string msg, string command, bool exact = tru } return false; } - private static bool CanBeConverted(PlayerControl pc) + public static bool CanBeConverted(PlayerControl pc) { return pc != null && (!pc.IsPlayerCoven() && !pc.Is(CustomRoles.Enchanted) && !pc.IsTransformedNeutralApocalypse()) && !pc.Is(CustomRoles.Soulless) && !pc.Is(CustomRoles.Lovers) && !pc.Is(CustomRoles.Loyal) && !((pc.Is(CustomRoles.NiceMini) || pc.Is(CustomRoles.EvilMini)) && Mini.Age < 18) diff --git a/Roles/Crewmate/Admirer.cs b/Roles/Crewmate/Admirer.cs index f6a3a6cf1..0ed388778 100644 --- a/Roles/Crewmate/Admirer.cs +++ b/Roles/Crewmate/Admirer.cs @@ -2,6 +2,7 @@ using TOHE.Roles.AddOns.Crewmate; using TOHE.Roles.AddOns.Impostor; using TOHE.Roles.Core; +using TOHE.Roles.Coven; using TOHE.Roles.Double; using TOHE.Roles.Neutral; using UnityEngine; @@ -107,7 +108,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } if (!killer.Is(CustomRoles.Madmate) && !killer.Is(CustomRoles.Recruit) && !killer.Is(CustomRoles.Charmed) - && !killer.Is(CustomRoles.Infected) && !killer.Is(CustomRoles.Contagious)) + && !killer.Is(CustomRoles.Infected) && !killer.Is(CustomRoles.Contagious) && !killer.Is(CustomRoles.Enchanted)) { Logger.Info("Set converted: " + target.GetNameWithRole().RemoveHtmlTags() + " to " + CustomRoles.Admired.ToString(), "Admirer Assign"); target.RpcSetCustomRole(CustomRoles.Admired); @@ -121,6 +122,13 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Madmate), GetString("AdmiredPlayer"))); target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Madmate), GetString("AdmirerAdmired"))); } + else if (killer.Is(CustomRoles.Enchanted) && Ritualist.CanBeConverted(target)) + { + Logger.Info("Set converted: " + target.GetNameWithRole().RemoveHtmlTags() + " to " + CustomRoles.Enchanted.ToString(), "Admirer Assign"); + target.RpcSetCustomRole(CustomRoles.Enchanted); + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Enchanted), GetString("AdmiredPlayer"))); + target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Enchanted), GetString("AdmirerAdmired"))); + } else if (killer.Is(CustomRoles.Recruit) && Jackal.CanBeSidekick(target)) { Logger.Info("Set converted: " + target.GetNameWithRole().RemoveHtmlTags() + " to " + CustomRoles.Recruit.ToString(), "Admirer Assign"); diff --git a/Roles/Impostor/Gangster.cs b/Roles/Impostor/Gangster.cs index 6548dc145..468e24cf2 100644 --- a/Roles/Impostor/Gangster.cs +++ b/Roles/Impostor/Gangster.cs @@ -6,6 +6,7 @@ using UnityEngine; using static TOHE.Translator; using TOHE.Roles.Core; +using TOHE.Roles.Coven; namespace TOHE.Roles.Impostor; @@ -91,6 +92,13 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t Admirer.AdmiredList[killer.PlayerId].Add(target.PlayerId); Admirer.SendRPC(killer.PlayerId, target.PlayerId); } + else if (killer.Is(CustomRoles.Enchanted) && Ritualist.CanBeConverted(target)) + { + Logger.Info("Set converted: " + target.GetNameWithRole().RemoveHtmlTags() + " to " + CustomRoles.Enchanted.ToString(), "Gangster Assign"); + target.RpcSetCustomRole(CustomRoles.Enchanted); + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Enchanted), GetString("GangsterSuccessfullyRecruited"))); + target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Enchanted), GetString("BeRecruitedByGangster"))); + } else if (killer.Is(CustomRoles.Recruit) && Jackal.CanBeSidekick(target)) { Logger.Info("Set converted: " + target.GetNameWithRole().RemoveHtmlTags() + " to " + CustomRoles.Recruit.ToString(), "Ganster Assign"); From 2a7cd176057badb21b170d00b15106be13870bc0 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:59:51 -0500 Subject: [PATCH 026/101] some changes, attempt to fix necronomicon (it didnt work) --- Modules/DisableDevice.cs | 2 + Modules/Utils.cs | 1 + Patches/CheckGameEndPatch.cs | 1 + Patches/PlayerControlPatch.cs | 5 ++ Resources/Lang/en_US.json | 10 ++-- Roles/Core/CustomRoleManager.cs | 1 + Roles/Coven/CovenManager.cs | 10 ++-- Roles/Coven/HexMaster.cs | 6 +-- Roles/Coven/Necromancer.cs | 88 +++++++++++++++++++++++---------- Roles/Coven/PotionMaster.cs | 58 ++++++++++------------ Roles/Coven/Sacrifist.cs | 42 +++++++--------- 11 files changed, 128 insertions(+), 96 deletions(-) diff --git a/Modules/DisableDevice.cs b/Modules/DisableDevice.cs index d2ad437e3..c08f32b9a 100644 --- a/Modules/DisableDevice.cs +++ b/Modules/DisableDevice.cs @@ -60,6 +60,7 @@ public static void FixedUpdate() bool ignore = (Options.DisableDevicesIgnoreImpostors.GetBool() && pc.Is(Custom_Team.Impostor)) || (Options.DisableDevicesIgnoreNeutrals.GetBool() && pc.Is(Custom_Team.Neutral)) || (Options.DisableDevicesIgnoreCrewmates.GetBool() && pc.Is(Custom_Team.Crewmate)) || + (Options.DisableDevicesIgnoreCoven.GetBool() && pc.Is(Custom_Team.Coven)) || (Options.DisableDevicesIgnoreAfterAnyoneDied.GetBool() && GameStates.AlreadyDied); var mapId = Utils.GetActiveMapId(); @@ -157,6 +158,7 @@ public static void UpdateDisableDevices() (Options.DisableDevicesIgnoreImpostors.GetBool() && player.Is(Custom_Team.Impostor)) || (Options.DisableDevicesIgnoreNeutrals.GetBool() && player.Is(Custom_Team.Neutral)) || (Options.DisableDevicesIgnoreCrewmates.GetBool() && player.Is(Custom_Team.Crewmate)) || + (Options.DisableDevicesIgnoreCoven.GetBool() && player.Is(Custom_Team.Coven)) || (Options.DisableDevicesIgnoreAfterAnyoneDied.GetBool() && GameStates.AlreadyDied); var admins = UnityEngine.Object.FindObjectsOfType(true); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 57b66453b..964a62182 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2031,6 +2031,7 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl else if (seerRole.IsCrewmate()) { RoleText = ColorString(GetTeamColor(seer), GetString("TeamCrewmate")); } else if (seerRole.IsNeutral()) { RoleText = ColorString(GetTeamColor(seer), GetString("TeamNeutral")); } else if (seerRole.IsMadmate()) { RoleText = ColorString(GetTeamColor(seer), GetString("TeamMadmate")); } + else if (seerRole.IsCoven()) { RoleText = ColorString(GetTeamColor(seer), GetString("TeamCoven")); } SelfName = $"{SelfName}\n \n{Font}{ColorString(seer.GetRoleColor(), RoleText)}\n{ColorString(seer.GetRoleColor(), seer.GetRoleInfo())}\n"; } diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 5e4b8f452..a68457f13 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -483,6 +483,7 @@ private static IEnumerator CoEndGame(AmongUsClient self, GameOverReason reason) { CustomRoleManager.AllEnabledRoles.Do(roleClass => roleClass.OnCoEndGame()); ForEndGame = true; + CovenManager.necroHolder = byte.MaxValue; // Set ghost role List ReviveRequiredPlayerIds = []; diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 271929b8b..947f79b2c 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1316,6 +1316,7 @@ public static Task DoPostfix(PlayerControl __instance) } } + Mark.Append(seerRoleClass?.GetMark(seer, target, false)); Mark.Append(CustomRoleManager.GetMarkOthers(seer, target, false)); @@ -1331,6 +1332,10 @@ public static Task DoPostfix(PlayerControl __instance) if (target.Is(CustomRoles.Snitch) && target.Is(CustomRoles.Madmate)) Mark.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), "★")); } + if ((seer.IsPlayerCoven() && target.IsPlayerCoven()) && (CovenManager.HasNecronomicon(target) || CovenManager.HasNecronomicon(seer))) + { + Mark.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Coven), "♣")); + } if (target.Is(CustomRoles.Cyber) && Cyber.CyberKnown.GetBool()) Mark.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cyber), "★")); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 0e5669009..815a24785 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -640,11 +640,11 @@ "VengefulRomanticInfo": "Revenge your partner to win together", "RuthlessRomanticInfo": "Kill everyone to win with your partner", "WraithInfo": "Vent to go invisible temporarily", - "PoisonerInfo": "Kill everyone with delayed kills", + "PoisonerInfo": "Make players unable to use their ability", "HexMasterInfo": "Hex players to kill them in meetings", - "JinxInfo": "Reflect attacks onto your attackers", + "JinxInfo": "Players \"accidentally\" die upon trying to kill your target", "PotionMasterInfo": "Use your potions to your advantage", - "NecromancerInfo": "Kill your killer to defy death", + "NecromancerInfo": "Use the dead to your advantage", "CovenLeaderInfo": "Help your teammates by retraining them", "RitualistInfo": "Perform Blood Rituals to Enchant other players!", "ConjurerInfo": "Blast away your enemies!", @@ -958,7 +958,7 @@ "JinxInfoLong": "(Coven):\nThe Jinx can use their kill button to Jinx a player. Anyone who interacts with the Jinxed player will die with the death reason Jinxed.\nWith the Necronomicon the Jinx can double-kill to kill normally. Also, the Jinxed player and the player who interacted with the Jinxed person will die.", "MedusaInfoLong": "(Coven):\nThe Medusa can use their kill button on players to mark them as Stoned. When the Medusa clicks the Shapeshift button, all Stoned players will be unable to move and will have reduced vision for a configurable amount of time.\nWith the Necronomicon, killed players will be unreportable.", "PotionMasterInfoLong": "(Coven):\nThe Potion Master has two potions available for their use. The Reveal potion reveals a player's role. The Barrier potion places a shield on a player for one round, the player will not be notified of this unless they are Coven as well. Click the Shapeshift button to change potions.\nWith the Necronomicon, the Potion Master can double-click their kill button to kill.", - "NecromancerInfoLong": "(Coven):\nAs the Necromancer, you can shapeshift to become the role of a random dead person for a set duration.\nSome roles can not be used.\nWith the Necronomicon, when someone tries to kill you, you will block the kill and be teleported to a random vent. You have a limited time to kill your killer. If the time runs out or you try to kill someone else, you die.", + "NecromancerInfoLong": "(Coven):\nAs the Necromancer, you can shapeshift to become the role of a random dead person for a set duration.\nSome roles can not be used.\nOnce a role is used, it can not be used the rest of the game.\nWith the Necronomicon, when someone tries to kill you, you will block the kill and be teleported to a random vent. You have a limited time to kill your killer. If the time runs out or you try to kill someone else, you die.", "CovenLeaderInfoLong": "(Coven):\nThe Coven Leader can use their kill button on a fellow Coven member to retrain them into a random Coven role that isn’t currently in the game. During the next meeting, that Coven member will be notified that the Coven Leader wishes to retrain them. They can vote themselves to accept the retrain, or vote otherwise to deny it. Denying the retrain does not take away an ability usage.\nWith the Necronomicon, you cannot retrain, and can only kill other players.", "RitualistInfoLong": "(Coven):\nDuring a meeting, the Ritualist can perform a Blood Ritual to guess a player’s exact role. If the Ritualist is correct, that player will be converted to Coven. If the Ritualist is incorrect, they will not die but will be unable to Blood Ritual until the next meeting.\nhe command is /rt id role.\nWith the Necronomicon, the Ritualist can kill.", "ConjurerInfoLong": "(Coven):\nShapeshift once to mark a location.\nShapeshift again to conjure a meteor at the place you marked, killing everyone in the radius.\nWith the Necronomicon, you can kill. You can also mark a player using the Shapeshift menu. When the Conjurer clicks the shapeshift button again, all the players in the radius of the marked player will die, including the marked player.", @@ -2080,6 +2080,8 @@ "NecromancerHide": "Venting is disabled, hide from the Necromancer!", "NecromancerAbilityDuration": "Necromancy Duration", "NecromancerAbilityCooldown": "Necromancy Cooldown", + "NecromancerCooldownNotDone": "Ability is still on cooldown!", + "NecromancerNoUsableRoles": "No usable roles", "LuckyProbability": "Probability of surviving a kill", diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 65cdcb776..f1f8dd1fd 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -423,6 +423,7 @@ public static void CheckDeadBody(PlayerControl killer, PlayerControl deadBody, b public static void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { player.GetRoleClass()?.OnFixedUpdate(player, lowLoad, nowTime); + CovenManager.NecronomiconCheck(); if (!OnFixedUpdateOthers.Any()) return; //Execute other viewpoint processing if any diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 8873a475b..0266eebb0 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -56,7 +56,7 @@ private static void SetUpVentOption(CustomRoles role, int Id, bool defaultValue /* public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => HasNecronomicon(seen) ? ColorString(GetRoleColor(CustomRoles.CovenLeader), "♣") : string.Empty; - */ + public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { if (HasNecronomicon(target) && seer.IsPlayerCoven()) @@ -65,6 +65,7 @@ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, b } return string.Empty; } + */ private static void SendRPC(byte playerId) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Necronomicon, SendOption.Reliable, -1); @@ -123,11 +124,8 @@ public static void GiveNecronomicon(byte target) GetPlayerById(necroHolder).Notify(GetString("NecronomiconNotification")); SendRPC(necroHolder); } - public override void OnCoEndGame() - { - necroHolder = byte.MaxValue; - } - public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) + + public static void NecronomiconCheck() { if (necroHolder == byte.MaxValue || !GetPlayerById(necroHolder).IsAlive() || !GetPlayerById(necroHolder).IsPlayerCoven()) { diff --git a/Roles/Coven/HexMaster.cs b/Roles/Coven/HexMaster.cs index 613c43aed..3bce48061 100644 --- a/Roles/Coven/HexMaster.cs +++ b/Roles/Coven/HexMaster.cs @@ -46,12 +46,12 @@ public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.HexMaster, 1, zeroOne: false); //ModeSwitchAction = StringOptionItem.Create(Id + 10, GeneralOption.ModeSwitchAction, EnumHelper.GetAllNames(), 2, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); - HexesLookLikeSpells = BooleanOptionItem.Create(Id + 11, "HexesLookLikeSpells", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); - HexCooldown = FloatOptionItem.Create(Id + 13, "HexMasterCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]) + HexCooldown = FloatOptionItem.Create(Id + 13, "HexMasterHexCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]) .SetValueFormat(OptionFormat.Seconds); - CovenCanGetMovingHex = BooleanOptionItem.Create(Id + 14, "HexMasterCovenCanGetMovingHex", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); MovingHexPassCooldown = FloatOptionItem.Create(Id + 15, "HexMasterMovingHexCooldown", new(0f, 5f, 0.25f), 1f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]) .SetValueFormat(OptionFormat.Seconds); + CovenCanGetMovingHex = BooleanOptionItem.Create(Id + 14, "HexMasterCovenCanGetMovingHex", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); + HexesLookLikeSpells = BooleanOptionItem.Create(Id + 11, "HexesLookLikeSpells", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); //HasImpostorVision = BooleanOptionItem.Create(Id + 12, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); } public override void Init() diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index 3dd61276e..b0b272fa2 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -5,6 +5,7 @@ using static TOHE.Translator; using TOHE.Roles.AddOns; using Rewired; +using UnityEngine; namespace TOHE.Roles.Coven; @@ -33,6 +34,10 @@ internal class Necromancer : CovenManager private static bool Success = false; private static float tempKillTimer = 0; + private static readonly Dictionary> UsedRoles = []; + private static float AbilityTimer; + private static bool canUseAbility; + public override void SetupCustomOption() { SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.Necromancer, 1, zeroOne: false); @@ -54,22 +59,20 @@ public override void Init() Success = false; Killer = null; tempKillTimer = 0; + UsedRoles.Clear(); + canUseAbility = false; + AbilityTimer = 0; } public override void Add(byte playerId) { playerIdList.Add(playerId); Timer = RevengeTime.GetInt(); + UsedRoles[playerId] = []; } //public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - public override bool CanUseKillButton(PlayerControl pc) => HasNecronomicon(pc); + public override bool CanUseKillButton(PlayerControl pc) => HasNecronomicon(pc) || IsRevenge; //public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); - public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.ShapeshifterCooldown = AbilityCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = AbilityDuration.GetFloat(); - } public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { @@ -110,16 +113,30 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return false; } } - public override bool OnCheckShapeshift(PlayerControl nm, PlayerControl target, ref bool resetCooldown, ref bool shouldAnimate) + public override string GetLowerText(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) { - resetCooldown = true; + return GetString("NecromancerAbilityCooldown") + ": " + AbilityTimer.ToString() + "s / " + AbilityCooldown.GetFloat().ToString() + "s"; + } + public override void UnShapeShiftButton(PlayerControl nm) + { + if (nm == null) return; + if (!canUseAbility) { + nm.Notify(GetString("NecromancerCooldownNotDone")); + return; + } var deadPlayers = Main.AllPlayerControls.Where(x => !x.IsAlive()); - CustomRoles[] deadRoles = new CustomRoles[deadPlayers.Count()]; - foreach (var deadPlayer in deadPlayers) { + List deadRoles = []; + foreach (var deadPlayer in deadPlayers) + { if (BlackList(deadPlayer.GetCustomRole())) continue; - deadRoles.AddItem(deadPlayer.GetCustomRole()); + if (UsedRoles[nm.PlayerId].Contains(deadPlayer.GetCustomRole())) continue; + deadRoles.Add(deadPlayer.GetCustomRole()); + } + if (deadRoles.Count < 1) + { + nm.Notify(GetString("NecromancerNoUsableRoles")); + return; } - if (deadRoles.Length < 0) return false; var role = deadRoles.RandomElement(); nm.RpcChangeRoleBasis(role); nm.RpcSetCustomRole(role); @@ -130,24 +147,33 @@ public override bool OnCheckShapeshift(PlayerControl nm, PlayerControl target, r Main.PlayerStates[nm.PlayerId].InitTask(nm); nm.RpcGuardAndKill(nm); nm.Notify(string.Format(GetString("CopyCatRoleChange"), Utils.GetRoleName(role))); - return false; - } - public override void OnShapeshift(PlayerControl pc, PlayerControl target, bool IsAnimate, bool shapeshifting) - { - IsAnimate = false; - if (!shapeshifting) + _ = new LateTask(() => { - if (pc.GetCustomRole() != CustomRoles.Necromancer) + if (nm.GetCustomRole() != CustomRoles.Necromancer) { - pc.GetRoleClass()?.OnRemove(pc.PlayerId); + nm.GetRoleClass()?.OnRemove(nm.PlayerId); } - Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Enchanted); - pc.RpcChangeRoleBasis(CustomRoles.Necromancer); - pc.RpcSetCustomRole(CustomRoles.Necromancer); - pc.ResetKillCooldown(); - pc.RpcGuardAndKill(pc); - pc.Notify(string.Format(GetString("CopyCatRoleChange"), Utils.GetRoleName(CustomRoles.Necromancer))); + Main.PlayerStates[nm.PlayerId].RemoveSubRole(CustomRoles.Enchanted); + nm.RpcChangeRoleBasis(CustomRoles.Necromancer); + nm.RpcSetCustomRole(CustomRoles.Necromancer); + nm.ResetKillCooldown(); + nm.SyncSettings(); + nm.RpcGuardAndKill(nm); + nm.Notify(string.Format(GetString("CopyCatRoleChange"), Utils.GetRoleName(CustomRoles.Necromancer))); + UsedRoles[nm.PlayerId].Add(role); + canUseAbility = false; + AbilityTimer = 0; + }, AbilityDuration.GetFloat(), "Necromancer Revert Role"); + } + public override void OnCoEndGame() + { + if (_Player.GetCustomRole() != CustomRoles.Necromancer) + { + _Player.GetRoleClass()?.OnRemove(_Player.PlayerId); } + Main.PlayerStates[_Player.PlayerId].RemoveSubRole(CustomRoles.Enchanted); + _Player.RpcChangeRoleBasis(CustomRoles.Necromancer); + _Player.RpcSetCustomRole(CustomRoles.Necromancer); } private static bool BlackList(CustomRoles role) { @@ -172,6 +198,14 @@ CustomRoles.NiceMini or CustomRoles.Mini or CustomRoles.EvilMini; } + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) + { + if (AbilityTimer < AbilityCooldown.GetFloat()) + { + AbilityTimer += Time.fixedDeltaTime; + } + else canUseAbility = true; + } private static void Countdown(int seconds, PlayerControl player) { var killer = Killer; diff --git a/Roles/Coven/PotionMaster.cs b/Roles/Coven/PotionMaster.cs index e8cf92331..f285ae378 100644 --- a/Roles/Coven/PotionMaster.cs +++ b/Roles/Coven/PotionMaster.cs @@ -65,34 +65,35 @@ public override void Add(byte playerId) pc?.AddDoubleTrigger(); } - private void SendRPC(byte playerId, byte targetId, int operate) + private static void SendRPC(byte typeId, PlayerControl player, PlayerControl target) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); - writer.WriteNetObject(_Player); - writer.Write(operate); - writer.Write(playerId); - writer.Write(targetId); - if (operate == 0) - writer.Write(RevealLimit[playerId]); - else - writer.Write(BarrierLimit[playerId]); + if (!player.IsNonHostModdedClient()) return; + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable); + writer.WriteNetObject(player); + writer.Write(typeId); + writer.Write(player.PlayerId); + writer.Write(target.PlayerId); + if (typeId == 0) writer.Write(RevealLimit[player.PlayerId]); + else if (typeId == 1) writer.Write(BarrierLimit[player.PlayerId]); AmongUsClient.Instance.FinishRpcImmediately(writer); } - public override void ReceiveRPC(MessageReader reader, PlayerControl pc) + public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) { - int operate = reader.ReadInt32(); + byte typeId = reader.ReadByte(); byte playerId = reader.ReadByte(); - if (operate == 0) - { - RevealLimit[playerId] = reader.ReadInt32(); - RevealList[playerId].Add(reader.ReadByte()); - } - if (operate == 1) + byte targetId = reader.ReadByte(); + + switch (typeId) { - BarrierLimit[playerId] = reader.ReadInt32(); - BarrierList[playerId].Add(reader.ReadByte()); + case 0: + RevealList[playerId].Add(targetId); + RevealLimit[playerId] = reader.ReadInt32(); + break; + case 1: + BarrierList[playerId].Add(targetId); + BarrierLimit[playerId] = reader.ReadInt32(); + break; } - } //public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); @@ -113,14 +114,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } } - public static bool IsReveal(byte seer, byte target) - { - if (RevealList[seer].Contains(target)) - { - return true; - } - return false; - } + public static bool IsReveal(byte seer, byte target) => RevealList[seer].Contains(target); private void SetRitual(PlayerControl killer, PlayerControl target) { switch (PotionMode) @@ -136,8 +130,8 @@ private void SetRitual(PlayerControl killer, PlayerControl target) RevealList[killer.PlayerId].Add(target.PlayerId); Logger.Info($"{killer.GetNameWithRole()}: Divined divination destination -> {target.GetNameWithRole()} || remaining {RevealLimit[killer.PlayerId]} times", "PotionMaster"); - Utils.NotifyRoles(SpecifySeer: killer); - SendRPC(killer.PlayerId, target.PlayerId, 0); + NotifyRoles(SpecifySeer: killer); + SendRPC(PotionMode, killer, target); killer.SetKillCooldown(); } @@ -153,7 +147,7 @@ private void SetRitual(PlayerControl killer, PlayerControl target) BarrierList[killer.PlayerId].Add(target.PlayerId); Logger.Info($"{killer.GetNameWithRole()}: Barrier destination -> {target.GetNameWithRole()} || remaining {BarrierLimit[killer.PlayerId]} times", "PotionMaster"); - SendRPC(killer.PlayerId, target.PlayerId, 1); + SendRPC(PotionMode, killer, target); killer.SetKillCooldown(); } diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index 8da186c0c..d5a80095e 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -226,8 +226,25 @@ public override void UnShapeShiftButton(PlayerControl pc) Logger.Info($"{pc.GetRealName()} and {randPlayerPC.GetRealName} will randomly freeze for duration", "Sacrifist"); pc.Notify(string.Format(GetString("SacrifistFreezeDebuff"), RandomFreezeDuration.GetFloat()), RandomFreezeDuration.GetFloat()); break; - // Swap Sacrifist and Target (done in different method) + // Swap Sacrifist and Target case 9: + _ = new LateTask(() => + { + var randPlayerPC = GetPlayerById(randPlayer); + if (pc.CanBeTeleported() && randPlayerPC.CanBeTeleported()) + { + var originPs = randPlayerPC.GetCustomPosition(); + randPlayerPC.RpcTeleport(pc.GetCustomPosition()); + pc.RpcTeleport(originPs); + + pc.RPCPlayCustomSound("Teleport"); + randPlayerPC.RPCPlayCustomSound("Teleport"); + } + else + { + pc.Notify(ColorString(GetRoleColor(CustomRoles.Sacrifist), GetString("ErrorTeleport"))); + } + }, 0.01f, "Sacrifist Swap"); Logger.Info($"{pc.GetRealName()} Will Swap with {randPlayerPC.GetRealName} 5s after exiting vent", "Sacrifist"); pc.Notify(GetString("SacrifistSwapDebuff"), 15f); break; @@ -236,29 +253,6 @@ public override void UnShapeShiftButton(PlayerControl pc) debuffTimer = 0; } } - public override void OnExitVent(PlayerControl pc, int ventId) - { - if (DebuffID == 9) - { - _ = new LateTask(() => - { - var randPlayerPC = GetPlayerById(randPlayer); - if (pc.CanBeTeleported() && randPlayerPC.CanBeTeleported()) - { - var originPs = randPlayerPC.GetCustomPosition(); - randPlayerPC.RpcTeleport(pc.GetCustomPosition()); - pc.RpcTeleport(originPs); - - pc.RPCPlayCustomSound("Teleport"); - randPlayerPC.RPCPlayCustomSound("Teleport"); - } - else - { - pc.Notify(ColorString(GetRoleColor(CustomRoles.Sacrifist), GetString("ErrorTeleport"))); - } - }, 3f, "Sacrifist Swap"); - } - } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { var sacrifist = _Player.PlayerId; From d8c8f04b1375b558f0afbd29f6bc04f47180077c Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 11 Nov 2024 13:20:11 -0500 Subject: [PATCH 027/101] FINALLY FIXED NECRONOMICON --- Patches/PlayerControlPatch.cs | 1 + Roles/Core/CustomRoleManager.cs | 1 - Roles/Coven/CovenManager.cs | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 947f79b2c..34a8f0a8b 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1106,6 +1106,7 @@ public static Task DoPostfix(PlayerControl __instance) DoubleTrigger.OnFixedUpdate(player); KillTimerManager.FixedUpdate(player); + CovenManager.NecronomiconCheck(); //Mini's count down needs to be done outside if intask if we are counting meeting time if (GameStates.IsInGame && player.GetRoleClass() is Mini min) diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index f1f8dd1fd..65cdcb776 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -423,7 +423,6 @@ public static void CheckDeadBody(PlayerControl killer, PlayerControl deadBody, b public static void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { player.GetRoleClass()?.OnFixedUpdate(player, lowLoad, nowTime); - CovenManager.NecronomiconCheck(); if (!OnFixedUpdateOthers.Any()) return; //Execute other viewpoint processing if any diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 0266eebb0..ad2381cf0 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -69,7 +69,6 @@ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, b private static void SendRPC(byte playerId) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Necronomicon, SendOption.Reliable, -1); - writer.WriteNetObject(GetPlayerById(playerId)); writer.Write(playerId); AmongUsClient.Instance.FinishRpcImmediately(writer); } From db20ed6ffeaca219009a32a85e05941200338a5a Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 11 Nov 2024 13:32:48 -0500 Subject: [PATCH 028/101] remove random freezing on sacrifist --- Resources/Lang/en_US.json | 1 - Roles/Coven/Sacrifist.cs | 68 +++++++-------------------------------- 2 files changed, 12 insertions(+), 57 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 815a24785..322b8e62f 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2033,7 +2033,6 @@ "SacrifistReportDebuff": "Can't Report Bodies", "SacrifistTasksDebuff": "Tasks Reset for Target", "SacrifistSwapSkinsDebuff": "Swapped Skins", - "SacrifistFreezeDebuff": "Random Freezing for {0} seconds", "SacrifistSwapDebuff": "Swapping with target after 3 seconds", "SacrifistVisionRevert": "Vision Reverted", "SacrifistSpeedRevert": "Speed Reverted", diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index d5a80095e..c969f2949 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -28,16 +28,15 @@ internal class Sacrifist : CovenManager private static OptionItem Speed; private static OptionItem SpeedDuration; private static OptionItem IncreasedCooldown; - private static OptionItem RandomFreezeDuration; private static byte DebuffID = 10; private static float debuffTimer; private static float maxDebuffTimer; - private static float freezeTimer; private static byte randPlayer; - private static bool isFreezing; private static readonly Dictionary originalSpeed = []; private static readonly Dictionary OriginalPlayerSkins = []; + private static readonly Dictionary> VisionChange = []; + @@ -56,8 +55,6 @@ public override void SetupCustomOption() .SetValueFormat(OptionFormat.Seconds); IncreasedCooldown = FloatOptionItem.Create(Id + 15, "SacrifistIncreasedCooldown", new(0f, 100f, 2.5f), 50f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) .SetValueFormat(OptionFormat.Percent); - RandomFreezeDuration = FloatOptionItem.Create(Id + 16, "SacrifistFreezeDuration", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) - .SetValueFormat(OptionFormat.Seconds); DeathsAfterVote = IntegerOptionItem.Create(Id + 11, "SacrifistDeathsAfterVote", new(0, 15, 1), 0, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) .SetValueFormat(OptionFormat.Players); NecroReducedCooldown = FloatOptionItem.Create(Id + 12, "SacrifistNecroReducedCooldown", new(0f, 100f, 2.5f), 50f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Sacrifist]) @@ -70,13 +67,13 @@ public override void Init() randPlayer = byte.MaxValue; originalSpeed.Clear(); OriginalPlayerSkins.Clear(); + VisionChange.Clear(); } public override void Add(byte playerId) { debuffTimer = 0; maxDebuffTimer = DebuffCooldown.GetFloat(); - freezeTimer = 0; - isFreezing = false; + VisionChange[playerId] = []; } public void SendRPC(PlayerControl pc) { @@ -96,7 +93,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) public override void UnShapeShiftButton(PlayerControl pc) { var rand = IRandom.Instance; - DebuffID = (byte)rand.Next(0, 10); + DebuffID = (byte)rand.Next(0, 9); if (randPlayer == byte.MaxValue) { randPlayer = Main.AllAlivePlayerControls.Where(x => !x.Is(Custom_Team.Coven) && !x.Is(CustomRoles.Enchanted)).ToList().RandomElement().PlayerId; @@ -148,9 +145,13 @@ public override void UnShapeShiftButton(PlayerControl pc) break; // Change Vision case 1: + VisionChange[sacrifist].Add(sacrifist); + VisionChange[sacrifist].Add(randPlayer); pc.Notify(GetString("SacrifistVisionDebuff"), VisionDuration.GetFloat()); _ = new LateTask(() => { + VisionChange[sacrifist].Remove(sacrifist); + VisionChange[sacrifist].Remove(randPlayer); DebuffID = 10; pc.Notify(GetString("SacrifistVisionRevert"), 5f); }, VisionDuration.GetFloat(), "Sacrifist Revert Vision"); @@ -220,14 +221,8 @@ public override void UnShapeShiftButton(PlayerControl pc) pc.Notify(GetString("SacrifistSwapSkinsDebuff"), 5f); Logger.Info($"{pc.GetRealName()} swapped outfit with {randPlayerPC.GetRealName}", "Sacrifist"); break; - // Random Freezing (done in different method) - case 8: - isFreezing = true; - Logger.Info($"{pc.GetRealName()} and {randPlayerPC.GetRealName} will randomly freeze for duration", "Sacrifist"); - pc.Notify(string.Format(GetString("SacrifistFreezeDebuff"), RandomFreezeDuration.GetFloat()), RandomFreezeDuration.GetFloat()); - break; // Swap Sacrifist and Target - case 9: + case 8: _ = new LateTask(() => { var randPlayerPC = GetPlayerById(randPlayer); @@ -289,7 +284,8 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf } public static void SetVision(PlayerControl player, IGameOptions opt) { - if ((player.PlayerId == randPlayer || player.PlayerId == Utils.GetPlayerListByRole(CustomRoles.Sacrifist).First().PlayerId) && DebuffID == 1) + if (VisionChange.Any(a => a.Value.Contains(player.PlayerId) && + Main.AllAlivePlayerControls.Any(b => b.PlayerId == a.Key)) && DebuffID == 1) { opt.SetVision(false); opt.SetFloat(FloatOptionNames.CrewLightMod, Vision.GetFloat()); @@ -306,46 +302,6 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT { debuffTimer += Time.fixedDeltaTime; } - if (isFreezing) - { - if (freezeTimer < RandomFreezeDuration.GetFloat()) - { - var rand = IRandom.Instance; - var num = rand.Next(0, 10); - if (num == 0) - { - originalSpeed.Remove(randPlayer); - originalSpeed.Add(randPlayer, Main.AllPlayerSpeed[randPlayer]); - Main.AllPlayerSpeed[randPlayer] = 0f; - GetPlayerById(randPlayer).MarkDirtySettings(); - originalSpeed.Remove(player.PlayerId); - originalSpeed.Add(player.PlayerId, Main.AllPlayerSpeed[player.PlayerId]); - Main.AllPlayerSpeed[player.PlayerId] = 0f; - player.MarkDirtySettings(); - } - else - { - Main.AllPlayerSpeed[randPlayer] = originalSpeed[randPlayer]; - GetPlayerById(randPlayer).SyncSettings(); - originalSpeed.Remove(randPlayer); - Main.AllPlayerSpeed[player.PlayerId] = originalSpeed[player.PlayerId]; - player.SyncSettings(); - originalSpeed.Remove(player.PlayerId); - } - freezeTimer += Time.fixedDeltaTime; - } - if (freezeTimer >= RandomFreezeDuration.GetFloat()) - { - Main.AllPlayerSpeed[randPlayer] = originalSpeed[randPlayer]; - GetPlayerById(randPlayer).SyncSettings(); - originalSpeed.Remove(randPlayer); - Main.AllPlayerSpeed[player.PlayerId] = originalSpeed[player.PlayerId]; - player.SyncSettings(); - originalSpeed.Remove(player.PlayerId); - isFreezing = false; - freezeTimer = 0; - } - } } public override void OnPlayerExiled(PlayerControl player, NetworkedPlayerInfo exiled) { From 25023a6cecf401a891dbc548284acc22a83aafaa Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 11 Nov 2024 18:31:31 -0500 Subject: [PATCH 029/101] finish hexmaster rework + change some ids around --- Modules/OptionHolder.cs | 6 +- Resources/Lang/en_US.json | 1 + Roles/AddOns/Common/Spurt.cs | 8 +- Roles/Coven/HexMaster.cs | 150 +++++++++++++++++++++++++++++++---- Roles/Coven/Necromancer.cs | 13 ++- Roles/Coven/Ritualist.cs | 2 +- Roles/Crewmate/Captain.cs | 4 +- Roles/Crewmate/Mayor.cs | 2 +- Roles/Crewmate/Merchant.cs | 2 +- Roles/Neutral/Executioner.cs | 2 +- 10 files changed, 160 insertions(+), 30 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 0a26c4f50..572c46fc7 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -648,7 +648,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 30200 last id for roles/add-ons (Next use 30300) + // 30800 last id for roles/add-ons (Next use 30900) // Limit id for roles/add-ons --- "59999" //####################################### @@ -748,11 +748,11 @@ private static System.Collections.IEnumerator CoLoadOptions() CovenManager.RunSetUpImpVisOptions(160032); CovenCanVent = BooleanOptionItem.Create(60030, "CovenCanVent", true, TabGroup.CovenRoles, false) .SetGameMode(CustomGameMode.Standard); - CovenVentMode = StringOptionItem.Create(60031, "CovenVentMode", EnumHelper.GetAllNames(), 0, TabGroup.CovenRoles, false) + CovenVentMode = StringOptionItem.Create(60032, "CovenVentMode", EnumHelper.GetAllNames(), 0, TabGroup.CovenRoles, false) .SetGameMode(CustomGameMode.Standard) .SetParent(CovenCanVent); CovenManager.RunSetUpVentOptions(260032); - CovenCanSeeEachOthersAddOns = BooleanOptionItem.Create(60032, "CovenCanSeeEachOthersAddOns", true, TabGroup.CovenRoles, false) + CovenCanSeeEachOthersAddOns = BooleanOptionItem.Create(60033, "CovenCanSeeEachOthersAddOns", true, TabGroup.CovenRoles, false) .SetGameMode(CustomGameMode.Standard); NameDisplayAddons = BooleanOptionItem.Create(60019, "NameDisplayAddons", true, TabGroup.Addons, false) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 322b8e62f..9e87e62cd 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2063,6 +2063,7 @@ "HexMasterModeHex": "Hex", "HexMasterModeKill": "Kill", "HexMasterModeDouble": "Double Click = Kill, Single Click = Hex", + "HexMasterPassNotify": "Hex successfully passed", "JinxSpellTimes": "Amount of Jinx Spells", "JinxCooldown": "Jinx Cooldown", diff --git a/Roles/AddOns/Common/Spurt.cs b/Roles/AddOns/Common/Spurt.cs index dc10b5169..a3f3177ec 100644 --- a/Roles/AddOns/Common/Spurt.cs +++ b/Roles/AddOns/Common/Spurt.cs @@ -20,16 +20,16 @@ public void SetupCustomOption() { const int id = 28800; SetupAdtRoleOptions(id, CustomRoles.Spurt, canSetNum: true, teamSpawnOptions: true); - MinSpeed = FloatOptionItem.Create(id + 6, "SpurtMinSpeed", new(0f, 3f, 0.25f), 0.75f, TabGroup.Addons, false) + MinSpeed = FloatOptionItem.Create(id + 7, "SpurtMinSpeed", new(0f, 3f, 0.25f), 0.75f, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) .SetValueFormat(OptionFormat.Multiplier); - MaxSpeed = FloatOptionItem.Create(id + 7, "SpurtMaxSpeed", new(1.5f, 3f, 0.25f), 3f, TabGroup.Addons, false) + MaxSpeed = FloatOptionItem.Create(id + 8, "SpurtMaxSpeed", new(1.5f, 3f, 0.25f), 3f, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) .SetValueFormat(OptionFormat.Multiplier); - Modulator =FloatOptionItem.Create(id + 8, "SpurtModule", new(0.25f, 3f, 0.25f), 1.25f, TabGroup.Addons, false) + Modulator =FloatOptionItem.Create(id + 9, "SpurtModule", new(0.25f, 3f, 0.25f), 1.25f, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) .SetValueFormat(OptionFormat.Multiplier); - DisplaysCharge = BooleanOptionItem.Create(id + 9, "EnableSpurtCharge", false, TabGroup.Addons, false) + DisplaysCharge = BooleanOptionItem.Create(id + 10, "EnableSpurtCharge", false, TabGroup.Addons, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]); } public void Init() diff --git a/Roles/Coven/HexMaster.cs b/Roles/Coven/HexMaster.cs index 3bce48061..180350904 100644 --- a/Roles/Coven/HexMaster.cs +++ b/Roles/Coven/HexMaster.cs @@ -3,7 +3,10 @@ using UnityEngine; using System.Text; using static TOHE.Options; +using static TOHE.Utils; using static TOHE.Translator; +using InnerNet; +using TOHE.Roles.Core; namespace TOHE.Roles.Coven; @@ -26,8 +29,12 @@ internal class HexMaster : CovenManager private static OptionItem CovenCanGetMovingHex; private static OptionItem MovingHexPassCooldown; - private static readonly Dictionary HexMode = []; private static readonly Dictionary> HexedPlayer = []; + public static byte CurrentHexedPlayer = byte.MaxValue; + public static byte LastHexedPlayer = byte.MaxValue; + public static bool HasHexed = false; + public static long? CurrentHexedPlayerTime = new(); + public static long? HexedTime = new(); private static readonly Color RoleColorHex = Utils.GetRoleColor(CustomRoles.HexMaster); private static readonly Color RoleColorSpell = Utils.GetRoleColor(CustomRoles.Impostor); @@ -57,23 +64,26 @@ public override void SetupCustomOption() public override void Init() { playerIdList.Clear(); - HexMode.Clear(); HexedPlayer.Clear(); + CurrentHexedPlayer = byte.MaxValue; + LastHexedPlayer = byte.MaxValue; + HasHexed = false; + CurrentHexedPlayerTime = new(); } public override void Add(byte playerId) { playerIdList.Add(playerId); - HexMode.Add(playerId, false); HexedPlayer.Add(playerId, []); // NowSwitchTrigger = (SwitchTriggerList)ModeSwitchAction.GetValue(); var pc = Utils.GetPlayerById(playerId); pc.AddDoubleTrigger(); + CustomRoleManager.OnFixedUpdateOthers.Add(OnFixedUpdateOthers); } - private static void SendRPC(bool doHex, byte hexId, byte target = 255) + private static void SendRPC(bool regularHex, byte hexId, byte target = 255, byte oldHex = 255, byte newHex = 255) { - if (doHex) + if (regularHex) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.DoHex, SendOption.Reliable, -1); writer.Write(hexId); @@ -82,16 +92,17 @@ private static void SendRPC(bool doHex, byte hexId, byte target = 255) } else { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetKillOrSpell, SendOption.Reliable, -1); - writer.Write(hexId); - writer.Write(HexMode[hexId]); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); + writer.WriteNetObject(GetPlayerById(hexId)); + writer.Write(newHex); + writer.Write(oldHex); AmongUsClient.Instance.FinishRpcImmediately(writer); } } - public static void ReceiveRPC(MessageReader reader, bool doHex) + public static void ReceiveRPC(MessageReader reader, bool regularHex) { - if (doHex) + if (regularHex) { var hexmaster = reader.ReadByte(); var hexedId = reader.ReadByte(); @@ -106,15 +117,16 @@ public static void ReceiveRPC(MessageReader reader, bool doHex) } else { - byte playerId = reader.ReadByte(); - HexMode[playerId] = reader.ReadBoolean(); + CurrentHexedPlayer = reader.ReadByte(); + LastHexedPlayer = reader.ReadByte(); } } //public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); public override bool CanUseKillButton(PlayerControl pc) => true; - public override bool CanUseImpostorVentButton(PlayerControl pc) => true; + // public override bool CanUseImpostorVentButton(PlayerControl pc) => true; + public override void SetKillCooldown(byte id) => HexCooldown.GetFloat(); /* private static bool IsHexMode(byte playerId) @@ -160,6 +172,59 @@ private static void SetHexed(PlayerControl killer, PlayerControl target) killer.SetKillCooldown(); } } + private void PassHex(PlayerControl player, PlayerControl target) + { + if (!HasHexed) return; + if (!target.IsAlive()) return; + + var now = GetTimeStamp(); + if (now - CurrentHexedPlayerTime < MovingHexPassCooldown.GetFloat()) return; + if (target.PlayerId == LastHexedPlayer) return; + if (!CovenCanGetMovingHex.GetBool() && target.IsPlayerCoven()) return; + + + if (target.Is(CustomRoles.Pestilence)) + { + target.RpcMurderPlayer(player); + ResetHex(); + return; + } + LastHexedPlayer = CurrentHexedPlayer; + CurrentHexedPlayer = target.PlayerId; + CurrentHexedPlayerTime = now; + MarkEveryoneDirtySettings(); + + + SendRPC(false, CurrentHexedPlayer, LastHexedPlayer); + Logger.Msg($"{player.GetNameWithRole()} passed hex to {target.GetNameWithRole()}", "Hex Master Pass"); + } + public static void ResetHex() + { + CurrentHexedPlayer = 254; + CurrentHexedPlayerTime = new(); + LastHexedPlayer = byte.MaxValue; + HasHexed = false; + } + public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) + { + if (!lowLoad && CurrentHexedPlayer == 254) + { + SendRPC(false, CurrentHexedPlayer, LastHexedPlayer); + CurrentHexedPlayer = byte.MaxValue; + } + } + public override void OnReportDeadBody(PlayerControl reported, NetworkedPlayerInfo agitatergoatedrole) + { + if (CurrentHexedPlayer == byte.MaxValue) return; + var target = GetPlayerById(CurrentHexedPlayer); + var killer = _Player; + if (target == null || killer == null) return; + + HexedPlayer[killer.PlayerId].Add(target.PlayerId); + SendRPC(true, killer.PlayerId, target.PlayerId); + ResetHex(); + Logger.Info($"Passing hex ended, {target.GetRealName()} ended with hex on report", "Hex Master"); + } public override void AfterMeetingTasks() { foreach (var hexmaster in playerIdList) @@ -184,7 +249,12 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return true; } */ - if (killer.CheckDoubleTrigger(target, () => { SetHexed(killer, target); })) + if (!HasNecronomicon(killer)) + { + SetHexed(killer, target); + return false; + } + if (killer.CheckDoubleTrigger(target, () => { SetHexedNecronomicon(killer, target); })) { if (HasNecronomicon(killer) && !target.IsPlayerCoven()) { @@ -193,6 +263,54 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } return false; } + private static void SetHexedNecronomicon(PlayerControl killer, PlayerControl target) + { + if (!HasEnabled) return; + + CurrentHexedPlayer = target.PlayerId; + LastHexedPlayer = killer.PlayerId; + CurrentHexedPlayerTime = GetTimeStamp(); + killer.RpcGuardAndKill(killer); + killer.Notify(GetString("HexMasterPassNotify")); + HasHexed = true; + killer.ResetKillCooldown(); + killer.SetKillCooldown(); + } + private void OnFixedUpdateOthers(PlayerControl player, bool lowLoad, long nowTime) + { + if (lowLoad || !HasHexed || CurrentHexedPlayer != player.PlayerId) return; + + if (!player.IsAlive()) + { + ResetHex(); + } + else + { + var playerPos = player.GetCustomPosition(); + Dictionary targetDistance = []; + float dis; + + foreach (var target in Main.AllAlivePlayerControls) + { + if (target.PlayerId != player.PlayerId && target.PlayerId != LastHexedPlayer) + { + dis = GetDistance(playerPos, target.transform.position); + targetDistance.Add(target.PlayerId, dis); + } + } + + if (targetDistance.Any()) + { + var min = targetDistance.OrderBy(c => c.Value).FirstOrDefault(); + var target = min.Key.GetPlayer(); + var KillRange = GameOptionsData.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 2)]; + if (min.Value <= KillRange && !player.inVent && !player.inMovingPlat && !target.inVent && !target.inMovingPlat && player.RpcCheckAndMurder(target, true)) + { + PassHex(player, target); + } + } + } + } public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { foreach (var id in exileIds) @@ -250,11 +368,11 @@ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, b { if (!HexesLookLikeSpells.GetBool()) { - return Utils.ColorString(RoleColorHex, "乂"); + return ColorString(RoleColorHex, "乂"); } else { - return Utils.ColorString(RoleColorSpell, "†"); + return ColorString(RoleColorSpell, "†"); } } return string.Empty; diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index b0b272fa2..599811946 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -6,6 +6,8 @@ using TOHE.Roles.AddOns; using Rewired; using UnityEngine; +using TOHE.Roles.Neutral; +using TOHE.Roles.Crewmate; namespace TOHE.Roles.Coven; @@ -196,7 +198,16 @@ CustomRoles.Retributionist or CustomRoles.Nemesis or CustomRoles.NiceMini or CustomRoles.Mini or - CustomRoles.EvilMini; + CustomRoles.EvilMini or + CustomRoles.SuperStar or + CustomRoles.RuthlessRomantic or + CustomRoles.VengefulRomantic or + CustomRoles.CursedSoul or + CustomRoles.Provocateur or + CustomRoles.Specter or + CustomRoles.Sunnyboy || + (role == CustomRoles.Workaholic && Workaholic.WorkaholicVisibleToEveryone.GetBool()) || + (role == CustomRoles.Mayor && Mayor.MayorRevealWhenDoneTasks.GetBool()); } public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index 03d866a57..6a0b2de40 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -16,7 +16,7 @@ namespace TOHE.Roles.Coven; internal class Ritualist : CovenManager { //===========================SETUP================================\\ - private const int Id = 29900; + private const int Id = 30800; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Ritualist); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; diff --git a/Roles/Crewmate/Captain.cs b/Roles/Crewmate/Captain.cs index 2bc1141bf..6e6022f29 100644 --- a/Roles/Crewmate/Captain.cs +++ b/Roles/Crewmate/Captain.cs @@ -48,8 +48,8 @@ public override void SetupCustomOption() CaptainCanTargetNE = BooleanOptionItem.Create(Id + 19, "CaptainCanTargetNE", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); CaptainCanTargetNK = BooleanOptionItem.Create(Id + 20, "CaptainCanTargetNK", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); CaptainCanTargetNA = BooleanOptionItem.Create(Id + 21, "CaptainCanTargetNA", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); - CaptainCanTargetCoven = BooleanOptionItem.Create(Id + 23, "CaptainCanTargetCoven", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); - OverrideTasksData.Create(Id + 22, TabGroup.CrewmateRoles, CustomRoles.Captain); + CaptainCanTargetCoven = BooleanOptionItem.Create(Id + 22, "CaptainCanTargetCoven", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Captain]); + OverrideTasksData.Create(Id + 23, TabGroup.CrewmateRoles, CustomRoles.Captain); } public override void Init() diff --git a/Roles/Crewmate/Mayor.cs b/Roles/Crewmate/Mayor.cs index 1f62366e7..5966d2a4d 100644 --- a/Roles/Crewmate/Mayor.cs +++ b/Roles/Crewmate/Mayor.cs @@ -22,7 +22,7 @@ internal partial class Mayor : RoleBase private static OptionItem MayorHasPortableButton; private static OptionItem MayorNumOfUseButton; private static OptionItem MayorHideVote; - private static OptionItem MayorRevealWhenDoneTasks; + public static OptionItem MayorRevealWhenDoneTasks; private static readonly Dictionary MayorUsedButtonCount = []; diff --git a/Roles/Crewmate/Merchant.cs b/Roles/Crewmate/Merchant.cs index 50ef498fc..46fee645d 100644 --- a/Roles/Crewmate/Merchant.cs +++ b/Roles/Crewmate/Merchant.cs @@ -57,7 +57,7 @@ public override void SetupCustomOption() OptionSellOnlyHelpfulToCrew = BooleanOptionItem.Create(Id + 14, "MerchantSellHelpfulToCrew", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); OptionSellOnlyEnabledAddons = BooleanOptionItem.Create(Id + 15, "MerchantSellOnlyEnabledAddons",false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); - OverrideTasksData.Create(Id + 16, TabGroup.CrewmateRoles, CustomRoles.Merchant); + OverrideTasksData.Create(Id + 17, TabGroup.CrewmateRoles, CustomRoles.Merchant); } public override void Init() { diff --git a/Roles/Neutral/Executioner.cs b/Roles/Neutral/Executioner.cs index 6ce8028e8..f3418c0d0 100644 --- a/Roles/Neutral/Executioner.cs +++ b/Roles/Neutral/Executioner.cs @@ -62,7 +62,7 @@ public override void SetupCustomOption() CanTargetNeutralEvil = BooleanOptionItem.Create(Id + 15, "ExecutionerCanTargetNeutralEvil", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); CanTargetNeutralChaos = BooleanOptionItem.Create(Id + 16, "ExecutionerCanTargetNeutralChaos", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); CanTargetNeutralApocalypse = BooleanOptionItem.Create(Id + 17, "ExecutionerCanTargetNeutralApocalypse", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); - CanTargetCoven = BooleanOptionItem.Create(Id + 18, "ExecutionerCanTargetCoven", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); + CanTargetCoven = BooleanOptionItem.Create(Id + 19, "ExecutionerCanTargetCoven", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); KnowTargetRole = BooleanOptionItem.Create(Id + 13, "KnowTargetRole", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); ChangeRolesAfterTargetKilled = StringOptionItem.Create(Id + 11, "ExecutionerChangeRolesAfterTargetKilled", EnumHelper.GetAllNames(), 1, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); RevealExeTargetUponEjection = BooleanOptionItem.Create(Id + 18, "Executioner_RevealTargetUponEject", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); From b862a838b6ec28de86fb8ff04cfc0a262f1b038e Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 11 Nov 2024 18:43:58 -0500 Subject: [PATCH 030/101] update hex master string and add /coveninfo --- Patches/ChatCommandPatch.cs | 10 ++++++++++ Resources/Lang/en_US.json | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index d756b397e..a457c32ca 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -220,6 +220,11 @@ public static bool Prefix(ChatController __instance) Utils.SendMessage(GetString("Message.ApocalypseInfo"), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); break; + case "/coveninfo": + case "/covinfo": + canceled = true; + Utils.SendMessage(GetString("Message.CovenInfo"), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Coven), GetString("CovenInfoTitle"))); + break; case "/rn": case "/rename": @@ -2204,6 +2209,11 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can Utils.SendMessage(GetString("Message.ApocalypseInfo"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); break; + case "/coveninfo": + case "/covinfo": + Utils.SendMessage(GetString("Message.CovenInfo"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Coven), GetString("CovenInfoTitle"))); + break; + case "/rn": case "/rename": case "/renomear": diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 9e87e62cd..5be581b30 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -954,7 +954,7 @@ "VengefulRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A crew or non-neutral killer) is killed. As a Vengeful Romantic, your goal is to avenge your partner, which means you must kill the killer of your partner. If you succeed, then you and your partner win with the winning team at the end. If you try to kill someone other than your partner's killer, then you will die by misfire.", "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", "PoisonerInfoLong": "(Coven):\nThe Poisoner can use their kill button on a player to roleblock them. The next time the roleblocked player tries to use their ability, it will do nothing, and their cooldown will be reset.\nWith the Necronomicon, you can double-click to kill. These kills will be delayed.", - "HexMasterInfoLong": "(Coven):\nAs the Hex Master, you can hex players or kill them.\nHexing a player works the same as spelling as a Witch.", + "HexMasterInfoLong": "(Coven):\nThe Hex Master can use their kill button to mark a player with the 乂 symbol. If a player has this at the end of the meeting and the Hex Master hasn't died - then they die.\nWith the Necronomicon, the hex will be passed around - similar to an agitator bomb. Also, you can double-click the kill button to kill normally.", "JinxInfoLong": "(Coven):\nThe Jinx can use their kill button to Jinx a player. Anyone who interacts with the Jinxed player will die with the death reason Jinxed.\nWith the Necronomicon the Jinx can double-kill to kill normally. Also, the Jinxed player and the player who interacted with the Jinxed person will die.", "MedusaInfoLong": "(Coven):\nThe Medusa can use their kill button on players to mark them as Stoned. When the Medusa clicks the Shapeshift button, all Stoned players will be unable to move and will have reduced vision for a configurable amount of time.\nWith the Necronomicon, killed players will be unreportable.", "PotionMasterInfoLong": "(Coven):\nThe Potion Master has two potions available for their use. The Reveal potion reveals a player's role. The Barrier potion places a shield on a player for one round, the player will not be notified of this unless they are Coven as well. Click the Shapeshift button to change potions.\nWith the Necronomicon, the Potion Master can double-click their kill button to kill.", @@ -2509,6 +2509,8 @@ "Message.GhostRoleInfo": "Ghost Role Info\nHiya! A little about ghost roles...\n\nGhost roles drastically impact the game, so it's not recommended for smaller lobbies if you're unfamiliar. If not explicitly stated otherwise in the description, the Guard button is their ability button ;)\n\nSpawning:\nGhost-roles only spawn after death; the first x people from (team) to die get them.\n\nPS: If your previous role didn't have tasks(e.g., sheriff), your tasks as a ghost-role aren't needed for task-win", "ApocalypseInfoTitle": "Neutral Apocalypse Info:", "Message.ApocalypseInfo": "Every role of the <#ff174f>Apocalypse Team has their own objective to carry out in order to transform.\n<#2B0804>Transformed <#ff174f>Apocalypse members have a drastic change on the game and are immortal (except for being voted), but everyone will be notified that they have transformed.\n\nRoles: <#e5f6b4>Plaguebearer, <#A675A1>Soul Collector, <#bf9f7a>Baker, <#cc0044>Berserker\nTransformed: <#343136>Pestilence, <#644661>Death, <#83461c>Famine, <#2B0804>War\n\nApocalypse members can see eachother's roles and ability icons.\nLike Neutral Killers, Apocalypse members keep the game going as well, have fun!", + "CovenInfoTitle": "Coven Info:", + "Message.CovenInfo": "The <#ac42f2>Coven is a faction of evildoers who specialize in witchcraft. Their goal is to kill all who would oppose the <#ac42f2>Coven.\nThey use the Necronomicon to enhance their powers, but only one member at a time can hold it.\nMost Coven roles can only kill if they hold the Necronomicon. A random Coven member will spawn with the Necronomicon, denoted by the ♣ symbol next to their name.", "Message.MeCommandInfo": "Hi [{0}] {1} !\n\nfriend-code Hash-Puid Type 
{2} {3} {4}

IsDev HasUp /color-Bypass
{5} {6} {7}

", "Message.MeCommandTargetInfo": "Selected [{0}] Player {1} ,\n\nTheir friend code is {2}.\n\nTheir hash puid is {3}.\n\nTheir TOHE Discord role is {4}.\n\n", "Message.MeCommandInvalidID": "The ID you entered seems incorrect. \nPlease use /id to get the player ID of online players", From 37a92d35ded49caf7e373fadf0121e7e17e26e8d Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 11 Nov 2024 18:48:07 -0500 Subject: [PATCH 031/101] /covinfo clarification --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 5be581b30..dc51023b4 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2510,7 +2510,7 @@ "ApocalypseInfoTitle": "Neutral Apocalypse Info:", "Message.ApocalypseInfo": "Every role of the <#ff174f>Apocalypse Team has their own objective to carry out in order to transform.\n<#2B0804>Transformed <#ff174f>Apocalypse members have a drastic change on the game and are immortal (except for being voted), but everyone will be notified that they have transformed.\n\nRoles: <#e5f6b4>Plaguebearer, <#A675A1>Soul Collector, <#bf9f7a>Baker, <#cc0044>Berserker\nTransformed: <#343136>Pestilence, <#644661>Death, <#83461c>Famine, <#2B0804>War\n\nApocalypse members can see eachother's roles and ability icons.\nLike Neutral Killers, Apocalypse members keep the game going as well, have fun!", "CovenInfoTitle": "Coven Info:", - "Message.CovenInfo": "The <#ac42f2>Coven is a faction of evildoers who specialize in witchcraft. Their goal is to kill all who would oppose the <#ac42f2>Coven.\nThey use the Necronomicon to enhance their powers, but only one member at a time can hold it.\nMost Coven roles can only kill if they hold the Necronomicon. A random Coven member will spawn with the Necronomicon, denoted by the ♣ symbol next to their name.", + "Message.CovenInfo": "The <#ac42f2>Coven is a faction of evildoers who specialize in witchcraft. Their goal is to kill all who would oppose the <#ac42f2>Coven.\nThey use the Necronomicon to enhance their powers, but only one member at a time can hold it.\nMost Coven roles can only kill if they hold the Necronomicon. A random Coven member will spawn with the Necronomicon, denoted by the <#ac42f2>♣ symbol next to their name.\nIf the Necronomicon holder dies, the Necronomicon will be randomly given to another <#ac42f2>Coven member that is alive.", "Message.MeCommandInfo": "Hi [{0}] {1} !\n\nfriend-code Hash-Puid Type 
{2} {3} {4}

IsDev HasUp /color-Bypass
{5} {6} {7}

", "Message.MeCommandTargetInfo": "Selected [{0}] Player {1} ,\n\nTheir friend code is {2}.\n\nTheir hash puid is {3}.\n\nTheir TOHE Discord role is {4}.\n\n", "Message.MeCommandInvalidID": "The ID you entered seems incorrect. \nPlease use /id to get the player ID of online players", From 8ff44053c46805cee5407e14f658885ceb9c95d5 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 11 Nov 2024 18:56:31 -0500 Subject: [PATCH 032/101] my dumbass forgot to do /r --- Modules/Utils.cs | 6 +++++- Resources/Lang/en_US.json | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 964a62182..aa2ff87c0 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -862,6 +862,7 @@ public static void ShowActiveRoles(byte PlayerId = byte.MaxValue) List impsb = []; List neutralsb = []; + List covenb = []; List crewsb = []; List addonsb = []; @@ -881,17 +882,20 @@ public static void ShowActiveRoles(byte PlayerId = byte.MaxValue) else if (role.IsCrewmate()) crewsb.Add(roleDisplay); else if (role.IsImpostor() || role.IsMadmate()) impsb.Add(roleDisplay); else if (role.IsNeutral()) neutralsb.Add(roleDisplay); + else if (role.IsCoven()) covenb.Add(roleDisplay); } } impsb.Sort(); crewsb.Sort(); neutralsb.Sort(); + covenb.Sort(); addonsb.Sort(); SendMessage(string.Join("\n", impsb), PlayerId, ColorString(GetRoleColor(CustomRoles.Impostor), GetString("ImpostorRoles")), ShouldSplit: true); SendMessage(string.Join("\n", crewsb), PlayerId, ColorString(GetRoleColor(CustomRoles.Crewmate), GetString("CrewmateRoles")), ShouldSplit: true); SendMessage(string.Join("\n", neutralsb), PlayerId, GetString("NeutralRoles"), ShouldSplit: true); + SendMessage(string.Join("\n", covenb), PlayerId, GetString("CovenRoles"), ShouldSplit: true); SendMessage(string.Join("\n", addonsb), PlayerId, GetString("AddonRoles"), ShouldSplit: true); } public static void ShowChildrenSettings(OptionItem option, ref StringBuilder sb, int deep = 0, bool command = false) @@ -1019,7 +1023,7 @@ public static string GetSubRolesText(byte id, bool disableColor = false, bool in { if (role is CustomRoles.NotAssigned or CustomRoles.LastImpostor) continue; - if (summary && role is CustomRoles.Madmate or CustomRoles.Charmed or CustomRoles.Recruit or CustomRoles.Admired or CustomRoles.Infected or CustomRoles.Contagious or CustomRoles.Soulless) continue; + if (summary && role is CustomRoles.Madmate or CustomRoles.Charmed or CustomRoles.Recruit or CustomRoles.Admired or CustomRoles.Infected or CustomRoles.Contagious or CustomRoles.Soulless or CustomRoles.Enchanted) continue; var RoleColor = GetRoleColor(role); var RoleText = disableColor ? GetRoleName(role) : ColorString(RoleColor, GetRoleName(role)); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index dc51023b4..ab0cb0c3c 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3713,6 +3713,7 @@ "CrewmateRoles": "★ Crewmate Roles ★", "ImpostorRoles": "★ Impostor Roles ★", "NeutralRoles": "★ Neutral Roles ★", + "CovenRoles": "★ Coven Roles ★", "AddonRoles": "★ Add-ons ★", "WinnerRoleText.Impostor": "Impostors Win!", "WinnerRoleText.Crewmate": "Crewmates Win!", From f3cf6b935827fd9570f175995fd73b2203184ac6 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 15 Nov 2024 18:23:51 -0500 Subject: [PATCH 033/101] enchanted and amnesiac interactions --- Modules/ExtendedPlayerControl.cs | 3 ++- Patches/MeetingHudPatch.cs | 2 +- Resources/Lang/en_US.json | 1 + Roles/Crewmate/Bodyguard.cs | 4 ++++ Roles/Crewmate/Grenadier.cs | 16 ++++++++++++++-- Roles/Crewmate/TimeManager.cs | 2 +- Roles/Neutral/Amnesiac.cs | 9 +++++---- Roles/Neutral/Hater.cs | 6 +++++- Roles/Neutral/Jackal.cs | 2 +- 9 files changed, 34 insertions(+), 11 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 8f6430fff..907251774 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1155,7 +1155,8 @@ public static bool IsNonCrewSheriff(this PlayerControl sheriff) || sheriff.Is(CustomRoles.Charmed) || sheriff.Is(CustomRoles.Infected) || sheriff.Is(CustomRoles.Contagious) - || sheriff.Is(CustomRoles.Egoist); + || sheriff.Is(CustomRoles.Egoist) + || sheriff.Is(CustomRoles.Enchanted); } public static bool ShouldBeDisplayed(this CustomRoles subRole) { diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 5db34e315..092364290 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -494,7 +494,7 @@ private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool Anti name += ColorString(new Color32(127, 140, 141, byte.MaxValue), GetString("TeamNeutral")); else if (player.GetCustomRole().IsCrewmate()) name += ColorString(new Color32(140, 255, 255, byte.MaxValue), GetString("TeamCrewmate")); - else if (player.GetCustomRole().IsCrewmate() || player.Is(CustomRoles.Enchanted)) + else if (player.GetCustomRole().IsCoven() || player.Is(CustomRoles.Enchanted)) name += ColorString(new Color32(172, 66, 242, byte.MaxValue), GetString("TeamCoven")); name += ")"; } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index ab0cb0c3c..43246484d 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1754,6 +1754,7 @@ "GrenadierSkillDuration": "Grenade Duration", "GrenadierCauseVision": "Lowered vision", "GrenadierCanAffectNeutral": "Can affect Neutrals", + "GrenadierCanAffectCoven": "Can affect Coven", "TicketsPerKill": "Votes Increase Amount Per Kill", "GangsterRecruitCooldown": "Recruit cooldown", "GangsterRecruitLimit": "Recruit limit", diff --git a/Roles/Crewmate/Bodyguard.cs b/Roles/Crewmate/Bodyguard.cs index 9290ac24a..849705e33 100644 --- a/Roles/Crewmate/Bodyguard.cs +++ b/Roles/Crewmate/Bodyguard.cs @@ -43,6 +43,10 @@ or CustomRoles.Veteran { Logger.Info($"{bodyguard.GetRealName()} He was a impostor, so he chose to ignore the murder scene", "Bodyguard"); } + else if (bodyguard.Is(CustomRoles.Enchanted) && killer.GetCustomRole().IsCoven()) + { + Logger.Info($"{bodyguard.GetRealName()} He was a impostor, so he chose to ignore the murder scene", "Bodyguard"); + } else if (bodyguard.CheckForInvalidMurdering(killer)) { bodyguard.SetDeathReason(PlayerState.DeathReason.Sacrifice); diff --git a/Roles/Crewmate/Grenadier.cs b/Roles/Crewmate/Grenadier.cs index ba9acdf68..a56337383 100644 --- a/Roles/Crewmate/Grenadier.cs +++ b/Roles/Crewmate/Grenadier.cs @@ -26,6 +26,7 @@ internal class Grenadier : RoleBase private static OptionItem GrenadierSkillDuration; private static OptionItem GrenadierCauseVision; private static OptionItem GrenadierCanAffectNeutral; + private static OptionItem GrenadierCanAffectCoven; private static OptionItem GrenadierSkillMaxOfUseage; private static OptionItem GrenadierAbilityUseGainWithEachTaskCompleted; @@ -39,6 +40,7 @@ public override void SetupCustomOption() GrenadierCauseVision = FloatOptionItem.Create(Id + 12, "GrenadierCauseVision", new(0f, 5f, 0.05f), 0.3f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) .SetValueFormat(OptionFormat.Multiplier); GrenadierCanAffectNeutral = BooleanOptionItem.Create(Id + 13, "GrenadierCanAffectNeutral", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]); + GrenadierCanAffectCoven = BooleanOptionItem.Create(Id + 16, "GrenadierCanAffectCoven", false, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]); GrenadierSkillMaxOfUseage = FloatOptionItem.Create(Id + 14, "GrenadierSkillMaxOfUseage", new(0, 20, 1), 2, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) .SetValueFormat(OptionFormat.Times); GrenadierAbilityUseGainWithEachTaskCompleted = FloatOptionItem.Create(Id + 15, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.1f), 1f, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) @@ -66,7 +68,8 @@ public static void ApplyGameOptionsForOthers(IGameOptions opt, PlayerControl pla // Grenadier or Mad Grenadier enter the vent if ((GrenadierBlinding.Any() && (player.GetCustomRole().IsImpostor() || - (player.GetCustomRole().IsNeutral() && GrenadierCanAffectNeutral.GetBool())) + (player.GetCustomRole().IsNeutral() && GrenadierCanAffectNeutral.GetBool()) || + (player.GetCustomRole().IsCoven() && GrenadierCanAffectCoven.GetBool())) ) || (MadGrenadierBlinding.Any() && !player.GetCustomRole().IsImpostorTeam() && !player.Is(CustomRoles.Madmate))) { @@ -103,12 +106,21 @@ public override void OnEnterVent(PlayerControl pc, Vent vent) .Where(x => !x.GetCustomRole().IsImpostorTeam() && !x.Is(CustomRoles.Madmate)) .Do(x => x.RPCPlayCustomSound("FlashBang")); } + // Why in the world is there a separate list for Mad, whatever i guess -- Marg + else if (pc.Is(CustomRoles.Enchanted)) + { + MadGrenadierBlinding.Remove(pc.PlayerId); + MadGrenadierBlinding.Add(pc.PlayerId, GetTimeStamp()); + Main.AllPlayerControls.Where(x => x.IsModded()) + .Where(x => !x.GetCustomRole().IsCoven() && !x.Is(CustomRoles.Enchanted)) + .Do(x => x.RPCPlayCustomSound("FlashBang")); + } else { GrenadierBlinding.Remove(pc.PlayerId); GrenadierBlinding.Add(pc.PlayerId, GetTimeStamp()); Main.AllPlayerControls.Where(x => x.IsModded()) - .Where(x => x.GetCustomRole().IsImpostor() || (x.GetCustomRole().IsNeutral() && GrenadierCanAffectNeutral.GetBool())) + .Where(x => x.GetCustomRole().IsImpostor() || (x.GetCustomRole().IsNeutral() && GrenadierCanAffectNeutral.GetBool()) || (x.GetCustomRole().IsCoven() && GrenadierCanAffectCoven.GetBool())) .Do(x => x.RPCPlayCustomSound("FlashBang")); } if (!DisableShieldAnimations.GetBool()) pc.RpcGuardAndKill(pc); diff --git a/Roles/Crewmate/TimeManager.cs b/Roles/Crewmate/TimeManager.cs index 70cdec041..274def295 100644 --- a/Roles/Crewmate/TimeManager.cs +++ b/Roles/Crewmate/TimeManager.cs @@ -49,7 +49,7 @@ public static int TotalIncreasedMeetingTime() int sec = 0; foreach (var playerId in playerIdList) { - if (Utils.GetPlayerById(playerId).Is(CustomRoles.Madmate)) sec -= AdditionalTime(playerId); + if (Utils.GetPlayerById(playerId).Is(CustomRoles.Madmate) || Utils.GetPlayerById(playerId).Is(CustomRoles.Enchanted)) sec -= AdditionalTime(playerId); else sec += AdditionalTime(playerId); } Logger.Info($"{sec}second", "TimeManager.TotalIncreasedMeetingTime"); diff --git a/Roles/Neutral/Amnesiac.cs b/Roles/Neutral/Amnesiac.cs index 63aadef1e..3d1954dc5 100644 --- a/Roles/Neutral/Amnesiac.cs +++ b/Roles/Neutral/Amnesiac.cs @@ -135,10 +135,11 @@ public override bool OnCheckReportDeadBody(PlayerControl __instance, NetworkedPl } if (tar.GetCustomRole().IsNA()) { - __instance.RpcSetCustomRole(tar.GetCustomRole()); - __instance.GetRoleClass().Add(__instance.PlayerId); - __instance.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("YouRememberedRole"))); - tar.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedYourRole"))); + tempRole = tar.GetCustomRole(); + } + if (tar.GetCustomRole().IsCoven() || tar.GetCustomRole().GetStaticRoleClass().ThisRoleBase == CustomRoles.Impostor) + { + tempRole = tar.GetCustomRole(); } if (tar.GetCustomRole().IsAmneNK()) { diff --git a/Roles/Neutral/Hater.cs b/Roles/Neutral/Hater.cs index 2c332ade3..c15bd11c7 100644 --- a/Roles/Neutral/Hater.cs +++ b/Roles/Neutral/Hater.cs @@ -25,6 +25,7 @@ internal class Hater : RoleBase private static OptionItem CanKillEgoists; private static OptionItem CanKillInfected; private static OptionItem CanKillContagious; + private static OptionItem CanKillEnchanted; public static bool isWon = false; // There's already a playerIdList, so replaced this with a boolean value @@ -41,6 +42,7 @@ public override void SetupCustomOption() CanKillInfected = BooleanOptionItem.Create(Id + 18, "HaterCanKillInfected", true, TabGroup.NeutralRoles, false).SetParent(ChooseConverted); CanKillContagious = BooleanOptionItem.Create(Id + 19, "HaterCanKillContagious", true, TabGroup.NeutralRoles, false).SetParent(ChooseConverted); CanKillAdmired = BooleanOptionItem.Create(Id + 20, "HaterCanKillAdmired", true, TabGroup.NeutralRoles, false).SetParent(ChooseConverted); + CanKillEnchanted = BooleanOptionItem.Create(Id + 21, "HaterCanKillEnchanted", true, TabGroup.NeutralRoles, false).SetParent(ChooseConverted); } public override void Init() @@ -79,6 +81,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t || ((target.Is(CustomRoles.Infected) || target.Is(CustomRoles.Infectious)) && CanKillInfected.GetBool()) || ((target.Is(CustomRoles.Contagious) || target.Is(CustomRoles.Virus)) && CanKillContagious.GetBool()) || ((target.Is(CustomRoles.Admired) || target.Is(CustomRoles.Admirer)) && CanKillAdmired.GetBool()) + || ((target.Is(CustomRoles.Enchanted) || target.Is(CustomRoles.Ritualist)) && CanKillEnchanted.GetBool()) ) { isWon = true; // Only win if target can be killed - this kills the target if they can be killed @@ -117,7 +120,8 @@ CustomRoles.Sidekick or CustomRoles.Jackal or CustomRoles.Virus or CustomRoles.Infectious or - CustomRoles.Admirer + CustomRoles.Admirer or + CustomRoles.Ritualist => true, _ => false, diff --git a/Roles/Neutral/Jackal.cs b/Roles/Neutral/Jackal.cs index 12eed319e..2d831c07c 100644 --- a/Roles/Neutral/Jackal.cs +++ b/Roles/Neutral/Jackal.cs @@ -211,7 +211,7 @@ public static bool CanBeSidekick(PlayerControl pc) return pc != null && !pc.Is(CustomRoles.Sidekick) && !pc.Is(CustomRoles.Recruit) && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Admired) && !pc.Is(CustomRoles.Rascal) && !pc.Is(CustomRoles.Madmate) && !pc.Is(CustomRoles.Charmed) && !pc.Is(CustomRoles.Infected) && !pc.Is(CustomRoles.Paranoia) - && !pc.Is(CustomRoles.Contagious) && pc.GetCustomRole().IsAbleToBeSidekicked() + && !pc.Is(CustomRoles.Contagious) && !pc.Is(CustomRoles.Enchanted) && pc.GetCustomRole().IsAbleToBeSidekicked() && !(pc.GetCustomSubRoles().Contains(CustomRoles.Hurried) && !Hurried.CanBeConverted.GetBool()); } From 81adf4eccdb472057f97424decbbdbc55bd163c5 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 15 Nov 2024 19:18:29 -0500 Subject: [PATCH 034/101] fix voting --- Patches/MeetingHudPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 092364290..109e4144b 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -697,7 +697,7 @@ public static bool Prefix(MeetingHud __instance, byte srcPlayerId, byte suspectP } // Coven Leader Retraining - if (target == voter && CovenLeader.retrainPlayer[voter.PlayerId].IsCoven()) + if (CustomRoles.CovenLeader.RoleExist() && target == voter && CovenLeader.retrainPlayer[voter.PlayerId].IsCoven()) { PlayerControl CL = CustomRoles.CovenLeader.GetPlayerListByRole().First(); voter.RpcSetCustomRole(CovenLeader.retrainPlayer[voter.PlayerId]); @@ -708,7 +708,7 @@ public static bool Prefix(MeetingHud __instance, byte srcPlayerId, byte suspectP CustomRoles.CovenLeader.GetStaticRoleClass().SendSkillRPC(); __instance.RpcClearVoteDelay(voter.GetClientId()); } - else if (target != voter && CovenLeader.retrainPlayer[voter.PlayerId].IsCoven()) + else if (CustomRoles.CovenLeader.RoleExist() && target != voter && CovenLeader.retrainPlayer[voter.PlayerId].IsCoven()) { PlayerControl CL = CustomRoles.CovenLeader.GetPlayerListByRole().First(); SendMessage(string.Format(GetString("CovenLeaderDeclineRetrain"), CovenLeader.retrainPlayer[voter.PlayerId].ToColoredString()), CL.PlayerId); From ed027b6eff994d011045c92506e8fd64ac69e0a3 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 15 Nov 2024 20:48:17 -0500 Subject: [PATCH 035/101] add necro passing + kill checks for all roles --- Modules/CustomRolesHelper.cs | 4 ++++ Modules/Utils.cs | 1 + Patches/MeetingHudPatch.cs | 30 +++++++++++++++++++++++++-- Patches/PlayerControlPatch.cs | 2 +- Resources/Lang/en_US.json | 3 +++ Roles/Coven/CovenLeader.cs | 9 ++++++++- Roles/Coven/CovenManager.cs | 38 +++++++++++++++++++++++++++++++++++ Roles/Coven/HexMaster.cs | 9 +++++++-- Roles/Coven/Illusionist.cs | 1 + Roles/Coven/Jinx.cs | 16 ++++++++++++--- Roles/Coven/Medusa.cs | 7 ++++++- Roles/Coven/Poisoner.cs | 1 + Roles/Coven/PotionMaster.cs | 19 +++++++++++++----- Roles/Coven/Ritualist.cs | 9 +++++++++ Roles/Coven/Sacrifist.cs | 16 +++++++-------- 15 files changed, 142 insertions(+), 23 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index dad828a54..3d7a8bffb 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -1139,6 +1139,10 @@ public static RoleTypes GetRoleTypes(this CustomRoles role) /// Role is not impostor nor rascal nor madmate nor converting nor neutral or role is trickster. /// public static bool IsCrewmateTeamV2(this CustomRoles role) => !(role.IsImpostorTeamV2() || role.IsNeutralTeamV2()) || role == CustomRoles.Trickster; + /// + /// Role is Enchanted Or Coven + /// + public static bool IsCovenTeam(this CustomRoles role) => role.IsCoven() || role == CustomRoles.Enchanted; public static bool IsImpostorTeamV3(this CustomRoles role) => role.IsImpostor() || role.IsMadmate(); public static bool IsNeutralKillerTeam(this CustomRoles role) => role.IsNK() && !role.IsMadmate(); public static bool IsPassiveNeutralTeam(this CustomRoles role) => role.IsNonNK() && !role.IsMadmate(); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index aa2ff87c0..e05acb480 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2412,6 +2412,7 @@ public static void AfterMeetingTasks() if (Antidote.IsEnable) Antidote.AfterMeetingTasks(); AntiBlackout.AfterMeetingTasks(); + CovenManager.CheckNecroVotes(); foreach (var playerState in Main.PlayerStates.Values.ToArray()) { diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 109e4144b..75c858d31 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -696,8 +696,32 @@ public static bool Prefix(MeetingHud __instance, byte srcPlayerId, byte suspectP } } + // Coven Necronomicon Voting + + if (suspectPlayerId == 253 && voter.IsPlayerCoven()) + { + if (!voter.GetRoleClass().HasVoted) + { + voter.GetRoleClass().HasVoted = true; + SendMessage(GetString("VoteNotUseAbility"), voter.PlayerId); + __instance.RpcClearVoteDelay(voter.GetClientId()); + return false; + } + } + else if (voter.IsPlayerCoven() && target.IsPlayerCoven()) + { + if (!voter.GetRoleClass().HasVoted) + { + voter.GetRoleClass().HasVoted = true; + CovenManager.necroVotes[voter.PlayerId] = target.PlayerId; + SendMessage(string.Format(GetString("NecronomiconVote"), target.GetRealName()), voter.PlayerId); + __instance.RpcClearVoteDelay(voter.GetClientId()); + return false; + } + } + // Coven Leader Retraining - if (CustomRoles.CovenLeader.RoleExist() && target == voter && CovenLeader.retrainPlayer[voter.PlayerId].IsCoven()) + if (CustomRoles.CovenLeader.RoleExist() && target == voter && CovenLeader.retrainPlayer.ContainsKey(voter.PlayerId) && CovenLeader.retrainPlayer[voter.PlayerId].IsCoven()) { PlayerControl CL = CustomRoles.CovenLeader.GetPlayerListByRole().First(); voter.RpcSetCustomRole(CovenLeader.retrainPlayer[voter.PlayerId]); @@ -707,14 +731,16 @@ public static bool Prefix(MeetingHud __instance, byte srcPlayerId, byte suspectP CustomRoles.CovenLeader.GetStaticRoleClass().AbilityLimit--; CustomRoles.CovenLeader.GetStaticRoleClass().SendSkillRPC(); __instance.RpcClearVoteDelay(voter.GetClientId()); + return false; } - else if (CustomRoles.CovenLeader.RoleExist() && target != voter && CovenLeader.retrainPlayer[voter.PlayerId].IsCoven()) + else if (CustomRoles.CovenLeader.RoleExist() && target != voter && CovenLeader.retrainPlayer.ContainsKey(voter.PlayerId) && CovenLeader.retrainPlayer[voter.PlayerId].IsCoven()) { PlayerControl CL = CustomRoles.CovenLeader.GetPlayerListByRole().First(); SendMessage(string.Format(GetString("CovenLeaderDeclineRetrain"), CovenLeader.retrainPlayer[voter.PlayerId].ToColoredString()), CL.PlayerId); SendMessage(string.Format(GetString("RetrainDeclineOffer"), CustomRoles.CovenLeader.ToColoredString()), voter.PlayerId); CovenLeader.retrainPlayer.Clear(); __instance.RpcClearVoteDelay(voter.GetClientId()); + return false; } if (target != null && suspectPlayerId < 253) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 34a8f0a8b..9cbef1a9c 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1333,7 +1333,7 @@ public static Task DoPostfix(PlayerControl __instance) if (target.Is(CustomRoles.Snitch) && target.Is(CustomRoles.Madmate)) Mark.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), "★")); } - if ((seer.IsPlayerCoven() && target.IsPlayerCoven()) && (CovenManager.HasNecronomicon(target) || CovenManager.HasNecronomicon(seer))) + if ((seer.IsPlayerCoven() && target.IsPlayerCoven()) && (CovenManager.HasNecronomicon(target))) { Mark.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Coven), "♣")); } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 43246484d..3e60c1a2b 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1966,6 +1966,8 @@ "CovenPerRole": "Per Role", "CovenCanSeeEachOthersAddOns": "Coven can see each other's Add-Ons", "NecronomiconNotification": "You have recieved the Necronomicon!
Your powers are now enhanced!", + "NecronomiconVote": "You have voted for {0} to receive the Necronomicon next round. You may now vote normally.", + "CovenDontKillOtherCoven": "Don't try to kill your fellow Coven!", "CovenLeaderMaxRetrains": "Max Retrains", "CovenLeaderRetrainCooldown": "Retrain Cooldown", @@ -3814,6 +3816,7 @@ "HaterCanKillInfected": "Can kill infected team", "HaterCanKillContagious": "Can kill virus team", "HaterCanKillAdmired": "Can kill admirer", + "HaterCanKillEnchanted": "Can kill enchanted", "HorseMode": "Enable to become a horse", "LongMode": "Enable to have a long neck", "InfluencedChangeVote": "Oops! You are so influenced by others!\nYou can not contain your fear that you change voted {0}!", diff --git a/Roles/Coven/CovenLeader.cs b/Roles/Coven/CovenLeader.cs index 60120aa70..ad576844e 100644 --- a/Roles/Coven/CovenLeader.cs +++ b/Roles/Coven/CovenLeader.cs @@ -53,7 +53,14 @@ public override string GetProgressText(byte playerId, bool comms) public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (killer == null || target == null) return false; - if (HasNecronomicon(killer)) return true; + if (HasNecronomicon(killer)) { + if (target.GetCustomRole().IsCovenTeam()) + { + killer.Notify(GetString("CovenDontKillOtherCoven")); + return false; + } + else return true; + } if (AbilityLimit <= 0) { killer.Notify(GetString("CovenLeaderNoRetrain")); diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index ad2381cf0..275d8f370 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -23,6 +23,8 @@ public enum VentOptionList private static readonly Dictionary CovenImpVisOptions = []; private static readonly Dictionary CovenVentOptions = []; + + public static readonly Dictionary necroVotes = []; public static void RunSetUpImpVisOptions(int Id) { foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven()).ToArray()) @@ -123,6 +125,42 @@ public static void GiveNecronomicon(byte target) GetPlayerById(necroHolder).Notify(GetString("NecronomiconNotification")); SendRPC(necroHolder); } + public static void GiveNecronomicon(PlayerControl target) + { + necroHolder = target.PlayerId; + GetPlayerById(necroHolder).Notify(GetString("NecronomiconNotification")); + SendRPC(necroHolder); + } + public static void CheckNecroVotes() + { + Dictionary voteCount = new Dictionary(); + byte currentResult = byte.MinValue; + byte lastResult = byte.MinValue; + foreach (byte voter in necroVotes.Keys) + { + voteCount[voter] = 0; + } + foreach (byte voter in necroVotes.Keys) { + voteCount[necroVotes[voter]]++; + } + foreach (byte vote in voteCount.Keys) { + if (voteCount[vote] >= voteCount[currentResult]) { + lastResult = currentResult; + currentResult = vote; + } + } + if (currentResult == byte.MinValue) { } + else if (currentResult == lastResult) + { + Logger.Info($"{GetPlayerById(currentResult).GetRealName()} and {GetPlayerById(lastResult).GetRealName()} had equal Necronomicon votes, not changing Necronomicon", "Coven"); + } + else + { + GiveNecronomicon(currentResult); + Logger.Info($"{GetPlayerById(currentResult).GetRealName()} had the most Necronomicon votes, giving them Necronomicon", "Coven"); + } + necroVotes.Clear(); + } public static void NecronomiconCheck() { diff --git a/Roles/Coven/HexMaster.cs b/Roles/Coven/HexMaster.cs index 180350904..de17486a4 100644 --- a/Roles/Coven/HexMaster.cs +++ b/Roles/Coven/HexMaster.cs @@ -256,9 +256,14 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } if (killer.CheckDoubleTrigger(target, () => { SetHexedNecronomicon(killer, target); })) { - if (HasNecronomicon(killer) && !target.IsPlayerCoven()) + if (HasNecronomicon(killer)) { - return true; + if (target.GetCustomRole().IsCovenTeam()) + { + killer.Notify(GetString("CovenDontKillOtherCoven")); + return false; + } + else return true; } } return false; diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index 8d9d2d5cd..d7a6d3ccd 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -73,6 +73,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t Main.PlayerStates[target.PlayerId].SetDead(); return true; } + killer.Notify(GetString("CovenDontKillOtherCoven")); return false; } else diff --git a/Roles/Coven/Jinx.cs b/Roles/Coven/Jinx.cs index f98146724..7e5fada09 100644 --- a/Roles/Coven/Jinx.cs +++ b/Roles/Coven/Jinx.cs @@ -81,9 +81,19 @@ public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl t public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (killer == null || target == null) return false; - return HasNecronomicon(killer) && killer.CheckDoubleTrigger(target, () => { - JinxPlayer(killer, target); - }); + if (killer.CheckDoubleTrigger(target, () => { JinxPlayer(killer, target); })) + { + if (HasNecronomicon(killer)) + { + if (target.GetCustomRole().IsCovenTeam()) + { + killer.Notify(GetString("CovenDontKillOtherCoven")); + return false; + } + else return true; + } + } + return false; } private void JinxPlayer(PlayerControl jinx, PlayerControl target) { diff --git a/Roles/Coven/Medusa.cs b/Roles/Coven/Medusa.cs index e82dfbe13..dbd5e8a03 100644 --- a/Roles/Coven/Medusa.cs +++ b/Roles/Coven/Medusa.cs @@ -95,7 +95,12 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (killer == null || target == null) return false; - if (HasNecronomicon(killer) && !target.IsPlayerCoven()) { + if (HasNecronomicon(killer)) { + if (target.GetCustomRole().IsCovenTeam()) + { + killer.Notify(GetString("CovenDontKillOtherCoven")); + return false; + } killer.RpcMurderPlayer(target); killer.ResetKillCooldown(); Main.UnreportableBodies.Add(target.PlayerId); diff --git a/Roles/Coven/Poisoner.cs b/Roles/Coven/Poisoner.cs index 64fddb298..eac250927 100644 --- a/Roles/Coven/Poisoner.cs +++ b/Roles/Coven/Poisoner.cs @@ -81,6 +81,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t PoisonedPlayers.Add(target.PlayerId, new(killer.PlayerId, 0f)); } } + killer.Notify(GetString("CovenDontKillOtherCoven")); return false; } else diff --git a/Roles/Coven/PotionMaster.cs b/Roles/Coven/PotionMaster.cs index f285ae378..0e940aed7 100644 --- a/Roles/Coven/PotionMaster.cs +++ b/Roles/Coven/PotionMaster.cs @@ -103,15 +103,24 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - if (HasNecronomicon(killer)) - { - return killer.CheckDoubleTrigger(target, () => { SetRitual(killer, target); }); - } - else + if (!HasNecronomicon(killer)) { SetRitual(killer, target); return false; } + if (killer.CheckDoubleTrigger(target, () => { SetRitual(killer, target); })) + { + if (HasNecronomicon(killer)) + { + if (target.GetCustomRole().IsCovenTeam()) + { + killer.Notify(GetString("CovenDontKillOtherCoven")); + return false; + } + else return true; + } + } + return false; } public static bool IsReveal(byte seer, byte target) => RevealList[seer].Contains(target); diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index 6a0b2de40..b8679f9f4 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -72,6 +72,15 @@ public override void OnReportDeadBody(PlayerControl hatsune, NetworkedPlayerInfo RitualLimit[pid] = MaxRitsPerRound.GetInt(); } } + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (target.GetCustomRole().IsCovenTeam()) + { + killer.Notify(GetString("CovenDontKillOtherCoven")); + return false; + } + return true; + } public override string NotifyPlayerName(PlayerControl seer, PlayerControl target, string TargetPlayerName = "", bool IsForMeeting = false) => IsForMeeting && seer.IsAlive() && target.IsAlive() ? ColorString(GetRoleColor(CustomRoles.Ritualist), target.PlayerId.ToString()) + " " + TargetPlayerName : ""; public override string PVANameText(PlayerVoteArea pva, PlayerControl seer, PlayerControl target) diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index c969f2949..1ec608482 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -130,7 +130,7 @@ public override void UnShapeShiftButton(PlayerControl pc) originalSpeed.Add(sacrifist, Main.AllPlayerSpeed[sacrifist]); Main.AllPlayerSpeed[sacrifist] = Speed.GetFloat(); pc.MarkDirtySettings(); - Logger.Info($"{pc.GetRealName()} Changed Speed for {randPlayerPC.GetRealName} and self", "Sacrifist"); + Logger.Info($"{pc.GetRealName()} Changed Speed for {randPlayerPC.GetRealName()} and self", "Sacrifist"); pc.Notify(GetString("SacrifistSpeedDebuff"), SpeedDuration.GetFloat()); _ = new LateTask(() => { @@ -161,14 +161,14 @@ public override void UnShapeShiftButton(PlayerControl pc) Main.AllPlayerKillCooldown[randPlayer] += Main.AllPlayerKillCooldown[randPlayer] * (IncreasedCooldown.GetFloat() / 100); Main.AllPlayerKillCooldown[sacrifist] += Main.AllPlayerKillCooldown[sacrifist] * (IncreasedCooldown.GetFloat() / 100); maxDebuffTimer += maxDebuffTimer * (IncreasedCooldown.GetFloat() / 100); - Logger.Info($"{pc.GetRealName()} Changed Cooldown for {randPlayerPC.GetRealName} and self", "Sacrifist"); + Logger.Info($"{pc.GetRealName()} Changed Cooldown for {randPlayerPC.GetRealName()} and self", "Sacrifist"); pc.Notify(GetString("SacrifistCooldownDebuff"), 5f); break; // Cant Fix Sabotage (not coding allat, just give them Fool) case 3: GetPlayerById(sacrifist).RpcSetCustomRole(CustomRoles.Fool); randPlayerPC.RpcSetCustomRole(CustomRoles.Fool); - Logger.Info($"{pc.GetRealName()} Gave Fool to {randPlayerPC.GetRealName} and self", "Sacrifist"); + Logger.Info($"{pc.GetRealName()} Gave Fool to {randPlayerPC.GetRealName()} and self", "Sacrifist"); pc.Notify(GetString("SacrifistFoolDebuff"), 5f); break; // Make one of them call a meeting @@ -184,7 +184,7 @@ public override void UnShapeShiftButton(PlayerControl pc) break; case 1: GetPlayerById(sacrifist).NoCheckStartMeeting(null); - Logger.Info($"{pc.GetRealName()} Made {randPlayerPC.GetRealName} call meeting", "Sacrifist"); + Logger.Info($"{pc.GetRealName()} Made {randPlayerPC.GetRealName()} call meeting", "Sacrifist"); break; } }, 2f, "Sacrifist Call Meeting"); @@ -193,7 +193,7 @@ public override void UnShapeShiftButton(PlayerControl pc) case 5: ReportDeadBodyPatch.CanReport[randPlayer] = false; ReportDeadBodyPatch.CanReport[sacrifist] = false; - Logger.Info($"{pc.GetRealName()} Made {randPlayerPC.GetRealName} and self unable to report", "Sacrifist"); + Logger.Info($"{pc.GetRealName()} Made {randPlayerPC.GetRealName()} and self unable to report", "Sacrifist"); pc.Notify(GetString("SacrifistReportDebuff"), 5f); break; // Reset Tasks @@ -205,7 +205,7 @@ public override void UnShapeShiftButton(PlayerControl pc) pc.Data.RpcSetTasks(new Il2CppStructArray(0)); //Let taskassign patch decide the tasks taskStateSacrif.CompletedTasksCount = 0; pc.Notify(GetString("SacrifistTasksDebuff"), 5f); - Logger.Info($"{pc.GetRealName()} Made {randPlayerPC.GetRealName} and self reset tasks", "Sacrifist"); + Logger.Info($"{pc.GetRealName()} Made {randPlayerPC.GetRealName()} and self reset tasks", "Sacrifist"); break; // Swap Skins case 7: @@ -219,7 +219,7 @@ public override void UnShapeShiftButton(PlayerControl pc) Camouflage.PlayerSkins[randPlayer] = randPlayerPC.CurrentOutfit; randPlayerPC.SetNewOutfit(temp, setName: true, setNamePlate: true); pc.Notify(GetString("SacrifistSwapSkinsDebuff"), 5f); - Logger.Info($"{pc.GetRealName()} swapped outfit with {randPlayerPC.GetRealName}", "Sacrifist"); + Logger.Info($"{pc.GetRealName()} swapped outfit with {randPlayerPC.GetRealName()}", "Sacrifist"); break; // Swap Sacrifist and Target case 8: @@ -240,7 +240,7 @@ public override void UnShapeShiftButton(PlayerControl pc) pc.Notify(ColorString(GetRoleColor(CustomRoles.Sacrifist), GetString("ErrorTeleport"))); } }, 0.01f, "Sacrifist Swap"); - Logger.Info($"{pc.GetRealName()} Will Swap with {randPlayerPC.GetRealName} 5s after exiting vent", "Sacrifist"); + Logger.Info($"{pc.GetRealName()} Will Swap with {randPlayerPC.GetRealName()} 5s after exiting vent", "Sacrifist"); pc.Notify(GetString("SacrifistSwapDebuff"), 15f); break; } From 0e961b578825311ab0731b51087e3902c64f3133 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 15 Nov 2024 20:49:58 -0500 Subject: [PATCH 036/101] edit coveninfo --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 3e60c1a2b..7e3361a61 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2513,7 +2513,7 @@ "ApocalypseInfoTitle": "Neutral Apocalypse Info:", "Message.ApocalypseInfo": "Every role of the <#ff174f>Apocalypse Team has their own objective to carry out in order to transform.\n<#2B0804>Transformed <#ff174f>Apocalypse members have a drastic change on the game and are immortal (except for being voted), but everyone will be notified that they have transformed.\n\nRoles: <#e5f6b4>Plaguebearer, <#A675A1>Soul Collector, <#bf9f7a>Baker, <#cc0044>Berserker\nTransformed: <#343136>Pestilence, <#644661>Death, <#83461c>Famine, <#2B0804>War\n\nApocalypse members can see eachother's roles and ability icons.\nLike Neutral Killers, Apocalypse members keep the game going as well, have fun!", "CovenInfoTitle": "Coven Info:", - "Message.CovenInfo": "The <#ac42f2>Coven is a faction of evildoers who specialize in witchcraft. Their goal is to kill all who would oppose the <#ac42f2>Coven.\nThey use the Necronomicon to enhance their powers, but only one member at a time can hold it.\nMost Coven roles can only kill if they hold the Necronomicon. A random Coven member will spawn with the Necronomicon, denoted by the <#ac42f2>♣ symbol next to their name.\nIf the Necronomicon holder dies, the Necronomicon will be randomly given to another <#ac42f2>Coven member that is alive.", + "Message.CovenInfo": "The <#ac42f2>Coven is a faction of evildoers who specialize in witchcraft. Their goal is to kill all who would oppose the <#ac42f2>Coven.\nThey use the Necronomicon to enhance their powers, but only one member at a time can hold it.\nMost Coven roles can only kill if they hold the Necronomicon. A random Coven member will spawn with the Necronomicon, denoted by the <#ac42f2>♣ symbol next to their name.\nIf the Necronomicon holder dies, the Necronomicon will be randomly given to another <#ac42f2>Coven member that is alive.\nDuring a meeting, Coven players can vote each other to determine who will gain the Necronomicon the following round. If the votes end in a tie or nobody votes, the Necronomicon holder will be the same. Votes will be returned and you will be allowed to vote normally.", "Message.MeCommandInfo": "Hi [{0}] {1} !\n\nfriend-code Hash-Puid Type 
{2} {3} {4}

IsDev HasUp /color-Bypass
{5} {6} {7}

", "Message.MeCommandTargetInfo": "Selected [{0}] Player {1} ,\n\nTheir friend code is {2}.\n\nTheir hash puid is {3}.\n\nTheir TOHE Discord role is {4}.\n\n", "Message.MeCommandInvalidID": "The ID you entered seems incorrect. \nPlease use /id to get the player ID of online players", From 8c78e7af35478bbb183d7868d4b03bd2bcef2f9c Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 16 Nov 2024 16:29:09 -0500 Subject: [PATCH 037/101] merge mod settings and game modifiers --- Modules/OptionHolder.cs | 233 +++++++++++++++---------------- Modules/OptionItem/OptionItem.cs | 1 - Patches/GameSettingMenuPatch.cs | 2 - 3 files changed, 116 insertions(+), 120 deletions(-) diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 572c46fc7..12c44fbcf 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -1279,6 +1279,45 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetGameMode(CustomGameMode.Standard) .SetColor(new Color32(255, 238, 232, byte.MaxValue)); + TextOptionItem.Create(10000028, "MenuTitle.Guessers", TabGroup.ModSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(Color.yellow) + .SetHeader(true); + GuesserMode = BooleanOptionItem.Create(60680, "GuesserMode", false, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.Standard) + .SetColor(Color.yellow) + .SetHeader(true); + CrewmatesCanGuess = BooleanOptionItem.Create(60681, "CrewmatesCanGuess", false, TabGroup.ModSettings, false) + .SetParent(GuesserMode); + CrewCanGuessCrew = BooleanOptionItem.Create(60686, "CrewCanGuessCrew", true, TabGroup.ModSettings, false) + .SetParent(CrewmatesCanGuess); + ImpostorsCanGuess = BooleanOptionItem.Create(60682, "ImpostorsCanGuess", false, TabGroup.ModSettings, false) + .SetParent(GuesserMode); + ImpCanGuessImp = BooleanOptionItem.Create(60687, "ImpCanGuessImp", true, TabGroup.ModSettings, false) + .SetParent(ImpostorsCanGuess); + NeutralKillersCanGuess = BooleanOptionItem.Create(60683, "NeutralKillersCanGuess", false, TabGroup.ModSettings, false) + .SetParent(GuesserMode); + NeutralApocalypseCanGuess = BooleanOptionItem.Create(60690, "NeutralApocalypseCanGuess", false, TabGroup.ModSettings, false) + .SetParent(GuesserMode); + ApocCanGuessApoc = BooleanOptionItem.Create(60691, "ApocCanGuessApoc", false, TabGroup.ModSettings, false) + .SetParent(NeutralApocalypseCanGuess); + PassiveNeutralsCanGuess = BooleanOptionItem.Create(60684, "PassiveNeutralsCanGuess", false, TabGroup.ModSettings, false) + .SetParent(GuesserMode); + CovenCanGuess = BooleanOptionItem.Create(60693, "CovenCanGuess", false, TabGroup.ModSettings, false) + .SetParent(GuesserMode); + CovenCanGuessCoven = BooleanOptionItem.Create(60692, "CovenCanGuessCoven", false, TabGroup.ModSettings, false) + .SetParent(CovenCanGuess); + CanGuessAddons = BooleanOptionItem.Create(60685, "CanGuessAddons", true, TabGroup.ModSettings, false) + .SetParent(GuesserMode); + HideGuesserCommands = BooleanOptionItem.Create(60688, "GuesserTryHideMsg", true, TabGroup.ModSettings, false) + .SetParent(GuesserMode) + .SetColor(Color.green); + + ShowOnlyEnabledRolesInGuesserUI = BooleanOptionItem.Create(60689, "ShowOnlyEnabledRolesInGuesserUI", true, TabGroup.ModSettings, false) + .SetHeader(true) + .SetGameMode(CustomGameMode.Standard) + .SetColor(Color.cyan); + //Maps Settings TextOptionItem.Create(10000025, "MenuTitle.MapsSettings", TabGroup.ModSettings) .SetColor(new Color32(19, 188, 233, byte.MaxValue)); @@ -1599,217 +1638,177 @@ private static System.Collections.IEnumerator CoLoadOptions() //.SetGameMode(CustomGameMode.Standard); //Disable Short Tasks - DisableShortTasks = BooleanOptionItem.Create(60594, "DisableShortTasks", false, TabGroup.ModifierSettings, false) + DisableShortTasks = BooleanOptionItem.Create(60594, "DisableShortTasks", false, TabGroup.ModSettings, false) .HideInFFA() .SetHeader(true) .SetColor(new Color32(239, 89, 175, byte.MaxValue)); - DisableCleanVent = BooleanOptionItem.Create(60595, "DisableCleanVent", false, TabGroup.ModifierSettings, false) + DisableCleanVent = BooleanOptionItem.Create(60595, "DisableCleanVent", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableCalibrateDistributor = BooleanOptionItem.Create(60596, "DisableCalibrateDistributor", false, TabGroup.ModifierSettings, false) + DisableCalibrateDistributor = BooleanOptionItem.Create(60596, "DisableCalibrateDistributor", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableChartCourse = BooleanOptionItem.Create(60597, "DisableChartCourse", false, TabGroup.ModifierSettings, false) + DisableChartCourse = BooleanOptionItem.Create(60597, "DisableChartCourse", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableStabilizeSteering = BooleanOptionItem.Create(60598, "DisableStabilizeSteering", false, TabGroup.ModifierSettings, false) + DisableStabilizeSteering = BooleanOptionItem.Create(60598, "DisableStabilizeSteering", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableCleanO2Filter = BooleanOptionItem.Create(60599, "DisableCleanO2Filter", false, TabGroup.ModifierSettings, false) + DisableCleanO2Filter = BooleanOptionItem.Create(60599, "DisableCleanO2Filter", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableUnlockManifolds = BooleanOptionItem.Create(60600, "DisableUnlockManifolds", false, TabGroup.ModifierSettings, false) + DisableUnlockManifolds = BooleanOptionItem.Create(60600, "DisableUnlockManifolds", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisablePrimeShields = BooleanOptionItem.Create(60601, "DisablePrimeShields", false, TabGroup.ModifierSettings, false) + DisablePrimeShields = BooleanOptionItem.Create(60601, "DisablePrimeShields", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableMeasureWeather = BooleanOptionItem.Create(60602, "DisableMeasureWeather", false, TabGroup.ModifierSettings, false) + DisableMeasureWeather = BooleanOptionItem.Create(60602, "DisableMeasureWeather", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableBuyBeverage = BooleanOptionItem.Create(60603, "DisableBuyBeverage", false, TabGroup.ModifierSettings, false) + DisableBuyBeverage = BooleanOptionItem.Create(60603, "DisableBuyBeverage", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableAssembleArtifact = BooleanOptionItem.Create(60604, "DisableAssembleArtifact", false, TabGroup.ModifierSettings, false) + DisableAssembleArtifact = BooleanOptionItem.Create(60604, "DisableAssembleArtifact", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableSortSamples = BooleanOptionItem.Create(60605, "DisableSortSamples", false, TabGroup.ModifierSettings, false) + DisableSortSamples = BooleanOptionItem.Create(60605, "DisableSortSamples", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableProcessData = BooleanOptionItem.Create(60606, "DisableProcessData", false, TabGroup.ModifierSettings, false) + DisableProcessData = BooleanOptionItem.Create(60606, "DisableProcessData", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableRunDiagnostics = BooleanOptionItem.Create(60607, "DisableRunDiagnostics", false, TabGroup.ModifierSettings, false) + DisableRunDiagnostics = BooleanOptionItem.Create(60607, "DisableRunDiagnostics", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableRepairDrill = BooleanOptionItem.Create(60608, "DisableRepairDrill", false, TabGroup.ModifierSettings, false) + DisableRepairDrill = BooleanOptionItem.Create(60608, "DisableRepairDrill", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableAlignTelescope = BooleanOptionItem.Create(60609, "DisableAlignTelescope", false, TabGroup.ModifierSettings, false) + DisableAlignTelescope = BooleanOptionItem.Create(60609, "DisableAlignTelescope", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableRecordTemperature = BooleanOptionItem.Create(60610, "DisableRecordTemperature", false, TabGroup.ModifierSettings, false) + DisableRecordTemperature = BooleanOptionItem.Create(60610, "DisableRecordTemperature", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableFillCanisters = BooleanOptionItem.Create(60611, "DisableFillCanisters", false, TabGroup.ModifierSettings, false) + DisableFillCanisters = BooleanOptionItem.Create(60611, "DisableFillCanisters", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableMonitorTree = BooleanOptionItem.Create(60612, "DisableMonitorTree", false, TabGroup.ModifierSettings, false) + DisableMonitorTree = BooleanOptionItem.Create(60612, "DisableMonitorTree", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableStoreArtifacts = BooleanOptionItem.Create(60613, "DisableStoreArtifacts", false, TabGroup.ModifierSettings, false) + DisableStoreArtifacts = BooleanOptionItem.Create(60613, "DisableStoreArtifacts", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisablePutAwayPistols = BooleanOptionItem.Create(60614, "DisablePutAwayPistols", false, TabGroup.ModifierSettings, false) + DisablePutAwayPistols = BooleanOptionItem.Create(60614, "DisablePutAwayPistols", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisablePutAwayRifles = BooleanOptionItem.Create(60615, "DisablePutAwayRifles", false, TabGroup.ModifierSettings, false) + DisablePutAwayRifles = BooleanOptionItem.Create(60615, "DisablePutAwayRifles", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableMakeBurger = BooleanOptionItem.Create(60616, "DisableMakeBurger", false, TabGroup.ModifierSettings, false) + DisableMakeBurger = BooleanOptionItem.Create(60616, "DisableMakeBurger", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableCleanToilet = BooleanOptionItem.Create(60617, "DisableCleanToilet", false, TabGroup.ModifierSettings, false) + DisableCleanToilet = BooleanOptionItem.Create(60617, "DisableCleanToilet", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableDecontaminate = BooleanOptionItem.Create(60618, "DisableDecontaminate", false, TabGroup.ModifierSettings, false) + DisableDecontaminate = BooleanOptionItem.Create(60618, "DisableDecontaminate", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableSortRecords = BooleanOptionItem.Create(60619, "DisableSortRecords", false, TabGroup.ModifierSettings, false) + DisableSortRecords = BooleanOptionItem.Create(60619, "DisableSortRecords", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableFixShower = BooleanOptionItem.Create(60620, "DisableFixShower", false, TabGroup.ModifierSettings, false) + DisableFixShower = BooleanOptionItem.Create(60620, "DisableFixShower", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisablePickUpTowels = BooleanOptionItem.Create(60621, "DisablePickUpTowels", false, TabGroup.ModifierSettings, false) + DisablePickUpTowels = BooleanOptionItem.Create(60621, "DisablePickUpTowels", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisablePolishRuby = BooleanOptionItem.Create(60622, "DisablePolishRuby", false, TabGroup.ModifierSettings, false) + DisablePolishRuby = BooleanOptionItem.Create(60622, "DisablePolishRuby", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableDressMannequin = BooleanOptionItem.Create(60623, "DisableDressMannequin", false, TabGroup.ModifierSettings, false) + DisableDressMannequin = BooleanOptionItem.Create(60623, "DisableDressMannequin", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableFixAntenna = BooleanOptionItem.Create(60656, "DisableFixAntenna", false, TabGroup.ModifierSettings, false) + DisableFixAntenna = BooleanOptionItem.Create(60656, "DisableFixAntenna", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableBuildSandcastle = BooleanOptionItem.Create(60657, "DisableBuildSandcastle", false, TabGroup.ModifierSettings, false) + DisableBuildSandcastle = BooleanOptionItem.Create(60657, "DisableBuildSandcastle", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableCrankGenerator = BooleanOptionItem.Create(60658, "DisableCrankGenerator", false, TabGroup.ModifierSettings, false) + DisableCrankGenerator = BooleanOptionItem.Create(60658, "DisableCrankGenerator", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableMonitorMushroom = BooleanOptionItem.Create(60659, "DisableMonitorMushroom", false, TabGroup.ModifierSettings, false) + DisableMonitorMushroom = BooleanOptionItem.Create(60659, "DisableMonitorMushroom", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisablePlayVideoGame = BooleanOptionItem.Create(60660, "DisablePlayVideoGame", false, TabGroup.ModifierSettings, false) + DisablePlayVideoGame = BooleanOptionItem.Create(60660, "DisablePlayVideoGame", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableFindSignal = BooleanOptionItem.Create(60661, "DisableFindSignal", false, TabGroup.ModifierSettings, false) + DisableFindSignal = BooleanOptionItem.Create(60661, "DisableFindSignal", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableThrowFisbee = BooleanOptionItem.Create(60662, "DisableThrowFisbee", false, TabGroup.ModifierSettings, false) + DisableThrowFisbee = BooleanOptionItem.Create(60662, "DisableThrowFisbee", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableLiftWeights = BooleanOptionItem.Create(60663, "DisableLiftWeights", false, TabGroup.ModifierSettings, false) + DisableLiftWeights = BooleanOptionItem.Create(60663, "DisableLiftWeights", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); - DisableCollectShells = BooleanOptionItem.Create(60664, "DisableCollectShells", false, TabGroup.ModifierSettings, false) + DisableCollectShells = BooleanOptionItem.Create(60664, "DisableCollectShells", false, TabGroup.ModSettings, false) .SetParent(DisableShortTasks); //Disable Common Tasks - DisableCommonTasks = BooleanOptionItem.Create(60627, "DisableCommonTasks", false, TabGroup.ModifierSettings, false) + DisableCommonTasks = BooleanOptionItem.Create(60627, "DisableCommonTasks", false, TabGroup.ModSettings, false) .HideInFFA() .SetColor(new Color32(239, 89, 175, byte.MaxValue)); - DisableSwipeCard = BooleanOptionItem.Create(60628, "DisableSwipeCardTask", false, TabGroup.ModifierSettings, false) + DisableSwipeCard = BooleanOptionItem.Create(60628, "DisableSwipeCardTask", false, TabGroup.ModSettings, false) .SetParent(DisableCommonTasks); - DisableFixWiring = BooleanOptionItem.Create(60629, "DisableFixWiring", false, TabGroup.ModifierSettings, false) + DisableFixWiring = BooleanOptionItem.Create(60629, "DisableFixWiring", false, TabGroup.ModSettings, false) .SetParent(DisableCommonTasks); - DisableEnterIdCode = BooleanOptionItem.Create(60630, "DisableEnterIdCode", false, TabGroup.ModifierSettings, false) + DisableEnterIdCode = BooleanOptionItem.Create(60630, "DisableEnterIdCode", false, TabGroup.ModSettings, false) .SetParent(DisableCommonTasks); - DisableInsertKeys = BooleanOptionItem.Create(60631, "DisableInsertKeys", false, TabGroup.ModifierSettings, false) + DisableInsertKeys = BooleanOptionItem.Create(60631, "DisableInsertKeys", false, TabGroup.ModSettings, false) .SetParent(DisableCommonTasks); - DisableScanBoardingPass = BooleanOptionItem.Create(60632, "DisableScanBoardingPass", false, TabGroup.ModifierSettings, false) + DisableScanBoardingPass = BooleanOptionItem.Create(60632, "DisableScanBoardingPass", false, TabGroup.ModSettings, false) .SetParent(DisableCommonTasks); - DisableRoastMarshmallow = BooleanOptionItem.Create(60624, "DisableRoastMarshmallow", false, TabGroup.ModifierSettings, false) + DisableRoastMarshmallow = BooleanOptionItem.Create(60624, "DisableRoastMarshmallow", false, TabGroup.ModSettings, false) .SetParent(DisableCommonTasks); - DisableCollectSamples = BooleanOptionItem.Create(60625, "DisableCollectSamples", false, TabGroup.ModifierSettings, false) + DisableCollectSamples = BooleanOptionItem.Create(60625, "DisableCollectSamples", false, TabGroup.ModSettings, false) .SetParent(DisableCommonTasks); - DisableReplaceParts = BooleanOptionItem.Create(60626, "DisableReplaceParts", false, TabGroup.ModifierSettings, false) + DisableReplaceParts = BooleanOptionItem.Create(60626, "DisableReplaceParts", false, TabGroup.ModSettings, false) .SetParent(DisableCommonTasks); //Disable Long Tasks - DisableLongTasks = BooleanOptionItem.Create(60640, "DisableLongTasks", false, TabGroup.ModifierSettings, false) + DisableLongTasks = BooleanOptionItem.Create(60640, "DisableLongTasks", false, TabGroup.ModSettings, false) .HideInFFA() .SetColor(new Color32(239, 89, 175, byte.MaxValue)); - DisableSubmitScan = BooleanOptionItem.Create(60641, "DisableSubmitScanTask", false, TabGroup.ModifierSettings, false) + DisableSubmitScan = BooleanOptionItem.Create(60641, "DisableSubmitScanTask", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableUnlockSafe = BooleanOptionItem.Create(60642, "DisableUnlockSafeTask", false, TabGroup.ModifierSettings, false) + DisableUnlockSafe = BooleanOptionItem.Create(60642, "DisableUnlockSafeTask", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableStartReactor = BooleanOptionItem.Create(60643, "DisableStartReactorTask", false, TabGroup.ModifierSettings, false) + DisableStartReactor = BooleanOptionItem.Create(60643, "DisableStartReactorTask", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableResetBreaker = BooleanOptionItem.Create(60644, "DisableResetBreakerTask", false, TabGroup.ModifierSettings, false) + DisableResetBreaker = BooleanOptionItem.Create(60644, "DisableResetBreakerTask", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableAlignEngineOutput = BooleanOptionItem.Create(60645, "DisableAlignEngineOutput", false, TabGroup.ModifierSettings, false) + DisableAlignEngineOutput = BooleanOptionItem.Create(60645, "DisableAlignEngineOutput", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableInspectSample = BooleanOptionItem.Create(60646, "DisableInspectSample", false, TabGroup.ModifierSettings, false) + DisableInspectSample = BooleanOptionItem.Create(60646, "DisableInspectSample", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableEmptyChute = BooleanOptionItem.Create(60647, "DisableEmptyChute", false, TabGroup.ModifierSettings, false) + DisableEmptyChute = BooleanOptionItem.Create(60647, "DisableEmptyChute", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableClearAsteroids = BooleanOptionItem.Create(60648, "DisableClearAsteroids", false, TabGroup.ModifierSettings, false) + DisableClearAsteroids = BooleanOptionItem.Create(60648, "DisableClearAsteroids", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableWaterPlants = BooleanOptionItem.Create(60649, "DisableWaterPlants", false, TabGroup.ModifierSettings, false) + DisableWaterPlants = BooleanOptionItem.Create(60649, "DisableWaterPlants", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableOpenWaterways = BooleanOptionItem.Create(60650, "DisableOpenWaterways", false, TabGroup.ModifierSettings, false) + DisableOpenWaterways = BooleanOptionItem.Create(60650, "DisableOpenWaterways", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableReplaceWaterJug = BooleanOptionItem.Create(60651, "DisableReplaceWaterJug", false, TabGroup.ModifierSettings, false) + DisableReplaceWaterJug = BooleanOptionItem.Create(60651, "DisableReplaceWaterJug", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableRebootWifi = BooleanOptionItem.Create(60652, "DisableRebootWifi", false, TabGroup.ModifierSettings, false) + DisableRebootWifi = BooleanOptionItem.Create(60652, "DisableRebootWifi", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableDevelopPhotos = BooleanOptionItem.Create(60653, "DisableDevelopPhotos", false, TabGroup.ModifierSettings, false) + DisableDevelopPhotos = BooleanOptionItem.Create(60653, "DisableDevelopPhotos", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableRewindTapes = BooleanOptionItem.Create(60654, "DisableRewindTapes", false, TabGroup.ModifierSettings, false) + DisableRewindTapes = BooleanOptionItem.Create(60654, "DisableRewindTapes", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableStartFans = BooleanOptionItem.Create(60655, "DisableStartFans", false, TabGroup.ModifierSettings, false) + DisableStartFans = BooleanOptionItem.Create(60655, "DisableStartFans", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableCollectVegetables = BooleanOptionItem.Create(60633, "DisableCollectVegetables", false, TabGroup.ModifierSettings, false) + DisableCollectVegetables = BooleanOptionItem.Create(60633, "DisableCollectVegetables", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableMineOres = BooleanOptionItem.Create(60634, "DisableMineOres", false, TabGroup.ModifierSettings, false) + DisableMineOres = BooleanOptionItem.Create(60634, "DisableMineOres", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableExtractFuel = BooleanOptionItem.Create(60635, "DisableExtractFuel", false, TabGroup.ModifierSettings, false) + DisableExtractFuel = BooleanOptionItem.Create(60635, "DisableExtractFuel", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableCatchFish = BooleanOptionItem.Create(60636, "DisableCatchFish", false, TabGroup.ModifierSettings, false) + DisableCatchFish = BooleanOptionItem.Create(60636, "DisableCatchFish", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisablePolishGem = BooleanOptionItem.Create(60637, "DisablePolishGem", false, TabGroup.ModifierSettings, false) + DisablePolishGem = BooleanOptionItem.Create(60637, "DisablePolishGem", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableHelpCritter = BooleanOptionItem.Create(60638, "DisableHelpCritter", false, TabGroup.ModifierSettings, false) + DisableHelpCritter = BooleanOptionItem.Create(60638, "DisableHelpCritter", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); - DisableHoistSupplies = BooleanOptionItem.Create(60639, "DisableHoistSupplies", false, TabGroup.ModifierSettings, false) + DisableHoistSupplies = BooleanOptionItem.Create(60639, "DisableHoistSupplies", false, TabGroup.ModSettings, false) .SetParent(DisableLongTasks); //Disable Divert Power, Weather Nodes and etc. situational Tasks - DisableOtherTasks = BooleanOptionItem.Create(60665, "DisableOtherTasks", false, TabGroup.ModifierSettings, false) + DisableOtherTasks = BooleanOptionItem.Create(60665, "DisableOtherTasks", false, TabGroup.ModSettings, false) .HideInFFA() .SetColor(new Color32(239, 89, 175, byte.MaxValue)); - DisableUploadData = BooleanOptionItem.Create(60666, "DisableUploadDataTask", false, TabGroup.ModifierSettings, false) + DisableUploadData = BooleanOptionItem.Create(60666, "DisableUploadDataTask", false, TabGroup.ModSettings, false) .SetParent(DisableOtherTasks); - DisableEmptyGarbage = BooleanOptionItem.Create(60667, "DisableEmptyGarbage", false, TabGroup.ModifierSettings, false) + DisableEmptyGarbage = BooleanOptionItem.Create(60667, "DisableEmptyGarbage", false, TabGroup.ModSettings, false) .SetParent(DisableOtherTasks); - DisableFuelEngines = BooleanOptionItem.Create(60668, "DisableFuelEngines", false, TabGroup.ModifierSettings, false) + DisableFuelEngines = BooleanOptionItem.Create(60668, "DisableFuelEngines", false, TabGroup.ModSettings, false) .SetParent(DisableOtherTasks); - DisableDivertPower = BooleanOptionItem.Create(60669, "DisableDivertPower", false, TabGroup.ModifierSettings, false) + DisableDivertPower = BooleanOptionItem.Create(60669, "DisableDivertPower", false, TabGroup.ModSettings, false) .SetParent(DisableOtherTasks); - DisableActivateWeatherNodes = BooleanOptionItem.Create(60670, "DisableActivateWeatherNodes", false, TabGroup.ModifierSettings, false) + DisableActivateWeatherNodes = BooleanOptionItem.Create(60670, "DisableActivateWeatherNodes", false, TabGroup.ModSettings, false) .SetParent(DisableOtherTasks); - - TextOptionItem.Create(10000028, "MenuTitle.Guessers", TabGroup.ModifierSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(Color.yellow) - .SetHeader(true); - GuesserMode = BooleanOptionItem.Create(60680, "GuesserMode", false, TabGroup.ModifierSettings, false) - .SetGameMode(CustomGameMode.Standard) - .SetColor(Color.yellow) - .SetHeader(true); - CrewmatesCanGuess = BooleanOptionItem.Create(60681, "CrewmatesCanGuess", false, TabGroup.ModifierSettings, false) - .SetParent(GuesserMode); - CrewCanGuessCrew = BooleanOptionItem.Create(60686, "CrewCanGuessCrew", true, TabGroup.ModifierSettings, false) - .SetParent(CrewmatesCanGuess); - ImpostorsCanGuess = BooleanOptionItem.Create(60682, "ImpostorsCanGuess", false, TabGroup.ModifierSettings, false) - .SetParent(GuesserMode); - ImpCanGuessImp = BooleanOptionItem.Create(60687, "ImpCanGuessImp", true, TabGroup.ModifierSettings, false) - .SetParent(ImpostorsCanGuess); - NeutralKillersCanGuess = BooleanOptionItem.Create(60683, "NeutralKillersCanGuess", false, TabGroup.ModifierSettings, false) - .SetParent(GuesserMode); - NeutralApocalypseCanGuess = BooleanOptionItem.Create(60690, "NeutralApocalypseCanGuess", false, TabGroup.ModifierSettings, false) - .SetParent(GuesserMode); - ApocCanGuessApoc = BooleanOptionItem.Create(60691, "ApocCanGuessApoc", false, TabGroup.ModifierSettings, false) - .SetParent(NeutralApocalypseCanGuess); - PassiveNeutralsCanGuess = BooleanOptionItem.Create(60684, "PassiveNeutralsCanGuess", false, TabGroup.ModifierSettings, false) - .SetParent(GuesserMode); - CovenCanGuess = BooleanOptionItem.Create(60693, "CovenCanGuess", false, TabGroup.ModifierSettings, false) - .SetParent(GuesserMode); - CovenCanGuessCoven = BooleanOptionItem.Create(60692, "CovenCanGuessCoven", false, TabGroup.ModifierSettings, false) - .SetParent(CovenCanGuess); - CanGuessAddons = BooleanOptionItem.Create(60685, "CanGuessAddons", true, TabGroup.ModifierSettings, false) - .SetParent(GuesserMode); - HideGuesserCommands = BooleanOptionItem.Create(60688, "GuesserTryHideMsg", true, TabGroup.ModifierSettings, false) - .SetParent(GuesserMode) - .SetColor(Color.green); - - ShowOnlyEnabledRolesInGuesserUI = BooleanOptionItem.Create(60689, "ShowOnlyEnabledRolesInGuesserUI", true, TabGroup.ModifierSettings, false) - .SetHeader(true) - .SetGameMode(CustomGameMode.Standard) - .SetColor(Color.cyan); - // Meeting Settings TextOptionItem.Create(10000030, "MenuTitle.Meeting", TabGroup.ModSettings) .SetGameMode(CustomGameMode.Standard) diff --git a/Modules/OptionItem/OptionItem.cs b/Modules/OptionItem/OptionItem.cs index 98356b5f0..8f670b5b8 100644 --- a/Modules/OptionItem/OptionItem.cs +++ b/Modules/OptionItem/OptionItem.cs @@ -302,7 +302,6 @@ public enum TabGroup { SystemSettings, ModSettings, - ModifierSettings, ImpostorRoles, CrewmateRoles, NeutralRoles, diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 56565d0dc..803f90c53 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -49,7 +49,6 @@ public static void StartPostfix(GameSettingMenu __instance) { TabGroup.SystemSettings => Main.ModColor, TabGroup.ModSettings => "#59ef83", - TabGroup.ModifierSettings => "#EF59AF", TabGroup.ImpostorRoles => "#f74631", TabGroup.CrewmateRoles => "#8cffff", TabGroup.NeutralRoles => "#7f8c8d", @@ -351,7 +350,6 @@ public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [ { case TabGroup.SystemSettings: case TabGroup.ModSettings: - case TabGroup.ModifierSettings: __instance.MenuDescriptionText.text = GetString("TabMenuDescription_General"); break; case TabGroup.ImpostorRoles: From cef21b03408c79769699ad3f6ace3209ad272f24 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 16 Nov 2024 16:42:11 -0500 Subject: [PATCH 038/101] finally fix guess menu holy shit that took forever --- Modules/GuessManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 30256d5d7..7f6c2e825 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -734,7 +734,7 @@ static void GuesserOnClick(byte playerId, MeetingHud __instance) container.transform.localPosition = new Vector3(0, 0, -200f); guesserUI = container.gameObject; - List info = [0, 0, 0, 0]; + List info = [0, 0, 0, 0, 0]; var buttonTemplate = __instance.playerStates[0].transform.FindChild("votePlayerBase"); var maskTemplate = __instance.playerStates[0].transform.FindChild("MaskArea"); var smallButtonTemplate = __instance.playerStates[0].Buttons.transform.Find("CancelButton"); From fea03918da7d5ec0cc46dd4a104f0be00904b60c Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 18 Nov 2024 18:29:01 -0500 Subject: [PATCH 039/101] fix necro voting + fix bugs --- Patches/MeetingHudPatch.cs | 2 +- Roles/Coven/CovenManager.cs | 17 ++++++++++++----- Roles/Coven/Illusionist.cs | 12 ++++++++---- Roles/Coven/Necromancer.cs | 10 ---------- Roles/Coven/PotionMaster.cs | 3 ++- Roles/Coven/Sacrifist.cs | 32 ++++++++++++++++++++------------ Roles/Coven/VoodooMaster.cs | 1 + 7 files changed, 44 insertions(+), 33 deletions(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 75c858d31..9621262e3 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -713,7 +713,7 @@ public static bool Prefix(MeetingHud __instance, byte srcPlayerId, byte suspectP if (!voter.GetRoleClass().HasVoted) { voter.GetRoleClass().HasVoted = true; - CovenManager.necroVotes[voter.PlayerId] = target.PlayerId; + CovenManager.necroVotes.Add(voter.PlayerId, target.PlayerId); SendMessage(string.Format(GetString("NecronomiconVote"), target.GetRealName()), voter.PlayerId); __instance.RpcClearVoteDelay(voter.GetClientId()); return false; diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 275d8f370..6bcf3e86c 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -138,19 +138,26 @@ public static void CheckNecroVotes() byte lastResult = byte.MinValue; foreach (byte voter in necroVotes.Keys) { - voteCount[voter] = 0; + if (!voteCount.ContainsKey(necroVotes[voter])) + voteCount.Add(necroVotes[voter], 0); } foreach (byte voter in necroVotes.Keys) { voteCount[necroVotes[voter]]++; + Logger.Info($"{voteCount[necroVotes[voter]]} votes tallied for {GetPlayerById(currentResult).GetRealName()} Necronomicon", "Coven"); } + currentResult = voteCount.Keys.First(); foreach (byte vote in voteCount.Keys) { - if (voteCount[vote] >= voteCount[currentResult]) { + if (voteCount[vote] >= voteCount[currentResult] && currentResult != vote) { lastResult = currentResult; - currentResult = vote; + currentResult = vote; + Logger.Info($"{GetPlayerById(currentResult).GetRealName()} has more votes than {GetPlayerById(lastResult).GetRealName()}", "Coven"); } } - if (currentResult == byte.MinValue) { } - else if (currentResult == lastResult) + if (currentResult == byte.MinValue && !necroVotes.ContainsKey(byte.MinValue)) { + Logger.Info($"currentResult == byte.MinValue, return", "Coven"); + return; + } + else if (voteCount[currentResult] == voteCount[lastResult] && currentResult != lastResult) { Logger.Info($"{GetPlayerById(currentResult).GetRealName()} and {GetPlayerById(lastResult).GetRealName()} had equal Necronomicon votes, not changing Necronomicon", "Coven"); } diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index d7a6d3ccd..d32e8f71b 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -7,6 +7,8 @@ using static TOHE.Utils; using System; using UnityEngine; +using static UnityEngine.GraphicsBuffer; +using TOHE.Roles.AddOns.Common; namespace TOHE.Roles.Coven; @@ -24,7 +26,7 @@ internal class Illusionist : CovenManager private static OptionItem MaxIllusions; public static OptionItem SnitchCanIllusioned; - private static readonly Dictionary> IllusionedPlayers = []; + private static readonly Dictionary> IllusionedPlayers = []; public override void SetupCustomOption() @@ -101,16 +103,18 @@ private static PlayerState.DeathReason ChangeRandomDeath() public static bool IsNonCovIllusioned(byte target) { byte pc = Utils.GetPlayerListByRole(CustomRoles.Illusionist).First().PlayerId; - return IllusionedPlayers[pc].Contains(target) && !GetPlayerById(target).IsPlayerCoven(); + if (!IllusionedPlayers.ContainsKey(pc)) return false; + return IllusionedPlayers.TryGetValue(pc, out var Targets) && Targets.Contains(target) && !GetPlayerById(target).IsPlayerCoven(); } public static bool IsCovIllusioned(byte target) { byte pc = Utils.GetPlayerListByRole(CustomRoles.Illusionist).First().PlayerId; - return IllusionedPlayers[pc].Contains(target) && GetPlayerById(target).IsPlayerCoven(); + if (!IllusionedPlayers.ContainsKey(pc)) return false; + return IllusionedPlayers.TryGetValue(pc, out var Targets) && Targets.Contains(target) && GetPlayerById(target).IsPlayerCoven(); } public override void AfterMeetingTasks() { IllusionedPlayers.Clear(); } - public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => IllusionedPlayers[seer.PlayerId].Contains(seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.Illusionist), "") : string.Empty; + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => (IllusionedPlayers.TryGetValue(seer.PlayerId, out var Targets) && Targets.Contains(seen.PlayerId)) ? ColorString(GetRoleColor(CustomRoles.Illusionist), "") : string.Empty; } \ No newline at end of file diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index 599811946..6d333b638 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -167,16 +167,6 @@ public override void UnShapeShiftButton(PlayerControl nm) AbilityTimer = 0; }, AbilityDuration.GetFloat(), "Necromancer Revert Role"); } - public override void OnCoEndGame() - { - if (_Player.GetCustomRole() != CustomRoles.Necromancer) - { - _Player.GetRoleClass()?.OnRemove(_Player.PlayerId); - } - Main.PlayerStates[_Player.PlayerId].RemoveSubRole(CustomRoles.Enchanted); - _Player.RpcChangeRoleBasis(CustomRoles.Necromancer); - _Player.RpcSetCustomRole(CustomRoles.Necromancer); - } private static bool BlackList(CustomRoles role) { return role.IsNA() || role.IsGhostRole() || role is diff --git a/Roles/Coven/PotionMaster.cs b/Roles/Coven/PotionMaster.cs index 0e940aed7..c119d477b 100644 --- a/Roles/Coven/PotionMaster.cs +++ b/Roles/Coven/PotionMaster.cs @@ -242,9 +242,10 @@ public override string GetMark(PlayerControl seer, PlayerControl seen = null, bo => BarrierList[seer.PlayerId].Contains(seen.PlayerId) ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.PotionMaster), "✚") : string.Empty; public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { + if (_Player == null) return string.Empty; if (BarrierList[_Player.PlayerId].Contains(target.PlayerId) && seer.IsPlayerCoven() && seer.PlayerId != _Player.PlayerId) { - return Utils.ColorString(Utils.GetRoleColor(CustomRoles.PotionMaster), "✚"); + return ColorString(GetRoleColor(CustomRoles.PotionMaster), "✚"); } return string.Empty; } diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index 1ec608482..733805c4b 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -250,19 +250,24 @@ public override void UnShapeShiftButton(PlayerControl pc) } public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { + if (_Player == null) return; + if (randPlayer == byte.MaxValue) return; var sacrifist = _Player.PlayerId; DebuffID = 10; ReportDeadBodyPatch.CanReport[randPlayer] = true; GetPlayerById(randPlayer).ResetKillCooldown(); - Camouflage.PlayerSkins[randPlayer] = OriginalPlayerSkins[randPlayer]; - - if (!Camouflage.IsCamouflage) + if (OriginalPlayerSkins.ContainsKey(randPlayer)) { - PlayerControl pc = - Main.AllAlivePlayerControls.FirstOrDefault(a => a.PlayerId == randPlayer); + Camouflage.PlayerSkins[randPlayer] = OriginalPlayerSkins[randPlayer]; + + if (!Camouflage.IsCamouflage) + { + PlayerControl pc = + Main.AllAlivePlayerControls.FirstOrDefault(a => a.PlayerId == randPlayer); - pc.SetNewOutfit(OriginalPlayerSkins[randPlayer], setName: true, setNamePlate: true); + pc.SetNewOutfit(OriginalPlayerSkins[randPlayer], setName: true, setNamePlate: true); + } } randPlayer = byte.MaxValue; Logger.Info($"Resetting Debuffs for Affected player", "Sacrifist"); @@ -271,14 +276,17 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf ReportDeadBodyPatch.CanReport[sacrifist] = true; _Player.ResetKillCooldown(); maxDebuffTimer = DebuffCooldown.GetFloat(); - Camouflage.PlayerSkins[sacrifist] = OriginalPlayerSkins[sacrifist]; - - if (!Camouflage.IsCamouflage) + if (OriginalPlayerSkins.ContainsKey(sacrifist)) { - PlayerControl pc = - Main.AllAlivePlayerControls.FirstOrDefault(a => a.PlayerId == sacrifist); + Camouflage.PlayerSkins[sacrifist] = OriginalPlayerSkins[sacrifist]; + + if (!Camouflage.IsCamouflage) + { + PlayerControl pc = + Main.AllAlivePlayerControls.FirstOrDefault(a => a.PlayerId == sacrifist); - pc.SetNewOutfit(OriginalPlayerSkins[sacrifist], setName: true, setNamePlate: true); + pc.SetNewOutfit(OriginalPlayerSkins[sacrifist], setName: true, setNamePlate: true); + } } Logger.Info($"Resetting Debuffs for Sacrifist", "Sacrifist"); } diff --git a/Roles/Coven/VoodooMaster.cs b/Roles/Coven/VoodooMaster.cs index c0454f4a3..383ac8f68 100644 --- a/Roles/Coven/VoodooMaster.cs +++ b/Roles/Coven/VoodooMaster.cs @@ -88,6 +88,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { if (Dolls[_Player.PlayerId].Count < 1) return true; + if (killer.IsPlayerCoven()) return true; PlayerControl ChoosenTarget = GetPlayerById(Dolls[target.PlayerId].Where(x => GetPlayerById(x).IsAlive()).ToList().RandomElement()); From 8e39584fae883f7fdf6a0e530de21aca05d52a87 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Tue, 19 Nov 2024 17:41:08 -0500 Subject: [PATCH 040/101] removed original role colors for consistency https://discord.com/channels/1094344790910455908/1270469940549386311/1308562617836765247 --- Resources/roleColor.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Resources/roleColor.json b/Resources/roleColor.json index d61476403..2bacd2843 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -115,15 +115,12 @@ "Collector": "#9d8892", "Provocateur": "#74ba43", "Sunnyboy": "#ff9902", - "Poisoner": "#478800", "Huntsman": "#ad8739", - "Necromancer": "#9C87AB", "Follower": "#ff9409", "Romantic": "#FF1493", "VengefulRomantic": "#8B0000", "RuthlessRomantic": "#D2691E", "Cultist": "#cf6acd", - "HexMaster": "#ff00ff", "Wraith": "#4B0082", "SerialKiller": "#233fcc", "BloodKnight": "#630000", @@ -136,16 +133,13 @@ "Overseer": "#BA55D3", "Pursuer": "#617218", "Specter": "#662962", - "Jinx": "#ed2f91", "Troller": "#006994", "Maverick": "#781717", "CursedSoul": "#531269", - "PotionMaster": "#663399", "Pickpocket": "#47008B", "Traitor": "#BA2E05", "Vulture": "#556B2F", "Taskinator": "#4737ae", - "Medusa": "#9900CC", "Spiritcaller": "#003366", "EvilSpirit": "#003366", "Evader": "#9beb34", From bc10087b941a1355afd5787beef3880661e6e4d9 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Tue, 19 Nov 2024 18:59:58 -0500 Subject: [PATCH 041/101] coveninfolong --- Resources/Lang/en_US.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 7e3361a61..2683e7a81 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -953,6 +953,7 @@ "RuthlessRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A neutral killer) is killed. As a Ruthless Romantic, you win if you kill everyone and are the last one standing. If you win, your dead partner will also win with you.", "VengefulRomanticInfoLong": "(Neutrals):\nYou change your roles from Romantic if your partner (A crew or non-neutral killer) is killed. As a Vengeful Romantic, your goal is to avenge your partner, which means you must kill the killer of your partner. If you succeed, then you and your partner win with the winning team at the end. If you try to kill someone other than your partner's killer, then you will die by misfire.", "WraithInfoLong": "(Neutrals):\nAs the Wraith, you can vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible. You win if you are the last player remaining.", + "CovenInfoLong": "(Coven):\nCoven members are on a separate team that works together and wins together. If there are multiple Coven roles in the game, they can see each other's roles.\nDepending on the Host's settings, Coven roles can guess or be guessed.\nDo /coveninfo for more info.", "PoisonerInfoLong": "(Coven):\nThe Poisoner can use their kill button on a player to roleblock them. The next time the roleblocked player tries to use their ability, it will do nothing, and their cooldown will be reset.\nWith the Necronomicon, you can double-click to kill. These kills will be delayed.", "HexMasterInfoLong": "(Coven):\nThe Hex Master can use their kill button to mark a player with the 乂 symbol. If a player has this at the end of the meeting and the Hex Master hasn't died - then they die.\nWith the Necronomicon, the hex will be passed around - similar to an agitator bomb. Also, you can double-click the kill button to kill normally.", "JinxInfoLong": "(Coven):\nThe Jinx can use their kill button to Jinx a player. Anyone who interacts with the Jinxed player will die with the death reason Jinxed.\nWith the Necronomicon the Jinx can double-kill to kill normally. Also, the Jinxed player and the player who interacted with the Jinxed person will die.", From 2784b7cb83582d53967f58783d818b0caf4e8369 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Tue, 19 Nov 2024 19:08:47 -0500 Subject: [PATCH 042/101] bug fixes? --- Patches/MeetingHudPatch.cs | 48 +++++++++++++++++++------------------ Roles/Coven/CovenManager.cs | 1 + Roles/Coven/Illusionist.cs | 20 +++++++++++----- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 9621262e3..03323c139 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -696,29 +696,6 @@ public static bool Prefix(MeetingHud __instance, byte srcPlayerId, byte suspectP } } - // Coven Necronomicon Voting - - if (suspectPlayerId == 253 && voter.IsPlayerCoven()) - { - if (!voter.GetRoleClass().HasVoted) - { - voter.GetRoleClass().HasVoted = true; - SendMessage(GetString("VoteNotUseAbility"), voter.PlayerId); - __instance.RpcClearVoteDelay(voter.GetClientId()); - return false; - } - } - else if (voter.IsPlayerCoven() && target.IsPlayerCoven()) - { - if (!voter.GetRoleClass().HasVoted) - { - voter.GetRoleClass().HasVoted = true; - CovenManager.necroVotes.Add(voter.PlayerId, target.PlayerId); - SendMessage(string.Format(GetString("NecronomiconVote"), target.GetRealName()), voter.PlayerId); - __instance.RpcClearVoteDelay(voter.GetClientId()); - return false; - } - } // Coven Leader Retraining if (CustomRoles.CovenLeader.RoleExist() && target == voter && CovenLeader.retrainPlayer.ContainsKey(voter.PlayerId) && CovenLeader.retrainPlayer[voter.PlayerId].IsCoven()) @@ -727,6 +704,7 @@ public static bool Prefix(MeetingHud __instance, byte srcPlayerId, byte suspectP voter.RpcSetCustomRole(CovenLeader.retrainPlayer[voter.PlayerId]); SendMessage(string.Format(GetString("CovenLeaderAcceptRetrain"), CustomRoles.CovenLeader.ToColoredString(), CovenLeader.retrainPlayer[voter.PlayerId].ToColoredString()), CL.PlayerId); SendMessage(string.Format(GetString("RetrainAcceptOffer"), CustomRoles.CovenLeader.ToColoredString(), CovenLeader.retrainPlayer[voter.PlayerId].ToColoredString()), voter.PlayerId); + CovenLeader.retrainPlayer.Clear(); CustomRoles.CovenLeader.GetStaticRoleClass().AbilityLimit--; CustomRoles.CovenLeader.GetStaticRoleClass().SendSkillRPC(); @@ -743,6 +721,30 @@ public static bool Prefix(MeetingHud __instance, byte srcPlayerId, byte suspectP return false; } + // Coven Necronomicon Voting + + if (suspectPlayerId == 253 && voter.IsPlayerCoven()) + { + if (!voter.GetRoleClass().HasVoted) + { + voter.GetRoleClass().HasVoted = true; + SendMessage(GetString("VoteNotUseAbility"), voter.PlayerId); + __instance.RpcClearVoteDelay(voter.GetClientId()); + return false; + } + } + else if (voter.IsPlayerCoven() && target.IsPlayerCoven()) + { + if (!voter.GetRoleClass().HasVoted) + { + voter.GetRoleClass().HasVoted = true; + CovenManager.necroVotes.Add(voter.PlayerId, target.PlayerId); + SendMessage(string.Format(GetString("NecronomiconVote"), target.GetRealName()), voter.PlayerId); + __instance.RpcClearVoteDelay(voter.GetClientId()); + return false; + } + } + if (target != null && suspectPlayerId < 253) { if (!target.IsAlive() || target.Data.Disconnected) diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 6bcf3e86c..d287feb31 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -133,6 +133,7 @@ public static void GiveNecronomicon(PlayerControl target) } public static void CheckNecroVotes() { + if (necroVotes.Count < 1) return; Dictionary voteCount = new Dictionary(); byte currentResult = byte.MinValue; byte lastResult = byte.MinValue; diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index d32e8f71b..994678b8c 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -102,15 +102,23 @@ private static PlayerState.DeathReason ChangeRandomDeath() // Affects the following roles: Snitch, Witness, Psychic, Inspector, Oracle, Investigator public static bool IsNonCovIllusioned(byte target) { - byte pc = Utils.GetPlayerListByRole(CustomRoles.Illusionist).First().PlayerId; - if (!IllusionedPlayers.ContainsKey(pc)) return false; - return IllusionedPlayers.TryGetValue(pc, out var Targets) && Targets.Contains(target) && !GetPlayerById(target).IsPlayerCoven(); + if (IllusionedPlayers.Count < 1) return false; + bool result = false; + foreach (var player in IllusionedPlayers.Keys) + { + if (IllusionedPlayers[player].Contains(target) && !GetPlayerById(target).IsPlayerCoven()) result = true; + } + return result; } public static bool IsCovIllusioned(byte target) { - byte pc = Utils.GetPlayerListByRole(CustomRoles.Illusionist).First().PlayerId; - if (!IllusionedPlayers.ContainsKey(pc)) return false; - return IllusionedPlayers.TryGetValue(pc, out var Targets) && Targets.Contains(target) && GetPlayerById(target).IsPlayerCoven(); + if (IllusionedPlayers.Count < 1) return false; + bool result = false; + foreach (var player in IllusionedPlayers.Keys) + { + if (IllusionedPlayers[player].Contains(target) && GetPlayerById(target).IsPlayerCoven()) result = true; + } + return result; } public override void AfterMeetingTasks() { From 014b1431b0b6e68a3ed53eac936b394f4d134588 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:26:48 -0500 Subject: [PATCH 043/101] clean up code a tiny bit --- Roles/Coven/Conjurer.cs | 9 +++---- Roles/Coven/CovenLeader.cs | 11 ++++----- Roles/Coven/CovenManager.cs | 35 ++++++++++++++------------ Roles/Coven/HexMaster.cs | 13 +++++----- Roles/Coven/Illusionist.cs | 11 ++++----- Roles/Coven/Jinx.cs | 13 +++++----- Roles/Coven/Medusa.cs | 14 +++++------ Roles/Coven/MoonDancer.cs | 22 +++++++++-------- Roles/Coven/Necromancer.cs | 49 +++++++++++++++++-------------------- Roles/Coven/Poisoner.cs | 7 +++--- Roles/Coven/PotionMaster.cs | 15 ++++++------ Roles/Coven/Ritualist.cs | 11 ++++----- Roles/Coven/Sacrifist.cs | 12 ++++----- Roles/Coven/VoodooMaster.cs | 16 ++++++------ 14 files changed, 116 insertions(+), 122 deletions(-) diff --git a/Roles/Coven/Conjurer.cs b/Roles/Coven/Conjurer.cs index 04326371f..504cd8b18 100644 --- a/Roles/Coven/Conjurer.cs +++ b/Roles/Coven/Conjurer.cs @@ -1,12 +1,9 @@ -using Hazel; +using AmongUs.GameOptions; using TOHE.Roles.Core; -using InnerNet; +using UnityEngine; using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; -using System; -using UnityEngine; -using AmongUs.GameOptions; namespace TOHE.Roles.Coven; @@ -113,7 +110,7 @@ public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl player.RpcMurderPlayer(player); player.SetRealKiller(shapeshifter); } - + } shapeshifter.Notify(GetString("ConjurerMeteor")); state[shapeshifter.PlayerId] = ConjState.NecroMark; diff --git a/Roles/Coven/CovenLeader.cs b/Roles/Coven/CovenLeader.cs index ad576844e..97a665d5a 100644 --- a/Roles/Coven/CovenLeader.cs +++ b/Roles/Coven/CovenLeader.cs @@ -1,6 +1,4 @@ -using AmongUs.GameOptions; -using Hazel; -using InnerNet; +using Hazel; using TOHE.Roles.Core; using UnityEngine; using static TOHE.Options; @@ -53,13 +51,14 @@ public override string GetProgressText(byte playerId, bool comms) public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (killer == null || target == null) return false; - if (HasNecronomicon(killer)) { + if (HasNecronomicon(killer)) + { if (target.GetCustomRole().IsCovenTeam()) { killer.Notify(GetString("CovenDontKillOtherCoven")); return false; } - else return true; + else return true; } if (AbilityLimit <= 0) { @@ -81,5 +80,5 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t killer.ResetKillCooldown(); return false; } - + } diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index d287feb31..ae2721d61 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -1,6 +1,5 @@ using AmongUs.GameOptions; using Hazel; -using InnerNet; using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; @@ -27,19 +26,19 @@ public enum VentOptionList public static readonly Dictionary necroVotes = []; public static void RunSetUpImpVisOptions(int Id) { - foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven()).ToArray()) - { - SetUpImpVisOption(cov, Id, true, CovenImpVisMode); - Id++; - } + foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven()).ToArray()) + { + SetUpImpVisOption(cov, Id, true, CovenImpVisMode); + Id++; + } } public static void RunSetUpVentOptions(int Id) { - foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven()).ToArray()) - { - SetUpVentOption(cov, Id, true, CovenVentMode); - Id++; - } + foreach (var cov in CustomRolesHelper.AllRoles.Where(x => x.IsCoven()).ToArray()) + { + SetUpVentOption(cov, Id, true, CovenVentMode); + Id++; + } } private static void SetUpImpVisOption(CustomRoles role, int Id, bool defaultValue = true, OptionItem parent = null) { @@ -107,7 +106,7 @@ public override bool CanUseImpostorVentButton(PlayerControl pc) public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); */ - + public static void GiveNecronomicon() { var pcList = Main.AllAlivePlayerControls.Where(pc => pc.IsPlayerCoven() && pc.IsAlive()).ToList(); @@ -142,19 +141,23 @@ public static void CheckNecroVotes() if (!voteCount.ContainsKey(necroVotes[voter])) voteCount.Add(necroVotes[voter], 0); } - foreach (byte voter in necroVotes.Keys) { + foreach (byte voter in necroVotes.Keys) + { voteCount[necroVotes[voter]]++; Logger.Info($"{voteCount[necroVotes[voter]]} votes tallied for {GetPlayerById(currentResult).GetRealName()} Necronomicon", "Coven"); } currentResult = voteCount.Keys.First(); - foreach (byte vote in voteCount.Keys) { - if (voteCount[vote] >= voteCount[currentResult] && currentResult != vote) { + foreach (byte vote in voteCount.Keys) + { + if (voteCount[vote] >= voteCount[currentResult] && currentResult != vote) + { lastResult = currentResult; currentResult = vote; Logger.Info($"{GetPlayerById(currentResult).GetRealName()} has more votes than {GetPlayerById(lastResult).GetRealName()}", "Coven"); } } - if (currentResult == byte.MinValue && !necroVotes.ContainsKey(byte.MinValue)) { + if (currentResult == byte.MinValue && !necroVotes.ContainsKey(byte.MinValue)) + { Logger.Info($"currentResult == byte.MinValue, return", "Coven"); return; } diff --git a/Roles/Coven/HexMaster.cs b/Roles/Coven/HexMaster.cs index de17486a4..037cc876a 100644 --- a/Roles/Coven/HexMaster.cs +++ b/Roles/Coven/HexMaster.cs @@ -1,12 +1,11 @@ using AmongUs.GameOptions; using Hazel; +using InnerNet; +using TOHE.Roles.Core; using UnityEngine; -using System.Text; using static TOHE.Options; -using static TOHE.Utils; using static TOHE.Translator; -using InnerNet; -using TOHE.Roles.Core; +using static TOHE.Utils; namespace TOHE.Roles.Coven; @@ -51,14 +50,14 @@ private enum SwitchTriggerList public override void SetupCustomOption() { - SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.HexMaster, 1, zeroOne: false); + SetupSingleRoleOptions(Id, TabGroup.CovenRoles, CustomRoles.HexMaster, 1, zeroOne: false); //ModeSwitchAction = StringOptionItem.Create(Id + 10, GeneralOption.ModeSwitchAction, EnumHelper.GetAllNames(), 2, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); HexCooldown = FloatOptionItem.Create(Id + 13, "HexMasterHexCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]) .SetValueFormat(OptionFormat.Seconds); MovingHexPassCooldown = FloatOptionItem.Create(Id + 15, "HexMasterMovingHexCooldown", new(0f, 5f, 0.25f), 1f, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]) .SetValueFormat(OptionFormat.Seconds); CovenCanGetMovingHex = BooleanOptionItem.Create(Id + 14, "HexMasterCovenCanGetMovingHex", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); - HexesLookLikeSpells = BooleanOptionItem.Create(Id + 11, "HexesLookLikeSpells", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); + HexesLookLikeSpells = BooleanOptionItem.Create(Id + 11, "HexesLookLikeSpells", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); //HasImpostorVision = BooleanOptionItem.Create(Id + 12, GeneralOption.ImpostorVision, true, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); } public override void Init() @@ -408,7 +407,7 @@ public override string GetLowerText(PlayerControl hexmaster, PlayerControl seen return str.ToString(); } */ - + public override void SetAbilityButtonText(HudManager hud, byte playerid) => hud.KillButton.OverrideText($"{GetString("HexButtonText")}"); } \ No newline at end of file diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index 994678b8c..d42481b76 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -1,14 +1,12 @@ using Hazel; -using TOHE.Roles.Core; using InnerNet; +using System; +using TOHE.Roles.Core; +using UnityEngine; using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; -using System; -using UnityEngine; -using static UnityEngine.GraphicsBuffer; -using TOHE.Roles.AddOns.Common; namespace TOHE.Roles.Coven; @@ -69,7 +67,8 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t { if (killer.CheckDoubleTrigger(target, () => { IllusionedPlayers[killer.PlayerId].Add(target.PlayerId); })) { - if (HasNecronomicon(killer) && !target.IsPlayerCoven()) { + if (HasNecronomicon(killer) && !target.IsPlayerCoven()) + { var randomDeathReason = ChangeRandomDeath(); Main.PlayerStates[target.PlayerId].deathReason = randomDeathReason; Main.PlayerStates[target.PlayerId].SetDead(); diff --git a/Roles/Coven/Jinx.cs b/Roles/Coven/Jinx.cs index 7e5fada09..92f3f5966 100644 --- a/Roles/Coven/Jinx.cs +++ b/Roles/Coven/Jinx.cs @@ -1,11 +1,10 @@ -using AmongUs.GameOptions; +using Hazel; +using InnerNet; +using TOHE.Roles.Core; using UnityEngine; using static TOHE.Options; -using static TOHE.Utils; using static TOHE.Translator; -using InnerNet; -using TOHE.Roles.Core; -using Hazel; +using static TOHE.Utils; namespace TOHE.Roles.Coven; @@ -171,8 +170,8 @@ public override string GetMark(PlayerControl seer, PlayerControl seen = null, bo //public override bool CanUseImpostorVentButton(PlayerControl player) => CanVent.GetBool(); - public override string GetProgressText(byte playerId, bool comms) + public override string GetProgressText(byte playerId, bool comms) => ColorString(CanJinx(playerId) ? GetRoleColor(CustomRoles.Jinx).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); - + private bool CanJinx(byte id) => AbilityLimit > 0; } diff --git a/Roles/Coven/Medusa.cs b/Roles/Coven/Medusa.cs index dbd5e8a03..2cae766cd 100644 --- a/Roles/Coven/Medusa.cs +++ b/Roles/Coven/Medusa.cs @@ -1,11 +1,10 @@ using AmongUs.GameOptions; -using static TOHE.Translator; -using static TOHE.Options; -using static TOHE.Utils; -using TOHE.Roles.Core; -using static UnityEngine.GraphicsBuffer; using Hazel; using InnerNet; +using TOHE.Roles.Core; +using static TOHE.Options; +using static TOHE.Translator; +using static TOHE.Utils; namespace TOHE.Roles.Coven; @@ -95,7 +94,8 @@ public override bool OnCheckReportDeadBody(PlayerControl reporter, NetworkedPlay public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (killer == null || target == null) return false; - if (HasNecronomicon(killer)) { + if (HasNecronomicon(killer)) + { if (target.GetCustomRole().IsCovenTeam()) { killer.Notify(GetString("CovenDontKillOtherCoven")); @@ -104,7 +104,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t killer.RpcMurderPlayer(target); killer.ResetKillCooldown(); Main.UnreportableBodies.Add(target.PlayerId); - return false; + return false; } else { diff --git a/Roles/Coven/MoonDancer.cs b/Roles/Coven/MoonDancer.cs index e3b815ea9..50076cc46 100644 --- a/Roles/Coven/MoonDancer.cs +++ b/Roles/Coven/MoonDancer.cs @@ -1,15 +1,15 @@ using Hazel; -using TOHE.Roles.Core; using InnerNet; -using static TOHE.Options; -using static TOHE.Translator; -using static TOHE.Utils; +using TOHE.Modules; using TOHE.Roles.AddOns; -using TOHE.Roles.Double; -using TOHE.Roles.Neutral; +using TOHE.Roles.Core; using TOHE.Roles.Crewmate; +using TOHE.Roles.Double; using TOHE.Roles.Impostor; -using TOHE.Modules; +using TOHE.Roles.Neutral; +using static TOHE.Options; +using static TOHE.Translator; +using static TOHE.Utils; namespace TOHE.Roles.Coven; @@ -69,7 +69,7 @@ private void SyncBlastList() private void SendRPC(byte playerId) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRoleSkill, SendOption.Reliable, -1); - writer.WriteNetObject(_Player); + writer.WriteNetObject(_Player); writer.Write(playerId); if (playerId != byte.MaxValue) { @@ -214,8 +214,10 @@ private void DistributeAddOns(PlayerControl md) var addon = addons.RandomElement(); var helpful = GroupedAddons[AddonTypes.Helpful].Where(x => addons.Contains(x)).ToList(); var harmful = GroupedAddons[AddonTypes.Harmful].Where(x => addons.Contains(x)).ToList(); - if (player.IsPlayerCoven() || player.Is(CustomRoles.Enchanted) || (player.Is(CustomRoles.Lovers) && md.Is(CustomRoles.Lovers))) { - if (helpful.Count <= 0) { + if (player.IsPlayerCoven() || player.Is(CustomRoles.Enchanted) || (player.Is(CustomRoles.Lovers) && md.Is(CustomRoles.Lovers))) + { + if (helpful.Count <= 0) + { SendMessage(string.Format(GetString("MoonDancerNoAddons"), player.GetRealName()), md.PlayerId); Logger.Info("No addons to pass.", "MoonDancer"); continue; diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index 6d333b638..90915b555 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -1,13 +1,9 @@ -using AmongUs.GameOptions; -using TOHE.Roles.Core; -using MS.Internal.Xml.XPath; +using TOHE.Roles.Core; +using TOHE.Roles.Crewmate; +using TOHE.Roles.Neutral; +using UnityEngine; using static TOHE.Options; using static TOHE.Translator; -using TOHE.Roles.AddOns; -using Rewired; -using UnityEngine; -using TOHE.Roles.Neutral; -using TOHE.Roles.Crewmate; namespace TOHE.Roles.Coven; @@ -122,9 +118,10 @@ public override string GetLowerText(PlayerControl seer, PlayerControl seen = nul public override void UnShapeShiftButton(PlayerControl nm) { if (nm == null) return; - if (!canUseAbility) { + if (!canUseAbility) + { nm.Notify(GetString("NecromancerCooldownNotDone")); - return; + return; } var deadPlayers = Main.AllPlayerControls.Where(x => !x.IsAlive()); List deadRoles = []; @@ -171,14 +168,14 @@ private static bool BlackList(CustomRoles role) { return role.IsNA() || role.IsGhostRole() || role is CustomRoles.Veteran or - CustomRoles.Solsticer or + CustomRoles.Solsticer or CustomRoles.Lawyer or CustomRoles.Amnesiac or - CustomRoles.Imitator or + CustomRoles.Imitator or CustomRoles.CopyCat or CustomRoles.Executioner or CustomRoles.Follower or - CustomRoles.Romantic or + CustomRoles.Romantic or CustomRoles.God or CustomRoles.Innocent or CustomRoles.Jackal or @@ -187,15 +184,15 @@ CustomRoles.Captain or CustomRoles.Retributionist or CustomRoles.Nemesis or CustomRoles.NiceMini or - CustomRoles.Mini or + CustomRoles.Mini or CustomRoles.EvilMini or - CustomRoles.SuperStar or - CustomRoles.RuthlessRomantic or - CustomRoles.VengefulRomantic or - CustomRoles.CursedSoul or - CustomRoles.Provocateur or + CustomRoles.SuperStar or + CustomRoles.RuthlessRomantic or + CustomRoles.VengefulRomantic or + CustomRoles.CursedSoul or + CustomRoles.Provocateur or CustomRoles.Specter or - CustomRoles.Sunnyboy || + CustomRoles.Sunnyboy || (role == CustomRoles.Workaholic && Workaholic.WorkaholicVisibleToEveryone.GetBool()) || (role == CustomRoles.Mayor && Mayor.MayorRevealWhenDoneTasks.GetBool()); } @@ -214,7 +211,7 @@ private static void Countdown(int seconds, PlayerControl player) { Timer = RevengeTime.GetInt(); Success = false; - Killer = null; + Killer = null; return; } if (GameStates.IsMeeting && player.IsAlive()) @@ -228,12 +225,12 @@ private static void Countdown(int seconds, PlayerControl player) Killer = null; return; } - if (seconds <= 0) - { - player.RpcMurderPlayer(player); + if (seconds <= 0) + { + player.RpcMurderPlayer(player); player.SetRealKiller(killer); - Killer = null; - return; + Killer = null; + return; } player.Notify(string.Format(GetString("NecromancerRevenge"), seconds, Killer.GetRealName()), 1.1f); Timer = seconds; diff --git a/Roles/Coven/Poisoner.cs b/Roles/Coven/Poisoner.cs index eac250927..07ce00308 100644 --- a/Roles/Coven/Poisoner.cs +++ b/Roles/Coven/Poisoner.cs @@ -1,6 +1,5 @@ -using AmongUs.GameOptions; +using TOHE.Roles.AddOns.Common; using UnityEngine; -using TOHE.Roles.AddOns.Common; using static TOHE.Translator; using static TOHE.Utils; @@ -8,7 +7,7 @@ namespace TOHE.Roles.Coven; internal class Poisoner : CovenManager { - private class PoisonedInfo(byte poisonerId, float killTimer) + private class PoisonedInfo(byte poisonerId, float killTimer) { public byte PoisonerId = poisonerId; public float KillTimer = killTimer; @@ -70,7 +69,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t { if (killer.CheckDoubleTrigger(target, () => { RoleblockedPlayers[killer.PlayerId].Add(target.PlayerId); })) { - if (HasNecronomicon(killer) && !target.IsPlayerCoven()) + if (HasNecronomicon(killer) && !target.IsPlayerCoven()) { if (target.Is(CustomRoles.Bait)) return true; diff --git a/Roles/Coven/PotionMaster.cs b/Roles/Coven/PotionMaster.cs index c119d477b..0c26380d1 100644 --- a/Roles/Coven/PotionMaster.cs +++ b/Roles/Coven/PotionMaster.cs @@ -1,13 +1,11 @@ -using AmongUs.GameOptions; -using Hazel; +using Hazel; using InnerNet; -using TOHE.Roles.Core; using System.Text; +using TOHE.Roles.Core; using UnityEngine; using static TOHE.Options; -using static TOHE.Utils; using static TOHE.Translator; -using MS.Internal.Xml.XPath; +using static TOHE.Utils; namespace TOHE.Roles.Coven; @@ -146,7 +144,7 @@ private void SetRitual(PlayerControl killer, PlayerControl target) } else if (RevealLimit[killer.PlayerId] <= 0) { - killer.Notify(string.Format(GetString("PotionMasterNoPotions"),GetString("PotionMasterReveal"))); + killer.Notify(string.Format(GetString("PotionMasterNoPotions"), GetString("PotionMasterReveal"))); } break; case 1: @@ -169,7 +167,8 @@ private void SetRitual(PlayerControl killer, PlayerControl target) } public override void UnShapeShiftButton(PlayerControl pm) { - switch (PotionMode) { + switch (PotionMode) + { case 0: PotionMode = 1; pm.Notify(string.Format(GetString("PotionMasterPotionSwitch"), GetString("PotionMasterBarrier"))); @@ -252,5 +251,5 @@ public override string GetMarkOthers(PlayerControl seer, PlayerControl target, b public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); - public override string GetProgressText(byte playerId, bool coooonms) => Utils.ColorString(RevealLimit[playerId] > 0 ? Utils.GetRoleColor(CustomRoles.PotionMaster).ShadeColor(0.25f) : Color.gray, $"({RevealLimit[playerId]})")+ Utils.ColorString(BarrierLimit[playerId] > 0 ? Utils.GetRoleColor(CustomRoles.Medic).ShadeColor(0.25f) : Color.gray, $" ({BarrierLimit[playerId]})"); + public override string GetProgressText(byte playerId, bool coooonms) => Utils.ColorString(RevealLimit[playerId] > 0 ? GetRoleColor(CustomRoles.PotionMaster).ShadeColor(0.25f) : Color.gray, $"({RevealLimit[playerId]})") + ColorString(BarrierLimit[playerId] > 0 ? GetRoleColor(CustomRoles.Medic).ShadeColor(0.25f) : Color.gray, $" ({BarrierLimit[playerId]})"); } \ No newline at end of file diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index b8679f9f4..be5fb202c 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -1,15 +1,14 @@ using Hazel; +using System; +using System.Text.RegularExpressions; +using TOHE.Modules.ChatManager; +using TOHE.Roles.AddOns.Crewmate; using TOHE.Roles.Core; using TOHE.Roles.Double; -using TOHE.Roles.AddOns.Crewmate; -using InnerNet; +using UnityEngine; using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; -using System.Text.RegularExpressions; -using System; -using TOHE.Modules.ChatManager; -using UnityEngine; namespace TOHE.Roles.Coven; diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index 733805c4b..c4d91addd 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -1,12 +1,12 @@ -using TOHE.Roles.Core; -using static TOHE.Options; +using AmongUs.GameOptions; +using Hazel; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using TOHE.Modules; +using TOHE.Roles.Core; using UnityEngine; +using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; -using TOHE.Modules; -using Il2CppInterop.Runtime.InteropTypes.Arrays; -using AmongUs.GameOptions; -using Hazel; namespace TOHE.Roles.Coven; diff --git a/Roles/Coven/VoodooMaster.cs b/Roles/Coven/VoodooMaster.cs index 383ac8f68..efebf1650 100644 --- a/Roles/Coven/VoodooMaster.cs +++ b/Roles/Coven/VoodooMaster.cs @@ -1,10 +1,10 @@ -using TOHE.Roles.Core; +using Hazel; +using InnerNet; +using TOHE.Roles.Core; +using UnityEngine; using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; -using Hazel; -using InnerNet; -using UnityEngine; namespace TOHE.Roles.Coven; @@ -71,7 +71,8 @@ public override string GetProgressText(byte playerId, bool comms) => ColorString(AbilityLimit >= 1 ? GetRoleColor(CustomRoles.VoodooMaster).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - return HasNecronomicon(killer) && killer.CheckDoubleTrigger(target, () => { + return HasNecronomicon(killer) && killer.CheckDoubleTrigger(target, () => + { if (AbilityLimit > 0 && (!target.IsPlayerCoven() || (target.IsPlayerCoven() && CanDollCoven.GetBool()))) { Dolls[killer.PlayerId].Add(target.PlayerId); @@ -113,10 +114,11 @@ public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerContr { if (!Dolls[_Player.PlayerId].Contains(target.PlayerId)) return false; if (!HasNecronomicon(_Player)) return false; - if (!killer.IsPlayerCoven() || (killer.IsPlayerCoven() && NecroAbilityCanKillCov.GetBool())) { + if (!killer.IsPlayerCoven() || (killer.IsPlayerCoven() && NecroAbilityCanKillCov.GetBool())) + { killer.SetDeathReason(PlayerState.DeathReason.Sacrifice); killer.RpcMurderPlayer(killer); - killer.SetRealKiller(target); + killer.SetRealKiller(target); } return false; } From 64004d4525f77b545cede7aa933acf6255a0ae83 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Wed, 20 Nov 2024 19:09:17 -0500 Subject: [PATCH 044/101] bug fixes and roleblock immunity --- Modules/GuessManager.cs | 2 +- Roles/Coven/CovenManager.cs | 9 ++++++++- Roles/Coven/Poisoner.cs | 3 ++- Roles/Crewmate/Deceiver.cs | 9 ++++++--- Roles/Crewmate/Deputy.cs | 8 +++++--- Roles/Neutral/Baker.cs | 5 ++++- Roles/Neutral/Pursuer.cs | 10 ++++++---- 7 files changed, 32 insertions(+), 14 deletions(-) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 7f6c2e825..4d61968cb 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -588,7 +588,7 @@ public static void TryHideMsg() var roles = CustomRolesHelper.AllRoles.Where(x => x is not CustomRoles.NotAssigned).ToArray(); var rd = IRandom.Instance; string msg; - string[] command = ["bet", "bt", "guess", "gs", "shoot", "st", "赌", "猜", "审判", "tl", "判", "审"]; + string[] command = ["bet", "bt", "guess", "gs", "shoot", "st", "rt", "赌", "猜", "审判", "tl", "判", "审"]; for (int i = 0; i < 20; i++) { msg = "/"; diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index ae2721d61..83072b9ee 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -133,6 +133,14 @@ public static void GiveNecronomicon(PlayerControl target) public static void CheckNecroVotes() { if (necroVotes.Count < 1) return; + if (necroVotes.Count == 1) + { + byte soloVote = necroVotes[necroVotes.Keys.First()]; + GiveNecronomicon(soloVote); + Logger.Info($"Only one vote for Necronomicon, giving to {GetPlayerById(soloVote).GetRealName()}", "Coven"); + necroVotes.Clear(); + return; + } Dictionary voteCount = new Dictionary(); byte currentResult = byte.MinValue; byte lastResult = byte.MinValue; @@ -159,7 +167,6 @@ public static void CheckNecroVotes() if (currentResult == byte.MinValue && !necroVotes.ContainsKey(byte.MinValue)) { Logger.Info($"currentResult == byte.MinValue, return", "Coven"); - return; } else if (voteCount[currentResult] == voteCount[lastResult] && currentResult != lastResult) { diff --git a/Roles/Coven/Poisoner.cs b/Roles/Coven/Poisoner.cs index 07ce00308..f8739869f 100644 --- a/Roles/Coven/Poisoner.cs +++ b/Roles/Coven/Poisoner.cs @@ -1,4 +1,5 @@ using TOHE.Roles.AddOns.Common; +using TOHE.Roles.Crewmate; using UnityEngine; using static TOHE.Translator; using static TOHE.Utils; @@ -149,7 +150,7 @@ public override void OnReportDeadBody(PlayerControl sans, NetworkedPlayerInfo ba public bool IsRoleblocked(byte id) => RoleblockedPlayers[_Player.PlayerId].Contains(id); public override bool CheckMurderOnOthersTarget(PlayerControl pc, PlayerControl _) // Target of Pursuer attempt to murder someone { - if (!IsRoleblocked(pc.PlayerId)) return false; + if (!IsRoleblocked(pc.PlayerId) && pc.GetCustomRole() is not CustomRoles.SerialKiller or CustomRoles.Pursuer or CustomRoles.Deputy or CustomRoles.Deceiver or CustomRoles.Poisoner) return false; // I was told these roles should be roleblock immune if (pc == null) return false; pc.ResetKillCooldown(); diff --git a/Roles/Crewmate/Deceiver.cs b/Roles/Crewmate/Deceiver.cs index 1bc0756ac..dd11d36bc 100644 --- a/Roles/Crewmate/Deceiver.cs +++ b/Roles/Crewmate/Deceiver.cs @@ -86,9 +86,12 @@ public override bool CheckMurderOnOthersTarget(PlayerControl pc, PlayerControl _ var target = pc; if (killer == null) return false; - target.SetDeathReason(PlayerState.DeathReason.Misfire); - target.RpcMurderPlayer(target); - target.SetRealKiller(killer); + if (target.GetCustomRole() is not CustomRoles.SerialKiller or CustomRoles.Pursuer or CustomRoles.Deputy or CustomRoles.Deceiver or CustomRoles.Poisoner) + { + target.SetDeathReason(PlayerState.DeathReason.Misfire); + target.RpcMurderPlayer(target); + target.SetRealKiller(killer); + } Logger.Info($"The customer {target.GetRealName()} of {pc.GetRealName()}, a counterfeiter, commits suicide by using counterfeits", "Deceiver"); return true; diff --git a/Roles/Crewmate/Deputy.cs b/Roles/Crewmate/Deputy.cs index 2e9e0b2b5..e28895035 100644 --- a/Roles/Crewmate/Deputy.cs +++ b/Roles/Crewmate/Deputy.cs @@ -50,9 +50,11 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Deputy), GetString("DeputyHandcuffedPlayer"))); target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Deputy), GetString("HandcuffedByDeputy"))); - target.SetKillCooldownV3(DeputyHandcuffCDForTarget.GetFloat()); - if (!DisableShieldAnimations.GetBool()) killer.RpcGuardAndKill(target); - if (!DisableShieldAnimations.GetBool()) target.RpcGuardAndKill(target); + if (target.GetCustomRole() is not CustomRoles.SerialKiller or CustomRoles.Pursuer or CustomRoles.Deputy or CustomRoles.Deceiver or CustomRoles.Poisoner) { + target.SetKillCooldownV3(DeputyHandcuffCDForTarget.GetFloat()); + if (!DisableShieldAnimations.GetBool()) killer.RpcGuardAndKill(target); + if (!DisableShieldAnimations.GetBool()) target.RpcGuardAndKill(target); + } return false; } diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index 153a12612..7182fca55 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -244,7 +244,10 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr SendRPC(1, killer, target); break; case 1: // Roleblock - target.SetKillCooldownV3(999f); + if (target.GetCustomRole() is not CustomRoles.SerialKiller or CustomRoles.Pursuer or CustomRoles.Deputy or CustomRoles.Deceiver or CustomRoles.Poisoner) + { + target.SetKillCooldownV3(999f); + } break; case 2: // Barrier BarrierList[killer.PlayerId].Add(target.PlayerId); diff --git a/Roles/Neutral/Pursuer.cs b/Roles/Neutral/Pursuer.cs index 76a9b77f9..007bb6bfa 100644 --- a/Roles/Neutral/Pursuer.cs +++ b/Roles/Neutral/Pursuer.cs @@ -93,10 +93,12 @@ public override bool CheckMurderOnOthersTarget(PlayerControl pc, PlayerControl _ var killer = Utils.GetPlayerById(cfId); var target = pc; if (killer == null) return false; - - target.SetDeathReason(PlayerState.DeathReason.Misfire); - target.RpcMurderPlayer(target); - target.SetRealKiller(killer); + if (target.GetCustomRole() is not CustomRoles.SerialKiller or CustomRoles.Pursuer or CustomRoles.Deputy or CustomRoles.Deceiver or CustomRoles.Poisoner) + { + target.SetDeathReason(PlayerState.DeathReason.Misfire); + target.RpcMurderPlayer(target); + target.SetRealKiller(killer); + } Logger.Info($"赝品商 {pc.GetRealName()} 的客户 {target.GetRealName()} 因使用赝品走火自杀", "Pursuer"); return true; From 417c3c46ae4732abcd958eda8ad6bcdc2c5b220a Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 22 Nov 2024 18:16:47 -0500 Subject: [PATCH 045/101] recruitment stuff --- Modules/CustomRolesHelper.cs | 3 ++- Modules/ExtendedPlayerControl.cs | 1 + Modules/OptionHolder.cs | 2 +- Modules/Utils.cs | 9 ++++++++- Resources/Lang/en_US.json | 7 +++++-- Roles/Coven/CovenLeader.cs | 2 +- Roles/Crewmate/Admirer.cs | 2 +- Roles/Impostor/Gangster.cs | 5 ++++- Roles/Neutral/Cultist.cs | 5 ++++- Roles/Neutral/CursedSoul.cs | 5 ++++- Roles/Neutral/Infectious.cs | 6 ++++-- 11 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 44418caa3..58cd412e6 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -307,7 +307,8 @@ CustomRoles.Jackal or CustomRoles.Cultist or CustomRoles.Necromancer or CustomRoles.Virus or - CustomRoles.Spiritcaller; + CustomRoles.Spiritcaller or + CustomRoles.Ritualist; public static bool IsMadmate(this CustomRoles role) { diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 912de31d0..ec246f285 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1175,6 +1175,7 @@ CustomRoles.Admired and not CustomRoles.Soulless and not CustomRoles.Lovers and not CustomRoles.Infected and not + CustomRoles.Enchanted and not CustomRoles.Contagious; } diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 1831e451c..cfcfc7a53 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -651,7 +651,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 30800 last id for roles/add-ons (Next use 30900) + // 30900 last id for roles/add-ons (Next use 31000) // Limit id for roles/add-ons --- "59999" //####################################### diff --git a/Modules/Utils.cs b/Modules/Utils.cs index f14557624..f7faeb6a7 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -25,6 +25,7 @@ using TOHE.Patches; using TOHE.Roles.Coven; using Epic.OnlineServices; +using UnityEngine.UI; namespace TOHE; @@ -1715,7 +1716,8 @@ public static bool IsSameTeammate(this PlayerControl player, PlayerControl targe { var Compare = player.GetCustomSubRoles().First(x => x.IsConverted()); - team = player.Is(CustomRoles.Madmate) ? Custom_Team.Impostor : Custom_Team.Neutral; + if (player.Is(CustomRoles.Enchanted)) team = Custom_Team.Coven; + else team = player.Is(CustomRoles.Madmate) ? Custom_Team.Impostor : Custom_Team.Neutral; return target.Is(Compare); } else if (!target.IsAnySubRole(x => x.IsConverted())) @@ -2063,6 +2065,11 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl if (seer.Is(Custom_Team.Impostor) && target.Is(CustomRoles.Snitch) && target.Is(CustomRoles.Madmate) && target.GetPlayerTaskState().IsTaskFinished) TargetMark.Append(ColorString(GetRoleColor(CustomRoles.Impostor), "★")); + if ((seer.IsPlayerCoven() && target.IsPlayerCoven()) && (CovenManager.HasNecronomicon(target))) + { + TargetMark.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Coven), "♣")); + } + if (target.Is(CustomRoles.Cyber) && Cyber.CyberKnown.GetBool()) TargetMark.Append(ColorString(GetRoleColor(CustomRoles.Cyber), "★")); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 88b194652..8670e8014 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1074,7 +1074,7 @@ "AbilityExpired": "Ability expired, {0} uses remain", "RevenantTargeted": "Your role has changed to {0}", - "RevenantCanCopyAddons": "Can Steal Addons", + "RevenantCanCopyAddons": "Can Steal Addons", "ShowArrows": "Has Arrows pointing toward bodies", "ArrowDelayMin": "Minimum Arrow show-up delay", @@ -1892,6 +1892,7 @@ "CultistKnowTargetRole": "Know Charmed Player's Role", "CultistTargetKnowOtherTarget": "Charmed players know each other", "CultistCanCharmNeutral": "Neutral Roles can be Charmed", + "CultistCanCharmCoven": "Coven Roles can be Charmed", "InfectiousBiteCooldown": "Infect Cooldown", "KnowTargetRole": "Knows role of target", "TargetKnowsLawyer": "Target knows their Lawyer", @@ -2273,7 +2274,7 @@ "Command.dump": "→ Output Log to Desktop", "Command.death": "→ Display info on how you died", "Command.icons": "
╳ - The Player was marked by the Blackmailer and can't talk during the Meeting
☆ - Used by Captain to display themselves. Only Crewmates can see the Captain's star
乂 - This player was hexed by the Hex Master and will die if the Hex Master isn't killed or ejected by the end of the Meeting.
♦ - Used by Lawyer or Executioner or Follower.
♥ - Used by Lovers or Romantic.
✚ - Used by Medic to mark their target.
⦿ - This player is in a duel with the Pirate.
!? - This player was marked by the Quizmaster and must answer the question correctly to survive.
☜ - Used by Schrödinger's cat to mark their teammate.
◈ - This player marked by the Shroud and will die if the Shroud is not killed or ejected by the end of the meeting.
⚠ - This player is a Snitch or Solsticer who has finished their tasks.
★ - Used by Super Star or Cyber or Marshall.
† - This player was spelled and will die if the Witch is not killed by the end of the meeting.
∇ - Used by Kamikaze to mark their targets.
■ - Used by Lightning to mark their quantum ghosts.
⊠ - Used by Jailer to mark their prisoner.
● - Used by Baker to mark who has Bread.
♠ - Used by Soul Collector to mark who's death they're predicting.
⦿ - Used by Plaguebearer to mark who they have plagued.", - "Command.start" : "[Seconds] → Start the game", + "Command.start": "[Seconds] → Start the game", "Command.iconinfo": "→ Display info on in-meeting icons", "Command.iconhelp": "→ Display info on in-meeting icons to everyone", @@ -3303,6 +3304,7 @@ "GanJudgeCanBeMadmate": "Judge can be converted", "GanMarshallCanBeMadmate": "Marshall can be converted", "GanOverseerCanBeMadmate": "Overseer can be converted", + "GanCovenCanBeMadmate": "Coven can be converted", "RascalAppearAsMadmate": "Appear As Madmate On Ejection", "CouncillorDead": "Sorry, you can't murder from the dead.", @@ -3451,6 +3453,7 @@ "CursedSoulCurseMax": "Maximum Soul Snatches", "CursedSoulKnowTargetRole": "Know the roles of Soulless players", "CursedSoulCanCurseNeutral": "Neutral roles have souls", + "CursedSoulCanCurseCoven": "Coven roles have souls", "CursedSoulKillButtonText": "Snatch", "SoullessByCursedSoul": "A Cursed Soul snatched your soul", "CursedSoulSoullessPlayer": "Soul snatched", diff --git a/Roles/Coven/CovenLeader.cs b/Roles/Coven/CovenLeader.cs index 97a665d5a..51a8418d3 100644 --- a/Roles/Coven/CovenLeader.cs +++ b/Roles/Coven/CovenLeader.cs @@ -10,7 +10,7 @@ namespace TOHE.Roles.Coven; internal class CovenLeader : CovenManager { //===========================SETUP================================\\ - private const int Id = 30200; + private const int Id = 30900; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.CovenLeader); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; diff --git a/Roles/Crewmate/Admirer.cs b/Roles/Crewmate/Admirer.cs index 0ed388778..1b7833a9a 100644 --- a/Roles/Crewmate/Admirer.cs +++ b/Roles/Crewmate/Admirer.cs @@ -216,7 +216,7 @@ public static bool CanBeAdmired(PlayerControl pc, PlayerControl admirer) } else AdmiredList.Add(admirer.PlayerId, []); - return pc != null && (pc.GetCustomRole().IsCrewmate() || pc.GetCustomRole().IsImpostor() || pc.GetCustomRole().IsNeutral()) + return pc != null && (pc.GetCustomRole().IsCrewmate() || pc.GetCustomRole().IsImpostor() || pc.GetCustomRole().IsNeutral() || pc.GetCustomRole().IsCoven()) && !pc.Is(CustomRoles.Soulless) && !pc.Is(CustomRoles.Lovers) && !pc.Is(CustomRoles.Loyal) && !((pc.Is(CustomRoles.NiceMini) || pc.Is(CustomRoles.EvilMini)) && Mini.Age < 18) && !(pc.GetCustomSubRoles().Contains(CustomRoles.Hurried) && !Hurried.CanBeConverted.GetBool()); diff --git a/Roles/Impostor/Gangster.cs b/Roles/Impostor/Gangster.cs index 468e24cf2..882c08008 100644 --- a/Roles/Impostor/Gangster.cs +++ b/Roles/Impostor/Gangster.cs @@ -30,6 +30,8 @@ internal class Gangster : RoleBase public static OptionItem RetributionistCanBeMadmate; public static OptionItem MarshallCanBeMadmate; public static OptionItem OverseerCanBeMadmate; + public static OptionItem CovenCanBeMadmate; + public override void SetupCustomOption() @@ -40,6 +42,7 @@ public override void SetupCustomOption() RecruitLimitOpt = IntegerOptionItem.Create(Id + 12, "GangsterRecruitLimit", new(1, 15, 1), 2, TabGroup.ImpostorRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]) .SetValueFormat(OptionFormat.Times); + CovenCanBeMadmate = BooleanOptionItem.Create(Id + 21, "GanCovenCanBeMadmate", false, TabGroup.ImpostorRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); SheriffCanBeMadmate = BooleanOptionItem.Create(Id + 14, "GanSheriffCanBeMadmate", false, TabGroup.ImpostorRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); MayorCanBeMadmate = BooleanOptionItem.Create(Id + 15, "GanMayorCanBeMadmate", false, TabGroup.ImpostorRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); NGuesserCanBeMadmate = BooleanOptionItem.Create(Id + 16, "GanNGuesserCanBeMadmate", false, TabGroup.ImpostorRoles, false).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); @@ -157,7 +160,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t private bool CanRecruit(byte id) => AbilityLimit >= 1; private static bool CanBeGansterRecruit(PlayerControl pc) { - return pc != null && (pc.GetCustomRole().IsCrewmate() || pc.GetCustomRole().IsImpostor()) + return pc != null && (pc.GetCustomRole().IsCrewmate() || pc.GetCustomRole().IsImpostor() || pc.GetCustomRole().IsCoven()) && !pc.Is(CustomRoles.Soulless) && !pc.Is(CustomRoles.Lovers) && !pc.Is(CustomRoles.Loyal) && !((pc.Is(CustomRoles.NiceMini) || pc.Is(CustomRoles.EvilMini)) && Mini.Age < 18) && !(pc.GetCustomSubRoles().Contains(CustomRoles.Hurried) && !Hurried.CanBeConverted.GetBool()); diff --git a/Roles/Neutral/Cultist.cs b/Roles/Neutral/Cultist.cs index c3c42027f..4457d64a1 100644 --- a/Roles/Neutral/Cultist.cs +++ b/Roles/Neutral/Cultist.cs @@ -23,6 +23,7 @@ internal class Cultist : RoleBase private static OptionItem KnowTargetRole; private static OptionItem TargetKnowOtherTarget; private static OptionItem CanCharmNeutral; + private static OptionItem CanCharmCoven; public static OptionItem CharmedCountMode; private enum CharmedCountModeSelectList @@ -45,6 +46,7 @@ public override void SetupCustomOption() TargetKnowOtherTarget = BooleanOptionItem.Create(Id + 14, "CultistTargetKnowOtherTarget", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cultist]); CharmedCountMode = StringOptionItem.Create(Id + 17, "Cultist_CharmedCountMode", EnumHelper.GetAllNames(), 1, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cultist]); CanCharmNeutral = BooleanOptionItem.Create(Id + 18, "CultistCanCharmNeutral", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cultist]); + CanCharmCoven = BooleanOptionItem.Create(Id + 19, "CultistCanCharmCoven", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Cultist]); } public override void Add(byte playerId) { @@ -102,7 +104,8 @@ public static bool KnowRole(PlayerControl player, PlayerControl target) public static bool CanBeCharmed(PlayerControl pc) { return pc != null && (pc.GetCustomRole().IsCrewmate() || pc.GetCustomRole().IsImpostor() || - (CanCharmNeutral.GetBool() && pc.GetCustomRole().IsNeutral())) && !pc.Is(CustomRoles.Charmed) + (CanCharmNeutral.GetBool() && pc.GetCustomRole().IsNeutral()) || + (CanCharmCoven.GetBool() && pc.GetCustomRole().IsCoven())) && !pc.Is(CustomRoles.Charmed) && !pc.Is(CustomRoles.Admired) && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Infectious) && !pc.Is(CustomRoles.Virus) && !pc.Is(CustomRoles.Cultist) && !(pc.GetCustomSubRoles().Contains(CustomRoles.Hurried) && !Hurried.CanBeConverted.GetBool()); diff --git a/Roles/Neutral/CursedSoul.cs b/Roles/Neutral/CursedSoul.cs index d2f0bf09d..e39f73e55 100644 --- a/Roles/Neutral/CursedSoul.cs +++ b/Roles/Neutral/CursedSoul.cs @@ -22,6 +22,7 @@ internal class CursedSoul : RoleBase private static OptionItem CurseMax; private static OptionItem KnowTargetRole; private static OptionItem CanCurseNeutral; + private static OptionItem CanCurseCoven; private int CurseLimit; @@ -36,6 +37,7 @@ public override void SetupCustomOption() .SetValueFormat(OptionFormat.Times); KnowTargetRole = BooleanOptionItem.Create(Id + 13, "CursedSoulKnowTargetRole", true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.CursedSoul]); CanCurseNeutral = BooleanOptionItem.Create(Id + 16, "CursedSoulCanCurseNeutral", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.CursedSoul]); + CanCurseCoven = BooleanOptionItem.Create(Id + 17, "CursedSoulCanCurseCoven", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.CursedSoul]); } public override void Init() { @@ -109,7 +111,8 @@ public override string PlayerKnowTargetColor(PlayerControl seer, PlayerControl t private static bool CanBeSoulless(PlayerControl pc) { return pc != null && (pc.GetCustomRole().IsCrewmate() || pc.GetCustomRole().IsImpostor() || - (CanCurseNeutral.GetBool() && pc.GetCustomRole().IsNeutral())) && !pc.Is(CustomRoles.Soulless) && !pc.Is(CustomRoles.Admired) && !pc.Is(CustomRoles.Loyal); + (CanCurseNeutral.GetBool() && pc.GetCustomRole().IsNeutral()) || + (CanCurseCoven.GetBool() && pc.GetCustomRole().IsCoven())) && !pc.Is(CustomRoles.Soulless) && !pc.Is(CustomRoles.Admired) && !pc.Is(CustomRoles.Loyal); } public override void SetAbilityButtonText(HudManager hud, byte playerId) { diff --git a/Roles/Neutral/Infectious.cs b/Roles/Neutral/Infectious.cs index b0100fbd1..f99e97b8b 100644 --- a/Roles/Neutral/Infectious.cs +++ b/Roles/Neutral/Infectious.cs @@ -170,10 +170,12 @@ public static bool CanBeBitten(PlayerControl pc) { return pc != null && (pc.GetCustomRole().IsCrewmate() || pc.GetCustomRole().IsImpostor() - || pc.GetCustomRole().IsNK()) && !pc.Is(CustomRoles.Infected) + || pc.GetCustomRole().IsNK() + || pc.GetCustomRole().IsCoven()) && !pc.Is(CustomRoles.Infected) && !pc.Is(CustomRoles.Admired) && !pc.Is(CustomRoles.Loyal) - && !pc.Is(CustomRoles.Cultist) + && !pc.Is(CustomRoles.Cultist) + && !pc.Is(CustomRoles.Enchanted) && !pc.Is(CustomRoles.Infectious) && !pc.Is(CustomRoles.Virus); } public override void SetAbilityButtonText(HudManager hud, byte playerId) From 094a8a50e98b602b3ac4f1d15e85eddfcfcb8911 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 22 Nov 2024 20:09:41 -0500 Subject: [PATCH 046/101] bug fixes --- Roles/Coven/CovenManager.cs | 2 +- Roles/Coven/Poisoner.cs | 6 +++++- Roles/Coven/VoodooMaster.cs | 40 +++++++++++++++++++++++++++---------- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 83072b9ee..9d20772d7 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -168,7 +168,7 @@ public static void CheckNecroVotes() { Logger.Info($"currentResult == byte.MinValue, return", "Coven"); } - else if (voteCount[currentResult] == voteCount[lastResult] && currentResult != lastResult) + else if (voteCount.ContainsKey(lastResult) && voteCount[currentResult] == voteCount[lastResult] && currentResult != lastResult) { Logger.Info($"{GetPlayerById(currentResult).GetRealName()} and {GetPlayerById(lastResult).GetRealName()} had equal Necronomicon votes, not changing Necronomicon", "Coven"); } diff --git a/Roles/Coven/Poisoner.cs b/Roles/Coven/Poisoner.cs index f8739869f..60c9ccb7d 100644 --- a/Roles/Coven/Poisoner.cs +++ b/Roles/Coven/Poisoner.cs @@ -72,6 +72,11 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t { if (HasNecronomicon(killer) && !target.IsPlayerCoven()) { + if (target.GetCustomRole().IsCovenTeam()) + { + killer.Notify(GetString("CovenDontKillOtherCoven")); + return false; + } if (target.Is(CustomRoles.Bait)) return true; killer.SetKillCooldown(); @@ -81,7 +86,6 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t PoisonedPlayers.Add(target.PlayerId, new(killer.PlayerId, 0f)); } } - killer.Notify(GetString("CovenDontKillOtherCoven")); return false; } else diff --git a/Roles/Coven/VoodooMaster.cs b/Roles/Coven/VoodooMaster.cs index efebf1650..f996acdb0 100644 --- a/Roles/Coven/VoodooMaster.cs +++ b/Roles/Coven/VoodooMaster.cs @@ -71,20 +71,38 @@ public override string GetProgressText(byte playerId, bool comms) => ColorString(AbilityLimit >= 1 ? GetRoleColor(CustomRoles.VoodooMaster).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - return HasNecronomicon(killer) && killer.CheckDoubleTrigger(target, () => + if (!HasNecronomicon(killer)) { - if (AbilityLimit > 0 && (!target.IsPlayerCoven() || (target.IsPlayerCoven() && CanDollCoven.GetBool()))) + SetDoll(killer, target); + return false; + } + if (killer.CheckDoubleTrigger(target, () => { SetDoll(killer, target); })) + { + if (HasNecronomicon(killer)) { - Dolls[killer.PlayerId].Add(target.PlayerId); - AbilityLimit--; - SendRPC(killer, target); - killer.RpcGuardAndKill(target); - killer.Notify(string.Format(GetString("VoodooMasterDolledSomeone"), target.GetRealName())); - if (HasNecronomicon(killer)) ReportDeadBodyPatch.CanReport[target.PlayerId] = false; + if (target.GetCustomRole().IsCovenTeam()) + { + killer.Notify(GetString("CovenDontKillOtherCoven")); + return false; + } + else return true; } - else if (target.IsPlayerCoven() && CanDollCoven.GetBool()) killer.Notify(GetString("VoodooMasterNoDollCoven")); - else killer.Notify(GetString("VoodooMasterNoDollsLeft")); - }); + } + return false; + } + private void SetDoll(PlayerControl killer, PlayerControl target) { + if (AbilityLimit > 0 && (!target.IsPlayerCoven() || (target.IsPlayerCoven() && CanDollCoven.GetBool()))) + { + Dolls[killer.PlayerId].Add(target.PlayerId); + AbilityLimit--; + SendRPC(killer, target); + killer.RpcGuardAndKill(target); + killer.Notify(string.Format(GetString("VoodooMasterDolledSomeone"), target.GetRealName())); + killer.SetKillCooldown(); + if (HasNecronomicon(killer)) ReportDeadBodyPatch.CanReport[target.PlayerId] = false; + } + else if (target.IsPlayerCoven() && CanDollCoven.GetBool()) killer.Notify(GetString("VoodooMasterNoDollCoven")); + else killer.Notify(GetString("VoodooMasterNoDollsLeft")); } public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { From c813bfcbe1b4d3d4e5717b1e83f2580c6f2a44c6 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 24 Nov 2024 18:55:28 -0500 Subject: [PATCH 047/101] fallback incase originalSpeed doesnt contain player somehow --- Roles/Coven/Medusa.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Roles/Coven/Medusa.cs b/Roles/Coven/Medusa.cs index 2cae766cd..894df9808 100644 --- a/Roles/Coven/Medusa.cs +++ b/Roles/Coven/Medusa.cs @@ -119,7 +119,6 @@ public override void UnShapeShiftButton(PlayerControl dusa) { dusa.Notify(GetString("MedusaStoningStart"), StoneDuration.GetFloat()); isStoning = true; - originalSpeed.Remove(player); originalSpeed.Add(player, Main.AllPlayerSpeed[player]); Main.AllPlayerSpeed[player] = 0f; ReportDeadBodyPatch.CanReport[player] = false; @@ -128,9 +127,11 @@ public override void UnShapeShiftButton(PlayerControl dusa) { dusa.Notify(GetString("MedusaStoningEnd")); isStoning = false; - Main.AllPlayerSpeed[player] = originalSpeed[player]; + // sometimes it doesn't contain the player for some stupid reason + if (originalSpeed.ContainsKey(player)) Main.AllPlayerSpeed[player] = originalSpeed[player]; + else Main.AllPlayerSpeed[player] = AURoleOptions.PlayerSpeedMod; GetPlayerById(player).SyncSettings(); - originalSpeed.Remove(player); + if (originalSpeed.ContainsKey(player)) originalSpeed.Remove(player); StonedPlayers[dusa.PlayerId].Remove(player); }, StoneDuration.GetFloat(), "Medusa Revert Stone"); } From 56d34d8bb73ef144bff63b07b3afbf2fd8e52249 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:10:17 -0500 Subject: [PATCH 048/101] fix ritualist tryhidemsg, fix necro and sacrifist cooldowns not resetting, add necro icon to /icon --- Modules/GuessManager.cs | 2 +- Resources/Lang/en_US.json | 2 +- Roles/Coven/Necromancer.cs | 4 ++++ Roles/Coven/Ritualist.cs | 39 +++++++++++++++++++++++++++++++++++++- Roles/Coven/Sacrifist.cs | 4 ++++ 5 files changed, 48 insertions(+), 3 deletions(-) diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 4d61968cb..7f6c2e825 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -588,7 +588,7 @@ public static void TryHideMsg() var roles = CustomRolesHelper.AllRoles.Where(x => x is not CustomRoles.NotAssigned).ToArray(); var rd = IRandom.Instance; string msg; - string[] command = ["bet", "bt", "guess", "gs", "shoot", "st", "rt", "赌", "猜", "审判", "tl", "判", "审"]; + string[] command = ["bet", "bt", "guess", "gs", "shoot", "st", "赌", "猜", "审判", "tl", "判", "审"]; for (int i = 0; i < 20; i++) { msg = "/"; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 8670e8014..44a5dffa4 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2273,7 +2273,7 @@ "Command.qq": "→ Lobby will be posted on QQ website (China only)", "Command.dump": "→ Output Log to Desktop", "Command.death": "→ Display info on how you died", - "Command.icons": "
╳ - The Player was marked by the Blackmailer and can't talk during the Meeting
☆ - Used by Captain to display themselves. Only Crewmates can see the Captain's star
乂 - This player was hexed by the Hex Master and will die if the Hex Master isn't killed or ejected by the end of the Meeting.
♦ - Used by Lawyer or Executioner or Follower.
♥ - Used by Lovers or Romantic.
✚ - Used by Medic to mark their target.
⦿ - This player is in a duel with the Pirate.
!? - This player was marked by the Quizmaster and must answer the question correctly to survive.
☜ - Used by Schrödinger's cat to mark their teammate.
◈ - This player marked by the Shroud and will die if the Shroud is not killed or ejected by the end of the meeting.
⚠ - This player is a Snitch or Solsticer who has finished their tasks.
★ - Used by Super Star or Cyber or Marshall.
† - This player was spelled and will die if the Witch is not killed by the end of the meeting.
∇ - Used by Kamikaze to mark their targets.
■ - Used by Lightning to mark their quantum ghosts.
⊠ - Used by Jailer to mark their prisoner.
● - Used by Baker to mark who has Bread.
♠ - Used by Soul Collector to mark who's death they're predicting.
⦿ - Used by Plaguebearer to mark who they have plagued.", + "Command.icons": "
╳ - The Player was marked by the Blackmailer and can't talk during the Meeting
☆ - Used by Captain to display themselves. Only Crewmates can see the Captain's star
乂 - This player was hexed by the Hex Master and will die if the Hex Master isn't killed or ejected by the end of the Meeting.
♦ - Used by Lawyer or Executioner or Follower.
♥ - Used by Lovers or Romantic.
✚ - Used by Medic to mark their target.
⦿ - This player is in a duel with the Pirate.
!? - This player was marked by the Quizmaster and must answer the question correctly to survive.
☜ - Used by Schrödinger's cat to mark their teammate.
◈ - This player marked by the Shroud and will die if the Shroud is not killed or ejected by the end of the meeting.
⚠ - This player is a Snitch or Solsticer who has finished their tasks.
★ - Used by Super Star or Cyber or Marshall.
† - This player was spelled and will die if the Witch is not killed by the end of the meeting.
∇ - Used by Kamikaze to mark their targets.
■ - Used by Lightning to mark their quantum ghosts.
⊠ - Used by Jailer to mark their prisoner.
● - Used by Baker to mark who has Bread.
♠ - Used by Soul Collector to mark who's death they're predicting.
⦿ - Used by Plaguebearer to mark who they have plagued.
<#ac42f2>♣ - Shown on the Coven member with the Necronomicon.", "Command.start": "[Seconds] → Start the game", "Command.iconinfo": "→ Display info on in-meeting icons", diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index 90915b555..337731551 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -204,6 +204,10 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT } else canUseAbility = true; } + public override void AfterMeetingTasks() + { + AbilityTimer = 0; + } private static void Countdown(int seconds, PlayerControl player) { var killer = Killer; diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index be5fb202c..e9ad27859 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -113,7 +113,7 @@ public static bool RitualistMsgCheck(PlayerControl pc, string msg, bool isUI = f { //if (Options.NewHideMsg.GetBool()) ChatManager.SendPreviousMessagesToAll(); //else GuessManager.TryHideMsg(); - GuessManager.TryHideMsg(); + TryHideMsgForRitual(); ChatManager.SendPreviousMessagesToAll(); } if (RitualLimit[pc.PlayerId] <= 0) @@ -152,6 +152,43 @@ public static bool RitualistMsgCheck(PlayerControl pc, string msg, bool isUI = f } return false; } + private static void TryHideMsgForRitual() + { + ChatUpdatePatch.DoBlockChat = true; + List roles = CustomRolesHelper.AllRoles.Where(x => x is not CustomRoles.NotAssigned).ToList(); + var rd = IRandom.Instance; + string msg; + string[] command = ["rt", "rit", "ritual", "bloodritual"]; + for (int i = 0; i < 20; i++) + { + msg = "/"; + if (rd.Next(1, 100) < 20) + { + msg += "id"; + } + else + { + msg += command[rd.Next(0, command.Length - 1)]; + msg += rd.Next(1, 100) < 50 ? string.Empty : " "; + msg += rd.Next(0, 15).ToString(); + msg += rd.Next(1, 100) < 50 ? string.Empty : " "; + CustomRoles role = roles.RandomElement(); + msg += rd.Next(1, 100) < 50 ? string.Empty : " "; + msg += Utils.GetRoleName(role); + + } + var player = Main.AllAlivePlayerControls.RandomElement(); + DestroyableSingleton.Instance.Chat.AddChat(player, msg); + var writer = CustomRpcSender.Create("MessagesToSend", SendOption.None); + writer.StartMessage(-1); + writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) + .Write(msg) + .EndRpc(); + writer.EndMessage(); + writer.SendMessage(); + } + ChatUpdatePatch.DoBlockChat = false; + } public override void AfterMeetingTasks() { var rit = Utils.GetPlayerListByRole(CustomRoles.Ritualist).First(); diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index c4d91addd..38e35127a 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -300,6 +300,10 @@ public static void SetVision(PlayerControl player, IGameOptions opt) opt.SetFloat(FloatOptionNames.ImpostorLightMod, Vision.GetFloat()); } } + public override void AfterMeetingTasks() + { + debuffTimer = 0; + } public override string GetLowerText(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) { return debuffTimer.ToString() + "s / " + maxDebuffTimer.ToString() + "s"; From c25411ea3bc1d4ad9d199cf0b662b8b1b87938f8 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Wed, 27 Nov 2024 00:00:33 +0800 Subject: [PATCH 049/101] Revert unnecessary changes From e00b488417e3fb3816153d32375b33d46b8570a1 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:33:09 -0500 Subject: [PATCH 050/101] Ritualist never uses an RPC, get rid of it --- Modules/RPC.cs | 4 ---- Roles/Coven/Ritualist.cs | 11 ----------- 2 files changed, 15 deletions(-) diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 6442ef1b7..58baee954 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -111,7 +111,6 @@ enum CustomRPC : byte // 185/255 USED SetImitateLimit, DictatorRPC, Necronomicon, - BloodRitual, //FFA SyncFFAPlayer, SyncFFANameNotify, @@ -591,9 +590,6 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.SetChameleonTimer: Chameleon.ReceiveRPC_Custom(reader); break; - case CustomRPC.BloodRitual: - Ritualist.ReceiveRPC_Custom(reader, __instance); - break; case CustomRPC.SetAlchemistTimer: Alchemist.ReceiveRPC(reader); break; diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index e9ad27859..960c93c32 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -52,17 +52,6 @@ public override void Add(byte PlayerId) EnchantedPlayers[PlayerId] = []; RitualLimit.Add(PlayerId, MaxRitsPerRound.GetInt()); } - private static void SendRPC(byte playerId) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.BloodRitual, SendOption.Reliable, -1); - writer.Write(playerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - public static void ReceiveRPC_Custom(MessageReader reader, PlayerControl pc) - { - int PlayerId = reader.ReadByte(); - RitualistMsgCheck(pc, $"/rt {PlayerId}", true); - } public override bool CanUseKillButton(PlayerControl pc) => HasNecronomicon(pc); public override void OnReportDeadBody(PlayerControl hatsune, NetworkedPlayerInfo miku) { From 3880fd45936678322c70e340e4b14eeaea3c9862 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:46:14 -0500 Subject: [PATCH 051/101] some changes from review --- Patches/MeetingHudPatch.cs | 2 +- Roles/AddOns/Common/Oiiai.cs | 1 + Roles/Coven/CovenLeader.cs | 2 +- Roles/Coven/CovenManager.cs | 2 +- Roles/Coven/Jinx.cs | 1 + 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 57918e4a1..85362321c 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1127,7 +1127,7 @@ public static void Postfix(MeetingHud __instance) { var randomRole = CustomRolesHelper.AllRoles.Where(role => role.IsEnable() && !role.IsAdditionRole() && role.IsCoven()).ToList().RandomElement(); roleTextMeeting.text = ColorString(GetRoleColor(randomRole), GetString(randomRole.ToString())); - if (randomRole is CustomRoles.CovenLeader or CustomRoles.Jinx or CustomRoles.Illusionist or CustomRoles.VoodooMaster) // Roles with Ability Uses + if (randomRole.GetStaticRoleClass().IsMethodOverridden("GetProgressText")) // Roles with Ability Uses { roleTextMeeting.text += randomRole.GetStaticRoleClass().GetProgressText(PlayerControl.LocalPlayer.PlayerId, false); } diff --git a/Roles/AddOns/Common/Oiiai.cs b/Roles/AddOns/Common/Oiiai.cs index cfaef2df1..364b6365e 100644 --- a/Roles/AddOns/Common/Oiiai.cs +++ b/Roles/AddOns/Common/Oiiai.cs @@ -93,6 +93,7 @@ public static void OnMurderPlayer(PlayerControl killer, PlayerControl target) } else if (killer.GetCustomRole().IsCoven() && !CovenManager.HasNecronomicon(killer)) { + killer.RpcChangeRoleBasis(CustomRoles.Amnesiac); killer.RpcSetCustomRole(CustomRoles.Amnesiac); killer.RpcSetCustomRole(CustomRoles.Enchanted); killer.AddInSwitchAddons(killer, CustomRoles.Enchanted); diff --git a/Roles/Coven/CovenLeader.cs b/Roles/Coven/CovenLeader.cs index 51a8418d3..59916c4c7 100644 --- a/Roles/Coven/CovenLeader.cs +++ b/Roles/Coven/CovenLeader.cs @@ -41,7 +41,6 @@ public override void Add(byte playerId) public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) { - byte playerId = reader.ReadByte(); AbilityLimit = reader.ReadSingle(); } public override bool CanUseKillButton(PlayerControl pc) => pc.IsAlive(); @@ -78,6 +77,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } killer.Notify(GetString("CovenLeaderRetrain")); killer.ResetKillCooldown(); + killer.SetKillCooldown(); return false; } diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 9d20772d7..2a53cf9e7 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -5,7 +5,7 @@ using static TOHE.Utils; namespace TOHE; -public abstract class CovenManager : RoleBase +public abstract class CovenManager : RoleBase // NO, THIS IS NOT A ROLE { public static byte necroHolder = byte.MaxValue; diff --git a/Roles/Coven/Jinx.cs b/Roles/Coven/Jinx.cs index 92f3f5966..ecc0bb1e2 100644 --- a/Roles/Coven/Jinx.cs +++ b/Roles/Coven/Jinx.cs @@ -116,6 +116,7 @@ public void SendRPC(PlayerControl player, PlayerControl target) } public override void ReceiveRPC(MessageReader reader, PlayerControl pc) { + AbilityLimit = reader.ReadSingle(); byte jinxID = reader.ReadByte(); byte jinxedID = reader.ReadByte(); JinxedPlayers[jinxID].Add(jinxedID); From fdeabdbc543c3d477065ea90af5fd0851a3ccf3c Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:57:27 -0500 Subject: [PATCH 052/101] fix some incorrect merges --- Roles/Coven/HexMaster.cs | 14 -------------- Roles/Neutral/Jackal.cs | 7 +------ 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/Roles/Coven/HexMaster.cs b/Roles/Coven/HexMaster.cs index 2edd28571..f842e27af 100644 --- a/Roles/Coven/HexMaster.cs +++ b/Roles/Coven/HexMaster.cs @@ -410,20 +410,6 @@ public override string GetLowerText(PlayerControl hexmaster, PlayerControl seen public override void SetAbilityButtonText(HudManager hud, byte playerid) => hud.KillButton.OverrideText($"{GetString("HexButtonText")}"); -} - - public override void SetAbilityButtonText(HudManager hud, byte playerid) - { - if (IsHexMode(playerid) && NowSwitchTrigger != SwitchTriggerList.TriggerDouble) - { - hud.KillButton.OverrideText($"{GetString("HexButtonText")}"); - } - else - { - hud.KillButton.OverrideText($"{GetString("KillButtonText")}"); - } - } - public override void Remove(byte playerId) { if (HexedPlayer.ContainsKey(playerId)) diff --git a/Roles/Neutral/Jackal.cs b/Roles/Neutral/Jackal.cs index 088dcadf8..5eeb4ac6e 100644 --- a/Roles/Neutral/Jackal.cs +++ b/Roles/Neutral/Jackal.cs @@ -355,12 +355,7 @@ public static bool CanBeSidekick(PlayerControl pc) return pc != null && !pc.Is(CustomRoles.Sidekick) && !pc.Is(CustomRoles.Recruit) && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Admired) && !pc.Is(CustomRoles.Rascal) && !pc.Is(CustomRoles.Madmate) && !pc.Is(CustomRoles.Charmed) && !pc.Is(CustomRoles.Infected) && !pc.Is(CustomRoles.Paranoia) - && !pc.Is(CustomRoles.Contagious) && pc.GetCustomRole().IsAbleToBeSidekicked() - return pc != null && !pc.Is(CustomRoles.Sidekick) && !pc.Is(CustomRoles.Recruit) - && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Admired) && !pc.Is(CustomRoles.Rascal) && !pc.Is(CustomRoles.Madmate) - && !pc.Is(CustomRoles.Charmed) && !pc.Is(CustomRoles.Infected) && !pc.Is(CustomRoles.Paranoia) - && !pc.Is(CustomRoles.Contagious) && !pc.Is(CustomRoles.Enchanted) && pc.GetCustomRole().IsAbleToBeSidekicked() - && !(pc.GetCustomSubRoles().Contains(CustomRoles.Hurried) && !Hurried.CanBeConverted.GetBool()); + && !pc.Is(CustomRoles.Contagious) && !pc.Is(CustomRoles.Enchanted) && pc.GetCustomRole().IsAbleToBeSidekicked(); } public override void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl target, bool inMeeting, bool isSuidice) From 50a5205a32a0b8cffcf5e225055b7c5e33327c68 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Wed, 27 Nov 2024 19:47:09 -0500 Subject: [PATCH 053/101] stuff i forgot to do forgot to remove poisoner counting as poisoner and not coven forgot to reset then set kd on half the roles --- Modules/CustomRolesHelper.cs | 2 -- Roles/Coven/CovenManager.cs | 1 + Roles/Coven/HexMaster.cs | 1 + Roles/Coven/Jinx.cs | 1 + Roles/Coven/Medusa.cs | 3 +++ Roles/Coven/MoonDancer.cs | 4 ++++ Roles/Coven/Poisoner.cs | 10 +++++++++- Roles/Coven/PotionMaster.cs | 2 ++ Roles/Coven/VoodooMaster.cs | 1 + 9 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index c227611f5..e0d4f53df 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -1243,7 +1243,6 @@ public static CountTypes GetCountTypes(this CustomRoles role) CustomRoles.Sidekick => CountTypes.Jackal, CustomRoles.Doppelganger => CountTypes.Doppelganger, CustomRoles.Bandit => CountTypes.Bandit, - CustomRoles.Poisoner => CountTypes.Poisoner, CustomRoles.Pelican => CountTypes.Pelican, CustomRoles.Minion => CountTypes.Impostor, CustomRoles.Bloodmoon => CountTypes.Impostor, @@ -1346,7 +1345,6 @@ var r when r.IsCoven() => CountTypes.Coven, CountTypes.Jackal => CustomRoles.Jackal, CountTypes.Doppelganger => CustomRoles.Doppelganger, CountTypes.Bandit => CustomRoles.Bandit, - CountTypes.Poisoner => CustomRoles.Poisoner, CountTypes.Pelican => CustomRoles.Pelican, CountTypes.Demon => CustomRoles.Demon, CountTypes.BloodKnight => CustomRoles.BloodKnight, diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 2a53cf9e7..706d85521 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -182,6 +182,7 @@ public static void CheckNecroVotes() public static void NecronomiconCheck() { + if (GetPlayerById(necroHolder) == null) return; if (necroHolder == byte.MaxValue || !GetPlayerById(necroHolder).IsAlive() || !GetPlayerById(necroHolder).IsPlayerCoven()) { GiveNecronomicon(); diff --git a/Roles/Coven/HexMaster.cs b/Roles/Coven/HexMaster.cs index f842e27af..0b3cacb7f 100644 --- a/Roles/Coven/HexMaster.cs +++ b/Roles/Coven/HexMaster.cs @@ -168,6 +168,7 @@ private static void SetHexed(PlayerControl killer, PlayerControl target) HexedPlayer[killer.PlayerId].Add(target.PlayerId); SendRPC(true, killer.PlayerId, target.PlayerId); //キルクールの適正化 + killer.ResetKillCooldown(); killer.SetKillCooldown(); } } diff --git a/Roles/Coven/Jinx.cs b/Roles/Coven/Jinx.cs index ecc0bb1e2..a210738a7 100644 --- a/Roles/Coven/Jinx.cs +++ b/Roles/Coven/Jinx.cs @@ -101,6 +101,7 @@ private void JinxPlayer(PlayerControl jinx, PlayerControl target) { JinxedPlayers[jinx.PlayerId].Add(target.PlayerId); jinx.ResetKillCooldown(); + jinx.SetKillCooldown(); AbilityLimit--; SendRPC(jinx, target); } diff --git a/Roles/Coven/Medusa.cs b/Roles/Coven/Medusa.cs index 894df9808..e00e3495f 100644 --- a/Roles/Coven/Medusa.cs +++ b/Roles/Coven/Medusa.cs @@ -103,12 +103,15 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } killer.RpcMurderPlayer(target); killer.ResetKillCooldown(); + killer.SetKillCooldown(); Main.UnreportableBodies.Add(target.PlayerId); return false; } else { StonedPlayers[killer.PlayerId].Add(target.PlayerId); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); killer.Notify(string.Format(GetString("MedusaStonedPlayer"), target.GetRealName())); return false; } diff --git a/Roles/Coven/MoonDancer.cs b/Roles/Coven/MoonDancer.cs index 50076cc46..cc7d2bb54 100644 --- a/Roles/Coven/MoonDancer.cs +++ b/Roles/Coven/MoonDancer.cs @@ -169,12 +169,14 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t { BlastPlayer(killer, target); if (!DisableShieldAnimations.GetBool()) killer.RpcGuardAndKill(killer); + killer.ResetKillCooldown(); killer.SetKillCooldown(); killer.RPCPlayCustomSound("BlastOff"); target.RPCPlayCustomSound("BlastOff"); } else { + killer.ResetKillCooldown(); killer.SetKillCooldown(); killer.Notify(GetString("MoonDancerCantBlastOff")); } @@ -196,6 +198,8 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t BatonPassList[killer.PlayerId].Add(target.PlayerId); killer.Notify(GetString("MoonDancerGiveHarmfulAddon")); } + killer.ResetKillCooldown(); + killer.SetKillCooldown(); return false; } diff --git a/Roles/Coven/Poisoner.cs b/Roles/Coven/Poisoner.cs index 60c9ccb7d..a20494a6e 100644 --- a/Roles/Coven/Poisoner.cs +++ b/Roles/Coven/Poisoner.cs @@ -151,7 +151,15 @@ public override void OnReportDeadBody(PlayerControl sans, NetworkedPlayerInfo ba } PoisonedPlayers.Clear(); } - public bool IsRoleblocked(byte id) => RoleblockedPlayers[_Player.PlayerId].Contains(id); + public static bool IsRoleblocked(byte target) + { + if (RoleblockedPlayers.Count < 1) return false; + foreach (var player in RoleblockedPlayers.Keys) + { + if (RoleblockedPlayers[player].Contains(target)) return true; + } + return false; + } public override bool CheckMurderOnOthersTarget(PlayerControl pc, PlayerControl _) // Target of Pursuer attempt to murder someone { if (!IsRoleblocked(pc.PlayerId) && pc.GetCustomRole() is not CustomRoles.SerialKiller or CustomRoles.Pursuer or CustomRoles.Deputy or CustomRoles.Deceiver or CustomRoles.Poisoner) return false; // I was told these roles should be roleblock immune diff --git a/Roles/Coven/PotionMaster.cs b/Roles/Coven/PotionMaster.cs index 0c26380d1..f430470e5 100644 --- a/Roles/Coven/PotionMaster.cs +++ b/Roles/Coven/PotionMaster.cs @@ -140,6 +140,7 @@ private void SetRitual(PlayerControl killer, PlayerControl target) NotifyRoles(SpecifySeer: killer); SendRPC(PotionMode, killer, target); + killer.ResetKillCooldown(); killer.SetKillCooldown(); } else if (RevealLimit[killer.PlayerId] <= 0) @@ -156,6 +157,7 @@ private void SetRitual(PlayerControl killer, PlayerControl target) SendRPC(PotionMode, killer, target); + killer.ResetKillCooldown(); killer.SetKillCooldown(); } else if (BarrierLimit[killer.PlayerId] <= 0) diff --git a/Roles/Coven/VoodooMaster.cs b/Roles/Coven/VoodooMaster.cs index f996acdb0..7f33f11db 100644 --- a/Roles/Coven/VoodooMaster.cs +++ b/Roles/Coven/VoodooMaster.cs @@ -98,6 +98,7 @@ private void SetDoll(PlayerControl killer, PlayerControl target) { SendRPC(killer, target); killer.RpcGuardAndKill(target); killer.Notify(string.Format(GetString("VoodooMasterDolledSomeone"), target.GetRealName())); + killer.ResetKillCooldown(); killer.SetKillCooldown(); if (HasNecronomicon(killer)) ReportDeadBodyPatch.CanReport[target.PlayerId] = false; } From 5d4e8ffba99a0c0e556bb7607055653ba49b5e76 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Wed, 27 Nov 2024 21:32:08 -0500 Subject: [PATCH 054/101] im stupid --- Roles/Coven/CovenManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 706d85521..35d34421b 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -182,8 +182,7 @@ public static void CheckNecroVotes() public static void NecronomiconCheck() { - if (GetPlayerById(necroHolder) == null) return; - if (necroHolder == byte.MaxValue || !GetPlayerById(necroHolder).IsAlive() || !GetPlayerById(necroHolder).IsPlayerCoven()) + if (GetPlayerById(necroHolder) == null || necroHolder == byte.MaxValue || !GetPlayerById(necroHolder).IsAlive() || !GetPlayerById(necroHolder).IsPlayerCoven()) { GiveNecronomicon(); } From 86dbfad1ff04e676b12a6ffabe58982faa4617df Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:14:43 -0500 Subject: [PATCH 055/101] remove dreamweaver and sorceress for now --- Roles/Coven/Dreamweaver.cs | 0 Roles/Coven/Sorceress.cs | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Roles/Coven/Dreamweaver.cs delete mode 100644 Roles/Coven/Sorceress.cs diff --git a/Roles/Coven/Dreamweaver.cs b/Roles/Coven/Dreamweaver.cs deleted file mode 100644 index e69de29bb..000000000 diff --git a/Roles/Coven/Sorceress.cs b/Roles/Coven/Sorceress.cs deleted file mode 100644 index e69de29bb..000000000 From 7c84becda40d3112cc12446450c4aba244d4bd95 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:21:36 -0500 Subject: [PATCH 056/101] remove unused usings --- Modules/Utils.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 05f6324ad..da6cb654f 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -24,9 +24,6 @@ using static TOHE.Translator; using TOHE.Patches; using TOHE.Roles.Coven; -using Epic.OnlineServices; -using UnityEngine.UI; - namespace TOHE; From a51be86eca5b47071981ce75223a645015b0976a Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:24:53 -0500 Subject: [PATCH 057/101] attempt to fix sacrifist vision --- Roles/Coven/Poisoner.cs | 1 - Roles/Coven/Sacrifist.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Roles/Coven/Poisoner.cs b/Roles/Coven/Poisoner.cs index a20494a6e..ac76887e4 100644 --- a/Roles/Coven/Poisoner.cs +++ b/Roles/Coven/Poisoner.cs @@ -1,5 +1,4 @@ using TOHE.Roles.AddOns.Common; -using TOHE.Roles.Crewmate; using UnityEngine; using static TOHE.Translator; using static TOHE.Utils; diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index 38e35127a..ebf317174 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -293,7 +293,7 @@ public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInf public static void SetVision(PlayerControl player, IGameOptions opt) { if (VisionChange.Any(a => a.Value.Contains(player.PlayerId) && - Main.AllAlivePlayerControls.Any(b => b.PlayerId == a.Key)) && DebuffID == 1) + Main.AllAlivePlayerControls.Any(b => b.PlayerId == a.Key))) { opt.SetVision(false); opt.SetFloat(FloatOptionNames.CrewLightMod, Vision.GetFloat()); From 35ba5129c9b0aa2bc4f6c6315d5fea54c9422512 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 30 Nov 2024 15:07:14 -0500 Subject: [PATCH 058/101] add some intro sounds --- Patches/IntroPatch.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 406549dcd..622196233 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -1,5 +1,6 @@ using AmongUs.GameOptions; using BepInEx.Unity.IL2CPP.Utils.Collections; +using MonoMod.Cil; using System; using System.IO; using System.Security.Cryptography; @@ -428,7 +429,8 @@ public static void Postfix(IntroCutscene __instance) break; case CustomRoles.SoulCatcher: case CustomRoles.Specter: - case CustomRoles.Stalker: + case CustomRoles.Stalker: + case CustomRoles.CovenLeader: case CustomRoles.PhantomTOHE: PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Phantom); break; @@ -436,6 +438,8 @@ public static void Postfix(IntroCutscene __instance) case CustomRoles.TrackerTOHE: PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Tracker); break; + case CustomRoles.Sacrifist: + case CustomRoles.Poisoner: case CustomRoles.NoisemakerTOHE: PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Noisemaker); break; @@ -450,6 +454,7 @@ public static void Postfix(IntroCutscene __instance) case CustomRoles.Terrorist: case CustomRoles.Bomber: + case CustomRoles.Conjurer: var sound = ShipStatus.Instance.CommonTasks.FirstOrDefault(task => task.TaskType == TaskTypes.FixWiring) .MinigamePrefab.OpenSound; PlayerControl.LocalPlayer.Data.Role.IntroSound = sound; @@ -502,6 +507,12 @@ public static void Postfix(IntroCutscene __instance) case CustomRoles.Chameleon: PlayerControl.LocalPlayer.Data.Role.IntroSound = PlayerControl.LocalPlayer.MyPhysics.ImpostorDiscoveredSound; break; + case CustomRoles.Jinx: + PlayerControl.LocalPlayer.Data.Role.IntroSound = RoleManager.Instance.AllRoles.FirstOrDefault((role) => role.Role == RoleTypes.GuardianAngel)?.UseSound; + break; + case CustomRoles.Illusionist: + PlayerControl.LocalPlayer.Data.Role.IntroSound = RoleManager.Instance.AllRoles.FirstOrDefault((role) => role.Role == RoleTypes.Phantom)?.UseSound; + break; } if (PlayerControl.LocalPlayer.Is(CustomRoles.Madmate) || role.IsMadmate()) From 41283f68dd065f387efdf35a3fdc7a9f2c92d43a Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 30 Nov 2024 18:01:39 -0500 Subject: [PATCH 059/101] i forgot to make roleblocks clear after meeting --- Resources/Lang/en_US.json | 2 +- Roles/Coven/Jinx.cs | 2 +- Roles/Coven/Poisoner.cs | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 7f50ff98a..ede39b92f 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -969,7 +969,7 @@ "PotionMasterInfoLong": "(Coven):\nThe Potion Master has two potions available for their use. The Reveal potion reveals a player's role. The Barrier potion places a shield on a player for one round, the player will not be notified of this unless they are Coven as well. Click the Shapeshift button to change potions.\nWith the Necronomicon, the Potion Master can double-click their kill button to kill.", "NecromancerInfoLong": "(Coven):\nAs the Necromancer, you can shapeshift to become the role of a random dead person for a set duration.\nSome roles can not be used.\nOnce a role is used, it can not be used the rest of the game.\nWith the Necronomicon, when someone tries to kill you, you will block the kill and be teleported to a random vent. You have a limited time to kill your killer. If the time runs out or you try to kill someone else, you die.", "CovenLeaderInfoLong": "(Coven):\nThe Coven Leader can use their kill button on a fellow Coven member to retrain them into a random Coven role that isn’t currently in the game. During the next meeting, that Coven member will be notified that the Coven Leader wishes to retrain them. They can vote themselves to accept the retrain, or vote otherwise to deny it. Denying the retrain does not take away an ability usage.\nWith the Necronomicon, you cannot retrain, and can only kill other players.", - "RitualistInfoLong": "(Coven):\nDuring a meeting, the Ritualist can perform a Blood Ritual to guess a player’s exact role. If the Ritualist is correct, that player will be converted to Coven. If the Ritualist is incorrect, they will not die but will be unable to Blood Ritual until the next meeting.\nhe command is /rt id role.\nWith the Necronomicon, the Ritualist can kill.", + "RitualistInfoLong": "(Coven):\nDuring a meeting, the Ritualist can perform a Blood Ritual to guess a player’s exact role. If the Ritualist is correct, that player will be converted to Coven. If the Ritualist is incorrect, they will not die but will be unable to Blood Ritual until the next meeting.\nThe command is /rt id role.\nWith the Necronomicon, the Ritualist can kill.", "ConjurerInfoLong": "(Coven):\nShapeshift once to mark a location.\nShapeshift again to conjure a meteor at the place you marked, killing everyone in the radius.\nWith the Necronomicon, you can kill. You can also mark a player using the Shapeshift menu. When the Conjurer clicks the shapeshift button again, all the players in the radius of the marked player will die, including the marked player.", "DreamweaverInfoLong": "(Coven):\nThe Dreamweaver can Dreamweave a player. The dreamweaved players will be notified of this during the next meeting. If the Dreamweaver is not voted out, these players will be unable to use their abilities until the Dreamweaver dies.\nWith the Necronomicon, the Dreamweaver can double-click to kill.", "IllusionistInfoLong": "(Coven):\nThe Illusionist can use their kill button on a player to reverse the results of any investigative role. For example, if someone with a kill button is Illusioned, they will appear not to have a kill button to the Investigator, and vice versa.\nIllusions wear off after meetings.\nWith the Necronomicon, you may double-click to kill. Every kill you make appears as a random death reason.", diff --git a/Roles/Coven/Jinx.cs b/Roles/Coven/Jinx.cs index a210738a7..5b4e03189 100644 --- a/Roles/Coven/Jinx.cs +++ b/Roles/Coven/Jinx.cs @@ -142,8 +142,8 @@ or CustomRoles.Veteran { killer.RpcGuardAndKill(target); killer.SetDeathReason(PlayerState.DeathReason.Jinx); - killer.RpcMurderPlayer(killer); killer.SetRealKiller(jinx); + killer.RpcMurderPlayer(killer); if (HasNecronomicon(jinx)) { target.SetDeathReason(PlayerState.DeathReason.Jinx); diff --git a/Roles/Coven/Poisoner.cs b/Roles/Coven/Poisoner.cs index ac76887e4..409b9c123 100644 --- a/Roles/Coven/Poisoner.cs +++ b/Roles/Coven/Poisoner.cs @@ -149,6 +149,10 @@ public override void OnReportDeadBody(PlayerControl sans, NetworkedPlayerInfo ba KillPoisoned(poisoner, target); } PoisonedPlayers.Clear(); + foreach (var poisoner in RoleblockedPlayers.Keys) + { + RoleblockedPlayers[poisoner].Clear(); + } } public static bool IsRoleblocked(byte target) { From deea0bcb2d2d9455d1db94a8521e0215b002f636 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 30 Nov 2024 18:52:52 -0500 Subject: [PATCH 060/101] fix IsJinxed --- Roles/Coven/Jinx.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Roles/Coven/Jinx.cs b/Roles/Coven/Jinx.cs index 5b4e03189..0d4bfbf8d 100644 --- a/Roles/Coven/Jinx.cs +++ b/Roles/Coven/Jinx.cs @@ -76,7 +76,15 @@ public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl t } */ //public override void ApplyGameOptions(IGameOptions opt, byte babushka) => opt.SetVision(HasImpostorVision.GetBool()); - public bool IsJinxed(byte playerId) => JinxedPlayers[_Player.PlayerId].Contains(playerId); + public static bool IsJinxed(byte target) + { + if (JinxedPlayers.Count < 1) return false; + foreach (var player in JinxedPlayers.Keys) + { + if (JinxedPlayers[player].Contains(target)) return true; + } + return false; + } public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { if (killer == null || target == null) return false; From 98b7820285123145e93d2e8ad8d4da54122c7a40 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 30 Nov 2024 18:59:09 -0500 Subject: [PATCH 061/101] coven cant be hexed initially --- Roles/Coven/HexMaster.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Roles/Coven/HexMaster.cs b/Roles/Coven/HexMaster.cs index 0b3cacb7f..f60e232b8 100644 --- a/Roles/Coven/HexMaster.cs +++ b/Roles/Coven/HexMaster.cs @@ -251,7 +251,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t */ if (!HasNecronomicon(killer)) { - SetHexed(killer, target); + if (!target.GetCustomRole().IsCovenTeam()) SetHexed(killer, target); return false; } if (killer.CheckDoubleTrigger(target, () => { SetHexedNecronomicon(killer, target); })) @@ -271,6 +271,11 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t private static void SetHexedNecronomicon(PlayerControl killer, PlayerControl target) { if (!HasEnabled) return; + if (target.GetCustomRole().IsCovenTeam()) + { + killer.Notify(GetString("CovenDontKillOtherCoven")); + return; + } CurrentHexedPlayer = target.PlayerId; LastHexedPlayer = killer.PlayerId; From c353680a8efa8067bd6c3ad5ae0f50c549fd5219 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 30 Nov 2024 19:06:47 -0500 Subject: [PATCH 062/101] replace most instances of IsPlayerCoven with IsCovenTeam --- Roles/Coven/Conjurer.cs | 4 ++-- Roles/Coven/CovenManager.cs | 18 ------------------ Roles/Coven/HexMaster.cs | 2 +- Roles/Coven/Illusionist.cs | 6 +++--- Roles/Coven/Jinx.cs | 2 +- Roles/Coven/MoonDancer.cs | 6 +++--- Roles/Coven/Necromancer.cs | 2 +- Roles/Coven/Poisoner.cs | 2 +- Roles/Coven/PotionMaster.cs | 2 +- Roles/Coven/Ritualist.cs | 2 +- Roles/Coven/VoodooMaster.cs | 8 ++++---- 11 files changed, 18 insertions(+), 36 deletions(-) diff --git a/Roles/Coven/Conjurer.cs b/Roles/Coven/Conjurer.cs index 504cd8b18..c86e7e4e6 100644 --- a/Roles/Coven/Conjurer.cs +++ b/Roles/Coven/Conjurer.cs @@ -81,7 +81,7 @@ public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl { var dis = GetDistance(pos, player.transform.position); if (dis > ConjureRadius.GetFloat()) continue; - if (player.IsPlayerCoven() && !CovenDiesInBlast.GetBool()) continue; + if (player.GetCustomRole().IsCovenTeam() && !CovenDiesInBlast.GetBool()) continue; else { player.SetDeathReason(PlayerState.DeathReason.Bombed); @@ -103,7 +103,7 @@ public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl { var dis = GetDistance(GetPlayerById(NecroBombHolder).transform.position, player.transform.position); if (dis > NecroRadius.GetFloat()) continue; - if (player.IsPlayerCoven() && !CovenDiesInBlast.GetBool()) continue; + if (player.GetCustomRole().IsCovenTeam() && !CovenDiesInBlast.GetBool()) continue; else { player.SetDeathReason(PlayerState.DeathReason.Bombed); diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 35d34421b..196ad6cfd 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -54,19 +54,6 @@ private static void SetUpVentOption(CustomRoles role, int Id, bool defaultValue CovenVentOptions[role] = BooleanOptionItem.Create(Id, "%role%CanVent", defaultValue, TabGroup.CovenRoles, false).SetParent(parent); CovenVentOptions[role].ReplacementDictionary = replacementDic; } - /* - public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) - => HasNecronomicon(seen) ? ColorString(GetRoleColor(CustomRoles.CovenLeader), "♣") : string.Empty; - - public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) - { - if (HasNecronomicon(target) && seer.IsPlayerCoven()) - { - return ColorString(GetRoleColor(CustomRoles.CovenLeader), "♣"); - } - return string.Empty; - } - */ private static void SendRPC(byte playerId) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Necronomicon, SendOption.Reliable, -1); @@ -102,11 +89,6 @@ public override bool CanUseImpostorVentButton(PlayerControl pc) return option.GetBool(); } } - /* - public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) => target.IsPlayerCoven() && seer.IsPlayerCoven(); - public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); - */ - public static void GiveNecronomicon() { var pcList = Main.AllAlivePlayerControls.Where(pc => pc.IsPlayerCoven() && pc.IsAlive()).ToList(); diff --git a/Roles/Coven/HexMaster.cs b/Roles/Coven/HexMaster.cs index f60e232b8..a1dc70327 100644 --- a/Roles/Coven/HexMaster.cs +++ b/Roles/Coven/HexMaster.cs @@ -180,7 +180,7 @@ private void PassHex(PlayerControl player, PlayerControl target) var now = GetTimeStamp(); if (now - CurrentHexedPlayerTime < MovingHexPassCooldown.GetFloat()) return; if (target.PlayerId == LastHexedPlayer) return; - if (!CovenCanGetMovingHex.GetBool() && target.IsPlayerCoven()) return; + if (!CovenCanGetMovingHex.GetBool() && target.GetCustomRole().IsCovenTeam()) return; if (target.Is(CustomRoles.Pestilence)) diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index d42481b76..c7ebb68ad 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -67,7 +67,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t { if (killer.CheckDoubleTrigger(target, () => { IllusionedPlayers[killer.PlayerId].Add(target.PlayerId); })) { - if (HasNecronomicon(killer) && !target.IsPlayerCoven()) + if (HasNecronomicon(killer) && !target.GetCustomRole().IsCovenTeam()) { var randomDeathReason = ChangeRandomDeath(); Main.PlayerStates[target.PlayerId].deathReason = randomDeathReason; @@ -105,7 +105,7 @@ public static bool IsNonCovIllusioned(byte target) bool result = false; foreach (var player in IllusionedPlayers.Keys) { - if (IllusionedPlayers[player].Contains(target) && !GetPlayerById(target).IsPlayerCoven()) result = true; + if (IllusionedPlayers[player].Contains(target) && !GetPlayerById(target).GetCustomRole().IsCovenTeam()) result = true; } return result; } @@ -115,7 +115,7 @@ public static bool IsCovIllusioned(byte target) bool result = false; foreach (var player in IllusionedPlayers.Keys) { - if (IllusionedPlayers[player].Contains(target) && GetPlayerById(target).IsPlayerCoven()) result = true; + if (IllusionedPlayers[player].Contains(target) && GetPlayerById(target).GetCustomRole().IsCovenTeam()) result = true; } return result; } diff --git a/Roles/Coven/Jinx.cs b/Roles/Coven/Jinx.cs index 0d4bfbf8d..43b55923d 100644 --- a/Roles/Coven/Jinx.cs +++ b/Roles/Coven/Jinx.cs @@ -144,7 +144,7 @@ or CustomRoles.Bodyguard or CustomRoles.Veteran or CustomRoles.Deputy) return false; - if (killer.IsPlayerCoven() && !CovenCanDieToJinx.GetBool()) return false; + if (killer.GetCustomRole().IsCovenTeam() && !CovenCanDieToJinx.GetBool()) return false; if (jinx.CheckForInvalidMurdering(killer) && jinx.RpcCheckAndMurder(killer, true)) { diff --git a/Roles/Coven/MoonDancer.cs b/Roles/Coven/MoonDancer.cs index cc7d2bb54..51d55fd31 100644 --- a/Roles/Coven/MoonDancer.cs +++ b/Roles/Coven/MoonDancer.cs @@ -158,7 +158,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t if (HasNecronomicon(killer)) { var rd = IRandom.Instance; - if (target.IsPlayerCoven()) + if (target.GetCustomRole().IsCovenTeam()) { killer.Notify(GetString("MoonDancerCantBlastOff")); return false; @@ -188,7 +188,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return true; } } - if (target.IsPlayerCoven() || target.Is(CustomRoles.Enchanted)) + if (target.GetCustomRole().IsCovenTeam()) { BatonPassList[killer.PlayerId].Add(target.PlayerId); killer.Notify(GetString("MoonDancerGiveHelpfulAddon")); @@ -218,7 +218,7 @@ private void DistributeAddOns(PlayerControl md) var addon = addons.RandomElement(); var helpful = GroupedAddons[AddonTypes.Helpful].Where(x => addons.Contains(x)).ToList(); var harmful = GroupedAddons[AddonTypes.Harmful].Where(x => addons.Contains(x)).ToList(); - if (player.IsPlayerCoven() || player.Is(CustomRoles.Enchanted) || (player.Is(CustomRoles.Lovers) && md.Is(CustomRoles.Lovers))) + if (player.GetCustomRole().IsCovenTeam() || (player.Is(CustomRoles.Lovers) && md.Is(CustomRoles.Lovers))) { if (helpful.Count <= 0) { diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index 337731551..f740dad32 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -75,7 +75,7 @@ public override void Add(byte playerId) public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { if (IsRevenge) return true; - if (killer.IsPlayerCoven()) return true; + if (killer.GetCustomRole().IsCovenTeam()) return true; if (!HasNecronomicon(target)) return true; if ((killer.Is(CustomRoles.Retributionist) || killer.Is(CustomRoles.Nemesis)) && !killer.IsAlive()) return true; diff --git a/Roles/Coven/Poisoner.cs b/Roles/Coven/Poisoner.cs index 409b9c123..3f22f4840 100644 --- a/Roles/Coven/Poisoner.cs +++ b/Roles/Coven/Poisoner.cs @@ -69,7 +69,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t { if (killer.CheckDoubleTrigger(target, () => { RoleblockedPlayers[killer.PlayerId].Add(target.PlayerId); })) { - if (HasNecronomicon(killer) && !target.IsPlayerCoven()) + if (HasNecronomicon(killer)) { if (target.GetCustomRole().IsCovenTeam()) { diff --git a/Roles/Coven/PotionMaster.cs b/Roles/Coven/PotionMaster.cs index f430470e5..2d23d06f3 100644 --- a/Roles/Coven/PotionMaster.cs +++ b/Roles/Coven/PotionMaster.cs @@ -244,7 +244,7 @@ public override string GetMark(PlayerControl seer, PlayerControl seen = null, bo public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { if (_Player == null) return string.Empty; - if (BarrierList[_Player.PlayerId].Contains(target.PlayerId) && seer.IsPlayerCoven() && seer.PlayerId != _Player.PlayerId) + if (BarrierList[_Player.PlayerId].Contains(target.PlayerId) && seer.GetCustomRole().IsCovenTeam() && seer.PlayerId != _Player.PlayerId) { return ColorString(GetRoleColor(CustomRoles.PotionMaster), "✚"); } diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index 960c93c32..2f890b7d7 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -250,7 +250,7 @@ public static bool CheckCommond(ref string msg, string command, bool exact = tru } public static bool CanBeConverted(PlayerControl pc) { - return pc != null && (!pc.IsPlayerCoven() && !pc.Is(CustomRoles.Enchanted) && !pc.IsTransformedNeutralApocalypse()) && !pc.Is(CustomRoles.Soulless) && !pc.Is(CustomRoles.Lovers) && !pc.Is(CustomRoles.Loyal) + return pc != null && (!pc.GetCustomRole().IsCovenTeam() && !pc.IsTransformedNeutralApocalypse()) && !pc.Is(CustomRoles.Soulless) && !pc.Is(CustomRoles.Lovers) && !pc.Is(CustomRoles.Loyal) && !((pc.Is(CustomRoles.NiceMini) || pc.Is(CustomRoles.EvilMini)) && Mini.Age < 18) && !(pc.GetCustomSubRoles().Contains(CustomRoles.Hurried) && !Hurried.CanBeConverted.GetBool()); } diff --git a/Roles/Coven/VoodooMaster.cs b/Roles/Coven/VoodooMaster.cs index 7f33f11db..f08c4b2ab 100644 --- a/Roles/Coven/VoodooMaster.cs +++ b/Roles/Coven/VoodooMaster.cs @@ -91,7 +91,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr return false; } private void SetDoll(PlayerControl killer, PlayerControl target) { - if (AbilityLimit > 0 && (!target.IsPlayerCoven() || (target.IsPlayerCoven() && CanDollCoven.GetBool()))) + if (AbilityLimit > 0 && (!target.GetCustomRole().IsCovenTeam() || (target.GetCustomRole().IsCovenTeam() && CanDollCoven.GetBool()))) { Dolls[killer.PlayerId].Add(target.PlayerId); AbilityLimit--; @@ -102,13 +102,13 @@ private void SetDoll(PlayerControl killer, PlayerControl target) { killer.SetKillCooldown(); if (HasNecronomicon(killer)) ReportDeadBodyPatch.CanReport[target.PlayerId] = false; } - else if (target.IsPlayerCoven() && CanDollCoven.GetBool()) killer.Notify(GetString("VoodooMasterNoDollCoven")); + else if (target.GetCustomRole().IsCovenTeam() && CanDollCoven.GetBool()) killer.Notify(GetString("VoodooMasterNoDollCoven")); else killer.Notify(GetString("VoodooMasterNoDollsLeft")); } public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { if (Dolls[_Player.PlayerId].Count < 1) return true; - if (killer.IsPlayerCoven()) return true; + if (killer.GetCustomRole().IsCovenTeam()) return true; PlayerControl ChoosenTarget = GetPlayerById(Dolls[target.PlayerId].Where(x => GetPlayerById(x).IsAlive()).ToList().RandomElement()); @@ -133,7 +133,7 @@ public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerContr { if (!Dolls[_Player.PlayerId].Contains(target.PlayerId)) return false; if (!HasNecronomicon(_Player)) return false; - if (!killer.IsPlayerCoven() || (killer.IsPlayerCoven() && NecroAbilityCanKillCov.GetBool())) + if (!killer.GetCustomRole().IsCovenTeam() || (killer.GetCustomRole().IsCovenTeam() && NecroAbilityCanKillCov.GetBool())) { killer.SetDeathReason(PlayerState.DeathReason.Sacrifice); killer.RpcMurderPlayer(killer); From 35da2df680ead5c6d938790a89456294e8d3b909 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 30 Nov 2024 19:11:02 -0500 Subject: [PATCH 063/101] add new illusionist setting --- Resources/Lang/en_US.json | 1 + Roles/Coven/Illusionist.cs | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index ede39b92f..6827dc01a 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2028,6 +2028,7 @@ "IllusionCooldown": "Illusion Cooldown", "IllusionistMaxIllusions": "Max Illusions", "IllusionistSnitchAffected": "Snitch is Affected by Illusions", + "IllusionistResetIllusionsPerRound": "Reset Illusions After Meeting", "MedusaStoneCooldown": "Stone Cooldown", "MedusaStoneDuration": "Stone Duration", diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index c7ebb68ad..d73e312c2 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -23,6 +23,8 @@ internal class Illusionist : CovenManager private static OptionItem IllusionCooldown; private static OptionItem MaxIllusions; public static OptionItem SnitchCanIllusioned; + private static OptionItem ResetIllusionsPerRound; + private static readonly Dictionary> IllusionedPlayers = []; @@ -35,6 +37,7 @@ public override void SetupCustomOption() MaxIllusions = IntegerOptionItem.Create(Id + 11, "IllusionistMaxIllusions", new(1, 100, 1), 5, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Illusionist]) .SetValueFormat(OptionFormat.Times); SnitchCanIllusioned = BooleanOptionItem.Create(Id + 12, "IllusionistSnitchAffected", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Illusionist]); + ResetIllusionsPerRound = BooleanOptionItem.Create(Id + 13, "IllusionistResetIllusionsPerRound", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Illusionist]); } public override void Init() @@ -121,7 +124,8 @@ public static bool IsCovIllusioned(byte target) } public override void AfterMeetingTasks() { - IllusionedPlayers.Clear(); + if (ResetIllusionsPerRound.GetBool()) + IllusionedPlayers.Clear(); } public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => (IllusionedPlayers.TryGetValue(seer.PlayerId, out var Targets) && Targets.Contains(seen.PlayerId)) ? ColorString(GetRoleColor(CustomRoles.Illusionist), "") : string.Empty; } \ No newline at end of file From bf07163858ae5972cb53298f82abdc8e63bcf91e Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 30 Nov 2024 20:20:50 -0500 Subject: [PATCH 064/101] another attempt to fix ritualist tryhidemsg --- Roles/Coven/Ritualist.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index 2f890b7d7..504dfba30 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -100,11 +100,10 @@ public static bool RitualistMsgCheck(PlayerControl pc, string msg, bool isUI = f { if (TryHideMsg.GetBool()) { - //if (Options.NewHideMsg.GetBool()) ChatManager.SendPreviousMessagesToAll(); - //else GuessManager.TryHideMsg(); TryHideMsgForRitual(); ChatManager.SendPreviousMessagesToAll(); } + else if (pc.AmOwner) SendMessage(msg, 255, pc.GetRealName()); if (RitualLimit[pc.PlayerId] <= 0) { pc.ShowInfoMessage(isUI, GetString("RitualistRitualMax")); From 72be92713a852aee98db9acd314bd4c62376ef04 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 1 Dec 2024 12:11:17 -0500 Subject: [PATCH 065/101] new illusionist settings --- Resources/Lang/en_US.json | 1 + Roles/Coven/Illusionist.cs | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 6827dc01a..468011faa 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2029,6 +2029,7 @@ "IllusionistMaxIllusions": "Max Illusions", "IllusionistSnitchAffected": "Snitch is Affected by Illusions", "IllusionistResetIllusionsPerRound": "Reset Illusions After Meeting", + "IllusionistClearIllusionsWhenDead": "Remove Illusions When Illusionist Dies", "MedusaStoneCooldown": "Stone Cooldown", "MedusaStoneDuration": "Stone Duration", diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index d73e312c2..340d54075 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -24,6 +24,8 @@ internal class Illusionist : CovenManager private static OptionItem MaxIllusions; public static OptionItem SnitchCanIllusioned; private static OptionItem ResetIllusionsPerRound; + private static OptionItem ClearIllusionsWhenDead; + private static readonly Dictionary> IllusionedPlayers = []; @@ -38,6 +40,7 @@ public override void SetupCustomOption() .SetValueFormat(OptionFormat.Times); SnitchCanIllusioned = BooleanOptionItem.Create(Id + 12, "IllusionistSnitchAffected", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Illusionist]); ResetIllusionsPerRound = BooleanOptionItem.Create(Id + 13, "IllusionistResetIllusionsPerRound", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Illusionist]); + ClearIllusionsWhenDead = BooleanOptionItem.Create(Id + 14, "IllusionistClearIllusionsWhenDead", false, TabGroup.CovenRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Illusionist]); } public override void Init() @@ -49,6 +52,7 @@ public override void Add(byte playerId) AbilityLimit = MaxIllusions.GetInt(); IllusionedPlayers[playerId] = []; GetPlayerById(playerId)?.AddDoubleTrigger(); + CustomRoleManager.CheckDeadBodyOthers.Add(OnPlayerDead); } public void SendRPC(PlayerControl player, PlayerControl target) { @@ -127,5 +131,15 @@ public override void AfterMeetingTasks() if (ResetIllusionsPerRound.GetBool()) IllusionedPlayers.Clear(); } + private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool inMeeting) + { + if (!ClearIllusionsWhenDead.GetBool()) return; + foreach (var player in IllusionedPlayers.Keys) + { + if (deadPlayer.PlayerId == player) IllusionedPlayers[player].Clear(); + } + } + + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => (IllusionedPlayers.TryGetValue(seer.PlayerId, out var Targets) && Targets.Contains(seen.PlayerId)) ? ColorString(GetRoleColor(CustomRoles.Illusionist), "") : string.Empty; } \ No newline at end of file From d7b52679218167e3a38ca08036d9d166e62b8a59 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 1 Dec 2024 14:01:54 -0500 Subject: [PATCH 066/101] Update en_US.json --- Resources/Lang/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 468011faa..69478d270 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -996,7 +996,7 @@ "ParanoiaInfoLong": "(Add-ons):\nNot assigned to Neutrals nor Madmates.\nAs the Paranoia, you will be considered as two players in the game to determine when the game ends due to killers having the majority. Additionally, this grants you an extra vote, depending on options.", "MimicInfoLong": "(Add-ons):\nOnly Impostor can become Mimic. When the Mimic is dead, other Impostors will receive a message once a meeting is called. This message will include information on roles which the Mimic killed.", "GuesserInfoLong": "(Add-ons):\nAs a guesser, guess the roles of players in meetings to kill them.\nGuessing the incorrect role kills you instead.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.", - "NecroviewInfoLong": "(Add-ons):\nThe Necroview can see the teams of dead players. The following info will be displayed on the dead player's name while in a meeting:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", + "NecroviewInfoLong": "(Add-ons):\nThe Necroview can see the teams of dead players. The following info will be displayed on the dead player's name while in a meeting:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.\n- The Purple name indicates the Coven", "ReachInfoLong": "(Add-on)\nOnly roles with a kill button can get this add-on. Unlike everyone else, you have the longest kill range possible in the game.", "BaitInfoLong": "(Add-ons):\nWhen the Bait dies, the murderer who killed the Bait will self-report the Bait's body. However, this won't happen when a Scavenger, Cleaner, Swooper, Wraith, Medusa, or Killing Machine kills the Bait. The report may have a delay according to the Host's settings.", "TrapperInfoLong": "(Add-ons):\nWhen Beartrap dies, Beartrap immobilizes killer for a configurable amount of time.", From f7eca49112667b6891b5684bc030c9162cf065e2 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:33:18 -0500 Subject: [PATCH 067/101] run code cleanup, only affected these two roles --- Roles/Coven/Illusionist.cs | 2 +- Roles/Coven/VoodooMaster.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index 340d54075..5bb61856f 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -128,7 +128,7 @@ public static bool IsCovIllusioned(byte target) } public override void AfterMeetingTasks() { - if (ResetIllusionsPerRound.GetBool()) + if (ResetIllusionsPerRound.GetBool()) IllusionedPlayers.Clear(); } private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool inMeeting) diff --git a/Roles/Coven/VoodooMaster.cs b/Roles/Coven/VoodooMaster.cs index f08c4b2ab..d3df71cc3 100644 --- a/Roles/Coven/VoodooMaster.cs +++ b/Roles/Coven/VoodooMaster.cs @@ -90,7 +90,8 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } return false; } - private void SetDoll(PlayerControl killer, PlayerControl target) { + private void SetDoll(PlayerControl killer, PlayerControl target) + { if (AbilityLimit > 0 && (!target.GetCustomRole().IsCovenTeam() || (target.GetCustomRole().IsCovenTeam() && CanDollCoven.GetBool()))) { Dolls[killer.PlayerId].Add(target.PlayerId); From 5c7a13773f07cdcf882e82e1d682f6d1b4dfb106 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Tue, 3 Dec 2024 23:27:46 +0800 Subject: [PATCH 068/101] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20main.cs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.cs b/main.cs index c8e3fb7af..fefcb29ef 100644 --- a/main.cs +++ b/main.cs @@ -47,8 +47,8 @@ public class Main : BasePlugin public static ConfigEntry DebugKeyInput { get; private set; } public const string PluginGuid = "com.0xdrmoe.townofhostenhanced"; - public const string PluginVersion = "2024.1202.220.00050"; // YEAR.MMDD.VERSION.CANARYDEV - public const string PluginDisplayVersion = "2.2.0 Alpha 5"; + public const string PluginVersion = "2024.1203.220.00059"; // YEAR.MMDD.VERSION.CANARYDEV + public const string PluginDisplayVersion = "2.2.0 Alpha 5 Coven"; public const string SupportedVersionAU = "2024.10.29"; // Changed becasue Dark theme works at this version. /******************* Change one of the three variables to true before making a release. *******************/ From b2c69757a7ae82db372b20273dde578d8bf68bdf Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Tue, 3 Dec 2024 23:32:41 +0800 Subject: [PATCH 069/101] 220 Alpha5 Coven --- Modules/CustomRolesHelper.cs | 12 ++++++------ Modules/ExtendedPlayerControl.cs | 6 +++--- Modules/RPC.cs | 2 +- Modules/Utils.cs | 4 ++-- Patches/CheckGameEndPatch.cs | 2 +- Patches/IntroPatch.cs | 1 - Patches/PlayerControlPatch.cs | 1 - Roles/AddOns/Common/Guesser.cs | 2 +- Roles/Core/CustomRoleManager.cs | 2 +- Roles/Crewmate/Captain.cs | 4 ++-- Roles/Crewmate/Deputy.cs | 3 ++- Roles/Crewmate/FortuneTeller.cs | 2 +- Roles/Crewmate/Inspector.cs | 3 +-- Roles/Crewmate/Investigator.cs | 2 +- Roles/Crewmate/Snitch.cs | 4 ++-- Roles/Crewmate/Swapper.cs | 3 +-- Roles/Crewmate/Witness.cs | 1 - Roles/Neutral/Baker.cs | 2 +- Roles/Neutral/Cultist.cs | 6 +++--- Roles/Neutral/CursedSoul.cs | 2 +- Roles/Neutral/Hater.cs | 2 +- Roles/Neutral/Infectious.cs | 10 +++++----- Roles/Neutral/Jackal.cs | 1 - Roles/Neutral/Opportunist.cs | 2 +- Roles/Neutral/Traitor.cs | 2 +- 25 files changed, 38 insertions(+), 43 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 41877746a..8f0b08955 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -303,9 +303,9 @@ CustomRoles.Shapeshifter or public static bool IsCoven(this CustomRoles role) { return role.GetStaticRoleClass().ThisRoleType is - Custom_RoleType.CovenKilling or - Custom_RoleType.CovenPower or - Custom_RoleType.CovenTrickery or + Custom_RoleType.CovenKilling or + Custom_RoleType.CovenPower or + Custom_RoleType.CovenTrickery or Custom_RoleType.CovenUtility; } public static bool IsAbleToBeSidekicked(this CustomRoles role) @@ -334,7 +334,7 @@ CustomRoles.Recruit or CustomRoles.Infected or CustomRoles.Contagious or CustomRoles.Soulless or - CustomRoles.Madmate or + CustomRoles.Madmate or CustomRoles.Enchanted; public static bool IsNotKnightable(this CustomRoles role) @@ -400,7 +400,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c // Only add-ons if (!role.IsAdditionRole() || pc == null) return false; - if (Options.AddonCanBeSettings.TryGetValue(role, out var o) && ((!o.Imp.GetBool() && pc.GetCustomRole().IsImpostor()) || (!o.Neutral.GetBool() && pc.GetCustomRole().IsNeutral()) || (!o.Crew.GetBool() && pc.GetCustomRole().IsCrewmate()) || (!o.Coven.GetBool() && pc.GetCustomRole().IsCoven()))) + if (Options.AddonCanBeSettings.TryGetValue(role, out var o) && ((!o.Imp.GetBool() && pc.GetCustomRole().IsImpostor()) || (!o.Neutral.GetBool() && pc.GetCustomRole().IsNeutral()) || (!o.Crew.GetBool() && pc.GetCustomRole().IsCrewmate()) || (!o.Coven.GetBool() && pc.GetCustomRole().IsCoven()))) return false; // if player already has this addon @@ -456,7 +456,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c break; case CustomRoles.Guesser: - if (Options.GuesserMode.GetBool() && ((pc.GetCustomRole().IsCrewmate() && !Guesser.CrewCanBeGuesser.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Guesser.NeutralCanBeGuesser.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Guesser.ImpCanBeGuesser.GetBool())|| (pc.GetCustomRole().IsCoven() && !Guesser.CovenCanBeGuesser.GetBool()))) + if (Options.GuesserMode.GetBool() && ((pc.GetCustomRole().IsCrewmate() && !Guesser.CrewCanBeGuesser.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Guesser.NeutralCanBeGuesser.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Guesser.ImpCanBeGuesser.GetBool()) || (pc.GetCustomRole().IsCoven() && !Guesser.CovenCanBeGuesser.GetBool()))) return false; if (pc.Is(CustomRoles.EvilGuesser) || pc.Is(CustomRoles.NiceGuesser) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 87b8c1daf..062a7f8a6 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -8,10 +8,10 @@ using TOHE.Roles.AddOns.Common; using TOHE.Roles.AddOns.Impostor; using TOHE.Roles.Core; +using TOHE.Roles.Coven; using TOHE.Roles.Crewmate; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; -using TOHE.Roles.Coven; using UnityEngine; using static TOHE.Translator; @@ -1255,7 +1255,7 @@ public static List GetPlayersInAbilityRangeSorted(this PlayerCont public static bool IsMurderedThisRound(this PlayerControl player) => player.PlayerId.IsMurderedThisRound(); public static bool IsMurderedThisRound(this byte playerId) => Main.MurderedThisRound.Contains(playerId); - + public static bool KnowDeathReason(this PlayerControl seer, PlayerControl target) => (Options.EveryoneCanSeeDeathReason.GetBool() || seer.Is(CustomRoles.Doctor) || seer.Is(CustomRoles.Autopsy) @@ -1322,7 +1322,7 @@ or CustomRoles.Charmed or CustomRoles.Infected or CustomRoles.Contagious or CustomRoles.Egoist - or CustomRoles.Enchanted) + or CustomRoles.Enchanted) && KnowSubRoleTarget(seer, target)) return true; diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 2ec267a40..90ec1c0ec 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -7,10 +7,10 @@ using TOHE.Patches; using TOHE.Roles.AddOns.Impostor; using TOHE.Roles.Core; +using TOHE.Roles.Coven; using TOHE.Roles.Crewmate; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; -using TOHE.Roles.Coven; using static TOHE.Translator; namespace TOHE; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index e2635df37..8c5ae9ddf 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2095,8 +2095,8 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl // Same thing as Trickster but for Illusioned Coven if (seer.IsAlive() && Overseer.IsRevealedPlayer(seer, target) && Illusionist.IsCovIllusioned(target.PlayerId)) { - TargetRoleText = Overseer.GetRandomRole(seer.PlayerId); - TargetRoleText += TaskState.GetTaskState(); + TargetRoleText = Overseer.GetRandomRole(seer.PlayerId); + TargetRoleText += TaskState.GetTaskState(); } if (seer.IsAlive() && Overseer.IsRevealedPlayer(seer, target) && Illusionist.IsNonCovIllusioned(target.PlayerId)) { diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 63a767cc8..94dd32d6f 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -115,7 +115,7 @@ public static bool Prefix() } break; case CustomWinner.Coven: - if (((pc.Is(Custom_Team.Coven) || pc.Is(CustomRoles.Enchanted)) && (countType == CountTypes.Coven || pc.Is(CustomRoles.Soulless))) + if (((pc.Is(Custom_Team.Coven) || pc.Is(CustomRoles.Enchanted)) && (countType == CountTypes.Coven || pc.Is(CustomRoles.Soulless))) || pc.Is(CustomRoles.Enchanted) && !WinnerIds.Contains(pc.PlayerId)) { WinnerIds.Add(pc.PlayerId); diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 255e7f848..57f50e75c 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -1,6 +1,5 @@ using AmongUs.GameOptions; using BepInEx.Unity.IL2CPP.Utils.Collections; -using MonoMod.Cil; using System; using System.IO; using System.Security.Cryptography; diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index b50a79286..bea1a42a4 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -19,7 +19,6 @@ using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Translator; -using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE; diff --git a/Roles/AddOns/Common/Guesser.cs b/Roles/AddOns/Common/Guesser.cs index f306b906a..aead6e0b2 100644 --- a/Roles/AddOns/Common/Guesser.cs +++ b/Roles/AddOns/Common/Guesser.cs @@ -23,7 +23,7 @@ public void SetupCustomOption() CrewCanBeGuesser = BooleanOptionItem.Create(Id + 11, "CrewCanBeGuesser", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); NeutralCanBeGuesser = BooleanOptionItem.Create(Id + 12, "NeutralCanBeGuesser", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); CovenCanBeGuesser = BooleanOptionItem.Create(Id + 16, "CovenCanBeGuesser", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); - GCanGuessAdt = BooleanOptionItem.Create(Id+ 13, "GCanGuessAdt", false, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); + GCanGuessAdt = BooleanOptionItem.Create(Id + 13, "GCanGuessAdt", false, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); GCanGuessTaskDoneSnitch = BooleanOptionItem.Create(Id + 14, "GCanGuessTaskDoneSnitch", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); GTryHideMsg = BooleanOptionItem.Create(Id + 15, "GuesserTryHideMsg", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]) .SetColor(Color.green); diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 283d152d0..73b3cde14 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -5,10 +5,10 @@ using TOHE.Roles.AddOns.Common; using TOHE.Roles.AddOns.Crewmate; using TOHE.Roles.AddOns.Impostor; +using TOHE.Roles.Coven; using TOHE.Roles.Crewmate; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; -using TOHE.Roles.Coven; using TOHE.Roles.Vanilla; namespace TOHE.Roles.Core; diff --git a/Roles/Crewmate/Captain.cs b/Roles/Crewmate/Captain.cs index 5f20aa656..e397e1831 100644 --- a/Roles/Crewmate/Captain.cs +++ b/Roles/Crewmate/Captain.cs @@ -80,8 +80,8 @@ public override bool OnTaskComplete(PlayerControl pc, int completedTaskCount, in (CaptainCanTargetNB.GetBool() && x.GetCustomRole().IsNB()) || (CaptainCanTargetNE.GetBool() && x.GetCustomRole().IsNE()) || (CaptainCanTargetNC.GetBool() && x.GetCustomRole().IsNC()) || - (CaptainCanTargetNK.GetBool() && x.GetCustomRole().IsNeutralKillerTeam()) - || (CaptainCanTargetNA.GetBool() && x.GetCustomRole().IsNA()) || + (CaptainCanTargetNK.GetBool() && x.GetCustomRole().IsNeutralKillerTeam()) + || (CaptainCanTargetNA.GetBool() && x.GetCustomRole().IsNA()) || (CaptainCanTargetCoven.GetBool() && x.GetCustomRole().IsCovenTeam()) )).ToList(); diff --git a/Roles/Crewmate/Deputy.cs b/Roles/Crewmate/Deputy.cs index b338db475..8d6e57c2a 100644 --- a/Roles/Crewmate/Deputy.cs +++ b/Roles/Crewmate/Deputy.cs @@ -50,7 +50,8 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Deputy), GetString("DeputyHandcuffedPlayer"))); target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Deputy), GetString("HandcuffedByDeputy"))); - if (target.GetCustomRole() is not CustomRoles.SerialKiller or CustomRoles.Pursuer or CustomRoles.Deputy or CustomRoles.Deceiver or CustomRoles.Poisoner) { + if (target.GetCustomRole() is not CustomRoles.SerialKiller or CustomRoles.Pursuer or CustomRoles.Deputy or CustomRoles.Deceiver or CustomRoles.Poisoner) + { target.SetKillCooldownV3(DeputyHandcuffCDForTarget.GetFloat()); if (!DisableShieldAnimations.GetBool()) killer.RpcGuardAndKill(target); if (!DisableShieldAnimations.GetBool()) target.RpcGuardAndKill(target); diff --git a/Roles/Crewmate/FortuneTeller.cs b/Roles/Crewmate/FortuneTeller.cs index 174ccb9be..9908d20b5 100644 --- a/Roles/Crewmate/FortuneTeller.cs +++ b/Roles/Crewmate/FortuneTeller.cs @@ -147,7 +147,7 @@ public override bool CheckVote(PlayerControl player, PlayerControl target) { msg = string.Format(GetString("FortuneTellerCheck.TaskDone"), target.GetRealName(), GetString(CustomRolesHelper.AllRoles.Where(role => role.IsEnable() && !role.IsAdditionRole() && role.IsCoven()).ToList().RandomElement().ToString())); } - else + else msg = string.Format(GetString("FortuneTellerCheck.TaskDone"), target.GetRealName(), GetString(target.GetCustomRole().ToString())); } else if (RandomActiveRoles.GetBool()) diff --git a/Roles/Crewmate/Inspector.cs b/Roles/Crewmate/Inspector.cs index 354a2819d..71c22ae39 100644 --- a/Roles/Crewmate/Inspector.cs +++ b/Roles/Crewmate/Inspector.cs @@ -9,7 +9,6 @@ using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; -using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Crewmate; internal class Inspector : RoleBase @@ -240,7 +239,7 @@ public static bool InspectCheckMsg(PlayerControl pc, string msg, bool isUI = fal if ( ( - ((target1.IsPlayerCoven() || target1.Is(CustomRoles.Enchanted) || Illusionist.IsNonCovIllusioned(target1.PlayerId))) + ((target1.IsPlayerCoven() || target1.Is(CustomRoles.Enchanted) || Illusionist.IsNonCovIllusioned(target1.PlayerId))) && (target2.IsPlayerCoven() || target2.Is(CustomRoles.Enchanted) || Illusionist.IsNonCovIllusioned(target2.PlayerId)) ) || diff --git a/Roles/Crewmate/Investigator.cs b/Roles/Crewmate/Investigator.cs index fdf5d0a31..a8524edeb 100644 --- a/Roles/Crewmate/Investigator.cs +++ b/Roles/Crewmate/Investigator.cs @@ -1,7 +1,7 @@ using AmongUs.GameOptions; using Hazel; -using UnityEngine; using TOHE.Roles.Coven; +using UnityEngine; using static TOHE.Options; namespace TOHE.Roles.Crewmate; diff --git a/Roles/Crewmate/Snitch.cs b/Roles/Crewmate/Snitch.cs index ad24cb9f6..9217a6ed9 100644 --- a/Roles/Crewmate/Snitch.cs +++ b/Roles/Crewmate/Snitch.cs @@ -100,7 +100,7 @@ private static bool GetExpose(PlayerControl pc) private static bool IsSnitchTarget(PlayerControl target) => HasEnabled && (target.Is(Custom_Team.Impostor) && !target.Is(CustomRoles.Trickster) || (target.IsNeutralKiller() && CanFindNeutralKiller) || (target.IsNeutralApocalypse() && CanFindNeutralApocalypse) || (target.IsPlayerCoven() && CanFindCoven) || (target.Is(CustomRoles.Madmate) && CanFindMadmate) || (target.Is(CustomRoles.Rascal) && CanFindMadmate)); - + private void CheckTask(PlayerControl snitch) { if (!snitch.IsAlive() || snitch.Is(CustomRoles.Madmate)) return; @@ -180,7 +180,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl pc) foreach (var target in Main.AllAlivePlayerControls) { if (!IsSnitchTarget(target) || !(Illusionist.IsNonCovIllusioned(target.PlayerId) && Illusionist.SnitchCanIllusioned.GetBool())) continue; - + var targetId = target.PlayerId; if (!TargetList.Contains(targetId)) diff --git a/Roles/Crewmate/Swapper.cs b/Roles/Crewmate/Swapper.cs index be4ce2a4d..2c7b8b092 100644 --- a/Roles/Crewmate/Swapper.cs +++ b/Roles/Crewmate/Swapper.cs @@ -4,12 +4,11 @@ using System.Text.RegularExpressions; using TOHE.Modules.ChatManager; using TOHE.Roles.Core; +using TOHE.Roles.Coven; using UnityEngine; using static TOHE.CheckForEndVotingPatch; using static TOHE.Translator; using static TOHE.Utils; -using static UnityEngine.GraphicsBuffer; -using TOHE.Roles.Coven; namespace TOHE.Roles.Crewmate; diff --git a/Roles/Crewmate/Witness.cs b/Roles/Crewmate/Witness.cs index e28755c49..6b870e452 100644 --- a/Roles/Crewmate/Witness.cs +++ b/Roles/Crewmate/Witness.cs @@ -4,7 +4,6 @@ using UnityEngine; using static TOHE.Options; using static TOHE.Translator; -using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Crewmate; diff --git a/Roles/Neutral/Baker.cs b/Roles/Neutral/Baker.cs index ba9129693..86f5c32d3 100644 --- a/Roles/Neutral/Baker.cs +++ b/Roles/Neutral/Baker.cs @@ -287,7 +287,7 @@ public override void AfterMeetingTasks() } public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { - if (lowLoad || ( !AllHasBread(player) && !TransformNoMoreBread.GetBool()) || player.Is(CustomRoles.Famine)) return; + if (lowLoad || (!AllHasBread(player) && !TransformNoMoreBread.GetBool()) || player.Is(CustomRoles.Famine)) return; if (TransformNoMoreBread.GetBool() && BreadedPlayerCount(player.PlayerId).Item1 < Main.AllAlivePlayerControls.Where(x => !x.IsNeutralApocalypse()).Count()) return; player.RpcChangeRoleBasis(CustomRoles.Famine); diff --git a/Roles/Neutral/Cultist.cs b/Roles/Neutral/Cultist.cs index c80731ca4..08b7a046b 100644 --- a/Roles/Neutral/Cultist.cs +++ b/Roles/Neutral/Cultist.cs @@ -103,10 +103,10 @@ public static bool KnowRole(PlayerControl player, PlayerControl target) public override string GetProgressText(byte playerid, bool cooms) => Utils.ColorString(AbilityLimit >= 1 ? Utils.GetRoleColor(CustomRoles.Cultist).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); public static bool CanBeCharmed(PlayerControl pc) { - return pc != null && (pc.GetCustomRole().IsCrewmate() || pc.GetCustomRole().IsImpostor() || + return pc != null && (pc.GetCustomRole().IsCrewmate() || pc.GetCustomRole().IsImpostor() || (CanCharmNeutral.GetBool() && pc.GetCustomRole().IsNeutral()) || - (CanCharmCoven.GetBool() && pc.GetCustomRole().IsCoven())) && !pc.Is(CustomRoles.Charmed) - && !pc.Is(CustomRoles.Admired) && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Infectious) + (CanCharmCoven.GetBool() && pc.GetCustomRole().IsCoven())) && !pc.Is(CustomRoles.Charmed) + && !pc.Is(CustomRoles.Admired) && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Infectious) && !pc.Is(CustomRoles.Virus) && !pc.Is(CustomRoles.Cultist) && !(pc.GetCustomSubRoles().Contains(CustomRoles.Hurried) && !Hurried.CanBeConverted.GetBool()); } diff --git a/Roles/Neutral/CursedSoul.cs b/Roles/Neutral/CursedSoul.cs index ab6e71dd1..18c105aaa 100644 --- a/Roles/Neutral/CursedSoul.cs +++ b/Roles/Neutral/CursedSoul.cs @@ -110,7 +110,7 @@ public override string PlayerKnowTargetColor(PlayerControl seer, PlayerControl t public override string GetProgressText(byte id, bool cooms) => Utils.ColorString(CurseLimit >= 1 ? Utils.GetRoleColor(CustomRoles.CursedSoul) : Color.gray, $"({CurseLimit})"); private static bool CanBeSoulless(PlayerControl pc) { - return pc != null && (pc.GetCustomRole().IsCrewmate() || pc.GetCustomRole().IsImpostor() || + return pc != null && (pc.GetCustomRole().IsCrewmate() || pc.GetCustomRole().IsImpostor() || (CanCurseNeutral.GetBool() && pc.GetCustomRole().IsNeutral()) || (CanCurseCoven.GetBool() && pc.GetCustomRole().IsCoven())) && !pc.Is(CustomRoles.Soulless) && !pc.Is(CustomRoles.Admired) && !pc.Is(CustomRoles.Loyal); } diff --git a/Roles/Neutral/Hater.cs b/Roles/Neutral/Hater.cs index c80718d81..da0da524a 100644 --- a/Roles/Neutral/Hater.cs +++ b/Roles/Neutral/Hater.cs @@ -120,7 +120,7 @@ CustomRoles.Sidekick or CustomRoles.Jackal or CustomRoles.Virus or CustomRoles.Infectious or - CustomRoles.Admirer or + CustomRoles.Admirer or CustomRoles.Ritualist => true, diff --git a/Roles/Neutral/Infectious.cs b/Roles/Neutral/Infectious.cs index b2d19dc65..f1a105331 100644 --- a/Roles/Neutral/Infectious.cs +++ b/Roles/Neutral/Infectious.cs @@ -168,12 +168,12 @@ public static bool InfectedKnowColorOthersInfected(PlayerControl player, PlayerC public static bool CanBeBitten(PlayerControl pc) { - return pc != null && (pc.GetCustomRole().IsCrewmate() - || pc.GetCustomRole().IsImpostor() + return pc != null && (pc.GetCustomRole().IsCrewmate() + || pc.GetCustomRole().IsImpostor() || pc.GetCustomRole().IsNK() - || pc.GetCustomRole().IsCoven()) && !pc.Is(CustomRoles.Infected) - && !pc.Is(CustomRoles.Admired) - && !pc.Is(CustomRoles.Loyal) + || pc.GetCustomRole().IsCoven()) && !pc.Is(CustomRoles.Infected) + && !pc.Is(CustomRoles.Admired) + && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Cultist) && !pc.Is(CustomRoles.Enchanted) && !pc.Is(CustomRoles.Infectious) && !pc.Is(CustomRoles.Virus); diff --git a/Roles/Neutral/Jackal.cs b/Roles/Neutral/Jackal.cs index 70f3d4d07..9d2db0a2a 100644 --- a/Roles/Neutral/Jackal.cs +++ b/Roles/Neutral/Jackal.cs @@ -1,5 +1,4 @@ using AmongUs.GameOptions; -using TOHE.Roles.AddOns.Crewmate; using TOHE.Roles.Core; using UnityEngine; using static TOHE.Options; diff --git a/Roles/Neutral/Opportunist.cs b/Roles/Neutral/Opportunist.cs index e1d7ce950..8ce0a2beb 100644 --- a/Roles/Neutral/Opportunist.cs +++ b/Roles/Neutral/Opportunist.cs @@ -9,7 +9,7 @@ internal class Opportunist : RoleBase private const int Id = 13300; private static readonly HashSet PlayerIds = []; public static bool HasEnabled = PlayerIds.Any(); - + public override CustomRoles ThisRoleBase => OpportunistCanUseVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralBenign; //==================================================================\\ diff --git a/Roles/Neutral/Traitor.cs b/Roles/Neutral/Traitor.cs index 107cff98a..27a0c7aca 100644 --- a/Roles/Neutral/Traitor.cs +++ b/Roles/Neutral/Traitor.cs @@ -58,7 +58,7 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - + public override bool CanUseKillButton(PlayerControl pc) => true; public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); public override bool CanUseSabotage(PlayerControl pc) => CanUsesSabotage.GetBool(); From 6923f8a44e15deaf6cae497bb38e0f6fa0268918 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Tue, 3 Dec 2024 23:35:36 +0800 Subject: [PATCH 070/101] Resolve build issue --- Roles/Impostor/DoubleAgent.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Roles/Impostor/DoubleAgent.cs b/Roles/Impostor/DoubleAgent.cs index 8a4ec1e9a..e2bdb0cf3 100644 --- a/Roles/Impostor/DoubleAgent.cs +++ b/Roles/Impostor/DoubleAgent.cs @@ -8,6 +8,7 @@ using UnityEngine; using static TOHE.Options; using static TOHE.Translator; +using static TOHE.Utils; namespace TOHE.Roles.Impostor; internal class DoubleAgent : RoleBase From e975b0d1db81b0e52c186267913a575860533190 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Tue, 3 Dec 2024 23:48:08 +0800 Subject: [PATCH 071/101] Force use utf8 encoding --- TOHE.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/TOHE.csproj b/TOHE.csproj index b5638002c..64e468001 100644 --- a/TOHE.csproj +++ b/TOHE.csproj @@ -11,6 +11,7 @@ Debug;Release;Canary true True + 65001 From 7bb4b20787079eea32e58d883cbadd0feff2c2d1 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Wed, 4 Dec 2024 17:02:24 -0500 Subject: [PATCH 072/101] where did the neutral HM spawn from wtf --- Roles/Neutral/HexMaster.cs | 290 ------------------------------------- 1 file changed, 290 deletions(-) delete mode 100644 Roles/Neutral/HexMaster.cs diff --git a/Roles/Neutral/HexMaster.cs b/Roles/Neutral/HexMaster.cs deleted file mode 100644 index 88e156e48..000000000 --- a/Roles/Neutral/HexMaster.cs +++ /dev/null @@ -1,290 +0,0 @@ -using AmongUs.GameOptions; -using Hazel; -using System.Text; -using UnityEngine; -using static TOHE.Options; -using static TOHE.Translator; - - -namespace TOHE.Roles.Neutral; - -internal class HexMaster : RoleBase -{ - //===========================SETUP================================\\ - private const int Id = 16400; - private static readonly HashSet playerIdList = []; - public static bool HasEnabled => playerIdList.Any(); - public override bool IsDesyncRole => true; - public override CustomRoles ThisRoleBase => CustomRoles.Impostor; - public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralKilling; - //==================================================================\\ - - private static OptionItem ModeSwitchAction; - private static OptionItem HexesLookLikeSpells; - private static OptionItem HasImpostorVision; - - private static readonly Dictionary HexMode = []; - private static readonly Dictionary> HexedPlayer = []; - - private static readonly Color RoleColorHex = Utils.GetRoleColor(CustomRoles.HexMaster); - private static readonly Color RoleColorSpell = Utils.GetRoleColor(CustomRoles.Impostor); - - private enum SwitchTriggerList - { - TriggerKill, - TriggerVent, - TriggerDouble, - }; - private static SwitchTriggerList NowSwitchTrigger; - - public override void SetupCustomOption() - { - SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.HexMaster, 1, zeroOne: false); - ModeSwitchAction = StringOptionItem.Create(Id + 10, GeneralOption.ModeSwitchAction, EnumHelper.GetAllNames(), 2, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); - HexesLookLikeSpells = BooleanOptionItem.Create(Id + 11, "HexesLookLikeSpells", false, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); - HasImpostorVision = BooleanOptionItem.Create(Id + 12, GeneralOption.ImpostorVision, true, TabGroup.NeutralRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); - } - public override void Init() - { - playerIdList.Clear(); - HexMode.Clear(); - HexedPlayer.Clear(); - } - public override void Add(byte playerId) - { - playerIdList.Add(playerId); - - HexMode.Add(playerId, false); - HexedPlayer.Add(playerId, []); - NowSwitchTrigger = (SwitchTriggerList)ModeSwitchAction.GetValue(); - - var pc = Utils.GetPlayerById(playerId); - pc.AddDoubleTrigger(); - } - - private static void SendRPC(bool doHex, byte hexId, byte target = 255) - { - if (doHex) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.DoHex, SendOption.Reliable, -1); - writer.Write(hexId); - writer.Write(target); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - else - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetKillOrSpell, SendOption.Reliable, -1); - writer.Write(hexId); - writer.Write(HexMode[hexId]); - AmongUsClient.Instance.FinishRpcImmediately(writer); - - } - } - public static void ReceiveRPC(MessageReader reader, bool doHex) - { - if (doHex) - { - var hexmaster = reader.ReadByte(); - var hexedId = reader.ReadByte(); - if (hexedId != 255) - { - HexedPlayer[hexmaster].Add(hexedId); - } - else - { - HexedPlayer[hexmaster].Clear(); - } - } - else - { - byte playerId = reader.ReadByte(); - HexMode[playerId] = reader.ReadBoolean(); - } - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); - - public override bool CanUseKillButton(PlayerControl pc) => true; - public override bool CanUseImpostorVentButton(PlayerControl pc) => true; - - private static bool IsHexMode(byte playerId) - { - return HexMode.ContainsKey(playerId) && HexMode[playerId]; - } - private static void SwitchHexMode(byte playerId, bool kill) - { - bool needSwitch = false; - switch (NowSwitchTrigger) - { - case SwitchTriggerList.TriggerKill: - needSwitch = kill; - break; - case SwitchTriggerList.TriggerVent: - needSwitch = !kill; - break; - } - if (needSwitch) - { - HexMode[playerId] = !HexMode[playerId]; - SendRPC(false, playerId); - Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(playerId)); - } - } - private static bool IsHexed(byte target) - { - foreach (var hexmaster in playerIdList) - { - if (HexedPlayer[hexmaster].Contains(target)) return true; - } - return false; - } - private static void SetHexed(PlayerControl killer, PlayerControl target) - { - if (!IsHexed(target.PlayerId)) - { - HexedPlayer[killer.PlayerId].Add(target.PlayerId); - SendRPC(true, killer.PlayerId, target.PlayerId); - //キルクールの適正化 - killer.SetKillCooldown(); - } - } - public override void AfterMeetingTasks() - { - foreach (var hexmaster in playerIdList) - { - HexedPlayer[hexmaster].Clear(); - SendRPC(true, hexmaster); - } - } - public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) - { - if (target.IsTransformedNeutralApocalypse()) return false; - - if (NowSwitchTrigger == SwitchTriggerList.TriggerDouble) - { - return killer.CheckDoubleTrigger(target, () => { SetHexed(killer, target); }); - } - if (!IsHexMode(killer.PlayerId)) - { - SwitchHexMode(killer.PlayerId, true); - //キルモードなら通常処理に戻る - return true; - } - SetHexed(killer, target); - - //スペルに失敗してもスイッチ判定 - SwitchHexMode(killer.PlayerId, true); - //キル処理終了させる - return false; - } - public override void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) - { - foreach (var id in exileIds) - { - if (HexedPlayer.ContainsKey(id)) - HexedPlayer[id].Clear(); - } - var hexedIdList = new List(); - foreach (var pc in Main.AllAlivePlayerControls) - { - var dic = HexedPlayer.Where(x => x.Value.Contains(pc.PlayerId)); - if (!dic.Any()) continue; - var whichId = dic.FirstOrDefault().Key; - var hexmaster = Utils.GetPlayerById(whichId); - if (hexmaster != null && hexmaster.IsAlive()) - { - if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) - { - pc.SetRealKiller(hexmaster); - hexedIdList.Add(pc.PlayerId); - } - } - else - { - Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); - } - } - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Hex, [.. hexedIdList]); - RemoveHexedPlayer(); - } - public override void OnPlayerExiled(PlayerControl player, NetworkedPlayerInfo exiled) - { - RemoveHexedPlayer(); - } - private static void RemoveHexedPlayer() - { - foreach (var hexmaster in playerIdList) - { - HexedPlayer[hexmaster].Clear(); - SendRPC(true, hexmaster); - } - } - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - if (NowSwitchTrigger is SwitchTriggerList.TriggerVent) - { - SwitchHexMode(pc.PlayerId, false); - } - } - public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) - { - if (isForMeeting && IsHexed(target.PlayerId)) - { - if (!HexesLookLikeSpells.GetBool()) - { - return Utils.ColorString(RoleColorHex, "乂"); - } - else - { - return Utils.ColorString(RoleColorSpell, "†"); - } - } - return string.Empty; - } - - public override string GetLowerText(PlayerControl hexmaster, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) - { - if (!hexmaster.IsAlive() || isForMeeting || hexmaster != seen) return string.Empty; - - var str = new StringBuilder(); - if (isForHud) - { - str.Append($"{GetString("WitchCurrentMode")}: "); - } - else - { - str.Append($"{GetString("Mode")}: "); - } - if (NowSwitchTrigger == SwitchTriggerList.TriggerDouble) - { - str.Append(GetString("HexMasterModeDouble")); - } - else - { - str.Append(IsHexMode(hexmaster.PlayerId) ? GetString("HexMasterModeHex") : GetString("HexMasterModeKill")); - } - - return str.ToString(); - } - - public override void SetAbilityButtonText(HudManager hud, byte playerid) - { - if (IsHexMode(playerid) && NowSwitchTrigger != SwitchTriggerList.TriggerDouble) - { - hud.KillButton.OverrideText($"{GetString("HexButtonText")}"); - } - else - { - hud.KillButton.OverrideText($"{GetString("KillButtonText")}"); - } - } - - public override void Remove(byte playerId) - { - if (HexedPlayer.ContainsKey(playerId)) - { - HexedPlayer[playerId].Clear(); - SendRPC(true, playerId); - } - } -} From 22bbccc9b1fd33f085f907c51e9b200c4ad752cb Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Wed, 4 Dec 2024 17:09:57 -0500 Subject: [PATCH 073/101] remove hasenabled and playeridlist --- Roles/Core/CustomRoleManager.cs | 4 ++-- Roles/Coven/Conjurer.cs | 1 - Roles/Coven/CovenLeader.cs | 1 - Roles/Coven/HexMaster.cs | 12 ++++-------- Roles/Coven/Illusionist.cs | 1 - Roles/Coven/Jinx.cs | 1 - Roles/Coven/Medusa.cs | 1 - Roles/Coven/MoonDancer.cs | 1 - Roles/Coven/Necromancer.cs | 4 ---- Roles/Coven/PotionMaster.cs | 1 - Roles/Coven/Ritualist.cs | 1 - Roles/Coven/Sacrifist.cs | 1 - Roles/Coven/VoodooMaster.cs | 1 - 13 files changed, 6 insertions(+), 24 deletions(-) diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index 00a87700a..0595678f5 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -115,8 +115,8 @@ public static void BuildCustomGameOptions(this PlayerControl player, ref IGameOp if (Deathpact.HasEnabled) Deathpact.SetDeathpactVision(player, opt); if (Spiritcaller.HasEnabled) Spiritcaller.ReduceVision(opt, player); if (CustomRoles.Pitfall.RoleExist()) Pitfall.SetPitfallTrapVision(opt, player); - if (Medusa.HasEnabled) Medusa.SetStoned(player, opt); - if (Sacrifist.HasEnabled) Sacrifist.SetVision(player, opt); + if (CustomRoles.Medusa.RoleExist()) Medusa.SetStoned(player, opt); + if (CustomRoles.Sacrifist.RoleExist()) Sacrifist.SetVision(player, opt); var playerSubRoles = player.GetCustomSubRoles(); diff --git a/Roles/Coven/Conjurer.cs b/Roles/Coven/Conjurer.cs index c86e7e4e6..be46ac486 100644 --- a/Roles/Coven/Conjurer.cs +++ b/Roles/Coven/Conjurer.cs @@ -18,7 +18,6 @@ private enum ConjState } //===========================SETUP================================\\ private const int Id = 30300; - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Conjurer); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenKilling; diff --git a/Roles/Coven/CovenLeader.cs b/Roles/Coven/CovenLeader.cs index 59916c4c7..fed5f2e93 100644 --- a/Roles/Coven/CovenLeader.cs +++ b/Roles/Coven/CovenLeader.cs @@ -11,7 +11,6 @@ internal class CovenLeader : CovenManager { //===========================SETUP================================\\ private const int Id = 30900; - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.CovenLeader); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenPower; diff --git a/Roles/Coven/HexMaster.cs b/Roles/Coven/HexMaster.cs index a1dc70327..c83c7fe9f 100644 --- a/Roles/Coven/HexMaster.cs +++ b/Roles/Coven/HexMaster.cs @@ -14,8 +14,6 @@ internal class HexMaster : CovenManager { //===========================SETUP================================\\ private const int Id = 16400; - private static readonly HashSet playerIdList = []; - public static bool HasEnabled => playerIdList.Any(); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenKilling; @@ -62,7 +60,6 @@ public override void SetupCustomOption() } public override void Init() { - playerIdList.Clear(); HexedPlayer.Clear(); CurrentHexedPlayer = byte.MaxValue; LastHexedPlayer = byte.MaxValue; @@ -71,7 +68,6 @@ public override void Init() } public override void Add(byte playerId) { - playerIdList.Add(playerId); HexedPlayer.Add(playerId, []); // NowSwitchTrigger = (SwitchTriggerList)ModeSwitchAction.GetValue(); @@ -155,7 +151,7 @@ private static void SwitchHexMode(byte playerId, bool kill) */ private static bool IsHexed(byte target) { - foreach (var hexmaster in playerIdList) + foreach (var hexmaster in HexedPlayer.Keys) { if (HexedPlayer[hexmaster].Contains(target)) return true; } @@ -227,7 +223,7 @@ public override void OnReportDeadBody(PlayerControl reported, NetworkedPlayerInf } public override void AfterMeetingTasks() { - foreach (var hexmaster in playerIdList) + foreach (var hexmaster in HexedPlayer.Keys) { HexedPlayer[hexmaster].Clear(); SendRPC(true, hexmaster); @@ -270,7 +266,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } private static void SetHexedNecronomicon(PlayerControl killer, PlayerControl target) { - if (!HasEnabled) return; + if (!CustomRoles.HexMaster.RoleExist()) return; if (target.GetCustomRole().IsCovenTeam()) { killer.Notify(GetString("CovenDontKillOtherCoven")); @@ -357,7 +353,7 @@ public override void OnPlayerExiled(PlayerControl player, NetworkedPlayerInfo ex } private static void RemoveHexedPlayer() { - foreach (var hexmaster in playerIdList) + foreach (var hexmaster in HexedPlayer.Keys) { HexedPlayer[hexmaster].Clear(); SendRPC(true, hexmaster); diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index 5bb61856f..313b678fa 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -14,7 +14,6 @@ internal class Illusionist : CovenManager { //===========================SETUP================================\\ private const int Id = 30400; - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Illusionist); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenTrickery; diff --git a/Roles/Coven/Jinx.cs b/Roles/Coven/Jinx.cs index 43b55923d..75120a0e5 100644 --- a/Roles/Coven/Jinx.cs +++ b/Roles/Coven/Jinx.cs @@ -12,7 +12,6 @@ internal class Jinx : CovenManager { //===========================SETUP================================\\ private const int Id = 16800; - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Jinx); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenKilling; diff --git a/Roles/Coven/Medusa.cs b/Roles/Coven/Medusa.cs index e00e3495f..204269128 100644 --- a/Roles/Coven/Medusa.cs +++ b/Roles/Coven/Medusa.cs @@ -12,7 +12,6 @@ internal class Medusa : CovenManager { //===========================SETUP================================\\ private const int Id = 17000; - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Medusa); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; diff --git a/Roles/Coven/MoonDancer.cs b/Roles/Coven/MoonDancer.cs index 51d55fd31..e12f620e5 100644 --- a/Roles/Coven/MoonDancer.cs +++ b/Roles/Coven/MoonDancer.cs @@ -17,7 +17,6 @@ internal class MoonDancer : CovenManager { //===========================SETUP================================\\ private const int Id = 30500; - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.MoonDancer); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index f740dad32..14780e5e6 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -11,8 +11,6 @@ internal class Necromancer : CovenManager { //===========================SETUP================================\\ private const int Id = 17100; - private static readonly HashSet playerIdList = []; - public static bool HasEnabled => playerIdList.Any(); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; @@ -52,7 +50,6 @@ public override void SetupCustomOption() } public override void Init() { - playerIdList.Clear(); IsRevenge = false; Success = false; Killer = null; @@ -63,7 +60,6 @@ public override void Init() } public override void Add(byte playerId) { - playerIdList.Add(playerId); Timer = RevengeTime.GetInt(); UsedRoles[playerId] = []; } diff --git a/Roles/Coven/PotionMaster.cs b/Roles/Coven/PotionMaster.cs index 2d23d06f3..96beaba06 100644 --- a/Roles/Coven/PotionMaster.cs +++ b/Roles/Coven/PotionMaster.cs @@ -13,7 +13,6 @@ internal class PotionMaster : CovenManager { //===========================SETUP================================\\ private const int Id = 17700; - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.PotionMaster); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index 504dfba30..fbe0f86a8 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -16,7 +16,6 @@ internal class Ritualist : CovenManager { //===========================SETUP================================\\ private const int Id = 30800; - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Ritualist); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenPower; diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index ebf317174..ee90748e7 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -14,7 +14,6 @@ internal class Sacrifist : CovenManager { //===========================SETUP================================\\ private const int Id = 30600; - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Sacrifist); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Shapeshifter; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; diff --git a/Roles/Coven/VoodooMaster.cs b/Roles/Coven/VoodooMaster.cs index d3df71cc3..75dcd9696 100644 --- a/Roles/Coven/VoodooMaster.cs +++ b/Roles/Coven/VoodooMaster.cs @@ -12,7 +12,6 @@ internal class VoodooMaster : CovenManager { //===========================SETUP================================\\ private const int Id = 30700; - public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.VoodooMaster); public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; public override Custom_RoleType ThisRoleType => Custom_RoleType.CovenUtility; From ce4731466d13974795d36b9a80838e7327b691a1 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 5 Dec 2024 06:47:25 -0500 Subject: [PATCH 074/101] remove Dreamweaver and Sorceress from CustomRoles since I can't code them rn --- main.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/main.cs b/main.cs index 3260781ed..9d46132c6 100644 --- a/main.cs +++ b/main.cs @@ -900,7 +900,6 @@ public enum CustomRoles Coven, Conjurer, CovenLeader, - Dreamweaver, HexMaster, Illusionist, Jinx, @@ -911,7 +910,6 @@ public enum CustomRoles PotionMaster, Ritualist, Sacrifist, - Sorceress, VoodooMaster, //two-way camp From c5832d4cb3fcbb8736472420f830061b1a1d5495 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:41:30 -0500 Subject: [PATCH 075/101] Update Necromancer.cs --- Roles/Coven/Necromancer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index 14780e5e6..bcd50d0e1 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -188,6 +188,7 @@ CustomRoles.VengefulRomantic or CustomRoles.CursedSoul or CustomRoles.Provocateur or CustomRoles.Specter or + CustomRoles.GameMaster or CustomRoles.Sunnyboy || (role == CustomRoles.Workaholic && Workaholic.WorkaholicVisibleToEveryone.GetBool()) || (role == CustomRoles.Mayor && Mayor.MayorRevealWhenDoneTasks.GetBool()); From bdb586eb9361dc49013ff8a9da49e291569af571 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:43:42 -0500 Subject: [PATCH 076/101] im stupid --- Roles/Coven/Necromancer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index bcd50d0e1..9a0524b9e 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -188,7 +188,7 @@ CustomRoles.VengefulRomantic or CustomRoles.CursedSoul or CustomRoles.Provocateur or CustomRoles.Specter or - CustomRoles.GameMaster or + CustomRoles.GM or CustomRoles.Sunnyboy || (role == CustomRoles.Workaholic && Workaholic.WorkaholicVisibleToEveryone.GetBool()) || (role == CustomRoles.Mayor && Mayor.MayorRevealWhenDoneTasks.GetBool()); From 1c69c67664edb77d6a19cca610a973206b506a5f Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:20:53 -0500 Subject: [PATCH 077/101] prevent moondancer and ritualist null errors update necromancer blacklist --- Roles/Coven/MoonDancer.cs | 5 +++-- Roles/Coven/Necromancer.cs | 5 ++++- Roles/Coven/Ritualist.cs | 12 +++++++----- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Roles/Coven/MoonDancer.cs b/Roles/Coven/MoonDancer.cs index e12f620e5..fecce42b8 100644 --- a/Roles/Coven/MoonDancer.cs +++ b/Roles/Coven/MoonDancer.cs @@ -205,8 +205,9 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { KillBlastedOff(); - var md = Utils.GetPlayerListByRole(CustomRoles.MoonDancer).First(); - DistributeAddOns(md); + foreach (var md in BatonPassList.Keys) { + DistributeAddOns(GetPlayerById(md)); + } } private void DistributeAddOns(PlayerControl md) { diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index 9a0524b9e..595398dcb 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -137,7 +137,6 @@ public override void UnShapeShiftButton(PlayerControl nm) nm.RpcSetCustomRole(role); nm.GetRoleClass()?.OnAdd(nm.PlayerId); nm.RpcSetCustomRole(CustomRoles.Enchanted); - nm.AddInSwitchAddons(nm, CustomRoles.Enchanted); nm.SyncSettings(); Main.PlayerStates[nm.PlayerId].InitTask(nm); nm.RpcGuardAndKill(nm); @@ -188,7 +187,11 @@ CustomRoles.VengefulRomantic or CustomRoles.CursedSoul or CustomRoles.Provocateur or CustomRoles.Specter or + // Just in case CustomRoles.GM or + CustomRoles.Killer or + CustomRoles.Coven or + CustomRoles.Apocalypse or CustomRoles.Sunnyboy || (role == CustomRoles.Workaholic && Workaholic.WorkaholicVisibleToEveryone.GetBool()) || (role == CustomRoles.Mayor && Mayor.MayorRevealWhenDoneTasks.GetBool()); diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index fbe0f86a8..1bed9b81c 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -178,12 +178,14 @@ private static void TryHideMsgForRitual() } public override void AfterMeetingTasks() { - var rit = Utils.GetPlayerListByRole(CustomRoles.Ritualist).First(); - foreach (var pc in EnchantedPlayers[rit.PlayerId]) - { - GetPlayerById(pc).RpcSetCustomRole(CustomRoles.Enchanted); + foreach (var rit in EnchantedPlayers.Keys) + { + foreach (var pc in EnchantedPlayers[rit]) + { + GetPlayerById(pc).RpcSetCustomRole(CustomRoles.Enchanted); + } + EnchantedPlayers[rit].Clear(); } - EnchantedPlayers[rit.PlayerId].Clear(); } private static bool MsgToPlayerAndRole(string msg, out byte id, out CustomRoles role, out string error) { From 5b45ed3af335c68067ee88db1dcd4313cf275190 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 6 Dec 2024 19:55:40 -0500 Subject: [PATCH 078/101] update addon conflicts --- Modules/CustomRolesHelper.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index ce30abf3c..f39f08b72 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -475,12 +475,12 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || (pc.Is(CustomRoles.Solsticer) && !Solsticer.SolsticerCanGuess.GetBool()) || (pc.Is(CustomRoles.God) && !God.CanGuess.GetBool())) return false; //Based on guess manager - if ((pc.GetCustomRole().IsCrewmate() && !Guesser.CrewCanBeGuesser.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Guesser.NeutralCanBeGuesser.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Guesser.ImpCanBeGuesser.GetBool())) + if ((pc.GetCustomRole().IsCrewmate() && !Guesser.CrewCanBeGuesser.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Guesser.NeutralCanBeGuesser.GetBool()) || (pc.GetCustomRole().IsImpostor() && !Guesser.ImpCanBeGuesser.GetBool()) || (pc.GetCustomRole().IsCoven() && !Guesser.CovenCanBeGuesser.GetBool())) return false; break; case CustomRoles.Mundane: - if (pc.HasImpKillButton() || !Utils.HasTasks(pc.Data, false) || pc.GetCustomRole().IsTasklessCrewmate() || pc.Is(Custom_Team.Impostor)) + if (pc.HasImpKillButton() || !Utils.HasTasks(pc.Data, false) || pc.GetCustomRole().IsTasklessCrewmate() || pc.Is(Custom_Team.Impostor) || pc.Is(Custom_Team.Coven)) return false; if ((pc.GetCustomRole().IsCrewmate() && !Mundane.CanBeOnCrew.GetBool()) || (pc.GetCustomRole().IsNeutral() && !Mundane.CanBeOnNeutral.GetBool())) return false; @@ -797,7 +797,7 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c || pc.Is(CustomRoles.Admirer) || pc.Is(CustomRoles.GuardianAngelTOHE)) return false; - if (pc.GetCustomRole().IsNeutral() || pc.GetCustomRole().IsMadmate() || pc.IsAnySubRole(sub => sub.IsConverted())) + if (pc.GetCustomRole().IsNeutral() || pc.GetCustomRole().IsMadmate() || pc.IsAnySubRole(sub => sub.IsConverted()) || pc.GetCustomRole().IsCoven()) return false; if ((pc.GetCustomRole().IsImpostor() && !Egoist.ImpCanBeEgoist.GetBool()) || (pc.GetCustomRole().IsCrewmate() && !Egoist.CrewCanBeEgoist.GetBool())) return false; @@ -999,7 +999,6 @@ public static bool CheckAddonConfilct(CustomRoles role, PlayerControl pc, bool c if (pc.Is(CustomRoles.Ventguard) || pc.Is(CustomRoles.Circumvent) || pc.Is(CustomRoles.Jester) && Jester.CantMoveInVents.GetBool() - || pc.Is(CustomRoles.Medusa) // Medusa needs to be able to vent to use ability ) return false; break; From 8435b36efa44372e103bf740c6ba65015a3070ba Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 7 Dec 2024 15:19:09 -0500 Subject: [PATCH 079/101] Necromancer enhancements no longer needs Enchanted add-on upon necromancy no longer sees teammates of roles --- Modules/AntiBlackout.cs | 6 ++--- Modules/ExtendedPlayerControl.cs | 12 ++++----- Modules/GameState.cs | 9 +++++++ Modules/NameColorManager.cs | 30 ++++++++++++--------- Modules/Utils.cs | 4 +-- Patches/ChatBubblePatch.cs | 7 ++++- Patches/CheckGameEndPatch.cs | 18 +++++++------ Patches/IntroPatch.cs | 14 ++++++++++ Patches/MeetingHudPatch.cs | 6 +++++ Patches/OneWayShadowsPatch.cs | 2 +- Patches/PhantomRolePatch.cs | 6 ++--- Patches/PlayerControlPatch.cs | 10 +++---- Patches/SabotageSystemPatch.cs | 5 ++-- Roles/AddOns/Impostor/LastImpostor.cs | 2 +- Roles/Core/AssignManager/GhostRoleAssign.cs | 10 ++++++- Roles/Coven/CovenLeader.cs | 3 +++ Roles/Coven/Necromancer.cs | 11 ++++---- Roles/Impostor/Crewpostor.cs | 4 +-- Roles/Impostor/Visionary.cs | 5 ++++ Roles/Neutral/Executioner.cs | 2 +- Roles/Neutral/Traitor.cs | 1 + 21 files changed, 113 insertions(+), 54 deletions(-) diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 6f95950f5..2d110fbf9 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -35,15 +35,15 @@ public static bool CheckBlackOut() if (lastExiled != null && pc.PlayerId == lastExiled.PlayerId) continue; // Impostors - if (pc.Is(Custom_Team.Impostor)) + if (pc.Is(Custom_Team.Impostor) && !Main.PlayerStates[pc.PlayerId].IsNecromancer) Impostors.Add(pc.PlayerId); // Only Neutral killers - else if (pc.IsNeutralKiller() || pc.IsNeutralApocalypse()) + else if ((pc.IsNeutralKiller() || pc.IsNeutralApocalypse()) && !Main.PlayerStates[pc.PlayerId].IsNecromancer) NeutralKillers.Add(pc.PlayerId); //Coven - if (pc.Is(Custom_Team.Coven)) + if (pc.Is(Custom_Team.Coven) || Main.PlayerStates[pc.PlayerId].IsNecromancer) Coven.Add(pc.PlayerId); // Crewmate diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 246efced0..140854da1 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1280,15 +1280,15 @@ public static bool KnowRoleTarget(PlayerControl seer, PlayerControl target) else if (Main.VisibleTasksCount && !seer.IsAlive() && Options.GhostCanSeeOtherRoles.GetBool()) return true; else if (seer.GetCustomRole() == target.GetCustomRole() && seer.GetCustomRole().IsNK()) return true; else if (Options.LoverKnowRoles.GetBool() && seer.Is(CustomRoles.Lovers) && target.Is(CustomRoles.Lovers)) return true; - else if (Options.ImpsCanSeeEachOthersRoles.GetBool() && seer.Is(Custom_Team.Impostor) && target.Is(Custom_Team.Impostor)) return true; - else if (Madmate.MadmateKnowWhosImp.GetBool() && seer.Is(CustomRoles.Madmate) && target.Is(Custom_Team.Impostor)) return true; - else if (Madmate.ImpKnowWhosMadmate.GetBool() && target.Is(CustomRoles.Madmate) && seer.Is(Custom_Team.Impostor)) return true; - else if (seer.Is(Custom_Team.Impostor) && target.GetCustomRole().IsGhostRole() && target.GetCustomRole().IsImpostor()) return true; + else if (Options.ImpsCanSeeEachOthersRoles.GetBool() && seer.Is(Custom_Team.Impostor) && target.Is(Custom_Team.Impostor) && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) return true; + else if (Madmate.MadmateKnowWhosImp.GetBool() && seer.Is(CustomRoles.Madmate) && target.Is(Custom_Team.Impostor) && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) return true; + else if (Madmate.ImpKnowWhosMadmate.GetBool() && target.Is(CustomRoles.Madmate) && seer.Is(Custom_Team.Impostor) && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) return true; + else if (seer.Is(Custom_Team.Impostor) && target.GetCustomRole().IsGhostRole() && target.GetCustomRole().IsImpostor() && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) return true; else if (Ritualist.EnchantedKnowsCoven.GetBool() && seer.Is(CustomRoles.Enchanted) && target.Is(Custom_Team.Coven)) return true; else if (target.Is(CustomRoles.Enchanted) && seer.Is(Custom_Team.Coven)) return true; else if (target.Is(Custom_Team.Coven) && seer.Is(Custom_Team.Coven)) return true; - else if (target.GetRoleClass().KnowRoleTarget(seer, target)) return true; - else if (seer.GetRoleClass().KnowRoleTarget(seer, target)) return true; + else if (target.GetRoleClass().KnowRoleTarget(seer, target) && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) return true; + else if (seer.GetRoleClass().KnowRoleTarget(seer, target) && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) return true; else if (Solsticer.OtherKnowSolsticer(target)) return true; else if (Overseer.IsRevealedPlayer(seer, target) && !target.Is(CustomRoles.Trickster)) return true; else if (Gravestone.EveryoneKnowRole(target)) return true; diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 0276736ec..4420358d5 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -26,6 +26,7 @@ public class PlayerState(byte playerId) #pragma warning restore IDE1006 public TaskState taskState = new(); public bool IsBlackOut { get; set; } = false; + public bool IsNecromancer { get; set; } = false; public (DateTime, byte) RealKiller = (DateTime.MinValue, byte.MaxValue); public PlainShipRoom LastRoom = null; public bool HasSpawned { get; set; } = false; @@ -43,6 +44,10 @@ public void SetMainRole(CustomRoles role) var pc = PlayerId.GetPlayer(); if (pc == null) return; + if (pc.Is(CustomRoles.Necromancer)) { + IsNecromancer = true; + } + // check for role addon if (pc.Is(CustomRoles.Madmate)) { @@ -100,6 +105,10 @@ public void SetMainRole(CustomRoles role) { countTypes = CountTypes.Coven; } + if (Main.PlayerStates[pc.PlayerId].IsNecromancer) + { + countTypes = CountTypes.Coven; + } if (GameStates.IsInGame && preMainRole != CustomRoles.NotAssigned) { diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index e0397d1f7..7c96cb364 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -44,24 +44,28 @@ private static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target color = seer.GetRoleClass()?.PlayerKnowTargetColor(seer, target); // returns "" unless overriden // Impostor & Madmate - if (seer.Is(Custom_Team.Impostor) && target.Is(Custom_Team.Impostor)) color = (seer.Is(CustomRoles.Egoist) && target.Is(CustomRoles.Egoist) && Egoist.ImpEgoistVisibalToAllies.GetBool() && seer != target) ? Main.roleColors[CustomRoles.Egoist] : Main.roleColors[CustomRoles.Impostor]; - if (seer.Is(CustomRoles.Madmate) && target.Is(Custom_Team.Impostor) && Madmate.MadmateKnowWhosImp.GetBool()) color = Main.roleColors[CustomRoles.Impostor]; - if (seer.Is(Custom_Team.Impostor) && target.Is(CustomRoles.Madmate) && Madmate.ImpKnowWhosMadmate.GetBool()) color = Main.roleColors[CustomRoles.Madmate]; - if (seer.Is(Custom_Team.Impostor) && target.GetCustomRole().IsGhostRole() && target.GetCustomRole().IsImpostor()) color = Main.roleColors[CustomRoles.Madmate]; - if (seer.Is(CustomRoles.Madmate) && target.Is(CustomRoles.Madmate) && Madmate.MadmateKnowWhosMadmate.GetBool()) color = Main.roleColors[CustomRoles.Madmate]; + if (seer.Is(Custom_Team.Impostor) && target.Is(Custom_Team.Impostor) && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) color = (seer.Is(CustomRoles.Egoist) && target.Is(CustomRoles.Egoist) && Egoist.ImpEgoistVisibalToAllies.GetBool() && seer != target) ? Main.roleColors[CustomRoles.Egoist] : Main.roleColors[CustomRoles.Impostor]; + if (seer.Is(CustomRoles.Madmate) && target.Is(Custom_Team.Impostor) && Madmate.MadmateKnowWhosImp.GetBool() && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) color = Main.roleColors[CustomRoles.Impostor]; + if (seer.Is(Custom_Team.Impostor) && target.Is(CustomRoles.Madmate) && Madmate.ImpKnowWhosMadmate.GetBool() && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) color = Main.roleColors[CustomRoles.Madmate]; + if (seer.Is(Custom_Team.Impostor) && target.GetCustomRole().IsGhostRole() && target.GetCustomRole().IsImpostor() && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) color = Main.roleColors[CustomRoles.Madmate]; + if (seer.Is(CustomRoles.Madmate) && target.Is(CustomRoles.Madmate) && Madmate.MadmateKnowWhosMadmate.GetBool() && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) color = Main.roleColors[CustomRoles.Madmate]; // Coven if (seer.Is(Custom_Team.Coven) && target.Is(Custom_Team.Coven)) color = Main.roleColors[CustomRoles.Coven]; if (seer.Is(CustomRoles.Enchanted) && target.Is(Custom_Team.Coven) && Ritualist.EnchantedKnowsCoven.GetBool()) color = Main.roleColors[CustomRoles.Coven]; + if (Main.PlayerStates[seer.PlayerId].IsNecromancer && target.Is(Custom_Team.Coven)) color = Main.roleColors[CustomRoles.Coven]; + if (Main.PlayerStates[target.PlayerId].IsNecromancer && seer.Is(Custom_Team.Coven)) color = Main.roleColors[CustomRoles.Coven]; if (seer.Is(Custom_Team.Coven) && target.Is(CustomRoles.Enchanted)) color = Main.roleColors[CustomRoles.Enchanted]; + if (Main.PlayerStates[seer.PlayerId].IsNecromancer && target.Is(CustomRoles.Enchanted)) color = Main.roleColors[CustomRoles.Enchanted]; + if (Main.PlayerStates[target.PlayerId].IsNecromancer && seer.Is(CustomRoles.Enchanted)) color = Main.roleColors[CustomRoles.Enchanted]; if (seer.Is(CustomRoles.Enchanted) && target.Is(CustomRoles.Enchanted) && Ritualist.EnchantedKnowsEnchanted.GetBool()) color = Main.roleColors[CustomRoles.Enchanted]; // Cultist if (Cultist.NameRoleColor(seer, target)) color = Main.roleColors[CustomRoles.Cultist]; // Admirer - if (seer.Is(CustomRoles.Admirer) && target.Is(CustomRoles.Admired)) color = Main.roleColors[CustomRoles.Admirer]; - if (seer.Is(CustomRoles.Admired) && target.Is(CustomRoles.Admirer)) color = Main.roleColors[CustomRoles.Admirer]; + if (seer.Is(CustomRoles.Admirer) && !Main.PlayerStates[seer.PlayerId].IsNecromancer && target.Is(CustomRoles.Admired)) color = Main.roleColors[CustomRoles.Admirer]; + if (seer.Is(CustomRoles.Admired) && target.Is(CustomRoles.Admirer) && !Main.PlayerStates[target.PlayerId].IsNecromancer) color = Main.roleColors[CustomRoles.Admirer]; // Bounties if (seer.Is(CustomRoles.BountyHunter) && BountyHunter.GetTarget(seer) == target.PlayerId) color = "bf1313"; @@ -69,8 +73,8 @@ private static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target // Amnesiac if (seer.GetCustomRole() == target.GetCustomRole() && seer.GetCustomRole().IsNK()) color = Main.roleColors[seer.GetCustomRole()]; - if (seer.Is(CustomRoles.Refugee) && (target.Is(Custom_Team.Impostor))) color = Main.roleColors[CustomRoles.ImpostorTOHE]; - if (seer.Is(Custom_Team.Impostor) && (target.Is(CustomRoles.Refugee))) color = Main.roleColors[CustomRoles.Refugee]; + if (seer.Is(CustomRoles.Refugee) && (target.Is(Custom_Team.Impostor)) && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) color = Main.roleColors[CustomRoles.ImpostorTOHE]; + if (seer.Is(Custom_Team.Impostor) && (target.Is(CustomRoles.Refugee)) && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) color = Main.roleColors[CustomRoles.Refugee]; // Infectious if (Infectious.InfectedKnowColorOthersInfected(seer, target)) color = Main.roleColors[CustomRoles.Infectious]; @@ -104,10 +108,10 @@ private static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target || (Main.VisibleTasksCount && Main.PlayerStates[seer.Data.PlayerId].IsDead && seer.Data.IsDead && !seer.IsAlive() && Options.GhostCanSeeOtherRoles.GetBool()) || target.GetRoleClass().OthersKnowTargetRoleColor(seer, target) || Mimic.CanSeeDeadRoles(seer, target) - || (seer.Is(Custom_Team.Impostor) && target.Is(Custom_Team.Impostor)) - || (seer.Is(CustomRoles.Madmate) && target.Is(Custom_Team.Impostor) && Madmate.MadmateKnowWhosImp.GetBool()) - || (seer.Is(Custom_Team.Impostor) && target.Is(CustomRoles.Madmate) && Madmate.ImpKnowWhosMadmate.GetBool()) - || (seer.Is(CustomRoles.Madmate) && target.Is(CustomRoles.Madmate) && Madmate.MadmateKnowWhosMadmate.GetBool()) + || (seer.Is(Custom_Team.Impostor) && target.Is(Custom_Team.Impostor) && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) + || (seer.Is(CustomRoles.Madmate) && target.Is(Custom_Team.Impostor) && Madmate.MadmateKnowWhosImp.GetBool() && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) + || (seer.Is(Custom_Team.Impostor) && target.Is(CustomRoles.Madmate) && Madmate.ImpKnowWhosMadmate.GetBool() && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) + || (seer.Is(CustomRoles.Madmate) && target.Is(CustomRoles.Madmate) && Madmate.MadmateKnowWhosMadmate.GetBool() && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) || Workaholic.OthersKnowWorka(target) || (target.Is(CustomRoles.Gravestone) && Main.PlayerStates[target.Data.PlayerId].IsDead) || Mare.KnowTargetRoleColor(target, isMeeting); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 8c5ae9ddf..508a3b4ad 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1897,7 +1897,7 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl var seerRoleClass = seer.GetRoleClass(); // Hide player names in during Mushroom Mixup if seer is alive and desync impostor - if (!CamouflageIsForMeeting && MushroomMixupIsActive && seer.IsAlive() && !seer.Is(Custom_Team.Impostor) && seer.HasDesyncRole()) + if (!CamouflageIsForMeeting && MushroomMixupIsActive && seer.IsAlive() && (!seer.Is(Custom_Team.Impostor) || Main.PlayerStates[seer.PlayerId].IsNecromancer) && seer.HasDesyncRole()) { seer.RpcSetNamePrivate("", force: NoCache); } @@ -2047,7 +2047,7 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl //logger.Info("NotifyRoles-Loop2-" + target.GetNameWithRole() + ":START"); // Hide player names in during Mushroom Mixup if seer is alive and desync impostor - if (!CamouflageIsForMeeting && MushroomMixupIsActive && target.IsAlive() && !seer.Is(Custom_Team.Impostor) && seer.HasDesyncRole()) + if (!CamouflageIsForMeeting && MushroomMixupIsActive && target.IsAlive() && (!seer.Is(Custom_Team.Impostor) || Main.PlayerStates[seer.PlayerId].IsNecromancer) && seer.HasDesyncRole()) { realTarget.RpcSetNamePrivate("", seer, force: NoCache); } diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs index c1b0bff88..4bfeab8d2 100644 --- a/Patches/ChatBubblePatch.cs +++ b/Patches/ChatBubblePatch.cs @@ -25,12 +25,17 @@ public static void Postfix(ChatBubble __instance, [HarmonyArgument(1)] bool isDe var seerRoleClass = seer.GetRoleClass(); - // if based role is Shapeshifter and is Desync Shapeshifter + // if based role is Shapeshifter and is Desync Shapeshifter or Necromancer if (seerRoleClass?.ThisRoleBase.GetRoleTypes() == RoleTypes.Shapeshifter && seer.HasDesyncRole()) { // When target is impostor, set name color as white __instance.NameText.color = Color.white; } + if (Main.PlayerStates[seer.PlayerId].IsNecromancer || Main.PlayerStates[target.PlayerId].IsNecromancer) + { + // When target is impostor, set name color as white + __instance.NameText.color = Color.white; + } if (Main.DarkTheme.Value) { diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 94dd32d6f..2ec29a0aa 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -9,6 +9,8 @@ using UnityEngine; using static TOHE.CustomWinnerHolder; using static TOHE.Translator; +using static UnityEngine.GraphicsBuffer; +using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE; @@ -97,7 +99,7 @@ public static bool Prefix() { case CustomWinner.Crewmate: if ((pc.Is(Custom_Team.Crewmate) && (countType == CountTypes.Crew || pc.Is(CustomRoles.Soulless))) || - pc.Is(CustomRoles.Admired) && !WinnerIds.Contains(pc.PlayerId)) + pc.Is(CustomRoles.Admired) && !WinnerIds.Contains(pc.PlayerId) || !Main.PlayerStates[pc.PlayerId].IsNecromancer) { // When admired neutral win, set end game reason "HumansByVote" if (reason is not GameOverReason.HumansByVote and not GameOverReason.HumansByTask) @@ -108,21 +110,21 @@ public static bool Prefix() } break; case CustomWinner.Impostor: - if (((pc.Is(Custom_Team.Impostor) || pc.GetCustomRole().IsMadmate()) && (countType == CountTypes.Impostor || pc.Is(CustomRoles.Soulless))) + if (((pc.Is(Custom_Team.Impostor) || pc.GetCustomRole().IsMadmate()) && (countType == CountTypes.Impostor || pc.Is(CustomRoles.Soulless)) || !Main.PlayerStates[pc.PlayerId].IsNecromancer) || pc.Is(CustomRoles.Madmate) && !WinnerIds.Contains(pc.PlayerId)) { WinnerIds.Add(pc.PlayerId); } break; case CustomWinner.Coven: - if (((pc.Is(Custom_Team.Coven) || pc.Is(CustomRoles.Enchanted)) && (countType == CountTypes.Coven || pc.Is(CustomRoles.Soulless))) + if (((pc.Is(Custom_Team.Coven) || pc.Is(CustomRoles.Enchanted) || Main.PlayerStates[pc.PlayerId].IsNecromancer) && (countType == CountTypes.Coven || pc.Is(CustomRoles.Soulless))) || pc.Is(CustomRoles.Enchanted) && !WinnerIds.Contains(pc.PlayerId)) { WinnerIds.Add(pc.PlayerId); } break; case CustomWinner.Apocalypse: - if ((pc.IsNeutralApocalypse()) && (countType == CountTypes.Apocalypse || pc.Is(CustomRoles.Soulless)) + if ((pc.IsNeutralApocalypse()) && (countType == CountTypes.Apocalypse || pc.Is(CustomRoles.Soulless) || !Main.PlayerStates[pc.PlayerId].IsNecromancer) && !WinnerIds.Contains(pc.PlayerId)) { WinnerIds.Add(pc.PlayerId); @@ -378,7 +380,7 @@ public static bool Prefix() } if (Main.AllAlivePlayerControls.All(p => p.IsNeutralApocalypse())) { - foreach (var pc in Main.AllPlayerControls.Where(x => x.IsNeutralApocalypse())) + foreach (var pc in Main.AllPlayerControls.Where(x => x.IsNeutralApocalypse() && !Main.PlayerStates[x.PlayerId].IsNecromancer)) { if (!WinnerIds.Contains(pc.PlayerId)) WinnerIds.Add(pc.PlayerId); @@ -386,7 +388,7 @@ public static bool Prefix() } if (Main.AllAlivePlayerControls.All(p => p.IsPlayerCoven() || p.Is(CustomRoles.Enchanted))) { - foreach (var pc in Main.AllPlayerControls.Where(x => x.IsPlayerCoven() || x.Is(CustomRoles.Enchanted))) + foreach (var pc in Main.AllPlayerControls.Where(x => x.IsPlayerCoven() || x.Is(CustomRoles.Enchanted) || Main.PlayerStates[x.PlayerId].IsNecromancer)) { if (!WinnerIds.Contains(pc.PlayerId)) WinnerIds.Add(pc.PlayerId); @@ -428,7 +430,7 @@ public static bool Prefix() } //Neutral Win Together - if (Options.NeutralWinTogether.GetBool() && !WinnerIds.Any(x => Utils.GetPlayerById(x) != null && (Utils.GetPlayerById(x).GetCustomRole().IsCrewmate() || Utils.GetPlayerById(x).GetCustomRole().IsImpostor()))) + if (Options.NeutralWinTogether.GetBool() && !WinnerIds.Any(x => Utils.GetPlayerById(x) != null && (Utils.GetPlayerById(x).GetCustomRole().IsCrewmate() || Utils.GetPlayerById(x).GetCustomRole().IsImpostor() || Utils.GetPlayerById(x).GetCustomRole().IsCoven()) && !Main.PlayerStates[x].IsNecromancer)) { foreach (var pc in Main.AllPlayerControls) if (pc.GetCustomRole().IsNeutral() && !WinnerIds.Contains(pc.PlayerId) && !WinnerRoles.Contains(pc.GetCustomRole())) @@ -439,7 +441,7 @@ public static bool Prefix() foreach (var id in WinnerIds) { var pc = Utils.GetPlayerById(id); - if (pc == null || !pc.GetCustomRole().IsNeutral()) continue; + if (pc == null || !pc.GetCustomRole().IsNeutral() || !Main.PlayerStates[pc.PlayerId].IsNecromancer) continue; foreach (var tar in Main.AllPlayerControls) if (!WinnerIds.Contains(tar.PlayerId) && tar.GetCustomRole() == pc.GetCustomRole()) diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index cda3f93cd..0c3276df4 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -771,6 +771,20 @@ public static void Prefix() target.Data.Role.NameColor = Color.white; } } + if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].IsNecromancer) + { + PlayerControl.LocalPlayer.Data.Role.AffectedByLightAffectors = false; + + foreach (var target in PlayerControl.AllPlayerControls.GetFastEnumerator().Where(x => !x.IsPlayerCoven())) + { + // Set all players as killable players + target.Data.Role.CanBeKilled = true; + + // When target is impostor, set name color as white + target.cosmetics.SetNameColor(Color.white); + target.Data.Role.NameColor = Color.white; + } + } if (Main.UnShapeShifter.Any()) { diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index cf1311756..e85392b40 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1229,6 +1229,12 @@ public static void Postfix(MeetingHud __instance) target.cosmetics.SetNameColor(Color.white); pva.NameText.color = Color.white; } + if (Main.PlayerStates[seer.PlayerId].IsNecromancer || Main.PlayerStates[target.PlayerId].IsNecromancer) + { + // When target is impostor, set name color as white + target.cosmetics.SetNameColor(Color.white); + pva.NameText.color = Color.white; + } var sb = new StringBuilder(); diff --git a/Patches/OneWayShadowsPatch.cs b/Patches/OneWayShadowsPatch.cs index c67fc59ef..cf34e63ec 100644 --- a/Patches/OneWayShadowsPatch.cs +++ b/Patches/OneWayShadowsPatch.cs @@ -7,7 +7,7 @@ public static class OneWayShadowsIsIgnoredPatch { public static bool Prefix(OneWayShadows __instance, ref bool __result) { - var amDesyncImpostor = PlayerControl.LocalPlayer.HasDesyncRole(); + var amDesyncImpostor = PlayerControl.LocalPlayer.HasDesyncRole() || Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].IsNecromancer; if (__instance.IgnoreImpostor && amDesyncImpostor) { diff --git a/Patches/PhantomRolePatch.cs b/Patches/PhantomRolePatch.cs index 083d61f7b..f015e1c95 100644 --- a/Patches/PhantomRolePatch.cs +++ b/Patches/PhantomRolePatch.cs @@ -55,7 +55,7 @@ private static void CheckVanish_Prefix(PlayerControl __instance) foreach (var target in Main.AllPlayerControls) { - if (!target.IsAlive() || phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; + if (!target.IsAlive() || phantom == target || target.AmOwner || !target.HasDesyncRole() || !Main.PlayerStates[target.PlayerId].IsNecromancer) continue; // Set Phantom when his start vanish phantom.RpcSetRoleDesync(RoleTypes.Phantom, target.GetClientId()); @@ -93,7 +93,7 @@ private static void CheckAppear_Prefix(PlayerControl __instance, bool shouldAnim foreach (var target in Main.AllPlayerControls) { - if (!target.IsAlive() || phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; + if (!target.IsAlive() || phantom == target || target.AmOwner || !target.HasDesyncRole() || !Main.PlayerStates[target.PlayerId].IsNecromancer) continue; var clientId = target.GetClientId(); @@ -131,7 +131,7 @@ private static void SetRoleInvisibility_Prefix(PlayerControl __instance, bool is public static void OnReportDeadBody(PlayerControl seer, bool force) { - if (InvisibilityList.Count == 0 || !seer.IsAlive() || seer.Data.Role.Role is RoleTypes.Phantom || seer.AmOwner || !seer.HasDesyncRole()) return; + if (InvisibilityList.Count == 0 || !seer.IsAlive() || seer.Data.Role.Role is RoleTypes.Phantom || seer.AmOwner || !seer.HasDesyncRole() || !Main.PlayerStates[seer.PlayerId].IsNecromancer) return; foreach (var phantom in InvisibilityList.GetFastEnumerator()) { diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index bea1a42a4..d1081fa7c 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -19,6 +19,7 @@ using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Translator; +using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE; @@ -279,9 +280,6 @@ public static bool RpcCheckAndMurder(PlayerControl killer, PlayerControl target, if (killer.Is(Custom_Team.Impostor) && !Madmate.ImpCanKillMadmate.GetBool() && target.Is(CustomRoles.Madmate)) return false; - // Coven CAN'T kill Coven/Enchanted - // if ((killer.Is(Custom_Team.Coven) || killer.Is(CustomRoles.Enchanted)) && (target.Is(Custom_Team.Coven) || target.Is(CustomRoles.Enchanted))) return false; - Logger.Info($"Start", "OnCheckMurderAsTargetOnOthers"); // Check murder on others targets @@ -1664,7 +1662,7 @@ public static bool Prefix(PlayerControl __instance, uint idx) break; case CustomRoles.Madmate when taskState.IsTaskFinished && player.Is(CustomRoles.Snitch): - foreach (var impostor in Main.AllAlivePlayerControls.Where(pc => pc.Is(Custom_Team.Impostor)).ToArray()) + foreach (var impostor in Main.AllAlivePlayerControls.Where(pc => pc.Is(Custom_Team.Impostor) || !Main.PlayerStates[pc.PlayerId].IsNecromancer).ToArray()) { NameColorManager.Add(impostor.PlayerId, player.PlayerId, "#ff1919"); } @@ -1792,7 +1790,9 @@ public static void Postfix(PlayerControl __instance) } // if player is Desync Impostor and the vanilla sees player as Imposter, the vanilla process does not hide your name, so the other person's name is hidden - if (!PlayerControl.LocalPlayer.Is(Custom_Team.Impostor) && // Not an Impostor + if ((!PlayerControl.LocalPlayer.Is(Custom_Team.Impostor) // Not an Impostor + || Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].IsNecromancer // Necromancer + ) && PlayerControl.LocalPlayer.HasDesyncRole()) // Desync Impostor { // Hide names diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index 6db11ec7b..5851e66a9 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -3,6 +3,7 @@ using TOHE.Roles.Core; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; +using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE; @@ -128,7 +129,7 @@ public static void Postfix() foreach (var pc in Main.AllAlivePlayerControls) { - if (!pc.Is(Custom_Team.Impostor) && pc.HasDesyncRole()) + if ((!pc.Is(Custom_Team.Impostor) || Main.PlayerStates[pc.PlayerId].IsNecromancer) && pc.HasDesyncRole()) { // Need for hiding player names if player is desync Impostor Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true, MushroomMixupIsActive: true); @@ -186,7 +187,7 @@ public static void Postfix(MushroomMixupSabotageSystem __instance, bool __state) foreach (var pc in Main.AllAlivePlayerControls) { - if (!pc.Is(Custom_Team.Impostor) && pc.HasDesyncRole()) + if ((!pc.Is(Custom_Team.Impostor) || Main.PlayerStates[pc.PlayerId].IsNecromancer) && pc.HasDesyncRole()) { // Need for display player names if player is desync Impostor Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true); diff --git a/Roles/AddOns/Impostor/LastImpostor.cs b/Roles/AddOns/Impostor/LastImpostor.cs index 276f81422..674b487c0 100644 --- a/Roles/AddOns/Impostor/LastImpostor.cs +++ b/Roles/AddOns/Impostor/LastImpostor.cs @@ -30,7 +30,7 @@ public static void SetKillCooldown() Main.AllPlayerKillCooldown[currentId] -= removeCooldown; } private static bool CanBeLastImpostor(PlayerControl pc) - => pc.IsAlive() && !pc.Is(CustomRoles.LastImpostor) && !pc.Is(CustomRoles.Overclocked) && pc.Is(Custom_Team.Impostor); + => pc.IsAlive() && !pc.Is(CustomRoles.LastImpostor) && !pc.Is(CustomRoles.Overclocked) && pc.Is(Custom_Team.Impostor) && !Main.PlayerStates[pc.PlayerId].IsNecromancer; public static void SetSubRole() { diff --git a/Roles/Core/AssignManager/GhostRoleAssign.cs b/Roles/Core/AssignManager/GhostRoleAssign.cs index 52346fde6..bc35ff714 100644 --- a/Roles/Core/AssignManager/GhostRoleAssign.cs +++ b/Roles/Core/AssignManager/GhostRoleAssign.cs @@ -40,7 +40,15 @@ public static void GhostAssignPatch(PlayerControl player) var getplrRole = player.GetCustomRole(); // Neutral Apocalypse can't get ghost roles - if (getplrRole.IsNA() || getplrRole.IsTNA()) return; + if (getplrRole.IsNA() || getplrRole.IsTNA() && !Main.PlayerStates[player.PlayerId].IsNecromancer) return; + + // Coven Ghost Roles don't exist yet + if (getplrRole.IsCoven() && !Main.PlayerStates[player.PlayerId].IsNecromancer) return; + if (Main.PlayerStates[player.PlayerId].IsNecromancer) + { + GhostGetPreviousRole[player.PlayerId] = CustomRoles.Necromancer; + return; + } // Roles can win after death, should not get ghost roles if (getplrRole is CustomRoles.GM diff --git a/Roles/Coven/CovenLeader.cs b/Roles/Coven/CovenLeader.cs index fed5f2e93..c4bf4105b 100644 --- a/Roles/Coven/CovenLeader.cs +++ b/Roles/Coven/CovenLeader.cs @@ -70,6 +70,9 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } var roleList = CustomRolesHelper.AllRoles.Where(role => (role.IsCoven() && (role.IsEnable() && !role.RoleExist(countDead: true)))).ToList(); retrainPlayer[target.PlayerId] = roleList.RandomElement(); + // if every enabled coven role is already in the game then use one of them anyways + if (retrainPlayer[target.PlayerId] == CustomRoles.Crewmate || retrainPlayer[target.PlayerId] == CustomRoles.CrewmateTOHE) + retrainPlayer[target.PlayerId] = CustomRolesHelper.AllRoles.Where(role => (role.IsCoven() && (role.IsEnable()))).ToList().RandomElement(); foreach (byte cov in retrainPlayer.Keys) { SendMessage(string.Format(GetString("RetrainNotification"), CustomRoles.CovenLeader.ToColoredString(), retrainPlayer[cov].ToColoredString()), cov); diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index 595398dcb..6c37c9f66 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -136,7 +136,6 @@ public override void UnShapeShiftButton(PlayerControl nm) nm.RpcChangeRoleBasis(role); nm.RpcSetCustomRole(role); nm.GetRoleClass()?.OnAdd(nm.PlayerId); - nm.RpcSetCustomRole(CustomRoles.Enchanted); nm.SyncSettings(); Main.PlayerStates[nm.PlayerId].InitTask(nm); nm.RpcGuardAndKill(nm); @@ -147,7 +146,6 @@ public override void UnShapeShiftButton(PlayerControl nm) { nm.GetRoleClass()?.OnRemove(nm.PlayerId); } - Main.PlayerStates[nm.PlayerId].RemoveSubRole(CustomRoles.Enchanted); nm.RpcChangeRoleBasis(CustomRoles.Necromancer); nm.RpcSetCustomRole(CustomRoles.Necromancer); nm.ResetKillCooldown(); @@ -161,19 +159,20 @@ public override void UnShapeShiftButton(PlayerControl nm) } private static bool BlackList(CustomRoles role) { - return role.IsNA() || role.IsGhostRole() || role is + return role.IsGhostRole() || role is CustomRoles.Veteran or CustomRoles.Solsticer or CustomRoles.Lawyer or CustomRoles.Amnesiac or CustomRoles.Imitator or CustomRoles.CopyCat or - CustomRoles.Executioner or CustomRoles.Follower or CustomRoles.Romantic or CustomRoles.God or CustomRoles.Innocent or CustomRoles.Jackal or + CustomRoles.Workaholic or + CustomRoles.Specter or CustomRoles.Marshall or CustomRoles.Captain or CustomRoles.Retributionist or @@ -192,9 +191,11 @@ CustomRoles.GM or CustomRoles.Killer or CustomRoles.Coven or CustomRoles.Apocalypse or + CustomRoles.Solsticer or CustomRoles.Sunnyboy || (role == CustomRoles.Workaholic && Workaholic.WorkaholicVisibleToEveryone.GetBool()) || - (role == CustomRoles.Mayor && Mayor.MayorRevealWhenDoneTasks.GetBool()); + (role == CustomRoles.Mayor && Mayor.MayorRevealWhenDoneTasks.GetBool()) || + (role == CustomRoles.Executioner && Executioner.KnowTargetRole.GetBool()); } public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { diff --git a/Roles/Impostor/Crewpostor.cs b/Roles/Impostor/Crewpostor.cs index 4cd0ff6b3..6bd206370 100644 --- a/Roles/Impostor/Crewpostor.cs +++ b/Roles/Impostor/Crewpostor.cs @@ -97,8 +97,8 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) public override string PlayerKnowTargetColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target) ? Utils.GetRoleColorCode(CustomRoles.Crewpostor) : string.Empty; public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) - => (AlliesKnowCrewpostor.GetBool() && seer.Is(Custom_Team.Impostor) && target.Is(CustomRoles.Crewpostor)) - || (KnowsAllies.GetBool() && seer.Is(CustomRoles.Crewpostor) && target.Is(Custom_Team.Impostor)); + => (AlliesKnowCrewpostor.GetBool() && seer.Is(Custom_Team.Impostor) && target.Is(CustomRoles.Crewpostor) && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer) + || (KnowsAllies.GetBool() && seer.Is(CustomRoles.Crewpostor) && target.Is(Custom_Team.Impostor) && !Main.PlayerStates[seer.PlayerId].IsNecromancer && !Main.PlayerStates[target.PlayerId].IsNecromancer); public override bool OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) { diff --git a/Roles/Impostor/Visionary.cs b/Roles/Impostor/Visionary.cs index d0c29d232..129291858 100644 --- a/Roles/Impostor/Visionary.cs +++ b/Roles/Impostor/Visionary.cs @@ -43,6 +43,11 @@ or CustomRoles.Refugee return Main.roleColors[CustomRoles.Knight]; } + if (Main.PlayerStates[target.PlayerId].IsNecromancer) + { + return Main.roleColors[CustomRoles.Coven]; + } + if (customRole.IsImpostorTeamV2() || customRole.IsMadmate()) { return Main.roleColors[CustomRoles.Impostor]; diff --git a/Roles/Neutral/Executioner.cs b/Roles/Neutral/Executioner.cs index f03c4303f..cdfac9840 100644 --- a/Roles/Neutral/Executioner.cs +++ b/Roles/Neutral/Executioner.cs @@ -23,7 +23,7 @@ internal class Executioner : RoleBase private static OptionItem CanTargetNeutralChaos; private static OptionItem CanTargetNeutralApocalypse; private static OptionItem CanTargetCoven; - private static OptionItem KnowTargetRole; + public static OptionItem KnowTargetRole; private static OptionItem ChangeRolesAfterTargetKilled; private static OptionItem RevealExeTargetUponEjection; diff --git a/Roles/Neutral/Traitor.cs b/Roles/Neutral/Traitor.cs index 626105682..70b6def3f 100644 --- a/Roles/Neutral/Traitor.cs +++ b/Roles/Neutral/Traitor.cs @@ -69,6 +69,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } public override string PlayerKnowTargetColor(PlayerControl seer, PlayerControl target) { + if (Main.PlayerStates[seer.PlayerId].IsNecromancer || Main.PlayerStates[target.PlayerId].IsNecromancer) return string.Empty; if (target.Is(Custom_Team.Impostor)) { return Main.roleColors[CustomRoles.Impostor]; From a387acc51be88e1ac45c79b624f1b1acd4e6828b Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:23:34 -0500 Subject: [PATCH 080/101] bug fixes ritualist tryhidemsg (again) necromancer reviving if revert role when dead necromancer duration going during meeting necromancer and sacrifist cooldown showing 5 decimal places win conditions --- Modules/ChatManager.cs | 4 ++-- Modules/Utils.cs | 3 +++ Patches/CheckGameEndPatch.cs | 10 ++++---- Resources/Lang/en_US.json | 1 + Roles/Coven/Necromancer.cs | 46 ++++++++++++++++++++++++------------ Roles/Coven/Ritualist.cs | 8 +++++-- Roles/Coven/Sacrifist.cs | 4 ++-- 7 files changed, 50 insertions(+), 26 deletions(-) diff --git a/Modules/ChatManager.cs b/Modules/ChatManager.cs index f9ed65791..74e28fc0a 100644 --- a/Modules/ChatManager.cs +++ b/Modules/ChatManager.cs @@ -100,7 +100,7 @@ public static void SendMessage(PlayerControl player, string message) if (GameStates.IsInGame) operate = 3; if (CheckCommond(ref msg, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id|編號|玩家編號")) operate = 1; - else if (CheckCommond(ref msg, "shoot|guess|bet|st|gs|bt|猜|赌|賭|sp|jj|tl|trial|审判|判|审|審判|審|compare|cmp|比较|比較|duel|sw|swap|st|换票|换|換票|換|finish|结束|结束会议|結束|結束會議|reveal|展示", false)) operate = 2; + else if (CheckCommond(ref msg, "shoot|guess|bet|st|gs|bt|猜|赌|賭|sp|jj|tl|trial|审判|判|审|審判|審|compare|cmp|比较|比較|duel|sw|swap|st|换票|换|換票|換|finish|结束|结束会议|結束|結束會議|reveal|展示|rt|rit|ritual|bloodritual", false)) operate = 2; else if (ChatSentBySystem.Contains(GetTextHash(msg))) operate = 5; if ((operate == 1 || Blackmailer.CheckBlackmaile(player)) && player.IsAlive()) @@ -280,4 +280,4 @@ public static void SendPreviousMessagesToAll() } } } -} \ No newline at end of file +} diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 36d2965af..22ecf26c6 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -546,6 +546,7 @@ static bool Checkif(string str) case CustomRoles.Infected: case CustomRoles.Contagious: case CustomRoles.Admired: + case CustomRoles.Enchanted: RoleColor = GetRoleColor(subRole); RoleText = GetRoleString($"{subRole}-") + RoleText; break; @@ -639,6 +640,7 @@ public static bool HasTasks(NetworkedPlayerInfo playerData, bool ForRecompute = case CustomRoles.EvilSpirit: case CustomRoles.Contagious: case CustomRoles.Soulless: + case CustomRoles.Enchanted: case CustomRoles.Rascal: hasTasks &= !ForRecompute; break; @@ -2431,6 +2433,7 @@ public static void AfterMeetingTasks() if (Burst.IsEnable) Burst.AfterMeetingTasks(); if (CustomRoles.CopyCat.HasEnabled()) CopyCat.UnAfterMeetingTasks(); // All crew hast to be before this + if (CustomRoles.Necromancer.HasEnabled()) Necromancer.UnAfterMeetingTasks(); } catch (Exception error) { diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 2ec29a0aa..57372f0eb 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -99,7 +99,7 @@ public static bool Prefix() { case CustomWinner.Crewmate: if ((pc.Is(Custom_Team.Crewmate) && (countType == CountTypes.Crew || pc.Is(CustomRoles.Soulless))) || - pc.Is(CustomRoles.Admired) && !WinnerIds.Contains(pc.PlayerId) || !Main.PlayerStates[pc.PlayerId].IsNecromancer) + pc.Is(CustomRoles.Admired) && !WinnerIds.Contains(pc.PlayerId) && !Main.PlayerStates[pc.PlayerId].IsNecromancer) { // When admired neutral win, set end game reason "HumansByVote" if (reason is not GameOverReason.HumansByVote and not GameOverReason.HumansByTask) @@ -110,7 +110,7 @@ public static bool Prefix() } break; case CustomWinner.Impostor: - if (((pc.Is(Custom_Team.Impostor) || pc.GetCustomRole().IsMadmate()) && (countType == CountTypes.Impostor || pc.Is(CustomRoles.Soulless)) || !Main.PlayerStates[pc.PlayerId].IsNecromancer) + if (((pc.Is(Custom_Team.Impostor) || pc.GetCustomRole().IsMadmate()) && (countType == CountTypes.Impostor || pc.Is(CustomRoles.Soulless)) && !Main.PlayerStates[pc.PlayerId].IsNecromancer) || pc.Is(CustomRoles.Madmate) && !WinnerIds.Contains(pc.PlayerId)) { WinnerIds.Add(pc.PlayerId); @@ -124,7 +124,7 @@ public static bool Prefix() } break; case CustomWinner.Apocalypse: - if ((pc.IsNeutralApocalypse()) && (countType == CountTypes.Apocalypse || pc.Is(CustomRoles.Soulless) || !Main.PlayerStates[pc.PlayerId].IsNecromancer) + if ((pc.IsNeutralApocalypse()) && (countType == CountTypes.Apocalypse || pc.Is(CustomRoles.Soulless) && !Main.PlayerStates[pc.PlayerId].IsNecromancer) && !WinnerIds.Contains(pc.PlayerId)) { WinnerIds.Add(pc.PlayerId); @@ -441,7 +441,7 @@ public static bool Prefix() foreach (var id in WinnerIds) { var pc = Utils.GetPlayerById(id); - if (pc == null || !pc.GetCustomRole().IsNeutral() || !Main.PlayerStates[pc.PlayerId].IsNecromancer) continue; + if (pc == null || !pc.GetCustomRole().IsNeutral() || Main.PlayerStates[pc.PlayerId].IsNecromancer) continue; foreach (var tar in Main.AllPlayerControls) if (!WinnerIds.Contains(tar.PlayerId) && tar.GetCustomRole() == pc.GetCustomRole()) @@ -805,4 +805,4 @@ public virtual bool CheckGameEndBySabotage(out GameOverReason reason) return false; } -} \ No newline at end of file +} diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index a0542b7a2..7daa17d13 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2039,6 +2039,7 @@ "RitualistEnchantedKnowsEnchanted": "Enchanted knows Enchanted", "RitualistCommandHelp": "Instructions: /rt [Player ID] [Role Name] \nExample: /rt 3 Bait \nYou can see the player IDs before everyone's names \n or use the /id command to list the player IDs", "RitualistConvertNotif": "Your role has been guessed by the {0} and you are now apart of the Coven!", + "RitualistGuessAddon": "Did you seriously think it would be that easy? You can't do a ritual solely based on add-ons.", "ConjurerCooldown": "Conjure Cooldown", "ConjurerRadius": "Blast Radius", diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index 6c37c9f66..b5d2f1571 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -109,7 +109,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t } public override string GetLowerText(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) { - return GetString("NecromancerAbilityCooldown") + ": " + AbilityTimer.ToString() + "s / " + AbilityCooldown.GetFloat().ToString() + "s"; + return string.Format(GetString("NecromancerAbilityCooldown") + ": {0:F0}s / {1:F0}s", AbilityTimer, AbilityCooldown.GetFloat()); } public override void UnShapeShiftButton(PlayerControl nm) { @@ -142,21 +142,30 @@ public override void UnShapeShiftButton(PlayerControl nm) nm.Notify(string.Format(GetString("CopyCatRoleChange"), Utils.GetRoleName(role))); _ = new LateTask(() => { - if (nm.GetCustomRole() != CustomRoles.Necromancer) - { - nm.GetRoleClass()?.OnRemove(nm.PlayerId); - } - nm.RpcChangeRoleBasis(CustomRoles.Necromancer); - nm.RpcSetCustomRole(CustomRoles.Necromancer); - nm.ResetKillCooldown(); - nm.SyncSettings(); - nm.RpcGuardAndKill(nm); - nm.Notify(string.Format(GetString("CopyCatRoleChange"), Utils.GetRoleName(CustomRoles.Necromancer))); - UsedRoles[nm.PlayerId].Add(role); - canUseAbility = false; - AbilityTimer = 0; + if (!GameStates.IsMeeting) + RevertRole(nm, role); }, AbilityDuration.GetFloat(), "Necromancer Revert Role"); } + private static void RevertRole(PlayerControl nm, CustomRoles role) + { + if (nm == null) return; + if (nm.GetCustomRole() != CustomRoles.Necromancer) + { + nm.GetRoleClass()?.OnRemove(nm.PlayerId); + } + if (nm.IsAlive()) + nm.RpcChangeRoleBasis(CustomRoles.Necromancer); + nm.RpcSetCustomRole(CustomRoles.Necromancer); + nm.ResetKillCooldown(); + nm.SyncSettings(); + nm.RpcGuardAndKill(nm); + nm.Notify(string.Format(GetString("CopyCatRoleChange"), Utils.GetRoleName(CustomRoles.Necromancer))); + UsedRoles[nm.PlayerId].Add(role); + canUseAbility = false; + AbilityTimer = 0; + Logger.Info($"Reverted Role for {nm.GetRealName()}", "Necromancer"); + } + private static bool BlackList(CustomRoles role) { return role.IsGhostRole() || role is @@ -205,9 +214,16 @@ public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowT } else canUseAbility = true; } - public override void AfterMeetingTasks() + public static void UnAfterMeetingTasks() { AbilityTimer = 0; + foreach (var nm in Main.AllPlayerControls.Where(x => Main.PlayerStates[x.PlayerId].IsNecromancer)) + { + if (nm.GetCustomRole() != CustomRoles.Necromancer) + { + RevertRole(nm, nm.GetCustomRole()); + } + } } private static void Countdown(int seconds, PlayerControl player) { diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index 1bed9b81c..c25a35272 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -115,7 +115,11 @@ public static bool RitualistMsgCheck(PlayerControl pc, string msg, bool isUI = f return true; } var target = GetPlayerById(targetId); - + if (role.IsAdditionRole()) + { + pc.ShowInfoMessage(isUI, GetString("RitualistGuessAddon")); + return true; + } if (!target.Is(role)) { pc.ShowInfoMessage(isUI, GetString("RitualistRitualFail")); @@ -254,4 +258,4 @@ public static bool CanBeConverted(PlayerControl pc) && !((pc.Is(CustomRoles.NiceMini) || pc.Is(CustomRoles.EvilMini)) && Mini.Age < 18) && !(pc.GetCustomSubRoles().Contains(CustomRoles.Hurried) && !Hurried.CanBeConverted.GetBool()); } -} \ No newline at end of file +} diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index ee90748e7..722ff7e0c 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -305,7 +305,7 @@ public override void AfterMeetingTasks() } public override string GetLowerText(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false, bool isForHud = false) { - return debuffTimer.ToString() + "s / " + maxDebuffTimer.ToString() + "s"; + return GetString("SacrifistDebuffCooldown") + ": " + string.Format("{0:f0}", debuffTimer) + "s / " + string.Format("{0:f0}", maxDebuffTimer) + "s"; } public override void OnFixedUpdate(PlayerControl player, bool lowLoad, long nowTime) { @@ -343,4 +343,4 @@ public override void OnPlayerExiled(PlayerControl player, NetworkedPlayerInfo ex CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Retribution, [.. killPlayers]); } -} \ No newline at end of file +} From b843c4ab5e6f9239e1e5e2e385b1ed8c0178f9dc Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:51:10 -0500 Subject: [PATCH 081/101] and statements not or statements im smart --- Patches/PlayerControlPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 70e727440..fcde55230 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1668,7 +1668,7 @@ public static bool Prefix(PlayerControl __instance, uint idx) break; case CustomRoles.Madmate when taskState.IsTaskFinished && player.Is(CustomRoles.Snitch): - foreach (var impostor in Main.AllAlivePlayerControls.Where(pc => pc.Is(Custom_Team.Impostor) || !Main.PlayerStates[pc.PlayerId].IsNecromancer).ToArray()) + foreach (var impostor in Main.AllAlivePlayerControls.Where(pc => pc.Is(Custom_Team.Impostor) && !Main.PlayerStates[pc.PlayerId].IsNecromancer).ToArray()) { NameColorManager.Add(impostor.PlayerId, player.PlayerId, "#ff1919"); } From ed291fa2f618bc0a3b65fa24643bac0ce1872f0a Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 9 Dec 2024 18:38:13 -0500 Subject: [PATCH 082/101] add Enchanted- string --- Resources/Lang/en_US.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 7daa17d13..196500113 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2260,6 +2260,7 @@ "Infected-": "Infected ", "Contagious-": "Contagious ", "Admired-": "Admired ", + "Enchanted-": "Enchanted ", "DeputyHandcuffCooldown": "Handcuff Cooldown", "DeputyHandcuffMax": "Maximum Handcuffs", From 7c5ceb3c535113d1b90edcd697b28ffc03aabc80 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:52:37 -0500 Subject: [PATCH 083/101] fix mod not starting --- Roles/Neutral/Arsonist.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Roles/Neutral/Arsonist.cs b/Roles/Neutral/Arsonist.cs index 7378a2981..74bec34ad 100644 --- a/Roles/Neutral/Arsonist.cs +++ b/Roles/Neutral/Arsonist.cs @@ -270,7 +270,11 @@ public override void OnCoEnterVent(PlayerPhysics __instance, int ventId) } } - public static bool CanIgniteAnytime() => ArsonistCanIgniteAnytimeOpt.GetBool(); + public static bool CanIgniteAnytime() + { + if (ArsonistCanIgniteAnytimeOpt == null) return true; + return ArsonistCanIgniteAnytimeOpt.GetBool(); + } private static void ResetCurrentDousingTarget(byte arsonistId) => SendCurrentDousingTargetRPC(arsonistId, 255); From d2d4a0dcdb7abfb744bd5b5cdeb9cf49885b0038 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:20:49 -0500 Subject: [PATCH 084/101] fix poisoner and illusionist cant do second action --- Roles/Coven/Illusionist.cs | 20 ++++++++++---------- Roles/Coven/Poisoner.cs | 19 ++++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index a136baf11..5655ee1dc 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -72,7 +72,7 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) } public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - if (killer.CheckDoubleTrigger(target, () => { IllusionedPlayers[killer.PlayerId].Add(target.PlayerId); })) + if (killer.CheckDoubleTrigger(target, () => { SetIllusioned(killer, target); })) { if (HasNecronomicon(killer) && !target.GetCustomRole().IsCovenTeam()) { @@ -82,16 +82,16 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t return true; } killer.Notify(GetString("CovenDontKillOtherCoven")); - return false; - } - else - { - AbilityLimit--; - SendRPC(killer, target); - killer.ResetKillCooldown(); - killer.SetKillCooldown(); - return false; } + return false; + } + private void SetIllusioned(PlayerControl killer, PlayerControl target) + { + IllusionedPlayers[killer.PlayerId].Add(target.PlayerId); + AbilityLimit--; + SendRPC(killer, target); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = IllusionCooldown.GetFloat(); public override bool CanUseKillButton(PlayerControl pc) => true; diff --git a/Roles/Coven/Poisoner.cs b/Roles/Coven/Poisoner.cs index 9151e8de5..87e0ef44b 100644 --- a/Roles/Coven/Poisoner.cs +++ b/Roles/Coven/Poisoner.cs @@ -64,7 +64,7 @@ public override void Add(byte playerId) public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) { - if (killer.CheckDoubleTrigger(target, () => { RoleblockedPlayers[killer.PlayerId].Add(target.PlayerId); })) + if (killer.CheckDoubleTrigger(target, () => { SetPoisoned(killer, target); })) { if (HasNecronomicon(killer)) { @@ -82,14 +82,15 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t PoisonedPlayers.Add(target.PlayerId, new(killer.PlayerId, 0f)); } } - return false; - } - else - { - killer.ResetKillCooldown(); - killer.SetKillCooldown(); - return false; - } + } + return false; + } + private static void SetPoisoned(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return; + RoleblockedPlayers[killer.PlayerId].Add(target.PlayerId); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); } public override void OnFixedUpdate(PlayerControl poisoner, bool lowLoad, long nowTime) From e3a67b5c3193437b31476abe595d083c5391c652 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:40:12 -0500 Subject: [PATCH 085/101] ritualist command fix attempt #3 --- Patches/ChatCommandPatch.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 3d38c9157..534243e07 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -1993,6 +1993,7 @@ public static void SendRolesInfo(string role, byte playerId, bool isDev = false, if (role.EndsWith("\r\n")) _ = role.Replace("\r\n", string.Empty); if (role.EndsWith("\n")) _ = role.Replace("\n", string.Empty); if (role.StartsWith("/bt")) _ = role.Replace("/bt", string.Empty); + if (role.StartsWith("/rt")) _ = role.Replace("/bt", string.Empty); if (role == "" || role == string.Empty) { From 2c736863be04718b3594a0c4f182096d6a761d33 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:40:34 -0500 Subject: [PATCH 086/101] oops --- Patches/ChatCommandPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 534243e07..66f43bbba 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -1993,7 +1993,7 @@ public static void SendRolesInfo(string role, byte playerId, bool isDev = false, if (role.EndsWith("\r\n")) _ = role.Replace("\r\n", string.Empty); if (role.EndsWith("\n")) _ = role.Replace("\n", string.Empty); if (role.StartsWith("/bt")) _ = role.Replace("/bt", string.Empty); - if (role.StartsWith("/rt")) _ = role.Replace("/bt", string.Empty); + if (role.StartsWith("/rt")) _ = role.Replace("/rt", string.Empty); if (role == "" || role == string.Empty) { From 2c91166093cfb91961016de75980f4ed2906d98a Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 21 Dec 2024 12:02:48 -0500 Subject: [PATCH 087/101] coven can know celebrity --- Roles/Crewmate/Celebrity.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Roles/Crewmate/Celebrity.cs b/Roles/Crewmate/Celebrity.cs index 921de86d5..fc237a35e 100644 --- a/Roles/Crewmate/Celebrity.cs +++ b/Roles/Crewmate/Celebrity.cs @@ -16,6 +16,7 @@ internal class Celebrity : RoleBase private static OptionItem ImpKnowCelebrityDead; private static OptionItem NeutralKnowCelebrityDead; + private static OptionItem CovenKnowCelebrityDead; private static readonly HashSet CelebrityDead = []; @@ -26,7 +27,8 @@ public override void SetupCustomOption() .SetParent(CustomRoleSpawnChances[CustomRoles.Celebrity]); NeutralKnowCelebrityDead = BooleanOptionItem.Create(Id + 11, "NeutralKnowCelebrityDead", false, TabGroup.CrewmateRoles, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Celebrity]); - + CovenKnowCelebrityDead = BooleanOptionItem.Create(Id + 12, "CovenKnowCelebrityDead", false, TabGroup.CrewmateRoles, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Celebrity]); } public override void Init() { @@ -40,6 +42,7 @@ public override bool GlobalKillFlashCheck(PlayerControl killer, PlayerControl ta // Hide kill flash for some team if (!ImpKnowCelebrityDead.GetBool() && seer.GetCustomRole().IsImpostor()) return false; if (!NeutralKnowCelebrityDead.GetBool() && seer.GetCustomRole().IsNeutral()) return false; + if (!CovenKnowCelebrityDead.GetBool() && seer.GetCustomRole().IsCoven()) return false; seer.Notify(ColorString(GetRoleColor(CustomRoles.Celebrity), GetString("OnCelebrityDead"))); return true; @@ -55,6 +58,7 @@ public override void OnMurderPlayerAsTarget(PlayerControl killer, PlayerControl { if (!ImpKnowCelebrityDead.GetBool() && pc.GetCustomRole().IsImpostor()) continue; if (!NeutralKnowCelebrityDead.GetBool() && pc.GetCustomRole().IsNeutral()) continue; + if (!CovenKnowCelebrityDead.GetBool() && pc.GetCustomRole().IsCoven()) continue; SendMessage(string.Format(GetString("CelebrityDead"), target.GetRealName()), pc.PlayerId, ColorString(GetRoleColor(CustomRoles.Celebrity), GetString("CelebrityNewsTitle"))); } @@ -71,6 +75,7 @@ public override void OnOthersMeetingHudStart(PlayerControl targets) { if (!ImpKnowCelebrityDead.GetBool() && targets.GetCustomRole().IsImpostor()) continue; if (!NeutralKnowCelebrityDead.GetBool() && targets.GetCustomRole().IsNeutral()) continue; + if (!CovenKnowCelebrityDead.GetBool() && targets.GetCustomRole().IsCoven()) continue; AddMsg(string.Format(GetString("CelebrityDead"), Main.AllPlayerNames[csId]), targets.PlayerId, ColorString(GetRoleColor(CustomRoles.Celebrity), GetString("CelebrityNewsTitle"))); } } From edcaa39d121ad042ef9c66f5b59e8254149b19dc Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Sun, 22 Dec 2024 20:45:45 +0800 Subject: [PATCH 088/101] Code cleaning 2024.12.22 --- Modules/GameState.cs | 3 ++- Modules/ModUpdater.cs | 6 +++--- Modules/Utils.cs | 2 +- Patches/CheckGameEndPatch.cs | 2 -- Patches/PlayerControlPatch.cs | 3 +-- Patches/SabotageSystemPatch.cs | 1 - Patches/VentSystemPatch.cs | 2 +- Roles/Coven/Conjurer.cs | 1 - Roles/Coven/CovenLeader.cs | 3 +-- Roles/Coven/Jinx.cs | 1 - Roles/Coven/Medusa.cs | 1 - Roles/Coven/MoonDancer.cs | 4 ++-- Roles/Coven/Poisoner.cs | 2 +- Roles/Coven/PotionMaster.cs | 1 - Roles/Coven/Ritualist.cs | 3 +-- Roles/Coven/Sacrifist.cs | 1 - Roles/Coven/VoodooMaster.cs | 1 - 17 files changed, 13 insertions(+), 24 deletions(-) diff --git a/Modules/GameState.cs b/Modules/GameState.cs index e2a985574..34045bb71 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -44,7 +44,8 @@ public void SetMainRole(CustomRoles role) var pc = PlayerId.GetPlayer(); if (pc == null) return; - if (pc.Is(CustomRoles.Necromancer)) { + if (pc.Is(CustomRoles.Necromancer)) + { IsNecromancer = true; } diff --git a/Modules/ModUpdater.cs b/Modules/ModUpdater.cs index 3fb07edf2..c02bee320 100644 --- a/Modules/ModUpdater.cs +++ b/Modules/ModUpdater.cs @@ -3,13 +3,13 @@ using System; using System.IO; using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using TOHE.Modules; using UnityEngine; using UnityEngine.Networking; using static TOHE.Translator; using IEnumerator = System.Collections.IEnumerator; -using TOHE.Modules; -using System.Threading.Tasks; -using System.Threading; namespace TOHE; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index d8fc211ee..a2c7471d0 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2433,7 +2433,7 @@ public static void AfterMeetingTasks() if (Burst.IsEnable) Burst.AfterMeetingTasks(); if (CustomRoles.CopyCat.HasEnabled()) CopyCat.UnAfterMeetingTasks(); // All crew hast to be before this - if (CustomRoles.Necromancer.HasEnabled()) Necromancer.UnAfterMeetingTasks(); + if (CustomRoles.Necromancer.HasEnabled()) Necromancer.UnAfterMeetingTasks(); } catch (Exception error) { diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 3a0ebe318..259d790b4 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -9,8 +9,6 @@ using UnityEngine; using static TOHE.CustomWinnerHolder; using static TOHE.Translator; -using static UnityEngine.GraphicsBuffer; -using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE; diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 1fad9e93e..7be07bda1 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -19,7 +19,6 @@ using TOHE.Roles.Neutral; using UnityEngine; using static TOHE.Translator; -using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE; @@ -1843,7 +1842,7 @@ public static void Postfix(PlayerControl __instance) // if player is Desync Impostor and the vanilla sees player as Imposter, the vanilla process does not hide your name, so the other person's name is hidden if ((!PlayerControl.LocalPlayer.Is(Custom_Team.Impostor) // Not an Impostor || Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].IsNecromancer // Necromancer - ) && + ) && PlayerControl.LocalPlayer.HasDesyncRole()) // Desync Impostor { // Hide names diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index 5851e66a9..fda38f3c5 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -3,7 +3,6 @@ using TOHE.Roles.Core; using TOHE.Roles.Impostor; using TOHE.Roles.Neutral; -using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE; diff --git a/Patches/VentSystemPatch.cs b/Patches/VentSystemPatch.cs index 9cda8ccec..785cd9b39 100644 --- a/Patches/VentSystemPatch.cs +++ b/Patches/VentSystemPatch.cs @@ -45,7 +45,7 @@ public static void Postfix(VentilationSystem __instance) LastUpadate = nowTime; foreach (var pc in Main.AllAlivePlayerControls) { - LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; + LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; } ShipStatus.Instance.Systems[SystemTypes.Ventilation].Cast().IsDirty = true; diff --git a/Roles/Coven/Conjurer.cs b/Roles/Coven/Conjurer.cs index f6c32c652..45f818aea 100644 --- a/Roles/Coven/Conjurer.cs +++ b/Roles/Coven/Conjurer.cs @@ -1,5 +1,4 @@ using AmongUs.GameOptions; -using TOHE.Roles.Core; using UnityEngine; using static TOHE.Options; using static TOHE.Translator; diff --git a/Roles/Coven/CovenLeader.cs b/Roles/Coven/CovenLeader.cs index e1dce0068..58727d6a0 100644 --- a/Roles/Coven/CovenLeader.cs +++ b/Roles/Coven/CovenLeader.cs @@ -1,5 +1,4 @@ using Hazel; -using TOHE.Roles.Core; using UnityEngine; using static TOHE.Options; using static TOHE.Translator; @@ -72,7 +71,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t var roleList = CustomRolesHelper.AllRoles.Where(role => (role.IsCoven() && (role.IsEnable() && !role.RoleExist(countDead: true)))).ToList(); retrainPlayer[target.PlayerId] = roleList.RandomElement(); // if every enabled coven role is already in the game then use one of them anyways - if (retrainPlayer[target.PlayerId] == CustomRoles.Crewmate || retrainPlayer[target.PlayerId] == CustomRoles.CrewmateTOHE) + if (retrainPlayer[target.PlayerId] == CustomRoles.Crewmate || retrainPlayer[target.PlayerId] == CustomRoles.CrewmateTOHE) retrainPlayer[target.PlayerId] = CustomRolesHelper.AllRoles.Where(role => (role.IsCoven() && (role.IsEnable()))).ToList().RandomElement(); foreach (byte cov in retrainPlayer.Keys) { diff --git a/Roles/Coven/Jinx.cs b/Roles/Coven/Jinx.cs index ea135d1f7..fe5e1366c 100644 --- a/Roles/Coven/Jinx.cs +++ b/Roles/Coven/Jinx.cs @@ -1,6 +1,5 @@ using Hazel; using InnerNet; -using TOHE.Roles.Core; using UnityEngine; using static TOHE.Options; using static TOHE.Translator; diff --git a/Roles/Coven/Medusa.cs b/Roles/Coven/Medusa.cs index daa089391..4c6e6c643 100644 --- a/Roles/Coven/Medusa.cs +++ b/Roles/Coven/Medusa.cs @@ -1,7 +1,6 @@ using AmongUs.GameOptions; using Hazel; using InnerNet; -using TOHE.Roles.Core; using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; diff --git a/Roles/Coven/MoonDancer.cs b/Roles/Coven/MoonDancer.cs index 5b7521fdb..2c9b0e086 100644 --- a/Roles/Coven/MoonDancer.cs +++ b/Roles/Coven/MoonDancer.cs @@ -2,7 +2,6 @@ using InnerNet; using TOHE.Modules; using TOHE.Roles.AddOns; -using TOHE.Roles.Core; using TOHE.Roles.Crewmate; using TOHE.Roles.Double; using TOHE.Roles.Impostor; @@ -206,7 +205,8 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { KillBlastedOff(); - foreach (var md in BatonPassList.Keys) { + foreach (var md in BatonPassList.Keys) + { DistributeAddOns(GetPlayerById(md)); } } diff --git a/Roles/Coven/Poisoner.cs b/Roles/Coven/Poisoner.cs index 87e0ef44b..2fe51d46d 100644 --- a/Roles/Coven/Poisoner.cs +++ b/Roles/Coven/Poisoner.cs @@ -82,7 +82,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t PoisonedPlayers.Add(target.PlayerId, new(killer.PlayerId, 0f)); } } - } + } return false; } private static void SetPoisoned(PlayerControl killer, PlayerControl target) diff --git a/Roles/Coven/PotionMaster.cs b/Roles/Coven/PotionMaster.cs index 000a70f13..51f35c1a4 100644 --- a/Roles/Coven/PotionMaster.cs +++ b/Roles/Coven/PotionMaster.cs @@ -1,7 +1,6 @@ using Hazel; using InnerNet; using System.Text; -using TOHE.Roles.Core; using UnityEngine; using static TOHE.Options; using static TOHE.Translator; diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index 32f94828a..0d094a0b8 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -3,7 +3,6 @@ using System.Text.RegularExpressions; using TOHE.Modules.ChatManager; using TOHE.Roles.AddOns.Crewmate; -using TOHE.Roles.Core; using TOHE.Roles.Double; using UnityEngine; using static TOHE.Options; @@ -184,7 +183,7 @@ private static void TryHideMsgForRitual() public override void AfterMeetingTasks() { foreach (var rit in EnchantedPlayers.Keys) - { + { foreach (var pc in EnchantedPlayers[rit]) { GetPlayerById(pc).RpcSetCustomRole(CustomRoles.Enchanted); diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index cacc16524..b3c82cbb1 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -2,7 +2,6 @@ using Hazel; using Il2CppInterop.Runtime.InteropTypes.Arrays; using TOHE.Modules; -using TOHE.Roles.Core; using UnityEngine; using static TOHE.Options; using static TOHE.Translator; diff --git a/Roles/Coven/VoodooMaster.cs b/Roles/Coven/VoodooMaster.cs index 445074033..f19874723 100644 --- a/Roles/Coven/VoodooMaster.cs +++ b/Roles/Coven/VoodooMaster.cs @@ -1,6 +1,5 @@ using Hazel; using InnerNet; -using TOHE.Roles.Core; using UnityEngine; using static TOHE.Options; using static TOHE.Translator; From 541c0be9670d491a7682eccf6f42f3479ba58b24 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Sun, 22 Dec 2024 21:27:42 +0800 Subject: [PATCH 089/101] Fix win condition check --- Patches/CheckGameEndPatch.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 259d790b4..152218ea2 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -647,21 +647,25 @@ public static bool CheckGameEndByLivingPlayers(out GameOverReason reason) else { if (impCount >= 1) return false; // Both Imp and NK or Coven are alive, the game must continue + + if (totalNKAlive >= 1 && covenCount >= 1) return false; // Both Coven and NK are alive, the game must continue + + // One of NK or Coven all dead here, check nk and coven count > crew + if (crewCount <= covenCount && totalNKAlive == 0) // Imps dead, NK dead, Crew <= Coven, Coven wins { reason = GameOverReason.ImpostorByKill; ResetAndSetWinner(CustomWinner.Coven); return true; } - if (covenCount >= 1) return false; // Both Coven and NK are alive, the game must continue - if (crewCount > totalNKAlive) return false; // Imps are dead, but Crew still outnumbers NK (the game must continue) - if (crewCount > covenCount) return false; // Imps are dead, but Crew still outnumbers Coven (the game must continue) - else // Imps dead, Crew <= NK, Checking if All nk alive are in 1 team + + else if (crewCount <= totalNKAlive && covenCount == 0) // Imps dead, Coven dead, Crew <= NK, Check NK win { var winners = neutralRoleCounts.Where(kvp => kvp.Value == totalNKAlive).ToArray(); var winnnerLength = winners.Length; if (winnnerLength == 1) { + // Only 1 NK team alive, NK wins try { var winnerRole = winners.First().Key.GetNeutralCustomRoleFromCountType(); @@ -671,15 +675,19 @@ public static bool CheckGameEndByLivingPlayers(out GameOverReason reason) } catch { + Logger.Warn("Error while trying to end game as single NK", "CheckGameEndByLivingPlayers"); return false; } + + return true; } - else if (winnnerLength == 0) + else { return false; // Not all alive neutrals were in one team } - return true; } + + else return false; // Crew > Coven or NK, the game must continue } } } From 65e8e8ec120f9de34447c4b1490132b857240c82 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:23:50 -0500 Subject: [PATCH 090/101] fix /rt showing for host for real this time i swear --- Patches/ChatCommandPatch.cs | 2 +- Resources/Lang/en_US.json | 4 ++-- Roles/Coven/Ritualist.cs | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 66f43bbba..462e1ec64 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -2095,7 +2095,7 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can if (Nemesis.NemesisMsgCheck(player, text)) { Logger.Info($"Is Nemesis Revenge command", "OnReceiveChat"); return; } if (Retributionist.RetributionistMsgCheck(player, text)) { Logger.Info($"Is Retributionist Revenge command", "OnReceiveChat"); return; } if (player.GetRoleClass() is Dictator dt && dt.ExilePlayer(player, text)) { canceled = true; Logger.Info($"Is Dictator command", "OnReceiveChat"); return; } - if (Ritualist.RitualistMsgCheck(player, text)) { Logger.Info($"Is Ritualist command", "OnReceiveChat"); return; } + if (Ritualist.RitualistMsgCheck(player, text)) { canceled = true; Logger.Info($"Is Ritualist command", "OnReceiveChat"); return; } Directory.CreateDirectory(modTagsFiles); Directory.CreateDirectory(vipTagsFiles); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index f39aa2586..c8c09cba1 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -817,7 +817,7 @@ "MorphlingInfoLong": "(Impostors):\nAs the Morphling, you are a Shapeshifter but cannot kill while not shapeshifted.", "TwisterInfoLong": "(Impostors):\nAs the Twister, you can use shapeshifting to swap the position of all players randomly. The swap happens twice, once when you start your shapeshift and once when you return to your original appearance.\nThe Twister itself will not swap places with anyone, and players in vents will not teleport.", "LurkerInfoLong": "(Impostors):\nAs the Lurker, you can jump into a vent to reduce your cooldown by a certain number of seconds. After you kill, your cooldown resets to its original value.", - "VisionaryInfoLong": "(Impostors):\nAs the Visionary, you see the alignments of living players during a meeting.\nThe following information will be displayed on the players:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.", + "VisionaryInfoLong": "(Impostors):\nAs the Visionary, you see the alignments of living players during a meeting.\nThe following information will be displayed on the players:\n- The Red name indicates the Impostors.\n- The Cyan name indicates the Crewmates.\n- The Gray name indicates the Neutrals.\n- The Purple name indicates the Coven.", "PlagueDoctorInfoLong": "(Neutrals):\n(Plague Doctor from TOH)\nThe Plague Scientist's goal is to infect every living player.\nThey start by choosing one player to infect, after which anyone who spends a set amount of time in the range of the infected player becomes infected themselves.\nInfection progress is cumulative and does not reset with distance or after meetings.", "RefugeeInfoLong": "(Madmates):\nAs the Refugee, you were either:\n -An Amnesiac who remembered an Impostor\n -A killer who killed the Godfather's target.\n -A Romantic whose partner was an Impostor\n -Or an Imitator that imitated an Impostor.\n\nNow your job is to help the Impostors kill the crewmates.", "UnderdogInfoLong": "(Impostors):\nAs the Underdog, you cannot kill until there's a certain amount of players alive.", @@ -4097,7 +4097,7 @@ "PreventSeeRolesBeforeSkillUsedUp": "Prevent seeing others roles before skill used up", "ChiefOfPoliceSkillCooldown": "Cooldown for recruiting sheriffs", - "PolicCanImpostorAndNeutarl": "Can recruit Impostor or Neutral", + "PolicCanImpostorAndNeutarl": "Can recruit Non-Crewmates", "SheriffSuccessfullyRecruited": "You recruited a sheriff.", "BeSheriffByPolice": "You've been recruited by the police chief! Serve the crew!", "PoliceFailedRecruit": "Failed to recruit target.", diff --git a/Roles/Coven/Ritualist.cs b/Roles/Coven/Ritualist.cs index 32f94828a..4576463d7 100644 --- a/Roles/Coven/Ritualist.cs +++ b/Roles/Coven/Ritualist.cs @@ -75,9 +75,12 @@ public override string PVANameText(PlayerVoteArea pva, PlayerControl seer, Playe => seer.IsAlive() && target.IsAlive() ? ColorString(GetRoleColor(CustomRoles.Ritualist), target.PlayerId.ToString()) + " " + pva.NameText.text : ""; public static bool RitualistMsgCheck(PlayerControl pc, string msg, bool isUI = false) { + var originMsg = msg; + if (!AmongUsClient.Instance.AmHost) return false; if (!GameStates.IsMeeting || pc == null || GameStates.IsExilling) return false; if (!pc.Is(CustomRoles.Ritualist)) return false; + int operate = 0; // 1:ID 2:猜测 msg = msg.ToLower().TrimStart().TrimEnd(); if (CheckCommond(ref msg, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id||編號|玩家編號")) operate = 1; @@ -103,7 +106,7 @@ public static bool RitualistMsgCheck(PlayerControl pc, string msg, bool isUI = f TryHideMsgForRitual(); ChatManager.SendPreviousMessagesToAll(); } - else if (pc.AmOwner) SendMessage(msg, 255, pc.GetRealName()); + else if (pc.AmOwner) SendMessage(originMsg, 255, pc.GetRealName()); if (RitualLimit[pc.PlayerId] <= 0) { pc.ShowInfoMessage(isUI, GetString("RitualistRitualMax")); From 13a0c968de66db53a1c1420315736063013ab838 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:32:56 -0500 Subject: [PATCH 091/101] update /icon and all coven can see coven icons --- Resources/Lang/en_US.json | 2 +- Roles/Coven/Illusionist.cs | 9 +++++++++ Roles/Coven/Jinx.cs | 9 +++++++++ Roles/Coven/Medusa.cs | 9 +++++++++ Roles/Coven/VoodooMaster.cs | 9 +++++++++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index e6f02507d..4d38d353e 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2321,7 +2321,7 @@ "Command.qq": "→ Lobby will be posted on QQ website (China only)", "Command.dump": "→ Output Log to Desktop", "Command.death": "→ Display info on how you died", - "Command.icons": "
╳ - The Player was marked by the Blackmailer and can't talk during the Meeting
☆ - Used by Captain to display themselves. Only Crewmates can see the Captain's star
乂 - This player was hexed by the Hex Master and will die if the Hex Master isn't killed or ejected by the end of the Meeting.
♦ - Used by Lawyer or Executioner or Follower.
♥ - Used by Lovers or Romantic.
✚ - Used by Medic to mark their target.
⦿ - This player is in a duel with the Pirate.
!? - This player was marked by the Quizmaster and must answer the question correctly to survive.
☜ - Used by Schrödinger's cat to mark their teammate.
◈ - This player marked by the Shroud and will die if the Shroud is not killed or ejected by the end of the meeting.
⚠ - This player is a Snitch or Solsticer who has finished their tasks.
★ - Used by Super Star or Cyber or Marshall.
† - This player was spelled and will die if the Witch is not killed by the end of the meeting.
∇ - Used by Kamikaze to mark their targets.
■ - Used by Lightning to mark their quantum ghosts.
⊠ - Used by Jailer to mark their prisoner.
● - Used by Baker to mark who has Bread.
♠ - Used by Soul Collector to mark who's death they're predicting.
⦿ - Used by Plaguebearer to mark who they have plagued.
<#ac42f2>♣ - Shown on the Coven member with the Necronomicon.", + "Command.icons": "
╳ - The Player was marked by the Blackmailer and can't talk during the Meeting
☆ - Used by Captain to display themselves. Only Crewmates can see the Captain's star
乂 - This player was hexed by the Hex Master and will die if the Hex Master isn't killed or ejected by the end of the Meeting.
♦ - Used by Lawyer or Executioner or Follower.
♥ - Used by Lovers or Romantic.
✚ - Used by Medic to mark their target.
⦿ - This player is in a duel with the Pirate.
!? - This player was marked by the Quizmaster and must answer the question correctly to survive.
☜ - Used by Schrödinger's cat to mark their teammate.
◈ - This player marked by the Shroud and will die if the Shroud is not killed or ejected by the end of the meeting.
⚠ - This player is a Snitch or Solsticer who has finished their tasks.
★ - Used by Super Star or Cyber or Marshall.
† - This player was spelled and will die if the Witch is not killed by the end of the meeting.
∇ - Used by Kamikaze to mark their targets.
■ - Used by Lightning to mark their quantum ghosts.
⊠ - Used by Jailer to mark their prisoner.
● - Used by Baker to mark who has Bread.
♠ - Used by Soul Collector to mark who's death they're predicting.
⦿ - Used by Plaguebearer to mark who they have plagued.
♣ - Shown on the Coven member with the Necronomicon. This is only shown to Coven.
⌘ - This player is Jinxed. This is only shown to Coven.
ø - This player is Illusioned. This is only shown to Coven.
♻ - This player is Stoned. This is only shown to Coven.
✂ - This player is a Voodoo Doll. This is only shown to Coven.", "Command.start": "[Seconds] → Start the game", "Command.iconinfo": "→ Display info on in-meeting icons", diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index 5655ee1dc..0ed362352 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -142,4 +142,13 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => (IllusionedPlayers.TryGetValue(seer.PlayerId, out var Targets) && Targets.Contains(seen.PlayerId)) ? ColorString(GetRoleColor(CustomRoles.Illusionist), "") : string.Empty; + public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) + { + if (_Player == null) return string.Empty; + if (IllusionedPlayers[_Player.PlayerId].Contains(target.PlayerId) && seer.GetCustomRole().IsCovenTeam() && seer.PlayerId != _Player.PlayerId) + { + return ColorString(GetRoleColor(CustomRoles.Illusionist), ""); + } + return string.Empty; + } } diff --git a/Roles/Coven/Jinx.cs b/Roles/Coven/Jinx.cs index fe5e1366c..782d1c7a5 100644 --- a/Roles/Coven/Jinx.cs +++ b/Roles/Coven/Jinx.cs @@ -172,6 +172,15 @@ or CustomRoles.Veteran } public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => IsJinxed(seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.Jinx), "⌘") : string.Empty; + public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) + { + if (_Player == null) return string.Empty; + if (IsJinxed(target.PlayerId) && seer.GetCustomRole().IsCovenTeam() && seer.PlayerId != _Player.PlayerId) + { + return ColorString(GetRoleColor(CustomRoles.Jinx), "⌘"); + } + return string.Empty; + } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); diff --git a/Roles/Coven/Medusa.cs b/Roles/Coven/Medusa.cs index 4c6e6c643..b5d95fb25 100644 --- a/Roles/Coven/Medusa.cs +++ b/Roles/Coven/Medusa.cs @@ -149,6 +149,15 @@ public static void SetStoned(PlayerControl player, IGameOptions opt) } } public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => StonedPlayers[seer.PlayerId].Contains(seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.Medusa), "♻") : string.Empty; + public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) + { + if (_Player == null) return string.Empty; + if (StonedPlayers[_Player.PlayerId].Contains(target.PlayerId) && seer.GetCustomRole().IsCovenTeam() && seer.PlayerId != _Player.PlayerId) + { + return ColorString(GetRoleColor(CustomRoles.Medusa), "♻"); + } + return string.Empty; + } public override void SetAbilityButtonText(HudManager hud, byte playerId) { hud.ReportButton.OverrideText(GetString("MedusaReportButtonText")); diff --git a/Roles/Coven/VoodooMaster.cs b/Roles/Coven/VoodooMaster.cs index f19874723..a8a525205 100644 --- a/Roles/Coven/VoodooMaster.cs +++ b/Roles/Coven/VoodooMaster.cs @@ -66,6 +66,15 @@ public override void SetAbilityButtonText(HudManager hud, byte playerId) => hud.KillButton.OverrideText(GetString("ShamanButtonText")); public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => Dolls[seer.PlayerId].Contains(seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.VoodooMaster), "✂") : string.Empty; + public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) + { + if (_Player == null) return string.Empty; + if (Dolls[_Player.PlayerId].Contains(target.PlayerId) && seer.GetCustomRole().IsCovenTeam() && seer.PlayerId != _Player.PlayerId) + { + return ColorString(GetRoleColor(CustomRoles.VoodooMaster), "✂"); + } + return string.Empty; + } public override string GetProgressText(byte playerId, bool comms) => ColorString(AbilityLimit >= 1 ? GetRoleColor(CustomRoles.VoodooMaster).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) From 6b7ed06d792f49dfce9754f03157e5c2fb4b4e68 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 26 Dec 2024 14:15:01 -0500 Subject: [PATCH 092/101] a bunch of quizmaster things hopefully fixed the bug where the rolebasis and rolefaction questions were actually swapped fix strings showing incorrectly fix faction related questions actually using rolebasis not customteams added three new questions for the hell of it realistically this should have been done in a seperate PR however i only intended to fix it for coven and i just kept seeing issues that i had to fix --- Resources/Lang/en_US.json | 16 ++++++++++++++-- Roles/Neutral/Arsonist.cs | 2 +- Roles/Neutral/Quizmaster.cs | 35 +++++++++++++++++++++++------------ 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 4d38d353e..5749a9c91 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3992,7 +3992,8 @@ "QuizmasterChat.AnswerNotValid": "Your answer must be A, B, or C", "QuizmasterChat.SyntaxNotValid": "Usage:\n/answer [A/B/C]", - "QuizmasterSettings.QuestionDifficulty": "Question Difficulty", + "QuizmasterSettings.MinQuestionDifficulty": "Min Question Difficulty", + "QuizmasterSettings.MaxQuestionDifficulty": "Max Question Difficulty", "QuizmasterSettings.CanVentAfterMark": "Can Vent After Marked Somebody For Quiz", "QuizmasterSettings.CanKillAfterMark": "Can Kill After Marked Somebody For Quiz", "QuizmasterSettings.NumOfKillAfterMark": "How Many Kills Per Round", @@ -4025,6 +4026,14 @@ "QuizmasterAnswers.Experimental": "Experimental", "QuizmasterAnswers.Enhanced": "Enhanced", "QuizmasterAnswers.Edited": "Edited", + "QuizmasterAnswers.Impostor": "Impostor", + "QuizmasterAnswers.Neutral": "Neutral", + "QuizmasterAnswers.Crewmate": "Crewmate", + "QuizmasterAnswers.Addon": "Addon", + "QuizmasterAnswers.Shapeshifter": "Shapeshifter", + "QuizmasterAnswers.Scientist": "Scientist", + "QuizmasterAnswers.Engineer": "Engineer", + "QuizmasterAnswers.GuardianAngel": "GuardianAngel", "QuizmasterQuestions.LastSabotage": "What was the sabotage was called last?", "QuizmasterQuestions.FirstRoundSabotage": "What was the first sabotage called this round?", @@ -4041,8 +4050,11 @@ "QuizmasterQuestions.WhatDoesEOgMeansInName": "What did the E in TOHE originally stand for?", "QuizmasterQuestions.PlrDieReason": "What was {PLR}'s cause of death?", "QuizmasterQuestions.PlrDieMethod": "How did {PLR} die?", - "LastAddedRoleForKarped": "What was the last role added to TOHE before KARPED1EM stepped down?", + "QuizmasterQuestions.LastAddedRoleForKarped": "What was the last role added to TOHE before KARPED1EM stepped down?", "QuizmasterQuestions.PlrDieFaction": "What kind of faction killed {PLR}?", + "QuizmasterQuestions.QuizmasterCooldown": "What will Quizmaster's cooldown always be? (excluding first kill override and changes from other roles/addons)", + "QuizmasterQuestions.WhoCoded": "Who coded Quizmaster?", + "QuizmasterQuestions.WhoOwns": "Who owns The Enhanced Network?", "DeathReason.WrongAnswer": "Wrong Quiz Answer", diff --git a/Roles/Neutral/Arsonist.cs b/Roles/Neutral/Arsonist.cs index a3ad78043..5a3accf7b 100644 --- a/Roles/Neutral/Arsonist.cs +++ b/Roles/Neutral/Arsonist.cs @@ -17,7 +17,7 @@ internal class Arsonist : RoleBase private const int id = 15900; public override bool IsDesyncRole => true; public override CustomRoles ThisRoleBase => CustomRoles.Impostor; - public override Custom_RoleType ThisRoleType => CanIgniteAnytime() ? Custom_RoleType.NeutralKilling : Custom_RoleType.NeutralBenign; + public override Custom_RoleType ThisRoleType => CanIgniteAnytime() ? Custom_RoleType.NeutralKilling : Custom_RoleType.NeutralEvil; //==================================================================\\ private static OptionItem ArsonistDouseTime; diff --git a/Roles/Neutral/Quizmaster.cs b/Roles/Neutral/Quizmaster.cs index ced2045e6..5bbc86f2e 100644 --- a/Roles/Neutral/Quizmaster.cs +++ b/Roles/Neutral/Quizmaster.cs @@ -21,7 +21,8 @@ internal class Quizmaster : RoleBase public override Custom_RoleType ThisRoleType => CanKillsAfterMark() ? Custom_RoleType.NeutralKilling : Custom_RoleType.NeutralChaos; //==================================================================\\ - private static OptionItem QuestionDifficulty; + private static OptionItem MinQuestionDifficulty; + private static OptionItem MaxQuestionDifficulty; public static OptionItem CanKillAfterMarkOpt; private static OptionItem CanVentAfterMark; private static OptionItem NumOfKillAfterMark; @@ -54,7 +55,9 @@ public override void SetupCustomOption() TabGroup tab = TabGroup.NeutralRoles; SetupSingleRoleOptions(Id, tab, CustomRoles.Quizmaster, 1); - QuestionDifficulty = IntegerOptionItem.Create(Id + 10, "QuizmasterSettings.QuestionDifficulty", new(1, 4, 1), 1, tab, false) + MinQuestionDifficulty = IntegerOptionItem.Create(Id + 15, "QuizmasterSettings.MinQuestionDifficulty", new(1, 4, 1), 1, tab, false) + .SetParent(CustomRoleSpawnChances[CustomRoles.Quizmaster]); + MaxQuestionDifficulty = IntegerOptionItem.Create(Id + 10, "QuizmasterSettings.MaxQuestionDifficulty", new(1, 4, 1), 1, tab, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Quizmaster]); CanVentAfterMark = BooleanOptionItem.Create(Id + 11, "QuizmasterSettings.CanVentAfterMark", true, tab, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Quizmaster]); @@ -167,7 +170,7 @@ public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl t private static QuizQuestionBase GetRandomQuestion(List qt) { - List questions = qt.Where(a => a.Stage <= QuestionDifficulty.GetInt()).ToList(); + List questions = qt.Where(a => a.Stage <= MaxQuestionDifficulty.GetInt() && a.Stage >= MinQuestionDifficulty.GetInt()).ToList(); var rnd = IRandom.Instance; QuizQuestionBase question = questions[rnd.Next(0, questions.Count)]; if (question == previousQuestion) @@ -238,19 +241,23 @@ private void DoQuestion() new PlrColorQuestion { Stage = 1, Question = "LastButtonPressedPlayerColor", QuizmasterQuestionType = QuizmasterQuestionType.LastMeetingColorQuestion }, new CountQuestion { Stage = 2, Question = "MeetingPassed", QuizmasterQuestionType = QuizmasterQuestionType.MeetingCountQuestion }, - new SetAnswersQuestion { Stage = 2, Question = "HowManyFactions", Answer = "Three", PossibleAnswers = { "One", "Two", "Three", "Four", "Five" }, QuizmasterQuestionType = QuizmasterQuestionType.FactionQuestion }, - new SetAnswersQuestion { Stage = 2, Question = GetString("QuizmasterQuestions.BasisOfRole").Replace("{QMROLE}", randomRoleWithAddon.ToString()), HasQuestionTranslation = false, Answer = CustomRolesHelper.GetCustomRoleTeam(randomRoleWithAddon).ToString(), PossibleAnswers = { "Crewmate", "Impostor", "Neutral", "Addon" }, QuizmasterQuestionType = QuizmasterQuestionType.RoleBasisQuestion }, - new SetAnswersQuestion { Stage = 2, Question = GetString("QuizmasterQuestions.FactionOfRole").Replace("{QMROLE}", randomRole.ToString()), HasQuestionTranslation = false, Answer = CustomRolesHelper.GetRoleTypes(randomRole).ToString(), PossibleAnswers = { "Crewmate", "Impostor", "Neutral" }, QuizmasterQuestionType = QuizmasterQuestionType.RoleFactionQuestion }, + new SetAnswersQuestion { Stage = 2, Question = "HowManyFactions", Answer = "Four", PossibleAnswers = { "One", "Two", "Three", "Four", "Five" }, QuizmasterQuestionType = QuizmasterQuestionType.FactionQuestion }, + new SetAnswersQuestion { Stage = 2, Question = GetString("QuizmasterQuestions.FactionOfRole").Replace("{QMRole}", randomRoleWithAddon.ToString()), HasQuestionTranslation = false, Answer = CustomRolesHelper.GetCustomRoleTeam(randomRoleWithAddon).ToString(), PossibleAnswers = { "Crewmate", "Impostor", "Neutral", "Coven", "Addon" }, QuizmasterQuestionType = QuizmasterQuestionType.RoleFactionQuestion }, + new SetAnswersQuestion { Stage = 2, Question = GetString("QuizmasterQuestions.BasisOfRole").Replace("{QMRole}", randomRole.ToString()), HasQuestionTranslation = false, Answer = CustomRolesHelper.GetRoleTypes(randomRole).ToString(), PossibleAnswers = { "Crewmate", "Impostor", "Shapeshifter", "Scientist", "Engineer", "GuardianAngel" }, QuizmasterQuestionType = QuizmasterQuestionType.RoleBasisQuestion }, - new SetAnswersQuestion { Stage = 3, Question = "FactionRemovedName", Answer = "Coven", PossibleAnswers = { "Sabotuer", "Sorcerers", "Coven", "Killer" }, QuizmasterQuestionType = QuizmasterQuestionType.RemovedFactionQuestion }, + new SetAnswersQuestion { Stage = 3, Question = "FactionRemovedName", Answer = "None", PossibleAnswers = { "Sabotuer", "Sorcerers", "Coven", "Killer", "None" }, QuizmasterQuestionType = QuizmasterQuestionType.RemovedFactionQuestion }, + // ^ I added Coven back so this question no longer applies :) - Marg new SetAnswersQuestion { Stage = 3, Question = "WhatDoesEOgMeansInName", Answer = "Edited", PossibleAnswers = { "Edition", "Experimental", "Enhanced", "Edited" }, QuizmasterQuestionType = QuizmasterQuestionType.NameOriginQuestion }, new CountQuestion { Stage = 3, Question = "HowManyDiedFirstRound", QuizmasterQuestionType = QuizmasterQuestionType.DiedFirstRoundCountQuestion }, new CountQuestion { Stage = 3, Question = "ButtonPressedBefore", QuizmasterQuestionType = QuizmasterQuestionType.ButtonPressedBeforeThisQuestion }, + new SetAnswersQuestion { Stage = 4, Question = "WhoOwns", Answer = "Moe", PossibleAnswers = { "Lauryn", "Jackler", "Moe", "Marg", "Sarha", "laikrai", "Niko", "D1GQ", "KARPED1EM", "Matt" }, QuizmasterQuestionType = QuizmasterQuestionType.WhoOwns }, new DeathReasonQuestion { Stage = 4, Question = "PlrDieReason", QuizmasterQuestionType = QuizmasterQuestionType.PlrDeathReasonQuestion}, new DeathReasonQuestion { Stage = 4, Question = "PlrDieMethod", QuizmasterQuestionType = QuizmasterQuestionType.PlrDeathMethodQuestion}, new SetAnswersQuestion { Stage = 4, Question = "LastAddedRoleForKarped", Answer = "Pacifist", PossibleAnswers = { "Pacifist", "Vampire", "Snitch", "Vigilante", "Jackal", "Mole", "Sniper" }, QuizmasterQuestionType = QuizmasterQuestionType.RoleAddedQuestion }, new DeathReasonQuestion { Stage = 4, Question = "PlrDieFaction", QuizmasterQuestionType = QuizmasterQuestionType.PlrDeathKillerFactionQuestion}, + new SetAnswersQuestion { Stage = 4, Question = "QuizmasterCooldown", Answer = "15", PossibleAnswers = { "15", "30", "0", "999", AURoleOptions.KillCooldown.ToString() }, QuizmasterQuestionType = QuizmasterQuestionType.QuizmasterCooldownQuestion }, // this is a level 4 because the only way to know this would be to look at the code for Quizmaster + new SetAnswersQuestion { Stage = 4, Question = "WhoCoded", Answer = "Multiple People", PossibleAnswers = { "Furo", "Drakos", "Moe", "Marg", "Multiple People", "TommyXL", "Niko", "Pyro", "KARPED1EM", "Ryuk" }, QuizmasterQuestionType = QuizmasterQuestionType.WhoCoded }, ]; Question = GetRandomQuestion(Questions); @@ -543,13 +550,15 @@ public override void FixUnsetAnswers() } else if (QuizmasterQuestionType == QuizmasterQuestionType.PlrDeathKillerFactionQuestion) { - PossibleAnswers.Add(""); - PossibleAnswers.Add(GetString("DeathReason.Vote")); - PossibleAnswers.Add(GetString("DeathReason.Kill")); + PossibleAnswers.Add(GetString("QuizmasterAnswers.Impostor")); + PossibleAnswers.Add(GetString("QuizmasterAnswers.Crewmate")); + PossibleAnswers.Add(GetString("QuizmasterAnswers.Neutral")); + PossibleAnswers.Add(GetString("QuizmasterAnswers.Coven")); } chosenPlayer = Main.AllPlayerControls[rnd.Next(Main.AllPlayerControls.Length)]; + foreach (PlayerControl plr in Main.AllPlayerControls) { if (QuizmasterQuestionType == QuizmasterQuestionType.PlrDeathReasonQuestion) @@ -570,7 +579,7 @@ public override void FixUnsetAnswers() { QuizmasterQuestionType.PlrDeathReasonQuestion => chosenPlayer.Data.IsDead ? Main.PlayerStates[chosenPlayer.PlayerId].deathReason.ToString() : "None", QuizmasterQuestionType.PlrDeathMethodQuestion => chosenPlayer.Data.Disconnected ? GetString("Disconnected") : (Main.PlayerStates[chosenPlayer.PlayerId].deathReason == PlayerState.DeathReason.Vote ? GetString("DeathReason.Vote") : GetString("DeathReason.Kill")), - QuizmasterQuestionType.PlrDeathKillerFactionQuestion => CustomRolesHelper.GetRoleTypes(chosenPlayer.GetRealKiller().GetCustomRole()).ToString(), + QuizmasterQuestionType.PlrDeathKillerFactionQuestion => CustomRolesHelper.GetCustomRoleTeam(chosenPlayer.GetRealKiller().GetCustomRole()).ToString(), _ => "None" }; @@ -578,7 +587,6 @@ public override void FixUnsetAnswers() for (int numOfQuestionsDone = 0; numOfQuestionsDone < 3; numOfQuestionsDone++) { var prefix = ""; - if (QuizmasterQuestionType == QuizmasterQuestionType.PlrDeathKillerFactionQuestion) prefix = "Type."; if (numOfQuestionsDone == positionForRightAnswer) { AnswerLetter = new List { "A", "B", "C" }[positionForRightAnswer]; @@ -762,6 +770,9 @@ public enum QuizmasterQuestionType PlrDeathMethodQuestion, RoleAddedQuestion, PlrDeathKillerFactionQuestion, + QuizmasterCooldownQuestion, + WhoCoded, + WhoOwns, } [Obfuscation(Exclude = true)] From 2d153fcfc49fddd39614710451ada654400ad16c Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:48:45 -0500 Subject: [PATCH 093/101] fix issues with getmarkothers also added a message if /kc is off and it is used --- Patches/ChatCommandPatch.cs | 15 +++++++++++++-- Roles/Coven/Illusionist.cs | 2 +- Roles/Coven/Medusa.cs | 7 +++++-- Roles/Coven/PotionMaster.cs | 10 +++++++--- Roles/Coven/VoodooMaster.cs | 8 +++++--- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index f68a31c15..df8c65ff5 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -449,8 +449,13 @@ public static bool Prefix(ChatController __instance) case "/阵营": case "/存货阵营信息": case "/阵营信息": - if (GameStates.IsLobby || !Options.EnableKillerLeftCommand.GetBool()) break; + if (GameStates.IsLobby) break; + if (!Options.EnableKillerLeftCommand.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } var allAlivePlayers = Main.AllAlivePlayerControls; int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); @@ -2396,7 +2401,13 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can case "/阵营": case "/存货阵营信息": case "/阵营信息": - if (GameStates.IsLobby || !Options.EnableKillerLeftCommand.GetBool()) break; + if (GameStates.IsLobby) break; + + if (!Options.EnableKillerLeftCommand.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } var allAlivePlayers = Main.AllAlivePlayerControls; int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); diff --git a/Roles/Coven/Illusionist.cs b/Roles/Coven/Illusionist.cs index 0ed362352..4799b1b3e 100644 --- a/Roles/Coven/Illusionist.cs +++ b/Roles/Coven/Illusionist.cs @@ -145,7 +145,7 @@ private void OnPlayerDead(PlayerControl killer, PlayerControl deadPlayer, bool i public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { if (_Player == null) return string.Empty; - if (IllusionedPlayers[_Player.PlayerId].Contains(target.PlayerId) && seer.GetCustomRole().IsCovenTeam() && seer.PlayerId != _Player.PlayerId) + if ((IsCovIllusioned(target.PlayerId) || IsNonCovIllusioned(target.PlayerId)) && seer.GetCustomRole().IsCovenTeam() && seer.PlayerId != _Player.PlayerId) { return ColorString(GetRoleColor(CustomRoles.Illusionist), ""); } diff --git a/Roles/Coven/Medusa.cs b/Roles/Coven/Medusa.cs index b5d95fb25..a949285d1 100644 --- a/Roles/Coven/Medusa.cs +++ b/Roles/Coven/Medusa.cs @@ -4,6 +4,7 @@ using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; +using static UnityEngine.GraphicsBuffer; namespace TOHE.Roles.Coven; @@ -148,16 +149,18 @@ public static void SetStoned(PlayerControl player, IGameOptions opt) opt.SetFloat(FloatOptionNames.ImpostorLightMod, StoneVision.GetFloat()); } } - public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => StonedPlayers[seer.PlayerId].Contains(seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.Medusa), "♻") : string.Empty; + public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) => IsStoned(seer.PlayerId, seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.Medusa), "♻") : string.Empty; public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { if (_Player == null) return string.Empty; - if (StonedPlayers[_Player.PlayerId].Contains(target.PlayerId) && seer.GetCustomRole().IsCovenTeam() && seer.PlayerId != _Player.PlayerId) + if (IsStoned(seer.PlayerId, target.PlayerId) && seer.GetCustomRole().IsCovenTeam() && seer.PlayerId != _Player.PlayerId) { return ColorString(GetRoleColor(CustomRoles.Medusa), "♻"); } return string.Empty; } + public static bool IsStoned(byte pc, byte target) => StonedPlayers.TryGetValue(pc, out var stoneds) && stoneds.Contains(target); + public override void SetAbilityButtonText(HudManager hud, byte playerId) { hud.ReportButton.OverrideText(GetString("MedusaReportButtonText")); diff --git a/Roles/Coven/PotionMaster.cs b/Roles/Coven/PotionMaster.cs index 51f35c1a4..e68377c79 100644 --- a/Roles/Coven/PotionMaster.cs +++ b/Roles/Coven/PotionMaster.cs @@ -1,10 +1,12 @@ using Hazel; using InnerNet; using System.Text; +using TOHE.Roles.AddOns.Common; using UnityEngine; using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; +using static UnityEngine.ParticleSystem.PlaybackState; namespace TOHE.Roles.Coven; @@ -148,7 +150,7 @@ private void SetRitual(PlayerControl killer, PlayerControl target) } break; case 1: - if (!BarrierList[killer.PlayerId].Contains(target.PlayerId) && BarrierLimit[killer.PlayerId] > 0) + if (!IsBarriered(killer.PlayerId, target.PlayerId) && BarrierLimit[killer.PlayerId] > 0) { BarrierLimit[killer.PlayerId]--; BarrierList[killer.PlayerId].Add(target.PlayerId); @@ -212,7 +214,7 @@ public override bool KnowRoleTarget(PlayerControl seer, PlayerControl target) public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) { if (_Player == null || !_Player.IsAlive()) return false; - if (!BarrierList[_Player.PlayerId].Contains(target.PlayerId)) return false; + if (!IsBarriered(killer.PlayerId, target.PlayerId)) return false; killer.RpcGuardAndKill(target); killer.ResetKillCooldown(); @@ -243,12 +245,14 @@ public override string GetMark(PlayerControl seer, PlayerControl seen = null, bo public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { if (_Player == null) return string.Empty; - if (BarrierList[_Player.PlayerId].Contains(target.PlayerId) && seer.GetCustomRole().IsCovenTeam() && seer.PlayerId != _Player.PlayerId) + if (IsBarriered(seer.PlayerId, target.PlayerId) && seer.GetCustomRole().IsCovenTeam() && seer.PlayerId != _Player.PlayerId) { return ColorString(GetRoleColor(CustomRoles.PotionMaster), "✚"); } return string.Empty; } + public static bool IsBarriered(byte pc, byte target) => BarrierList.TryGetValue(pc, out var protectList) && protectList.Contains(target); + public override bool OthersKnowTargetRoleColor(PlayerControl seer, PlayerControl target) => KnowRoleTarget(seer, target); diff --git a/Roles/Coven/VoodooMaster.cs b/Roles/Coven/VoodooMaster.cs index a8a525205..e6bcbb35d 100644 --- a/Roles/Coven/VoodooMaster.cs +++ b/Roles/Coven/VoodooMaster.cs @@ -65,16 +65,17 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) public override void SetAbilityButtonText(HudManager hud, byte playerId) => hud.KillButton.OverrideText(GetString("ShamanButtonText")); public override string GetMark(PlayerControl seer, PlayerControl seen = null, bool isForMeeting = false) - => Dolls[seer.PlayerId].Contains(seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.VoodooMaster), "✂") : string.Empty; + => IsDoll(seer.PlayerId, seen.PlayerId) ? ColorString(GetRoleColor(CustomRoles.VoodooMaster), "✂") : string.Empty; public override string GetMarkOthers(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { if (_Player == null) return string.Empty; - if (Dolls[_Player.PlayerId].Contains(target.PlayerId) && seer.GetCustomRole().IsCovenTeam() && seer.PlayerId != _Player.PlayerId) + if (IsDoll(_Player.PlayerId, target.PlayerId) && seer.GetCustomRole().IsCovenTeam() && seer.PlayerId != _Player.PlayerId) { return ColorString(GetRoleColor(CustomRoles.VoodooMaster), "✂"); } return string.Empty; } + public static bool IsDoll(byte pc, byte target) => Dolls.TryGetValue(pc, out var dollList) && dollList.Contains(target); public override string GetProgressText(byte playerId, bool comms) => ColorString(AbilityLimit >= 1 ? GetRoleColor(CustomRoles.VoodooMaster).ShadeColor(0.25f) : Color.gray, $"({AbilityLimit})"); public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) @@ -100,6 +101,7 @@ public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerContr } private void SetDoll(PlayerControl killer, PlayerControl target) { + if (IsDoll(killer.PlayerId, target.PlayerId)) return; if (AbilityLimit > 0 && (!target.GetCustomRole().IsCovenTeam() || (target.GetCustomRole().IsCovenTeam() && CanDollCoven.GetBool()))) { Dolls[killer.PlayerId].Add(target.PlayerId); @@ -140,7 +142,7 @@ public override void AfterMeetingTasks() } public override bool CheckMurderOnOthersTarget(PlayerControl killer, PlayerControl target) { - if (!Dolls[_Player.PlayerId].Contains(target.PlayerId)) return false; + if (!IsDoll(_Player.PlayerId, target.PlayerId)) return false; if (!HasNecronomicon(_Player)) return false; if (!killer.GetCustomRole().IsCovenTeam() || (killer.GetCustomRole().IsCovenTeam() && NecroAbilityCanKillCov.GetBool())) { From 0bb0fa54f4554f4d3269651bc9b197382f2246f9 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Thu, 26 Dec 2024 19:44:48 -0500 Subject: [PATCH 094/101] conj and sacrif cant kill coven --- Roles/Coven/Conjurer.cs | 10 ++++++++++ Roles/Coven/Sacrifist.cs | 11 ++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Roles/Coven/Conjurer.cs b/Roles/Coven/Conjurer.cs index 45f818aea..70c679de3 100644 --- a/Roles/Coven/Conjurer.cs +++ b/Roles/Coven/Conjurer.cs @@ -59,6 +59,16 @@ public override void ApplyGameOptions(IGameOptions opt, byte playerId) { AURoleOptions.ShapeshifterCooldown = ConjureCooldown.GetFloat(); } + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (!CanUseKillButton(killer)) return false; + if (HasNecronomicon(killer) && !target.GetCustomRole().IsCovenTeam()) + { + return true; + } + killer.Notify(GetString("CovenDontKillOtherCoven")); + return false; + } public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl target, ref bool resetCooldown, ref bool shouldAnimate) { resetCooldown = true; diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index b3c82cbb1..ef39c6b7c 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -88,7 +88,16 @@ public override void ReceiveRPC(MessageReader reader, PlayerControl NaN) // Sacrifist shouldn't be able to kill at all but if there's solo Sacrifist the game is unwinnable so they can kill when solo public override bool CanUseKillButton(PlayerControl pc) => Main.AllAlivePlayerControls.Where(pc => pc.Is(Custom_Team.Coven)).Count() == 1; - + public override bool OnCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (!CanUseKillButton(killer)) return false; + if (HasNecronomicon(killer) && !target.GetCustomRole().IsCovenTeam()) + { + return true; + } + killer.Notify(GetString("CovenDontKillOtherCoven")); + return false; + } public override void UnShapeShiftButton(PlayerControl pc) { var rand = IRandom.Instance; From d51b4338a8a61a1cb4de7f42d79b6d2300564940 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 28 Dec 2024 15:43:35 -0500 Subject: [PATCH 095/101] questions --- Roles/Neutral/Quizmaster.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Roles/Neutral/Quizmaster.cs b/Roles/Neutral/Quizmaster.cs index 5bbc86f2e..acbcac2de 100644 --- a/Roles/Neutral/Quizmaster.cs +++ b/Roles/Neutral/Quizmaster.cs @@ -258,6 +258,12 @@ private void DoQuestion() new DeathReasonQuestion { Stage = 4, Question = "PlrDieFaction", QuizmasterQuestionType = QuizmasterQuestionType.PlrDeathKillerFactionQuestion}, new SetAnswersQuestion { Stage = 4, Question = "QuizmasterCooldown", Answer = "15", PossibleAnswers = { "15", "30", "0", "999", AURoleOptions.KillCooldown.ToString() }, QuizmasterQuestionType = QuizmasterQuestionType.QuizmasterCooldownQuestion }, // this is a level 4 because the only way to know this would be to look at the code for Quizmaster new SetAnswersQuestion { Stage = 4, Question = "WhoCoded", Answer = "Multiple People", PossibleAnswers = { "Furo", "Drakos", "Moe", "Marg", "Multiple People", "TommyXL", "Niko", "Pyro", "KARPED1EM", "Ryuk" }, QuizmasterQuestionType = QuizmasterQuestionType.WhoCoded }, + + new SetAnswersQuestion { Stage = 5, Question = "TOHEPartners", Answer = "Modded Among Us Lobbies & Purple Among Us", PossibleAnswers = { "Innersloth", "Modded Among Us Lobbies", "Purple Among Us", "Modded Among Us Lobbies & Purple Among Us", "Steam", "Twitter", "Town Of Us: Reactivated", "Moe Corporation", "Digital Bandidos" }, QuizmasterQuestionType = QuizmasterQuestionType.TOHEPartners }, + new SetAnswersQuestion { Stage = 5, Question = "TOHEEventCoordinator", Answer = "Sarha", PossibleAnswers = { "Moe", "Sarha", "Lauryn", "Jackler", "Matt", "Tasha", "Pyro", "Fish" }, QuizmasterQuestionType = QuizmasterQuestionType.TOHEEventCoordinator }, + new SetAnswersQuestion { Stage = 5, Question = "HowManyCats", Answer = "3", PossibleAnswers = { "0", "1", "2", "3", "4", "5", "6" }, QuizmasterQuestionType = QuizmasterQuestionType.HowManyCats }, // Copycat, Schrodinger's Cat, OIIAI (I want to count Jinx because of its origin in TOS2, but I won't) + new SetAnswersQuestion { Stage = 5, Question = "GuessingCommand", Answer = "Bet", PossibleAnswers = { "Nothing, it's just /bt", "Bet", "Bloodthirst", "Betray Them", "Bomb Tag", "Bad Thing" }, QuizmasterQuestionType = QuizmasterQuestionType.GuessingCommand }, + new SetAnswersQuestion { Stage = 5, Question = "NotFromTOS2", Answer = "Moon Dancer", PossibleAnswers = { "Coven Leader", "Jinx", "Marshall", "Doomsayer", "Baker", "Moon Dancer", "Pirate", "Mayor", "Veteran", "Psychic" }, QuizmasterQuestionType = QuizmasterQuestionType.NotFromTOS2 }, ]; Question = GetRandomQuestion(Questions); @@ -773,6 +779,11 @@ public enum QuizmasterQuestionType QuizmasterCooldownQuestion, WhoCoded, WhoOwns, + TOHEPartners, + TOHEEventCoordinator, + HowManyCats, + GuessingCommand, + NotFromTOS2, } [Obfuscation(Exclude = true)] From 9e5306af639a1fb7d92529aa0c9e74a687977260 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Sat, 28 Dec 2024 15:44:24 -0500 Subject: [PATCH 096/101] strings --- Resources/Lang/en_US.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 5749a9c91..5988cd155 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -4055,6 +4055,11 @@ "QuizmasterQuestions.QuizmasterCooldown": "What will Quizmaster's cooldown always be? (excluding first kill override and changes from other roles/addons)", "QuizmasterQuestions.WhoCoded": "Who coded Quizmaster?", "QuizmasterQuestions.WhoOwns": "Who owns The Enhanced Network?", + "QuizmasterQuestions.TOHEPartners": "Who are The Enhanced Network's partners?", + "QuizmasterQuestions.TOHEEventCoordinator": "Who is the Event Coordinator for The Enhanced Network?", + "QuizmasterQuestions.HowManyCats": "How many cat related roles are in the mod?", + "QuizmasterQuestions.GuessingCommand": "What does /bt actually mean?", + "QuizmasterQuestions.NotFromTOS2": "Which of these roles are NOT from Town of Salem 2?", "DeathReason.WrongAnswer": "Wrong Quiz Answer", From 7479bfae04cd604608a1668cd75dc8a12f269c7a Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:59:32 -0500 Subject: [PATCH 097/101] WhoOwns was supposed to be a stage 3 --- Roles/Neutral/Quizmaster.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Neutral/Quizmaster.cs b/Roles/Neutral/Quizmaster.cs index acbcac2de..945b20f02 100644 --- a/Roles/Neutral/Quizmaster.cs +++ b/Roles/Neutral/Quizmaster.cs @@ -250,7 +250,7 @@ private void DoQuestion() new SetAnswersQuestion { Stage = 3, Question = "WhatDoesEOgMeansInName", Answer = "Edited", PossibleAnswers = { "Edition", "Experimental", "Enhanced", "Edited" }, QuizmasterQuestionType = QuizmasterQuestionType.NameOriginQuestion }, new CountQuestion { Stage = 3, Question = "HowManyDiedFirstRound", QuizmasterQuestionType = QuizmasterQuestionType.DiedFirstRoundCountQuestion }, new CountQuestion { Stage = 3, Question = "ButtonPressedBefore", QuizmasterQuestionType = QuizmasterQuestionType.ButtonPressedBeforeThisQuestion }, - new SetAnswersQuestion { Stage = 4, Question = "WhoOwns", Answer = "Moe", PossibleAnswers = { "Lauryn", "Jackler", "Moe", "Marg", "Sarha", "laikrai", "Niko", "D1GQ", "KARPED1EM", "Matt" }, QuizmasterQuestionType = QuizmasterQuestionType.WhoOwns }, + new SetAnswersQuestion { Stage = 3, Question = "WhoOwns", Answer = "Moe", PossibleAnswers = { "Lauryn", "Jackler", "Moe", "Marg", "Sarha", "laikrai", "Niko", "D1GQ", "KARPED1EM", "Matt" }, QuizmasterQuestionType = QuizmasterQuestionType.WhoOwns }, new DeathReasonQuestion { Stage = 4, Question = "PlrDieReason", QuizmasterQuestionType = QuizmasterQuestionType.PlrDeathReasonQuestion}, new DeathReasonQuestion { Stage = 4, Question = "PlrDieMethod", QuizmasterQuestionType = QuizmasterQuestionType.PlrDeathMethodQuestion}, From 3dc8507a6c6b303f084194d38b8558671ce0502f Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Mon, 30 Dec 2024 13:24:07 -0500 Subject: [PATCH 098/101] necromancer no longer dies if target dies during revenge --- Roles/Coven/Necromancer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Coven/Necromancer.cs b/Roles/Coven/Necromancer.cs index ec1268c19..b95545b16 100644 --- a/Roles/Coven/Necromancer.cs +++ b/Roles/Coven/Necromancer.cs @@ -229,7 +229,7 @@ public static void UnAfterMeetingTasks() private static void Countdown(int seconds, PlayerControl player) { var killer = Killer; - if (Success || !player.IsAlive()) + if (Success || !player.IsAlive() || !killer.IsAlive()) { Timer = RevengeTime.GetInt(); Success = false; From 5672f157cf3d4a9d4d28bbaf33416072fcb7c2e6 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Wed, 1 Jan 2025 18:26:42 -0500 Subject: [PATCH 099/101] fix quizmaster cant set to level 5 and add shapeshift ability text for conjurer and sacrifist --- Resources/Lang/en_US.json | 4 ++++ Roles/Coven/Conjurer.cs | 11 +++++++++++ Roles/Coven/CovenManager.cs | 2 ++ Roles/Coven/Sacrifist.cs | 11 +++++++++++ Roles/Neutral/Quizmaster.cs | 4 ++-- 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 046ebde64..cf215349b 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2053,6 +2053,8 @@ "ConjurerMark": "Location marked", "ConjurerMeteor": "Meteor summoned", "ConjurerNecroMark": "Player marked", + "ConjurerMarkShapeshift": "Mark", + "ConjurerConjureShapeshift": "Conjure", "IllusionCooldown": "Illusion Cooldown", "IllusionistMaxIllusions": "Max Illusions", @@ -2096,6 +2098,8 @@ "SacrifistSwapDebuff": "Swapping with target after 3 seconds", "SacrifistVisionRevert": "Vision Reverted", "SacrifistSpeedRevert": "Speed Reverted", + "SacrifistShapeshiftButton": "Debuff", + "SacrifistNecroShapeshiftButton": "Ultimate Sacrifice", "VoodooMasterPerRound": "Voodoo Dolls per Round", "VoodooMasterCanDollCoven": "Voodoo Master can Voodoo Coven", diff --git a/Roles/Coven/Conjurer.cs b/Roles/Coven/Conjurer.cs index 70c679de3..6479aea32 100644 --- a/Roles/Coven/Conjurer.cs +++ b/Roles/Coven/Conjurer.cs @@ -129,4 +129,15 @@ public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl } return false; } + public override void SetAbilityButtonText(HudManager hud, byte playerId) + { + if (state[playerId] is ConjState.NormalMark or ConjState.NecroMark) + { + hud.AbilityButton.OverrideText(GetString("ConjurerMarkShapeshift")); + } + else if (state[playerId] is ConjState.NormalBomb or ConjState.NecroBomb) + { + hud.AbilityButton.OverrideText(GetString("ConjurerConjureShapeshift")); + } + } } diff --git a/Roles/Coven/CovenManager.cs b/Roles/Coven/CovenManager.cs index 7680e2246..bfbd16bca 100644 --- a/Roles/Coven/CovenManager.cs +++ b/Roles/Coven/CovenManager.cs @@ -171,4 +171,6 @@ public static void NecronomiconCheck() } } public static bool HasNecronomicon(PlayerControl pc) => necroHolder == pc.PlayerId; + public static bool HasNecronomicon(byte playerId) => necroHolder == playerId; + } diff --git a/Roles/Coven/Sacrifist.cs b/Roles/Coven/Sacrifist.cs index ef39c6b7c..d3f37e41a 100644 --- a/Roles/Coven/Sacrifist.cs +++ b/Roles/Coven/Sacrifist.cs @@ -352,4 +352,15 @@ public override void OnPlayerExiled(PlayerControl player, NetworkedPlayerInfo ex CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Retribution, [.. killPlayers]); } + public override void SetAbilityButtonText(HudManager hud, byte playerId) + { + if (HasNecronomicon(playerId)) + { + hud.AbilityButton.OverrideText(GetString("SacrifistNecroShapeshiftButton")); + } + else + { + hud.AbilityButton.OverrideText(GetString("SacrifistShapeshiftButton")); + } + } } diff --git a/Roles/Neutral/Quizmaster.cs b/Roles/Neutral/Quizmaster.cs index 945b20f02..5e0b72613 100644 --- a/Roles/Neutral/Quizmaster.cs +++ b/Roles/Neutral/Quizmaster.cs @@ -55,9 +55,9 @@ public override void SetupCustomOption() TabGroup tab = TabGroup.NeutralRoles; SetupSingleRoleOptions(Id, tab, CustomRoles.Quizmaster, 1); - MinQuestionDifficulty = IntegerOptionItem.Create(Id + 15, "QuizmasterSettings.MinQuestionDifficulty", new(1, 4, 1), 1, tab, false) + MinQuestionDifficulty = IntegerOptionItem.Create(Id + 15, "QuizmasterSettings.MinQuestionDifficulty", new(1, 5, 1), 1, tab, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Quizmaster]); - MaxQuestionDifficulty = IntegerOptionItem.Create(Id + 10, "QuizmasterSettings.MaxQuestionDifficulty", new(1, 4, 1), 1, tab, false) + MaxQuestionDifficulty = IntegerOptionItem.Create(Id + 10, "QuizmasterSettings.MaxQuestionDifficulty", new(1, 5, 1), 1, tab, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Quizmaster]); CanVentAfterMark = BooleanOptionItem.Create(Id + 11, "QuizmasterSettings.CanVentAfterMark", true, tab, false) .SetParent(CustomRoleSpawnChances[CustomRoles.Quizmaster]); From d9c08b2f139b5e419830a67f8856e76558a35a16 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Wed, 1 Jan 2025 20:11:06 -0500 Subject: [PATCH 100/101] conj cant kill TNA --- Roles/Coven/Conjurer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Roles/Coven/Conjurer.cs b/Roles/Coven/Conjurer.cs index 6479aea32..ce2e29ed2 100644 --- a/Roles/Coven/Conjurer.cs +++ b/Roles/Coven/Conjurer.cs @@ -92,6 +92,7 @@ public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl var dis = GetDistance(pos, player.transform.position); if (dis > ConjureRadius.GetFloat()) continue; if (player.GetCustomRole().IsCovenTeam() && !CovenDiesInBlast.GetBool()) continue; + if (player.IsTransformedNeutralApocalypse()) continue; else { player.SetDeathReason(PlayerState.DeathReason.Bombed); @@ -114,6 +115,7 @@ public override bool OnCheckShapeshift(PlayerControl shapeshifter, PlayerControl var dis = GetDistance(GetPlayerById(NecroBombHolder).transform.position, player.transform.position); if (dis > NecroRadius.GetFloat()) continue; if (player.GetCustomRole().IsCovenTeam() && !CovenDiesInBlast.GetBool()) continue; + if (player.IsTransformedNeutralApocalypse()) continue; else { player.SetDeathReason(PlayerState.DeathReason.Bombed); From 7564c6dead03ffeb6ca316dda727b44646376489 Mon Sep 17 00:00:00 2001 From: Marg <51059123+MargaretTheFool@users.noreply.github.com> Date: Wed, 1 Jan 2025 22:57:31 -0500 Subject: [PATCH 101/101] cough cough --- Roles/Neutral/Quizmaster.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roles/Neutral/Quizmaster.cs b/Roles/Neutral/Quizmaster.cs index 5e0b72613..f8cfa2eed 100644 --- a/Roles/Neutral/Quizmaster.cs +++ b/Roles/Neutral/Quizmaster.cs @@ -259,7 +259,7 @@ private void DoQuestion() new SetAnswersQuestion { Stage = 4, Question = "QuizmasterCooldown", Answer = "15", PossibleAnswers = { "15", "30", "0", "999", AURoleOptions.KillCooldown.ToString() }, QuizmasterQuestionType = QuizmasterQuestionType.QuizmasterCooldownQuestion }, // this is a level 4 because the only way to know this would be to look at the code for Quizmaster new SetAnswersQuestion { Stage = 4, Question = "WhoCoded", Answer = "Multiple People", PossibleAnswers = { "Furo", "Drakos", "Moe", "Marg", "Multiple People", "TommyXL", "Niko", "Pyro", "KARPED1EM", "Ryuk" }, QuizmasterQuestionType = QuizmasterQuestionType.WhoCoded }, - new SetAnswersQuestion { Stage = 5, Question = "TOHEPartners", Answer = "Modded Among Us Lobbies & Purple Among Us", PossibleAnswers = { "Innersloth", "Modded Among Us Lobbies", "Purple Among Us", "Modded Among Us Lobbies & Purple Among Us", "Steam", "Twitter", "Town Of Us: Reactivated", "Moe Corporation", "Digital Bandidos" }, QuizmasterQuestionType = QuizmasterQuestionType.TOHEPartners }, + new SetAnswersQuestion { Stage = 5, Question = "TOHEPartners", Answer = "Modded Among Us Lobbies", PossibleAnswers = { "Innersloth", "Modded Among Us Lobbies", "Purple Among Us", "Modded Among Us Lobbies & Purple Among Us", "Steam", "Twitter", "Town Of Us: Reactivated", "Moe Corporation", "Digital Bandidos" }, QuizmasterQuestionType = QuizmasterQuestionType.TOHEPartners }, new SetAnswersQuestion { Stage = 5, Question = "TOHEEventCoordinator", Answer = "Sarha", PossibleAnswers = { "Moe", "Sarha", "Lauryn", "Jackler", "Matt", "Tasha", "Pyro", "Fish" }, QuizmasterQuestionType = QuizmasterQuestionType.TOHEEventCoordinator }, new SetAnswersQuestion { Stage = 5, Question = "HowManyCats", Answer = "3", PossibleAnswers = { "0", "1", "2", "3", "4", "5", "6" }, QuizmasterQuestionType = QuizmasterQuestionType.HowManyCats }, // Copycat, Schrodinger's Cat, OIIAI (I want to count Jinx because of its origin in TOS2, but I won't) new SetAnswersQuestion { Stage = 5, Question = "GuessingCommand", Answer = "Bet", PossibleAnswers = { "Nothing, it's just /bt", "Bet", "Bloodthirst", "Betray Them", "Bomb Tag", "Bad Thing" }, QuizmasterQuestionType = QuizmasterQuestionType.GuessingCommand },