Add Centralizer, Vacuum, Shade, Task Master

This commit is contained in:
Gurge44 committed 2025-10-16 11:15:21 +02:00
1 parent fbdaae733d
commit 3f4a9cce47
25 files changed
+379 -34

No files matched your search

+4
View File
@@ -34,6 +34,7 @@ public enum CustomRoles
Camouflager,
Capitalism,
Cantankerous,
Centralizer,
Changeling,
Chronomancer,
Cleaner,
@@ -272,6 +273,7 @@ public enum CustomRoles
Tracefinder,
Tree,
Tunneler,
Vacuum,
Ventguard,
Veteran,
SwordsMan, // Vigilante
@@ -560,11 +562,13 @@ public enum CustomRoles
Trainee,
DualPersonality, // Schizophrenic
Seer,
Shade, // Ghost role
Shy,
Sleuth,
Sonar,
Stained,
Taskcounter,
TaskMaster,
TicketsStealer, // Stealer
Stressed,
Swift,
+4
View File
@@ -422,6 +422,7 @@ public class Main : BasePlugin
{ CustomRoles.Astral, "#b329d6" },
{ CustomRoles.Gardener, "#00ff00" },
{ CustomRoles.Farmer, "#FFDE59" },
{ CustomRoles.Vacuum, "#E44CD6" },
{ CustomRoles.Transmitter, "#c9a11e" },
{ CustomRoles.Captain, "#53B3EF" },
{ CustomRoles.Tree, "#00ff00" },
@@ -637,6 +638,7 @@ public class Main : BasePlugin
{ CustomRoles.Bloodmoon, "#ff1313" },
{ CustomRoles.GA, "#8cffff" },
{ CustomRoles.Facilitator, CovenColor },
{ CustomRoles.Shade, "#060270" },
// GM
{ CustomRoles.GM, "#ff5b70" },
// Add-ons
@@ -653,6 +655,7 @@ public class Main : BasePlugin
{ CustomRoles.Listener, "#060270" },
{ CustomRoles.Unbound, "#DFC57B" },
{ CustomRoles.AntiTP, "#fcba03" },
{ CustomRoles.TaskMaster, "#00ffa5" },
{ CustomRoles.Compelled, "#D2E44C" },
{ CustomRoles.Commited, "#f5c542" },
{ CustomRoles.BananaMan, "#ffe135" },
@@ -1115,6 +1118,7 @@ public enum AdditionalWinners
// -------------
Specter = CustomRoles.Specter,
Shade = CustomRoles.Shade,
Lovers = CustomRoles.Lovers,
Executioner = CustomRoles.Executioner,
Opportunist = CustomRoles.Opportunist,
+14 -1
View File
@@ -156,6 +156,7 @@ internal static class CustomRolesHelper
CustomRoles.Psychopath => CustomRoles.Impostor,
CustomRoles.Loner => CustomRoles.Impostor,
CustomRoles.Postponer => CustomRoles.Impostor,
CustomRoles.Centralizer => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter,
CustomRoles.Venerer => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter,
CustomRoles.Kidnapper => CustomRoles.Shapeshifter,
CustomRoles.Ambusher => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter,
@@ -245,6 +246,7 @@ internal static class CustomRolesHelper
CustomRoles.Gardener => CustomRoles.Crewmate,
CustomRoles.Imitator => CustomRoles.Crewmate,
CustomRoles.PortalMaker => CustomRoles.Crewmate,
CustomRoles.Vacuum => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer,
CustomRoles.Astral => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer,
CustomRoles.Helper => CustomRoles.Crewmate,
CustomRoles.Ankylosaurus => CustomRoles.Crewmate,
@@ -360,7 +362,13 @@ internal static class CustomRolesHelper
CustomRoles.Lurker => CustomRoles.Impostor,
CustomRoles.Doomsayer => CustomRoles.Crewmate,
CustomRoles.Godfather => CustomRoles.Impostor,
CustomRoles.Silencer => Silencer.SilenceMode.GetValue() == 1 ? CustomRoles.Shapeshifter : CustomRoles.Impostor,
CustomRoles.Silencer => Silencer.SilenceMode.GetValue() switch
{
0 => CustomRoles.Impostor,
1 => CustomRoles.Shapeshifter,
2 => CustomRoles.Phantom,
_ => CustomRoles.Impostor
},
CustomRoles.NoteKiller => CustomRoles.Crewmate,
CustomRoles.RoomRusher => RoomRusher.CanVent ? CustomRoles.Engineer : CustomRoles.Crewmate,
CustomRoles.Clerk => CustomRoles.Crewmate,
@@ -720,6 +728,7 @@ internal static class CustomRolesHelper
CustomRoles.Psychopath or
CustomRoles.Ambusher or
CustomRoles.Kidnapper or
CustomRoles.Centralizer or
CustomRoles.Postponer or
CustomRoles.Venerer or
CustomRoles.ClockBlocker or
@@ -1043,6 +1052,7 @@ internal static class CustomRolesHelper
CustomRoles.Bomber or
CustomRoles.Nuker or
CustomRoles.Camouflager or
CustomRoles.Centralizer or
CustomRoles.Disperser or
CustomRoles.Escapee or
CustomRoles.FireWorks or
@@ -1084,6 +1094,7 @@ internal static class CustomRolesHelper
{
return role.IsAdditionRole() && (!Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary<CustomRoles, List<CustomRoles>> neverList) || !neverList.TryGetValue(pc.GetCustomRole(), out List<CustomRoles> bannedAddonList) || !bannedAddonList.Contains(role)) && pc.GetCustomRole() is not CustomRoles.GuardianAngelEHR and not CustomRoles.God && !pc.Is(CustomRoles.Madmate) && !pc.Is(CustomRoles.GM) && role is not CustomRoles.Lovers && !pc.Is(CustomRoles.Needy) && (!pc.HasSubRole() || pc.GetCustomSubRoles().Count < Options.NoLimitAddonsNumMax.GetInt()) && (!Options.AddonCanBeSettings.TryGetValue(role, out (OptionItem Imp, OptionItem Neutral, OptionItem Crew, OptionItem Coven) o) || ((o.Imp.GetBool() || !pc.GetCustomRole().IsImpostor()) && (o.Neutral.GetBool() || !pc.GetCustomRole().IsNeutral()) && (o.Crew.GetBool() || !pc.IsCrewmate()) && (o.Coven.GetBool() || !pc.Is(Team.Coven)))) && (!role.IsImpOnlyAddon() || (pc.IsImpostor() && !pc.Is(CustomRoles.DoubleAgent)) || (pc.Is(CustomRoles.Traitor) && Traitor.CanGetImpostorOnlyAddons.GetBool())) && role switch
{
CustomRoles.TaskMaster when !pc.IsCrewmate() || !Utils.HasTasks(pc.Data, forRecompute: false) => false,
CustomRoles.Commited when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter or RoleTypes.Viper) => false,
CustomRoles.Underdog when pc.Is(CustomRoles.Mare) => false,
CustomRoles.Shy when Options.DisableWhisperCommand.GetBool() => false,
@@ -1633,6 +1644,7 @@ internal static class CustomRolesHelper
CustomRoles.Blackmailer => RoleOptionType.Impostor_Support,
CustomRoles.Camouflager => RoleOptionType.Impostor_Support,
CustomRoles.Capitalism => RoleOptionType.Impostor_Support,
CustomRoles.Centralizer => RoleOptionType.Impostor_Support,
CustomRoles.Cleaner => RoleOptionType.Impostor_Support,
CustomRoles.ClockBlocker => RoleOptionType.Impostor_Support,
CustomRoles.Commander => RoleOptionType.Impostor_Support,
@@ -1741,6 +1753,7 @@ internal static class CustomRolesHelper
CustomRoles.Transmitter => RoleOptionType.Crewmate_Miscellaneous,
CustomRoles.Tracefinder => RoleOptionType.Crewmate_Miscellaneous,
CustomRoles.Tunneler => RoleOptionType.Crewmate_Miscellaneous,
CustomRoles.Vacuum => RoleOptionType.Crewmate_Miscellaneous,
CustomRoles.Analyst => RoleOptionType.Crewmate_Investigate,
CustomRoles.Captain => RoleOptionType.Crewmate_Investigate,
CustomRoles.Catcher => RoleOptionType.Crewmate_Investigate,
+2
View File
@@ -1950,6 +1950,8 @@ internal static class ExtendedPlayerControl
void DoKill()
{
Vacuum.BeforeMurderCheck(target);
killer.RpcMurderPlayer(target, true);
if (Main.PlayerStates.TryGetValue(target.PlayerId, out var state) && !state.IsDead)
+2
View File
@@ -90,6 +90,7 @@ internal static class GhostRolesManager
{
try
{
if (GameStates.IsEnded) return false;
if (Options.CurrentGameMode != CustomGameMode.Standard) return false;
if (AssignedGhostRoles.Count >= GhostRoles.Count) return false;
@@ -100,6 +101,7 @@ internal static class GhostRolesManager
case CustomRoles.GM:
case CustomRoles.Curser:
case CustomRoles.Backstabber:
case CustomRoles.Innocent:
case CustomRoles.Workaholic when !Workaholic.WorkaholicCannotWinAtDeath.GetBool():
return false;
}
+1 -1
View File
@@ -1114,7 +1114,7 @@ public static class GuessManager
CreateGuesserButton(__instance);
}
private static bool CanGuess(PlayerControl lp, bool restrictions)
public static bool CanGuess(PlayerControl lp, bool restrictions)
{
if (Banshee.Instances.Exists(x => x.ScreechedPlayers.Contains(lp.PlayerId))) return false; // Vanilla clients can't guess with their chat hidden, so don't let modded clients guess for fairness
return lp.Is(CustomRoles.Guesser) || lp.GetCustomRole() switch
+17
View File
@@ -100,6 +100,7 @@ public static class Options
public static Dictionary<CustomGameMode, OptionItem> GMPollGameModesSettings;
public static Dictionary<MapNames, OptionItem> MPollMapsSettings;
public static Dictionary<CustomRoles, OptionItem> CrewAdvancedGameEndCheckingSettings;
public static OptionItem GuessersKeepTheGameGoing;
public static readonly Dictionary<Team, (OptionItem MinSetting, OptionItem MaxSetting)> FactionMinMaxSettings = [];
public static readonly Dictionary<RoleOptionType, OptionItem[]> RoleSubCategoryLimits = [];
@@ -3034,6 +3035,9 @@ public static class Options
yield return null;
id = 69900;
new TextOptionItem(110020, "MenuTitle.GMPoll", TabGroup.SystemSettings)
.SetHeader(true);
GMPollGameModesSettings = Enum.GetValues<CustomGameMode>()[..^1].ToDictionary(x => x, x => new BooleanOptionItem(id++, "GMPoll.Allow", true, TabGroup.SystemSettings)
.SetColor(Main.GameModeColors[x])
@@ -3042,6 +3046,9 @@ public static class Options
id = 69920;
new TextOptionItem(110030, "MenuTitle.MPoll", TabGroup.SystemSettings)
.SetHeader(true);
MPollMapsSettings = Enum.GetValues<MapNames>().ToDictionary(x => x, x => new BooleanOptionItem(id++, "MPoll.Allow", true, TabGroup.SystemSettings)
.SetHeader(x == MapNames.Skeld)
.AddReplacement(("{m}", Translator.GetString($"{x}"))));
@@ -3051,6 +3058,9 @@ public static class Options
CrewAdvancedGameEndCheckingSettings = [];
bool first = true;
new TextOptionItem(110000, "MenuTitle.CrewAdvancedGameEndChecking", TabGroup.SystemSettings)
.SetHeader(true);
foreach (RoleBase roleBase in Main.AllRoleClasses)
{
Type type = roleBase.GetType();
@@ -3063,9 +3073,16 @@ public static class Options
first = false;
}
GuessersKeepTheGameGoing = new BooleanOptionItem(id, "GuessersKeepTheGameGoing", false, TabGroup.SystemSettings)
.SetHeader(true)
.SetColor(Color.yellow);
id = 69000;
new TextOptionItem(110010, "MenuTitle.AutoGMRotation", TabGroup.SystemSettings)
.SetHeader(true);
EnableAutoGMRotation = new BooleanOptionItem(id++, "EnableAutoGMRotation", false, TabGroup.SystemSettings)
.SetColor(Color.magenta)
.SetHeader(true);
+5
View File
@@ -3316,6 +3316,7 @@ public static class Utils
CustomRoles.Rhapsode => Rhapsode.AbilityCooldown.GetInt() + (includeDuration ? Rhapsode.AbilityDuration.GetInt() : 0),
CustomRoles.Whisperer => Whisperer.Cooldown.GetInt() + (includeDuration ? Whisperer.Duration.GetInt() : 0),
CustomRoles.Astral => Astral.AbilityCooldown.GetInt() + (includeDuration ? Astral.AbilityDuration.GetInt() : 0),
CustomRoles.Vacuum => Vacuum.AbilityCooldown.GetInt() + (includeDuration ? Vacuum.AbilityDuration.GetInt() : 0),
CustomRoles.TimeMaster => TimeMaster.TimeMasterSkillCooldown.GetInt(),
CustomRoles.Perceiver => Perceiver.CD.GetInt(),
CustomRoles.Convener => Convener.CD.GetInt(),
@@ -3342,6 +3343,7 @@ public static class Utils
CustomRoles.QuickShooter => QuickShooter.ShapeshiftCooldown.GetInt(),
CustomRoles.Disperser => Disperser.DisperserShapeshiftCooldown.GetInt(),
CustomRoles.Twister => Twister.ShapeshiftCooldown.GetInt(),
CustomRoles.Centralizer => Centralizer.AbilityCooldown.GetInt(),
CustomRoles.Abyssbringer => Abyssbringer.BlackHolePlaceCooldown.GetInt(),
CustomRoles.Venerer => Venerer.AbilityCooldown.GetInt(),
CustomRoles.Wiper => Wiper.AbilityCooldown.GetInt(),
@@ -3453,6 +3455,9 @@ public static class Utils
if (pc.Is(CustomRoles.Specter) || pc.Is(CustomRoles.Haunter))
pc.RpcResetAbilityCooldown();
if (pc.Is(CustomRoles.TaskMaster))
TaskMaster.AfterMeetingTasks(pc);
Main.CheckShapeshift[pc.PlayerId] = false;
}
+12
View File
@@ -190,6 +190,12 @@ internal static class GameEndChecker
CustomRoles role = pc.GetCustomRole();
RoleBase roleBase = Main.PlayerStates[pc.PlayerId].Role;
if (GhostRolesManager.AssignedGhostRoles.TryGetValue(pc.PlayerId, out var ghostRole) && ghostRole.Instance is Shade shade && shade.Protected.IsSupersetOf(Main.AllAlivePlayerControls.Select(x => x.PlayerId)))
{
AdditionalWinnerTeams.Add(AdditionalWinners.Shade);
WinnerIds.Add(pc.PlayerId);
}
switch (role)
{
case CustomRoles.Stalker when pc.IsAlive() && ((WinnerTeam == CustomWinner.Impostor && !reason.Equals(GameOverReason.ImpostorsBySabotage)) || WinnerTeam == CustomWinner.Stalker || (WinnerTeam == CustomWinner.Crewmate && !reason.Equals(GameOverReason.CrewmatesByTask) && roleBase is Stalker { IsWinKill: true } && Stalker.SnatchesWin.GetBool())):
@@ -600,6 +606,12 @@ internal static class GameEndChecker
int coven = AlivePlayersCount(CountTypes.Coven);
var crewKeepsGameGoing = false;
if (Options.GuessersKeepTheGameGoing.GetBool())
{
bool restrictions = Options.GuesserNumRestrictions.GetBool();
crewKeepsGameGoing |= statesCoutingAsCrew.Any(x => !x.IsDead && x.Player != null && GuessManager.StartMeetingPatch.CanGuess(x.Player, restrictions));
}
foreach (PlayerState playerState in statesCoutingAsCrew)
{
+21
View File
@@ -4500,6 +4500,7 @@
"LibrarianNormalMode": "Normal",
"CanKillWhileShifted": "Can Kill While Shapeshifting",
"Shapeshift": "Shapeshift",
"Vanish": "Vanish",
"UsesPetInsteadOfKillNotice": "<#ffff00>⚠ As this role, you need to use your pet button as your kill button!</color>",
"UsesMeetingShapeshiftNotice": "<#ffff00>⚠ As this role, instead of commands and votes, you need to use the shapeshift menu in meetings to select your target!</color>",
"UseMeetingShapeshift": "Use Shapeshift Menu in Meetings to Select Targets Instead of Commands/Votes",
@@ -9125,6 +9126,16 @@
"NonplusInfoLong": "(Neutrals):\\nAs the Nonplus, you can blind others by venting or petting. Blinded players can't see anything for a set amount of time.\\nKill everyone to win.",
"Nonplus.BlindDuration": "Blind Duration",
"Nonplus.BlindCooldown": "Blind Cooldown",
"TaskMaster": "Task Master",
"TaskMasterInfo": "Auto-Completes 1 Task After Every Meeting",
"TaskMasterInfoLong": "(Add-ons):\\nAs the Task Master, you automatically complete 1 task after every meeting.",
"Prefix.TaskMaster": "Task Master",
"Centralizer": "Centralizer",
"CentralizerInfo": "Centralize Players' Locations",
"CentralizerInfoLong": "(Impostors):\\nAs the Centralizer, mark a position. Then use your ability again to teleport a certain number of players to that position. You may also be included.",
"Vacuum": "Vacuum",
"VacuumInfo": "Suck In Dead Bodies",
"VacuumInfoLong": "(Crewmates):\\nAs the Vacuum, while using your ability, all kills' bodies will teleport to you.",
"Rookie": "Rookie",
"RookieInfo": "You Can't Kill on the First Round",
"RookieInfoLong": "(Add-ons):\\nYou can't kill on the first round.",
@@ -9297,6 +9308,10 @@
"FacilitatorInfo": "Give Necronomicon",
"FacilitatorInfoLong": "(Coven Ghost):\\nAs the Facilitator, you can protect a Coven member to give them the Necronomicon. (Protecting a non-Coven member or one who already has the Necronomicon does nothing.)",
"Prefix.Facilitator": "Facilitator",
"Shade": "Shade",
"ShadeInfo": "Protect Everyone to Win",
"ShadeInfoLong": "(Neutral Ghost):\\nAs the Shade, you must protect everyone to win! You can protect someone by using your protect button on them. If you succeed to protect everyone before the end of the game, you win with the winning team at the end. This is only an additional win condition, it does not override your original win condition.",
"Prefix.Shade": "Shade",
"Haunter": "Haunter",
"HaunterInfo": "You're the Impostors' Worst Nightmare",
"HaunterInfoLong": "(Crewmate/Neutral Ghost):\\nAs the Haunter, when you complete all of your tasks, a random living impostor's name will show in red to everyone in meetings. When you have 1 task left, all impostors will get an arrow pointing towards you, and if any of them come close to you, you lose this role.",
@@ -9980,6 +9995,7 @@
"AdditionalWinnerRoleText.Postman": "The Postman delivered mail to everyone",
"AdditionalWinnerRoleText.SoulHunter": "Soul Hunter wins along",
"AdditionalWinnerRoleText.Specter": "Phantasm wins along",
"AdditionalWinnerRoleText.Shade": "Shade wins along",
"AdditionalWinnerRoleText.Default": "{0} wins along",
"ErrorEndText": "An error occurred",
"ErrorEndTextDescription": "To avoid crashing, the game was forcibly ended.",
@@ -9998,7 +10014,12 @@
"SettingsForRoleText": "Settings for",
"GMPoll.Allow": "Show {gm} in /gmpoll choices",
"MPoll.Allow": "Show {m} in /mpoll choices",
"MenuTitle.GMPoll": "Game Mode Poll",
"MenuTitle.MPoll": "Map Poll",
"CrewAdvancedGameEndChecking": "Enable Advanced Game End Checking For {role}",
"GuessersKeepTheGameGoing": "Crewmate Guessers Keep The Game Going",
"MenuTitle.CrewAdvancedGameEndChecking": "Advanced Game End Checking",
"MenuTitle.AutoGMRotation": "Automatic Game Mode Rotation",
"EnableAutoGMRotation": "Automatically Rotate Game Modes",
"AutoGMRotationSlot": "Game Mode for Game {index} (Slot {index})",
"AGMR.Explicit": "Specified Explicitly",
+28
View File
@@ -0,0 +1,28 @@
using EHR.Modules;
using Il2CppSystem;
using Exception = System.Exception;
namespace EHR.AddOns.Crewmate;
public class TaskMaster : IAddon
{
public AddonTypes Type => AddonTypes.Helpful;
public void SetupCustomOption()
{
Options.SetupAdtRoleOptions(655500, CustomRoles.TaskMaster, canSetNum: true);
}
public static void AfterMeetingTasks(PlayerControl pc)
{
try
{
TaskState ts = pc.GetTaskState();
if (!ts.HasTasks || ts.IsTaskFinished || !Utils.HasTasks(pc.Data, forRecompute: false)) return;
var incompleteTasks = pc.myTasks.FindAll((Predicate<PlayerTask>)(x => !x.IsComplete));
RPC.PlaySoundRPC(pc.PlayerId, Sounds.TaskUpdateSound);
pc.RpcCompleteTask(incompleteTasks[IRandom.Instance.Next(0, incompleteTasks.Count)].Id);
}
catch (Exception e) { Utils.ThrowException(e); }
}
}
+27
View File
@@ -0,0 +1,27 @@
using System.Collections.Generic;
namespace EHR.AddOns.GhostRoles;
public class Shade : IGhostRole
{
private static OptionItem CD;
public HashSet<byte> Protected = [];
public Team Team => Team.Neutral;
public int Cooldown => CD.GetInt();
public void OnProtect(PlayerControl pc, PlayerControl target)
{
Protected.Add(target.PlayerId);
}
public void OnAssign(PlayerControl pc)
{
Protected = [];
}
public void SetupCustomOption()
{
Options.SetupRoleOptions(649900, TabGroup.OtherRoles, CustomRoles.Bloodmoon);
CD = new IntegerOptionItem(649902, "AbilityCooldown", new(0, 120, 1), 10, TabGroup.OtherRoles)
.SetParent(Options.CustomRoleSpawnChances[CustomRoles.Bloodmoon])
.SetValueFormat(OptionFormat.Seconds);
}
}
+2 -7
View File
@@ -123,12 +123,7 @@ public class Coroner : RoleBase
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (target != null && seer.PlayerId != target.PlayerId) return string.Empty;
if (GameStates.IsMeeting || seer.PlayerId != CoronerId || hud) return string.Empty;
if (Main.PlayerStates[seer.PlayerId].Role is not Coroner bh) return string.Empty;
return bh.CoronerTargets.Count > 0 ? bh.CoronerTargets.Select(targetId => TargetArrow.GetArrows(seer, targetId)).Aggregate(string.Empty, (current, arrow) => current + Utils.ColorString(seer.GetRoleColor(), arrow)) : Utils.ColorString(Color.white, LocateArrow.GetArrows(seer));
if (target != null && seer.PlayerId != target.PlayerId || GameStates.IsMeeting || seer.PlayerId != CoronerId || hud || Main.PlayerStates[seer.PlayerId].Role is not Coroner cn) return string.Empty;
return cn.CoronerTargets.Count > 0 ? cn.CoronerTargets.Select(targetId => TargetArrow.GetArrows(seer, targetId)).Aggregate(string.Empty, (current, arrow) => current + Utils.ColorString(seer.GetRoleColor(), arrow)) : Utils.ColorString(Color.white, LocateArrow.GetArrows(seer));
}
}
+2 -5
View File
@@ -82,11 +82,8 @@ public class Mortician : RoleBase
{
if (ShowArrows.GetBool())
{
if (target != null && seer.PlayerId != target.PlayerId) return string.Empty;
if (seer.PlayerId != MorticianId) return string.Empty;
return GameStates.IsMeeting ? string.Empty : Utils.ColorString(Color.white, LocateArrow.GetArrows(seer));
if (target != null && seer.PlayerId != target.PlayerId || seer.PlayerId != MorticianId || hud) return string.Empty;
return meeting ? string.Empty : Utils.ColorString(Color.white, LocateArrow.GetArrows(seer));
}
return string.Empty;
+1 -1
View File
@@ -56,7 +56,7 @@ internal class Rabbit : RoleBase
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (seer == null || seer.PlayerId != target.PlayerId || !RabbitStates.TryGetValue(seer.PlayerId, out RabbitState state)) return string.Empty;
if (seer == null || seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || !RabbitStates.TryGetValue(seer.PlayerId, out RabbitState state)) return string.Empty;
string suffix = state.Suffix;
return hud ? $"<size=200%>{suffix}</size>" : suffix;
+1 -1
View File
@@ -125,7 +125,7 @@ public class Scout : RoleBase
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (seer == null || seer.PlayerId != TrackerId || target != null && seer.PlayerId != target.PlayerId || !TrackerTarget.ContainsKey(seer.PlayerId) || GameStates.IsMeeting) return string.Empty;
if (seer == null || seer.PlayerId != TrackerId || target != null && seer.PlayerId != target.PlayerId || !TrackerTarget.ContainsKey(seer.PlayerId) || meeting || hud) return string.Empty;
return TrackerTarget[seer.PlayerId].Aggregate(string.Empty, (current, trackTarget) => current + Utils.ColorString(CanGetColoredArrow.GetBool() ? Main.PlayerColors[trackTarget] : Color.white, TargetArrow.GetArrows(seer, trackTarget))) + LocateArrow.GetArrows(seer);
}
+1 -1
View File
@@ -168,7 +168,7 @@ public class Snitch : RoleBase
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (seer.Is(CustomRoles.Madmate) || !EnableTargetArrow || GameStates.IsMeeting || seer.PlayerId != SnitchId || (target != null && seer.PlayerId != target.PlayerId)) return string.Empty;
if (seer.Is(CustomRoles.Madmate) || !EnableTargetArrow || GameStates.IsMeeting || seer.PlayerId != SnitchId || (target != null && seer.PlayerId != target.PlayerId) || hud) return string.Empty;
var arrows = string.Empty;
+1 -6
View File
@@ -126,12 +126,7 @@ internal class Spiritualist : RoleBase
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (!seer.IsAlive() || seer.PlayerId != SpiritualistId) return string.Empty;
if (target != null && seer.PlayerId != target.PlayerId) return string.Empty;
if (GameStates.IsMeeting) return string.Empty;
if (!seer.IsAlive() || seer.PlayerId != SpiritualistId || target != null && seer.PlayerId != target.PlayerId || meeting || hud) return string.Empty;
return SpiritualistTarget != byte.MaxValue && ShowArrow ? Utils.ColorString(seer.GetRoleColor(), TargetArrow.GetArrows(seer, SpiritualistTarget)) : string.Empty;
}
+104
View File
@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using AmongUs.GameOptions;
namespace EHR.Crewmate;
public class Vacuum : RoleBase
{
public static bool On;
private static List<Vacuum> Instances = [];
public override bool IsEnable => On;
public static OptionItem AbilityCooldown;
public static OptionItem AbilityDuration;
private static OptionItem AbilityUseLimit;
private static OptionItem AbilityUseGainWithEachTaskCompleted;
private static OptionItem AbilityChargesWhenFinishedTasks;
private byte VacuumId;
private long AbilityEndTS;
public override void SetupCustomOption()
{
StartSetup(655600)
.AutoSetupOption(ref AbilityCooldown, 30, new IntegerValueRule(0, 120, 1), OptionFormat.Seconds)
.AutoSetupOption(ref AbilityDuration, 15, new IntegerValueRule(0, 60, 1), OptionFormat.Seconds)
.AutoSetupOption(ref AbilityUseLimit, 1f, new FloatValueRule(0, 20, 0.05f), OptionFormat.Times)
.AutoSetupOption(ref AbilityUseGainWithEachTaskCompleted, 1f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times)
.AutoSetupOption(ref AbilityChargesWhenFinishedTasks, 0.2f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times);
}
public override void Init()
{
On = false;
Instances = [];
}
public override void Add(byte playerId)
{
On = true;
AbilityEndTS = 0;
VacuumId = playerId;
playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat());
Instances.Add(this);
}
public override string GetProgressText(byte playerId, bool comms)
{
StringBuilder sb = new();
sb.Append(Utils.GetAbilityUseLimitDisplay(playerId, AbilityEndTS != 0));
sb.Append(Utils.GetTaskCount(playerId, comms));
return sb.ToString();
}
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
{
if (Options.UsePets.GetBool()) return;
AURoleOptions.EngineerCooldown = AbilityCooldown.GetFloat();
AURoleOptions.EngineerInVentMaxTime = 1f;
try { AURoleOptions.GuardianAngelCooldown = 900f; }
catch { }
}
public override void OnEnterVent(PlayerControl pc, Vent vent)
{
if (Options.UsePets.GetBool()) return;
AbilityEndTS = Utils.TimeStamp + AbilityDuration.GetInt();
}
public override void OnPet(PlayerControl pc)
{
AbilityEndTS = Utils.TimeStamp + AbilityDuration.GetInt();
}
public override void OnFixedUpdate(PlayerControl pc)
{
if (AbilityEndTS == 0) return;
if (AbilityEndTS > Utils.TimeStamp) return;
AbilityEndTS = 0;
}
public static void BeforeMurderCheck(PlayerControl target)
{
try
{
foreach (Vacuum instance in Instances)
{
try
{
if (instance.AbilityEndTS == 0) continue;
PlayerControl vacuum = instance.VacuumId.GetPlayer();
if (vacuum == null || !vacuum.IsAlive()) continue;
target.TP(vacuum);
return;
}
catch (Exception e) { Utils.ThrowException(e); }
}
}
catch (Exception e) { Utils.ThrowException(e); }
}
}
+1
View File
@@ -223,6 +223,7 @@ public class BountyHunter : RoleBase
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (seer.IsModdedClient() && !hud) return string.Empty;
return GetTargetText(seer, target, hud) + GetTargetArrow(seer, target);
}
+86
View File
@@ -0,0 +1,86 @@
using System.Linq;
using AmongUs.GameOptions;
namespace EHR.Impostor;
public class Centralizer : RoleBase
{
public static bool On;
public override bool IsEnable => On;
private static OptionItem NumPlayersTeleported;
public static OptionItem AbilityCooldown;
private static OptionItem AbilityUseLimit;
private static OptionItem AbilityUseGainWithEachKill;
private Vector2? MarkedPosition;
public override void SetupCustomOption()
{
StartSetup(655700)
.AutoSetupOption(ref NumPlayersTeleported, 4, new IntegerValueRule(1, 15, 1), OptionFormat.Players)
.AutoSetupOption(ref AbilityCooldown, 30, new IntegerValueRule(0, 120, 1), OptionFormat.Seconds)
.AutoSetupOption(ref AbilityUseLimit, 1f, new FloatValueRule(0, 20, 0.1f), OptionFormat.Times)
.AutoSetupOption(ref AbilityUseGainWithEachKill, 0.6f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times);
}
public override void Init()
{
On = false;
}
public override void Add(byte playerId)
{
On = true;
MarkedPosition = null;
playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat());
}
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
if (Options.UsePhantomBasis.GetBool())
AURoleOptions.PhantomCooldown = AbilityCooldown.GetFloat();
else
{
if (Options.UsePets.GetBool()) return;
AURoleOptions.ShapeshifterCooldown = AbilityCooldown.GetFloat();
AURoleOptions.ShapeshifterDuration = 1f;
}
}
public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting)
{
if (!shapeshifting) return true;
UseAbility(shapeshifter);
return false;
}
public override bool OnVanish(PlayerControl pc)
{
UseAbility(pc);
return false;
}
public override void OnPet(PlayerControl pc)
{
UseAbility(pc);
}
void UseAbility(PlayerControl pc)
{
if (MarkedPosition.HasValue)
{
if (pc.GetAbilityUseLimit() < 1) return;
Main.AllAlivePlayerControls.Shuffle().Take(NumPlayersTeleported.GetInt()).MassTP(MarkedPosition.Value);
MarkedPosition = null;
pc.RpcRemoveAbilityUse();
}
else
{
MarkedPosition = pc.Pos();
pc.Notify(Translator.GetString("MarkDone"));
}
}
}
+1
View File
@@ -258,6 +258,7 @@ internal class Commander : RoleBase
if (seer.PlayerId == target.PlayerId)
{
if (hud) return string.Empty;
if (arrowToCommander.Length > 0) return $"{Translator.GetString("Commander")} {arrowToCommander}";
}
else if (isTargetTarget)
+1 -7
View File
@@ -218,13 +218,7 @@ public class Deathpact : RoleBase
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (GameStates.IsMeeting) return string.Empty;
if (!ShowArrowsToOtherPlayersInPact.GetBool()) return string.Empty;
if (target != null && seer.PlayerId != target.PlayerId) return string.Empty;
if (!IsInActiveDeathpact(seer)) return string.Empty;
if (meeting || hud || !ShowArrowsToOtherPlayersInPact.GetBool() || target != null && seer.PlayerId != target.PlayerId || !IsInActiveDeathpact(seer)) return string.Empty;
var arrows = string.Empty;
+1 -1
View File
@@ -202,7 +202,7 @@ public class EvilTracker : RoleBase
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (!GameStates.IsInTask) return string.Empty;
if (meeting || hud) return string.Empty;
byte trackerId = target.PlayerId;
if (seer.PlayerId != trackerId || seer.PlayerId != EvilTrackerId) return string.Empty;
+40 -3
View File
@@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
using EHR.Crewmate;
using EHR.Modules;
using UnityEngine;
using static EHR.Options;
namespace EHR.Impostor;
@@ -21,7 +23,8 @@ public class Silencer : RoleBase
private static readonly string[] SilenceModes =
[
"EKill",
"Shapeshift"
"Shapeshift",
"Vanish"
];
public override bool IsEnable => PlayerIdList.Count > 0;
@@ -58,8 +61,17 @@ public class Silencer : RoleBase
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
AURoleOptions.ShapeshifterCooldown = SkillCooldown.GetFloat();
AURoleOptions.ShapeshifterDuration = 1f;
switch (SilenceMode.GetValue())
{
case 1:
AURoleOptions.ShapeshifterCooldown = SkillCooldown.GetFloat();
AURoleOptions.ShapeshifterDuration = 1f;
break;
case 2:
AURoleOptions.PhantomCooldown = SkillCooldown.GetFloat();
AURoleOptions.PhantomDuration = 1f;
break;
}
}
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
@@ -101,6 +113,31 @@ public class Silencer : RoleBase
return false;
}
public override bool OnVanish(PlayerControl pc)
{
if (SilenceMode.GetValue() == 2 && ForSilencer.Count == 0)
{
var pos = pc.Pos();
var killRange = NormalGameOptionsV10.KillDistances[Mathf.Clamp(Main.NormalOptions.KillDistance, 0, 2)];
var nearPlayers = Main.AllAlivePlayerControls.Without(pc).Where(x => !x.IsImpostor()).Select(x => (pc: x, distance: Vector2.Distance(x.Pos(), pos))).Where(x => x.distance <= killRange).ToArray();
PlayerControl target = nearPlayers.Length == 0 ? null : nearPlayers.MinBy(x => x.distance).pc;
if (target == null) return false;
ForSilencer.Add(target.PlayerId);
if (pc.IsLocalPlayer())
{
LocalPlayerTotalSilences++;
if (LocalPlayerTotalSilences >= 5) Achievements.Type.Censorship.Complete();
if (target.Is(CustomRoles.Snitch) && Snitch.IsExposed.TryGetValue(target.PlayerId, out bool exposed) && exposed)
Achievements.Type.YouWontTellAnyone.Complete();
}
}
return false;
}
public override void AfterMeetingTasks()
{
ForSilencer.Clear();