diff --git a/EHR.csproj b/EHR.csproj
index a31768c5f..4b919b20a 100644
--- a/EHR.csproj
+++ b/EHR.csproj
@@ -7,7 +7,7 @@
EHR
Gurge44
preview
- E:\Program Files\AmongUs
+ C:\Program Files\Epic Games\AmongUs
true
True
$(MSBuildProjectName)
diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs
index 78ce62107..b6ef499e5 100644
--- a/Modules/ExtendedPlayerControl.cs
+++ b/Modules/ExtendedPlayerControl.cs
@@ -307,8 +307,20 @@ static class ExtendedPlayerControl
var newRoleType = newCustomRole.GetRoleTypes();
RoleTypes remeberRoleType;
- if (Options.CurrentGameMode == CustomGameMode.Speedrun && newCustomRole == CustomRoles.Runner)
- newRoleType = SpeedrunManager.CanKill.Contains(player.PlayerId) ? RoleTypes.Impostor : RoleTypes.Crewmate;
+ newRoleType = Options.CurrentGameMode switch
+ {
+ CustomGameMode.Speedrun when newCustomRole == CustomRoles.Runner => SpeedrunManager.CanKill.Contains(player.PlayerId) ? RoleTypes.Impostor : RoleTypes.Crewmate,
+ CustomGameMode.Standard when StartGameHostPatch.BasisChangingAddons.FindFirst(x => x.Value.Contains(player.PlayerId), out var kvp) => kvp.Key switch
+ {
+ CustomRoles.Bloodlust when newRoleType is RoleTypes.Crewmate or RoleTypes.Engineer or RoleTypes.Scientist => RoleTypes.Impostor,
+ CustomRoles.Nimble when newRoleType is RoleTypes.Crewmate or RoleTypes.Scientist => RoleTypes.Engineer,
+ CustomRoles.Physicist when newRoleType == RoleTypes.Crewmate => RoleTypes.Scientist,
+ CustomRoles.Finder when newRoleType is RoleTypes.Crewmate or RoleTypes.Scientist => RoleTypes.Tracker,
+ CustomRoles.Noisy when newRoleType == RoleTypes.Crewmate => RoleTypes.Noisemaker,
+ _ => newRoleType
+ },
+ _ => newRoleType
+ };
var oldRoleIsDesync = playerRole.IsDesyncRole();
var newRoleIsDesync = newCustomRole.IsDesyncRole();
diff --git a/Modules/GameState.cs b/Modules/GameState.cs
index 0067b77fc..cbee46fee 100644
--- a/Modules/GameState.cs
+++ b/Modules/GameState.cs
@@ -140,7 +140,7 @@ public class PlayerState(byte playerId)
if (Lyncher.On) Lyncher.Instances.ForEach(x => x.OnRoleChange(PlayerId));
if (!role.Is(Team.Impostor)) SubRoles.ToArray().DoIf(x => x.IsImpOnlyAddon(), RemoveSubRole);
- if (role is CustomRoles.Sidekick or CustomRoles.Necromancer or CustomRoles.Deathknight or CustomRoles.Refugee) RemoveSubRole(CustomRoles.Nimble);
+ if (role is CustomRoles.Sidekick or CustomRoles.Necromancer or CustomRoles.Deathknight or CustomRoles.Refugee) SubRoles.ToArray().DoIf(StartGameHostPatch.BasisChangingAddons.ContainsKey, RemoveSubRole);
if (role == CustomRoles.Sidekick && Jackal.Instances.FindFirst(x => x.SidekickId == byte.MaxValue || x.SidekickId.GetPlayer() == null, out var jackal))
jackal.SidekickId = PlayerId;
diff --git a/Modules/GhostRolesManager.cs b/Modules/GhostRolesManager.cs
index bd0ecd271..aac1818f6 100644
--- a/Modules/GhostRolesManager.cs
+++ b/Modules/GhostRolesManager.cs
@@ -18,7 +18,7 @@ namespace EHR.Modules
public static void Initialize()
{
AssignedGhostRoles = [];
- GhostRoles = Enum.GetValues().Where(x => x != CustomRoles.EvilSpirit && x.IsGhostRole() && x.IsEnable()).ToList();
+ GhostRoles = Enum.GetValues().Where(x => x != CustomRoles.EvilSpirit && x.IsGhostRole() && x.IsEnable() && x.GetMode() != 0).ToList();
Logger.Msg($"Ghost roles: {GhostRoles.Join()}", "GhostRoles");
Haunter.AllHauntedPlayers = [];
diff --git a/Modules/Utils.cs b/Modules/Utils.cs
index 4e822bdb0..57e162cc6 100644
--- a/Modules/Utils.cs
+++ b/Modules/Utils.cs
@@ -2027,10 +2027,10 @@ public static class Utils
? SeerRealName
: seerTeam switch
{
- Team.Impostor when seer.IsMadmate() => $"{GetString("YouAreMadmate")}\n{(showLongInfo ? longInfo : seer.GetRoleInfo()) + mHelp}",
+ Team.Impostor when seer.IsMadmate() => $"{GetString("YouAreMadmate")}\n{(showLongInfo ? longInfo : seer.GetRoleInfo()) + mHelp}",
Team.Impostor => $"\n{(showLongInfo ? longInfo : seer.GetRoleInfo()) + mHelp}",
- Team.Crewmate => $"{GetString("YouAreCrewmate")}\n{(showLongInfo ? longInfo : seer.GetRoleInfo()) + mHelp}",
- Team.Neutral => $"{GetString("YouAreNeutral")}\n{(showLongInfo ? longInfo : seer.GetRoleInfo()) + mHelp}",
+ Team.Crewmate => $"{GetString("YouAreCrewmate")}\n{(showLongInfo ? longInfo : seer.GetRoleInfo()) + mHelp}",
+ Team.Neutral => $"{GetString("YouAreNeutral")}\n{(showLongInfo ? longInfo : seer.GetRoleInfo()) + mHelp}",
_ => SeerRealName
};
}
diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs
index 585e461a6..c5c1fde00 100644
--- a/Patches/ChatCommandPatch.cs
+++ b/Patches/ChatCommandPatch.cs
@@ -103,6 +103,8 @@ internal static class ChatCommands
public static Dictionary> DraftRoles = [];
public static Dictionary DraftResult = [];
+ private static HashSet ReadyPlayers = [];
+
public static void LoadCommands()
{
AllCommands =
@@ -170,6 +172,8 @@ internal static class ChatCommands
new(["unmute", "размут"], "{id}", GetString("CommandDescription.Unmute"), Command.UsageLevels.Host, Command.UsageTimes.Always, UnmuteCommand, true, [GetString("CommandArgs.Unmute.Id")]),
new(["draftstart", "ds", "драфтстарт"], "", GetString("CommandDescription.DraftStart"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, DraftStartCommand, true),
new(["draft", "драфт"], "{number}", GetString("CommandDescription.Draft"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, DraftCommand, true, [GetString("CommandArgs.Draft.Number")]),
+ new(["readycheck", "rc", "готов"], "", GetString("CommandDescription.ReadyCheck"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, ReadyCheckCommand, true),
+ new(["ready", "готов", "г"], "", GetString("CommandDescription.Ready"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, ReadyCommand, true),
// Commands with action handled elsewhere
new(["shoot", "guess", "bet", "bt", "st", "угадать", "бт"], "{id} {role}", GetString("CommandDescription.Guess"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _, _) => { }, true, [GetString("CommandArgs.Guess.Id"), GetString("CommandArgs.Guess.Role")]),
@@ -298,6 +302,35 @@ internal static class ChatCommands
// ---------------------------------------------------------------------------------------------------------------------------------------------
+ private static void ReadyCheckCommand(ChatController __instance, PlayerControl player, string text, string[] args)
+ {
+ Utils.SendMessage(GetString("ReadyCheckMessage"), title: GetString("ReadyCheckTitle"));
+ ReadyPlayers = [player.PlayerId];
+ Main.Instance.StartCoroutine(Countdown());
+ return;
+
+ System.Collections.IEnumerator Countdown()
+ {
+ var timer = 30f;
+ while (timer > 0f)
+ {
+ if (!GameStates.IsLobby) yield break;
+ if (Main.AllPlayerControls.Select(x => x.PlayerId).All(ReadyPlayers.Contains)) break;
+ timer -= Time.deltaTime;
+ yield return null;
+ }
+
+ var notReadyPlayers = Main.AllPlayerControls.Select(x => x.PlayerId).Except(ReadyPlayers).ToArray();
+ if (notReadyPlayers.Length == 0) Utils.SendMessage("\n", player.PlayerId, GetString("EveryoneReadyTitle"));
+ else Utils.SendMessage(string.Join(", ", notReadyPlayers.Select(x => x.ColoredPlayerName())), player.PlayerId, string.Format(GetString("PlayersNotReadyTitle"), notReadyPlayers.Length));
+ }
+ }
+
+ private static void ReadyCommand(ChatController __instance, PlayerControl player, string text, string[] args)
+ {
+ ReadyPlayers.Add(player.PlayerId);
+ }
+
private static void DraftStartCommand(ChatController __instance, PlayerControl player, string text, string[] args)
{
DraftResult = [];
diff --git a/Patches/Crowded.cs b/Patches/Crowded.cs
index 546e24633..35ff62843 100644
--- a/Patches/Crowded.cs
+++ b/Patches/Crowded.cs
@@ -1,167 +1,13 @@
-using System;
-using AmongUs.GameOptions;
+using AmongUs.GameOptions;
using HarmonyLib;
-using TMPro;
-using UnityEngine;
-// All errors in this file are fake, the build succeeds
-// https://github.com/CrowdedMods/CrowdedMod/blob/master/src/CrowdedMod/Patches/CreateGameOptionsPatches.cs
+// https://github.com/CrowdedMods/CrowdedMod/blob/master/src/CrowdedMod
namespace EHR.Patches
{
internal static class Crowded
{
- public const int MaxPlayers = 127;
public const int MaxImpostors = 127 / 2;
- [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.Awake))]
- public static class CreateOptionsPickerAwake
- {
- public static void Postfix(CreateOptionsPicker __instance)
- {
- if (__instance.mode != SettingsMode.Host) return;
-
- {
- var firstButtonRenderer = __instance.MaxPlayerButtons[0];
- firstButtonRenderer.GetComponentInChildren().text = "-";
- firstButtonRenderer.enabled = false;
-
- var firstButtonButton = firstButtonRenderer.GetComponent();
- firstButtonButton.OnClick.RemoveAllListeners();
- firstButtonButton.OnClick.AddListener((Action)(() =>
- {
- for (var i = 1; i < 11; i++)
- {
- var playerButton = __instance.MaxPlayerButtons[i];
-
- var tmp = playerButton.GetComponentInChildren();
- var newValue = Mathf.Max(byte.Parse(tmp.text) - 10, byte.Parse(playerButton.name) - 2);
- tmp.text = newValue.ToString();
- }
-
- __instance.UpdateMaxPlayersButtons(__instance.GetTargetOptions());
- }));
- Object.Destroy(firstButtonRenderer);
-
- var lastButtonRenderer = __instance.MaxPlayerButtons[^1];
- lastButtonRenderer.GetComponentInChildren().text = "+";
- lastButtonRenderer.enabled = false;
-
- var lastButtonButton = lastButtonRenderer.GetComponent();
- lastButtonButton.OnClick.RemoveAllListeners();
- lastButtonButton.OnClick.AddListener((Action)(() =>
- {
- for (var i = 1; i < 11; i++)
- {
- var playerButton = __instance.MaxPlayerButtons[i];
-
- var tmp = playerButton.GetComponentInChildren();
- var newValue = Mathf.Min(byte.Parse(tmp.text) + 10,
- MaxPlayers - 14 + byte.Parse(playerButton.name));
- tmp.text = newValue.ToString();
- }
-
- __instance.UpdateMaxPlayersButtons(__instance.GetTargetOptions());
- }));
- Object.Destroy(lastButtonRenderer);
-
- for (var i = 1; i < 11; i++)
- {
- var playerButton = __instance.MaxPlayerButtons[i].GetComponent();
- var text = playerButton.GetComponentInChildren();
-
- playerButton.OnClick.RemoveAllListeners();
- playerButton.OnClick.AddListener((Action)(() =>
- {
- var maxPlayers = byte.Parse(text.text);
- var maxImp = Mathf.Min(__instance.GetTargetOptions().NumImpostors, maxPlayers / 2);
- __instance.GetTargetOptions().SetInt(Int32OptionNames.NumImpostors, maxImp);
- __instance.ImpostorButtons[1].TextMesh.text = maxImp.ToString();
- __instance.SetMaxPlayersButtons(maxPlayers);
- }));
- }
-
- foreach (var button in __instance.MaxPlayerButtons)
- {
- button.enabled = button.GetComponentInChildren().text == __instance.GetTargetOptions().MaxPlayers.ToString();
- }
- }
-
- {
- var secondButton = __instance.ImpostorButtons[1];
- secondButton.SpriteRenderer.enabled = false;
- Object.Destroy(secondButton.transform.FindChild("ConsoleHighlight").gameObject);
- Object.Destroy(secondButton.PassiveButton);
- Object.Destroy(secondButton.BoxCollider);
-
- var secondButtonText = secondButton.TextMesh;
- secondButtonText.text = __instance.GetTargetOptions().NumImpostors.ToString();
-
- var firstButton = __instance.ImpostorButtons[0];
- firstButton.SpriteRenderer.enabled = false;
- firstButton.TextMesh.text = "-";
-
- var firstPassiveButton = firstButton.PassiveButton;
- firstPassiveButton.OnClick.RemoveAllListeners();
- firstPassiveButton.OnClick.AddListener((Action)(() =>
- {
- var newVal = Mathf.Clamp(
- byte.Parse(secondButtonText.text) - 1,
- 1,
- __instance.GetTargetOptions().MaxPlayers / 2
- );
- __instance.SetImpostorButtons(newVal);
- secondButtonText.text = newVal.ToString();
- }));
-
- var thirdButton = __instance.ImpostorButtons[2];
- thirdButton.SpriteRenderer.enabled = false;
- thirdButton.TextMesh.text = "+";
-
- var thirdPassiveButton = thirdButton.PassiveButton;
- thirdPassiveButton.OnClick.RemoveAllListeners();
- thirdPassiveButton.OnClick.AddListener((Action)(() =>
- {
- var newVal = Mathf.Clamp(
- byte.Parse(secondButtonText.text) + 1,
- 1,
- __instance.GetTargetOptions().MaxPlayers / 2
- );
- __instance.SetImpostorButtons(newVal);
- secondButtonText.text = newVal.ToString();
- }));
- }
- }
- }
-
- [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.UpdateMaxPlayersButtons))]
- public static class CreateOptionsPickerUpdateMaxPlayersButtons
- {
- public static bool Prefix(CreateOptionsPicker __instance, [HarmonyArgument(0)] IGameOptions opts)
- {
- if (__instance.CrewArea)
- {
- __instance.CrewArea.SetCrewSize(opts.MaxPlayers, opts.NumImpostors);
- }
-
- var selectedAsString = opts.MaxPlayers.ToString();
- for (var i = 1; i < __instance.MaxPlayerButtons.Count - 1; i++)
- {
- __instance.MaxPlayerButtons[i].enabled = __instance.MaxPlayerButtons[i].GetComponentInChildren().text == selectedAsString;
- }
-
- return false;
- }
- }
-
- [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.UpdateImpostorsButtons))]
- public static class CreateOptionsPickerUpdateImpostorsButtons
- {
- public static bool Prefix()
- {
- return false;
- }
- }
-
[HarmonyPatch(typeof(GameOptionsData), nameof(GameOptionsData.AreInvalid))]
public static class InvalidOptionsPatches
{
diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs
index a61abc560..7b3ffc36b 100644
--- a/Patches/OnGameStartedPatch.cs
+++ b/Patches/OnGameStartedPatch.cs
@@ -367,7 +367,7 @@ internal static class StartGameHostPatch
{
private static AmongUsClient AUClient;
- private static readonly Dictionary> BasisChangingAddons = [];
+ public static readonly Dictionary> BasisChangingAddons = [];
private static Dictionary RoleTypeNums = [];
private static readonly Dictionary DataDisconnected = [];
diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs
index a9a6cbd24..fc7f3fcbe 100644
--- a/Patches/PlayerControlPatch.cs
+++ b/Patches/PlayerControlPatch.cs
@@ -494,9 +494,6 @@ static class CheckMurderPatch
case CustomRoles.Medic:
Medic.IsDead(target);
break;
- case CustomRoles.Monarch when killer.Is(CustomRoles.Knighted):
- Notify("KnightedKillMonarch");
- return false;
case CustomRoles.Gambler when Gambler.IsShielded.ContainsKey(target.PlayerId):
Notify("SomeSortOfProtection");
killer.SetKillCooldown(time: 5f);
diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json
index 5faaf2f8c..3395a898c 100644
--- a/Resources/Lang/en_US.json
+++ b/Resources/Lang/en_US.json
@@ -1153,6 +1153,10 @@
"MenuTitle.GuesserModeRoles": "★ Roles and Add-ons for Guesser Mode ★",
"MenuTitle.CTA": "★ Custom Team Assignment ★",
"MenuTitle.AFKDetection": "★ AFK Detection ★",
+ "ReadyCheckMessage": "The host is checking how many players are ready to start the game.\\nPlease type /ready to let the host know you're not AFK.",
+ "ReadyCheckTitle": "<#ffff00>★ Ready Check ★",
+ "EveryoneReadyTitle": "<#00ff00>Everyone is ready!",
+ "PlayersNotReadyTitle": "<#ffff00><#ffab1b>{0} players are not ready! They are:",
"ShieldPersonDiedFirst": "Shield the person who got killed first in the last game",
"SidekickKnowOtherSidekick": "Sidekicks know each other",
"SidekickKnowOtherSidekickRole": "Sidekicks know the roles of other Sidekicks",
@@ -1701,7 +1705,6 @@
"RefugeeKillImpostor": "Renegades cannot kill Impostors",
"GAGuarded": "This player is shielded by the Guardian Angel",
"TraitorKillImpostor": "As the Traitor, you cannot kill Impostors",
- "KnightedKillMonarch": "As the Knighted player, you cannot kill the Monarch",
"RookieKillRoundOne": "As a Rookie, you cannot kill in the first round",
"SocialiteTarget": "The target of the Socialite cannot be killed\\nYou were added to their Guest List",
"BodyguardProtected": "The Bodyguard protected your target and died for them",
@@ -2256,6 +2259,8 @@
"CommandDescription.Unmute": "Unmute someone.",
"CommandDescription.DraftStart": "Start drafting, where players may choose their roles for the next game",
"CommandDescription.Draft": "Choose a role in the draft",
+ "CommandDescription.ReadyCheck": "Start a ready check (check who is AFK) (results displayed after 30s)",
+ "CommandDescription.Ready": "Mark yourself as ready",
"CommandArgs.ChangeSetting.Name": "The setting's name to change (leave blank to view all settings)",
"CommandArgs.ChangeSetting.UnknownValue": "(leave blank to view what to put here)",
"CommandArgs.Rename.Name": "Your new name",
diff --git a/Roles/Neutral/Enderman.cs b/Roles/Neutral/Enderman.cs
index 7b88b3b2d..3f45d607a 100644
--- a/Roles/Neutral/Enderman.cs
+++ b/Roles/Neutral/Enderman.cs
@@ -47,7 +47,7 @@ namespace EHR.Neutral
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat();
public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool();
- public override bool CanUseSabotage(PlayerControl pc) => pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool());
+ public override bool CanUseSabotage(PlayerControl pc) => base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()));
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
diff --git a/Roles/Neutral/Evolver.cs b/Roles/Neutral/Evolver.cs
index d7fe0a468..f237b3561 100644
--- a/Roles/Neutral/Evolver.cs
+++ b/Roles/Neutral/Evolver.cs
@@ -61,7 +61,7 @@ namespace EHR.Neutral
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Stats.KillCooldown;
public override bool CanUseImpostorVentButton(PlayerControl pc) => pc.inVent || Stats is { CanVent: true, VentUseLimit: > 0 };
- public override bool CanUseSabotage(PlayerControl pc) => Stats is { CanSabotage: true, SabotageUseLimit: > 0 };
+ public override bool CanUseSabotage(PlayerControl pc) => base.CanUseSabotage(pc) || Stats is { CanSabotage: true, SabotageUseLimit: > 0 };
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
{
diff --git a/Roles/Neutral/Hookshot.cs b/Roles/Neutral/Hookshot.cs
index 14f3c09b6..b51cd6acc 100644
--- a/Roles/Neutral/Hookshot.cs
+++ b/Roles/Neutral/Hookshot.cs
@@ -49,7 +49,7 @@ namespace EHR.Neutral
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat();
public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool();
- public override bool CanUseSabotage(PlayerControl pc) => pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool());
+ public override bool CanUseSabotage(PlayerControl pc) => base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()));
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
diff --git a/Roles/Neutral/Innocent.cs b/Roles/Neutral/Innocent.cs
index 8e4e58a79..b8f3ebbe2 100644
--- a/Roles/Neutral/Innocent.cs
+++ b/Roles/Neutral/Innocent.cs
@@ -31,7 +31,7 @@ namespace EHR.Neutral
public override bool CanUseSabotage(PlayerControl pc)
{
- return false;
+ return pc.Is(CustomRoles.Mischievous);
}
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
diff --git a/Roles/Neutral/Jackal.cs b/Roles/Neutral/Jackal.cs
index 95ffd872e..d838b930c 100644
--- a/Roles/Neutral/Jackal.cs
+++ b/Roles/Neutral/Jackal.cs
@@ -96,7 +96,7 @@ public class Jackal : RoleBase
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat();
public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool();
- public override bool CanUseSabotage(PlayerControl pc) => CanSabotage.GetBool() && pc.IsAlive();
+ public override bool CanUseSabotage(PlayerControl pc) => base.CanUseSabotage(pc) || (CanSabotage.GetBool() && pc.IsAlive());
public override void SetButtonTexts(HudManager hud, byte id)
{
@@ -116,11 +116,11 @@ public class Jackal : RoleBase
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
- if (killer.GetAbilityUseLimit() < 1 || !CanBeSidekick(target, out var needBasisChange, out var targetRoleType)) return true;
+ if (killer.GetAbilityUseLimit() < 1 || !CanBeSidekick(target)) return true;
killer.RpcRemoveAbilityUse();
- target.RpcSetCustomRole(targetRoleType == RoleTypes.Shapeshifter ? CustomRoles.Recruit : CustomRoles.Sidekick);
- if (needBasisChange) target.RpcChangeRoleBasis(CustomRoles.Sidekick);
+ target.RpcSetCustomRole(CustomRoles.Sidekick);
+ target.RpcChangeRoleBasis(CustomRoles.Sidekick);
SidekickId = target.PlayerId;
Main.ResetCamPlayerList.Add(target.PlayerId);
@@ -139,13 +139,11 @@ public class Jackal : RoleBase
return false;
}
- private static bool CanBeSidekick(PlayerControl pc, out bool needBasisChange, out RoleTypes targetRoleType)
+ private static bool CanBeSidekick(PlayerControl pc)
{
- targetRoleType = pc.GetRoleTypes();
- needBasisChange = targetRoleType is not RoleTypes.Impostor and not RoleTypes.Shapeshifter;
if (!CanRecruitImpostors.GetBool() && pc.Is(CustomRoleTypes.Impostor)) return false;
if (!CanRecruitMadmates.GetBool() && pc.IsMadmate()) return false;
- return pc != null && !pc.Is(CustomRoles.Sidekick) && !pc.Is(CustomRoles.Recruit) && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Rascal) && !pc.Is(CustomRoles.Charmed) && !pc.Is(CustomRoles.Contagious) && pc.GetCustomRole().IsAbleToBeSidekicked();
+ return pc != null && !pc.Is(CustomRoles.Sidekick) && !pc.IsConverted() && pc.GetCustomRole().IsAbleToBeSidekicked();
}
public override void OnFixedUpdate(PlayerControl pc)
@@ -196,7 +194,7 @@ public class Sidekick : RoleBase
public override bool CanUseImpostorVentButton(PlayerControl pc) => Jackal.CanVentSK.GetBool();
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Jackal.KillCooldownSK.GetFloat();
public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(Jackal.HasImpostorVision.GetBool());
- public override bool CanUseSabotage(PlayerControl pc) => Jackal.CanSabotageSK.GetBool() && pc.IsAlive();
+ public override bool CanUseSabotage(PlayerControl pc) => base.CanUseSabotage(pc) || (Jackal.CanSabotageSK.GetBool() && pc.IsAlive());
public override void SetButtonTexts(HudManager __instance, byte id)
{
diff --git a/Roles/Neutral/Magician.cs b/Roles/Neutral/Magician.cs
index 3987b1cf2..6560a0532 100644
--- a/Roles/Neutral/Magician.cs
+++ b/Roles/Neutral/Magician.cs
@@ -103,7 +103,7 @@ public class Magician : RoleBase
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat();
public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool();
- public override bool CanUseSabotage(PlayerControl pc) => pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool());
+ public override bool CanUseSabotage(PlayerControl pc) => base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()));
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
diff --git a/Roles/Neutral/Maverick.cs b/Roles/Neutral/Maverick.cs
index ab8f722e4..4181c9b42 100644
--- a/Roles/Neutral/Maverick.cs
+++ b/Roles/Neutral/Maverick.cs
@@ -48,7 +48,7 @@ public class Maverick : RoleBase
public override void ApplyGameOptions(IGameOptions opt, byte id) => opt.SetVision(HasImpostorVision.GetBool());
public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool();
public override bool CanUseKillButton(PlayerControl pc) => pc.IsAlive();
- public override bool CanUseSabotage(PlayerControl pc) => false;
+ public override bool CanUseSabotage(PlayerControl pc) => pc.Is(CustomRoles.Mischievous);
public override string GetProgressText(byte playerId, bool comms)
{
diff --git a/Roles/Neutral/Mycologist.cs b/Roles/Neutral/Mycologist.cs
index 5a4afd791..155f5ec6c 100644
--- a/Roles/Neutral/Mycologist.cs
+++ b/Roles/Neutral/Mycologist.cs
@@ -166,6 +166,6 @@ namespace EHR.Neutral
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) => IsEnable && target != null && InfectedPlayers.Contains(target.PlayerId);
public override void AfterMeetingTasks() => Mycologist_.AddAbilityCD(CD.GetInt());
public override bool CanUseImpostorVentButton(PlayerControl pc) => true;
- public override bool CanUseSabotage(PlayerControl pc) => SpreadAction.GetValue() == 1 && pc.IsAlive();
+ public override bool CanUseSabotage(PlayerControl pc) => base.CanUseSabotage(pc) || (SpreadAction.GetValue() == 1 && pc.IsAlive());
}
}
\ No newline at end of file
diff --git a/Roles/Neutral/Patroller.cs b/Roles/Neutral/Patroller.cs
index cbe7c078c..346a4114c 100644
--- a/Roles/Neutral/Patroller.cs
+++ b/Roles/Neutral/Patroller.cs
@@ -57,7 +57,7 @@ namespace EHR.Neutral
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = Utils.GetPlayerById(id).GetPlainShipRoom() == RoomBoosts[Boost.Cooldown] ? DecreasedKillCooldown.GetFloat() : KillCooldown.GetFloat();
public override bool CanUseImpostorVentButton(PlayerControl pc) => pc.inVent || pc.GetAbilityUseLimit() > 0 || pc.GetPlainShipRoom() == RoomBoosts[Boost.Vent];
- public override bool CanUseSabotage(PlayerControl pc) => pc.GetPlainShipRoom() == RoomBoosts[Boost.Sabotage];
+ public override bool CanUseSabotage(PlayerControl pc) => base.CanUseSabotage(pc) || (pc.GetPlainShipRoom() == RoomBoosts[Boost.Sabotage]);
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
diff --git a/Roles/Neutral/Provocateur.cs b/Roles/Neutral/Provocateur.cs
index f9a8a238a..96af6a211 100644
--- a/Roles/Neutral/Provocateur.cs
+++ b/Roles/Neutral/Provocateur.cs
@@ -34,7 +34,7 @@ namespace EHR.Neutral
public override bool CanUseSabotage(PlayerControl pc)
{
- return false;
+ return pc.Is(CustomRoles.Mischievous);
}
public override void SetKillCooldown(byte id)
diff --git a/Roles/Neutral/Revolutionist.cs b/Roles/Neutral/Revolutionist.cs
index d0f1e4163..7afb75c26 100644
--- a/Roles/Neutral/Revolutionist.cs
+++ b/Roles/Neutral/Revolutionist.cs
@@ -66,7 +66,7 @@ namespace EHR.Neutral
public override bool CanUseSabotage(PlayerControl pc)
{
- return false;
+ return pc.Is(CustomRoles.Mischievous);
}
public override void SetKillCooldown(byte id)
diff --git a/Roles/Neutral/Rogue.cs b/Roles/Neutral/Rogue.cs
index 5a2187998..cd7f3538e 100644
--- a/Roles/Neutral/Rogue.cs
+++ b/Roles/Neutral/Rogue.cs
@@ -63,7 +63,7 @@ namespace EHR.Neutral
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = GotRewards.Contains(Reward.DecreasedKillCooldown) ? KillCooldown.GetFloat() / 2f : KillCooldown.GetFloat();
public override bool CanUseImpostorVentButton(PlayerControl pc) => CurrentTask.Objective == Objective.VentXTimes || CanVent.GetBool();
- public override bool CanUseSabotage(PlayerControl pc) => GotRewards.Contains(Reward.Sabotage) || GotRewards.Contains(Reward.Morph);
+ public override bool CanUseSabotage(PlayerControl pc) => base.CanUseSabotage(pc) || (GotRewards.Contains(Reward.Sabotage) || GotRewards.Contains(Reward.Morph));
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
diff --git a/Roles/Neutral/RouleteGrandeur.cs b/Roles/Neutral/RouleteGrandeur.cs
index 6ae653d41..ccd3e50f9 100644
--- a/Roles/Neutral/RouleteGrandeur.cs
+++ b/Roles/Neutral/RouleteGrandeur.cs
@@ -55,7 +55,7 @@ namespace EHR.Neutral
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KCD;
public override bool CanUseImpostorVentButton(PlayerControl pc) => true;
- public override bool CanUseSabotage(PlayerControl pc) => !(Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool());
+ public override bool CanUseSabotage(PlayerControl pc) => base.CanUseSabotage(pc) || (!(Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()));
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
diff --git a/Roles/Neutral/Simon.cs b/Roles/Neutral/Simon.cs
index 3e7badba0..0bb65b9f4 100644
--- a/Roles/Neutral/Simon.cs
+++ b/Roles/Neutral/Simon.cs
@@ -60,7 +60,7 @@ namespace EHR.Neutral
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat();
public override bool CanUseImpostorVentButton(PlayerControl pc) => pc.IsAlive();
- public override bool CanUseSabotage(PlayerControl pc) => !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool());
+ public override bool CanUseSabotage(PlayerControl pc) => base.CanUseSabotage(pc) || (!(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()));
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
diff --git a/Roles/Neutral/Sprayer.cs b/Roles/Neutral/Sprayer.cs
index fed26173f..caaa577ba 100644
--- a/Roles/Neutral/Sprayer.cs
+++ b/Roles/Neutral/Sprayer.cs
@@ -81,7 +81,7 @@ namespace EHR.Neutral
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat();
public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool();
- public override bool CanUseSabotage(PlayerControl pc) => pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool());
+ public override bool CanUseSabotage(PlayerControl pc) => base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()));
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
diff --git a/Roles/Neutral/Tiger.cs b/Roles/Neutral/Tiger.cs
index db9f211bb..f969e4863 100644
--- a/Roles/Neutral/Tiger.cs
+++ b/Roles/Neutral/Tiger.cs
@@ -66,7 +66,7 @@ namespace EHR.Neutral
public override bool CanUseSabotage(PlayerControl pc)
{
- return pc.IsAlive() && !(Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool());
+ return base.CanUseSabotage(pc) || (pc.IsAlive() && !(Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()));
}
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
diff --git a/Roles/Neutral/Traitor.cs b/Roles/Neutral/Traitor.cs
index f4dfdbaea..d09873c0f 100644
--- a/Roles/Neutral/Traitor.cs
+++ b/Roles/Neutral/Traitor.cs
@@ -43,5 +43,5 @@ public class Traitor : RoleBase
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 CanUseSabotage(PlayerControl pc) => CanSabotage.GetBool() && pc.IsAlive();
+ public override bool CanUseSabotage(PlayerControl pc) => base.CanUseSabotage(pc) || (CanSabotage.GetBool() && pc.IsAlive());
}
\ No newline at end of file
diff --git a/Roles/Neutral/WeaponMaster.cs b/Roles/Neutral/WeaponMaster.cs
index a4a50a56e..9e578af4c 100644
--- a/Roles/Neutral/WeaponMaster.cs
+++ b/Roles/Neutral/WeaponMaster.cs
@@ -89,7 +89,7 @@ public class WeaponMaster : RoleBase
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat();
public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool();
- public override bool CanUseSabotage(PlayerControl pc) => pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool());
+ public override bool CanUseSabotage(PlayerControl pc) => base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()));
public override void ApplyGameOptions(IGameOptions opt, byte id)
{