From 234c6ce9786998856aba21d328e412e7d98dec7b Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Sun, 24 Aug 2025 13:48:00 +0200 Subject: [PATCH 1/5] a bunch of fixes and additions --- CTA/CustomTeamAssigner/CustomRoles.cs | 3 +- CustomRoles.cs | 3 +- GameMode/BedWars.cs | 38 +++++--- GameMode/CaptureTheFlag.cs | 1 - GameMode/KingOfTheZones.cs | 2 +- GameMode/NaturalDisasters.cs | 2 + Main.cs | 4 + Modules/AntiBlackout.cs | 2 +- Modules/AutoHaunt.cs | 16 ++-- Modules/CustomRolesHelper.cs | 49 +++++------ Modules/CustomRpcSender.cs | 2 +- Modules/CustomSabotage.cs | 13 ++- Modules/DisableDevice.cs | 2 +- Modules/EAC.cs | 2 +- Modules/ExtendedPlayerControl.cs | 60 ++++++++----- Modules/FixedUpdateCaller.cs | 7 ++ .../PlayerGameOptionsSender.cs | 2 +- Modules/GuessManager.cs | 2 + Modules/NameColorManager.cs | 1 + Modules/OptionHolder.cs | 62 ++++++++++++- Modules/Utils.cs | 30 +++++-- Patches/ChatCommandPatch.cs | 88 ++++++++++++++++++- Patches/HudSpritePatch.cs | 2 +- Patches/MeetingHudPatch.cs | 5 +- Patches/OnGameStartedPatch.cs | 9 ++ Patches/PlayerControlPatch.cs | 14 +++ Patches/SabotageSystemPatch.cs | 3 + Resources/Lang/en_US.json | 33 ++++++- Roles/AddOns/Common/Commited.cs | 71 +++++++++++++++ Roles/Crewmate/Cleanser.cs | 7 +- Roles/Crewmate/Dictator.cs | 5 ++ Roles/Crewmate/Divinator.cs | 2 + Roles/Crewmate/Express.cs | 6 +- Roles/Crewmate/Markseeker.cs | 2 + Roles/Crewmate/Negotiator.cs | 4 +- Roles/Crewmate/NiceEraser.cs | 2 + Roles/Crewmate/Oracle.cs | 2 + Roles/Crewmate/President.cs | 2 + Roles/Crewmate/Retributionist.cs | 52 ++++++++++- Roles/Crewmate/Ricochet.cs | 2 + Roles/Crewmate/Scout.cs | 2 + Roles/Crewmate/Socialite.cs | 2 + Roles/Crewmate/Soothsayer.cs | 2 + Roles/Crewmate/Spiritualist.cs | 2 + Roles/Crewmate/Tether.cs | 2 + Roles/Crewmate/TimeMaster.cs | 53 +++++++++++ Roles/Impostor/BountyHunter.cs | 17 ++-- Roles/Impostor/Eraser.cs | 2 + Roles/Impostor/Godfather.cs | 5 +- Roles/Impostor/SerialKiller.cs | 32 +++---- Roles/Impostor/Vampire.cs | 12 +-- Roles/Neutral/Bargainer.cs | 9 +- Roles/Neutral/Cherokious.cs | 14 ++- Roles/Neutral/Doppelganger.cs | 2 + Roles/Neutral/Enderman.cs | 8 +- Roles/Neutral/Executioner.cs | 2 +- Roles/Neutral/Lawyer.cs | 1 + Roles/Neutral/Nonplus.cs | 13 ++- Roles/Neutral/Phantasm.cs | 9 ++ Roles/Neutral/PlagueBearer.cs | 1 + Roles/Neutral/Poisoner.cs | 4 + Roles/Neutral/Rogue.cs | 23 +++-- Roles/Neutral/Tiger.cs | 9 +- 63 files changed, 689 insertions(+), 151 deletions(-) create mode 100644 Roles/AddOns/Common/Commited.cs diff --git a/CTA/CustomTeamAssigner/CustomRoles.cs b/CTA/CustomTeamAssigner/CustomRoles.cs index add07710e..129c2e6c0 100644 --- a/CTA/CustomTeamAssigner/CustomRoles.cs +++ b/CTA/CustomTeamAssigner/CustomRoles.cs @@ -358,7 +358,7 @@ public enum CustomRoles Sprayer, DarkHide, // Stalker Starspawn, - Succubus, + Succubus, // Cultist Sunnyboy, Technician, Tank, @@ -477,6 +477,7 @@ public enum CustomRoles Circumvent, Cleansed, Clumsy, + Commited, Contagious, Damocles, Deadlined, diff --git a/CustomRoles.cs b/CustomRoles.cs index add07710e..129c2e6c0 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -358,7 +358,7 @@ public enum CustomRoles Sprayer, DarkHide, // Stalker Starspawn, - Succubus, + Succubus, // Cultist Sunnyboy, Technician, Tank, @@ -477,6 +477,7 @@ public enum CustomRoles Circumvent, Cleansed, Clumsy, + Commited, Contagious, Damocles, Deadlined, diff --git a/GameMode/BedWars.cs b/GameMode/BedWars.cs index fe213a42a..e96ff396e 100644 --- a/GameMode/BedWars.cs +++ b/GameMode/BedWars.cs @@ -736,9 +736,17 @@ public static class BedWars Health = MaxHealth; NameNotifyManager.Notifies.Remove(pc.PlayerId); RPC.PlaySoundRPC(pc.PlayerId, Sounds.TaskComplete); - pc.ReviveFromTemporaryExile(); - pc.RpcSetColor(Team.GetColorId()); + pc.ReviveFromTemporaryExile(sender => + { + sender.AutoStartRpc(pc.NetId, RpcCalls.SetColor) + .Write(pc.Data.NetId) + .Write(Team.GetColorId()) + .EndRpc(); + + return true; + }); pc.TP(Base.SpawnPosition); + pc.SetChatVisible(true); Utils.NotifyRoles(SpecifyTarget: pc, SendOption: SendOption.None); Reviving.Remove(pc.PlayerId); @@ -1202,7 +1210,7 @@ public static class BedWars [Item.WoodenSword] = (Item.Iron, 1), [Item.IronSword] = (Item.Gold, 3), - [Item.DiamondSword] = (Item.Emerald, 1), + [Item.DiamondSword] = (Item.Emerald, 2), [Item.IronArmor] = (Item.Gold, 6), [Item.DiamondArmor] = (Item.Emerald, 4), @@ -1397,7 +1405,7 @@ public static class BedWars [Item.DiamondSword] = new(Color.cyan, '✽', () => Translator.GetString("BedWars.Item.DiamondSword")), [Item.IronArmor] = new(Color.white, '◎', () => Translator.GetString("BedWars.Item.IronArmor")), - [Item.DiamondArmor] = new(Color.cyan, '◍', () => Translator.GetString("BedWars.Item.DiamondArmor")), + [Item.DiamondArmor] = new(Color.cyan, '⊗', () => Translator.GetString("BedWars.Item.DiamondArmor")), [Item.Pickaxe] = new(new Color32(85, 92, 133, 255), '┯', () => Translator.GetString("BedWars.Item.Pickaxe")), [Item.Axe] = new(new Color32(108, 112, 77, 255), '┭', () => Translator.GetString("BedWars.Item.Axe")), @@ -1708,7 +1716,7 @@ public static class BedWars if (newStr != str) pc.Notify(newStr, 100f, true); } - if (Vector2.Distance(pc.Pos(), Position) > BedBreakAndProtectRange) + if (!pc.IsAlive() || Vector2.Distance(pc.Pos(), Position) > BedBreakAndProtectRange) { Breaking.Remove(pc.PlayerId); yield break; @@ -1721,20 +1729,13 @@ public static class BedWars data.Inventory.Adjust(topLayer, GetNextProtectReq()); UpdateStatus(); Breaking.Remove(pc.PlayerId); - if (Layers.Count == 0) Broken(); + if (Layers.Count == 0 && data.Team != Team) Broken(); } } private void Broken() { - BedWarsTeam team = this switch - { - BlueBed => BedWarsTeam.Blue, - YellowBed => BedWarsTeam.Yellow, - GreenBed => BedWarsTeam.Green, - RedBed => BedWarsTeam.Red, - _ => throw new InvalidOperationException("Unknown bed type") - }; + BedWarsTeam team = Team; foreach ((byte id, PlayerData data) in Data) { @@ -1748,6 +1749,15 @@ public static class BedWars Despawn(); IsBroken = true; } + + private BedWarsTeam Team => this switch + { + BlueBed => BedWarsTeam.Blue, + YellowBed => BedWarsTeam.Yellow, + GreenBed => BedWarsTeam.Green, + RedBed => BedWarsTeam.Red, + _ => throw new InvalidOperationException("Unknown bed type") + }; } public static void OnTNTExplode(Vector2 position) diff --git a/GameMode/CaptureTheFlag.cs b/GameMode/CaptureTheFlag.cs index 02ece154f..c05fa2225 100644 --- a/GameMode/CaptureTheFlag.cs +++ b/GameMode/CaptureTheFlag.cs @@ -752,7 +752,6 @@ public static class CaptureTheFlag TemporarilyOutPlayers.Remove(__instance.PlayerId); __instance.ReviveFromTemporaryExile(); __instance.TP(team.GetFlagBase().Position); - __instance.SetKillCooldown(); RPC.PlaySoundRPC(__instance.PlayerId, Sounds.TaskComplete); Utils.NotifyRoles(SpecifySeer: __instance, SpecifyTarget: __instance, SendOption: SendOption.None); } diff --git a/GameMode/KingOfTheZones.cs b/GameMode/KingOfTheZones.cs index 841f9f698..22d57aab7 100644 --- a/GameMode/KingOfTheZones.cs +++ b/GameMode/KingOfTheZones.cs @@ -789,7 +789,7 @@ public static class KingOfTheZones player.ReviveFromTemporaryExile(); player.TP(RandomSpawn.SpawnMap.GetSpawnMap().Positions.ExceptBy(Zones, x => x.Key).RandomElement().Value); - player.SetKillCooldown(GetKillCooldown(player)); + LateTask.New(() => player.SetKillCooldown(GetKillCooldown(player)), 1.5f, log: false); RPC.PlaySoundRPC(player.PlayerId, Sounds.TaskComplete); Utils.NotifyRoles(SpecifyTarget: player, SendOption: SendOption.None); diff --git a/GameMode/NaturalDisasters.cs b/GameMode/NaturalDisasters.cs index 21913f60f..2f6f084c5 100644 --- a/GameMode/NaturalDisasters.cs +++ b/GameMode/NaturalDisasters.cs @@ -961,6 +961,8 @@ public static class NaturalDisasters foreach (PlayerControl pc in Main.AllAlivePlayerControls) { + if (pc.onLadder || pc.inMovingPlat || pc.MyPhysics.Animations.IsPlayingAnyLadderAnimation()) continue; + PlainShipRoom room = pc.GetPlainShipRoom(); if (room != null && CollapsedRooms.Exists(x => x == room)) diff --git a/Main.cs b/Main.cs index 58150e208..908f2d542 100644 --- a/Main.cs +++ b/Main.cs @@ -127,6 +127,7 @@ public class Main : BasePlugin public static readonly Dictionary GamesPlayed = []; public static readonly HashSet GotShieldAnimationInfoThisGame = []; public static readonly HashSet Invisible = []; + public static readonly Dictionary UserData = []; public static readonly Dictionary> HasPlayedGM = new() { @@ -150,7 +151,9 @@ public class Main : BasePlugin public static Dictionary KilledAntidote = []; public static List BrakarVoteFor = []; public static Dictionary SleuthMsgs = []; + public static Dictionary NumEmergencyMeetingsUsed = []; public static int MadmateNum; + public static uint LobbyBehaviourNetId; public static bool ShowResult = true; @@ -629,6 +632,7 @@ public class Main : BasePlugin { CustomRoles.Messenger, "#28b573" }, { CustomRoles.Dynamo, "#ebe534" }, { CustomRoles.AntiTP, "#fcba03" }, + { CustomRoles.Commited, "#f5c542" }, { CustomRoles.BananaMan, "#ffe135" }, { CustomRoles.Blind, "#666666" }, { CustomRoles.Shy, "#9582f5" }, diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 0506e3a9e..a2185792d 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -124,7 +124,7 @@ public static class AntiBlackout { SkipTasks = false; ExileControllerWrapUpPatch.AfterMeetingTasks(); - }, Math.Max(1f, Utils.CalculatePingDelay() * 2f), "Reset SkipTasks after SetRealPlayerRoles"); + }, Math.Min(3f, Math.Max(1f, Utils.CalculatePingDelay() * 2f)), "Reset SkipTasks after SetRealPlayerRoles"); }, 0.2f, "SetRealPlayerRoles - Reset Cooldowns"); } diff --git a/Modules/AutoHaunt.cs b/Modules/AutoHaunt.cs index ffb92af89..8568e0e7c 100644 --- a/Modules/AutoHaunt.cs +++ b/Modules/AutoHaunt.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using AmongUs.GameOptions; using EHR.GameMode.HideAndSeekRoles; using UnityEngine; @@ -37,13 +38,18 @@ public static class AutoHaunt { while (Main.AutoHaunt.Value) { - if (GameStates.IsInTask && !ExileController.Instance && !AntiBlackout.SkipTasks && !PlayerControl.LocalPlayer.IsAlive() && HauntMenuMinigameStartPatch.Instance != null) + if (GameStates.IsInTask && !ExileController.Instance && !AntiBlackout.SkipTasks && !PlayerControl.LocalPlayer.IsAlive() && PlayerControl.LocalPlayer.Data.RoleType is RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost) { - PlayerControl currentTarget = HauntMenuMinigameStartPatch.Instance.HauntTarget; - PlayerControl preferredTarget = GetPreferredHauntTarget(); + if (HauntMenuMinigameStartPatch.Instance != null) + { + PlayerControl currentTarget = HauntMenuMinigameStartPatch.Instance.HauntTarget; + PlayerControl preferredTarget = GetPreferredHauntTarget(); - if (preferredTarget != null && currentTarget != preferredTarget) - HauntMenuMinigameSetHauntTargetPatch.Prefix(HauntMenuMinigameStartPatch.Instance, preferredTarget); + if (preferredTarget != null && currentTarget != preferredTarget) + HauntMenuMinigameSetHauntTargetPatch.Prefix(HauntMenuMinigameStartPatch.Instance, preferredTarget); + } + else + FastDestroyableSingleton.Instance.AbilityButton.DoClick(); } yield return new WaitForSeconds(5f); diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index f04533bec..669939326 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -865,6 +865,7 @@ internal static class CustomRolesHelper CustomRoles.Godfather when Options.GodfatherCancelVote.GetBool() => true, CustomRoles.Socialite when Socialite.CancelVote.GetBool() => true, CustomRoles.Negotiator when Negotiator.CancelVote.GetBool() => true, + CustomRoles.Retributionist when Retributionist.CancelVote.GetBool() => true, CustomRoles.President => true, @@ -1034,24 +1035,25 @@ internal static class CustomRolesHelper { return role.IsAdditionRole() && (!Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> neverList) || !neverList.TryGetValue(pc.GetCustomRole(), out List bannedAddonList) || !bannedAddonList.Contains(role)) && pc.GetCustomRole() is not CustomRoles.GuardianAngelEHR and not CustomRoles.God && !pc.Is(CustomRoles.Madmate) && !pc.Is(CustomRoles.GM) && role is not CustomRoles.Lovers && !pc.Is(CustomRoles.Needy) && (!pc.HasSubRole() || pc.GetCustomSubRoles().Count < Options.NoLimitAddonsNumMax.GetInt()) && (!Options.AddonCanBeSettings.TryGetValue(role, out (OptionItem Imp, OptionItem Neutral, OptionItem Crew, OptionItem Coven) o) || ((o.Imp.GetBool() || !pc.GetCustomRole().IsImpostor()) && (o.Neutral.GetBool() || !pc.GetCustomRole().IsNeutral()) && (o.Crew.GetBool() || !pc.IsCrewmate()) && (o.Coven.GetBool() || !pc.Is(Team.Coven)))) && (!role.IsImpOnlyAddon() || (pc.IsImpostor() && !pc.Is(CustomRoles.DoubleAgent)) || (pc.Is(CustomRoles.Traitor) && Traitor.CanGetImpostorOnlyAddons.GetBool())) && role switch { + CustomRoles.Commited when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter) => false, CustomRoles.Underdog when pc.Is(CustomRoles.Mare) => false, CustomRoles.Shy when Options.DisableWhisperCommand.GetBool() => false, CustomRoles.Blocked when !pc.CanUseVent() => false, CustomRoles.Aide when pc.IsMadmate() || pc.Is(CustomRoles.Saboteur) => false, - CustomRoles.Sleuth when pc.GetCustomRole() is CustomRoles.NecroGuesser or CustomRoles.Imitator => false, + CustomRoles.Sleuth when pc.GetCustomRole() is CustomRoles.NecroGuesser or CustomRoles.Imitator or CustomRoles.Detective => false, CustomRoles.Introvert when pc.GetCustomRole() is CustomRoles.Leery or CustomRoles.Samurai or CustomRoles.Arsonist or CustomRoles.Revolutionist or CustomRoles.Farseer or CustomRoles.Scavenger or CustomRoles.Analyst => false, CustomRoles.Circumvent when pc.GetCustomRole() is CustomRoles.Swooper or CustomRoles.RiftMaker => false, CustomRoles.Oblivious when pc.Is(CustomRoles.Altruist) => false, - CustomRoles.AntiTP when pc.GetCustomRole() is CustomRoles.Transmitter or CustomRoles.Miner or CustomRoles.Escapee or CustomRoles.Tunneler => false, + CustomRoles.AntiTP when pc.GetCustomRole() is CustomRoles.Transmitter or CustomRoles.Miner or CustomRoles.Escapee or CustomRoles.Tunneler or CustomRoles.Assassin => false, CustomRoles.Swift when pc.Is(CustomRoles.Stealth) => false, - CustomRoles.BananaMan when pc.Is(CustomRoles.Disco) => false, - CustomRoles.Disco when pc.GetCustomRole() is CustomRoles.Chameleon or CustomRoles.Swooper or CustomRoles.Wraith or CustomRoles.Alchemist => false, + CustomRoles.BananaMan when pc.Is(CustomRoles.Disco) || pc.Is(CustomRoles.Doppelganger) => false, + CustomRoles.Disco when pc.GetCustomRole() is CustomRoles.Chameleon or CustomRoles.Swooper or CustomRoles.Wraith or CustomRoles.Alchemist or CustomRoles.Doppelganger => false, CustomRoles.Egoist when pc.Is(CustomRoles.Gangster) => false, - CustomRoles.Nimble when pc.Is(CustomRoles.Oxyman) => false, + CustomRoles.Nimble when pc.GetCustomRole() is CustomRoles.Oxyman or CustomRoles.EngineerEHR or CustomRoles.SabotageMaster => false, CustomRoles.Magnet when pc.Is(Team.Impostor) => false, CustomRoles.Swift when pc.Is(CustomRoles.Magnet) => false, CustomRoles.Oblivious when pc.Is(CustomRoles.Amnesiac) && Amnesiac.RememberMode.GetValue() == 0 => false, - CustomRoles.Rookie when !pc.CanUseKillButton() => false, + CustomRoles.Rookie when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter) => false, CustomRoles.Madmate when pc.Is(CustomRoles.Sidekick) => false, CustomRoles.Autopsy when pc.Is(CustomRoles.Doctor) || pc.Is(CustomRoles.Tracefinder) || pc.Is(CustomRoles.Scientist) || pc.Is(CustomRoles.ScientistEHR) || pc.Is(CustomRoles.Sunnyboy) => false, CustomRoles.Necroview when pc.Is(CustomRoles.Doctor) => false, @@ -1064,8 +1066,8 @@ internal static class CustomRolesHelper CustomRoles.TicketsStealer or CustomRoles.Swift or CustomRoles.DeadlyQuota or CustomRoles.Damocles or CustomRoles.Mare when pc.Is(CustomRoles.Bomber) || pc.Is(CustomRoles.Nuker) || pc.Is(CustomRoles.BoobyTrap) || pc.Is(CustomRoles.Capitalism) => false, CustomRoles.Torch when !pc.IsCrewmate() || pc.Is(CustomRoles.Bewilder) || pc.Is(CustomRoles.Sunglasses) || pc.Is(CustomRoles.GuardianAngelEHR) => false, CustomRoles.Bewilder when pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.GuardianAngelEHR) || pc.Is(CustomRoles.Sunglasses) => false, - CustomRoles.Dynamo when pc.Is(CustomRoles.Spurt) => false, - CustomRoles.Spurt when pc.GetCustomSubRoles().Any(x => x is CustomRoles.Dynamo or CustomRoles.Swiftclaw or CustomRoles.Giant or CustomRoles.Flashman) => false, + CustomRoles.Dynamo when pc.Is(CustomRoles.Spurt) || pc.GetCustomRole() is CustomRoles.Tank or CustomRoles.Ankylosaurus => false, + CustomRoles.Spurt when pc.GetCustomSubRoles().Any(x => x is CustomRoles.Dynamo or CustomRoles.Swiftclaw or CustomRoles.Giant or CustomRoles.Flashman) || pc.Is(CustomRoles.Ankylosaurus) => false, CustomRoles.Sunglasses when pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.GuardianAngelEHR) || pc.Is(CustomRoles.Bewilder) => false, CustomRoles.Guesser when pc.GetCustomRole() is CustomRoles.EvilGuesser or CustomRoles.NiceGuesser or CustomRoles.Doomsayer or CustomRoles.CopyCat => false, CustomRoles.Madmate when !pc.CanBeMadmate() || pc.Is(CustomRoles.Egoist) || pc.Is(CustomRoles.Rascal) => false, @@ -1073,8 +1075,8 @@ internal static class CustomRolesHelper CustomRoles.Youtuber when !pc.IsCrewmate() || pc.Is(CustomRoles.Madmate) || pc.Is(CustomRoles.Sheriff) || pc.Is(CustomRoles.GuardianAngelEHR) => false, CustomRoles.Egoist when !pc.IsImpostor() => false, CustomRoles.Damocles when pc.GetCustomRole() is CustomRoles.Bomber or CustomRoles.Nuker or CustomRoles.SerialKiller or CustomRoles.Cantankerous => false, - CustomRoles.Damocles when !pc.CanUseKillButton() => false, - CustomRoles.Flashman when pc.Is(CustomRoles.Swiftclaw) || pc.Is(CustomRoles.Giant) || pc.Is(CustomRoles.Spurt) => false, + CustomRoles.Damocles when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter) => false, + CustomRoles.Flashman when pc.Is(CustomRoles.Swiftclaw) || pc.Is(CustomRoles.Giant) || pc.Is(CustomRoles.Spurt) || pc.GetCustomRole() is CustomRoles.Tank or CustomRoles.Zombie => false, CustomRoles.Giant when pc.Is(CustomRoles.Flashman) || pc.Is(CustomRoles.Spurt) || pc.Is(CustomRoles.RoomRusher) => false, CustomRoles.Necroview when pc.Is(CustomRoles.Visionary) => false, CustomRoles.Mimic when pc.Is(CustomRoles.Mafia) => false, @@ -1090,12 +1092,9 @@ internal static class CustomRolesHelper CustomRoles.Mare when pc.Is(CustomRoles.FireWorks) => false, CustomRoles.Mare when pc.Is(CustomRoles.Swooper) => false, CustomRoles.Mare when pc.Is(CustomRoles.Vampire) => false, - CustomRoles.Torch when pc.Is(CustomRoles.Lighter) => false, - CustomRoles.Torch when pc.Is(CustomRoles.Ignitor) => false, - CustomRoles.Bewilder when pc.Is(CustomRoles.Lighter) => false, - CustomRoles.Bewilder when pc.Is(CustomRoles.Ignitor) => false, - CustomRoles.Sunglasses when pc.Is(CustomRoles.Lighter) => false, - CustomRoles.Sunglasses when pc.Is(CustomRoles.Ignitor) => false, + CustomRoles.Torch when pc.GetCustomRole() is CustomRoles.Lighter or CustomRoles.Ignitor or CustomRoles.Farseer or CustomRoles.Eclipse or CustomRoles.Decryptor => false, + CustomRoles.Bewilder when pc.GetCustomRole() is CustomRoles.Lighter or CustomRoles.Ignitor or CustomRoles.Farseer or CustomRoles.Eclipse or CustomRoles.Decryptor => false, + CustomRoles.Sunglasses when pc.GetCustomRole() is CustomRoles.Lighter or CustomRoles.Ignitor or CustomRoles.Farseer or CustomRoles.Eclipse or CustomRoles.Decryptor => false, CustomRoles.Bait when pc.Is(CustomRoles.Demolitionist) => false, CustomRoles.Trapper when pc.Is(CustomRoles.Demolitionist) => false, CustomRoles.Lovers when pc.Is(CustomRoles.Romantic) => false, @@ -1104,11 +1103,11 @@ internal static class CustomRolesHelper CustomRoles.DualPersonality when (!pc.IsImpostor() && !pc.IsCrewmate()) || pc.Is(CustomRoles.Madmate) => false, CustomRoles.Loyal when (!pc.IsImpostor() && !pc.IsCrewmate()) || pc.Is(CustomRoles.Madmate) => false, CustomRoles.Loyal when pc.IsImpostor() && !Options.ImpCanBeLoyal.GetBool() => false, - CustomRoles.Seer when pc.Is(CustomRoles.Mortician) => false, - CustomRoles.Onbound when pc.Is(CustomRoles.SuperStar) => false, + CustomRoles.Seer when pc.GetCustomRole() is CustomRoles.Mortician or CustomRoles.TimeMaster => false, + CustomRoles.Onbound when pc.GetCustomRole() is CustomRoles.SuperStar or CustomRoles.Ankylosaurus or CustomRoles.Car => false, CustomRoles.Rascal when pc.Is(CustomRoles.SuperStar) || pc.Is(CustomRoles.Madmate) => false, CustomRoles.Madmate when pc.Is(CustomRoles.SuperStar) => false, - CustomRoles.Gravestone when pc.Is(CustomRoles.SuperStar) => false, + CustomRoles.Gravestone when pc.GetCustomRole() is CustomRoles.SuperStar or CustomRoles.Innocent => false, CustomRoles.Lucky when pc.Is(CustomRoles.Guardian) => false, CustomRoles.Bait when pc.Is(CustomRoles.GuardianAngelEHR) => false, CustomRoles.Bait when pc.Is(CustomRoles.Trapper) => false, @@ -1124,17 +1123,17 @@ internal static class CustomRolesHelper CustomRoles.Swift when pc.Is(CustomRoles.Mafia) => false, CustomRoles.Reach when pc.GetCustomRole() is CustomRoles.Mafioso or CustomRoles.Evolver => false, CustomRoles.Trapper when pc.Is(CustomRoles.GuardianAngelEHR) => false, - CustomRoles.Reach when !pc.CanUseKillButton() => false, - CustomRoles.Magnet when !pc.CanUseKillButton() => false, - CustomRoles.Haste when !pc.CanUseKillButton() || !pc.CanUseImpostorVentButton() => false, + CustomRoles.Reach when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter) => false, + CustomRoles.Magnet when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter) => false, + CustomRoles.Haste when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter) || !pc.CanUseImpostorVentButton() => false, CustomRoles.Diseased when pc.Is(CustomRoles.Antidote) => false, CustomRoles.Antidote when pc.Is(CustomRoles.Diseased) => false, - CustomRoles.Flashman or CustomRoles.Giant when pc.GetCustomRole() is CustomRoles.Swooper or CustomRoles.Wraith or CustomRoles.Chameleon or CustomRoles.Alchemist => false, + CustomRoles.Flashman or CustomRoles.Giant when pc.GetCustomRole() is CustomRoles.Swooper or CustomRoles.Wraith or CustomRoles.Chameleon or CustomRoles.Alchemist or CustomRoles.Ankylosaurus => false, CustomRoles.Bait when pc.Is(CustomRoles.Unreportable) => false, CustomRoles.Busy when !pc.GetTaskState().HasTasks => false, CustomRoles.Truant when pc.Is(CustomRoles.SoulHunter) => false, CustomRoles.Nimble when !pc.IsCrewmate() => false, - CustomRoles.Physicist when !pc.IsCrewmate() || pc.GetCustomRole().IsDesyncRole() => false, + CustomRoles.Physicist when !pc.IsCrewmate() || pc.GetCustomRole().IsDesyncRole() || (pc.Is(CustomRoles.TimeMaster) && !TimeMaster.TimeMasterCanUseVitals.GetBool()) => false, CustomRoles.Finder when !pc.IsCrewmate() || pc.GetCustomRole().IsDesyncRole() => false, CustomRoles.Noisy when !pc.IsCrewmate() || pc.GetCustomRole().IsDesyncRole() => false, CustomRoles.Unreportable when pc.Is(CustomRoles.Bait) => false, @@ -1146,7 +1145,7 @@ internal static class CustomRolesHelper CustomRoles.Unlucky when pc.Is(CustomRoles.Luckey) => false, CustomRoles.Unlucky when pc.Is(CustomRoles.Lucky) => false, CustomRoles.Lucky when pc.Is(CustomRoles.Unlucky) => false, - CustomRoles.Fool when pc.Is(CustomRoles.SabotageMaster) || pc.Is(CustomRoles.GuardianAngelEHR) => false, + CustomRoles.Fool when pc.GetCustomRole() is CustomRoles.SabotageMaster or CustomRoles.GuardianAngelEHR or CustomRoles.Technician => false, CustomRoles.Bloodhound when pc.Is(CustomRoles.Oblivious) => false, CustomRoles.DoubleShot when pc.GetCustomRole() is not CustomRoles.EvilGuesser and not CustomRoles.NiceGuesser and not CustomRoles.Augur && !Options.GuesserMode.GetBool() => false, CustomRoles.DoubleShot when !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.EvilGuesser) && pc.Is(CustomRoleTypes.Impostor) && !Options.ImpostorsCanGuess.GetBool() => false, diff --git a/Modules/CustomRpcSender.cs b/Modules/CustomRpcSender.cs index 34df51439..e8bf86939 100644 --- a/Modules/CustomRpcSender.cs +++ b/Modules/CustomRpcSender.cs @@ -689,7 +689,7 @@ public static class CustomRpcSenderExtensions { senderWasCleared = false; newSender = sender; - if (seer == null || seer.Data.Disconnected || (seer.IsModdedClient() && (seer.IsHost() || Options.CurrentGameMode == CustomGameMode.Standard)) || (!SetUpRoleTextPatch.IsInIntro && GameStates.IsLobby)) return false; + if (!AmongUsClient.Instance.AmHost || seer == null || seer.Data.Disconnected || (seer.IsModdedClient() && (seer.IsHost() || Options.CurrentGameMode == CustomGameMode.Standard)) || (!SetUpRoleTextPatch.IsInIntro && GameStates.IsLobby)) return false; var hasValue = Utils.WriteSetNameRpcsToSender(ref sender, false, false, false, false, false, false, seer, [seer], [target], out senderWasCleared) && !senderWasCleared; newSender = sender; return hasValue; diff --git a/Modules/CustomSabotage.cs b/Modules/CustomSabotage.cs index 450fd1f20..4811a65cc 100644 --- a/Modules/CustomSabotage.cs +++ b/Modules/CustomSabotage.cs @@ -51,6 +51,8 @@ public abstract class CustomSabotage public static string GetAllSuffix(PlayerControl seer, PlayerControl target, bool hud, bool meeting) { + if (!AmongUsClient.Instance.AmHost) return string.Empty; + StringBuilder suffix = new(); foreach (CustomSabotage sabotage in Instances) @@ -61,7 +63,16 @@ public abstract class CustomSabotage suffix.Append($"{tempSuffix}\n"); } - return suffix.ToString().Trim(); + var result = suffix.ToString(); + + if (seer.IsNonHostModdedClient()) + { + if (NameNotifyManager.GetNameNotify(seer, out string name) && name == result) return string.Empty; + seer.Notify(result, 3f, true, false); + return string.Empty; + } + + return result.Trim(); } public static void UpdateAll() diff --git a/Modules/DisableDevice.cs b/Modules/DisableDevice.cs index 2af9711ee..9e7330c1c 100644 --- a/Modules/DisableDevice.cs +++ b/Modules/DisableDevice.cs @@ -12,7 +12,7 @@ namespace EHR; // Reference: https://github.com/ykundesu/SuperNewRoles/blob/master/SuperNewRoles/Mode/SuperHostRoles/BlockTool.cs internal static class DisableDevice { - private static readonly List DesyncComms = []; + public static readonly List DesyncComms = []; private static int frame; public static readonly Dictionary DevicePos = new() diff --git a/Modules/EAC.cs b/Modules/EAC.cs index 79b846370..81ddc8ceb 100644 --- a/Modules/EAC.cs +++ b/Modules/EAC.cs @@ -246,7 +246,7 @@ internal static class EAC return true; } - if (!resultFlags.HasFlag(MurderResultFlags.FailedError) && !resultFlags.HasFlag(MurderResultFlags.FailedProtected) && target != null) + if (!resultFlags.HasFlag(MurderResultFlags.FailedError) && !resultFlags.HasFlag(MurderResultFlags.FailedProtected) && target != null && !target.Data.IsDead) LateTask.New(() => target.RpcRevive(), 0.1f, log: false); Report(pc, "Directly Murder Player"); diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 5bbd25bb2..20e65b8f4 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection; using AmongUs.GameOptions; +using EHR.AddOns.Common; using EHR.AddOns.Crewmate; using EHR.AddOns.Impostor; using EHR.Coven; @@ -299,7 +300,7 @@ internal static class ExtendedPlayerControl } // Saves some RPC calls for vanilla servers to make innersloth's rate limit happy - public static void ReviveFromTemporaryExile(this PlayerControl player) // Only used in game modes + public static void ReviveFromTemporaryExile(this PlayerControl player, Func extraWrites = null) // Only used in game modes { if (GameStates.CurrentServerType != GameStates.ServerType.Vanilla) { @@ -313,29 +314,37 @@ internal static class ExtendedPlayerControl state.IsDead = false; state.deathReason = PlayerState.DeathReason.etc; - var sender = CustomRpcSender.Create("ReviveFromTemporaryExile", SendOption.Reliable); - var hasValue = false; - player.RpcSetRoleGlobal(RoleTypes.Crewmate); - RoleTypes newRoleType = state.MainRole.GetRoleTypes(); - - if (Options.CurrentGameMode is CustomGameMode.SoloKombat or CustomGameMode.FFA or CustomGameMode.CaptureTheFlag or CustomGameMode.KingOfTheZones or CustomGameMode.BedWars) - hasValue |= sender.RpcSetRole(player, newRoleType, player.OwnerId); - - player.ResetKillCooldown(); - LateTask.New(() => player.SetKillCooldown(), 0.2f, log: false); - - if (newRoleType is not (RoleTypes.Crewmate or RoleTypes.Impostor or RoleTypes.Noisemaker)) - hasValue |= sender.RpcResetAbilityCooldown(player); - - if (DoRPC) + LateTask.New(() => { - sender.SyncGeneralOptions(player); - hasValue = true; - } + var sender = CustomRpcSender.Create("ReviveFromTemporaryExile", SendOption.Reliable); + var hasValue = false; - sender.SendMessage(!hasValue); + RoleTypes newRoleType = state.MainRole.GetRoleTypes(); + + if (Options.CurrentGameMode is CustomGameMode.SoloKombat or CustomGameMode.FFA or CustomGameMode.CaptureTheFlag or CustomGameMode.KingOfTheZones or CustomGameMode.BedWars) + hasValue |= sender.RpcSetRole(player, newRoleType, player.OwnerId); + + player.ResetKillCooldown(); + + if (Options.CurrentGameMode != CustomGameMode.KingOfTheZones) + LateTask.New(() => player.SetKillCooldown(), 0.2f, log: false); + + if (newRoleType is not (RoleTypes.Crewmate or RoleTypes.Impostor or RoleTypes.Noisemaker)) + hasValue |= sender.RpcResetAbilityCooldown(player); + + if (DoRPC) + { + sender.SyncGeneralOptions(player); + hasValue = true; + } + + if (extraWrites != null) + hasValue |= extraWrites(sender); + + sender.SendMessage(!hasValue); + }, 1f, log: false); } // If you use vanilla RpcSetRole, it will block further SetRole calls until the next game starts. @@ -904,11 +913,14 @@ internal static class ExtendedPlayerControl { if (player == null) return; - Logger.Info($"{player.GetNameWithRole()}'s KCD set to {(Math.Abs(time - -1f) < 0.5f ? Main.AllPlayerKillCooldown[player.PlayerId] : time)}s", "SetKCD"); + if (!Mathf.Approximately(time, -1f) && Commited.ReduceKCD.TryGetValue(player.PlayerId, out float reduction)) + time = Math.Max(time - reduction, 0.01f); + + Logger.Info($"{player.GetNameWithRole()}'s KCD set to {(time < 0f ? Main.AllPlayerKillCooldown[player.PlayerId] : time)}s", "SetKCD"); if (player.GetCustomRole().UsesPetInsteadOfKill()) { - if (Math.Abs(time - -1f) < 0.5f) + if (time < 0f) player.AddKCDAsAbilityCD(); else player.AddAbilityCD((int)Math.Round(time)); @@ -1714,6 +1726,9 @@ internal static class ExtendedPlayerControl Logger.Info($"KCD of player set to {Main.AllPlayerKillCooldown[player.PlayerId]}", "Antidote"); } + if (Commited.ReduceKCD.TryGetValue(player.PlayerId, out float reduction)) + Main.AllPlayerKillCooldown[player.PlayerId] -= reduction; + if (sync) player.SyncSettings(); } @@ -2168,6 +2183,7 @@ internal static class ExtendedPlayerControl if (ChatCommands.IsPlayerModerator(pc.FriendCode)) return true; if (ChatCommands.IsPlayerVIP(pc.FriendCode)) return true; if (PrivateTagManager.Tags.ContainsKey(pc.FriendCode)) return true; + if (Main.UserData.TryGetValue(pc.FriendCode, out Options.UserData userData) && !string.IsNullOrWhiteSpace(userData.Tag) && userData.Tag.Length > 0) return true; ClientData client = pc.GetClient(); return client != null && FastDestroyableSingleton.Instance.IsPlayerFriend(client.ProductUserId); diff --git a/Modules/FixedUpdateCaller.cs b/Modules/FixedUpdateCaller.cs index b0b316cda..95f68cd83 100644 --- a/Modules/FixedUpdateCaller.cs +++ b/Modules/FixedUpdateCaller.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using EHR.Crewmate; using EHR.Patches; using HarmonyLib; using InnerNet; @@ -95,6 +96,12 @@ public static class FixedUpdateCaller } catch { } + try + { + if (CopyCat.Instances.Count > 0) CopyCat.Instances.RemoveAll(x => x.CopyCatPC == null); + } + catch { } + bool lobby = GameStates.IsLobby; if (lobby || (Main.IntroDestroyed && GameStates.InGame && !GameStates.IsMeeting && !ExileController.Instance && !AntiBlackout.SkipTasks)) diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index 209d37fa7..67d176ee7 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -368,7 +368,7 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS case CustomRoles.Mare when Options.MareHasIncreasedSpeed.GetBool(): Main.AllPlayerSpeed[player.PlayerId] = Options.MareSpeedDuringLightsOut.GetFloat(); break; - case CustomRoles.Sleep when Utils.IsActive(SystemTypes.Electrical): + case CustomRoles.Sleep when player.IsAlive() && Utils.IsActive(SystemTypes.Electrical): SetBlind(); Main.AllPlayerSpeed[player.PlayerId] = Main.MinSpeed; break; diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 3d1ae4eef..5d31f4192 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -461,6 +461,8 @@ public static class GuessManager Main.GuesserGuessed[pc.PlayerId]++; Main.GuesserGuessedMeeting[pc.PlayerId]++; + if (pc.IsHost()) Utils.FlashColor(guesserSuicide ? new(1f, 0f, 0f, 0.3f) : new(0f, 1f, 0f, 0.3f)); + if (Main.PlayerStates[pc.PlayerId].Role is NecroGuesser ng) { if (!guesserSuicide) ng.GuessedPlayers++; diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index 532e0f869..80252921d 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -140,6 +140,7 @@ public static class NameColorManager // Check if the seer can see the target's role color color = seerRole switch { + CustomRoles.PlagueBearer when PlagueBearer.IsPlagued(seer.PlayerId, target.PlayerId) => "000000", CustomRoles.Executioner when Executioner.Target.TryGetValue(seer.PlayerId, out byte exeTarget) && exeTarget == target.PlayerId => "000000", CustomRoles.Gangster when target.Is(CustomRoles.Madmate) => Main.RoleColors[CustomRoles.Madmate], CustomRoles.Crewpostor when target.Is(CustomRoleTypes.Impostor) && Options.CrewpostorKnowsAllies.GetBool() => Main.ImpostorColor, diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index c6c625dbd..081a0ba1e 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -2,8 +2,10 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; using System.Reflection; +using System.Text.Json; using EHR.AddOns; using EHR.AddOns.GhostRoles; using EHR.Modules; @@ -173,7 +175,9 @@ public static class Options public static readonly string[] PetToAssign = [ + "pet_Goose", "pet_Bedcrab", + "pet_DancingSkeletonPet", "pet_BredPet", "pet_YuleGoatPet", "pet_Bush", @@ -181,9 +185,12 @@ public static class Options "pet_ChewiePet", "pet_clank", "pet_coaltonpet", + "pet_Creb", "pet_Cube", + "pet_lny_dragon", "pet_Doggy", "pet_Ellie", + "pet_Strawb", "pet_frankendog", "pet_D2GhostPet", "pet_test", @@ -194,13 +201,23 @@ public static class Options "pet_Alien", "pet_poro", "pet_HamPet", + "pet_Crow", "pet_Lava", "pet_Crewmate", + "pet_Mister", + "pet_nancy", + "pet_napstamate", + "pet_Pip", + "pet_pocketCircuitCar", "pet_D2PoukaPet", "pet_Pusheen", + "pet_Pate", + "pet_Rammy", "pet_Robot", "pet_Snow", + "pet_spaceCat", "pet_Squig", + "pet_Stormy", "pet_nuggetPet", "pet_Charles_Red", "pet_UFO", @@ -907,6 +924,7 @@ public static class Options Logger.Info("Options.Load End", "Options"); GroupOptions(); GroupAddons(); + LoadUserData(); Achievements.LoadAllData(); OptionShower.LastText = Translator.GetString("Loading"); @@ -1127,6 +1145,48 @@ public static class Options .ToDictionary(x => x.Key, x => x.Select(y => Enum.Parse(y.GetType().Name, true)).ToList()); } + public static void LoadUserData() + { + try + { + Main.UserData.Clear(); + + var path = $"{Main.DataPath}/EHR_DATA/UserData"; + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + File.WriteAllText(path + "/friendcode#1234.txt", JsonSerializer.Serialize(new UserData(), new JsonSerializerOptions { WriteIndented = true })); + } + + foreach (string file in Directory.GetFiles(path, "*.txt")) + { + try + { + string content = File.ReadAllText(file); + var userData = JsonSerializer.Deserialize(content); + if (userData == null) throw new FormatException($"The data in {file} was not in the correct format."); + string fileName = Path.GetFileNameWithoutExtension(file); + Main.UserData[fileName] = userData; + } + catch (Exception e) + { + Logger.Error($"Failed to load user data from {file}", "Options"); + Utils.ThrowException(e); + } + } + } + catch (Exception e) { Utils.ThrowException(e); } + } + + public class UserData + { + public bool Vip { get; init; } + public bool Moderator { get; init; } + public bool Admin { get; init; } + public string Tag { get; init; } + } + public static VoteMode GetWhenSkipVote() { return (VoteMode)WhenSkipVote.GetValue(); @@ -2280,7 +2340,7 @@ public static class Options .SetHeader(true) .SetColor(new Color32(60, 0, 255, byte.MaxValue)); - PetToAssignToEveryone = new StringOptionItem(23854, "PetToAssign", PetToAssign, 24, TabGroup.TaskSettings) + PetToAssignToEveryone = new StringOptionItem(23854, "PetToAssign", PetToAssign, 18, TabGroup.TaskSettings) .SetParent(UsePets) .SetColor(new Color32(60, 0, 255, byte.MaxValue)); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 9edc18236..1dd0e1548 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2068,8 +2068,9 @@ public static class Utils bool vip = ChatCommands.IsPlayerVIP(player.FriendCode); bool hasTag = devUser.HasTag(); bool hasPrivateTag = PrivateTagManager.Tags.TryGetValue(player.FriendCode, out string privateTag); + bool hasTagInUserData = Main.UserData.TryGetValue(player.FriendCode, out Options.UserData userData) && !string.IsNullOrWhiteSpace(userData.Tag) && userData.Tag.Length > 0; - if (!player.AmOwner && !hasTag && !mod && !vip && !hasPrivateTag && !DirtyName.Contains(player.PlayerId)) return false; + if (!player.AmOwner && !hasTag && !mod && !vip && !hasPrivateTag && !hasTagInUserData && !DirtyName.Contains(player.PlayerId)) return false; if (!Main.AllPlayerNames.TryGetValue(player.PlayerId, out name)) return false; if (Main.NickName != string.Empty && player.AmOwner) name = Main.NickName; @@ -2114,9 +2115,9 @@ public static class Utils }; } - if (hasTag || mod || vip || hasPrivateTag) + if (hasTag || mod || vip || hasPrivateTag || hasTagInUserData) { - string pTag = hasPrivateTag ? privateTag : string.Empty; + string pTag = hasPrivateTag ? privateTag : hasTagInUserData ? userData.Tag : string.Empty; string tag = hasTag ? devUser.GetTag() : string.Empty; if (tag == "null") tag = string.Empty; @@ -2332,7 +2333,7 @@ public static class Utils public static IEnumerator NotifyEveryoneAsync(int speed = 2, bool noCache = true) { - if (GameStates.IsMeeting) yield break; + if (!AmongUsClient.Instance.AmHost || GameStates.IsMeeting) yield break; var count = 0; PlayerControl[] aapc = Main.AllAlivePlayerControls; @@ -2355,7 +2356,8 @@ public static class Utils { try { - if (!SetUpRoleTextPatch.IsInIntro && ((SpecifySeer != null && SpecifySeer.IsModdedClient() && (Options.CurrentGameMode == CustomGameMode.Standard || SpecifySeer.IsHost())) || !AmongUsClient.Instance.AmHost || (GameStates.IsMeeting && !ForMeeting) || GameStates.IsLobby)) return; + if (!AmongUsClient.Instance.AmHost) return; + if (!SetUpRoleTextPatch.IsInIntro && ((SpecifySeer != null && SpecifySeer.IsModdedClient() && (Options.CurrentGameMode == CustomGameMode.Standard || SpecifySeer.IsHost())) || (GameStates.IsMeeting && !ForMeeting) || GameStates.IsLobby)) return; PlayerControl[] apc = Main.AllPlayerControls; PlayerControl[] seerList = SpecifySeer != null ? [SpecifySeer] : apc; @@ -2714,6 +2716,7 @@ public static class Utils TargetMark.Append(Witch.GetSpelledMark(target.PlayerId, forMeeting)); if (forMeeting) TargetMark.Append(Wasp.GetStungMark(target.PlayerId)); if (forMeeting) TargetMark.Append(SpellCaster.HasSpelledMark(seer.PlayerId) ? ColorString(Team.Coven.GetColor(), "\u25c0") : string.Empty); + if (forMeeting) TargetMark.Append(Commited.GetMark(seer, target)); if (target.Is(CustomRoles.SuperStar) && Options.EveryOneKnowSuperStar.GetBool()) TargetMark.Append(ColorString(GetRoleColor(CustomRoles.SuperStar), "★")); @@ -2731,7 +2734,6 @@ public static class Utils { case CustomRoles.PlagueBearer when PlagueBearer.IsPlagued(seer.PlayerId, target.PlayerId): TargetMark.Append($"●"); - PlagueBearer.SendRPC(seer, target); break; case CustomRoles.Arsonist: if (seer.IsDousedPlayer(target)) @@ -3005,6 +3007,8 @@ public static class Utils public static bool RpcChangeSkin(PlayerControl pc, NetworkedPlayerInfo.PlayerOutfit newOutfit, CustomRpcSender writer = null, SendOption sendOption = SendOption.Reliable) { + if (!AmongUsClient.Instance.AmHost) return false; + if (pc.Is(CustomRoles.BananaMan)) newOutfit = BananaMan.GetOutfit(Main.AllPlayerNames.GetValueOrDefault(pc.PlayerId, "Banana")); @@ -3364,6 +3368,20 @@ public static class Utils CustomNetObject.AfterMeeting(); RPCHandlerPatch.RemoveExpiredWhiteList(); + + LateTask.New(() => + { + if (GameStates.IsEnded) return; + MessageWriter writer = MessageWriter.Get(SendOption.Reliable); + writer.StartMessage(5); + writer.Write(AmongUsClient.Instance.GameId); + writer.StartMessage(5); + writer.WritePacked(Main.LobbyBehaviourNetId); + writer.EndMessage(); + writer.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); + }, 3f, "Repeat Lobby Despawn"); } public static void AfterPlayerDeathTasks(PlayerControl target, bool onMeeting = false, bool disconnect = false) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 197f522f2..8ffb9588d 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Text.RegularExpressions; using AmongUs.GameOptions; using Assets.CoreScripts; @@ -14,6 +15,7 @@ using HarmonyLib; using Hazel; using InnerNet; using UnityEngine; +using UnityEngine.Networking; using static EHR.Translator; // ReSharper disable InconsistentNaming @@ -216,6 +218,8 @@ internal static class ChatCommands new(["vs", "votestart", "голосованиестарт", "投票开始"], "", GetString("CommandDescription.VoteStart"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, VoteStartCommand, true, false), new(["imitate", "имитировать", "模仿"], "{id}", GetString("CommandDescription.Imitate"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, ImitateCommand, true, true, [GetString("CommandArgs.Imitate.Id")]), new(["retribute", "воздать", "报复"], "{id}", GetString("CommandDescription.Retribute"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, RetributeCommand, true, true, [GetString("CommandArgs.Retribute.Id")]), + + new(["confirmauth"], "{uuid}", GetString("CommandDescription.ConfirmAuth"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, ConfirmAuthCommand, true, false, [GetString("CommandArgs.ConfirmAuth.UUID")]), // Commands with action handled elsewhere new(["shoot", "guess", "bet", "bt", "st", "угадать", "бт", "猜测", "赌", "adivinhar"], "{id} {role}", GetString("CommandDescription.Guess"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _) => { }, true, false, [GetString("CommandArgs.Guess.Id"), GetString("CommandArgs.Guess.Role")]), @@ -243,6 +247,9 @@ internal static class ChatCommands if (friendCode == "" || friendCode == string.Empty || !Options.ApplyModeratorList.GetBool()) return false; + if (Main.UserData.TryGetValue(friendCode, out Options.UserData userData) && userData.Moderator) + return true; + long now = Utils.TimeStamp; string[] friendCodes; @@ -272,6 +279,9 @@ internal static class ChatCommands if (friendCode == "" || friendCode == string.Empty || !Options.ApplyVIPList.GetBool()) return false; + if (Main.UserData.TryGetValue(friendCode, out Options.UserData userData) && userData.Vip) + return true; + long now = Utils.TimeStamp; string[] friendCodes; @@ -301,6 +311,9 @@ internal static class ChatCommands if (friendCode == "" || friendCode == string.Empty || !Options.ApplyAdminList.GetBool()) return false; + if (Main.UserData.TryGetValue(friendCode, out Options.UserData userData) && userData.Admin) + return true; + long now = Utils.TimeStamp; string[] friendCodes; @@ -468,7 +481,76 @@ internal static class ChatCommands // --------------------------------------------------------------------------------------------------------------------------------------------- - private static void RetributeCommand(PlayerControl player, string text, string[] args) + private static void ConfirmAuthCommand(PlayerControl player, string text, string[] args) + { + if (GameStates.CurrentServerType != GameStates.ServerType.Vanilla) + { + Utils.SendMessage("\n", player.PlayerId, GetString("ConfirmAuth.ErrorNotVanilla")); + return; + } + + if (!Options.PostLobbyCodeToEHRWebsite.GetBool()) + { + Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("ConfirmAuth.ErrorLobbyUnlisted"), GetString("PostLobbyCodeToEHRDiscordServer"))); + return; + } + + if (args.Length >= 2) + { + string uuid = args[1].Trim(); + if (string.IsNullOrEmpty(uuid)) return; + + Main.Instance.StartCoroutine(SendVerificationCoroutine(uuid)); + } + + return; + + IEnumerator SendVerificationCoroutine(string uuid) + { + string friendCode = player.FriendCode; + string puid = player.GetClient().ProductUserId; + var gameId = AmongUsClient.Instance.GameId.ToString(); + + if (string.IsNullOrEmpty(friendCode) || string.IsNullOrEmpty(puid)) + { + Logger.Error($" Missing friendcode/puid for player {player.PlayerId}", "ConfirmAuth"); + yield break; + } + + if (string.IsNullOrEmpty(gameId) || gameId == "32") + { + Logger.Error(" Invalid GameId", "ConfirmAuth"); + yield break; + } + + var json = $"{{\"uuid\":\"{uuid}\",\"friend_code\":\"{friendCode}\",\"puid\":\"{puid}\",\"game_id\":\"{gameId}\"}}"; + byte[] bodyRaw = Encoding.UTF8.GetBytes(json); + + var uwr = new UnityWebRequest("https://gurge44.pythonanywhere.com/api/verify_ingame", "POST") + { + uploadHandler = new UploadHandlerRaw(bodyRaw), + downloadHandler = new DownloadHandlerBuffer() + }; + uwr.SetRequestHeader("Content-Type", "application/json"); + uwr.SetRequestHeader("User-Agent", $"{Main.ModName} v{Main.PluginVersion}"); + uwr.timeout = 10; // seconds + + yield return uwr.SendWebRequest(); + + if (uwr.result != UnityWebRequest.Result.Success) + { + Logger.Error($" HTTP error sending verification: {uwr.error} (code {(int)uwr.responseCode})", "ConfirmAuth"); + Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("ConfirmAuth.Error"), uwr.error)); + } + else + { + Logger.Msg($" Sent ok. Resp code {(int)uwr.responseCode}. Body: {uwr.downloadHandler?.text}", "ConfirmAuth"); + Utils.SendMessage("\n", player.PlayerId, GetString("ConfirmAuth.Success")); + } + } + } + + public static void RetributeCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; @@ -2809,21 +2891,25 @@ internal static class ChatCommands switch (subArgs) { + case "skeld": case "theskeld": GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 0); break; + case "mira": case "mirahq": GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 1); break; case "polus": GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 2); break; + case "dleks": case "dlekseht": GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 3); break; case "airship": GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 4); break; + case "fungle": case "thefungle": GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 5); break; diff --git a/Patches/HudSpritePatch.cs b/Patches/HudSpritePatch.cs index 0fd005163..107708d56 100644 --- a/Patches/HudSpritePatch.cs +++ b/Patches/HudSpritePatch.cs @@ -42,7 +42,7 @@ public static class HudSpritePatch if (!Main.EnableCustomButton.Value || !Main.ProcessShapeshifts || Mastermind.ManipulatedPlayers.ContainsKey(player.PlayerId) || ExileController.Instance || GameStates.IsMeeting) return; if ((!SetHudActivePatch.IsActive && !MeetingStates.FirstMeeting) || !player.IsAlive() || Options.CurrentGameMode is not CustomGameMode.Standard and not CustomGameMode.CaptureTheFlag) return; - if (ResetButtonIcons || !AmongUsClient.Instance.IsGameStarted || !Main.IntroDestroyed) + if (ResetButtonIcons || !AmongUsClient.Instance.IsGameStarted || !Main.IntroDestroyed || GameStates.IsLobby || GameStates.IsNotJoined || !GameStates.InGame) { Kill = null; Ability = null; diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 50e4fa9f7..d689fd932 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -66,7 +66,7 @@ internal static class CheckForEndVotingPatch } } - if (pc.Is(CustomRoles.Dictator) && pva.DidVote && pc.PlayerId != pva.VotedFor && pva.VotedFor < 253 && pc.Data?.IsDead == false) + if (pc.Is(CustomRoles.Dictator) && pva.DidVote && pc.PlayerId != pva.VotedFor && pva.VotedFor < 253 && pc.Data?.IsDead == false && pc.GetTaskState().CompletedTasksCount >= Dictator.MinTasksToDictate.GetInt()) { PlayerControl voteTarget = Utils.GetPlayerById(pva.VotedFor); TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Suicide, pc.PlayerId); @@ -234,6 +234,8 @@ internal static class CheckForEndVotingPatch Blackmailer.OnCheckForEndVoting(); NiceSwapper.OnCheckForEndVoting(); + Commited.OnVotingResultsShown(statesList); + states = [.. statesList]; Dictionary votingData = __instance.CustomCalculateVotes(); @@ -1062,6 +1064,7 @@ internal static class MeetingHudStartPatch sb.Append(Witch.GetSpelledMark(target.PlayerId, true)); sb.Append(Wasp.GetStungMark(target.PlayerId)); sb.Append(SpellCaster.HasSpelledMark(seer.PlayerId) ? Utils.ColorString(Team.Coven.GetColor(), "\u25c0") : string.Empty); + sb.Append(Commited.GetMark(seer, target)); if (target.Is(CustomRoles.SuperStar) && Options.EveryOneKnowSuperStar.GetBool()) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.SuperStar), "★")); diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs index ca9cf5072..2d9927c57 100644 --- a/Patches/OnGameStartedPatch.cs +++ b/Patches/OnGameStartedPatch.cs @@ -314,6 +314,8 @@ internal static class ChangeRoleSettings Camouflage.BlockCamouflage = false; Camouflage.Init(); + Main.NumEmergencyMeetingsUsed = Main.AllPlayerControls.ToDictionary(x => x.PlayerId, _ => 0); + if (AmongUsClient.Instance.AmHost) { string[] invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId).Select(p => $"{p.name}").ToArray(); @@ -394,6 +396,7 @@ internal static class ChangeRoleSettings Asthmatic.Init(); DoubleShot.Init(); Circumvent.Init(); + Commited.Init(); } catch (Exception ex) { Logger.Exception(ex, "Init Roles"); } @@ -549,6 +552,7 @@ internal static class StartGameHostPatch if (LobbyBehaviour.Instance) { + Main.LobbyBehaviourNetId = LobbyBehaviour.Instance.NetId; MessageWriter writer = MessageWriter.Get(SendOption.Reliable); writer.StartMessage(5); writer.Write(AUClient.GameId); @@ -561,6 +565,11 @@ internal static class StartGameHostPatch AUClient.RemoveNetObject(LobbyBehaviour.Instance); Object.Destroy(LobbyBehaviour.Instance.gameObject); } + else + { + Logger.Fatal($"LobbyBehaviour.Instance is null in {nameof(StartGameHostPatch)}.{nameof(StartGameHost)}", "StartGameHost"); + Main.LobbyBehaviourNetId = uint.MaxValue; + } if (!ShipStatus.Instance) { diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 3c8a563d9..ed2c12b47 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -987,6 +987,17 @@ internal static class ReportDeadBodyPatch // Next, check whether this meeting is allowed //============================================= + if (Main.NumEmergencyMeetingsUsed.TryGetValue(__instance.PlayerId, out int used)) + { + if (used >= Main.RealOptionsData.GetInt(Int32OptionNames.NumEmergencyMeetings)) + { + Notify("NoMoreEmergencyMeetingsLeft"); + return false; + } + + Main.NumEmergencyMeetingsUsed[__instance.PlayerId]++; + } + PlayerControl killer = target?.Object?.GetRealKiller(); CustomRoles? killerRole = killer?.GetCustomRole(); @@ -1149,6 +1160,8 @@ internal static class ReportDeadBodyPatch if (Lovers.PrivateChat.GetBool()) ChatManager.ClearChat(Main.AllAlivePlayerControls.ExceptBy(Main.LoversPlayers.ConvertAll(x => x.PlayerId), x => x.PlayerId).ToArray()); + CustomSabotage.Reset(); + if (target == null) { switch (Main.PlayerStates[player.PlayerId].Role) @@ -1223,6 +1236,7 @@ internal static class ReportDeadBodyPatch Bloodmoon.OnMeetingStart(); Deadlined.OnMeetingStart(); + Commited.OnMeetingStart(); Main.LastVotedPlayerInfo = null; Arsonist.ArsonistTimer.Clear(); diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index 984285caa..e83cf6bb9 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -296,6 +296,9 @@ public static class ElectricTaskCompletePatch if (Options.UsePhantomBasis.GetBool()) pc.RpcResetAbilityCooldown(); else pc.AddAbilityCD(); } + + if (pc.Is(CustomRoles.Sleep)) + Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); } } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 5290c865e..75ac8b22d 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1456,6 +1456,8 @@ "LawyerCanTargetJester": "Can Target Jester", "LawyerChangeRolesAfterTargetKilled": "When Target Dies, Lawyer becomes", "LawyerChangeRole": "Your client is dead", + "BountyHunterSwapTimer": "<#00ffa5>New target in <#ffffff>{0}s", + "SerialKillerTimeLeft": "<#ffff00>Must kill within <#ffa500>{0}s", "SerialKillerLimit": "Time Until Suicide", "ArsonistDouseTime": "Douse Duration", "CanTerroristSuicideWin": "Can Win By Suicide", @@ -1826,6 +1828,7 @@ "TargetDisregarded": "The player whose body you tried to report is <#FF6347>Disregarded", "AmOblivious": "You are Oblivious, meaning you cannot report bodies", "ShipHasNoMoreMeetingsLeft": "The ship has no more emergency meetings left", + "NoMoreEmergencyMeetingsLeft": "You have no more emergency meetings left", "UnreportableBody": "This body is unreportable due to a role-specific action", "CheckMurderFail": "<#ff0000>The murder was canceled due to the following reason:\\n", "SameCTATeam": "The player you attacked is in the same custom team as you", @@ -2476,6 +2479,7 @@ "CommandDescription.VoteStart": "Vote to start the game", "CommandDescription.Imitate": "Imitate a dead player's role as the Imitator", "CommandDescription.Retribute": "Guess who killed the player you camped as the Retributionist", + "CommandDescription.ConfirmAuth": "Command required to register a new EHR account", "CommandArgs.ChangeSetting.Name": "The setting's name to change (leave blank to view all settings)", "CommandArgs.ChangeSetting.UnknownValue": "(leave blank to view what to put here)", "CommandArgs.Rename.Name": "Your new name", @@ -2566,6 +2570,7 @@ "CommandArgs.DeleteAdmin.Id": "The player's ID to remove from the list of admins", "CommandArgs.Imitate.Id": "The dead player's ID to imitate the role of", "CommandArgs.Retribute.Id": "The potential killer's ID to guess", + "CommandArgs.ConfirmAuth.UUID": "The unique token provided by the server", "Commands.ChangeSettingHelp": "Arguments:\\n- map (theskeld/mirahq/polus/dlekseht/airship/thefungle/custom)\\n- impostors (number)\\n- players (number)\\n- recommended (on/off)\\n- confirmejects (on/off)\\n- emergencymeetings (number)\\n- anonymousvotes (on/off)\\n- emergencycooldown (number)\\n- discussiontime (number)\\n- votingtime (number)\\n- playerspeed (float)\\n- crewmatevision (float)\\n- impostorvision (float)\\n- killcooldown (float)\\n- killdistance (short/medium/long/custom)\\n- taskbarupdates (always/meetings/never)\\n- visualtasks (on/off)\\n- commontasks (number)\\n- longtasks (number)\\n- shorttasks (number)\\n- scientistcount (number)\\n- scientistchance (number)\\n- vitalsdisplaycooldown (float)\\n- batteryduration (float)\\n- engineercount (number)\\n- engineerchance (number)\\n- ventusecooldown (float)\\n- maxtimeinvents (float)\\n- guardianangelcount (number)\\n- guardianangelchance (number)\\n- protectcooldown (float)\\n- protectduration (float)\\n- protectvisibletoimpostors (on/off)\\n- shapeshiftercount (number)\\n- shapeshifterchance (number)\\n- shapeshiftduration (float)\\n- shapeshiftcooldown (float)\\n- leaveshapeshiftevidence (on/off)\\n- phantomcount (number)\\n- phantomchance (number)\\n- inviscooldown (float)\\n- invisduration (float)\\n- noisemakercount (number)\\n- noisemakerchance (number)\\n- noisemakerimpostoralert (on/off)\\n- noisemakeralertduration (number)\\n- trackercount (number)\\n- trackerchance (number)\\n- trackduration (float)\\n- trackcooldown (float)\\n- trackdelay (float)\\n- ghostdotasks (on/off)\\n\\nNotes:\\n1. Upper and lowercase matters\\n2. Where it says custom, you obviously put a number argument after it\\n3. A float is a single precision floating number, like 1.7\\n4. \"ghostdotasks\" causes issues if you set it\\n5. Example uses:\\n - \"/cs map polus\"\\n - \"/cs killdistance custom 4\"\\n - \"/cs trackdelay 1,7\"\\n - \"/cs confirmejects on\"", "AddTagSuccess": "<#00ffa5>✓ Suceessfully created the tag {0} for {1}. If you want to change it, just use this command again and it will override the current tag. To delete it, simply type <#00a5ff>/deletetag {2}!", "DeleteTagSuccess": "<#00ffa5>✓ Successfully deleted {0}'s tag.", @@ -2590,6 +2595,10 @@ "8BallResponse.17": "My sources say no.", "8BallResponse.18": "Outlook not so good.", "8BallResponse.19": "Very doubtful.", + "ConfirmAuth.ErrorNotVanilla": "<#ff5555>X <#bb0000>You must be in an Online game on a Vanilla server (Europe, North America, Asia)", + "ConfirmAuth.ErrorLobbyUnlisted": "<#ff5555>X <#bb0000>This lobby is not listed on the EHR server.\\n<#ffffff>Please enable <#ffff44>{0} in System Settings, otherwise the server will reject your request.", + "ConfirmAuth.Error": "<#ff5555>X <#bb0000>Network error\\n<#ffffff>{0}", + "ConfirmAuth.Success": "<#00ff00>✓ <#00ffa5>Successfully sent the verification request to the server.", "GameModePoll.Question": "Which game mode should we play next?", "PollResultTitle": "Poll Results", "Poll.Title": "<#ff0000>➡ POLL ⬅", @@ -3340,7 +3349,9 @@ "CamouflageMode.Karpe": "KARPED1EM", "CamouflageMode.Gurge44": "Gurge44", "CamouflageMode.TommyXL": "TommyXL", + "pet_Goose": "A Goose", "pet_Bedcrab": "Bedcrab", + "pet_DancingSkeletonPet": "Bone Shaker", "pet_BredPet": "Breb", "pet_YuleGoatPet": "Bränt", "pet_Bush": "Bushfriend", @@ -3348,9 +3359,12 @@ "pet_ChewiePet": "Chewie", "pet_clank": "Clank", "pet_coaltonpet": "Coalton", + "pet_Creb": "Creb", "pet_Cube": "Deitied Guy", + "pet_lny_dragon": "Desolation of Skeld", "pet_Doggy": "Doggy", "pet_Ellie": "E. Rose", + "pet_Strawb": "Flying Strawberry", "pet_frankendog": "Frankendog", "pet_D2GhostPet": "Ghost", "pet_test": "Glitch Pet", @@ -3360,13 +3374,23 @@ "pet_Alien": "Headslug", "pet_poro": "Heimerdinger's Poro", "pet_HamPet": "Holiday Hammy", + "pet_Crow": "Mad Crow", "pet_Lava": "Magmate", "pet_Crewmate": "Mini Crewmate", + "pet_Mister": "Mister", + "pet_nancy": "Nancy", + "pet_napstamate": "Napstamate", + "pet_Pip": "Pip", + "pet_pocketCircuitCar": "Pocket Circuit", "pet_D2PoukaPet": "Pouka", "pet_Pusheen": "Pusheen", + "pet_Pate": "Pâté", + "pet_Rammy": "Rammy", "pet_Robot": "Ro-Bot", "pet_Snow": "Snowball", + "pet_spaceCat": "Space Cat", "pet_Squig": "Squig", + "pet_Stormy": "Stormy", "pet_nuggetPet": "The Nug", "pet_Charles_Red": "Toppat Chopper", "pet_UFO": "UFO", @@ -3818,9 +3842,9 @@ "ConvenerInfoLong": "(Crewmates):\\nAs the Convener, you can vent/pet to teleport everyone to you.", "Mathematician": "Mathematician", "MathematicianInfo": "Ask Math Questions", - "MathematicianInfoLong": "(Crewmates):\\nAs the Mathematician, you can use '/ask [number] [number]' to ask a math question (What is [number] + [number]?). The first player to answer correctly will get shielded for the next round. You can only ask once per meeting.", + "MathematicianInfoLong": "(Crewmates):\\nAs the Mathematician, you can use '/ask [number] [number]' to ask a math question (What is [number] + [number]?). The first player to answer correctly will get +1 ability use and shielded for the next round. You can only ask once per meeting.", "MathematicianQuestionString": "What is {0} + {1}?\n\nType '/answer [answer]' to answer.\nFirst to answer correctly gets shielded for the next round!", - "MathematicianAnsweredString": "{0} has answered correctly! ({1})\nThey will be shielded for the next round.", + "MathematicianAnsweredString": "{0} has answered correctly! ({1})\nThey got +1 ability use and will be shielded for the next round.", "Escort": "Escort", "EscortInfo": "Role-Block Players to Help the Crew!", "EscortInfoLong": "(Crewmates):\\nAs the Escort, you can role-block a player for a specified time using your kill button. A role-blocked player cannot kill, vent, sabotage or report.\\n(Role-Block duration is the same as the hack duration for The Glitch)", @@ -5106,7 +5130,7 @@ "TMGPlayerInfoLong": "THINK WITH OTHERS' BRAINS TO WIN", "BedWars": "BED WARS", "ModeBedWars": "Gamemode: Bed Wars", - "ModeDescribe.BedWars": "In the Bed Wars gamemode, players are split into teams. Each team has a bed. If a team's bed is destroyed, all players in that team can no longer respawn. The goal is to destroy all other teams' beds (via Pet button) and be the last team alive.\\n\\nYou can buy items (blocks, weapons, tool, etc.) and team upgrades (heal pool, haste, trap, etc.) in shops from the resources (iron, gold, diamond, emerald) you can collect from the generators on the map. These are useful for protecting the bed with various layers to make it harder to break it, increase your attack damage, increase your defense, or give you or your team various abilities.\\n\\nIn the beginning, there's a grace period, where no attacks are allowed, and players can't leave their own base.\\n\\nEach player has an inventory with all of their items. Cycle through the slots using the Vanish button or by typing the number of the slot in the chat (ex. '5'). Use weapons through the kill button, other items via Pet. To start breaking someone's bed, Pet. Make sure to always have the correct item selected in your inventory (swords when you're fighting, blocks when you protect the bed, tools when you break beds, etc.).\\nYou can break beds with anything, but using the correct tool for each layer will make the process significantly faster.\\n\\nIf you die, you will respawn after a few seconds, unless your bed is broken. If your bed is broken, you will not respawn anymore.", + "ModeDescribe.BedWars": "In the Bed Wars gamemode, players are split into teams. Each team has a bed. If a team's bed is destroyed, all players in that team can no longer respawn. The goal is to destroy all other teams' beds (via Pet button) and be the last team alive.\\n\\nYou can buy items (blocks, weapons, tool, etc.) and team upgrades (heal pool, haste, trap, etc.) in shops from the resources (iron, gold, diamond, emerald) you can collect from the generators on the map. These are useful for protecting the bed with various layers to make it harder to break it, increase your attack damage, increase your defense, or give you or your team various abilities.\\n\\nIn the beginning, there's a grace period, where no attacks are allowed, and players can't leave their own base.\\n\\nEach player has an inventory with all of their items. Cycle through the slots using the Vanish button or by typing the number of the slot in the chat (ex. '5'). Use weapons through the kill button, other items via Pet. To start breaking someone's bed, Pet. Make sure to always have the correct item selected in your inventory (blocks when you protect the bed, tools when you break beds, etc.).\\nYou can break beds with anything, but using the correct tool for each layer will make the process significantly faster.\\n\\nIf you die, you will respawn after a few seconds, unless your bed is broken. If your bed is broken, you will not respawn anymore.", "Bedwars.Shop.ItemShop": "ITEM SHOP", "Bedwars.Shop.UpgradeShop": "UPGRADE SHOP", "Bedwars.Rooms": "Teams are located in the following rooms:", @@ -8994,7 +9018,7 @@ "Tank.Suffix": "<#00ffa5>A random vent you haven't entered yet: {0}", "Retributionist": "Retributionist", "RetributionistInfo": "Find & Kill Your Target's Killer", - "RetributionistInfoLong": "(Crewmates):\\nAs the Retributionist, you may mark a player using your kill button to camp them. If they die, you will be notified. In the meeting after that, you may use /retribute {id} to kill the player who killed your target. If you guess wrong, nothing happens. After the meeting, you may camp another player.", + "RetributionistInfoLong": "(Crewmates):\\nAs the Retributionist, you may mark a player using your kill button to camp them. If they die, you will be notified. In the meeting after that, you may use /retribute {id} to kill the player who killed your target. (If the vote cancelling setting is enabled, you can vote for your target instead of using this command.) If you guess wrong, nothing happens. After the meeting, you may camp another player.", "Retributionist.ResetCampedPlayerAfterEveryMeeting": "Can Camp a New Player After Every Meeting", "Retributionist.TargetDead": "<#ffff00>Your target is dead!\\nYou can use /retribute {id} to kill the player who killed your target in the next meeting.", "Retributionist.Fail": "<#ff0000>X <#bb0000>Wrong!", @@ -9032,6 +9056,7 @@ "CleanserInfo": "Erase All Addons of your vote target", "CleanserInfoLong": "(Crewmates):\\nCleanser can vote for any target at the meeting to erase the target's Add-ons, and the erasure will take effect after the meeting ends. Depending on the settings cleansed player may never get add on in future", "CleansedInfoLong": "(Add-ons):\\nCleansed Add-on can only be obtained if cleanser erases all your Add-ons. Depending on the cleanser settings, you may not be able to obtain any more Add-ons in the future.", + "TimeMasterCanUseVitals": "Can Use Vitals", "TimeMasterRewindTimeLength": "Rewind Time Length", "TimeMasterOnGuard": "Time Shield is active!", "TimeMasterSkillStop": "Time Shield has ended!", diff --git a/Roles/AddOns/Common/Commited.cs b/Roles/AddOns/Common/Commited.cs new file mode 100644 index 000000000..f1d13efdb --- /dev/null +++ b/Roles/AddOns/Common/Commited.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Linq; + +namespace EHR.AddOns.Common; + +public class Commited : IAddon +{ + public AddonTypes Type => AddonTypes.Helpful; + + private static OptionItem PermanentReduction; + private static OptionItem Reduction; + private static OptionItem IgnoreSkips; + + private static Dictionary Target = []; + public static Dictionary ReduceKCD = []; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(653600, CustomRoles.Commited, canSetNum: true, teamSpawnOptions: true); + + PermanentReduction = new BooleanOptionItem(653610, "Commited.PermanentReduction", false, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Commited]); + + Reduction = new FloatOptionItem(653611, "Commited.Reduction", new(0.5f, 30f, 0.5f), 5f, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Commited]) + .SetValueFormat(OptionFormat.Seconds); + + IgnoreSkips = new BooleanOptionItem(653612, "Commited.IgnoreSkips", true, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Commited]); + } + + public static void Init() + { + Target = []; + ReduceKCD = []; + } + + public static void OnMeetingStart() + { + Target = []; + + if (!PermanentReduction.GetBool()) + ReduceKCD = []; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (pc.Is(CustomRoles.Commited)) + Target[pc.PlayerId] = Main.AllAlivePlayerControls.Where(x => !x.Is(CustomRoles.Commited)).RandomElement().PlayerId; + } + } + + public static void OnVotingResultsShown(List vs) + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (Target.TryGetValue(pc.PlayerId, out byte target) && vs.FindFirst(x => x.VoterId == pc.PlayerId, out MeetingHud.VoterState s) && vs.FindFirst(x => x.VoterId == target, out MeetingHud.VoterState ts) && !(IgnoreSkips.GetBool() && s.SkippedVote) && s.VotedForId == ts.VotedForId) + { + if (ReduceKCD.ContainsKey(pc.PlayerId)) + ReduceKCD[pc.PlayerId] += Reduction.GetFloat(); + else + ReduceKCD[pc.PlayerId] = Reduction.GetFloat(); + } + } + } + + public static string GetMark(PlayerControl seer, PlayerControl target) + { + if (!seer.Is(CustomRoles.Commited) || !Target.TryGetValue(seer.PlayerId, out byte t) || t != target.PlayerId) return string.Empty; + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Commited), "⌆"); + } +} \ No newline at end of file diff --git a/Roles/Crewmate/Cleanser.cs b/Roles/Crewmate/Cleanser.cs index f0b663a30..f940b4c36 100644 --- a/Roles/Crewmate/Cleanser.cs +++ b/Roles/Crewmate/Cleanser.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using EHR.Modules; +using EHR.Neutral; using Hazel; -using UnityEngine; using static EHR.Options; using static EHR.Translator; @@ -40,11 +40,11 @@ public class Cleanser : RoleBase CancelVote = CreateVoteCancellingUseSetting(Id + 12, CustomRoles.Cleanser, TabGroup.CrewmateRoles); CleanserAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 13, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.4f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Cleanser]) + .SetParent(CustomRoleSpawnChances[CustomRoles.Cleanser]) .SetValueFormat(OptionFormat.Times); AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 14, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Cleanser]) + .SetParent(CustomRoleSpawnChances[CustomRoles.Cleanser]) .SetValueFormat(OptionFormat.Times); } @@ -95,6 +95,7 @@ public class Cleanser : RoleBase public override bool OnVote(PlayerControl voter, PlayerControl target) { + if (Starspawn.IsDayBreak) return false; if (DidVote[voter.PlayerId] || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; DidVote[voter.PlayerId] = true; diff --git a/Roles/Crewmate/Dictator.cs b/Roles/Crewmate/Dictator.cs index 8e761a252..09e95d7fe 100644 --- a/Roles/Crewmate/Dictator.cs +++ b/Roles/Crewmate/Dictator.cs @@ -4,9 +4,14 @@ internal class Dictator : RoleBase { public override bool IsEnable => false; + public static OptionItem MinTasksToDictate; + public override void SetupCustomOption() { Options.SetupRoleOptions(652400, TabGroup.CrewmateRoles, CustomRoles.Dictator); + + MinTasksToDictate = new IntegerOptionItem(652410, "Dictator.MinTasksToDictate", new(0, 10, 1), 3, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dictator]); } public override void Init() { } diff --git a/Roles/Crewmate/Divinator.cs b/Roles/Crewmate/Divinator.cs index 6ecd4ae76..1110adcd7 100644 --- a/Roles/Crewmate/Divinator.cs +++ b/Roles/Crewmate/Divinator.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using EHR.Modules; +using EHR.Neutral; using Hazel; using static EHR.Options; using static EHR.Translator; @@ -100,6 +101,7 @@ public class Divinator : RoleBase public override bool OnVote(PlayerControl player, PlayerControl target) { + if (Starspawn.IsDayBreak) return false; if (player == null || target == null) return false; if (DidVote.Contains(player.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; diff --git a/Roles/Crewmate/Express.cs b/Roles/Crewmate/Express.cs index b22f54d11..fca37094f 100644 --- a/Roles/Crewmate/Express.cs +++ b/Roles/Crewmate/Express.cs @@ -12,13 +12,13 @@ internal class Express : RoleBase public override void SetupCustomOption() { - Options.SetupRoleOptions(5585, TabGroup.CrewmateRoles, CustomRoles.Express); + Options.SetupRoleOptions(653500, TabGroup.CrewmateRoles, CustomRoles.Express); - Options.ExpressSpeed = new FloatOptionItem(5587, "ExpressSpeed", new(0.25f, 5f, 0.25f), 1.5f, TabGroup.CrewmateRoles) + Options.ExpressSpeed = new FloatOptionItem(653502, "ExpressSpeed", new(0.25f, 5f, 0.25f), 1.5f, TabGroup.CrewmateRoles) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Express]) .SetValueFormat(OptionFormat.Multiplier); - Options.ExpressSpeedDur = new IntegerOptionItem(5588, "ExpressSpeedDur", new(0, 90, 1), 5, TabGroup.CrewmateRoles) + Options.ExpressSpeedDur = new IntegerOptionItem(653503, "ExpressSpeedDur", new(0, 90, 1), 5, TabGroup.CrewmateRoles) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Express]) .SetValueFormat(OptionFormat.Seconds); } diff --git a/Roles/Crewmate/Markseeker.cs b/Roles/Crewmate/Markseeker.cs index 91692db02..09c20d634 100644 --- a/Roles/Crewmate/Markseeker.cs +++ b/Roles/Crewmate/Markseeker.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using EHR.Neutral; namespace EHR.Crewmate; @@ -40,6 +41,7 @@ internal class Markseeker : RoleBase public override bool OnVote(PlayerControl player, PlayerControl target) { + if (Starspawn.IsDayBreak) return false; if (player == null || target == null || player.PlayerId == target.PlayerId || MarkedId != byte.MaxValue || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; MarkedId = target.PlayerId; diff --git a/Roles/Crewmate/Negotiator.cs b/Roles/Crewmate/Negotiator.cs index ba1e2f492..57eabdd0b 100644 --- a/Roles/Crewmate/Negotiator.cs +++ b/Roles/Crewmate/Negotiator.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; using EHR.Modules; +using EHR.Neutral; namespace EHR.Crewmate; @@ -17,8 +18,8 @@ public class Negotiator : RoleBase private static OptionItem AbilityUseLimit; private static OptionItem AbilityUseGainWithEachTaskCompleted; private static OptionItem AbilityChargesWhenFinishedTasks; - public static OptionItem CancelVote; + private byte NegotiatorId; private NegotiationType Penalty; private Dictionary> PermanentPenalties; @@ -126,6 +127,7 @@ public class Negotiator : RoleBase public override bool OnVote(PlayerControl voter, PlayerControl target) { + if (Starspawn.IsDayBreak) return false; if (target == null || voter == null || voter.PlayerId == target.PlayerId || TargetId != byte.MaxValue || voter.GetAbilityUseLimit() < 1f || MinVotingTimeLeftToNegotiate.GetInt() > MeetingTimeManager.VotingTimeLeft || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; bool votedLast = MeetingHud.Instance.playerStates.All(x => x.TargetPlayerId == voter.PlayerId || x.DidVote); diff --git a/Roles/Crewmate/NiceEraser.cs b/Roles/Crewmate/NiceEraser.cs index eb50d9c0b..611f5e6c6 100644 --- a/Roles/Crewmate/NiceEraser.cs +++ b/Roles/Crewmate/NiceEraser.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using EHR.Neutral; using Hazel; using static EHR.Translator; @@ -60,6 +61,7 @@ internal class NiceEraser : RoleBase public override bool OnVote(PlayerControl player, PlayerControl target) { + if (Starspawn.IsDayBreak) return false; if (player == null || target == null) return false; if (DidVote.Contains(player.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; diff --git a/Roles/Crewmate/Oracle.cs b/Roles/Crewmate/Oracle.cs index 53ae063fc..121ab1145 100644 --- a/Roles/Crewmate/Oracle.cs +++ b/Roles/Crewmate/Oracle.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using EHR.Neutral; using Hazel; using static EHR.Options; using static EHR.Translator; @@ -66,6 +67,7 @@ public class Oracle : RoleBase public override bool OnVote(PlayerControl player, PlayerControl target) { + if (Starspawn.IsDayBreak) return false; if (player == null || target == null) return false; if (DidVote.Contains(player.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; diff --git a/Roles/Crewmate/President.cs b/Roles/Crewmate/President.cs index a018d45c9..f14e65432 100644 --- a/Roles/Crewmate/President.cs +++ b/Roles/Crewmate/President.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; +using EHR.Neutral; namespace EHR.Crewmate; @@ -234,6 +235,7 @@ public class President : RoleBase public override bool OnVote(PlayerControl voter, PlayerControl target) { + if (Starspawn.IsDayBreak) return false; if (voter.PlayerId != PresidentId || !IsRecruiting || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; if (!target.Is(CustomRoleTypes.Crewmate) || target.IsConverted()) diff --git a/Roles/Crewmate/Retributionist.cs b/Roles/Crewmate/Retributionist.cs index 50663185f..2e0bcd27a 100644 --- a/Roles/Crewmate/Retributionist.cs +++ b/Roles/Crewmate/Retributionist.cs @@ -1,5 +1,6 @@ using AmongUs.GameOptions; using EHR.Modules; +using EHR.Neutral; using Hazel; namespace EHR.Crewmate; @@ -12,6 +13,7 @@ public class Retributionist : RoleBase public static OptionItem ResetCampedPlayerAfterEveryMeeting; public static OptionItem UsePet; + public static OptionItem CancelVote; public byte Camping; public bool Notified; @@ -21,7 +23,8 @@ public class Retributionist : RoleBase { StartSetup(653200) .AutoSetupOption(ref ResetCampedPlayerAfterEveryMeeting, false) - .CreatePetUseSetting(ref UsePet); + .CreatePetUseSetting(ref UsePet) + .CreateVoteCancellingUseSetting(ref CancelVote); } public override void Init() @@ -101,6 +104,53 @@ public class Retributionist : RoleBase } } + public override bool OnVote(PlayerControl voter, PlayerControl target) + { + if (!CancelVote.GetBool()) return false; + if (Starspawn.IsDayBreak) return false; + if (target == null || voter == null || voter.PlayerId == target.PlayerId || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; + + if (!Notified || Camping == byte.MaxValue) return false; + + PlayerControl campTarget = Utils.GetPlayerById(Camping); + if (campTarget == null || campTarget.IsAlive() || !Main.PlayerStates.TryGetValue(campTarget.PlayerId, out PlayerState campState)) return false; + + byte realKiller = campState.GetRealKiller(); + + if (realKiller != target.PlayerId) + { + Notified = false; + Utils.SendMessage("\n", voter.PlayerId, Translator.GetString("Retributionist.Fail")); + } + else + { + PlayerControl killer = Utils.GetPlayerById(realKiller); + + if (killer == null || !killer.IsAlive()) + { + Notified = false; + Utils.SendMessage("\n", voter.PlayerId, Translator.GetString("Retributionist.KillerDead")); + } + else + { + killer.SetRealKiller(voter); + PlayerState killerState = Main.PlayerStates[killer.PlayerId]; + killerState.deathReason = PlayerState.DeathReason.Retribution; + killerState.SetDead(); + Medic.IsDead(killer); + killer.RpcExileV2(); + Utils.AfterPlayerDeathTasks(killer, true); + Utils.SendMessage("\n", title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Retributionist), string.Format(Translator.GetString("Retributionist.SuccessOthers"), target.PlayerId.ColoredPlayerName(), CustomRoles.Retributionist.ToColoredString()))); + Utils.SendMessage("\n", voter.PlayerId, Translator.GetString("Retributionist.Success")); + } + } + + MeetingManager.SendCommandUsedMessage("/retribute"); + + Main.DontCancelVoteList.Add(voter.PlayerId); + return true; + } + public void ReceiveRPC(MessageReader reader) { Camping = reader.ReadByte(); diff --git a/Roles/Crewmate/Ricochet.cs b/Roles/Crewmate/Ricochet.cs index 7fd84b116..960796986 100644 --- a/Roles/Crewmate/Ricochet.cs +++ b/Roles/Crewmate/Ricochet.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using EHR.Modules; +using EHR.Neutral; using Hazel; namespace EHR.Crewmate; @@ -94,6 +95,7 @@ public class Ricochet : RoleBase public override bool OnVote(PlayerControl pc, PlayerControl target) { + if (Starspawn.IsDayBreak) return false; if (target == null || pc == null || pc.PlayerId == target.PlayerId || Main.DontCancelVoteList.Contains(pc.PlayerId)) return false; if (pc.GetAbilityUseLimit() >= 1) diff --git a/Roles/Crewmate/Scout.cs b/Roles/Crewmate/Scout.cs index 713ddbd06..caf7a221a 100644 --- a/Roles/Crewmate/Scout.cs +++ b/Roles/Crewmate/Scout.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using EHR.Modules; +using EHR.Neutral; using Hazel; using UnityEngine; using static EHR.Options; @@ -105,6 +106,7 @@ public class Scout : RoleBase public override bool OnVote(PlayerControl player, PlayerControl target) { + if (Starspawn.IsDayBreak) return false; if (player == null || target == null || player.GetAbilityUseLimit() < 1f || player.PlayerId == target.PlayerId || TrackerTarget[player.PlayerId].Contains(target.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; player.RpcRemoveAbilityUse(); diff --git a/Roles/Crewmate/Socialite.cs b/Roles/Crewmate/Socialite.cs index 3322e155f..5fd40f5db 100644 --- a/Roles/Crewmate/Socialite.cs +++ b/Roles/Crewmate/Socialite.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using AmongUs.GameOptions; using EHR.Modules; +using EHR.Neutral; using Hazel; namespace EHR.Crewmate; @@ -100,6 +101,7 @@ public class Socialite : RoleBase public override bool OnVote(PlayerControl pc, PlayerControl target) { + if (Starspawn.IsDayBreak) return false; if (pc == null || target == null || pc.PlayerId == target.PlayerId || Main.DontCancelVoteList.Contains(pc.PlayerId)) return false; if (GuestList.Add(target.PlayerId)) diff --git a/Roles/Crewmate/Soothsayer.cs b/Roles/Crewmate/Soothsayer.cs index 358c7fa6a..b9d05334b 100644 --- a/Roles/Crewmate/Soothsayer.cs +++ b/Roles/Crewmate/Soothsayer.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using EHR.Neutral; namespace EHR.Crewmate; @@ -42,6 +43,7 @@ public class Soothsayer : RoleBase public override bool OnVote(PlayerControl player, PlayerControl target) { + if (Starspawn.IsDayBreak) return false; if (player == null || target == null || player.PlayerId == target.PlayerId) return false; if (Target != byte.MaxValue || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; diff --git a/Roles/Crewmate/Spiritualist.cs b/Roles/Crewmate/Spiritualist.cs index aab63fc4a..209430887 100644 --- a/Roles/Crewmate/Spiritualist.cs +++ b/Roles/Crewmate/Spiritualist.cs @@ -93,6 +93,8 @@ internal class Spiritualist : RoleBase LastGhostArrowShowTime = 0; ShowGhostArrowUntil = 0; + if (!AmongUsClient.Instance.AmHost) continue; + PlayerControl target = Main.AllPlayerControls.FirstOrDefault(a => a.PlayerId == SpiritualistTarget); if (target == null) continue; diff --git a/Roles/Crewmate/Tether.cs b/Roles/Crewmate/Tether.cs index 878e89c09..f414d7757 100644 --- a/Roles/Crewmate/Tether.cs +++ b/Roles/Crewmate/Tether.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using AmongUs.GameOptions; using EHR.Modules; +using EHR.Neutral; using Hazel; namespace EHR.Crewmate; @@ -116,6 +117,7 @@ public class Tether : RoleBase public override bool OnVote(PlayerControl pc, PlayerControl target) { + if (Starspawn.IsDayBreak) return false; if (pc == null || target == null || pc.PlayerId == target.PlayerId || Main.DontCancelVoteList.Contains(pc.PlayerId)) return false; if (pc.GetAbilityUseLimit() >= 1) diff --git a/Roles/Crewmate/TimeMaster.cs b/Roles/Crewmate/TimeMaster.cs index 38353f769..4cef94380 100644 --- a/Roles/Crewmate/TimeMaster.cs +++ b/Roles/Crewmate/TimeMaster.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; +using Hazel; using UnityEngine; using static EHR.Options; @@ -12,6 +13,7 @@ internal class TimeMaster : RoleBase { public static bool On; + public static OptionItem TimeMasterCanUseVitals; public static OptionItem TimeMasterRewindTimeLength; public static OptionItem TimeMasterSkillCooldown; public static OptionItem TimeMasterMaxUses; @@ -23,10 +25,15 @@ internal class TimeMaster : RoleBase public override bool IsEnable => On; + private bool DesyncCommsActive; + public override void SetupCustomOption() { SetupRoleOptions(652100, TabGroup.CrewmateRoles, CustomRoles.TimeMaster); + TimeMasterCanUseVitals = new BooleanOptionItem(652109, "TimeMasterCanUseVitals", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.TimeMaster]); + TimeMasterRewindTimeLength = new IntegerOptionItem(652110, "TimeMasterRewindTimeLength", new(0, 30, 1), 15, TabGroup.CrewmateRoles) .SetParent(CustomRoleSpawnChances[CustomRoles.TimeMaster]) .SetValueFormat(OptionFormat.Seconds); @@ -52,6 +59,7 @@ internal class TimeMaster : RoleBase { On = true; BackTrack = []; + DesyncCommsActive = false; playerId.SetAbilityUseLimit(TimeMasterMaxUses.GetFloat()); } @@ -166,6 +174,51 @@ internal class TimeMaster : RoleBase if (BackTrack.ContainsKey(now)) return; BackTrack[now] = Main.AllAlivePlayerControls.ToDictionary(x => x.PlayerId, x => x.Pos()); + + if (TimeMasterCanUseVitals.GetBool()) return; + + var doComms = false; + bool commsSaboActive = Utils.IsActive(SystemTypes.Comms); + float usableDistance = DisableDevice.UsableDistance + 2f; + Vector2 pos = player.Pos(); + + if (!commsSaboActive) + { + switch (Main.NormalOptions.MapId) + { + case 2: + doComms |= Vector2.Distance(pos, DisableDevice.DevicePos["PolusVital"]) <= usableDistance; + break; + case 4: + doComms |= Vector2.Distance(pos, DisableDevice.DevicePos["AirshipVital"]) <= usableDistance; + break; + case 5: + doComms |= Vector2.Distance(pos, DisableDevice.DevicePos["FungleVital"]) <= usableDistance; + break; + } + } + + var sender = CustomRpcSender.Create("DisableDevice.FixedUpdate", SendOption.Reliable, log: false); + var hasValue = false; + + if (doComms && !player.inVent && !DisableDevice.DesyncComms.Contains(player.PlayerId)) + { + DesyncCommsActive = true; + sender.RpcDesyncRepairSystem(player, SystemTypes.Comms, 128); + hasValue = true; + } + else if (!commsSaboActive && DesyncCommsActive) + { + DesyncCommsActive = false; + sender.RpcDesyncRepairSystem(player, SystemTypes.Comms, 16); + + if (Main.NormalOptions.MapId is 1 or 5) // Mira HQ or The Fungle + sender.RpcDesyncRepairSystem(player, SystemTypes.Comms, 17); + + hasValue = true; + } + + sender.SendMessage(!hasValue); } public override bool CanUseVent(PlayerControl pc, int ventId) diff --git a/Roles/Impostor/BountyHunter.cs b/Roles/Impostor/BountyHunter.cs index 7c752d8fc..54c9cb80d 100644 --- a/Roles/Impostor/BountyHunter.cs +++ b/Roles/Impostor/BountyHunter.cs @@ -214,6 +214,13 @@ public class BountyHunter : RoleBase } } + public override string GetProgressText(byte playerId, bool comms) + { + if (!AmongUsClient.Instance.AmHost) return string.Empty; + if (Timer > 15) return base.GetProgressText(playerId, comms); + return $"{base.GetProgressText(playerId, comms)} <#777777>- {string.Format(GetString("BountyHunterSwapTimer"), Timer)}"; + } + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) { return GetTargetText(seer, target, hud) + GetTargetArrow(seer, target); @@ -221,9 +228,7 @@ public class BountyHunter : RoleBase private static string GetTargetText(PlayerControl bounty, PlayerControl tar, bool hud) { - if (GameStates.IsMeeting || bounty.PlayerId != tar.PlayerId) return string.Empty; - - if (Main.PlayerStates[bounty.PlayerId].Role is not BountyHunter bh) return string.Empty; + if (GameStates.IsMeeting || bounty.PlayerId != tar.PlayerId || Main.PlayerStates[bounty.PlayerId].Role is not BountyHunter bh) return string.Empty; byte targetId = bh.GetTarget(bounty); return targetId != 0xff ? $"{(hud ? GetString("BountyCurrentTarget") : GetString("Target"))}: {Main.AllPlayerNames[targetId].RemoveHtmlTags().Replace("\r\n", string.Empty)}" : string.Empty; @@ -231,11 +236,7 @@ public class BountyHunter : RoleBase private static string GetTargetArrow(PlayerControl seer, PlayerControl target = null) { - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; - - if (!ShowTargetArrow || GameStates.IsMeeting) return string.Empty; - - if (Main.PlayerStates[seer.PlayerId].Role is not BountyHunter bh) return string.Empty; + if ((target != null && seer.PlayerId != target.PlayerId) || !ShowTargetArrow || GameStates.IsMeeting || Main.PlayerStates[seer.PlayerId].Role is not BountyHunter bh) return string.Empty; byte targetId = bh.GetTarget(seer); return $" {TargetArrow.GetArrows(seer, targetId)}"; diff --git a/Roles/Impostor/Eraser.cs b/Roles/Impostor/Eraser.cs index b5117600a..f0caa9717 100644 --- a/Roles/Impostor/Eraser.cs +++ b/Roles/Impostor/Eraser.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using EHR.Neutral; using Hazel; using static EHR.Translator; @@ -101,6 +102,7 @@ internal class Eraser : RoleBase public override bool OnVote(PlayerControl player, PlayerControl target) { + if (Starspawn.IsDayBreak) return false; if (player == null || target == null || EraseMethod.GetInt() == 0) return false; if (DidVote.Contains(player.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; diff --git a/Roles/Impostor/Godfather.cs b/Roles/Impostor/Godfather.cs index 5c445ecbd..a26739884 100644 --- a/Roles/Impostor/Godfather.cs +++ b/Roles/Impostor/Godfather.cs @@ -1,4 +1,6 @@ -namespace EHR.Impostor; +using EHR.Neutral; + +namespace EHR.Impostor; internal class Godfather : RoleBase { @@ -24,6 +26,7 @@ internal class Godfather : RoleBase public override bool OnVote(PlayerControl voter, PlayerControl target) { + if (Starspawn.IsDayBreak) return false; if (voter == null || target == null || voter.PlayerId == target.PlayerId || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; GodfatherTarget = target.PlayerId; diff --git a/Roles/Impostor/SerialKiller.cs b/Roles/Impostor/SerialKiller.cs index 9f96cc3a2..2f6984e0f 100644 --- a/Roles/Impostor/SerialKiller.cs +++ b/Roles/Impostor/SerialKiller.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using EHR.Modules; -using UnityEngine; namespace EHR.Impostor; @@ -13,8 +12,8 @@ public class SerialKiller : RoleBase private static OptionItem TimeLimit; private static OptionItem WaitFor1Kill; - private float SuicideTimer; private int Timer; + private long LastUpdate; public override bool IsEnable => PlayerIdList.Count > 0; @@ -37,7 +36,6 @@ public class SerialKiller : RoleBase public override void Init() { PlayerIdList = []; - SuicideTimer = TimeLimit.GetFloat(); Timer = TimeLimit.GetInt(); } @@ -45,7 +43,6 @@ public class SerialKiller : RoleBase { PlayerIdList.Add(serial); Timer = TimeLimit.GetInt(); - SuicideTimer = TimeLimit.GetFloat(); } public override void Remove(byte playerId) @@ -67,7 +64,6 @@ public class SerialKiller : RoleBase { if (!killer.Is(CustomRoles.SerialKiller)) return true; - SuicideTimer = float.NaN; Timer = TimeLimit.GetInt(); killer.MarkDirtySettings(); return true; @@ -75,44 +71,42 @@ public class SerialKiller : RoleBase public override void OnReportDeadBody() { - SuicideTimer = float.NaN; Timer = TimeLimit.GetInt(); } public override void OnFixedUpdate(PlayerControl player) { - if (!GameStates.IsInTask) return; + if (!GameStates.IsInTask || ExileController.Instance || AntiBlackout.SkipTasks || !player.IsAlive()) return; + + long now = Utils.TimeStamp; + if (now == LastUpdate) return; + LastUpdate = now; if (!HasKilled(player)) { - SuicideTimer = float.NaN; - Timer = TimeLimit.GetInt(); + Timer = int.MaxValue; return; } - if (float.IsNaN(SuicideTimer)) return; - - if (SuicideTimer >= TimeLimit.GetFloat()) + if (Timer < 0) { player.Suicide(); - SuicideTimer = float.NaN; - Timer = TimeLimit.GetInt(); + Timer = int.MaxValue; if (player.IsLocalPlayer()) Achievements.Type.OutOfTime.Complete(); } else { - SuicideTimer += Time.fixedDeltaTime; - int tempTimer = Timer; - Timer = TimeLimit.GetInt() - (int)SuicideTimer; - if (Timer != tempTimer && Timer <= 20 && !player.IsModdedClient()) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + Timer--; + + if (Timer <= 20) + player.Notify(string.Format(Translator.GetString("SerialKillerTimeLeft"), Timer), 3f, true, false); } } public override void AfterMeetingTasks() { - SuicideTimer = 0f; Timer = TimeLimit.GetInt(); } } \ No newline at end of file diff --git a/Roles/Impostor/Vampire.cs b/Roles/Impostor/Vampire.cs index fa1c52e45..e3362803a 100644 --- a/Roles/Impostor/Vampire.cs +++ b/Roles/Impostor/Vampire.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using AmongUs.GameOptions; using EHR.Crewmate; using EHR.Modules; using EHR.Neutral; @@ -75,6 +76,11 @@ public class Vampire : RoleBase PlayerIdList.Remove(playerId); } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (IsPoisoner) opt.SetVision(Poisoner.ImpostorVision.GetBool()); + } + public static bool IsVampire(byte playerId) { return PlayerIdList.Contains(playerId); @@ -93,17 +99,11 @@ public class Vampire : RoleBase public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) { if (!IsVampire(killer.PlayerId)) return true; - if (target.Is(CustomRoles.Bait)) return true; - if (target.Is(CustomRoles.Pestilence)) return true; - if (target.Is(CustomRoles.Guardian) && target.AllTasksCompleted()) return true; - if (target.Is(CustomRoles.Opportunist) && target.AllTasksCompleted() && Opportunist.OppoImmuneToAttacksWhenTasksDone.GetBool()) return true; - if (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.ContainsKey(target.PlayerId)) return true; - if (Medic.ProtectList.Contains(target.PlayerId)) return true; if (CanKillNormally) return killer.CheckDoubleTrigger(target, Bite); diff --git a/Roles/Neutral/Bargainer.cs b/Roles/Neutral/Bargainer.cs index ad1448877..69fa5c2d4 100644 --- a/Roles/Neutral/Bargainer.cs +++ b/Roles/Neutral/Bargainer.cs @@ -415,8 +415,8 @@ internal class Bargainer : RoleBase switch (ActiveItems[i].Item) { case Item.LensOfTruth: - if ((AlignmentVisibleOptions)AlignmentVisible.GetValue() is AlignmentVisibleOptions.ForSpecifiedTime or AlignmentVisibleOptions.UntilNextMeeting) indexesToRemove.Add(i); - + if ((AlignmentVisibleOptions)AlignmentVisible.GetValue() is AlignmentVisibleOptions.ForSpecifiedTime or AlignmentVisibleOptions.UntilNextMeeting) + indexesToRemove.Add(i); break; default: indexesToRemove.Add(i); @@ -436,7 +436,6 @@ internal class Bargainer : RoleBase public override bool KnowRole(PlayerControl seer, PlayerControl target) { if (base.KnowRole(seer, target)) return true; - return Main.PlayerStates[seer.PlayerId].Role is Bargainer bg && bg.ActiveItems.Any(x => x.Target == target.PlayerId); } @@ -470,9 +469,7 @@ internal class Bargainer : RoleBase public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) { - if (seer.PlayerId != target.PlayerId) return string.Empty; - - if (Main.PlayerStates[seer.PlayerId].Role is not Bargainer bg) return string.Empty; + if (seer.PlayerId != target.PlayerId || Main.PlayerStates[seer.PlayerId].Role is not Bargainer bg) return string.Empty; var result = string.Empty; diff --git a/Roles/Neutral/Cherokious.cs b/Roles/Neutral/Cherokious.cs index ad6fa21d6..d96ee2efb 100644 --- a/Roles/Neutral/Cherokious.cs +++ b/Roles/Neutral/Cherokious.cs @@ -1,4 +1,5 @@ -using EHR.Crewmate; +using AmongUs.GameOptions; +using EHR.Crewmate; using EHR.Patches; namespace EHR.Neutral; @@ -8,6 +9,7 @@ internal class Cherokious : RoleBase public static bool On; public static OptionItem KillCooldown; + public static OptionItem ImpostorVision; public override bool IsEnable => On; public override void SetupCustomOption() @@ -19,7 +21,10 @@ internal class Cherokious : RoleBase .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Cherokious]) .SetValueFormat(OptionFormat.Seconds); - Options.OverrideTasksData.Create(id + 3, TabGroup.NeutralRoles, CustomRoles.Cherokious); + ImpostorVision = new BooleanOptionItem(id + 3, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Cherokious]); + + Options.OverrideTasksData.Create(id + 4, TabGroup.NeutralRoles, CustomRoles.Cherokious); } public override void Add(byte playerId) @@ -33,6 +38,11 @@ internal class Cherokious : RoleBase On = false; } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(ImpostorVision.GetBool()); + } + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) { pc.RpcIncreaseAbilityUseLimitBy(1); diff --git a/Roles/Neutral/Doppelganger.cs b/Roles/Neutral/Doppelganger.cs index 673c8be27..8836be555 100644 --- a/Roles/Neutral/Doppelganger.cs +++ b/Roles/Neutral/Doppelganger.cs @@ -146,6 +146,8 @@ public class Doppelganger : RoleBase private static void RpcChangeSkin(PlayerControl pc, NetworkedPlayerInfo.PlayerOutfit newOutfit) { + if (!AmongUsClient.Instance.AmHost) return; + var sender = CustomRpcSender.Create($"Doppelganger.RpcChangeSkin({pc.Data.PlayerName})", SendOption.Reliable); pc.SetName(newOutfit.PlayerName); diff --git a/Roles/Neutral/Enderman.cs b/Roles/Neutral/Enderman.cs index e49483377..9541c6f33 100644 --- a/Roles/Neutral/Enderman.cs +++ b/Roles/Neutral/Enderman.cs @@ -10,8 +10,9 @@ internal class Enderman : RoleBase private static OptionItem KillCooldown; private static OptionItem CanVent; private static OptionItem Time; - private byte EndermanId = byte.MaxValue; + private static OptionItem ImpostorVision; + private byte EndermanId = byte.MaxValue; private (Vector2 Position, long MarkTimeStamp, bool TP) MarkedPosition = (Vector2.zero, 0, false); private static int Id => 643200; @@ -33,6 +34,9 @@ internal class Enderman : RoleBase Time = new IntegerOptionItem(Id + 4, "EndermanSecondsBeforeTP", new(1, 60, 1), 7, TabGroup.NeutralRoles) .SetParent(CustomRoleSpawnChances[CustomRoles.Enderman]) .SetValueFormat(OptionFormat.Seconds); + + ImpostorVision = new BooleanOptionItem(Id + 5, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Enderman]); } public override void Init() @@ -64,7 +68,7 @@ internal class Enderman : RoleBase public override void ApplyGameOptions(IGameOptions opt, byte id) { - opt.SetVision(true); + opt.SetVision(ImpostorVision.GetBool()); if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = Time.GetInt() + 2f; } diff --git a/Roles/Neutral/Executioner.cs b/Roles/Neutral/Executioner.cs index 6868d0432..39b70369a 100644 --- a/Roles/Neutral/Executioner.cs +++ b/Roles/Neutral/Executioner.cs @@ -68,7 +68,7 @@ public class Executioner : RoleBase { List targetList = []; targetList.AddRange(from target in Main.AllPlayerControls where playerId != target.PlayerId where CanTargetImpostor.GetBool() || !target.Is(CustomRoleTypes.Impostor) where CanTargetNeutralKiller.GetBool() || !target.IsNeutralKiller() where CanTargetNeutralBenign.GetBool() || !target.IsNeutralBenign() where CanTargetNeutralEvil.GetBool() || (!target.IsNeutralEvil() && !target.IsNeutralPariah()) where CanTargetCoven.GetBool() || !target.Is(CustomRoleTypes.Coven) where target.GetCustomRole() is not (CustomRoles.GM or CustomRoles.SuperStar) where Main.LoversPlayers.TrueForAll(x => x.PlayerId != playerId) select target); - targetList.AddRange(Main.AllPlayerControls.Where(x => x.IsCrewmate())); + targetList.AddRange(Main.AllPlayerControls.Where(x => x.IsCrewmate() && x.GetCustomRole() is not (CustomRoles.NiceSwapper or CustomRoles.Mayor or CustomRoles.NiceGuesser or CustomRoles.Dictator))); if (!CanTargetNeutralBenign.GetBool() && !CanTargetNeutralEvil.GetBool() && !CanTargetNeutralKiller.GetBool()) targetList.RemoveAll(x => x.GetCustomRole().IsNeutral() || x.Is(CustomRoles.Bloodlust)); if (targetList.Count == 0) diff --git a/Roles/Neutral/Lawyer.cs b/Roles/Neutral/Lawyer.cs index f22b64710..6c3497985 100644 --- a/Roles/Neutral/Lawyer.cs +++ b/Roles/Neutral/Lawyer.cs @@ -92,6 +92,7 @@ public class Lawyer : RoleBase public override void Remove(byte playerId) { PlayerIdList.Remove(playerId); + Target.Remove(playerId); } public static void SendRPC(byte lawyerId, byte targetId = 0x73, string Progress = "") diff --git a/Roles/Neutral/Nonplus.cs b/Roles/Neutral/Nonplus.cs index d9cf3d4e9..0ee8be183 100644 --- a/Roles/Neutral/Nonplus.cs +++ b/Roles/Neutral/Nonplus.cs @@ -1,4 +1,6 @@ -namespace EHR.Neutral; +using AmongUs.GameOptions; + +namespace EHR.Neutral; public class Nonplus : RoleBase { @@ -8,6 +10,7 @@ public class Nonplus : RoleBase public static OptionItem BlindDuration; public static OptionItem UseLimit; public static OptionItem NonplusAbilityUseGainWithEachKill; + public static OptionItem ImpostorVision; public override bool IsEnable => On; public override void SetupCustomOption() @@ -33,6 +36,9 @@ public class Nonplus : RoleBase NonplusAbilityUseGainWithEachKill = new FloatOptionItem(id + 5, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 1.5f, tab) .SetParent(Options.CustomRoleSpawnChances[role]) .SetValueFormat(OptionFormat.Times); + + ImpostorVision = new BooleanOptionItem(id + 6, "ImpostorVision", true, tab) + .SetParent(Options.CustomRoleSpawnChances[role]); } public override void Init() @@ -46,6 +52,11 @@ public class Nonplus : RoleBase playerId.SetAbilityUseLimit(UseLimit.GetFloat()); } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(ImpostorVision.GetBool()); + } + public override bool CanUseImpostorVentButton(PlayerControl pc) { return true; diff --git a/Roles/Neutral/Phantasm.cs b/Roles/Neutral/Phantasm.cs index 9e497f7ea..bc147bc6e 100644 --- a/Roles/Neutral/Phantasm.cs +++ b/Roles/Neutral/Phantasm.cs @@ -25,4 +25,13 @@ internal class Phantasm : RoleBase public override void Init() { } public override void Add(byte playerId) { } + + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + if (completedTaskCount + 1 >= totalTaskCount) + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Phantom); + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + } + } } \ No newline at end of file diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index be1eac394..7f4edf530 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -125,6 +125,7 @@ public class PlagueBearer : RoleBase } PlaguedList[killer.PlayerId].Add(target.PlayerId); + SendRPC(killer, target); Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); killer.ResetKillCooldown(); killer.SetKillCooldown(); diff --git a/Roles/Neutral/Poisoner.cs b/Roles/Neutral/Poisoner.cs index 9f49f9bc6..0dd52fee4 100644 --- a/Roles/Neutral/Poisoner.cs +++ b/Roles/Neutral/Poisoner.cs @@ -7,6 +7,7 @@ public class Poisoner : RoleBase public static OptionItem CanVent; public static OptionItem KillCooldown; public static OptionItem CanKillNormally; + public static OptionItem ImpostorVision; public override bool IsEnable => false; @@ -27,6 +28,9 @@ public class Poisoner : RoleBase CanKillNormally = new BooleanOptionItem(Id + 13, "CanKillNormally", true, TabGroup.NeutralRoles) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]); + + ImpostorVision = new BooleanOptionItem(Id + 14, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]); } public override void Init() { } diff --git a/Roles/Neutral/Rogue.cs b/Roles/Neutral/Rogue.cs index 70bbb39ff..cee572082 100644 --- a/Roles/Neutral/Rogue.cs +++ b/Roles/Neutral/Rogue.cs @@ -17,8 +17,8 @@ public class Rogue : RoleBase private static OptionItem KillCooldown; private static OptionItem CanVent; - private bool AllTasksCompleted; + private bool AllTasksCompleted; private int Count; private (Objective Objective, Reward Reward, object Data, bool IsCompleted) CurrentTask; private bool DoCheck; @@ -93,18 +93,29 @@ public class Rogue : RoleBase public override bool OnSabotage(PlayerControl pc) { - if (GotRewards.Contains(Reward.Morph) && MorphCooldown <= 0) + if (!Options.UsePets.GetBool() && GotRewards.Contains(Reward.Morph) && MorphCooldown <= 0) { - MorphCooldown = 15 + (int)Options.AdjustedDefaultKillCooldown; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2, MorphCooldown); - PlayerControl target = Main.AllAlivePlayerControls.Except([pc]).RandomElement(); - pc.RpcShapeshift(target, !Options.DisableAllShapeshiftAnimations.GetBool()); + Morph(pc); return false; } return GotRewards.Contains(Reward.Sabotage) || pc.Is(CustomRoles.Mischievous); } + public override void OnPet(PlayerControl pc) + { + if (GotRewards.Contains(Reward.Morph) && MorphCooldown <= 0) + Morph(pc); + } + + private void Morph(PlayerControl pc) + { + MorphCooldown = 15 + (int)Options.AdjustedDefaultKillCooldown; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2, MorphCooldown); + PlayerControl target = Main.AllAlivePlayerControls.Except([pc]).RandomElement(); + pc.RpcShapeshift(target, !Options.DisableAllShapeshiftAnimations.GetBool()); + } + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) { return !GotRewards.Contains(Reward.Shield); diff --git a/Roles/Neutral/Tiger.cs b/Roles/Neutral/Tiger.cs index b33f3e88a..1d30cb3e8 100644 --- a/Roles/Neutral/Tiger.cs +++ b/Roles/Neutral/Tiger.cs @@ -14,6 +14,7 @@ internal class Tiger : RoleBase public static OptionItem EnrageDuration; public static OptionItem KillCooldown; public static OptionItem CanVent; + public static OptionItem ImpostorVision; public static bool On; @@ -45,6 +46,9 @@ internal class Tiger : RoleBase CanVent = new BooleanOptionItem(Id + 6, "CanVent", true, TabGroup.NeutralRoles) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiger]); + + ImpostorVision = new BooleanOptionItem(Id + 7, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiger]); } public override void Add(byte playerId) @@ -76,7 +80,10 @@ internal class Tiger : RoleBase public override void ApplyGameOptions(IGameOptions opt, byte playerId) { - if (Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = EnrageCooldown.GetFloat() + EnrageDuration.GetFloat(); + opt.SetVision(ImpostorVision.GetBool()); + + if (Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()) + AURoleOptions.PhantomCooldown = EnrageCooldown.GetFloat() + EnrageDuration.GetFloat(); } public override bool OnSabotage(PlayerControl pc) From ad73463258fb9ca8f0f4844504293b580e2db3f0 Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Sun, 24 Aug 2025 14:22:49 +0200 Subject: [PATCH 2/5] fix pos --- GameMode/BedWars.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/GameMode/BedWars.cs b/GameMode/BedWars.cs index e96ff396e..c01052f95 100644 --- a/GameMode/BedWars.cs +++ b/GameMode/BedWars.cs @@ -852,13 +852,13 @@ public static class BedWars { [BedWarsTeam.Blue] = new(new(-20.29f, -5.31f), SystemTypes.Reactor, new(-21.61f, -8.2f), new(-21.47f, -2.24f), new(-22.56f, -2.91f)), [BedWarsTeam.Yellow] = new(new(-9.04f, -1.79f), SystemTypes.MedBay, new(-7.68f, -3.7f), new(-7.68f, -5.18f), new(-5.67f, -5.31f)), - [BedWarsTeam.Red] = new(new(5.08f, -14.71f), SystemTypes.Comms, new(2.72f, -14.76f), new(2.72f, -17.05f), new(5.63f, -16.71f)), + [BedWarsTeam.Red] = new(new(5.08f, -14.71f), SystemTypes.Comms, new(2.72f, -14.76f), new(2.72f, -16.37f), new(5.63f, -16.71f)), [BedWarsTeam.Green] = new(new(14.54f, -4.61f), SystemTypes.Nav, new(16.57f, -3.21f), new(16.57f, -6.12f), new(17.57f, -4.07f)) }, [MapNames.MiraHQ] = new() { [BedWarsTeam.Blue] = new(new(2.81f, -1.43f), SystemTypes.Launchpad, new(-4.46f, 0.48f), new(-4.46f, 4.12f), new(-4.45f, 2.4f)), - [BedWarsTeam.Yellow] = new(new(22.16f, 1.5f), SystemTypes.Balcony, new(20.02f, -1.81f), new(27.62f, -1.81f), new(23.77f, -1.58f)), + [BedWarsTeam.Yellow] = new(new(22.37f, 1.5f), SystemTypes.Balcony, new(20.02f, -1.81f), new(27.62f, -1.81f), new(23.77f, -1.58f)), [BedWarsTeam.Red] = new(new(17.8f, 23.17f), SystemTypes.Greenhouse, new(13.58f, 22.4f), new(22.08f, 22.37f), new(17.85f, 25.55f)), [BedWarsTeam.Green] = new(new(6.12f, 12.12f), SystemTypes.Reactor, new(1.69f, 10.63f), new(8.88f, 12.38f), new(2.47f, 11.9f)) }, @@ -1538,7 +1538,7 @@ public static class BedWars LateTask.New(() => { if (GameStates.IsEnded || !GameStates.InGame || GameStates.IsLobby) return; - Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.KillCooldown); + Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); pc.MarkDirtySettings(); }, SpeedPotionDuration); break; From 4d4075bec03c3606585aaaa53623dde98832b08a Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Sun, 24 Aug 2025 14:27:43 +0200 Subject: [PATCH 3/5] strings --- Resources/Lang/en_US.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 75ac8b22d..9f45b010c 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3534,6 +3534,12 @@ "JailedNotifyMsg": "You have been locked up in Jail by the Jailor. No one can guess or judge you and you can only guess Jailor.", "JailorTitle": "Jailor Message", "JailedSuffix": "JAILED", + "Commited": "Commited", + "CommitedInfo": "Vote Equivalently for Low Cooldown", + "CommitedInfoLong": "(Add-ons):\\nAs the Commited, you get a random target at the start of every meeting. They will have a ⌆ symbol next to their name. If you vote for the same player as your target, your kill cooldown will be reduced. Skips may be ignored.", + "Commited.PermanentReduction": "Cooldown Reduction is Permanent", + "Commited.Reduction": "Cooldown is Reduced by", + "Commited.IgnoreSkips": "Ignore Skips", "Car": "Car", "CarInfo": "Drive around the map", "CarInfoLong": "(Crewmates):\\nAs the Car, you can drive around the map. Everyone sees you as a car, including yourself. When you touch someone, you propel them far away in the direction you were driving. This role has absolutely no purpose other than to have fun.", From ef04ef0d3e70edcdb639ef4cb638182ff5e551c7 Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Sun, 24 Aug 2025 21:09:41 +0200 Subject: [PATCH 4/5] sendoption.none --- GameMode/BedWars.cs | 6 +++--- GameMode/Speedrun.cs | 3 ++- Modules/NameNotifyManager.cs | 10 +++++----- Patches/SabotageSystemPatch.cs | 4 ++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/GameMode/BedWars.cs b/GameMode/BedWars.cs index c01052f95..6a244f002 100644 --- a/GameMode/BedWars.cs +++ b/GameMode/BedWars.cs @@ -728,7 +728,7 @@ public static class BedWars { if (pc == null) yield break; - pc.Notify(string.Format(Translator.GetString("Bedwars.ReviveCountdown"), time), overrideAll: true); + pc.Notify(string.Format(Translator.GetString("Bedwars.ReviveCountdown"), time), overrideAll: true, sendOption: SendOption.None); yield return new WaitForSeconds(1f); time--; } @@ -754,13 +754,13 @@ public static class BedWars public string BuildSuffix(PlayerControl pc) { - if (!pc.IsAlive()) return string.Empty; - var sb = new StringBuilder(); if (NameNotifyManager.GetNameNotify(pc, out string notify) && notify.Length > 0) sb.AppendLine(notify); + if (!pc.IsAlive()) return $"<#ffffff>{sb.ToString().Trim()}"; + sb.AppendLine(IsGracePeriod ? Translator.GetString("Bedwars.GracePeriod") : GetHealthInfo()); var topLines = 12; diff --git a/GameMode/Speedrun.cs b/GameMode/Speedrun.cs index 8d66aef01..6115cbabd 100644 --- a/GameMode/Speedrun.cs +++ b/GameMode/Speedrun.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using EHR.Modules; +using Hazel; using UnityEngine; namespace EHR; @@ -77,7 +78,7 @@ public static class Speedrun int kcd = KillCooldown.GetInt(); Main.AllPlayerKillCooldown[pc.PlayerId] = kcd; pc.RpcChangeRoleBasis(CustomRoles.NSerialKiller); - pc.Notify(Translator.GetString("Speedrun_CompletedTasks")); + pc.Notify(Translator.GetString("Speedrun_CompletedTasks"), sendOption: SendOption.None); pc.SyncSettings(); LateTask.New(() => pc.SetKillCooldown(kcd), 0.2f, log: false); } diff --git a/Modules/NameNotifyManager.cs b/Modules/NameNotifyManager.cs index 8191b7ca5..3c9697a45 100644 --- a/Modules/NameNotifyManager.cs +++ b/Modules/NameNotifyManager.cs @@ -16,7 +16,7 @@ public static class NameNotifyManager Notifies = []; } - public static void Notify(this PlayerControl pc, string text, float time = 6f, bool overrideAll = false, bool log = true) + public static void Notify(this PlayerControl pc, string text, float time = 6f, bool overrideAll = false, bool log = true, SendOption sendOption = SendOption.Reliable) { if (!AmongUsClient.Instance.AmHost || pc == null) return; if (!GameStates.IsInTask) return; @@ -30,8 +30,8 @@ public static class NameNotifyManager else notifies[text] = expireTS; - if (pc.IsNonHostModdedClient()) SendRPC(pc.PlayerId, text, expireTS, overrideAll); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + if (pc.IsNonHostModdedClient()) SendRPC(pc.PlayerId, text, expireTS, overrideAll, sendOption); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc, SendOption: sendOption); if (log) Logger.Info($"New name notify for {pc.GetNameWithRole().RemoveHtmlTags()}: {text} ({time}s)", "Name Notify"); } @@ -73,11 +73,11 @@ public static class NameNotifyManager return true; } - private static void SendRPC(byte playerId, string text, long expireTS, bool overrideAll) // Only sent when adding a new notification + private static void SendRPC(byte playerId, string text, long expireTS, bool overrideAll, SendOption sendOption) // Only sent when adding a new notification { if (!AmongUsClient.Instance.AmHost) return; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncNameNotify, SendOption.Reliable); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncNameNotify, sendOption); writer.Write(playerId); writer.Write(text); writer.Write(expireTS.ToString()); diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index e83cf6bb9..7c41e9ba8 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -280,8 +280,6 @@ public static class ElectricTaskCompletePatch if (LastUpdate >= now) return; LastUpdate = now; - Utils.MarkEveryoneDirtySettingsV2(); - if (GameStates.IsInTask) { foreach (PlayerControl pc in Main.AllAlivePlayerControls) @@ -302,6 +300,8 @@ public static class ElectricTaskCompletePatch } } + Utils.MarkEveryoneDirtySettingsV2(); + Logger.Info("Lights sabotage fixed", "ElectricTask"); } } From 07aa91312c5b8f3e1b6d30c94d7f3004cee69fbb Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Sun, 24 Aug 2025 21:10:22 +0200 Subject: [PATCH 5/5] more none --- Patches/SabotageSystemPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index 7c41e9ba8..23a357b5b 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -261,7 +261,7 @@ public static class ElectricTaskInitializePatch foreach (PlayerControl pc in Main.AllAlivePlayerControls) { if (pc.GetCustomRole().NeedUpdateOnLights() || pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.Sleep) || Beacon.IsAffectedPlayer(pc.PlayerId)) - Utils.NotifyRoles(SpecifyTarget: pc, ForceLoop: true); + Utils.NotifyRoles(SpecifyTarget: pc, ForceLoop: true, SendOption: SendOption.None); } } @@ -287,7 +287,7 @@ public static class ElectricTaskCompletePatch CustomRoles role = pc.GetCustomRole(); if (role.NeedUpdateOnLights() || pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.Sleep) || Beacon.IsAffectedPlayer(pc.PlayerId)) - Utils.NotifyRoles(SpecifyTarget: pc, ForceLoop: true); + Utils.NotifyRoles(SpecifyTarget: pc, ForceLoop: true, SendOption: SendOption.None); if (role == CustomRoles.Wiper) {