diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 3ee5226ae8..00ab26faed 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -965,7 +965,7 @@ "BanditInfoLong": "(Neutrals):\nAs the Bandit, you can click your Kill button one time to steal a player's Add-on and twice to kill. Depending on the settings, you may instantly steal the Add-on or after the meeting starts. After the maximum number of steals is reached, you will kill normally. Additionally, if there are no stealable Add-ons on the target or the target is Stubborn, you will kill the target.\n\nKill everyone to win.\n\nNote: Cleansed, Last Impostor, and Lovers cannot be stolen.\nNote: If Bandit can Vent is on, Nimble will become unstealable.", "DoppelgangerInfoLong": "(Neutrals):\nAs the Doppelganger, use your Kill button to steal a player's identity (their name and skin) and then kill your target.\n\nKill everyone to win.\n\nNote: You cannot steal the target's identity when Camouflage is active.", "PunchingBagInfoLong": "(Neutrals):\nAs the Punching Bag, your goal is to get attacked a few times to win.\n\nYou cannot be guessed, as that adds to your attack count.", - "DoomsayerInfoLong": "(Neutrals):\nThe Doomsayer can guess the role of a certain player during the meeting.\nIf the Doomsayer guesses a certain number of roles (the number depends on the Host settings), then he wins.\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.", + "DoomsayerInfoLong": "(Neutrals):\nThe Doomsayer can guess the role of a certain player during the meeting.\nIf the Doomsayer guesses a certain number of roles (the number depends on the Host settings), then he wins.\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.\nIf \"Easy Mode\" is on, the Doomsayer can use their kill button to Observe a player and get a riddle based on that player's role type at the next meeting.", "ShroudInfoLong": "(Neutrals):\nAs the Shroud, you do not kill normally.\nInstead, use your Kill button to shroud a player.\nShrouded players kill others.\nIf the shrouded player doesn't make a kill, they'll kill themselves after a meeting.\n\nShroud sees shrouded players with a 「◈」mark next to their name.\nShrouded players who did not make a kill will also have the 「◈」mark in meetings, where they'll die if the Shroud is alive by the end of the meeting.", "WerewolfInfoLong": "(Neutrals):\nAs the Werewolf, you can kill much like any killer.\nHowever, when you kill, any nearby players also die.\nAny player who dies to this will have their death reason as Mauled.\n\nTo balance this, you have a higher Kill Cooldown than anyone else.", "ShamanInfoLong": "(Neutrals):\nAs the Shaman, you can use your Kill button to select a Voodoo Doll once per round. If the Kill button is used on you, the effect will be deflected onto the Voodoo Doll.\nIf you survive until the end, you win with the winning team.\nNote: If the killer cannot kill the chosen target, murder is canceled, but if the killer rechecks the Shaman, the killer will kill the Shaman.", @@ -3799,6 +3799,8 @@ "DCanGuessCoven": "Can Guess Coven", "DCanGuessAdt": "Can Guess Add-Ons", "DoomsayerAdvancedSettings": "Advanced Settings", + "DoomsayerEasyMode": "Easy Mode (Can Get Hints)", + "DoomsayerObserveCooldown": "Observe Cooldown", "DoomsayerMaxNumberOfGuessesPerMeeting": "Maximum number of guesses per meeting", "DoomsayerKillCorrectlyGuessedPlayers": "Kill correctly guessed players", "DoomsayerDoesNotSuicideWhenMisguessing": "Doomsayer does not suicide when misguessing", @@ -3810,6 +3812,19 @@ "DoomsayerGuessCountMsg": "You correctly guessed {0} Roles", "DoomsayerGuessCountTitle": "DOOMSAYER", "DoomsayerGuessSameRoleAgainMsg": "You tried to guess the same Role or Add-on that you guessed before", + "DoomsayerObserveTitle": "OBSERVATION RESULTS", + "DoomsayerKillButtonText": "Observe", + "DoomsayerObserveNotif": "{0} observed", + + "DoomsayerObserve.Basic": "Your observation of {0} shows that they live a mostly ordinary life.", + "DoomsayerObserve.Fear": "Your observation of {0} shows that they are feared by many.", + "DoomsayerObserve.Skilled": "Your observation of {0} shows that they are very skilled in their craft.", + "DoomsayerObserve.Dedicated": "Your observation of {0} shows that they are very dedicated to their job.", + "DoomsayerObserve.Secret": "Your observation of {0} shows that they are hiding a secret.", + "DoomsayerObserve.Dead": "Your observation of {0} shows that they are dead...", + "DoomsayerObserve.Unknown": "You are unable to comprehend your observation of {0}...", + "DoomsayerObserve.GM": "It should not be possible to be able to Observe the Game Master. If you somehow got this string, screenshot it and ping Marg so she can laugh about it.", + "DoomsayerObserve.Obvious": "Good job, you wasted your observation on {0}, considering anyone can see it with their own two eyes...", "EveryoneCanKnowMini": "Everyone can see the Mini", "CanBeEvil": "Mini can be an Impostor", diff --git a/Roles/Neutral/Doomsayer.cs b/Roles/Neutral/Doomsayer.cs index 278ad53c3e..a13321aab9 100644 --- a/Roles/Neutral/Doomsayer.cs +++ b/Roles/Neutral/Doomsayer.cs @@ -2,7 +2,10 @@ using System.Text; using TOHE.Modules; using TOHE.Roles.Core; +using TOHE.Roles.Coven; using UnityEngine; +using static TOHE.MeetingHudStartPatch; +using static TOHE.Options; using static TOHE.Translator; using static TOHE.Utils; @@ -14,7 +17,8 @@ internal class Doomsayer : RoleBase public override CustomRoles Role => CustomRoles.Doomsayer; private const int Id = 14100; public static bool HasEnabled => CustomRoleManager.HasEnabled(CustomRoles.Doomsayer); - public override CustomRoles ThisRoleBase => CustomRoles.Crewmate; + public override bool IsDesyncRole => EasyMode.GetBool(); + public override CustomRoles ThisRoleBase => EasyMode.GetBool() ? CustomRoles.Impostor : CustomRoles.Crewmate; public override Custom_RoleType ThisRoleType => Custom_RoleType.NeutralEvil; //==================================================================\\ @@ -31,8 +35,13 @@ internal class Doomsayer : RoleBase private static OptionItem MisguessRolePrevGuessRoleUntilNextMeeting; private static OptionItem DoomsayerTryHideMsg; private static OptionItem ImpostorVision; + private static OptionItem EasyMode; + private static OptionItem ObserveCooldown; private readonly HashSet GuessedRoles = []; + private static readonly Dictionary> MsgToSend = []; + private static readonly Dictionary> ObserveList = []; + private int GuessesCount = 0; private int GuessesCountPerMeeting = 0; @@ -40,23 +49,29 @@ internal class Doomsayer : RoleBase public override void SetupCustomOption() { - Options.SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Doomsayer); + SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Doomsayer); DoomsayerAmountOfGuessesToWin = IntegerOptionItem.Create(Id + 10, "DoomsayerAmountOfGuessesToWin", new(1, 10, 1), 3, TabGroup.NeutralRoles, false) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]) + .SetParent(CustomRoleSpawnChances[CustomRoles.Doomsayer]) .SetValueFormat(OptionFormat.Times); DCanGuessImpostors = BooleanOptionItem.Create(Id + 12, "DCanGuessImpostors", true, TabGroup.NeutralRoles, true) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + .SetParent(CustomRoleSpawnChances[CustomRoles.Doomsayer]); DCanGuessCrewmates = BooleanOptionItem.Create(Id + 13, "DCanGuessCrewmates", true, TabGroup.NeutralRoles, true) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + .SetParent(CustomRoleSpawnChances[CustomRoles.Doomsayer]); DCanGuessNeutrals = BooleanOptionItem.Create(Id + 14, "DCanGuessNeutrals", true, TabGroup.NeutralRoles, true) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + .SetParent(CustomRoleSpawnChances[CustomRoles.Doomsayer]); DCanGuessCoven = BooleanOptionItem.Create(Id + 26, "DCanGuessCoven", true, TabGroup.NeutralRoles, true) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + .SetParent(CustomRoleSpawnChances[CustomRoles.Doomsayer]); DCanGuessAdt = BooleanOptionItem.Create(Id + 15, "DCanGuessAdt", false, TabGroup.NeutralRoles, false) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + .SetParent(CustomRoleSpawnChances[CustomRoles.Doomsayer]); + + EasyMode = BooleanOptionItem.Create(Id + 27, "DoomsayerEasyMode", false, TabGroup.NeutralRoles, true) + .SetParent(CustomRoleSpawnChances[CustomRoles.Doomsayer]); + ObserveCooldown = FloatOptionItem.Create(Id + 29, "DoomsayerObserveCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.NeutralRoles, false).SetParent(EasyMode) + .SetValueFormat(OptionFormat.Seconds); + AdvancedSettings = BooleanOptionItem.Create(Id + 16, "DoomsayerAdvancedSettings", true, TabGroup.NeutralRoles, true) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + .SetParent(CustomRoleSpawnChances[CustomRoles.Doomsayer]); MaxNumberOfGuessesPerMeeting = IntegerOptionItem.Create(Id + 23, "DoomsayerMaxNumberOfGuessesPerMeeting", new(1, 10, 1), 3, TabGroup.NeutralRoles, false) .SetParent(AdvancedSettings); KillCorrectlyGuessedPlayers = BooleanOptionItem.Create(Id + 18, "DoomsayerKillCorrectlyGuessedPlayers", true, TabGroup.NeutralRoles, true) @@ -67,18 +82,22 @@ public override void SetupCustomOption() .SetParent(DoesNotSuicideWhenMisguessing); ImpostorVision = BooleanOptionItem.Create(Id + 25, GeneralOption.ImpostorVision, true, TabGroup.NeutralRoles, false) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + .SetParent(CustomRoleSpawnChances[CustomRoles.Doomsayer]); DoomsayerTryHideMsg = BooleanOptionItem.Create(Id + 21, "DoomsayerTryHideMsg", true, TabGroup.NeutralRoles, true) .SetColor(Color.green) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + .SetParent(CustomRoleSpawnChances[CustomRoles.Doomsayer]); } public override void Init() { CantGuess = false; + MsgToSend.Clear(); + ObserveList.Clear(); } public override void Add(byte playerId) { playerId.SetAbilityUseLimit(GuessesCount); + ObserveList[playerId] = []; + MsgToSend[playerId] = []; } public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(ImpostorVision.GetBool()); public override string GetProgressText(byte playerId, bool comms) @@ -247,4 +266,106 @@ public void SendMessageAboutGuess(PlayerControl guesser, PlayerControl playerMis }, 0.7f, "Doomsayer Guess Msg 2"); } } + public override bool CanUseKillButton(PlayerControl pc) => EasyMode.GetBool(); + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = ObserveCooldown.GetFloat(); + + public override bool ForcedCheckMurderAsKiller(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + if (!EasyMode.GetBool()) return false; + MsgToSend[killer.PlayerId].Add(string.Format(ObserveRiddleMsg(target), target.GetRealName())); + killer.Notify(string.Format(GetString("DoomsayerObserveNotif"), target.GetRealName())); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); + return false; + } + public override void SetAbilityButtonText(HudManager hud, byte playerId) + { + hud.KillButton?.OverrideText(GetString("DoomsayerKillButtonText")); + } + + public static string ObserveRiddleMsg(PlayerControl player) + { + string result = "DoomsayerObserve."; + var role = player.GetCustomRole(); + if (role == CustomRoles.GM) + { + return GetString(result + "GM"); + } + if (role.IsGhostRole()) + { + return GetString(result + "Dead"); + } + if (Main.PlayerStates[player.PlayerId].IsNecromancer || Illusionist.IsNonCovIllusioned(player.PlayerId) || role is CustomRoles.SchrodingersCat or CustomRoles.Glitch) + { + return GetString(result + "Unknown"); + } + if (role.IsVanilla() || Illusionist.IsCovIllusioned(player.PlayerId) || role == CustomRoles.Trickster) + { + return GetString(result + "Basic"); + } + if (role.IsRevealingRole(player) || role == CustomRoles.Solsticer) + { + return GetString(result + "Obvious"); + } + if (DCanGuessAdt.GetBool() && player.IsAnySubRole(sub => sub.IsConverted())) + { + return GetString(result + "Secret"); + } + if (role == CustomRoles.God) + { + return GetString(result + "Fear"); + } + switch (role.GetCustomRoleType()) + { + case Custom_RoleType.CrewmateVanilla: + case Custom_RoleType.CrewmateBasic: + case Custom_RoleType.NeutralBenign: + case Custom_RoleType.ImpostorVanilla: + result += "Basic"; + break; + case Custom_RoleType.CrewmatePower: + case Custom_RoleType.CovenPower: + case Custom_RoleType.NeutralApocalypse: + result += "Fear"; + break; + case Custom_RoleType.CrewmateSupport: + case Custom_RoleType.CovenUtility: + case Custom_RoleType.ImpostorSupport: + case Custom_RoleType.NeutralEvil: + case Custom_RoleType.NeutralChaos: + result += "Dedicated"; + break; + case Custom_RoleType.Madmate: + case Custom_RoleType.CovenTrickery: + case Custom_RoleType.ImpostorHindering: + case Custom_RoleType.ImpostorConcealing: + result += "Secret"; + break; + case Custom_RoleType.NeutralKilling: + case Custom_RoleType.CovenKilling: + case Custom_RoleType.ImpostorKilling: + case Custom_RoleType.CrewmateKilling: + result += "Skilled"; + break; + default: + result += "Unknown"; + break; + } + return GetString(result); + } + public override void OnMeetingHudStart(PlayerControl pc) + { + if (MsgToSend.ContainsKey(pc.PlayerId)) + { + foreach (var msg in MsgToSend[pc.PlayerId]) + { + AddMsg(msg, pc.PlayerId, ColorString(GetRoleColor(CustomRoles.Doomsayer), GetString("DoomsayerObserveTitle"))); + } + } + } + public override void AfterMeetingTasks() + { + MsgToSend.Clear(); + } }