From 1e9079d5bc4f0a4225e0b5d27783a4376e96458e Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Thu, 18 Jul 2024 18:55:29 +0200 Subject: [PATCH] Unshift button trigger (it's amazing), Add Beehive, Add Oxyman, Add Rhapsode, Bug Fixes --- CTA/CustomTeamAssigner/CustomRoles.cs | 3 + CustomRoles.cs | 3 + EHR.sln.DotSettings | 1 + Modules/CustomRolesHelper.cs | 20 +- .../PlayerGameOptionsSender.cs | 5 +- Modules/GuessManager.cs | 1 + Modules/NameColorManager.cs | 1 + Modules/OptionHolder.cs | 23 ++- Modules/ServerAddManager.cs | 3 +- Modules/Utils.cs | 74 +++++++- Patches/CheckGameEndPatch.cs | 10 +- Patches/CredentialsPatch.cs | 2 + Patches/GameOptionsMenuPatch.cs | 2 +- Patches/IntroPatch.cs | 18 ++ Patches/OnGameStartedPatch.cs | 13 +- Patches/PlayerControlPatch.cs | 48 +++-- Resources/Lang/en_US.json | 52 ++++-- Roles/Crewmate/Chef.cs | 9 +- Roles/Crewmate/Clairvoyant.cs | 21 ++- Roles/Crewmate/DonutDelivery.cs | 63 ++++++- Roles/Crewmate/Marshall.cs | 2 +- Roles/Crewmate/Mediumshiper.cs | 4 +- Roles/Crewmate/Oxyman.cs | 174 ++++++++++++++++++ Roles/Crewmate/Rhapsode.cs | 118 ++++++++++++ Roles/Crewmate/Sheriff.cs | 2 + Roles/Impostor/Abyssbringer.cs | 1 + Roles/Impostor/Assassin.cs | 22 +-- Roles/Impostor/Bomber.cs | 2 +- Roles/Impostor/Camouflager.cs | 10 +- Roles/Impostor/Disperser.cs | 2 +- Roles/Impostor/Escapee.cs | 2 +- Roles/Impostor/Librarian.cs | 4 +- Roles/Impostor/Miner.cs | 2 +- Roles/Impostor/QuickShooter.cs | 2 +- Roles/Impostor/RiftMaker.cs | 2 +- Roles/Impostor/Sapper.cs | 1 + Roles/Impostor/Sniper.cs | 2 +- Roles/Impostor/Swiftclaw.cs | 9 + Roles/Impostor/Warlock.cs | 2 +- Roles/Neutral/Amnesiac.cs | 35 +++- Roles/Neutral/Bargainer.cs | 9 + Roles/Neutral/Beehive.cs | 119 ++++++++++++ Roles/Neutral/Chemist.cs | 12 ++ Roles/Neutral/Enderman.cs | 9 + Roles/Neutral/Hookshot.cs | 9 + Roles/Neutral/Lawyer.cs | 18 +- Roles/Neutral/Magician.cs | 12 +- Roles/Neutral/Mycologist.cs | 16 +- Roles/Neutral/Patroller.cs | 9 + Roles/Neutral/RouleteGrandeur.cs | 9 + Roles/Neutral/Simon.cs | 9 + Roles/Neutral/Sprayer.cs | 9 + Roles/Neutral/Tiger.cs | 13 ++ Roles/Neutral/WeaponMaster.cs | 9 + Roles/Neutral/Werewolf.cs | 9 + main.cs | 4 + 56 files changed, 927 insertions(+), 118 deletions(-) create mode 100644 Roles/Crewmate/Oxyman.cs create mode 100644 Roles/Crewmate/Rhapsode.cs create mode 100644 Roles/Neutral/Beehive.cs diff --git a/CTA/CustomTeamAssigner/CustomRoles.cs b/CTA/CustomTeamAssigner/CustomRoles.cs index ff4170a67..f92b13efc 100644 --- a/CTA/CustomTeamAssigner/CustomRoles.cs +++ b/CTA/CustomTeamAssigner/CustomRoles.cs @@ -199,12 +199,14 @@ public enum CustomRoles Nightmare, Observer, Oracle, + Oxyman, Paranoia, Perceiver, Philantropist, Psychic, Rabbit, Randomizer, + Rhapsode, Ricochet, Safeguard, Scout, @@ -244,6 +246,7 @@ public enum CustomRoles Arsonist, Bandit, Bargainer, + Beehive, BloodKnight, Bubble, Chemist, diff --git a/CustomRoles.cs b/CustomRoles.cs index ff4170a67..f92b13efc 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -199,12 +199,14 @@ public enum CustomRoles Nightmare, Observer, Oracle, + Oxyman, Paranoia, Perceiver, Philantropist, Psychic, Rabbit, Randomizer, + Rhapsode, Ricochet, Safeguard, Scout, @@ -244,6 +246,7 @@ public enum CustomRoles Arsonist, Bandit, Bargainer, + Beehive, BloodKnight, Bubble, Chemist, diff --git a/EHR.sln.DotSettings b/EHR.sln.DotSettings index 929413dec..2f976bca1 100644 --- a/EHR.sln.DotSettings +++ b/EHR.sln.DotSettings @@ -28,6 +28,7 @@ PC RNG RPC + SE SK SS SSCD diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 20b7d7d7a..ea4b32160 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -86,7 +86,8 @@ internal static class CustomRolesHelper if (role.IsGhostRole()) return CustomRoles.GuardianAngel; if (role.IsVanilla()) return role; if (checkDesyncRole && role.GetDYRole() == RoleTypes.Impostor) return CustomRoles.Impostor; - if (Options.UsePhantomBasis.GetBool() && role.InvisActivatedAbility()) return CustomRoles.Phantom; + if (Options.UsePhantomBasis.GetBool() && role.SimpleAbilityTrigger()) return CustomRoles.Phantom; + if (Options.UseUnshiftTrigger.GetBool() && role.SimpleAbilityTrigger()) return CustomRoles.Shapeshifter; bool UsePets = Options.UsePets.GetBool(); return role switch { @@ -196,11 +197,13 @@ internal static class CustomRolesHelper CustomRoles.GuessManagerRole => CustomRoles.Crewmate, CustomRoles.Altruist => CustomRoles.Crewmate, CustomRoles.Transmitter => CustomRoles.Crewmate, + CustomRoles.Oxyman => CustomRoles.Engineer, CustomRoles.Chef => CustomRoles.Crewmate, CustomRoles.Lyncher => CustomRoles.Crewmate, CustomRoles.Adrenaline => CustomRoles.Crewmate, CustomRoles.Safeguard => CustomRoles.Crewmate, CustomRoles.Clairvoyant => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Rhapsode => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, CustomRoles.Telekinetic => CustomRoles.Engineer, CustomRoles.Autocrat => CustomRoles.Crewmate, CustomRoles.Inquirer => CustomRoles.Crewmate, @@ -343,7 +346,8 @@ internal static class CustomRolesHelper public static RoleTypes GetDYRole(this CustomRoles role, bool load = false) { - if (!load && Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool() && !role.IsImpostor() && role.InvisActivatedAbility()) return RoleTypes.Phantom; + if (!load && Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool() && !role.IsImpostor() && role.SimpleAbilityTrigger()) return RoleTypes.Phantom; + if (!load && Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool() && !role.IsImpostor() && role.SimpleAbilityTrigger()) return RoleTypes.Shapeshifter; bool UsePets = !load && Options.UsePets.GetBool(); return role switch { @@ -398,6 +402,7 @@ internal static class CustomRolesHelper CustomRoles.BloodKnight => RoleTypes.Impostor, CustomRoles.Poisoner => RoleTypes.Impostor, CustomRoles.NSerialKiller => RoleTypes.Impostor, + CustomRoles.Beehive => RoleTypes.Impostor, CustomRoles.RouleteGrandeur => RoleTypes.Impostor, CustomRoles.Nonplus => RoleTypes.Impostor, CustomRoles.Tremor => RoleTypes.Impostor, @@ -514,6 +519,7 @@ internal static class CustomRolesHelper CustomRoles.Rogue or CustomRoles.Parasite or CustomRoles.NSerialKiller or + CustomRoles.Beehive or CustomRoles.RouleteGrandeur or CustomRoles.Nonplus or CustomRoles.Tremor or @@ -848,7 +854,7 @@ internal static class CustomRolesHelper public static bool IsNoAnimationShifter(this CustomRoles role) => role is CustomRoles.Echo; - public static bool InvisActivatedAbility(this CustomRoles role) => role is + public static bool SimpleAbilityTrigger(this CustomRoles role) => role is CustomRoles.Warlock or CustomRoles.Swiftclaw or CustomRoles.Undertaker or @@ -883,6 +889,7 @@ internal static class CustomRolesHelper public static bool CheckAddonConflict(CustomRoles role, PlayerControl pc) => role.IsAdditionRole() && (!Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out var neverList) || !neverList.TryGetValue(pc.GetCustomRole(), out var 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 var o) || ((o.Imp.GetBool() || !pc.GetCustomRole().IsImpostor()) && (o.Neutral.GetBool() || !pc.GetCustomRole().IsNeutral()) && (o.Crew.GetBool() || !pc.IsCrewmate()))) && (!role.IsImpOnlyAddon() || pc.IsImpostor()) && role switch { + CustomRoles.Oblivious when pc.Is(CustomRoles.Amnesiac) && Amnesiac.RememberMode.GetValue() == 1 => false, CustomRoles.Rookie when !pc.CanUseKillButton() => false, CustomRoles.Energetic when !Options.UsePets.GetBool() => false, CustomRoles.Madmate when pc.Is(CustomRoles.Sidekick) => false, @@ -908,7 +915,7 @@ internal static class CustomRolesHelper 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.Flashman when pc.Is(CustomRoles.Swiftclaw) || pc.Is(CustomRoles.Giant) || pc.Is(CustomRoles.Spurt) => false, CustomRoles.Giant when pc.Is(CustomRoles.Flashman) || pc.Is(CustomRoles.Spurt) => false, CustomRoles.Necroview when pc.Is(CustomRoles.Visionary) => false, CustomRoles.Mimic when pc.Is(CustomRoles.Mafia) => false, @@ -1124,6 +1131,7 @@ internal static class CustomRolesHelper CustomRoles.SchrodingersCat => SchrodingersCat.WinsWithCrewIfNotAttacked.GetBool() ? CountTypes.Crew : CountTypes.OutOfGame, CustomRoles.DarkHide => !DarkHide.SnatchesWin.GetBool() ? CountTypes.DarkHide : CountTypes.Crew, CustomRoles.Arsonist => Options.ArsonistKeepsGameGoing.GetBool() ? CountTypes.Arsonist : CountTypes.Crew, + CustomRoles.Sheriff => Sheriff.KeepsGameGoing.GetBool() ? CountTypes.Sheriff : CountTypes.Crew, CustomRoles.Shifter => CountTypes.OutOfGame, _ => Enum.TryParse(role.ToString(), true, out CountTypes type) @@ -1256,6 +1264,7 @@ public enum CountTypes HexMaster, Wraith, NSerialKiller, + Beehive, RouleteGrandeur, Nonplus, Tremor, @@ -1299,5 +1308,6 @@ public enum CountTypes PlagueBearer, Glitch, Arsonist, - Cherokious + Cherokious, + Sheriff } \ No newline at end of file diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index 13e373f5b..f745046ab 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -413,9 +413,12 @@ public class PlayerGameOptionsSender(PlayerControl player) : GameOptionsSender AURoleOptions.ShapeshifterDuration = 1f; } - if (Options.UsePhantomBasis.GetBool() && role.InvisActivatedAbility()) + if (Options.UsePhantomBasis.GetBool() && role.SimpleAbilityTrigger()) AURoleOptions.PhantomDuration = 1f; + if (Options.UseUnshiftTrigger.GetBool() && role.SimpleAbilityTrigger()) + AURoleOptions.ShapeshifterDuration = 0f; + // =================================================================================================================== AURoleOptions.EngineerCooldown = Mathf.Max(0.01f, AURoleOptions.EngineerCooldown); diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 00b65f238..ac7d9bb87 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -321,6 +321,7 @@ public static class GuessManager if (!isUI) Utils.SendMessage(GetString("GuessSuperStar"), pc.PlayerId); else pc.ShowPopUp(GetString("GuessSuperStar")); return true; + case CustomRoles.DonutDelivery when DonutDelivery.IsUnguessable(pc, target): case CustomRoles.Shifter: case CustomRoles.Goose when !Goose.CanBeGuessed.GetBool(): case CustomRoles.Disco: diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index 9a1bab351..01e3b841d 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -151,6 +151,7 @@ public static class NameColorManager CustomRoles.Augmenter when ((Augmenter)Main.PlayerStates[seer.PlayerId].Role).Target == target.PlayerId => "000000", CustomRoles.Socialite when ((Socialite)Main.PlayerStates[seer.PlayerId].Role).GuestList.Contains(target.PlayerId) => "000000", CustomRoles.Socialite when ((Socialite)Main.PlayerStates[seer.PlayerId].Role).MarkedPlayerId == target.PlayerId => Main.RoleColors[seerRole], + CustomRoles.Beehive when ((Beehive)Main.PlayerStates[seer.PlayerId].Role).StungPlayers.ContainsKey(target.PlayerId) => "000000", _ => color }; diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 32231aee3..5a5357dfe 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -655,6 +655,8 @@ public static class Options public static OptionItem UsePets; public static OptionItem PetToAssignToEveryone; + public static OptionItem UseUnshiftTrigger; + public static OptionItem UseUnshiftTriggerForNKs; public static OptionItem UsePhantomBasis; public static OptionItem UsePhantomBasisForNKs; public static OptionItem UseVoteCancelling; @@ -1620,23 +1622,24 @@ public static class Options UsePets = new BooleanOptionItem(23850, "UsePets", false, TabGroup.TaskSettings) .SetHeader(true) - .SetColor(new Color32(60, 0, 255, byte.MaxValue)) - .RegisterUpdateValueEvent((o, _) => - { - if ((((OptionItem)o)!).GetBool()) UsePhantomBasis.SetValue(0); - }); + .SetColor(new Color32(60, 0, 255, byte.MaxValue)); PetToAssignToEveryone = new StringOptionItem(23854, "PetToAssign", PetToAssign, 24, TabGroup.TaskSettings) .SetParent(UsePets) .SetColor(new Color32(60, 0, 255, byte.MaxValue)); + UseUnshiftTrigger = new BooleanOptionItem(23871, "UseUnshiftTrigger", false, TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(new Color32(255, 44, 44, byte.MaxValue)); + UseUnshiftTriggerForNKs = new BooleanOptionItem(23872, "UseUnshiftTriggerForNKs", false, TabGroup.TaskSettings) + .SetParent(UseUnshiftTrigger) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 44, 44, byte.MaxValue)); + UsePhantomBasis = new BooleanOptionItem(23851, "UsePhantomBasis", false, TabGroup.TaskSettings) .SetGameMode(CustomGameMode.Standard) .SetHeader(true) - .SetColor(new Color32(255, 255, 44, byte.MaxValue)) - .RegisterUpdateValueEvent((o, _) => - { - if ((((OptionItem)o)!).GetBool()) UsePets.SetValue(0); - }); + .SetColor(new Color32(255, 255, 44, byte.MaxValue)); UsePhantomBasisForNKs = new BooleanOptionItem(23864, "UsePhantomBasisForNKs", false, TabGroup.TaskSettings) .SetGameMode(CustomGameMode.Standard) .SetParent(UsePhantomBasis) diff --git a/Modules/ServerAddManager.cs b/Modules/ServerAddManager.cs index 8b391bfa9..1cb713192 100644 --- a/Modules/ServerAddManager.cs +++ b/Modules/ServerAddManager.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.Linq; using System.Linq.Expressions; using Il2CppInterop.Runtime.InteropTypes; -using StringComparison = Il2CppSystem.StringComparison; namespace EHR; @@ -32,7 +31,7 @@ public static class ServerAddManager // regionInfos.Add(CreateHttp("35.247.251.253", "Modded SA (MSA)", 22023, false)); regionInfos.Where(x => !ServerManager.AvailableRegions.Contains(x)).Do(ServerManager.AddOrUpdateRegion); - ServerManager.CurrentRegion = ServerManager.AvailableRegions.FirstOrDefault(x => x.Name.Contains("Europe", StringComparison.OrdinalIgnoreCase)) ?? ServerManager.AvailableRegions.FirstOrDefault() ?? regionInfos.First(); + ServerManager.AvailableRegions = ServerManager.AvailableRegions.OrderByDescending(ServerManager.DefaultRegions.Contains).ToArray(); } private static IRegionInfo CreateHttp(string ip, string name, ushort port, bool ishttps) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 4b8b37f64..55b343c64 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -653,6 +653,7 @@ public static class Utils case CustomRoles.Eclipse: case CustomRoles.Pyromaniac: case CustomRoles.NSerialKiller: + case CustomRoles.Beehive: case CustomRoles.RouleteGrandeur: case CustomRoles.Nonplus: case CustomRoles.Tremor: @@ -1578,7 +1579,7 @@ public static class Utils { if (!AmongUsClient.Instance.AmHost) return; if (title == "") title = "" + GetString("DefaultSystemMessageTitle") + ""; - if (title.Contains('\u2605')) + if (title.Count(x => x == '\u2605') == 2 && !title.Contains('\n')) { if (title.Contains('<') && title.Contains('>') && title.Contains('#')) title = $"{title[..(title.IndexOf('>') + 1)]}\u27a1{title.Replace("\u2605", "")[..(title.LastIndexOf('<') - 2)]}\u2b05"; @@ -2023,6 +2024,7 @@ public static class Utils TargetMark.Append(ColorString(GetRoleColor(CustomRoles.BallLightning), "■")); TargetMark.Append(Snitch.GetWarningMark(seer, target)); + TargetMark.Append(Marshall.GetWarningMark(seer, target)); if ((seer.Data.IsDead || Main.LoversPlayers.Any(x => x.PlayerId == seer.PlayerId)) && Main.LoversPlayers.Any(x => x.PlayerId == target.PlayerId)) { @@ -2270,7 +2272,58 @@ public static class Utils Main.Instance.StartCoroutine(GameOptionsSender.SendAllGameOptionsAsync()); } - public static string GetGameStateData() + public static void RpcChangeSkin(PlayerControl pc, NetworkedPlayerInfo.PlayerOutfit newOutfit) + { + var sender = CustomRpcSender.Create(name: $"Utils.RpcChangeSkin({pc.Data.PlayerName})"); + + pc.SetName(newOutfit.PlayerName); + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetName) + .Write(pc.Data.NetId) + .Write(newOutfit.PlayerName) + .EndRpc(); + + Main.AllPlayerNames[pc.PlayerId] = newOutfit.PlayerName; + + pc.SetColor(newOutfit.ColorId); + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetColor) + .Write(pc.Data.NetId) + .Write((byte)newOutfit.ColorId) + .EndRpc(); + + pc.SetHat(newOutfit.HatId, newOutfit.ColorId); + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetHatStr) + .Write(newOutfit.HatId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetHatStr)) + .EndRpc(); + + pc.SetSkin(newOutfit.SkinId, newOutfit.ColorId); + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetSkinStr) + .Write(newOutfit.SkinId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) + .EndRpc(); + + pc.SetVisor(newOutfit.VisorId, newOutfit.ColorId); + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetVisorStr) + .Write(newOutfit.VisorId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) + .EndRpc(); + + pc.SetPet(newOutfit.PetId); + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetPetStr) + .Write(newOutfit.PetId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetPetStr)) + .EndRpc(); + + pc.SetNamePlate(newOutfit.NamePlateId); + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetNamePlateStr) + .Write(newOutfit.NamePlateId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) + .EndRpc(); + + sender.SendMessage(); + } + + public static string GetGameStateData(bool clairvoyant = false) { var nums = Enum.GetValues().ToDictionary(x => x, _ => 0); @@ -2300,8 +2353,9 @@ public static class Utils // 24: Romantic exists, has picked a partner who is alive, and Romantic is alive var sb = new StringBuilder(); + var checkDict = clairvoyant ? Clairvoyant.Settings : Options.GameStateSettings; var states = nums.ToDictionary(x => x.Key, x => x.Key == Options.GameStateInfo.RomanticState ? GetString($"GSRomanticState.{x.Value}") : (object)x.Value); - states.DoIf(x => Options.GameStateSettings[x.Key].GetBool(), x => sb.AppendLine(string.Format(GetString($"GSInfo.{x.Key}"), x.Value))); + states.DoIf(x => checkDict[x.Key].GetBool(), x => sb.AppendLine(string.Format(GetString($"GSInfo.{x.Key}"), x.Value))); return sb.ToString().TrimEnd(); } @@ -2325,6 +2379,7 @@ public static class Utils CustomRoles.SecurityGuard => Options.SecurityGuardSkillCooldown.GetInt() + (includeDuration ? Options.SecurityGuardSkillDuration.GetInt() : 0), CustomRoles.TimeMaster => Options.TimeMasterSkillCooldown.GetInt() + (includeDuration ? Options.TimeMasterSkillDuration.GetInt() : 0), CustomRoles.Veteran => Options.VeteranSkillCooldown.GetInt() + (includeDuration ? Options.VeteranSkillDuration.GetInt() : 0), + CustomRoles.Rhapsode => Rhapsode.AbilityCooldown.GetInt() + (includeDuration ? Rhapsode.AbilityDuration.GetInt() : 0), CustomRoles.Perceiver => Perceiver.CD.GetInt(), CustomRoles.Convener => Convener.CD.GetInt(), CustomRoles.DovesOfNeace => Options.DovesOfNeaceCooldown.GetInt(), @@ -2376,7 +2431,7 @@ public static class Utils if (loversChat) GameEndChecker.Prefix(); Lovers.IsChatActivated = false; - Main.ProcessShapeshifts = true; + if (!Options.UseUnshiftTrigger.GetBool()) Main.ProcessShapeshifts = true; AFKDetector.NumAFK = 0; AFKDetector.PlayerData.Clear(); @@ -2400,6 +2455,14 @@ public static class Utils if (Options.UsePets.GetBool()) pc.AddAbilityCD(includeDuration: false); + if (pc.GetCustomRole().SimpleAbilityTrigger() && Options.UseUnshiftTrigger.GetBool() && (!pc.IsNeutralKiller() || Options.UseUnshiftTriggerForNKs.GetBool())) + { + var target = Main.AllAlivePlayerControls.Without(pc).RandomElement(); + var outfit = pc.Data.DefaultOutfit; + pc.RpcShapeshift(target, false); + RpcChangeSkin(pc, outfit); + } + AFKDetector.RecordPosition(pc); Main.PlayerStates[pc.PlayerId].Role.AfterMeetingTasks(); @@ -2418,6 +2481,8 @@ public static class Utils Main.CheckShapeshift[pc.PlayerId] = false; } + LateTask.New(() => Main.ProcessShapeshifts = true, 1f, log: false); + CopyCat.ResetRoles(); if (Options.DiseasedCDReset.GetBool()) @@ -2554,6 +2619,7 @@ public static class Utils Scout.OnPlayerDeath(target); Adventurer.OnAnyoneDead(target); Soothsayer.OnAnyoneDeath(target.GetRealKiller(), target); + Amnesiac.OnAnyoneDeath(target); EHR.Impostor.Sentry.OnAnyoneMurder(target); if (QuizMaster.On) QuizMaster.Data.NumPlayersDeadThisRound++; diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index cacd9ad61..7e41a9575 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -5,6 +5,7 @@ using System.Linq; using AmongUs.GameOptions; using BepInEx.Unity.IL2CPP.Utils.Collections; using EHR.AddOns.GhostRoles; +using EHR.Crewmate; using EHR.Impostor; using EHR.Modules; using EHR.Neutral; @@ -389,8 +390,10 @@ class GameEndChecker return true; } + var sheriffCount = AlivePlayersCount(CountTypes.Sheriff); + int Imp = AlivePlayersCount(CountTypes.Impostor); - int Crew = AlivePlayersCount(CountTypes.Crew); + int Crew = AlivePlayersCount(CountTypes.Crew) + sheriffCount; Dictionary<(CustomRoles? ROLE, CustomWinner WINNER), int> roleCounts = []; @@ -464,6 +467,9 @@ class GameEndChecker // There are multiple types of NKs alive, game must continue case > 1: return false; + // If the Sheriff keeps the game going, the game must continue + case 1 when Sheriff.KeepsGameGoing.GetBool() && sheriffCount > 0: + return false; // There is only one type of NK alive, they've won case 1: { @@ -788,4 +794,4 @@ class CheckGameEndPatch __result = GameEndChecker.Predicate?.CheckGameEndByTask(out _) ?? false; return false; } -} +} \ No newline at end of file diff --git a/Patches/CredentialsPatch.cs b/Patches/CredentialsPatch.cs index c7dddefbe..0843c8b2e 100644 --- a/Patches/CredentialsPatch.cs +++ b/Patches/CredentialsPatch.cs @@ -46,6 +46,8 @@ internal class PingTrackerUpdatePatch }; Sb.Append(GameStates.InGame ? " - " : "\r\n"); Sb.Append($"{GetString("PingText")}: {ping} ms"); + Sb.Append(GameStates.InGame ? " - " : "\r\n"); + Sb.Append(string.Format(GetString("Server"), Utils.GetRegionName())); if (GameStates.InGame) Sb.Append("\r\n."); // if (Options.NoGameEnd.GetBool()) Sb.Append("\r\n").Append(Utils.ColorString(Color.red, GetString("NoGameEnd"))).Append(""); diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 312dced01..68cb83292 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -795,7 +795,7 @@ public class GameSettingMenuPatch { settingsTab.gameObject.SetActive(true); __instance.MenuDescriptionText.DestroyTranslator(); - __instance.MenuDescriptionText.text = Translator.GetString($"TabInfo.{tabGroup}"); + __instance.MenuDescriptionText.text = Translator.GetString("TabInfoTip"); } } diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index da5ba8e6f..9baafbc51 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -741,6 +741,24 @@ class IntroCutsceneDestroyPatch LateTask.New(() => Main.ProcessShapeshifts = true, 1f, "Enable SS Processing"); } + if (Options.UseUnshiftTrigger.GetBool()) + { + LateTask.New(() => + { + foreach (var pc in Main.AllAlivePlayerControls) + { + if (pc.GetCustomRole().SimpleAbilityTrigger() && (!pc.IsNeutralKiller() || Options.UseUnshiftTriggerForNKs.GetBool())) + { + var target = Main.AllAlivePlayerControls.Without(pc).RandomElement(); + var outfit = pc.Data.DefaultOutfit; + pc.RpcShapeshift(target, false); + Utils.RpcChangeSkin(pc, outfit); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc, NoCache: true); + } + } + }, 2f, "UnshiftTrigger SS"); + } + if (PlayerControl.LocalPlayer.Is(CustomRoles.GM)) { PlayerControl.LocalPlayer.RpcExile(); diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs index c10d73523..213e71922 100644 --- a/Patches/OnGameStartedPatch.cs +++ b/Patches/OnGameStartedPatch.cs @@ -423,15 +423,17 @@ internal class SelectRolesPatch HashSet bloodlustList = RoleResult.Where(x => x.Value.IsCrewmate() && !x.Value.IsTaskBasedCrewmate()).Select(x => x.Key.PlayerId).ToHashSet(); if (bloodlustList.Count == 0) bloodlustSpawn = false; - if (Main.AlwaysSpawnTogetherCombos[OptionItem.CurrentPreset].Values.Any(l => l.Contains(CustomRoles.Bloodlust))) + if (Main.AlwaysSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out var combos) && combos.Values.Any(l => l.Contains(CustomRoles.Bloodlust))) { - var roles = Main.AlwaysSpawnTogetherCombos[OptionItem.CurrentPreset].Where(x => x.Value.Contains(CustomRoles.Bloodlust)).Select(x => x.Key).ToHashSet(); + var roles = combos.Where(x => x.Value.Contains(CustomRoles.Bloodlust)).Select(x => x.Key).ToHashSet(); var players = RoleResult.Where(x => roles.Contains(x.Value) && x.Value.IsCrewmate() && !x.Value.IsTaskBasedCrewmate()).Select(x => x.Key.PlayerId).ToHashSet(); if (players.Count > 0) { bloodlustList = players; bloodlustSpawn = true; } + + combos.Do(x => x.Value.Remove(CustomRoles.Bloodlust)); } if (Main.SetAddOns.Values.Any(x => x.Contains(CustomRoles.Bloodlust))) @@ -522,9 +524,9 @@ internal class SelectRolesPatch (CustomRoles addon, (bool SpawnFlag, HashSet RoleList) value) = roleSpawnMapping.ElementAt(i); if (value.RoleList.Count == 0) value.SpawnFlag = false; - if (Main.AlwaysSpawnTogetherCombos[OptionItem.CurrentPreset].Values.Any(l => l.Contains(addon))) + if (Main.AlwaysSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out var combos) && combos.Values.Any(l => l.Contains(addon))) { - var roles = Main.AlwaysSpawnTogetherCombos[OptionItem.CurrentPreset].Where(x => x.Value.Contains(addon)).Select(x => x.Key).ToHashSet(); + var roles = combos.Where(x => x.Value.Contains(addon)).Select(x => x.Key).ToHashSet(); var players = RoleResult.Where(x => roles.Contains(x.Value) && x.Value.IsCrewmate() && (addon == CustomRoles.Nimble || x.Value.GetRoleTypes() == RoleTypes.Crewmate) && !IsBasisChangingPlayer(x.Key.PlayerId, CustomRoles.Bloodlust)).Select(x => x.Key.PlayerId).ToHashSet(); if (players.Count > 0) { @@ -532,6 +534,8 @@ internal class SelectRolesPatch value.SpawnFlag = true; roleSpawnMapping[addon] = value; } + + combos.Do(x => x.Value.Remove(addon)); } if (Main.SetAddOns.Values.Any(x => x.Contains(addon))) @@ -725,7 +729,6 @@ internal class SelectRolesPatch if (Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out var neverList) && neverList.TryGetValue(state.MainRole, out var bannedAddonList)) { bannedAddonList.ForEach(x => state.RemoveSubRole(x)); - continue; } if (Main.AlwaysSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out var alwaysList) && alwaysList.TryGetValue(state.MainRole, out var addonList)) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 8add34760..05b7f76d9 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -735,6 +735,8 @@ class ShapeshiftPatch return true; } + if (AmongUsClient.Instance.AmHost && shapeshifting && !Rhapsode.CheckAbilityUse(shapeshifter)) return false; + Main.CheckShapeshift[shapeshifter.PlayerId] = shapeshifting; Main.ShapeshiftTarget[shapeshifter.PlayerId] = target.PlayerId; @@ -756,6 +758,8 @@ class ShapeshiftPatch var role = shapeshifter.GetCustomRole(); bool forceCancel = role.ForceCancelShapeshift(); + bool unshiftTrigger = role.SimpleAbilityTrigger() && Options.UseUnshiftTrigger.GetBool() && (!role.IsNeutral() || Options.UseUnshiftTriggerForNKs.GetBool()); + forceCancel |= unshiftTrigger; if (shapeshifter.Is(CustomRoles.Camouflager) && !shapeshifting) Camouflager.Reset(); if (Changeling.ChangedRole.TryGetValue(shapeshifter.PlayerId, out var changed) && changed && shapeshifter.GetRoleTypes() != RoleTypes.Shapeshifter) @@ -797,7 +801,16 @@ class ShapeshiftPatch return false; } - return isSSneeded || (!shouldCancel && !forceCancel) || (!shapeshifting && !shouldAlwaysCancel); + if (unshiftTrigger) + { + var rndTarget = Main.AllAlivePlayerControls.Without(shapeshifter).RandomElement(); + var outfit = shapeshifter.Data.DefaultOutfit; + shapeshifter.RpcShapeshift(rndTarget, false); + RpcChangeSkin(shapeshifter, outfit); + NotifyRoles(SpecifySeer: shapeshifter, SpecifyTarget: shapeshifter, NoCache: true); + } + + return isSSneeded || (!shouldCancel && !forceCancel) || (!shapeshifting && !shouldAlwaysCancel && !unshiftTrigger); } // Tasks that should run when someone performs a shapeshift (with the egg animation) should be here. @@ -1083,7 +1096,12 @@ class ReportDeadBodyPatch LateTask.New(SyncAllSettings, 3f, "SyncAllSettings on meeting start"); - LateTask.New(() => Main.ProcessShapeshifts = false, 5f, log: false); + Main.ProcessShapeshifts = false; + foreach (var pc in Main.AllAlivePlayerControls) + { + if (pc.GetCustomRole().SimpleAbilityTrigger() && Options.UseUnshiftTrigger.GetBool() && (!pc.IsNeutralKiller() || Options.UseUnshiftTriggerForNKs.GetBool())) + pc.RpcShapeshift(pc, false); + } } } @@ -1849,13 +1867,13 @@ class CoEnterVentPatch LateTask.New(() => { __instance.myPlayer?.Notify(GetString("FFA-NoVentingBecauseKCDIsUP"), 7f); - __instance.myPlayer?.MyPhysics?.RpcBootFromVent(id); + __instance.RpcBootFromVent(id); }, 0.5f, "FFA-NoVentingWhenKCDIsUP"); return true; case CustomGameMode.MoveAndStop: case CustomGameMode.HotPotato: case CustomGameMode.Speedrun: - LateTask.New(() => { __instance.myPlayer?.MyPhysics?.RpcBootFromVent(id); }, 0.5f, log: false); + LateTask.New(() => __instance.RpcBootFromVent(id), 0.5f, log: false); return true; case CustomGameMode.HideAndSeek: HnSManager.OnCoEnterVent(__instance, id); @@ -1867,14 +1885,20 @@ class CoEnterVentPatch LateTask.New(() => { __instance.myPlayer?.Notify(BlockedAction.Vent.GetBlockNotify()); - __instance.myPlayer?.MyPhysics?.RpcBootFromVent(id); + __instance.RpcBootFromVent(id); }, 0.5f, "RoleBlockedBootFromVent"); return true; } + if (!Rhapsode.CheckAbilityUse(__instance.myPlayer)) + { + LateTask.New(() => __instance.RpcBootFromVent(id), 0.5f, log: false); + return true; + } + if (Penguin.IsVictim(__instance.myPlayer)) { - LateTask.New(() => { __instance.myPlayer?.MyPhysics?.RpcBootFromVent(id); }, 0.5f, "PenguinVictimBootFromVent"); + LateTask.New(() => __instance.RpcBootFromVent(id), 0.5f, "PenguinVictimBootFromVent"); return true; } @@ -1883,7 +1907,7 @@ class CoEnterVentPatch LateTask.New(() => { __instance.myPlayer?.Notify(GetString("SoulHunterTargetNotifyNoVent")); - __instance.myPlayer?.MyPhysics?.RpcBootFromVent(id); + __instance.RpcBootFromVent(id); LateTask.New(() => { if (SoulHunter.IsSoulHunterTarget(__instance.myPlayer.PlayerId)) __instance.myPlayer.Notify(string.Format(GetString("SoulHunterTargetNotify"), SoulHunter.GetSoulHunter(__instance.myPlayer.PlayerId).SoulHunter_.GetRealName()), 300f); @@ -1900,7 +1924,7 @@ class CoEnterVentPatch LateTask.New(() => { pc?.Notify(GetString("EnteredBlockedVent")); - pc?.MyPhysics?.RpcBootFromVent(id); + __instance.RpcBootFromVent(id); }, 0.5f, "VentguardBlockedVentBootFromVent"); if (Ventguard.VentguardNotifyOnBlockedVentUse.GetBool()) @@ -1922,7 +1946,7 @@ class CoEnterVentPatch if (__instance.myPlayer.GetCustomRole().GetDYRole() == RoleTypes.Impostor && !Main.PlayerStates[__instance.myPlayer.PlayerId].Role.CanUseImpostorVentButton(__instance.myPlayer) && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.HideAndSeek && !__instance.myPlayer.Is(CustomRoles.Nimble) && !__instance.myPlayer.Is(CustomRoles.Bloodlust)) { - LateTask.New(() => { __instance.RpcBootFromVent(id); }, 0.5f, "CannotUseVentBootFromVent"); + LateTask.New(() => __instance.RpcBootFromVent(id), 0.5f, "CannotUseVentBootFromVent"); } if (((__instance.myPlayer.Data.Role.Role != RoleTypes.Engineer && !__instance.myPlayer.CanUseImpostorVentButton()) || @@ -1932,7 +1956,7 @@ class CoEnterVentPatch { try { - LateTask.New(() => { __instance.RpcBootFromVent(id); }, 0.5f, "CannotUseVentBootFromVent2"); + LateTask.New(() => __instance.RpcBootFromVent(id), 0.5f, "CannotUseVentBootFromVent2"); return true; } catch @@ -2192,8 +2216,8 @@ class CheckVanishPatch { Logger.Info($" {__instance.GetNameWithRole()}", "CheckVanish"); bool allow = Main.PlayerStates[__instance.PlayerId].Role.OnVanish(__instance); - if (!allow) __instance.RpcResetAbilityCooldown(); - return allow; + if (!allow) LateTask.New(__instance.RpcResetAbilityCooldown, 0.2f, log: false); + return true; } } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 9cf6d54cd..48e715a86 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -6,6 +6,7 @@ "HostColor": "#4bf4ff", "Icon": "♥", "NameColor": "#00ffa5", + "Server": "<#00ffa5>Server: {0}", "SubText.Crewmate": "Find and exile the Impostors", "SubText.Impostor": "Sabotage and kill everyone", "SubText.Neutral": "A team on their own", @@ -31,23 +32,6 @@ "MadmateSub": "Help the Impostors", "KB_Normal": "Challenger", "KB_NormalInfo": "Kill all of them!", - "Role.Crewmate": "Crewmate", - "Role.Jester": "Jester", - "Role.Opportunist": "Opportunist", - "Role.Convict": "Convict", - "Role.Celebrity": "Celebrity", - "Role.Bodyguard": "Bodyguard", - "Role.Dictator": "Dictator", - "Role.Mayor": "Mayor", - "Role.Doctor": "Doctor", - "Role.Maverick": "Maverick", - "Role.Witch": "Witch", - "Role.Pursuer": "Pursuer", - "Role.Follower": "Follower", - "Role.Amnesiac": "Amnesiac", - "Role.Sheriff": "Sheriff", - "Role.Knight": "Vigilante", - "Role.Deputy": "Deputy", "UnsupportedMap": "This map is not supported by EHR\\nPlease use a supported map or you'll experience issues", "GuesserMode": "Guesser Mode", "CrewmatesCanGuess": "Crewmates can guess", @@ -1036,6 +1020,7 @@ "IncreasedVision": "Increased Vision", "IncreasedVisionDuration": "Increased Vision Duration", "ShieldDuration": "Shield Duration", + "DecreasedSpeed": "Decreased Speed", "ChangeDecontaminationTime": "Change Decontamination Time (MIRA HQ/Polus)", "DecontaminationTimeOnMiraHQ": "Decontamination Time On MIRA HQ", "DecontaminationTimeOnPolus": "Decontamination Time On Polus", @@ -1322,6 +1307,7 @@ "SheriffCanKillLovers": "Can Kill Lovers", "SheriffCanKillMadmate": "Can Kill Madmates", "SheriffCanKillContagious": "Can Kill Contagious players", + "SheriffKeepsGameGoing": "Keeps the game going", "SheriffSetMadCanKill": "Non-Crew Sheriff Configuration", "SheriffMadCanKillImp": "Can kill Impostors", "SheriffMadCanKillNeutral": "Can kill Neutrals", @@ -2033,6 +2019,7 @@ "TrapConsecutiveBodies": "Trap Consecutive Bodies", "HauntedByEvilSpirit": "Haunted by an Evil Spirit", "AbilityCooldown": "Ability Cooldown", + "AbilityDuration": "Ability Duration", "MonarchKnightCooldown": "Knight Cooldown", "MonarchKnightMax": "Maximum Knights", "MonarchKnightedPlayer": "You successfully knighted a player!", @@ -2361,6 +2348,7 @@ "TabInfo.Addons": "Settings for Add-ons", "TabInfo.OtherRoles": "Settings for Ghost roles", "TabInfo.TaskSettings": "Modded, specific settings that have a major impact on the game", + "TabInfoTip": "Tip: Hold SHIFT to adjust number values by 5 or CTRL to adjust by 10!", "OtherRoles.CrewmateRoles": "★ Crewmate Roles", "OtherRoles.NeutralRoles": "★ Neutral Roles", "OtherRoles.ImpostorRoles": "★ Impostor Roles", @@ -2568,6 +2556,8 @@ "CanGuessTarget": "Can Guess their Target", "UsePhantomBasis": "Use Phantom Basis for some roles", "UsePhantomBasisForNKs": "Even for <#ffab1b>Neutral <#ff1919>Killing (Desynced) roles <#ffff00>(EXPERIMENTAL)", + "UseUnshiftTrigger": "Use Unshift Button as Ability Trigger", + "UseUnshiftTriggerForNKs": "Even for <#ffab1b>Neutral <#ff1919>Killing (Desynced) roles <#ffff00>(EXPERIMENTAL)", "Mad-": "Mad ", "Charmed-": "Charmed ", "Infected-": "Infected ", @@ -3128,6 +3118,16 @@ "DonutGot-4": "\\nDing-dong! A donut delivery is at your door.\\nCourtesy of the Donut Delivery crewmate.\\nSavor the sweetness!", "DonutGot-5": "\\nSpecial delivery alert!\\nYou've just received a 'killer' donut from the Donut Delivery crewmate.\\nEnjoy the crime scene... I mean snack!", "DonutDeliverCD": "Deliver Cooldown", + "DonutDeliverSpeedEffect": "Speed Boost For Players Who Get Donuts", + "DonutDeliverSEDelay": "Speed Boost Delay", + "DonutDeliverSEAmount": "Increase Speed by", + "DonutDeliverSEDuration": "Speed Boost Duration", + "DonutDeliverSEEvilsGetDecreased": "<#ff1919>Evil Players Get Decreased Speed", + "DonutDeliverSEEvilsDecreaseAmount": "Decrease Speed by", + "Rhapsode": "Rhapsode", + "RhapsodeInfo": "Sing to Distract the Killers", + "RhapsodeInfoLong": "(Crewmates):\\nAs the Rhapsode, you can vent/pet your pet to start singing. During this time, no one can vent or shapeshift since they're distracted by your song. (Settings may exclude Crewmates.)", + "Rhapsode.ExcludeCrewmates": "Exclude <#8cffff>Crewmates", "Chronomancer": "Chronomancer", "ChronomancerInfo": "Charge up your kill button and go ham!", "ChronomancerInfoLong": "(Impostors):\\nAs the Chronomancer, when your kill cooldown is up, your kill button starts charging (you'll see the percentage of the charge under your name). When you make a kill, you use the charge. Depending on how much you've charged your kill button up, you'll have a 0 second kill cooldown until the charge runs out. When the charge runs out, your kill cooldown is reset.", @@ -3625,7 +3625,7 @@ "AdventurerGun.RNG": "RNG", "Chef": "Chef", "ChefInfo": "Cook Delicious Meals for Everyone", - "ChefInfoLong": "(Crewmates):\\nAs the Chef, you can cook meals for everyone. If a crewmate gets a meal, they'll get a random boost (speed, vision, shield). If an impostor or a neutral killer gets a meal, it will be rotten and they have to spit it out by venting or killing in a specified amount of time or they'll get blinded and frozen for some time.", + "ChefInfoLong": "(Crewmates):\\nAs the Chef, you can cook meals for everyone by completing tasks. If a crewmate gets a meal, they'll get a random boost (speed, vision, shield). If an impostor or a neutral killer gets a meal, it will be rotten and they have to spit it out by venting or killing in a specified amount of time or they'll get blinded and frozen for some time.", "Chef.EventTarget": "Effect Applies to", "Chef.ET.RandomPlayer": "Random Player", "Chef.ET.NearestPlayer": "Nearest Player", @@ -4036,6 +4036,22 @@ "AADelay": "Time to Leave Device", "AAWarning": "<#ffff00>You must move away from this device or you die!", "AADone": "The process has finished", + "Oxyman": "Oxyman", + "OxymanInfo": "Your Oxygen Level Affects Everything", + "OxymanInfoLong": "(Crewmates):\\nAs the Oxyman, you have an Oxygen level below your name. The Oxygen level decreases overtime. You have to vent to increase your Oxygen. If the Oxygen level reaches 0%, you die. If it's very low, you're blind. If it's low, you're slower. If it's high, you're faster. If it's very high, you're invulnerable.", + "Oxyman.Slow.BelowPercentage": "Slow Below", + "Oxyman.Blind.BelowPercentage": "Blind Below", + "Oxyman.Fast.AbovePercentage": "Fast Above", + "Oxyman.Invulnerable.AbovePercentage": "Invulnerable Above", + "Oxyman.IncrementByVenting": "On Venting, Oxygen Level Increases by", + "Oxyman.DecreasementEachSecond": "Every Second, Oxygen Level Decreases by", + "Beehive": "Beehive", + "BeehiveInfo": "Sting Players", + "BeehiveInfoLong": "(Neutrals):\\nAs the Beehive, you can sting players by single clicking your kill button on them. Stung players will have a certain time to walk a certain distance (get away from your bees) (distance set by the host), or they die.\\nDouble click on someone with your kill button to kill them normally.\\nKill everyone to win.", + "Beehive.Distance": "Distance to Walk", + "Beehive.Time": "Time to Walk the Distance", + "Beehive.StingCooldown": "Sting Cooldown", + "Beehive.Notify": "<#ffff00>You were stung by the Beehive!\\nWalk {0} units away from the Beehive in {1}s or you die!", "Spurt": "Spurt", "Prefix.Spurt": "Spurt", "SpurtInfo": "Boosts Movement", diff --git a/Roles/Crewmate/Chef.cs b/Roles/Crewmate/Chef.cs index 251b20255..bb1fbdd7d 100644 --- a/Roles/Crewmate/Chef.cs +++ b/Roles/Crewmate/Chef.cs @@ -155,7 +155,7 @@ namespace EHR.Crewmate public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) { - if (lowLoad) return; + if (lowLoad || !pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; if (ActiveEvents.TryGetValue(pc.PlayerId, out var events)) events.ToArray().Do(x => x.Update()); @@ -186,6 +186,13 @@ namespace EHR.Crewmate } } + public override void OnReportDeadBody() + { + ActiveEvents.Clear(); + RottenFood.Keys.Do(x => Utils.GetPlayerById(x).Suicide()); + RottenFood.Clear(); + } + public static void SpitOutFood(PlayerControl pc) { Instances.Do(x => x.RottenFood.Remove(pc.PlayerId)); diff --git a/Roles/Crewmate/Clairvoyant.cs b/Roles/Crewmate/Clairvoyant.cs index e47ebfbb6..bafd7ebe0 100644 --- a/Roles/Crewmate/Clairvoyant.cs +++ b/Roles/Crewmate/Clairvoyant.cs @@ -1,13 +1,28 @@ -using AmongUs.GameOptions; +using System; +using System.Collections.Generic; +using AmongUs.GameOptions; namespace EHR.Crewmate { public class Clairvoyant : RoleBase { public static bool On; + + public static Dictionary Settings = []; public override bool IsEnable => On; - public static void SetupCustomOption() => Options.SetupRoleOptions(644970, TabGroup.CrewmateRoles, CustomRoles.Clairvoyant); + public static void SetupCustomOption() + { + Options.SetupRoleOptions(644970, TabGroup.CrewmateRoles, CustomRoles.Clairvoyant); + + int i = 2; + foreach (var s in Enum.GetValues()) + { + Settings[s] = new BooleanOptionItem(644970 + i, $"GameStateCommand.Show{s}", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Clairvoyant]); + i++; + } + } public override void Init() { @@ -26,7 +41,7 @@ namespace EHR.Crewmate AURoleOptions.EngineerInVentMaxTime = 1f; } - static void UseAbility(PlayerControl pc) => pc.Notify(Utils.GetRemainingKillers(notify: true)); + static void UseAbility(PlayerControl pc) => pc.Notify(Utils.GetGameStateData(clairvoyant: true)); public override void OnPet(PlayerControl pc) => UseAbility(pc); public override void OnEnterVent(PlayerControl pc, Vent vent) => UseAbility(pc); } diff --git a/Roles/Crewmate/DonutDelivery.cs b/Roles/Crewmate/DonutDelivery.cs index de96fcde9..ec029b1e1 100644 --- a/Roles/Crewmate/DonutDelivery.cs +++ b/Roles/Crewmate/DonutDelivery.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using AmongUs.GameOptions; using static EHR.Options; using static EHR.Translator; @@ -8,13 +9,22 @@ namespace EHR.Crewmate public class DonutDelivery : RoleBase { private const int Id = 642700; - private static List playerIdList = []; + private static List Instances = []; + private static Dictionary StartingSpeed = []; private static OptionItem CD; private static OptionItem UseLimit; + private static OptionItem SpeedEffect; + private static OptionItem SEDelay; + private static OptionItem SEAmount; + private static OptionItem SEDuration; + private static OptionItem SEEvilsGetDecreased; + private static OptionItem SEEvilsDecreaseAmount; public static OptionItem UsePet; + private byte DonutDeliveryId; + private HashSet Players = []; - public override bool IsEnable => playerIdList.Count > 0; + public override bool IsEnable => Instances.Count > 0; public static void SetupCustomOption() { @@ -25,17 +35,34 @@ namespace EHR.Crewmate UseLimit = new IntegerOptionItem(Id + 12, "AbilityUseLimit", new(1, 20, 1), 5, TabGroup.CrewmateRoles) .SetParent(CustomRoleSpawnChances[CustomRoles.DonutDelivery]) .SetValueFormat(OptionFormat.Times); + SpeedEffect = new BooleanOptionItem(Id + 14, "DonutDeliverSpeedEffect", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.DonutDelivery]); + SEDelay = new FloatOptionItem(Id + 15, "DonutDeliverSEDelay", new(0f, 30f, 0.5f), 3f, TabGroup.CrewmateRoles) + .SetParent(SpeedEffect) + .SetValueFormat(OptionFormat.Seconds); + SEAmount = new FloatOptionItem(Id + 16, "DonutDeliverSEAmount", new(0.05f, 3f, 0.05f), 0.5f, TabGroup.CrewmateRoles) + .SetParent(SpeedEffect); + SEDuration = new FloatOptionItem(Id + 17, "DonutDeliverSEDuration", new(0.5f, 60f, 0.5f), 5f, TabGroup.CrewmateRoles) + .SetParent(SpeedEffect) + .SetValueFormat(OptionFormat.Seconds); + SEEvilsGetDecreased = new BooleanOptionItem(Id + 18, "DonutDeliverSEEvilsGetDecreased", true, TabGroup.CrewmateRoles) + .SetParent(SpeedEffect); + SEEvilsDecreaseAmount = new FloatOptionItem(Id + 19, "DonutDeliverSEEvilsDecreaseAmount", new(0.1f, 5f, 0.1f), 1f, TabGroup.CrewmateRoles) + .SetParent(SEEvilsGetDecreased); UsePet = CreatePetUseSetting(Id + 13, CustomRoles.DonutDelivery); } public override void Init() { - playerIdList = []; + Instances = []; + StartingSpeed = Main.AllPlayerSpeed.ToDictionary(x => x.Key, x => x.Value); } public override void Add(byte playerId) { - playerIdList.Add(playerId); + Instances.Add(this); + Players = []; + DonutDeliveryId = playerId; playerId.SetAbilityUseLimit(UseLimit.GetInt()); } @@ -56,9 +83,26 @@ namespace EHR.Crewmate var num1 = IRandom.Instance.Next(0, 19); killer.Notify(GetString($"DonutDelivered-{num1}")); RandomNotifyTarget(target); + Players.Add(target.PlayerId); killer.SetKillCooldown(); + if (SpeedEffect.GetBool()) + { + LateTask.New(() => + { + if (target.IsCrewmate() || target.GetCustomRole().IsNonNK() || !SEEvilsGetDecreased.GetBool()) Main.AllPlayerSpeed[target.PlayerId] += SEAmount.GetFloat(); + else Main.AllPlayerSpeed[target.PlayerId] -= SEEvilsDecreaseAmount.GetFloat(); + target.MarkDirtySettings(); + + LateTask.New(() => + { + Main.AllPlayerSpeed[target.PlayerId] = StartingSpeed[target.PlayerId]; + target.MarkDirtySettings(); + }, SEDuration.GetFloat(), log: false); + }, SEDelay.GetFloat(), log: false); + } + return false; } @@ -67,5 +111,16 @@ namespace EHR.Crewmate var num2 = IRandom.Instance.Next(0, 6); target.Notify(GetString($"DonutGot-{num2}")); } + + public static bool IsUnguessable(PlayerControl guesser, PlayerControl target) + { + foreach (DonutDelivery instance in Instances) + { + if (instance.DonutDeliveryId == target.PlayerId && instance.Players.Contains(guesser.PlayerId)) + return true; + } + + return false; + } } } \ No newline at end of file diff --git a/Roles/Crewmate/Marshall.cs b/Roles/Crewmate/Marshall.cs index ad06db615..d5ef718b9 100644 --- a/Roles/Crewmate/Marshall.cs +++ b/Roles/Crewmate/Marshall.cs @@ -42,7 +42,7 @@ public class Marshall : RoleBase public static bool CanSeeMarshall(PlayerControl seer) { var team = seer.GetTeam(); - var madmate = seer.GetCustomRole().IsMadmate() || seer.Is(CustomRoles.Madmate); + var madmate = seer.IsMadmate(); return team switch { Team.Crewmate when !madmate => true, diff --git a/Roles/Crewmate/Mediumshiper.cs b/Roles/Crewmate/Mediumshiper.cs index cb8b105ee..5c9ade1f0 100644 --- a/Roles/Crewmate/Mediumshiper.cs +++ b/Roles/Crewmate/Mediumshiper.cs @@ -65,7 +65,7 @@ public class Mediumshiper : RoleBase if (OnlyReceiveMsgFromCrew.GetBool() && !pc.IsCrewmate()) return false; if (pc.IsAlive()) return false; msg = msg.ToLower().Trim(); - if (!CheckCommond(ref msg, "通灵|ms|mediumship|medium", false)) return false; + if (!CheckCommand(ref msg, "通灵|ms|mediumship|medium", false)) return false; bool ans; if (msg.Contains('n') || msg.Contains(GetString("No")) || msg.Contains('错') || msg.Contains("不是")) ans = false; @@ -84,7 +84,7 @@ public class Mediumshiper : RoleBase return true; } - public static bool CheckCommond(ref string msg, string command, bool exact = true) + private static bool CheckCommand(ref string msg, string command, bool exact = true) { var comList = command.Split('|'); foreach (string str in comList) diff --git a/Roles/Crewmate/Oxyman.cs b/Roles/Crewmate/Oxyman.cs new file mode 100644 index 000000000..b4af35c33 --- /dev/null +++ b/Roles/Crewmate/Oxyman.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using AmongUs.GameOptions; +using EHR.Modules; +using UnityEngine; + +namespace EHR.Crewmate +{ + public class Oxyman : RoleBase + { + public static bool On; + + private static Dictionary LevelSettings = []; + private static OptionItem IncrementByVenting; + private static OptionItem DecreasementEachSecond; + private static OptionItem IncreasedSpeed; + private static OptionItem DecreasedSpeed; + private long LastUpdate; + private int OxygenLevel; + private byte OxymanId; + private float StartingSpeed; + + public override bool IsEnable => On; + + public static void SetupCustomOption() + { + int id = 647700; + Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Oxyman); + (List trueList, List falseList) = Enum.GetValues().Without(Level.None).Split(x => x <= Level.Slow); + trueList.ForEach(x => LevelSettings[x] = new IntegerOptionItem(++id, $"Oxyman.{x}.BelowPercentage", new(0, 100, 1), x == Level.Blind ? 10 : 30, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) + .SetValueFormat(OptionFormat.Percent)); + falseList.ForEach(x => LevelSettings[x] = new IntegerOptionItem(++id, $"Oxyman.{x}.AbovePercentage", new(0, 100, 1), x == Level.Fast ? 60 : 80, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) + .SetValueFormat(OptionFormat.Percent)); + IncrementByVenting = new IntegerOptionItem(++id, "Oxyman.IncrementByVenting", new(0, 100, 1), 7, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) + .SetValueFormat(OptionFormat.Percent); + DecreasementEachSecond = new IntegerOptionItem(++id, "Oxyman.DecreasementEachSecond", new(0, 10, 1), 1, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) + .SetValueFormat(OptionFormat.Percent); + IncreasedSpeed = new FloatOptionItem(++id, "IncreasedSpeed", new(0f, 3f, 0.05f), 1.75f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) + .SetValueFormat(OptionFormat.Multiplier); + DecreasedSpeed = new FloatOptionItem(++id, "DecreasedSpeed", new(0f, 3f, 0.05f), 1f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) + .SetValueFormat(OptionFormat.Multiplier); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + OxymanId = playerId; + OxygenLevel = 59; + LastUpdate = Utils.TimeStamp; + StartingSpeed = Main.AllPlayerSpeed[playerId]; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = 1f; + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + var pc = physics.myPlayer; + OxygenLevel += IncrementByVenting.GetValue(); + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, OxygenLevel); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; + + long now = Utils.TimeStamp; + if (now == LastUpdate) return; + LastUpdate = now; + + if (OxygenLevel <= 0) + { + pc.Suicide(); + return; + } + + var previousLevel = GetCurrentLevel(); + + OxygenLevel -= DecreasementEachSecond.GetInt(); + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, OxygenLevel); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + + var nowLevel = GetCurrentLevel(); + + if (nowLevel != previousLevel) + { + switch (nowLevel) + { + case Level.Blind: + Main.PlayerStates[pc.PlayerId].IsBlackOut = true; + Main.AllPlayerSpeed[pc.PlayerId] = DecreasedSpeed.GetFloat(); + pc.MarkDirtySettings(); + break; + case Level.Slow: + Main.PlayerStates[pc.PlayerId].IsBlackOut = false; + Main.AllPlayerSpeed[pc.PlayerId] = DecreasedSpeed.GetFloat(); + pc.MarkDirtySettings(); + break; + case Level.None: + Main.PlayerStates[pc.PlayerId].IsBlackOut = false; + Main.AllPlayerSpeed[pc.PlayerId] = StartingSpeed; + pc.MarkDirtySettings(); + break; + case Level.Invulnerable: + case Level.Fast: + Main.PlayerStates[pc.PlayerId].IsBlackOut = false; + Main.AllPlayerSpeed[pc.PlayerId] = IncreasedSpeed.GetFloat(); + pc.MarkDirtySettings(); + break; + } + } + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return GetCurrentLevel() != Level.Invulnerable; + } + + Level GetCurrentLevel() + { + // ReSharper disable ConvertIfStatementToReturnStatement + if (OxygenLevel <= LevelSettings[Level.Blind].GetInt()) return Level.Blind; + if (OxygenLevel <= LevelSettings[Level.Slow].GetInt()) return Level.Slow; + if (OxygenLevel >= LevelSettings[Level.Invulnerable].GetInt()) return Level.Invulnerable; + if (OxygenLevel >= LevelSettings[Level.Fast].GetInt()) return Level.Fast; + return Level.None; + // ReSharper restore ConvertIfStatementToReturnStatement + } + + Color GetLevelColor() => GetCurrentLevel() switch + { + Level.Blind => Palette.Orange, + Level.Slow => Color.yellow, + Level.Fast => Color.cyan, + Level.Invulnerable => Color.green, + _ => Color.white + }; + + public void ReceiveRPC(Hazel.MessageReader reader) + { + OxygenLevel = reader.ReadPackedInt32(); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool isHUD = false, bool isMeeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != OxymanId || (seer.IsModClient() && !isHUD)) return string.Empty; + return $"<#ff0000>O2: {Utils.ColorString(GetLevelColor(), OxygenLevel.ToString())}%"; + } + + enum Level + { + Blind, + Slow, + None, + Fast, + Invulnerable + } + } +} \ No newline at end of file diff --git a/Roles/Crewmate/Rhapsode.cs b/Roles/Crewmate/Rhapsode.cs new file mode 100644 index 000000000..0cb707b4c --- /dev/null +++ b/Roles/Crewmate/Rhapsode.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using System.Linq; +using AmongUs.GameOptions; +using EHR.Modules; + +namespace EHR.Crewmate +{ + public class Rhapsode : RoleBase + { + public static bool On; + private static List Instances = []; + + public static OptionItem AbilityCooldown; + public static OptionItem AbilityDuration; + private static OptionItem ExcludeCrewmates; + private static OptionItem AbilityUseLimit; + public static OptionItem RhapsodeAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + private bool AbilityActive; + private long ActivateTimeStamp; + private long LastUpdate; + private byte RhapsodeId; + + public override bool IsEnable => On; + + public static void SetupCustomOption() + { + int id = 647650; + Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Rhapsode); + AbilityCooldown = new IntegerOptionItem(++id, "AbilityCooldown", new(0, 60, 1), 30, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) + .SetValueFormat(OptionFormat.Seconds); + AbilityDuration = new IntegerOptionItem(++id, "AbilityDuration", new(0, 60, 1), 10, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) + .SetValueFormat(OptionFormat.Seconds); + ExcludeCrewmates = new BooleanOptionItem(++id, "Rhapsode.ExcludeCrewmates", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]); + AbilityUseLimit = new IntegerOptionItem(++id, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) + .SetValueFormat(OptionFormat.Times); + RhapsodeAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(++id, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.3f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) + .SetValueFormat(OptionFormat.Times); + AbilityChargesWhenFinishedTasks = new FloatOptionItem(++id, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + On = false; + Instances = []; + } + + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + RhapsodeId = playerId; + AbilityActive = false; + ActivateTimeStamp = 0; + LastUpdate = 0; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (Options.UsePets.GetBool()) return; + AURoleOptions.EngineerCooldown = AbilityCooldown.GetInt(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) => ActivateAbility(physics.myPlayer); + + public override void OnPet(PlayerControl pc) => ActivateAbility(pc); + + private void ActivateAbility(PlayerControl pc) + { + AbilityActive = true; + ActivateTimeStamp = Utils.TimeStamp; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, AbilityActive); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + long now = Utils.TimeStamp; + if (now == LastUpdate) return; + LastUpdate = now; + + if (AbilityActive && Utils.TimeStamp - ActivateTimeStamp >= AbilityDuration.GetInt()) + { + AbilityActive = false; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, AbilityActive); + } + + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public static bool CheckAbilityUse(PlayerControl pc) + { + if (pc.IsCrewmate() && ExcludeCrewmates.GetBool()) return true; + return !Instances.Any(x => x.AbilityActive); + } + + public void ReceiveRPC(Hazel.MessageReader reader) + { + AbilityActive = reader.ReadBoolean(); + if (AbilityActive) ActivateTimeStamp = Utils.TimeStamp; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool isHUD = false, bool isMeeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != RhapsodeId || isHUD || isMeeting || !AbilityActive) return string.Empty; + return $"\u25b6 ({AbilityDuration.GetInt() - (Utils.TimeStamp - ActivateTimeStamp)}s)"; + } + } +} \ No newline at end of file diff --git a/Roles/Crewmate/Sheriff.cs b/Roles/Crewmate/Sheriff.cs index 03d5a17b6..ad5077064 100644 --- a/Roles/Crewmate/Sheriff.cs +++ b/Roles/Crewmate/Sheriff.cs @@ -27,6 +27,7 @@ public class Sheriff : RoleBase public static OptionItem NonCrewCanKillCrew; public static OptionItem NonCrewCanKillImp; public static OptionItem NonCrewCanKillNeutral; + public static OptionItem KeepsGameGoing; public static OptionItem UsePet; public static Dictionary KillTargetOptions = []; @@ -62,6 +63,7 @@ public class Sheriff : RoleBase NonCrewCanKillImp = new BooleanOptionItem(Id + 19, "SheriffMadCanKillImp", true, TabGroup.CrewmateRoles).SetParent(SetNonCrewCanKill); NonCrewCanKillCrew = new BooleanOptionItem(Id + 21, "SheriffMadCanKillCrew", true, TabGroup.CrewmateRoles).SetParent(SetNonCrewCanKill); NonCrewCanKillNeutral = new BooleanOptionItem(Id + 20, "SheriffMadCanKillNeutral", true, TabGroup.CrewmateRoles).SetParent(SetNonCrewCanKill); + KeepsGameGoing = new BooleanOptionItem(Id + 26, "SheriffKeepsGameGoing", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); UsePet = Options.CreatePetUseSetting(Id + 29, CustomRoles.Sheriff); } diff --git a/Roles/Impostor/Abyssbringer.cs b/Roles/Impostor/Abyssbringer.cs index 4861ecb54..943283d03 100644 --- a/Roles/Impostor/Abyssbringer.cs +++ b/Roles/Impostor/Abyssbringer.cs @@ -62,6 +62,7 @@ namespace EHR.Impostor public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) { + if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; CreateBlackHole(shapeshifter); return false; } diff --git a/Roles/Impostor/Assassin.cs b/Roles/Impostor/Assassin.cs index 0fa146ef1..c037421d3 100644 --- a/Roles/Impostor/Assassin.cs +++ b/Roles/Impostor/Assassin.cs @@ -129,10 +129,7 @@ internal class Assassin : RoleBase public override bool OnShapeshift(PlayerControl pc, PlayerControl t, bool shapeshifting) { if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return false; - if (!shapeshifting) - { - return true; - } + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; Take(pc); @@ -154,17 +151,14 @@ internal class Assassin : RoleBase if (IsUndertaker) target.TP(pc); else pc.TP(target); - LateTask.New(() => + if (!(target == null || !target.IsAlive() || Pelican.IsEaten(target.PlayerId) || target.inVent || !GameStates.IsInTask) && pc.RpcCheckAndMurder(target)) { - if (!(target == null || !target.IsAlive() || Pelican.IsEaten(target.PlayerId) || target.inVent || !GameStates.IsInTask) && pc.RpcCheckAndMurder(target)) - { - MarkedPlayer = byte.MaxValue; - SendRPC(pc.PlayerId); - pc.ResetKillCooldown(); - pc.SyncSettings(); - pc.SetKillCooldown(DefaultKillCooldown); - } - }, UsePets.GetBool() ? 0.1f : 1.2f, "Assassin Assassinate"); + MarkedPlayer = byte.MaxValue; + SendRPC(pc.PlayerId); + pc.ResetKillCooldown(); + pc.SyncSettings(); + pc.SetKillCooldown(DefaultKillCooldown); + } } } diff --git a/Roles/Impostor/Bomber.cs b/Roles/Impostor/Bomber.cs index d0bb7e3d6..ded12513f 100644 --- a/Roles/Impostor/Bomber.cs +++ b/Roles/Impostor/Bomber.cs @@ -94,7 +94,7 @@ namespace EHR.Impostor public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) { - if (!shapeshifting) return true; + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; Bomb(shapeshifter); return false; diff --git a/Roles/Impostor/Camouflager.cs b/Roles/Impostor/Camouflager.cs index 385ab40a3..1375e4c03 100644 --- a/Roles/Impostor/Camouflager.cs +++ b/Roles/Impostor/Camouflager.cs @@ -57,12 +57,18 @@ namespace EHR.Impostor public override bool OnShapeshift(PlayerControl pc, PlayerControl target, bool shapeshifting) { - if (shapeshifting && pc.GetAbilityUseLimit() < 1) + if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) + { + Camouflage.CheckCamouflage(); + return true; + } + + if (pc.GetAbilityUseLimit() < 1) { pc.SetKillCooldown(CamouflageDuration.GetFloat() + 1f); } - if (shapeshifting) pc.RpcRemoveAbilityUse(); + pc.RpcRemoveAbilityUse(); IsActive = true; Camouflage.CheckCamouflage(); diff --git a/Roles/Impostor/Disperser.cs b/Roles/Impostor/Disperser.cs index 5cb24c626..1c02c3abb 100644 --- a/Roles/Impostor/Disperser.cs +++ b/Roles/Impostor/Disperser.cs @@ -56,7 +56,7 @@ public class Disperser : RoleBase public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) { - if (shapeshifter == null || !shapeshifting) return false; + if (shapeshifter == null || (!shapeshifting && !UseUnshiftTrigger.GetBool())) return false; if (shapeshifter.GetAbilityUseLimit() < 1) { shapeshifter.SetKillCooldown(DisperserShapeshiftDuration.GetFloat() + 1f); diff --git a/Roles/Impostor/Escapee.cs b/Roles/Impostor/Escapee.cs index a4085ee08..09b40e41c 100644 --- a/Roles/Impostor/Escapee.cs +++ b/Roles/Impostor/Escapee.cs @@ -75,7 +75,7 @@ namespace EHR.Impostor public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) { - if (shapeshifting) + if (shapeshifting || Options.UseUnshiftTrigger.GetBool()) { TeleportOrMark(shapeshifter); } diff --git a/Roles/Impostor/Librarian.cs b/Roles/Impostor/Librarian.cs index 60bec0de2..6df52f4d4 100644 --- a/Roles/Impostor/Librarian.cs +++ b/Roles/Impostor/Librarian.cs @@ -129,7 +129,7 @@ namespace EHR.Impostor ChangeSilencingMode(pc); - return !shapeshifting || ShowSSAnimation.GetBool(); + return (!shapeshifting && !UseUnshiftTrigger.GetBool()) || ShowSSAnimation.GetBool(); } public override bool OnVanish(PlayerControl pc) @@ -151,7 +151,7 @@ namespace EHR.Impostor public override void OnFixedUpdate(PlayerControl pc) { if (!IsEnable) return; - if (ShowSSAnimation.GetBool() && !UsePhantomBasis.GetBool()) return; + if (ShowSSAnimation.GetBool() && !UsePhantomBasis.GetBool() && !UseUnshiftTrigger.GetBool()) return; if (IsInSilencingMode.SILENCING && IsInSilencingMode.LAST_CHANGE + SSDur.GetInt() < TimeStamp) { diff --git a/Roles/Impostor/Miner.cs b/Roles/Impostor/Miner.cs index a978ddd95..4c9b6d815 100644 --- a/Roles/Impostor/Miner.cs +++ b/Roles/Impostor/Miner.cs @@ -50,7 +50,7 @@ namespace EHR.Impostor public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) { - if (!shapeshifting) return true; + if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; TeleportToVent(shapeshifter); return false; diff --git a/Roles/Impostor/QuickShooter.cs b/Roles/Impostor/QuickShooter.cs index 35797ce46..7e9fbbf87 100644 --- a/Roles/Impostor/QuickShooter.cs +++ b/Roles/Impostor/QuickShooter.cs @@ -72,7 +72,7 @@ internal class QuickShooter : RoleBase public override bool OnShapeshift(PlayerControl pc, PlayerControl target, bool shapeshifting) { - if (Main.KillTimers[pc.PlayerId] <= 0 && shapeshifting) + if (Main.KillTimers[pc.PlayerId] <= 0 && (shapeshifting || Options.UseUnshiftTrigger.GetBool())) { Store(pc); } diff --git a/Roles/Impostor/RiftMaker.cs b/Roles/Impostor/RiftMaker.cs index 62142e365..2acd27040 100644 --- a/Roles/Impostor/RiftMaker.cs +++ b/Roles/Impostor/RiftMaker.cs @@ -123,7 +123,7 @@ namespace EHR.Impostor public override bool OnShapeshift(PlayerControl player, PlayerControl target, bool shapeshifting) { if (player == null) return false; - if (!shapeshifting) return true; + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; if (Marks.Count >= 2) return false; Mark(player); diff --git a/Roles/Impostor/Sapper.cs b/Roles/Impostor/Sapper.cs index 0246af193..c4b3f5953 100644 --- a/Roles/Impostor/Sapper.cs +++ b/Roles/Impostor/Sapper.cs @@ -62,6 +62,7 @@ namespace EHR.Impostor public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; return PlaceBomb(shapeshifter); } diff --git a/Roles/Impostor/Sniper.cs b/Roles/Impostor/Sniper.cs index e44281e50..dcba9918c 100644 --- a/Roles/Impostor/Sniper.cs +++ b/Roles/Impostor/Sniper.cs @@ -171,7 +171,7 @@ public class Sniper : RoleBase public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) { - return Snipe(shapeshifter, shapeshifting); + return Options.UseUnshiftTrigger.GetBool() ? Snipe(shapeshifter, !IsAim) : Snipe(shapeshifter, shapeshifting); } public override void OnPet(PlayerControl pc) diff --git a/Roles/Impostor/Swiftclaw.cs b/Roles/Impostor/Swiftclaw.cs index 097c61109..64ef50a75 100644 --- a/Roles/Impostor/Swiftclaw.cs +++ b/Roles/Impostor/Swiftclaw.cs @@ -43,6 +43,8 @@ namespace EHR.Impostor { if (Options.UsePhantomBasis.GetBool()) AURoleOptions.PhantomCooldown = DashCD.GetFloat(); + if (Options.UseUnshiftTrigger.GetBool()) + AURoleOptions.ShapeshifterCooldown = DashCD.GetFloat(); } public override void OnPet(PlayerControl pc) @@ -56,6 +58,13 @@ namespace EHR.Impostor return false; } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; + Dash(shapeshifter); + return false; + } + private static void Dash(PlayerControl pc) { if (pc == null || DashStart.ContainsKey(pc.PlayerId)) return; diff --git a/Roles/Impostor/Warlock.cs b/Roles/Impostor/Warlock.cs index d05c74b62..a98de9d08 100644 --- a/Roles/Impostor/Warlock.cs +++ b/Roles/Impostor/Warlock.cs @@ -168,7 +168,7 @@ namespace EHR.Impostor public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) { - if (!shapeshifting) return true; + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; Curse(shapeshifter); diff --git a/Roles/Neutral/Amnesiac.cs b/Roles/Neutral/Amnesiac.cs index 34cf6e41e..d0cdb43ec 100644 --- a/Roles/Neutral/Amnesiac.cs +++ b/Roles/Neutral/Amnesiac.cs @@ -10,7 +10,7 @@ namespace EHR.Neutral; public class Amnesiac : RoleBase { private const int Id = 35000; - private static List playerIdList = []; + private static List Instances = []; public static OptionItem RememberCooldown; public static OptionItem IncompatibleNeutralMode; @@ -31,7 +31,9 @@ public class Amnesiac : RoleBase "AmnesiacRM.ByReportingBody" ]; - public override bool IsEnable => playerIdList.Count > 0; + private byte AmnesiacId; + + public override bool IsEnable => Instances.Count > 0; public static void SetupCustomOption() { @@ -49,12 +51,13 @@ public class Amnesiac : RoleBase public override void Init() { - playerIdList = []; + Instances = []; } public override void Add(byte playerId) { - playerIdList.Add(playerId); + Instances.Add(this); + AmnesiacId = playerId; } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = RememberCooldown.GetFloat(); @@ -62,6 +65,19 @@ public class Amnesiac : RoleBase public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(false); + public static void OnAnyoneDeath(PlayerControl target) + { + if (RememberMode.GetValue() == 1) + { + foreach (Amnesiac instance in Instances) + { + LocateArrow.Add(instance.AmnesiacId, target.Pos()); + var amne = Utils.GetPlayerById(instance.AmnesiacId); + Utils.NotifyRoles(SpecifySeer: amne, SpecifyTarget: amne); + } + } + } + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) { if (RememberMode.GetValue() == 0) @@ -173,6 +189,11 @@ public class Amnesiac : RoleBase target.RpcGuardAndKill(target); } + public override void OnReportDeadBody() + { + LocateArrow.RemoveAllTarget(AmnesiacId); + } + public override bool KnowRole(PlayerControl player, PlayerControl target) { if (player.Is(CustomRoles.Refugee) && target.Is(CustomRoleTypes.Impostor)) return true; @@ -184,4 +205,10 @@ public class Amnesiac : RoleBase ActionButton amneButton = RememberMode.GetValue() == 0 ? hud.KillButton : hud.ReportButton; amneButton?.OverrideText(GetString("RememberButtonText")); } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool isHUD = false, bool isMeeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != AmnesiacId || isMeeting || isHUD) return string.Empty; + return LocateArrow.GetArrows(seer); + } } \ No newline at end of file diff --git a/Roles/Neutral/Bargainer.cs b/Roles/Neutral/Bargainer.cs index 2d0f88e07..550c60438 100644 --- a/Roles/Neutral/Bargainer.cs +++ b/Roles/Neutral/Bargainer.cs @@ -217,6 +217,8 @@ namespace EHR.Neutral opt.SetVision(HasImpostorVision.GetBool()); if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) + AURoleOptions.ShapeshifterCooldown = 1f; } void Update() => BargainerId.SetAbilityUseLimit(Money); @@ -265,6 +267,13 @@ namespace EHR.Neutral return false; } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + CycleItem(shapeshifter); + return false; + } + public override void AfterMeetingTasks() { if (Gains.TryGetValue(MoneyGainingAction.SurviveMeeting, out var gain)) diff --git a/Roles/Neutral/Beehive.cs b/Roles/Neutral/Beehive.cs new file mode 100644 index 000000000..e50877cdc --- /dev/null +++ b/Roles/Neutral/Beehive.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using AmongUs.GameOptions; +using EHR.Modules; +using UnityEngine; + +namespace EHR.Neutral +{ + public class Beehive : RoleBase + { + public static bool On; + + private static OptionItem Distance; + private static OptionItem Time; + private static OptionItem StingCooldown; + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + private byte BeehiveId; + public Dictionary StungPlayers = []; + + public override bool IsEnable => On; + + public static void SetupCustomOption() + { + int id = 647350; + Options.SetupRoleOptions(id++, TabGroup.NeutralRoles, CustomRoles.Beehive); + Distance = new FloatOptionItem(++id, "Beehive.Distance", new(0f, 20f, 0.5f), 15f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]); + Time = new FloatOptionItem(++id, "Beehive.Time", new(0f, 30f, 0.5f), 10f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]) + .SetValueFormat(OptionFormat.Seconds); + StingCooldown = new FloatOptionItem(++id, "Beehive.StingCooldown", new(0f, 180f, 0.5f), 5f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]) + .SetValueFormat(OptionFormat.Seconds); + KillCooldown = new FloatOptionItem(++id, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]) + .SetValueFormat(OptionFormat.Seconds); + CanVent = new BooleanOptionItem(++id, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]); + HasImpostorVision = new BooleanOptionItem(++id, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + BeehiveId = playerId; + StungPlayers = []; + } + + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool()); + public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!base.OnCheckMurder(killer, target)) return false; + + return killer.CheckDoubleTrigger(target, () => + { + Vector2 pos = target.Pos(); + StungPlayers[target.PlayerId] = (Utils.TimeStamp, pos); + killer.SetKillCooldown(time: StingCooldown.GetFloat()); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + Utils.SendRPC(CustomRPC.SyncRoleData, BeehiveId, 1, target.PlayerId, pos); + target.Notify(string.Format(Translator.GetString("Beehive.Notify"), Math.Round(Distance.GetFloat(), 1), Math.Round(Time.GetFloat(), 1)), 8f); + }); + } + + public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + { + if (lowLoad || !pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; + + if (StungPlayers.TryGetValue(pc.PlayerId, out var sp)) + { + if (Utils.TimeStamp - sp.TimeStamp >= Time.GetFloat()) + { + StungPlayers.Remove(pc.PlayerId); + Utils.SendRPC(CustomRPC.SyncRoleData, BeehiveId, 2, pc.PlayerId); + if (Vector2.Distance(pc.Pos(), sp.InitialPosition) < Distance.GetFloat()) + pc.Suicide(realKiller: Utils.GetPlayerById(BeehiveId)); + } + + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + } + + public void ReceiveRPC(Hazel.MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + StungPlayers[reader.ReadByte()] = (Utils.TimeStamp, reader.ReadVector2()); + break; + case 2: + StungPlayers.Remove(reader.ReadByte()); + break; + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool isHUD = false, bool isMeeting = false) + { + if (seer.PlayerId != target.PlayerId || isMeeting || isHUD || !StungPlayers.TryGetValue(seer.PlayerId, out var sp)) return string.Empty; + + var walked = Math.Round(Vector2.Distance(seer.Pos(), sp.InitialPosition), 1); + var distance = Math.Round(Distance.GetFloat(), 1); + var time = Time.GetInt() - (Utils.TimeStamp - sp.TimeStamp); + var color = walked >= distance ? "<#00ffa5>" : "<#ffa500>"; + var color2 = walked >= distance ? "<#00ffff>" : "<#ffff00>"; + return $"{color}{walked}{color2}/{distance} <#ffffff>({time}s)"; + } + } +} \ No newline at end of file diff --git a/Roles/Neutral/Chemist.cs b/Roles/Neutral/Chemist.cs index 7576d1006..1eeeb409f 100644 --- a/Roles/Neutral/Chemist.cs +++ b/Roles/Neutral/Chemist.cs @@ -213,6 +213,8 @@ namespace EHR.Neutral opt.SetVision(HasImpostorVision.GetBool()); if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) + AURoleOptions.ShapeshifterCooldown = 1f; } static ItemType GetItemType(Item item) => item switch @@ -486,6 +488,16 @@ namespace EHR.Neutral return false; } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + if (SortedAvailableProcesses.Count == 0) return false; + + Cycle(shapeshifter); + + return false; + } + private void Cycle(PlayerControl pc) { var index = SortedAvailableProcesses.IndexOf(SelectedProcess); diff --git a/Roles/Neutral/Enderman.cs b/Roles/Neutral/Enderman.cs index 94755f38a..7967825b6 100644 --- a/Roles/Neutral/Enderman.cs +++ b/Roles/Neutral/Enderman.cs @@ -54,6 +54,8 @@ namespace EHR.Neutral opt.SetVision(true); if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = Time.GetInt() + 2f; + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) + AURoleOptions.ShapeshifterCooldown = Time.GetInt() + 2f; } @@ -74,6 +76,13 @@ namespace EHR.Neutral return false; } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + MarkPosition(); + return false; + } + void MarkPosition() { if (!IsEnable || Enderman_.HasAbilityCD()) return; diff --git a/Roles/Neutral/Hookshot.cs b/Roles/Neutral/Hookshot.cs index 73eaccd1d..8a2fbacb1 100644 --- a/Roles/Neutral/Hookshot.cs +++ b/Roles/Neutral/Hookshot.cs @@ -55,6 +55,8 @@ namespace EHR.Neutral opt.SetVision(HasImpostorVision.GetBool()); if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) + AURoleOptions.ShapeshifterCooldown = 1f; } void SendRPC() @@ -92,6 +94,13 @@ namespace EHR.Neutral return false; } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + ExecuteAction(); + return false; + } + void ExecuteAction() { if (MarkedPlayerId == byte.MaxValue) return; diff --git a/Roles/Neutral/Lawyer.cs b/Roles/Neutral/Lawyer.cs index 3a844e247..b52e6001c 100644 --- a/Roles/Neutral/Lawyer.cs +++ b/Roles/Neutral/Lawyer.cs @@ -22,19 +22,6 @@ public class Lawyer : RoleBase public static Dictionary Target = []; - public static readonly string[] ChangeRoles = - [ - "Role.Crewmate", - "Role.Jester", - "Role.Opportunist", - "Role.Convict", - "Role.Celebrity", - "Role.Bodyguard", - "Role.Dictator", - "Role.Mayor", - "Role.Doctor" - ]; - public static readonly CustomRoles[] CRoleChangeRoles = [ CustomRoles.CrewmateEHR, @@ -45,7 +32,8 @@ public class Lawyer : RoleBase CustomRoles.Bodyguard, CustomRoles.Dictator, CustomRoles.Mayor, - CustomRoles.Doctor + CustomRoles.Doctor, + CustomRoles.Amnesiac ]; public override bool IsEnable => playerIdList.Count > 0; @@ -59,7 +47,7 @@ public class Lawyer : RoleBase CanTargetJester = new BooleanOptionItem(Id + 13, "LawyerCanTargetJester", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); KnowTargetRole = new BooleanOptionItem(Id + 14, "KnowTargetRole", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); TargetKnowsLawyer = new BooleanOptionItem(Id + 15, "TargetKnowsLawyer", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); - ChangeRolesAfterTargetKilled = new StringOptionItem(Id + 16, "LawyerChangeRolesAfterTargetKilled", ChangeRoles, 2, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); + ChangeRolesAfterTargetKilled = new StringOptionItem(Id + 16, "LawyerChangeRolesAfterTargetKilled", CRoleChangeRoles.Select(x => x.ToColoredString()).ToArray(), 2, TabGroup.NeutralRoles, noTranslation: true).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); } public override void Init() diff --git a/Roles/Neutral/Magician.cs b/Roles/Neutral/Magician.cs index e1d36085f..67b8215e9 100644 --- a/Roles/Neutral/Magician.cs +++ b/Roles/Neutral/Magician.cs @@ -110,6 +110,8 @@ public class Magician : RoleBase opt.SetVision(HasImpostorVision.GetBool()); if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) + AURoleOptions.ShapeshifterCooldown = 1f; } public override void OnMurder(PlayerControl killer, PlayerControl target) @@ -123,7 +125,7 @@ public class Magician : RoleBase sb.Append("\n\n"); sb.AppendLine(ColorString(GetRoleColor(CustomRoles.Magician), $"Card name: {GetString($"Magician-GetIdToName-{CardId}")}")); sb.AppendLine(ColorString(GetRoleColor(CustomRoles.Magician), $"Description: {GetString($"Magician-GetIdToDesc-{CardId}")}")); - sb.AppendLine(ColorString(GetRoleColor(CustomRoles.Magician), $"Trigger by: {(UsePets.GetBool() ? "Pet button" : UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool() ? "Vanish button" : "Sabotage")}")); + sb.AppendLine(ColorString(GetRoleColor(CustomRoles.Magician), $"Trigger by: {(UsePets.GetBool() ? "Pet button" : UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool() ? "Vanish button" : UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool() ? "Shapeshift button" : "Sabotage")}")); killer.Notify(sb.ToString(), 15f); } @@ -145,6 +147,14 @@ public class Magician : RoleBase return false; } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (shapeshifter == null) return false; + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return false; + UseCard(shapeshifter); + return false; + } + public static void UseCard(PlayerControl pc) { if (pc == null) return; diff --git a/Roles/Neutral/Mycologist.cs b/Roles/Neutral/Mycologist.cs index df5ed70af..10ff0c712 100644 --- a/Roles/Neutral/Mycologist.cs +++ b/Roles/Neutral/Mycologist.cs @@ -16,7 +16,8 @@ namespace EHR.Neutral "VentButtonText", // 0 "SabotageButtonText", // 1 "PetButtonText", // 2 - "AbilityButtonText.Phantom" // 3 + "AbilityButtonText.Phantom", // 3 + "AbilityButtonText.Shapeshifter" // 4 ]; private static OptionItem KillCooldown; @@ -74,6 +75,8 @@ namespace EHR.Neutral opt.SetVision(HasImpostorVision.GetBool()); if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = CD.GetInt(); + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) + AURoleOptions.ShapeshifterCooldown = CD.GetInt(); } void SendRPC() @@ -136,6 +139,17 @@ namespace EHR.Neutral return false; } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + if (SpreadAction.GetValue() == 4) + { + SpreadSpores(); + } + + return false; + } + void SpreadSpores() { if (!IsEnable || Mycologist_.HasAbilityCD()) return; diff --git a/Roles/Neutral/Patroller.cs b/Roles/Neutral/Patroller.cs index 9e79c9591..4a1d874da 100644 --- a/Roles/Neutral/Patroller.cs +++ b/Roles/Neutral/Patroller.cs @@ -63,6 +63,8 @@ namespace EHR.Neutral { if (Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + if (Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()) + AURoleOptions.ShapeshifterCooldown = 1f; var room = Utils.GetPlayerById(id)?.GetPlainShipRoom(); if (room == null) return; opt.SetVision(room == RoomBoosts[Boost.Vision]); @@ -123,6 +125,13 @@ namespace EHR.Neutral return false; } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; + OnPet(shapeshifter); + return false; + } + enum Boost { Speed, diff --git a/Roles/Neutral/RouleteGrandeur.cs b/Roles/Neutral/RouleteGrandeur.cs index 77545ba3e..edf54f0ca 100644 --- a/Roles/Neutral/RouleteGrandeur.cs +++ b/Roles/Neutral/RouleteGrandeur.cs @@ -62,6 +62,8 @@ namespace EHR.Neutral opt.SetVision(HasImpostorVision.GetBool()); if (Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + if (Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()) + AURoleOptions.ShapeshifterCooldown = 1f; } public override void OnExitVent(PlayerControl pc, Vent vent) @@ -88,6 +90,13 @@ namespace EHR.Neutral return false; } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; + Roll(shapeshifter); + return false; + } + void Roll(PlayerControl pc) { long now = Utils.TimeStamp; diff --git a/Roles/Neutral/Simon.cs b/Roles/Neutral/Simon.cs index f3069707e..4a328ef41 100644 --- a/Roles/Neutral/Simon.cs +++ b/Roles/Neutral/Simon.cs @@ -67,6 +67,8 @@ namespace EHR.Neutral opt.SetVision(HasImpostorVision.GetBool()); if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) + AURoleOptions.ShapeshifterCooldown = 1f; } public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) @@ -131,6 +133,13 @@ namespace EHR.Neutral return false; } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + OnPet(shapeshifter); + return false; + } + public override void OnReportDeadBody() { if (Executed) diff --git a/Roles/Neutral/Sprayer.cs b/Roles/Neutral/Sprayer.cs index df42479fb..2ee838350 100644 --- a/Roles/Neutral/Sprayer.cs +++ b/Roles/Neutral/Sprayer.cs @@ -88,6 +88,8 @@ namespace EHR.Neutral opt.SetVision(HasImpostorVision.GetBool()); if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = CD.GetInt(); + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) + AURoleOptions.ShapeshifterCooldown = CD.GetInt(); } public override bool OnSabotage(PlayerControl pc) @@ -107,6 +109,13 @@ namespace EHR.Neutral return false; } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + PlaceTrap(); + return false; + } + void PlaceTrap() { if (!IsEnable || SprayerId.GetAbilityUseLimit() <= 0 || Sprayer_.HasAbilityCD()) return; diff --git a/Roles/Neutral/Tiger.cs b/Roles/Neutral/Tiger.cs index 42ddbab08..ed7da6c98 100644 --- a/Roles/Neutral/Tiger.cs +++ b/Roles/Neutral/Tiger.cs @@ -95,6 +95,19 @@ namespace EHR.Neutral return false; } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; + + if (CooldownTimer <= 0f) + { + StartEnraging(); + CooldownTimer = EnrageCooldown.GetFloat() + EnrageDuration.GetFloat(); + } + + return false; + } + void StartEnraging() { EnrageTimer = EnrageDuration.GetFloat(); diff --git a/Roles/Neutral/WeaponMaster.cs b/Roles/Neutral/WeaponMaster.cs index 971f80c69..f0ac043cc 100644 --- a/Roles/Neutral/WeaponMaster.cs +++ b/Roles/Neutral/WeaponMaster.cs @@ -92,6 +92,8 @@ public class WeaponMaster : RoleBase opt.SetVision(HasImpostorVision.GetBool()); if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) + AURoleOptions.ShapeshifterCooldown = 1f; } public override bool CanUseKillButton(PlayerControl pc) => Mode != 3; @@ -113,6 +115,13 @@ public class WeaponMaster : RoleBase return false; } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + SwitchMode(); + return false; + } + void SwitchMode() { var id = playerIdList[0]; diff --git a/Roles/Neutral/Werewolf.cs b/Roles/Neutral/Werewolf.cs index 675dcfabd..9a749bdb5 100644 --- a/Roles/Neutral/Werewolf.cs +++ b/Roles/Neutral/Werewolf.cs @@ -71,6 +71,8 @@ public class Werewolf : RoleBase opt.SetVision(HasImpostorVision.GetBool()); if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) + AURoleOptions.ShapeshifterCooldown = 1f; } void SendRPC() @@ -160,6 +162,13 @@ public class Werewolf : RoleBase return false; } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + Rampage(shapeshifter); + return false; + } + private void Rampage(PlayerControl pc) { if (!AmongUsClient.Instance.AmHost || IsRampaging) return; diff --git a/main.cs b/main.cs index 474344d8f..c95c9075a 100644 --- a/main.cs +++ b/main.cs @@ -355,6 +355,8 @@ public class Main : BasePlugin { CustomRoles.Convener, "#34eb7a" }, { CustomRoles.Mathematician, "#eb3474" }, { CustomRoles.Transmitter, "#c9a11e" }, + { CustomRoles.Oxyman, "#ffa58c" }, + { CustomRoles.Rhapsode, "#f5ad42" }, { CustomRoles.Chef, "#d6d6ff" }, { CustomRoles.Lyncher, "#14ba7d" }, { CustomRoles.Socialite, "#32a8a8" }, @@ -461,6 +463,7 @@ public class Main : BasePlugin { CustomRoles.HexMaster, "#ff00ff" }, { CustomRoles.Wraith, "#4B0082" }, { CustomRoles.NSerialKiller, "#233fcc" }, + { CustomRoles.Beehive, "#ffff00" }, { CustomRoles.RouleteGrandeur, "#a88332" }, { CustomRoles.Nonplus, "#09632f" }, { CustomRoles.Tremor, "#e942f5" }, @@ -771,6 +774,7 @@ public enum CustomWinner Necromancer = CustomRoles.Necromancer, Wraith = CustomRoles.Wraith, SerialKiller = CustomRoles.NSerialKiller, + Beehive = CustomRoles.Beehive, RouleteGrandeur = CustomRoles.RouleteGrandeur, Nonplus = CustomRoles.Nonplus, Tremor = CustomRoles.Tremor,