diff --git a/CustomRoles.cs b/CustomRoles.cs index ad5da108b..3bb18f8e0 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -3,10 +3,10 @@ namespace EHR; public enum CustomRoles { - //Default + // Default Crewmate = 0, - //Impostors (Vanilla) + // Impostors (Vanilla) Impostor, Shapeshifter, @@ -14,7 +14,7 @@ public enum CustomRoles ImpostorEHR, ShapeshifterEHR, - //Impostors + // Impostors Hacker, // Anonymous AntiAdminer, @@ -101,7 +101,7 @@ public enum CustomRoles YinYanger, Zombie, - //Crewmates (Vanilla) + // Crewmates (Vanilla) Engineer, GuardianAngel, Scientist, @@ -112,7 +112,7 @@ public enum CustomRoles GuardianAngelEHR, ScientistEHR, - // Crewmate + // Crewmates Addict, Adventurer, @@ -214,7 +214,7 @@ public enum CustomRoles SwordsMan, // Vigilante Witness, - //Neutrals + // Neutrals Agitater, Amnesiac, @@ -222,6 +222,7 @@ public enum CustomRoles Bandit, BloodKnight, Bubble, + Cherokious, Collector, Deathknight, Gamer, // Demon @@ -270,6 +271,7 @@ public enum CustomRoles Ritualist, Romantic, RuthlessRomantic, + SchrodingersCat, NSerialKiller, // Serial Killer Sidekick, SoulHunter, @@ -290,25 +292,25 @@ public enum CustomRoles WeaponMaster, Workaholic, - //SoloKombat + // Solo Kombat KB_Normal, - //FFA + // FFA Killer, - //MoveAndStop + // Move And Stop Tasker, - //HotPotato + // Hot Potato Potato, - //H&S + // H&S Hider, Seeker, Fox, Troll, - //GM + // GM GM, // ???? diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 7e1a83aec..ab99b1de3 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -232,6 +232,7 @@ internal static class CustomRolesHelper CustomRoles.Bard => CustomRoles.Impostor, CustomRoles.Swooper => CustomRoles.Impostor, CustomRoles.Crewpostor => CustomRoles.Engineer, + CustomRoles.Cherokious => CustomRoles.Engineer, CustomRoles.Observer => CustomRoles.Crewmate, CustomRoles.DovesOfNeace => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, CustomRoles.Disperser => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, @@ -341,6 +342,7 @@ internal static class CustomRolesHelper CustomRoles.Sprayer => RoleTypes.Impostor, CustomRoles.PlagueDoctor => RoleTypes.Impostor, CustomRoles.Postman => RoleTypes.Impostor, + CustomRoles.SchrodingersCat => RoleTypes.Impostor, CustomRoles.Impartial => RoleTypes.Impostor, CustomRoles.Predator => RoleTypes.Impostor, CustomRoles.Reckless => RoleTypes.Impostor, @@ -379,6 +381,7 @@ internal static class CustomRolesHelper public static bool IsNonNK(this CustomRoles role) => role is CustomRoles.Jester or CustomRoles.Postman or + CustomRoles.SchrodingersCat or CustomRoles.Impartial or CustomRoles.Predator or CustomRoles.SoulHunter or @@ -415,6 +418,7 @@ internal static class CustomRolesHelper CustomRoles.Succubus or CustomRoles.Gamer or CustomRoles.Crewpostor or + CustomRoles.Cherokious or CustomRoles.Necromancer or CustomRoles.Agitater or CustomRoles.Wraith or @@ -479,6 +483,7 @@ internal static class CustomRolesHelper CustomRoles.Amnesiac or CustomRoles.God or CustomRoles.Postman or + CustomRoles.SchrodingersCat or CustomRoles.Predator or CustomRoles.Pursuer or CustomRoles.FFF or @@ -682,7 +687,8 @@ internal static class CustomRolesHelper CustomRoles.Tornado, CustomRoles.Swiftclaw, CustomRoles.Adventurer, - CustomRoles.Sentry + CustomRoles.Sentry, + CustomRoles.Cherokious ]; public static bool OnlySpawnsWithPets(this CustomRoles role) => OnlySpawnsWithPetsRoleList.Contains(role); @@ -1179,8 +1185,10 @@ internal static class CustomRolesHelper CustomRoles.Refugee => CountTypes.Impostor, CustomRoles.Glitch => CountTypes.Glitch, CustomRoles.Spiritcaller => CountTypes.Spiritcaller, + CustomRoles.Cherokious => CountTypes.Cherokious, CustomRoles.DarkHide => DarkHide.SnatchesWin.GetBool() ? CountTypes.Crew : CountTypes.DarkHide, CustomRoles.Arsonist => !Options.ArsonistCanIgniteAnytime.GetBool() ? CountTypes.Crew : CountTypes.Arsonist, + CustomRoles.SchrodingersCat => SchrodingersCat.WinsWithCrewIfNotAttacked.GetBool() ? CountTypes.Crew : CountTypes.OutOfGame, _ => role.Is(Team.Impostor) ? CountTypes.Impostor : CountTypes.Crew }; @@ -1329,5 +1337,6 @@ public enum CountTypes Pestilence, PlagueBearer, Glitch, - Arsonist + Arsonist, + Cherokious } \ No newline at end of file diff --git a/Modules/CustomTeamManager.cs b/Modules/CustomTeamManager.cs index d4c39bc1a..1bdc4da26 100644 --- a/Modules/CustomTeamManager.cs +++ b/Modules/CustomTeamManager.cs @@ -86,26 +86,33 @@ namespace EHR.Modules { if (CustomTeams.Count == 0 || CustomTeamPlayerIds.Count == 0) return false; - CustomTeamPlayerIds.Do(x => x.Value.RemoveWhere(p => + try { - var pc = Utils.GetPlayerById(p); - return pc == null || pc.Data.Disconnected; - })); - - var aliveTeamPlayers = CustomTeamPlayerIds.ToDictionary(x => x.Key, x => x.Value); - aliveTeamPlayers.Do(x => x.Value.RemoveWhere(p => !Utils.GetPlayerById(p).IsAlive())); - - var team = aliveTeamPlayers.Keys.First(); - if (aliveTeamPlayers.Count == 1 && Main.AllAlivePlayerControls.All(x => + CustomTeamPlayerIds.Do(x => x.Value.RemoveWhere(p => { - var customTeam = GetCustomTeam(x.PlayerId); - return customTeam != null && customTeam.Equals(team); - })) + var pc = Utils.GetPlayerById(p); + return pc == null || pc.Data.Disconnected; + })); + + var aliveTeamPlayers = CustomTeamPlayerIds.ToDictionary(x => x.Key, x => x.Value); + aliveTeamPlayers.Do(x => x.Value.RemoveWhere(p => !Utils.GetPlayerById(p).IsAlive())); + + var team = aliveTeamPlayers.Keys.First(); + if (aliveTeamPlayers.Count == 1 && Main.AllAlivePlayerControls.All(x => + { + var customTeam = GetCustomTeam(x.PlayerId); + return customTeam != null && customTeam.Equals(team); + })) + { + WinnerTeam = team; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.CustomTeam); + CustomWinnerHolder.WinnerIds = aliveTeamPlayers.Values.First(); + return true; + } + } + catch { - WinnerTeam = team; - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.CustomTeam); - CustomWinnerHolder.WinnerIds = aliveTeamPlayers.Values.First(); - return true; + return false; } return false; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index c8c58e6ea..10ca2d753 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -528,6 +528,7 @@ public static class Utils case CustomRoles.Doppelganger: case CustomRoles.PlagueDoctor: case CustomRoles.Postman: + case CustomRoles.SchrodingersCat: case CustomRoles.Impartial: case CustomRoles.Predator: case CustomRoles.Reckless: @@ -606,6 +607,7 @@ public static class Utils if (ForRecompute) hasTasks = false; break; + case CustomRoles.Cherokious: case CustomRoles.Crewpostor: if (ForRecompute && !p.IsDead) hasTasks = false; @@ -805,8 +807,15 @@ public static class Utils NonCompleteColor = Workhorse.RoleColor; var NormalColor = taskState.IsTaskFinished ? TaskCompleteColor : NonCompleteColor; - if (Main.PlayerStates.TryGetValue(playerId, out var ps) && ps.MainRole == CustomRoles.Crewpostor) - NormalColor = Color.red; + if (Main.PlayerStates.TryGetValue(playerId, out var ps)) + { + NormalColor = ps.MainRole switch + { + CustomRoles.Crewpostor => Color.red, + CustomRoles.Cherokious => GetRoleColor(CustomRoles.Cherokious), + _ => NormalColor + }; + } Color TextColor = comms ? Color.gray : NormalColor; string Completed = comms ? "?" : $"{taskState.CompletedTasksCount}"; @@ -2407,6 +2416,7 @@ public static class Utils CustomRoles.Warlock => Warlock.IsCursed ? -1 : (int)Options.DefaultKillCooldown, CustomRoles.Swiftclaw => Swiftclaw.DashCD.GetInt() + (includeDuration ? Swiftclaw.DashDuration.GetInt() : 0), CustomRoles.Tiger => Tiger.EnrageCooldown.GetInt() + (includeDuration ? Tiger.EnrageDuration.GetInt() : 0), + CustomRoles.Cherokious => Cherokious.KillCooldown.GetInt(), _ => -1, }; if (CD == -1) return; @@ -2693,8 +2703,15 @@ public static class Utils NonCompleteColor = Workhorse.RoleColor; var NormalColor = taskState.IsTaskFinished ? TaskCompleteColor : NonCompleteColor; - if (Main.PlayerStates.TryGetValue(id, out var ps) && ps.MainRole == CustomRoles.Crewpostor) - NormalColor = Color.red; + if (Main.PlayerStates.TryGetValue(id, out var ps)) + { + NormalColor = ps.MainRole switch + { + CustomRoles.Crewpostor => Color.red, + CustomRoles.Cherokious => GetRoleColor(CustomRoles.Cherokious), + _ => NormalColor + }; + } Color TextColor = NormalColor; string Completed = $"{taskState.CompletedTasksCount}"; diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index cd1606b28..e69c9e5d5 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -183,6 +183,10 @@ class GameEndChecker WinnerIds.Add(pc.PlayerId); AdditionalWinnerTeams.Add(AdditionalWinners.SoulHunter); break; + case CustomRoles.SchrodingersCat when SchrodingersCat.WinsWithCrewIfNotAttacked.GetBool(): + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.SchrodingersCat); + break; } } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index ce3e24b21..b4aa55b74 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2620,6 +2620,14 @@ "SentryInfoLong": "(Crewmates):\\nAs the Sentry, you can monitor a room by petting your pet. When you pet your pet in the future, you get information from that room, including:\\n- The players currently in that room\\n- Who is dead in that room (if any)\\n- The players that used a vent in that room\\n- Whether someone shapeshifted in that room\\n\\nDepending on the settings, players may know if a room is monitored, and you may not be able to view this info during Comms Sabotages.", "Sentry.Suffix.MonitoredRoom": "This room is monitored by the {0}", "Sentry.Notify.Info": "Info about your monitored room:\\nPlayers: {0}\\nDead players: {1}\\nPlayers that used a vent: {2}\\nShapeshifters: {3}", + "SchrodingersCat": "Schrödinger's Cat", + "SchrodingersCatInfo": "Get Attacked to Get a Role", + "SchrodingersCatInfoLong": "(Neutrals):\\nAs Schrödinger's Cat, if you get attacked, you turn into the same role as your killer.\\nIf you don't get attacked, you lose, or you can win with crewmates, depending on the settings.", + "SchrodingersCat.WinsWithCrewIfNotAttacked": "Wins with crewmates if not attacked", + "SchrodingersCat.Notify.KillerRecruited": "You have recruited the {1}! {0} now has the same role as you.", + "Cherokious": "Cherokious", + "CherokiousInfo": "Do tasks to get Bullets to Kill", + "CherokiousInfoLong": "(Neutrals):\\nAs the Cherokious, you can get bullets by completing tasks. You can kill by petting your pet near someone. You can only kill if you have bullets.", "Autocrat": "Autocrat", "AutocratInfo": "TP nearest player to you by doing a task", "AutocratInfoLong": "(Crewmates):\\nAs the Autocrat, when you complete a task, you teleport the nearest player to you.", diff --git a/Roles/Crewmate/Analyzer.cs b/Roles/Crewmate/Analyst.cs similarity index 100% rename from Roles/Crewmate/Analyzer.cs rename to Roles/Crewmate/Analyst.cs diff --git a/Roles/Crewmate/Sentry.cs b/Roles/Crewmate/Sentry.cs index b42ccda3f..5992c874b 100644 --- a/Roles/Crewmate/Sentry.cs +++ b/Roles/Crewmate/Sentry.cs @@ -1,9 +1,6 @@ -using System; +using HarmonyLib; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using HarmonyLib; namespace EHR.Roles.Impostor { @@ -16,8 +13,8 @@ namespace EHR.Roles.Impostor private static OptionItem ShowInfoDuration; private static OptionItem PlayersKnowAboutCamera; private static OptionItem AbilityUseLimit; - private static OptionItem AbilityUseGainWithEachTaskCompleted; - private static OptionItem AbilityChargesWhenFinishedTasks; + public static OptionItem AbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; private PlainShipRoom MonitoredRoom; @@ -72,11 +69,14 @@ namespace EHR.Roles.Impostor pc.RpcRemoveAbilityUse(); var players = Main.AllAlivePlayerControls.Where(IsInMonitoredRoom).Select(x => Utils.ColorString(Main.PlayerColors[x.PlayerId], x.GetRealName())).Join(); - var bodies = DeadBodiesInRoom.Where(x => Utils.GetPlayerById(x) != null).Select(x => Utils.ColorString(Main.PlayerColors[x], Utils.GetPlayerById(x).GetRealName())).Join(); - var vented = PlayersVentedInRoom.Where(x => Utils.GetPlayerById(x) != null).Select(x => Utils.ColorString(Main.PlayerColors[x], Utils.GetPlayerById(x).GetRealName())).Join(); - var shifted = PlayersShiftedInRoom.Where(x => Utils.GetPlayerById(x) != null).Select(x => Utils.ColorString(Main.PlayerColors[x], Utils.GetPlayerById(x).GetRealName())).Join(); + var bodies = GetColoredNames(DeadBodiesInRoom); + var vented = GetColoredNames(PlayersVentedInRoom); + var shifted = GetColoredNames(PlayersShiftedInRoom); pc.Notify(string.Format(Translator.GetString("Sentry.Notify.Info"), players, bodies, vented, shifted), ShowInfoDuration.GetInt()); + return; + + static string GetColoredNames(IEnumerable ids) => ids.Where(x => Utils.GetPlayerById(x) != null).Select(x => Utils.ColorString(Main.PlayerColors[x], Utils.GetPlayerById(x).GetRealName())).Join(); } bool IsInMonitoredRoom(PlayerControl pc) => pc.GetPlainShipRoom().name == MonitoredRoom.name; diff --git a/Roles/Neutral/Cherokious.cs b/Roles/Neutral/Cherokious.cs new file mode 100644 index 000000000..816082bb8 --- /dev/null +++ b/Roles/Neutral/Cherokious.cs @@ -0,0 +1,46 @@ +using EHR.Patches; + +namespace EHR.Roles.Impostor +{ + internal class Cherokious : RoleBase + { + public static bool On; + public override bool IsEnable => On; + + public static OptionItem KillCooldown; + public static Options.OverrideTasksData Tasks; + + public static void SetupCustomOption() + { + const int id = 13860; + Options.SetupRoleOptions(id, TabGroup.NeutralRoles, CustomRoles.Cherokious); + KillCooldown = IntegerOptionItem.Create(id + 2, "KillCooldown", new(0, 60, 1), 15, TabGroup.NeutralRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Cherokious]) + .SetValueFormat(OptionFormat.Seconds); + Tasks = Options.OverrideTasksData.Create(id + 3, TabGroup.NeutralRoles, CustomRoles.Cherokious); + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(0); + } + + public override void Init() + { + On = false; + } + + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + pc.RpcIncreaseAbilityUseLimitBy(1); + } + + public override void OnPet(PlayerControl pc) + { + if (pc.GetAbilityUseLimit() < 1) return; + var target = ExternalRpcPetPatch.SelectKillButtonTarget(pc); + if (target != null && pc.RpcCheckAndMurder(target)) pc.RpcRemoveAbilityUse(); + } + } +} diff --git a/Roles/Neutral/SchrodingersCat.cs b/Roles/Neutral/SchrodingersCat.cs new file mode 100644 index 000000000..86e5ac650 --- /dev/null +++ b/Roles/Neutral/SchrodingersCat.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EHR.Roles.Impostor +{ + internal class SchrodingersCat : RoleBase + { + public static bool On; + public override bool IsEnable => On; + + public static OptionItem WinsWithCrewIfNotAttacked; + + public static void SetupCustomOption() + { + const int id = 13900; + Options.SetupRoleOptions(id, TabGroup.NeutralRoles, CustomRoles.SchrodingersCat); + WinsWithCrewIfNotAttacked = BooleanOptionItem.Create(id + 2, "SchrodingersCat.WinsWithCrewIfNotAttacked", true, TabGroup.NeutralRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SchrodingersCat]); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + var killerRole = killer.GetCustomRole(); + + target.RpcSetCustomRole(killerRole); + + killer.SetKillCooldown(5f); + + killer.Notify(string.Format(Translator.GetString("SchrodingersCat.Notify.KillerRecruited"), target.GetRealName(), CustomRoles.SchrodingersCat.ToColoredString())); + target.Notify(string.Format(Translator.GetString("SchrodingersCat.Notify.RecruitedByKiller"), killer.GetRealName(), killerRole.ToColoredString())); + + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); + + return false; + } + } +} diff --git a/main.cs b/main.cs index 46598b618..d306cda4c 100644 --- a/main.cs +++ b/main.cs @@ -433,6 +433,7 @@ public class Main : BasePlugin { CustomRoles.Sprayer, "#ffc038" }, { CustomRoles.PlagueDoctor, "#ff6633" }, { CustomRoles.Postman, "#00b893" }, + { CustomRoles.SchrodingersCat, "#616161" }, { CustomRoles.Impartial, "#4287f5" }, { CustomRoles.Predator, "#c73906" }, { CustomRoles.Reckless, "#6e000d" }, @@ -447,6 +448,7 @@ public class Main : BasePlugin { CustomRoles.Agitater, "#F4A460" }, { CustomRoles.BloodKnight, "#630000" }, { CustomRoles.Juggernaut, "#A41342" }, + { CustomRoles.Cherokious, "#de4b9e" }, { CustomRoles.Parasite, "#ff1919" }, { CustomRoles.Crewpostor, "#ff1919" }, { CustomRoles.Refugee, "#ff1919" }, @@ -714,6 +716,7 @@ public enum CustomWinner RuthlessRomantic = CustomRoles.RuthlessRomantic, Doppelganger = CustomRoles.Doppelganger, Imitator = CustomRoles.Imitator, + Cherokious = CustomRoles.Cherokious, Bloodlust = CustomRoles.Bloodlust } @@ -744,7 +747,8 @@ public enum AdditionalWinners Postman = CustomRoles.Postman, Impartial = CustomRoles.Impartial, Predator = CustomRoles.Predator, - SoulHunter = CustomRoles.SoulHunter + SoulHunter = CustomRoles.SoulHunter, + SchrodingersCat = CustomRoles.SchrodingersCat } public enum SuffixModes