diff --git a/CTA/CustomTeamAssigner/CustomRoles.cs b/CTA/CustomTeamAssigner/CustomRoles.cs index dc7957afe..f5b44014d 100644 --- a/CTA/CustomTeamAssigner/CustomRoles.cs +++ b/CTA/CustomTeamAssigner/CustomRoles.cs @@ -68,6 +68,7 @@ public enum CustomRoles Frightener, Gambler, Gangster, + Gatekeeper, Generator, Godfather, Greedy, diff --git a/CustomRoles.cs b/CustomRoles.cs index dc7957afe..f5b44014d 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -68,6 +68,7 @@ public enum CustomRoles Frightener, Gambler, Gangster, + Gatekeeper, Generator, Godfather, Greedy, diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index ef0025977..318c082a1 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -162,6 +162,7 @@ internal static class CustomRolesHelper CustomRoles.Exclusionary => CustomRoles.Impostor, CustomRoles.Centralizer => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, CustomRoles.Venerer => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.Gatekeeper => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, CustomRoles.Kidnapper => CustomRoles.Shapeshifter, CustomRoles.Frightener => CustomRoles.Shapeshifter, CustomRoles.Chainbinder => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, @@ -774,6 +775,7 @@ internal static class CustomRolesHelper CustomRoles.Psychopath or CustomRoles.Ambusher or CustomRoles.Kidnapper or + CustomRoles.Gatekeeper or CustomRoles.Exorcist or CustomRoles.Frightener or CustomRoles.Obstructer or @@ -1265,6 +1267,7 @@ internal static class CustomRolesHelper CustomRoles.EvilEraser => RoleOptionType.Impostor_Support, CustomRoles.Freezer => RoleOptionType.Impostor_Support, CustomRoles.Gangster => RoleOptionType.Impostor_Support, + CustomRoles.Gatekeeper => RoleOptionType.Impostor_Support, CustomRoles.Godfather => RoleOptionType.Impostor_Support, CustomRoles.Hypnotist => RoleOptionType.Impostor_Support, CustomRoles.Librarian => RoleOptionType.Impostor_Support, diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index a2466052b..cc59f3302 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1945,6 +1945,10 @@ "GangsterRecruitLimit": "Recruit limit", "GangsterRecruitmentFailure": "Target cannot be recruited", "GangsterSuccessfullyRecruited": "You successfully recruited a player", + "Gatekeeper": "Gatekeeper", + "GatekeeperInfo": "Keep Track Of People In Rooms", + "GatekeeperInfoLong": "(Impostors):\\nAs the Gatekeeper, you may pet/shapeshift to mark the room you're in. From now, you'll now see how many living players are in that room, updated in real time. Dead bodies aren't counted.", + "Gatekeeper.MaxRoomsMarkedAtOnce": "Max Rooms Marked At Once", "Generator": "Generator", "GeneratorInfo": "Power up the ship", "GeneratorInfoLong": "(Impostors):\\nAs the Generator, you generate charges every second. You can only kill/vent/shapeshift/sabotage if you have enough charges to do so. You also lose charges while shapeshifted.", diff --git a/Roles/Standard/Impostor/Support/Gatekeeper.cs b/Roles/Standard/Impostor/Support/Gatekeeper.cs new file mode 100644 index 000000000..88cb4d39a --- /dev/null +++ b/Roles/Standard/Impostor/Support/Gatekeeper.cs @@ -0,0 +1,119 @@ +using System.Collections.Generic; +using System.Linq; +using EHR.Modules; +using Hazel; + +namespace EHR.Roles; + +public class Gatekeeper : RoleBase +{ + public static bool On; + + private static OptionItem AbilityUseLimit; + private static OptionItem AbilityUseGainWithEachKill; + private static OptionItem MaxRoomsMarkedAtOnce; + + public override bool IsEnable => On; + + private Dictionary MarkedRooms = []; + private StringBuilder Suffix; + private byte GatekeeperId; + + public override void SetupCustomOption() + { + StartSetup(659200) + .AutoSetupOption(ref AbilityUseLimit, 1f, new FloatValueRule(0, 20, 0.05f), OptionFormat.Times) + .AutoSetupOption(ref AbilityUseGainWithEachKill, 1f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times) + .AutoSetupOption(ref MaxRoomsMarkedAtOnce, 5, new IntegerValueRule(1, 30, 1)); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + Suffix = new(); + MarkedRooms = []; + GatekeeperId = playerId; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat()); + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting) return true; + MarkRoom(shapeshifter); + return false; + } + + public override void OnPet(PlayerControl pc) + { + MarkRoom(pc); + } + + private void MarkRoom(PlayerControl pc) + { + if (pc.GetAbilityUseLimit() < 1f) return; + + var room = pc.GetPlainShipRoom(); + + if (room && MarkedRooms.TryAdd(room.RoomId, -1)) + { + pc.RpcRemoveAbilityUse(); + + if (MarkedRooms.Count > MaxRoomsMarkedAtOnce.GetInt()) + MarkedRooms.Remove(MarkedRooms.Keys.First()); + } + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive() || !PerSecondUpdateScheduler.ShouldRunUpdate(pc.PlayerId)) return; + + bool first = true; + bool changed = false; + var aapc = Main.CachedAlivePlayerControls(); + + Suffix.Clear(); + Suffix.Append(""); + + foreach ((SystemTypes markedRoom, int oldCount) in MarkedRooms) + { + int newCount = aapc.Count(x => x.IsInRoom(markedRoom)); + MarkedRooms[markedRoom] = newCount; + if (oldCount != newCount) changed = true; + + if (!first) Suffix.Append('\n'); + Suffix.Append("<#00a5ff>"); + Suffix.Append(Translator.GetString(markedRoom)); + Suffix.Append(""); + Suffix.Append(' '); + Suffix.Append('-'); + Suffix.Append(' '); + Suffix.Append("<#00ffff>"); + Suffix.Append(newCount); + Suffix.Append(""); + first = false; + } + + if (changed) + { + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + if (pc.IsNonHostModdedClient()) Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, MarkedRooms.Count > 5 ? Suffix.ToString().RemoveHtmlTags() : Suffix.ToString()); + } + } + + public void ReceiveRPC(MessageReader reader) + { + Suffix.Clear(); + Suffix.Append(reader.ReadString()); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != GatekeeperId || seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || meeting) return string.Empty; + return Suffix.ToString(); + } +} \ No newline at end of file