From 3171969b836b4bfd7c5ca4fcaa5e5f41ffc31d16 Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Tue, 4 Nov 2025 10:44:32 +0100 Subject: [PATCH] Fix bugs, add Catalyst, Hidden, Exclusionary --- CustomRoles.cs | 3 + GameMode/BedWars.cs | 33 ++- Main.cs | 1 + Modules/AntiBlackout.cs | 3 +- Modules/Camouflague.cs | 1 + Modules/CustomRoleSelector.cs | 2 +- Modules/CustomRolesHelper.cs | 6 + Modules/ExtendedPlayerControl.cs | 2 +- .../PlayerGameOptionsSender.cs | 4 + Modules/OptionHolder.cs | 5 + Modules/Utils.cs | 19 +- Resources/Lang/en_US.json | 20 ++ Roles/AddOns/Common/Hidden.cs | 11 + Roles/Crewmate/CameraMan.cs | 2 +- Roles/Impostor/Catalyst.cs | 56 +++++ Roles/Impostor/Exclusionary.cs | 228 ++++++++++++++++++ 16 files changed, 386 insertions(+), 10 deletions(-) create mode 100644 Roles/AddOns/Common/Hidden.cs create mode 100644 Roles/Impostor/Catalyst.cs create mode 100644 Roles/Impostor/Exclusionary.cs diff --git a/CustomRoles.cs b/CustomRoles.cs index e3f45b50f..8c7bab59b 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -34,6 +34,7 @@ public enum CustomRoles Camouflager, Capitalism, Cantankerous, + Catalyst, Centralizer, Changeling, Chronomancer, @@ -55,6 +56,7 @@ public enum CustomRoles Eraser, EvilGuesser, EvilTracker, + Exclusionary, Fabricator, Fireworker, Forger, @@ -536,6 +538,7 @@ public enum CustomRoles Guesser, Haste, Haunter, // Ghost role + Hidden, Insane, Introvert, Knighted, diff --git a/GameMode/BedWars.cs b/GameMode/BedWars.cs index ab61139b1..b6c09f6fb 100644 --- a/GameMode/BedWars.cs +++ b/GameMode/BedWars.cs @@ -38,6 +38,8 @@ public static class BedWars private static float WoodenSwordDamageMultiplier = 2f; private static float IronSwordDamageMultiplier = 4f; private static float DiamondSwordDamageMultiplier = 7f; + private static bool SuddenDeath; + private static int AllBedsBrokenAfterTime = 300; private static OptionItem InventorySlotsOption; private static OptionItem SpeedPotionDurationOption; @@ -66,6 +68,8 @@ public static class BedWars private static OptionItem WoodenSwordDamageMultiplierOption; private static OptionItem IronSwordDamageMultiplierOption; private static OptionItem DiamondSwordDamageMultiplierOption; + private static OptionItem SuddenDeathOption; + private static OptionItem AllBedsBrokenAfterTimeOption; public static (Color Color, string Team) WinnerData = (Color.white, "No one wins"); @@ -204,10 +208,19 @@ public static class BedWars .SetColor(color) .SetGameMode(gameMode); - DiamondSwordDamageMultiplierOption = new FloatOptionItem(id, "BedWars.DiamondSwordDamageMultiplierOption", new(1f, 10f, 0.1f), 7f, tab) + DiamondSwordDamageMultiplierOption = new FloatOptionItem(id++, "BedWars.DiamondSwordDamageMultiplierOption", new(1f, 10f, 0.1f), 7f, tab) .SetValueFormat(OptionFormat.Multiplier) .SetColor(color) .SetGameMode(gameMode); + + SuddenDeathOption = new BooleanOptionItem(id++, "BedWars.SuddenDeathOption", false, tab) + .SetColor(color) + .SetGameMode(gameMode); + + AllBedsBrokenAfterTimeOption = new IntegerOptionItem(id, "BedWars.AllBedsBrokenAfterTimeOption", new(10, 900, 10), 300, tab) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(color) + .SetGameMode(gameMode); } public static string GetNameColor(PlayerControl player) @@ -392,6 +405,8 @@ public static class BedWars WoodenSwordDamageMultiplier = WoodenSwordDamageMultiplierOption.GetFloat(); IronSwordDamageMultiplier = IronSwordDamageMultiplierOption.GetFloat(); DiamondSwordDamageMultiplier = DiamondSwordDamageMultiplierOption.GetFloat(); + SuddenDeath = SuddenDeathOption.GetBool(); + AllBedsBrokenAfterTime = AllBedsBrokenAfterTimeOption.GetInt(); } public static IEnumerator OnGameStart() @@ -578,12 +593,18 @@ public static class BedWars { if (!AmongUsClient.Instance.AmHost || !GameStates.IsInTask || ExileController.Instance || Options.CurrentGameMode != CustomGameMode.BedWars || !Main.IntroDestroyed || GameStates.IsEnded || __instance == null || __instance.PlayerId >= 254 || Utils.GameStartTimeStamp + 25 > Utils.TimeStamp) return; - if (__instance.IsHost()) ItemGenerators.ForEach(x => x.Update()); + long now = Utils.TimeStamp; + + if (__instance.AmOwner) + { + ItemGenerators.ForEach(x => x.Update()); + + if (SuddenDeath && now >= GracePeriodEnd + AllBedsBrokenAfterTime) + AllNetObjects.Values.DoIf(x => !x.Bed.IsBroken, x => x.Bed.Broken()); + } if (!Data.TryGetValue(__instance.PlayerId, out PlayerData data)) return; - long now = Utils.TimeStamp; - if (!LastUpdate.TryGetValue(__instance.PlayerId, out long lastUpdate) || lastUpdate != now) { LastUpdate[__instance.PlayerId] = now; @@ -1764,6 +1785,8 @@ public static class BedWars if (!pc.IsAlive() || Vector2.Distance(pc.Pos(), Position) > BedBreakAndProtectRange) { Breaking.Remove(pc.PlayerId); + NameNotifyManager.Notifies.Remove(pc.PlayerId); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); yield break; } } @@ -1778,7 +1801,7 @@ public static class BedWars } } - private void Broken() + public void Broken() { BedWarsTeam team = Team; diff --git a/Main.cs b/Main.cs index 751422efe..508565b8e 100644 --- a/Main.cs +++ b/Main.cs @@ -659,6 +659,7 @@ public class Main : BasePlugin { CustomRoles.Listener, "#060270" }, { CustomRoles.Unbound, "#DFC57B" }, { CustomRoles.AntiTP, "#fcba03" }, + { CustomRoles.Hidden, "#E2EAF4" }, { CustomRoles.Looter, "#F5D866" }, { CustomRoles.Tired, "#ff1919" }, { CustomRoles.Concealer, "#ff1919" }, diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 6b7e83565..3aa83ea3e 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -110,7 +110,8 @@ public static class AntiBlackout if (pc.IsAlive()) { // Due to the role base change, we need to reset the cooldowns for abilities. - pc.RpcResetAbilityCooldown(); + if (!Utils.ShouldNotApplyAbilityCooldownAfterMeeting(pc)) + pc.RpcResetAbilityCooldown(); if (Main.AllPlayerKillCooldown.TryGetValue(pc.PlayerId, out float kcd)) { diff --git a/Modules/Camouflague.cs b/Modules/Camouflague.cs index c09489f3d..cb9c0c893 100644 --- a/Modules/Camouflague.cs +++ b/Modules/Camouflague.cs @@ -126,6 +126,7 @@ public static class Camouflage Logger.Info($"IsCamouflage: {IsCamouflage}", "CheckCamouflage"); WaitingForSkinChange = []; Main.Instance.StartCoroutine(UpdateCamouflageStatusAsync()); + if (Options.CommsCamouflageSetSameSpeed.GetBool()) Utils.MarkEveryoneDirtySettings(); if (!Utils.DoRPC) return true; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncCamouflage, SendOption.Reliable); writer.Write(IsCamouflage); diff --git a/Modules/CustomRoleSelector.cs b/Modules/CustomRoleSelector.cs index b40ce7974..560066fba 100644 --- a/Modules/CustomRoleSelector.cs +++ b/Modules/CustomRoleSelector.cs @@ -995,7 +995,7 @@ internal static class CustomRoleSelector switch (role) { - case CustomRoles.Concealer when Options.AnonymousBodies.GetBool(): + case CustomRoles.Concealer or CustomRoles.Hidden when Options.AnonymousBodies.GetBool(): case CustomRoles.Autopsy when Options.EveryoneSeesDeathReasons.GetBool(): case CustomRoles.Gravestone when Options.EveryoneSeesDeadPlayersRoles.GetBool(): case CustomRoles.Mare or CustomRoles.Glow or CustomRoles.Sleep when Main.CurrentMap == MapNames.Fungle: diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index f3d39a086..bde4c62c3 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -155,6 +155,8 @@ internal static class CustomRolesHelper CustomRoles.Psychopath => CustomRoles.Impostor, CustomRoles.Loner => CustomRoles.Impostor, CustomRoles.Postponer => CustomRoles.Impostor, + CustomRoles.Catalyst => CustomRoles.Impostor, + CustomRoles.Exclusionary => CustomRoles.Impostor, CustomRoles.Centralizer => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, CustomRoles.Venerer => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, CustomRoles.Kidnapper => CustomRoles.Shapeshifter, @@ -732,6 +734,8 @@ internal static class CustomRolesHelper CustomRoles.Psychopath or CustomRoles.Ambusher or CustomRoles.Kidnapper or + CustomRoles.Exclusionary or + CustomRoles.Catalyst or CustomRoles.Fabricator or CustomRoles.Centralizer or CustomRoles.Postponer or @@ -1653,6 +1657,7 @@ internal static class CustomRolesHelper CustomRoles.Blackmailer => RoleOptionType.Impostor_Support, CustomRoles.Camouflager => RoleOptionType.Impostor_Support, CustomRoles.Capitalism => RoleOptionType.Impostor_Support, + CustomRoles.Catalyst => RoleOptionType.Impostor_Support, CustomRoles.Centralizer => RoleOptionType.Impostor_Support, CustomRoles.Cleaner => RoleOptionType.Impostor_Support, CustomRoles.ClockBlocker => RoleOptionType.Impostor_Support, @@ -1683,6 +1688,7 @@ internal static class CustomRolesHelper CustomRoles.Duellist => RoleOptionType.Impostor_Concealing, CustomRoles.Echo => RoleOptionType.Impostor_Concealing, CustomRoles.Escapee => RoleOptionType.Impostor_Concealing, + CustomRoles.Exclusionary => RoleOptionType.Impostor_Concealing, CustomRoles.Fabricator => RoleOptionType.Impostor_Concealing, CustomRoles.Forger => RoleOptionType.Impostor_Concealing, CustomRoles.Hangman => RoleOptionType.Impostor_Concealing, diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 91fc93d71..777599dee 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1948,7 +1948,7 @@ internal static class ExtendedPlayerControl if (killer.IsLocalPlayer() && !killer.HasKillButton() && killer.PlayerId != target.PlayerId && Options.CurrentGameMode == CustomGameMode.Standard) Achievements.Type.InnocentKiller.Complete(); - if (Options.AnonymousBodies.GetBool() || realKiller.Is(CustomRoles.Concealer)) + if (Options.AnonymousBodies.GetBool() || realKiller.Is(CustomRoles.Concealer) || target.Is(CustomRoles.Hidden)) { Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; target.SyncSettings(); diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index 79c37a4fd..ff4837654 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -582,6 +582,10 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS const float limit = 3f; if (energeticIncreaseSpeed) speed *= 1.25f; if (Mathf.Approximately(speed, 0f)) speed = Main.MinSpeed; + + if (Camouflage.IsCamouflage && Options.CommsCamouflageSetSameSpeed.GetBool()) + speed = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + AURoleOptions.PlayerSpeedMod = Mathf.Clamp(speed, -limit, limit); } diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index d395bdb3a..ed7711587 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -613,6 +613,7 @@ public static class Options public static OptionItem CommsCamouflageLimitSetMaxTimes; public static OptionItem CommsCamouflageLimitMaxTimesPerGame; public static OptionItem CommsCamouflageLimitMaxTimesPerRound; + public static OptionItem CommsCamouflageSetSameSpeed; public static OptionItem DisableReportWhenCC; public static OptionItem SabotageTimeControl; public static OptionItem SkeldReactorTimeLimit; @@ -2104,6 +2105,10 @@ public static class Options .SetGameMode(CustomGameMode.Standard) .SetParent(CommsCamouflageLimitSetMaxTimes) .SetColor(new Color32(243, 96, 96, byte.MaxValue)); + + CommsCamouflageSetSameSpeed = new BooleanOptionItem(22211, "CommsCamouflageSetSameSpeed", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)); DisableReportWhenCC = new BooleanOptionItem(22300, "DisableReportWhenCC", false, TabGroup.GameSettings) .SetGameMode(CustomGameMode.Standard) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index a67cffeb0..ff10ae751 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -3326,12 +3326,21 @@ public static class Utils { Sniper { IsAim: true } => true, Centralizer { MarkedPosition: not null } => true, - Escapee { EscapeeLocation: not null } => true, + Escapee { EscapeeLocation: null } => true, Silencer when Silencer.ForSilencer.Count == 0 => true, _ => false }; } + public static bool ShouldNotApplyAbilityCooldownAfterMeeting(PlayerControl pc) + { + return pc.GetCustomRole() switch + { + CustomRoles.Escapee => true, + _ => false + }; + } + public static void AddAbilityCD(CustomRoles role, byte playerId, bool includeDuration = true) { if (Options.UsePhantomBasis.GetBool() && (!role.IsNK() || Options.UsePhantomBasisForNKs.GetBool()) && role.SimpleAbilityTrigger()) return; @@ -3586,6 +3595,14 @@ public static class Utils switch (target.GetCustomRole()) { + case CustomRoles.Catalyst when Catalyst.RemoveGivenAddonsAfterDeath.GetBool(): + foreach ((byte id, List addons) in ((Catalyst)Main.PlayerStates[target.PlayerId].Role).GivenAddons) + { + if (!Main.PlayerStates.TryGetValue(id, out var state)) continue; + addons.ForEach(state.RemoveSubRole); + } + + break; case CustomRoles.NiceSwapper when disconnect: NiceSwapper.SwapTargets = (byte.MaxValue, byte.MaxValue); break; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 6ea913417..c16e2689a 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1196,6 +1196,7 @@ "CommsCamouflageLimitSetMaxTimes": "Set Maximum Times of Camouflaging", "CommsCamouflageLimitMaxTimesPerGame": "Maximum Times of Camouflaging Per Game", "CommsCamouflageLimitMaxTimesPerRound": "Maximum Times of Camouflaging Per Round", + "CommsCamouflageSetSameSpeed": "All Players Have the Same Speed During Camouflage", "DisableReportWhenCC": "Disable body reporting while camouflaged", "EnableDebugMode": "Enable Debug Mode", "ChangeNameToRoleInfo": "Show Role Info to Unmodded Clients Round 1", @@ -5356,6 +5357,8 @@ "BedWars.WoodenSwordDamageMultiplierOption": "Wooden Sword Damage Multiplier", "BedWars.IronSwordDamageMultiplierOption": "Iron Sword Damage Multiplier", "BedWars.DiamondSwordDamageMultiplierOption": "Diamond Sword Damage Multiplier", + "BedWars.SuddenDeathOption": "Sudden Death", + "BedWars.AllBedsBrokenAfterTimeOption": "All Beds Break After", "BedWarsPlayer": "BED WARS", "BedWarsPlayerInfo": "DESTROY ALL BEDS TO WIN", "BedWarsPlayerInfoLong": "DESTROY ALL BEDS TO WIN", @@ -9247,6 +9250,23 @@ "AgitaterTargetNotify": "<#ffff00>YOU HAVE THE BOMB!!\\n<#ffa500>Pass it to someone else or die!", "AgitaterCanGetBombed": "Agitator can get the bomb", "AgitaterAutoReportBait": "Agitator Auto Report Bait", + "Exclusionary": "Exclusionary", + "ExclusionaryInfo": "Exclude Players From The World", + "ExclusionaryInfoLong": "(Impostors):\\nAs the Exclusionary, single click with your kill button on someone to exclude them (after a short delay). This will hide all players from them in their POV. Double click to kill normally.", + "Exclusionary.ExclusionDelay": "Exclusion Delay", + "Exclusionary.ExclusionDuration": "Exclusion Duration", + "Exclusionary.CooldownAfterExclusion": "Cooldown After Exclusion", + "Catalyst": "Catalyst", + "CatalystInfo": "Give Helpful Add-ons to Your Team With Each Kill", + "CatalystInfoLong": "(Impostors):\\nAs the Catalyst, with each kill you make, every impostor-teamed players gains a new helpful add-on. Depending on the settings, they may lose these add-ons after you die.", + "Catalyst.GiveAddonsToSelf": "<#ff1919>Catalyst also receives helpful add-ons", + "Catalyst.GiveOnlyEnabledAddons": "Only give enabled add-ons", + "RemoveGivenAddonsAfterDeath": "Remove all given add-ons after the <#ff1919>Catalyst dies", + "Catalyst.AddonGivenNotify": "The <#ff1919>Catalyst gave you a new add-on!", + "Hidden": "Hidden", + "HiddenInfo": "Your Body Is Anonymous", + "HiddenInfoLong": "(Add-ons):\\nYour body is anonymous.", + "Prefix.Hidden": "Hidden", "Concealer": "Concealer", "ConcealerInfo": "Your Kills are Anonymous", "ConcealerInfoLong": "(Add-ons):\\nAs the Concealer, each body of yours will be camouflaged.", diff --git a/Roles/AddOns/Common/Hidden.cs b/Roles/AddOns/Common/Hidden.cs new file mode 100644 index 000000000..38c21e2fb --- /dev/null +++ b/Roles/AddOns/Common/Hidden.cs @@ -0,0 +1,11 @@ +namespace EHR.AddOns.Common; + +public class Hidden : IAddon +{ + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(656600, CustomRoles.Hidden, canSetNum: true, teamSpawnOptions: true); + } +} \ No newline at end of file diff --git a/Roles/Crewmate/CameraMan.cs b/Roles/Crewmate/CameraMan.cs index 7580aae42..d2d80f687 100644 --- a/Roles/Crewmate/CameraMan.cs +++ b/Roles/Crewmate/CameraMan.cs @@ -56,7 +56,7 @@ public class CameraMan : RoleBase MapNames.Skeld => new(-13.5f, -5.5f), MapNames.MiraHQ => new(15.3f, 3.8f), MapNames.Polus => new(3.0f, -12.0f), - MapNames.Dleks => new(-13.5f, -5.5f), + MapNames.Dleks => new(13.5f, -5.5f), MapNames.Airship => new(5.8f, -10.8f), MapNames.Fungle => new(9.5f, 1.2f), (MapNames)6 => new(-4.23f, -33.38f), diff --git a/Roles/Impostor/Catalyst.cs b/Roles/Impostor/Catalyst.cs new file mode 100644 index 000000000..755efb7f5 --- /dev/null +++ b/Roles/Impostor/Catalyst.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Linq; + +namespace EHR.Impostor; + +public class Catalyst : RoleBase +{ + public static bool On; + private static List Addons = []; + + private static OptionItem GiveAddonsToSelf; + private static OptionItem GiveOnlyEnabledAddons; + public static OptionItem RemoveGivenAddonsAfterDeath; + + public override bool IsEnable => On; + + public Dictionary> GivenAddons; + + public override void SetupCustomOption() + { + StartSetup(656700) + .AutoSetupOption(ref GiveAddonsToSelf, true) + .AutoSetupOption(ref GiveOnlyEnabledAddons, true) + .AutoSetupOption(ref RemoveGivenAddonsAfterDeath, false); + } + + public override void Init() + { + On = false; + Addons = Options.GroupedAddons[AddonTypes.Helpful].ToList(); + if (GiveOnlyEnabledAddons.GetBool()) Addons.RemoveAll(x => !x.IsEnable()); + } + + public override void Add(byte playerId) + { + On = true; + GivenAddons = []; + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (pc.Is(Team.Impostor)) + { + if (!GiveAddonsToSelf.GetBool() && pc.PlayerId == killer.PlayerId) continue; + CustomRoles addon = Addons.FindAll(x => !pc.Is(x)).RandomElement(); + pc.RpcSetCustomRole(addon); + pc.Notify(Translator.GetString("Catalyst.AddonGivenNotify")); + + if (!GivenAddons.TryGetValue(pc.PlayerId, out var given)) GivenAddons[pc.PlayerId] = [addon]; + else given.Add(addon); + } + } + } +} \ No newline at end of file diff --git a/Roles/Impostor/Exclusionary.cs b/Roles/Impostor/Exclusionary.cs new file mode 100644 index 000000000..00b08de4f --- /dev/null +++ b/Roles/Impostor/Exclusionary.cs @@ -0,0 +1,228 @@ +using System.Collections.Generic; +using EHR.Modules; +using Hazel; + +namespace EHR.Impostor; + +public class Exclusionary : RoleBase +{ + public static bool On; + + private static OptionItem ExclusionDelay; + private static OptionItem ExclusionDuration; + private static OptionItem CooldownAfterExclusion; + + public override bool IsEnable => On; + + private List<(byte ID, long TS)> ExcludedPlayers; + + public override void SetupCustomOption() + { + StartSetup(656800) + .AutoSetupOption(ref ExclusionDelay, 5, new IntegerValueRule(0, 30, 1), OptionFormat.Seconds) + .AutoSetupOption(ref ExclusionDuration, 10, new IntegerValueRule(0, 90, 1), OptionFormat.Seconds) + .AutoSetupOption(ref CooldownAfterExclusion, 30f, new FloatValueRule(0.5f, 120f, 0.5f), OptionFormat.Seconds); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + ExcludedPlayers = []; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (ExcludedPlayers.Exists(x => x.ID == target.PlayerId)) return false; + + return killer.CheckDoubleTrigger(target, () => + { + killer.SetKillCooldown(CooldownAfterExclusion.GetFloat()); + + LateTask.New(() => + { + if (ReportDeadBodyPatch.MeetingStarted || GameStates.IsMeeting || ExileController.Instance || GameStates.IsEnded || GameStates.IsLobby || AntiBlackout.SkipTasks || target == null || !target.IsAlive() || ExcludedPlayers.Exists(x => x.ID == target.PlayerId)) return; + + ExcludedPlayers.Add((target.PlayerId, Utils.TimeStamp + ExclusionDuration.GetInt())); + + if (killer.AmOwner) + { + foreach (PlayerControl player in Main.AllAlivePlayerControls) + { + if (player.AmOwner) continue; + player.SetPet(""); + player.invisibilityAlpha = 0f; + player.cosmetics.SetPhantomRoleAlpha(player.invisibilityAlpha); + player.shouldAppearInvisible = true; + player.Visible = false; + } + + return; + } + + if (killer.IsModdedClient()) + { + Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 1); + return; + } + + var sender = CustomRpcSender.Create("Exclusionary", SendOption.Reliable); + sender.StartMessage(killer.GetClientId()); + + foreach (PlayerControl player in Main.AllAlivePlayerControls) + { + if (killer == player) continue; + + if (sender.stream.Length > 500) + { + Utils.NumSnapToCallsThisRound++; + sender.SendMessage(); + sender = CustomRpcSender.Create("Exclusionary", SendOption.Reliable); + sender.StartMessage(killer.GetClientId()); + } + + sender.StartRpc(player.NetId, RpcCalls.SetPetStr) + .Write("") + .Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)) + .EndRpc(); + sender.StartRpc(player.NetTransform.NetId, RpcCalls.SnapTo) + .WriteVector2(new Vector2(50f, 50f)) + .Write(player.NetTransform.lastSequenceId) + .EndRpc(); + sender.StartRpc(player.NetTransform.NetId, RpcCalls.SnapTo) + .WriteVector2(new Vector2(50f, 50f)) + .Write((ushort)(player.NetTransform.lastSequenceId + 16383)) + .EndRpc(); + } + + sender.SendMessage(); + Utils.NumSnapToCallsThisRound++; + }, ExclusionDelay.GetInt()); + }); + } + + public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + { + if (!On || lowLoad || !pc.IsAlive() || !ExcludedPlayers.FindFirst(x => x.ID == pc.PlayerId, out var tuple) || tuple.TS > Utils.TimeStamp) return; + ExcludedPlayers.RemoveAll(x => x.ID == pc.PlayerId); + RevertExclusion(pc); + } + + public override void OnReportDeadBody() + { + ExcludedPlayers.ForEach(x => + { + var pc = x.ID.GetPlayer(); + if (pc == null || !pc.IsAlive()) return; + RevertExclusion(pc); + }); + ExcludedPlayers.Clear(); + } + + private static void RevertExclusion(PlayerControl pc) + { + if (pc.AmOwner) + { + foreach (PlayerControl player in Main.AllAlivePlayerControls) + { + if (player.AmOwner) continue; + if (Options.UsePets.GetBool()) PetsHelper.SetPet(player, PetsHelper.GetPetId()); + player.shouldAppearInvisible = false; + player.Visible = true; + player.invisibilityAlpha = 1f; + player.cosmetics.SetPhantomRoleAlpha(player.invisibilityAlpha); + player.shouldAppearInvisible = false; + player.Visible = !player.inVent; + } + + return; + } + + if (pc.IsModdedClient()) + { + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2); + return; + } + + var sender = CustomRpcSender.Create("Exclusionary Revert", SendOption.Reliable); + sender.StartMessage(pc.GetClientId()); + + foreach (PlayerControl player in Main.AllAlivePlayerControls) + { + if (pc == player) continue; + + if (sender.stream.Length > 500) + { + Utils.NumSnapToCallsThisRound++; + sender.SendMessage(); + sender = CustomRpcSender.Create("Exclusionary Revert", SendOption.Reliable); + sender.StartMessage(pc.GetClientId()); + } + + if (Options.UsePets.GetBool()) + { + sender.StartRpc(player.NetId, RpcCalls.SetPetStr) + .Write(PetsHelper.GetPetId()) + .Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)) + .EndRpc(); + } + + sender.StartRpc(player.NetTransform.NetId, RpcCalls.SnapTo) + .WriteVector2(new Vector2(50f, 50f)) + .Write((ushort)(player.NetTransform.lastSequenceId + 32767)) + .EndRpc(); + sender.StartRpc(player.NetTransform.NetId, RpcCalls.SnapTo) + .WriteVector2(new Vector2(50f, 50f)) + .Write((ushort)(player.NetTransform.lastSequenceId + 32767 + 16383)) + .EndRpc(); + sender.StartRpc(player.NetTransform.NetId, RpcCalls.SnapTo) + .WriteVector2(player.transform.position) + .Write(player.NetTransform.lastSequenceId) + .EndRpc(); + } + + sender.SendMessage(); + Utils.NumSnapToCallsThisRound++; + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + { + foreach (PlayerControl player in Main.AllAlivePlayerControls) + { + if (player.AmOwner) continue; + player.SetPet(""); + player.invisibilityAlpha = 0f; + player.cosmetics.SetPhantomRoleAlpha(player.invisibilityAlpha); + player.shouldAppearInvisible = true; + player.Visible = false; + } + + break; + } + case 2: + { + foreach (PlayerControl player in Main.AllAlivePlayerControls) + { + if (player.AmOwner) continue; + if (Options.UsePets.GetBool()) player.SetPet(PetsHelper.GetPetId()); + player.shouldAppearInvisible = false; + player.Visible = true; + player.invisibilityAlpha = 1f; + player.cosmetics.SetPhantomRoleAlpha(player.invisibilityAlpha); + player.shouldAppearInvisible = false; + player.Visible = !player.inVent; + } + + break; + } + } + } +} \ No newline at end of file