From c5a2069b5b32b2c93069caf19bf4fca7c7a2cca6 Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Tue, 6 Aug 2024 14:05:09 +0200 Subject: [PATCH] Add Framer & Backstabber, Try fixing CNO (no success), other fixes --- CustomRoles.cs | 2 + Modules/CustomNetObject.cs | 56 +++++++++++---------- Modules/CustomRolesHelper.cs | 34 ++++++++----- Modules/EAC.cs | 3 +- Modules/ExtendedPlayerControl.cs | 7 +-- Modules/GameState.cs | 7 ++- Modules/GhostRolesManager.cs | 2 +- Modules/OptionHolder.cs | 1 - Modules/RPC.cs | 2 +- Modules/RoleBasisChanger.cs | 5 +- Modules/Utils.cs | 21 +++----- Patches/CheckGameEndPatch.cs | 6 ++- Patches/IntroPatch.cs | 2 +- Patches/OutroPatch.cs | 4 +- Resources/Lang/en_US.json | 16 ++++++ Roles/Crewmate/Detective.cs | 38 +++++++++++++-- Roles/Crewmate/Psychic.cs | 3 +- Roles/Impostor/Framer.cs | 50 +++++++++++++++++++ Roles/Neutral/Backstabber.cs | 83 ++++++++++++++++++++++++++++++++ Roles/Neutral/Jackal.cs | 7 ++- Roles/VanillaRole.cs | 1 + main.cs | 6 ++- 22 files changed, 281 insertions(+), 75 deletions(-) create mode 100644 Roles/Impostor/Framer.cs create mode 100644 Roles/Neutral/Backstabber.cs diff --git a/CustomRoles.cs b/CustomRoles.cs index 7359e534d..f869f25c7 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -50,6 +50,7 @@ public enum CustomRoles EvilGuesser, EvilTracker, FireWorks, + Framer, Freezer, Gambler, Gangster, @@ -248,6 +249,7 @@ public enum CustomRoles Agitater, Amnesiac, Arsonist, + Backstabber, Bandit, Bargainer, Beehive, diff --git a/Modules/CustomNetObject.cs b/Modules/CustomNetObject.cs index 57dacccc3..d6efb4a41 100644 --- a/Modules/CustomNetObject.cs +++ b/Modules/CustomNetObject.cs @@ -20,13 +20,13 @@ namespace EHR public PlayerControl playerControl; private float PlayerControlTimer; public Vector2 Position; - private string Sprite; + private string Sprite; +/* protected void RpcChangeSprite(string sprite) { Sprite = sprite; - LateTask.New(() => - { + LateTask.New(() => { playerControl.RawSetName(sprite); var name = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName; var colorId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId; @@ -67,8 +67,9 @@ namespace EHR writer.EndMessage(); sender.EndMessage(); sender.SendMessage(); - }, 0f, "CustomNetObject.RpcChangeSprite"); + }, 0f); } +*/ public void TP(Vector2 position) { @@ -78,15 +79,17 @@ namespace EHR public void Despawn() { + Logger.Info($" Despawn Custom Net Object {this.GetType().Name} (ID {Id})", "CNO.Despawn"); playerControl.Despawn(); AllObjects.Remove(this); } - protected void Show(PlayerControl player) + protected void Hide(PlayerControl player) { + Logger.Info($" Hide Custom Net Object {this.GetType().Name} (ID {Id}) from {player.GetNameWithRole()}", "CNO.Hide"); if (player.AmOwner) { - playerControl.Visible = true; + playerControl.Visible = false; return; } @@ -107,6 +110,7 @@ namespace EHR PlayerControlTimer += Time.fixedDeltaTime; if (PlayerControlTimer > 20f) { + Logger.Info($" Recreate Custom Net Object {this.GetType().Name} (ID {Id})", "CNO.OnFixedUpdate"); PlayerControl oldPlayerControl = playerControl; playerControl = Object.Instantiate(AmongUsClient.Instance.PlayerPrefab, Vector2.zero, Quaternion.identity); playerControl.PlayerId = 255; @@ -182,11 +186,11 @@ namespace EHR writer.EndMessage(); sender.EndMessage(); sender.SendMessage(); - }, 0.2f, "CustomNetObject.OnFixedUpdate"); + }, 0.2f); LateTask.New(() => oldPlayerControl.Despawn(), 0.3f); playerControl.cosmetics.currentBodySprite.BodySprite.color = Color.clear; playerControl.cosmetics.colorBlindText.color = Color.clear; - foreach (var pc in PlayerControl.AllPlayerControls) + foreach (var pc in Main.AllPlayerControls) { if (pc.AmOwner) continue; LateTask.New(() => @@ -215,21 +219,23 @@ namespace EHR }, 0.1f); } +/* if (this is TrapArea trapArea) { foreach (var pc in PlayerControl.AllPlayerControls) { - if (trapArea.VisibleList.Contains(pc.PlayerId)) - Show(pc); + if (!trapArea.VisibleList.Contains(pc.PlayerId)) + Hide(pc); } } - +*/ PlayerControlTimer = 0f; } } protected void CreateNetObject(string sprite, Vector2 position) { + Logger.Info($" Create Custom Net Object {this.GetType().Name} (ID {Id}) at {position}", "CNO.CreateNetObject"); playerControl = Object.Instantiate(AmongUsClient.Instance.PlayerPrefab, Vector2.zero, Quaternion.identity); playerControl.PlayerId = 255; playerControl.isNew = false; @@ -304,7 +310,7 @@ namespace EHR writer.EndMessage(); sender.EndMessage(); sender.SendMessage(); - }, 0.2f, "CustomNetObject.CreateNetObject"); + }, 0.2f); Position = position; PlayerControlTimer = 0f; playerControl.cosmetics.currentBodySprite.BodySprite.color = Color.clear; @@ -312,9 +318,9 @@ namespace EHR Sprite = sprite; ++MaxId; Id = MaxId; - if (MaxId > 10000) MaxId = 0; + if (MaxId == int.MaxValue) MaxId = int.MinValue; AllObjects.Add(this); - foreach (var pc in PlayerControl.AllPlayerControls) + foreach (var pc in Main.AllPlayerControls) { if (pc.AmOwner) continue; LateTask.New(() => @@ -340,7 +346,7 @@ namespace EHR writer.EndMessage(); sender.EndMessage(); sender.SendMessage(); - }, 0.1f, "CustomNetObject.CreateNetObject_2"); + }, 0.1f); } } @@ -361,6 +367,7 @@ namespace EHR } } +/* internal sealed class Explosion : CustomNetObject { private readonly float Duration; @@ -430,7 +437,7 @@ namespace EHR WaitDuration = waitDuration; State = 0; CreateNetObject($"<#c7c7c769>●", position); - visibleList.Select(x => x.GetPlayer()).DoIf(x => x != null, Show); + Main.AllAlivePlayerControls.ExceptBy(visibleList, x => x.PlayerId).Do(Hide); } protected override void OnFixedUpdate() @@ -451,7 +458,7 @@ namespace EHR } } } - +*/ internal sealed class TornadoObject : CustomNetObject { private readonly long SpawnTimeStamp; @@ -461,7 +468,7 @@ namespace EHR { SpawnTimeStamp = Utils.TimeStamp; CreateNetObject("\u2588\u2588<#bababa>\u2588<#bababa>\u2588<#bababa>\u2588<#bababa>\u2588\u2588\u2588
\u2588<#bababa>\u2588<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588<#bababa>\u2588\u2588
<#bababa>\u2588<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588<#bababa>\u2588
<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#636363>\u2588<#636363>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588
<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#636363>\u2588<#636363>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588
<#bababa>\u2588<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588<#bababa>\u2588
\u2588<#bababa>\u2588<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588<#bababa>\u2588\u2588
\u2588\u2588<#bababa>\u2588<#bababa>\u2588<#bababa>\u2588<#bababa>\u2588\u2588\u2588
", position); - visibleList.Select(x => x.GetPlayer()).DoIf(x => x != null, Show); + Main.AllAlivePlayerControls.ExceptBy(visibleList, x => x.PlayerId).Do(Hide); } protected override void OnFixedUpdate() @@ -496,7 +503,7 @@ namespace EHR public PlayerDetector(Vector2 position, List visibleList, out int id) { CreateNetObject("\u2588\u2588<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588\u2588\u2588
\u2588<#33e6b0>\u2588\u2588\u2588\u2588\u2588<#33e6b0>\u2588\u2588
<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588
<#33e6b0>\u2588\u2588<#33e6b0>\u2588<#000000>\u2588<#000000>\u2588<#33e6b0>\u2588\u2588<#33e6b0>\u2588
<#33e6b0>\u2588\u2588<#33e6b0>\u2588<#000000>\u2588<#000000>\u2588<#33e6b0>\u2588\u2588<#33e6b0>\u2588
<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588
\u2588<#33e6b0>\u2588\u2588\u2588\u2588\u2588<#33e6b0>\u2588\u2588
\u2588\u2588<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588\u2588\u2588
", position); - visibleList.Select(x => x.GetPlayer()).DoIf(x => x != null, Show); + Main.AllAlivePlayerControls.ExceptBy(visibleList, x => x.PlayerId).Do(Hide); id = Id; } } @@ -510,7 +517,7 @@ namespace EHR Resource = resource; var data = Adventurer.ResourceDisplayData[resource]; CreateNetObject($"{Utils.ColorString(data.Color, data.Icon.ToString())}", position); - visibleList.Select(x => x.GetPlayer()).DoIf(x => x != null, Show); + Main.AllAlivePlayerControls.ExceptBy(visibleList, x => x.PlayerId).Do(Hide); } } @@ -519,7 +526,7 @@ namespace EHR internal Toilet(Vector2 position, IEnumerable hideList) { CreateNetObject("\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588\u2588
<#e6e6e6>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#e6e6e6>\u2588
<#e6e6e6>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#e6e6e6>\u2588
\u2588<#e6e6e6>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#e6e6e6>\u2588\u2588
\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#d3d4ce>\u2588<#dedede>\u2588<#dedede>\u2588<#d3d4ce>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588\u2588
\u2588<#bfbfbf>\u2588<#454545>\u2588<#333333>\u2588<#333333>\u2588<#333333>\u2588<#333333>\u2588<#333333>\u2588<#333333>\u2588<#bfbfbf>\u2588
\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#454545>\u2588<#454545>\u2588<#454545>\u2588<#454545>\u2588<#454545>\u2588<#454545>\u2588<#bfbfbf>\u2588
\u2588\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588
\u2588\u2588\u2588<#dedede>\u2588<#dedede>\u2588<#dedede>\u2588<#dedede>\u2588\u2588\u2588\u2588
\u2588\u2588\u2588\u2588<#dedede>\u2588<#dedede>\u2588\u2588\u2588\u2588\u2588
", position); - Main.AllPlayerControls.Except(hideList).Do(Show); + hideList.Do(Hide); } } @@ -528,7 +535,6 @@ namespace EHR internal BlackHole(Vector2 position) { CreateNetObject("\u2588\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588\u2588\u2588
\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588\u2588
<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588
<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588
<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588
<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588
\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588\u2588
\u2588\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588\u2588\u2588
", position); - Main.AllPlayerControls.Do(Show); } } @@ -537,7 +543,7 @@ namespace EHR public SprayedArea(Vector2 position, IEnumerable visibleList) { CreateNetObject("\u2588\u2588<#ffd000>\u2588<#ffd000>\u2588\u2588\u2588
\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588\u2588
<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588
<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588
\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588\u2588
\u2588\u2588<#ffd000>\u2588<#ffd000>\u2588\u2588\u2588
", position); - visibleList.Select(x => x.GetPlayer()).DoIf(x => x != null, Show); + Main.AllAlivePlayerControls.ExceptBy(visibleList, x => x.PlayerId).Do(Hide); } } @@ -546,7 +552,7 @@ namespace EHR public CatcherTrap(Vector2 position, PlayerControl catcher) { CreateNetObject("\u2588\u2588<#ccffda>\u2588<#ccffda>\u2588\u2588\u2588
\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588\u2588
<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588
<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588
\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588\u2588
\u2588\u2588<#ccffda>\u2588<#ccffda>\u2588\u2588\u2588
", position); - Show(catcher); + Main.AllAlivePlayerControls.Without(catcher).Do(Hide); } } @@ -555,7 +561,6 @@ namespace EHR public YellowFlag(Vector2 position) { CreateNetObject("<#000000>\u2588<#ffff00>\u2588<#ffff00>\u2588<#ffff00>\u2588\u2588\u2588
<#000000>\u2588<#ffff00>\u2588<#ffff00>\u2588<#ffff00>\u2588<#ffff00>\u2588<#ffff00>\u2588
<#000000>\u2588\u2588\u2588<#ffff00>\u2588<#ffff00>\u2588<#ffff00>\u2588
<#000000>\u2588\u2588\u2588\u2588\u2588\u2588
<#000000>\u2588\u2588\u2588\u2588\u2588\u2588
<#000000>\u2588\u2588\u2588\u2588\u2588\u2588
", position); - Main.AllPlayerControls.Do(Show); } } @@ -564,7 +569,6 @@ namespace EHR public BlueFlag(Vector2 position) { CreateNetObject("<#000000>\u2588<#0000ff>\u2588<#0000ff>\u2588<#0000ff>\u2588\u2588\u2588
<#000000>\u2588<#0000ff>\u2588<#0000ff>\u2588<#0000ff>\u2588<#0000ff>\u2588<#0000ff>\u2588
<#000000>\u2588\u2588\u2588<#0000ff>\u2588<#0000ff>\u2588<#0000ff>\u2588
<#000000>\u2588\u2588\u2588\u2588\u2588\u2588
<#000000>\u2588\u2588\u2588\u2588\u2588\u2588
<#000000>\u2588\u2588\u2588\u2588\u2588\u2588
", position); - Main.AllPlayerControls.Do(Show); } } } \ No newline at end of file diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 9abd32175..a727e8675 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -144,6 +144,7 @@ internal static class CustomRolesHelper CustomRoles.YinYanger => CustomRoles.Impostor, CustomRoles.Duellist => CustomRoles.Shapeshifter, CustomRoles.Consort => CustomRoles.Impostor, + CustomRoles.Framer => CustomRoles.Impostor, CustomRoles.Mafioso => CustomRoles.Impostor, CustomRoles.Chronomancer => CustomRoles.Impostor, CustomRoles.Nullifier => CustomRoles.Impostor, @@ -434,6 +435,7 @@ internal static class CustomRolesHelper CustomRoles.SchrodingersCat => RoleTypes.Impostor, CustomRoles.Shifter => RoleTypes.Impostor, CustomRoles.Impartial => RoleTypes.Impostor, + CustomRoles.Backstabber => RoleTypes.Impostor, CustomRoles.Predator => RoleTypes.Impostor, CustomRoles.Reckless => RoleTypes.Impostor, CustomRoles.Magician => RoleTypes.Impostor, @@ -583,6 +585,7 @@ internal static class CustomRolesHelper CustomRoles.Duellist or CustomRoles.YinYanger or CustomRoles.Consort or + CustomRoles.Framer or CustomRoles.Mafioso or CustomRoles.Nullifier or CustomRoles.Chronomancer or @@ -1062,6 +1065,20 @@ internal static class CustomRolesHelper : CountTypes.Crew }; + public static Color GetTeamColor(this Team team) => ColorUtility.TryParseHtmlString(team switch + { + Team.Crewmate => Main.CrewmateColor, + Team.Neutral => Main.NeutralColor, + Team.Impostor => Main.ImpostorColor, + _ => string.Empty + }, out var color) + ? color + : Color.clear; + + public static string ToColoredString(this CustomRoles role) => Utils.ColorString(Utils.GetRoleColor(role), Translator.GetString($"{role}")); + + #region OptionsAndCategories + public static RoleOptionType GetRoleOptionType(this CustomRoles role) { if (role.IsImpostor()) return role.GetImpostorRoleCategory(); @@ -1115,18 +1132,6 @@ internal static class CustomRolesHelper _ => Palette.CrewmateBlue }; - public static Color GetTeamColor(this Team team) => ColorUtility.TryParseHtmlString(team switch - { - Team.Crewmate => Main.CrewmateColor, - Team.Neutral => Main.NeutralColor, - Team.Impostor => Main.ImpostorColor, - _ => string.Empty - }, out var color) - ? color - : Color.clear; - - public static string ToColoredString(this CustomRoles role) => Utils.ColorString(Utils.GetRoleColor(role), Translator.GetString($"{role}")); - public static RoleOptionType GetNeutralRoleCategory(this CustomRoles role) => role switch { CustomRoles.Opportunist => RoleOptionType.Neutral_Benign, @@ -1139,6 +1144,7 @@ internal static class CustomRolesHelper CustomRoles.FFF => RoleOptionType.Neutral_Benign, CustomRoles.Revolutionist => RoleOptionType.Neutral_Benign, CustomRoles.Impartial => RoleOptionType.Neutral_Benign, + CustomRoles.Backstabber => RoleOptionType.Neutral_Benign, CustomRoles.Shifter => RoleOptionType.Neutral_Benign, CustomRoles.Sunnyboy => RoleOptionType.Neutral_Benign, CustomRoles.Maverick => RoleOptionType.Neutral_Benign, @@ -1201,6 +1207,7 @@ internal static class CustomRolesHelper CustomRoles.Cleaner => RoleOptionType.Impostor_Support, CustomRoles.Commander => RoleOptionType.Impostor_Support, CustomRoles.Consort => RoleOptionType.Impostor_Support, + CustomRoles.Framer => RoleOptionType.Impostor_Support, CustomRoles.Deathpact => RoleOptionType.Impostor_Support, CustomRoles.Devourer => RoleOptionType.Impostor_Support, CustomRoles.Disperser => RoleOptionType.Impostor_Support, @@ -1372,6 +1379,8 @@ internal static class CustomRolesHelper CustomRoles.Tunneler => RoleOptionType.Crewmate_Miscellaneous, _ => role.IsImpostor() ? RoleOptionType.Impostor_Miscellaneous : RoleOptionType.Neutral_Benign }; + + #endregion } #pragma warning disable IDE0079 @@ -1425,6 +1434,7 @@ public enum CountTypes None, Crew, Impostor, + CustomTeam, Bloodlust, Jackal, Doppelganger, diff --git a/Modules/EAC.cs b/Modules/EAC.cs index 8921a2a58..93b4c9dec 100644 --- a/Modules/EAC.cs +++ b/Modules/EAC.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using EHR.Modules; using HarmonyLib; using Hazel; using InnerNet; @@ -27,7 +26,7 @@ internal static class EAC public static bool ReceiveRpc(PlayerControl pc, byte callId, MessageReader reader) { if (!AmongUsClient.Instance.AmHost) return false; - if (RoleBasisChanger.IsChangeInProgress) return false; + // if (RoleBasisChanger.IsChangeInProgress) return false; if (pc == null || reader == null) return false; try { diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 19ad04841..682ea2d29 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -975,7 +975,7 @@ static class ExtendedPlayerControl public static bool IsNeutralKiller(this PlayerControl player) => player.Is(CustomRoles.Bloodlust) || player.GetCustomRole().IsNK(); public static bool IsNeutralBenign(this PlayerControl player) => player.GetCustomRole().GetNeutralRoleCategory() == RoleOptionType.Neutral_Benign; public static bool IsNeutralEvil(this PlayerControl player) => player.GetCustomRole().GetNeutralRoleCategory() == RoleOptionType.Neutral_Evil; - public static bool IsSnitchTarget(this PlayerControl player) => player.Is(CustomRoles.Bloodlust) || player.GetCustomRole().IsSnitchTarget(); + public static bool IsSnitchTarget(this PlayerControl player) => player.Is(CustomRoles.Bloodlust) || Framer.FramedPlayers.Contains(player.PlayerId) || player.GetCustomRole().IsSnitchTarget(); public static bool IsMadmate(this PlayerControl player) => player.Is(CustomRoles.Madmate) || player.GetCustomRole().IsMadmate(); public static bool HasGhostRole(this PlayerControl player) => GhostRolesManager.AssignedGhostRoles.ContainsKey(player.PlayerId) || Main.PlayerStates.TryGetValue(player.PlayerId, out var state) && state.SubRoles.Any(x => x.IsGhostRole()); @@ -1019,7 +1019,7 @@ static class ExtendedPlayerControl } var State = Main.PlayerStates[target.PlayerId]; - if (State.RealKiller.TIMESTAMP != DateTime.MinValue && NotOverRide) return; // Do not overwrite if value already exists + if (State.RealKiller.TimeStamp != DateTime.MinValue && NotOverRide) return; // Do not overwrite if value already exists byte killerId = killer == null ? byte.MaxValue : killer.PlayerId; RPC.SetRealKiller(target.PlayerId, killerId); } @@ -1061,7 +1061,7 @@ static class ExtendedPlayerControl public static bool Is(this PlayerControl target, Team team) => team switch { - Team.Impostor => (target.IsMadmate() || target.GetCustomRole().IsImpostorTeamV2()) && !target.Is(CustomRoles.Bloodlust), + Team.Impostor => (target.IsMadmate() || target.GetCustomRole().IsImpostorTeamV2() || Framer.FramedPlayers.Contains(target.PlayerId)) && !target.Is(CustomRoles.Bloodlust), Team.Neutral => target.GetCustomRole().IsNeutralTeamV2() || target.Is(CustomRoles.Bloodlust), Team.Crewmate => target.GetCustomRole().IsCrewmateTeamV2(), Team.None => target.Is(CustomRoles.GM) || target.Is(CountTypes.None) || target.Is(CountTypes.OutOfGame), @@ -1070,6 +1070,7 @@ static class ExtendedPlayerControl public static Team GetTeam(this PlayerControl target) { + if (Framer.FramedPlayers.Contains(target.PlayerId)) return Team.Impostor; var subRoles = target.GetCustomSubRoles(); if (target.Is(CustomRoles.Bloodlust) || subRoles.Any(x => x.IsConverted())) return Team.Neutral; if (subRoles.Contains(CustomRoles.Madmate)) return Team.Impostor; diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 7715b0f83..0452e8488 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -64,7 +64,7 @@ public class PlayerState(byte playerId) public CountTypes countTypes = CountTypes.Crew; public PlainShipRoom LastRoom; public CustomRoles MainRole = CustomRoles.NotAssigned; - public (DateTime TIMESTAMP, byte ID) RealKiller = (DateTime.MinValue, byte.MaxValue); + public (DateTime TimeStamp, byte ID) RealKiller = (DateTime.MinValue, byte.MaxValue); public RoleBase Role = new VanillaRole(); public List SubRoles = []; public Dictionary TargetColorData = []; @@ -93,6 +93,9 @@ public class PlayerState(byte playerId) }; } + if (CustomTeamManager.GetCustomTeam(PlayerId) != null && !CustomTeamManager.IsSettingEnabledForPlayerTeam(PlayerId, CTAOption.WinWithOriginalTeam)) + countTypes = CountTypes.CustomTeam; + SubRoles.ForEach(SetAddonCountTypes); Role = role.GetRoleClass(); @@ -283,7 +286,7 @@ public class PlayerState(byte playerId) public void InitTask(PlayerControl player) => taskState.Init(player); public void UpdateTask(PlayerControl player) => taskState.Update(player); - public byte GetRealKiller() => IsDead && RealKiller.TIMESTAMP != DateTime.MinValue ? RealKiller.ID : byte.MaxValue; + public byte GetRealKiller() => IsDead && RealKiller.TimeStamp != DateTime.MinValue ? RealKiller.ID : byte.MaxValue; public int GetKillCount(bool ExcludeSelfKill = false) => Main.PlayerStates.Values.Where(state => !(ExcludeSelfKill && state.PlayerId == PlayerId) && state.GetRealKiller() == PlayerId).ToArray().Length; } diff --git a/Modules/GhostRolesManager.cs b/Modules/GhostRolesManager.cs index 6a6daf93f..eb71031a4 100644 --- a/Modules/GhostRolesManager.cs +++ b/Modules/GhostRolesManager.cs @@ -93,7 +93,7 @@ namespace EHR.Modules { if (Options.CurrentGameMode != CustomGameMode.Standard) return false; if (AssignedGhostRoles.Count >= GhostRoles.Count) return false; - if (pc.IsAlive() || pc.GetCountTypes() is CountTypes.None or CountTypes.OutOfGame || pc.Is(CustomRoles.EvilSpirit)) return false; + if (pc.IsAlive() || pc.GetCountTypes() is CountTypes.None or CountTypes.OutOfGame || pc.Is(CustomRoles.EvilSpirit) || pc.Is(CustomRoles.Backstabber)) return false; var suitableRole = GetSuitableGhostRole(pc); return suitableRole switch diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 070c528ca..686a1fb5a 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -243,7 +243,6 @@ public static class Options public static OptionItem VindicatorHideVote; public static OptionItem DoctorTaskCompletedBatteryCharge; public static OptionItem TrapperBlockMoveTime; - public static OptionItem DetectiveCanknowKiller; public static OptionItem TransporterTeleportMax; public static OptionItem CanTerroristSuicideWin; public static OptionItem InnocentCanWinByImp; diff --git a/Modules/RPC.cs b/Modules/RPC.cs index cc74fd072..29d35636b 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -1115,7 +1115,7 @@ internal static class RPC public static void SetRealKiller(byte targetId, byte killerId) { var state = Main.PlayerStates[targetId]; - state.RealKiller.TIMESTAMP = DateTime.Now; + state.RealKiller.TimeStamp = DateTime.Now; state.RealKiller.ID = killerId; if (!AmongUsClient.Instance.AmHost) return; diff --git a/Modules/RoleBasisChanger.cs b/Modules/RoleBasisChanger.cs index fce5ca896..43707d8ed 100644 --- a/Modules/RoleBasisChanger.cs +++ b/Modules/RoleBasisChanger.cs @@ -1,4 +1,4 @@ -using AmongUs.GameOptions; +/*using AmongUs.GameOptions; using HarmonyLib; using Hazel; using InnerNet; @@ -98,4 +98,5 @@ namespace EHR.Modules } } } -} \ No newline at end of file +}*/ + diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 3674a23fa..21ec4f9c6 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -680,6 +680,7 @@ public static class Utils case CustomRoles.SchrodingersCat: case CustomRoles.Shifter: case CustomRoles.Impartial: + case CustomRoles.Backstabber: case CustomRoles.Predator: case CustomRoles.Reckless: case CustomRoles.WeaponMaster: @@ -1797,16 +1798,11 @@ public static class Utils public static void NotifyRoles(bool isForMeeting = false, PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool NoCache = false, bool ForceLoop = false, bool CamouflageIsForMeeting = false, bool GuesserIsForMeeting = false, bool MushroomMixup = false) { - //if (Options.DeepLowLoad.GetBool()) await Task.Run(() => { DoNotifyRoles(isForMeeting, SpecifySeer, NoCache, ForceLoop, CamouflageIsForMeeting, GuesserIsForMeeting); }); - /*else */ - if (!SetUpRoleTextPatch.IsInIntro && ((SpecifySeer != null && SpecifySeer.IsModClient()) || !AmongUsClient.Instance.AmHost || Main.AllPlayerControls == null || (GameStates.IsMeeting && !isForMeeting) || GameStates.IsLobby)) return; - - if (SetUpRoleTextPatch.IsInIntro || isForMeeting || CamouflageIsForMeeting || GuesserIsForMeeting || MushroomMixup) _ = DoNotifyRoles(isForMeeting, SpecifySeer, SpecifyTarget, NoCache, ForceLoop, CamouflageIsForMeeting, GuesserIsForMeeting, MushroomMixup); - else Main.Instance.StartCoroutine(DoNotifyRoles(false, SpecifySeer, SpecifyTarget, NoCache, ForceLoop)); + DoNotifyRoles(isForMeeting, SpecifySeer, SpecifyTarget, NoCache, ForceLoop, CamouflageIsForMeeting, GuesserIsForMeeting, MushroomMixup); } - public static System.Collections.IEnumerator DoNotifyRoles(bool isForMeeting = false, PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool NoCache = false, bool ForceLoop = false, bool CamouflageIsForMeeting = false, bool GuesserIsForMeeting = false, bool MushroomMixup = false) + public static void DoNotifyRoles(bool isForMeeting = false, PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool NoCache = false, bool ForceLoop = false, bool CamouflageIsForMeeting = false, bool GuesserIsForMeeting = false, bool MushroomMixup = false) { PlayerControl[] seerList = SpecifySeer != null ? [SpecifySeer] : Main.AllPlayerControls; PlayerControl[] targetList = SpecifyTarget != null ? [SpecifyTarget] : Main.AllPlayerControls; @@ -1816,8 +1812,6 @@ public static class Utils // seer: Players who can see changes made here // target: Players subject to changes that seer can see - int i = 0; - bool instant = SetUpRoleTextPatch.IsInIntro || isForMeeting || CamouflageIsForMeeting || GuesserIsForMeeting || MushroomMixup; foreach (PlayerControl seer in seerList) { try @@ -2293,12 +2287,13 @@ public static class Utils { Logger.Error($"Error for {seer.GetNameWithRole()}: {ex}", "NR"); } - - i++; - if (i % 3 == 0 && !instant) yield return null; } - Logger.Info($" Seers: {seerLogInfo.ToString().TrimEnd(',', ' ')} ---- Targets: {targetLogInfo.ToString().TrimEnd(',', ' ')}", "NR"); + string seers = seerLogInfo.ToString().TrimEnd(',', ' '); + string targets = targetLogInfo.ToString().TrimEnd(',', ' '); + if (seers.Length == 0) seers = "\u2205"; + if (targets.Length == 0) targets = "\u2205"; + Logger.Info($" Seers: {seers} ---- Targets: {targets}", "NR"); } public static void MarkEveryoneDirtySettings() diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 150349c19..ef0009476 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -177,6 +177,10 @@ class GameEndChecker WinnerIds.Add(pc.PlayerId); AdditionalWinnerTeams.Add(AdditionalWinners.Impartial); break; + case CustomRoles.Backstabber when (Main.PlayerStates[pc.PlayerId].Role as Backstabber).CheckWin(): + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Backstabber); + break; case CustomRoles.Predator when (Main.PlayerStates[pc.PlayerId].Role as Predator).IsWon: WinnerIds.Add(pc.PlayerId); AdditionalWinnerTeams.Add(AdditionalWinners.Predator); @@ -405,7 +409,7 @@ class GameEndChecker if ((!role.IsNK() && role != CustomRoles.Bloodlust) || role.IsMadmate() || role is CustomRoles.Sidekick) continue; var countTypes = role.GetCountTypes(); - if (countTypes is CountTypes.Crew or CountTypes.Impostor or CountTypes.None or CountTypes.OutOfGame) continue; + if (countTypes is CountTypes.Crew or CountTypes.Impostor or CountTypes.None or CountTypes.OutOfGame or CountTypes.CustomTeam) continue; CustomRoles? keyRole = role.IsRecruitingRole() ? null : role; CustomWinner keyWinner = (CustomWinner)role; diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index c503c7881..ae2303d25 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -18,7 +18,7 @@ class SetUpRoleTextPatch { // After showing team for non-modded clients, update player names. IsInIntro = false; - _ = Utils.DoNotifyRoles(NoCache: true); + Utils.DoNotifyRoles(NoCache: true); var lp = PlayerControl.LocalPlayer; diff --git a/Patches/OutroPatch.cs b/Patches/OutroPatch.cs index 345df41b5..1edc19d01 100644 --- a/Patches/OutroPatch.cs +++ b/Patches/OutroPatch.cs @@ -56,9 +56,9 @@ class EndGamePatch } var sb = new StringBuilder(GetString("KillLog") + ":"); - foreach ((byte key, PlayerState value) in Main.PlayerStates.OrderBy(x => x.Value.RealKiller.TIMESTAMP.Ticks)) + foreach ((byte key, PlayerState value) in Main.PlayerStates.OrderBy(x => x.Value.RealKiller.TimeStamp.Ticks)) { - var date = value.RealKiller.TIMESTAMP; + var date = value.RealKiller.TimeStamp; if (date == DateTime.MinValue) continue; var killerId = value.GetRealKiller(); var gmIsFM = Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.MoveAndStop; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 160bb87ed..d54ac1dd4 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1408,6 +1408,9 @@ "PsychicCanSeeNum": "Max number of red names", "PsychicFresh": "New red names every meeting", "DetectiveCanknowKiller": "Can find the killer's role", + "DetectiveCanknowDeathReason": "Can find the cause of death", + "DetectiveCanknowAddons": "Can find the dead player's add-ons", + "DetectiveCanknowKillTime": "Can find how long ago the player died", "ParanoiaVentCooldown": "Vent Cooldown", "ParanoiaNumOfUseButton": "Max number of portable buttons", "EveryOneKnowSuperStar": "Everyone knows the Super Star", @@ -1898,6 +1901,9 @@ "DetectiveNoticeVictim": "According to your investigation,\\nthe victim ({0}) had the role [{1}]", "DetectiveNoticeKiller": "\\nThe killer's role is {0}", "DetectiveNoticeKillerNotFound": "The Detective couldn't find evidence leading to a murderer, this is most likely suicide.", + "DetectiveNoticeDeathReason": "\\nThe victim's cause of death is {0}", + "DetectiveNoticeAddons": "\\nThe victim's add-ons are: {0}", + "DetectiveNoticeKillTime": "\\nThe victim died {0} seconds ago", "SleuthMsg": "{0}'s role is: {1}", "GodNoticeAlive": "During the meeting, each player felt a revelation from heaven, and it turned out that God is still alive!", "WorkaholicAdviceAlive": "It's not recommended to kill or vote {0} out. Doing so will help them finish their tasks quicker.", @@ -3605,6 +3611,16 @@ "LibrarianNormalMode": "Normal", "CanKillWhileShifted": "Can Kill While Shapeshifting", "Shapeshift": "Shapeshift", + "Framer": "Framer", + "FramerInfo": "Frame Other Players", + "FramerInfoLong": "(Impostors):\\nAs the Framer, single click with your kill button on someone to frame them, making the game think they're on the impostor team. Double click to kill normally.", + "Framer.TargetFramedNotify": "The game now thinks {0} is an <#ff1919>impostor", + "Backstabber": "Backstabber", + "BackstabberInfo": "Stab a Team in the Back", + "BackstabberInfoLong": "(Neutral):\\nAs the Backstabber, you can optionally kill 1 player. If you don't kill anyone, you will win with the crewmates. If you kill an Impostor or a Madmate, you will win with the winning Neutral Killer. If you kill anyone other than an Impostor or a Madmate, you will win with the Impostors.", + "Backstabber.MurderNotify": "Your kill target was {0}\\nThis means, you'll win with {1}", + "BackstabberTeam.Imp": "the <#ff1919>Impostors", + "BackstabberTeam.NK": "a <#ffab1b>Neutral Killer", "Postman": "Postman", "PostmanInfo": "Deliver Mail to Everyone!", "PostmanInfoLong": "(Neutral):\\nAs the Postman, your job is to deliver mail to everyone. You'll always have a target, and you need to click your kill button on that target to deliver the mail to them. When you've delivered mail to everyone, your win condition is satisfied. You'll win with the winning team, no matter what happens.", diff --git a/Roles/Crewmate/Detective.cs b/Roles/Crewmate/Detective.cs index 146e0aa59..869fc11f3 100644 --- a/Roles/Crewmate/Detective.cs +++ b/Roles/Crewmate/Detective.cs @@ -1,9 +1,16 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; namespace EHR.Crewmate { internal class Detective : RoleBase { + public static OptionItem DetectiveCanknowKiller; + public static OptionItem DetectiveCanknowDeathReason; + public static OptionItem DetectiveCanknowAddons; + public static OptionItem DetectiveCanknowKillTime; + public static Dictionary DetectiveNotify = []; public override bool IsEnable => false; @@ -11,7 +18,13 @@ namespace EHR.Crewmate public override void SetupCustomOption() { Options.SetupRoleOptions(6600, TabGroup.CrewmateRoles, CustomRoles.Detective); - Options.DetectiveCanknowKiller = new BooleanOptionItem(6610, "DetectiveCanknowKiller", true, TabGroup.CrewmateRoles) + DetectiveCanknowKiller = new BooleanOptionItem(6610, "DetectiveCanknowKiller", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detective]); + DetectiveCanknowDeathReason = new BooleanOptionItem(6611, "DetectiveCanknowDeathReason", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detective]); + DetectiveCanknowAddons = new BooleanOptionItem(6612, "DetectiveCanknowAddons", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detective]); + DetectiveCanknowKillTime = new BooleanOptionItem(6613, "DetectiveCanknowKillTime", true, TabGroup.CrewmateRoles) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detective]); } @@ -26,13 +39,32 @@ namespace EHR.Crewmate public static void OnReportDeadBody(PlayerControl player, PlayerControl tpc) { string msg = string.Format(Translator.GetString("DetectiveNoticeVictim"), tpc.GetRealName(), tpc.GetCustomRole().ToColoredString()); - if (Options.DetectiveCanknowKiller.GetBool()) + + if (DetectiveCanknowKiller.GetBool()) { var realKiller = tpc.GetRealKiller(); if (realKiller == null) msg += ";" + Translator.GetString("DetectiveNoticeKillerNotFound"); else msg += ";" + string.Format(Translator.GetString("DetectiveNoticeKiller"), realKiller.GetCustomRole().ToColoredString()); } + if (DetectiveCanknowDeathReason.GetBool()) + { + msg += ";" + string.Format(Translator.GetString("DetectiveNoticeDeathReason"), Translator.GetString($"DeathReason.{Main.PlayerStates[tpc.PlayerId].deathReason}")); + } + + if (DetectiveCanknowAddons.GetBool()) + { + msg += ";" + string.Format(Translator.GetString("DetectiveNoticeAddons"), string.Join(", ", tpc.GetCustomSubRoles().Select(x => x.ToColoredString()))); + } + + if (DetectiveCanknowKillTime.GetBool()) + { + var deathTimeStamp = Main.PlayerStates[tpc.PlayerId].RealKiller.TimeStamp; + var now = DateTime.UtcNow; + var timeSpanSeconds = (now - deathTimeStamp).TotalSeconds; + msg += ";" + string.Format(Translator.GetString("DetectiveNoticeKillTime"), (int)timeSpanSeconds); + } + DetectiveNotify.Add(player.PlayerId, msg); } } diff --git a/Roles/Crewmate/Psychic.cs b/Roles/Crewmate/Psychic.cs index b2dfc2876..3d717febc 100644 --- a/Roles/Crewmate/Psychic.cs +++ b/Roles/Crewmate/Psychic.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; +using EHR.Impostor; using EHR.Modules; using Hazel; using static EHR.Options; @@ -92,7 +93,7 @@ public class Psychic : RoleBase if (!IsEnable || !AmongUsClient.Instance.AmHost) return; List BadListPc = Main.AllAlivePlayerControls.Where(x => - (x.Is(CustomRoleTypes.Impostor) && !x.Is(CustomRoles.Trickster)) || x.Is(CustomRoles.Madmate) || x.Is(CustomRoles.Rascal) || x.Is(CustomRoles.Recruit) || x.Is(CustomRoles.Charmed) || x.Is(CustomRoles.Contagious) || + (x.Is(CustomRoleTypes.Impostor) && !x.Is(CustomRoles.Trickster)) || x.Is(CustomRoles.Madmate) || x.Is(CustomRoles.Rascal) || Framer.FramedPlayers.Contains(x.PlayerId) || x.IsConverted() || (x.GetCustomRole().GetCrewmateRoleCategory() == RoleOptionType.Crewmate_Killing && CkshowEvil.GetBool()) || (x.GetCustomRole().GetNeutralRoleCategory() == RoleOptionType.Neutral_Evil && NEshowEvil.GetBool()) || (x.GetCustomRole().GetNeutralRoleCategory() == RoleOptionType.Neutral_Benign && NBshowEvil.GetBool()) diff --git a/Roles/Impostor/Framer.cs b/Roles/Impostor/Framer.cs new file mode 100644 index 000000000..7086165b5 --- /dev/null +++ b/Roles/Impostor/Framer.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; + +namespace EHR.Impostor +{ + public class Framer : RoleBase + { + public static bool On; + public static readonly HashSet FramedPlayers = []; + + private static OptionItem AbilityUseLimit; + private static OptionItem FramerAbilityUseGainWithEachKill; + + public override bool IsEnable => On; + + public override void SetupCustomOption() + { + Options.SetupRoleOptions(647196, TabGroup.ImpostorRoles, CustomRoles.Framer); + AbilityUseLimit = new IntegerOptionItem(647198, "AbilityUseLimit", new(0, 5, 1), 0, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Framer]) + .SetValueFormat(OptionFormat.Times); + FramerAbilityUseGainWithEachKill = new FloatOptionItem(647199, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.4f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Framer]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + On = false; + FramedPlayers.Clear(); + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.GetAbilityUseLimit() < 1f) return true; + return killer.CheckDoubleTrigger(target, () => + { + FramedPlayers.Add(target.PlayerId); + killer.SetKillCooldown(); + killer.Notify(string.Format(Translator.GetString("Framer.TargetFramedNotify"), target.PlayerId.ColoredPlayerName())); + killer.RpcRemoveAbilityUse(); + }); + } + } +} \ No newline at end of file diff --git a/Roles/Neutral/Backstabber.cs b/Roles/Neutral/Backstabber.cs new file mode 100644 index 000000000..0241de670 --- /dev/null +++ b/Roles/Neutral/Backstabber.cs @@ -0,0 +1,83 @@ +using AmongUs.GameOptions; + +namespace EHR.Neutral +{ + public class Backstabber : RoleBase + { + public static bool On; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + private WinningTeam Team; + + public override bool IsEnable => On; + + public override void SetupCustomOption() + { + int id = 649100; + Options.SetupRoleOptions(id++, TabGroup.NeutralRoles, CustomRoles.Backstabber); + KillCooldown = new FloatOptionItem(++id, "KillCooldown", new(0f, 180f, 0.5f), 15f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Backstabber]) + .SetValueFormat(OptionFormat.Seconds); + CanVent = new BooleanOptionItem(++id, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Backstabber]); + HasImpostorVision = new BooleanOptionItem(++id, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Backstabber]); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + Team = WinningTeam.Crew; + playerId.SetAbilityUseLimit(1); + } + + 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) + { + return killer.GetAbilityUseLimit() > 0f; + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + Team targetTeam = target.GetTeam(); + Team = targetTeam switch + { + EHR.Team.Impostor => WinningTeam.NK, + _ => WinningTeam.Imp + }; + + killer.RpcRemoveAbilityUse(); + killer.Notify(string.Format(Translator.GetString("Backstabber.MurderNotify"), Utils.ColorString(targetTeam.GetTeamColor(), Translator.GetString(targetTeam.ToString())), Translator.GetString($"BackstabberTeam.{Team}")), 10f); + } + + public bool CheckWin() + { + return CustomWinnerHolder.WinnerTeam switch + { + CustomWinner.Crewmate => Team == WinningTeam.Crew, + CustomWinner.Impostor => Team == WinningTeam.Imp, + CustomWinner.Neutrals => Team == WinningTeam.NK, + CustomWinner.None => false, + CustomWinner.Default or CustomWinner.Draw or CustomWinner.Error => true, + _ => Team == WinningTeam.NK + }; + } + + enum WinningTeam + { + Crew, + Imp, + NK + } + } +} \ No newline at end of file diff --git a/Roles/Neutral/Jackal.cs b/Roles/Neutral/Jackal.cs index cf71a307a..2116f1129 100644 --- a/Roles/Neutral/Jackal.cs +++ b/Roles/Neutral/Jackal.cs @@ -28,6 +28,7 @@ public class Jackal : RoleBase private static OptionItem PromotedSKCanRecruit; public static bool On; + private byte SidekickId; public override bool IsEnable => PlayerIdList.Count > 0; @@ -88,6 +89,7 @@ public class Jackal : RoleBase On = true; PlayerIdList.Add(playerId); playerId.SetAbilityUseLimit(1); + SidekickId = byte.MaxValue; } public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); @@ -117,6 +119,7 @@ public class Jackal : RoleBase killer.RpcRemoveAbilityUse(); target.RpcSetCustomRole(targetRoleType == RoleTypes.Shapeshifter ? CustomRoles.Recruit : CustomRoles.Sidekick); // if (needBasisChange) target.ChangeRoleBasis(RoleTypes.Impostor); + SidekickId = target.PlayerId; Main.ResetCamPlayerList.Add(target.PlayerId); @@ -146,8 +149,8 @@ public class Jackal : RoleBase public override void OnFixedUpdate(PlayerControl pc) { if (!SKPromotesToJackal.GetBool() || pc.IsAlive()) return; - var sk = Main.AllPlayerControls.FirstOrDefault(x => x.Is(CustomRoles.Sidekick)); - if (sk == null) return; + var sk = SidekickId.GetPlayer(); + if (sk == null || !sk.Is(CustomRoles.Sidekick)) return; sk.RpcSetCustomRole(CustomRoles.Jackal); if (!PromotedSKCanRecruit.GetBool()) sk.SetAbilityUseLimit(0); } diff --git a/Roles/VanillaRole.cs b/Roles/VanillaRole.cs index 4fc265c8e..9ae2c3cb1 100644 --- a/Roles/VanillaRole.cs +++ b/Roles/VanillaRole.cs @@ -3,6 +3,7 @@ internal class VanillaRole : RoleBase { public static bool On; + public override bool IsEnable => On; public override void SetupCustomOption() diff --git a/main.cs b/main.cs index aaa7492c9..72db8f64a 100644 --- a/main.cs +++ b/main.cs @@ -180,7 +180,7 @@ public class Main : BasePlugin int i = 0; foreach (var pc in PlayerControl.AllPlayerControls) { - if (pc == null) continue; + if (pc == null || pc.PlayerId == 255) continue; result[i++] = pc; } @@ -200,7 +200,7 @@ public class Main : BasePlugin int i = 0; foreach (var pc in PlayerControl.AllPlayerControls) { - if (pc == null || !pc.IsAlive() || pc.Data.Disconnected || Pelican.IsEaten(pc.PlayerId)) continue; + if (pc == null || pc.PlayerId == 255 || !pc.IsAlive() || pc.Data.Disconnected || Pelican.IsEaten(pc.PlayerId)) continue; result[i++] = pc; } @@ -491,6 +491,7 @@ public class Main : BasePlugin { CustomRoles.SchrodingersCat, "#616161" }, { CustomRoles.Shifter, "#777777" }, { CustomRoles.Impartial, "#4287f5" }, + { CustomRoles.Backstabber, "#fcba03" }, { CustomRoles.Predator, "#c73906" }, { CustomRoles.Reckless, "#6e000d" }, { CustomRoles.Magician, "#BF5FFF" }, @@ -864,6 +865,7 @@ public enum AdditionalWinners Maverick = CustomRoles.Maverick, Postman = CustomRoles.Postman, Impartial = CustomRoles.Impartial, + Backstabber = CustomRoles.Backstabber, Predator = CustomRoles.Predator, SoulHunter = CustomRoles.SoulHunter, SchrodingersCat = CustomRoles.SchrodingersCat