diff --git a/CTA/CustomTeamAssigner/CustomRoles.cs b/CTA/CustomTeamAssigner/CustomRoles.cs
index d7b920fa2..c1d6457d1 100644
--- a/CTA/CustomTeamAssigner/CustomRoles.cs
+++ b/CTA/CustomTeamAssigner/CustomRoles.cs
@@ -18,7 +18,7 @@ public enum CustomRoles
// Impostors
Abyssbringer,
- Hacker, // Anonymous
+ Anonymous,
AntiAdminer,
Sans, // Arrogance
Assumer,
@@ -106,6 +106,7 @@ public enum CustomRoles
Twister,
Undertaker,
Vampire,
+ Venerer,
Ventriloquist,
Vindicator,
Visionary,
@@ -166,7 +167,6 @@ public enum CustomRoles
Dealer,
Demolitionist,
Deputy,
- Detective,
Detour,
Dictator,
Doctor,
@@ -181,6 +181,7 @@ public enum CustomRoles
Express,
Farmer,
Farseer,
+ Forensic,
Divinator, // Fortune Teller
Gardener,
Gaulois,
@@ -189,6 +190,7 @@ public enum CustomRoles
Grenadier,
MeetingManager,
Guardian,
+ Hacker,
Helper,
Ignitor,
Imitator,
@@ -218,7 +220,6 @@ public enum CustomRoles
Mortician,
NiceEraser,
NiceGuesser,
- NiceHacker,
NiceSwapper,
Nightmare,
Negotiator,
@@ -295,6 +296,7 @@ public enum CustomRoles
Enderman,
Evolver,
Executioner,
+ Explosivist,
Totocalcio, // Follower
Gaslighter,
Glitch,
@@ -366,6 +368,7 @@ public enum CustomRoles
Technician,
Tank,
Terrorist,
+ Thanos,
Tiger,
Traitor,
Tremor,
@@ -494,6 +497,7 @@ public enum CustomRoles
Energetic,
Entranced,
EvilSpirit,
+ Facilitator, // Ghost role
Finder,
Flashman,
Fragile,
diff --git a/CustomRoles.cs b/CustomRoles.cs
index 51eb836e5..367769416 100644
--- a/CustomRoles.cs
+++ b/CustomRoles.cs
@@ -9,11 +9,13 @@ public enum CustomRoles
Impostor,
Phantom,
Shapeshifter,
+ Viper,
// Vanilla Remakes
ImpostorEHR,
PhantomEHR,
ShapeshifterEHR,
+ ViperEHR,
// Impostors
@@ -124,6 +126,7 @@ public enum CustomRoles
Noisemaker,
Scientist,
Tracker,
+ Detective,
// Vanilla Remakes
CrewmateEHR,
@@ -132,6 +135,7 @@ public enum CustomRoles
NoisemakerEHR,
ScientistEHR,
TrackerEHR,
+ DetectiveEHR,
// Crewmates
@@ -167,7 +171,6 @@ public enum CustomRoles
Dealer,
Demolitionist,
Deputy,
- Detective,
Detour,
Dictator,
Doctor,
@@ -182,6 +185,7 @@ public enum CustomRoles
Express,
Farmer,
Farseer,
+ Forensic,
Divinator, // Fortune Teller
Gardener,
Gaulois,
@@ -296,6 +300,7 @@ public enum CustomRoles
Enderman,
Evolver,
Executioner,
+ Explosivist,
Totocalcio, // Follower
Gaslighter,
Glitch,
@@ -496,6 +501,7 @@ public enum CustomRoles
Energetic,
Entranced,
EvilSpirit,
+ Examiner,
Facilitator, // Ghost role
Finder,
Flashman,
@@ -556,6 +562,7 @@ public enum CustomRoles
Undead,
Underdog,
Unlucky,
+ Venom,
Warden, // Ghost role
Watcher,
Workhorse,
diff --git a/EHR.csproj b/EHR.csproj
index df22bae4d..0834b3bfb 100644
--- a/EHR.csproj
+++ b/EHR.csproj
@@ -27,11 +27,11 @@
-
+
-
+
diff --git a/Main.cs b/Main.cs
index d46e4784c..ae929adbe 100644
--- a/Main.cs
+++ b/Main.cs
@@ -58,7 +58,7 @@ public class Main : BasePlugin
public const string ModColor = "#00ffff";
public const bool AllowPublicRoom = true;
public const string ForkId = "EHR";
- public const string SupportedAUVersion = "2025.4.15";
+ public const string SupportedAUVersion = "2025.9.9";
public static readonly string DataPath =
#if ANDROID
@@ -175,7 +175,7 @@ public class Main : BasePlugin
private Harmony Harmony { get; } = new(PluginGuid);
- public static NormalGameOptionsV09 NormalOptions => GameOptionsManager.Instance != null ? GameOptionsManager.Instance.currentNormalGameOptions : null;
+ public static NormalGameOptionsV10 NormalOptions => GameOptionsManager.Instance != null ? GameOptionsManager.Instance.currentNormalGameOptions : null;
// Client Options
public static ConfigEntry HideName { get; private set; }
@@ -356,6 +356,7 @@ public class Main : BasePlugin
{ CustomRoles.GuardianAngel, "#77e6d1" },
{ CustomRoles.Tracker, "#34ad50" },
{ CustomRoles.Noisemaker, "#ff4a62" },
+ { CustomRoles.Detective, "#625EEE" },
// Vanilla Remakes
{ CustomRoles.CrewmateEHR, "#8cffff" },
{ CustomRoles.EngineerEHR, "#FF6A00" },
@@ -363,6 +364,7 @@ public class Main : BasePlugin
{ CustomRoles.GuardianAngelEHR, "#77e6d1" },
{ CustomRoles.TrackerEHR, "#34ad50" },
{ CustomRoles.NoisemakerEHR, "#ff4a62" },
+ { CustomRoles.DetectiveEHR, "#625EEE" },
// Crewmates
{ CustomRoles.DoubleAgent, "#ff1919" },
{ CustomRoles.Luckey, "#b8d7a3" },
@@ -455,7 +457,7 @@ public class Main : BasePlugin
{ CustomRoles.SpeedBooster, "#00ffff" },
{ CustomRoles.Doctor, "#80ffdd" },
{ CustomRoles.Dictator, "#df9b00" },
- { CustomRoles.Detective, "#7160e8" },
+ { CustomRoles.Forensic, "#7160e8" },
{ CustomRoles.NiceGuesser, "#f0e68c" },
{ CustomRoles.SwordsMan, "#7a7a7a" },
{ CustomRoles.Transporter, "#42D1FF" },
@@ -538,6 +540,7 @@ public class Main : BasePlugin
{ CustomRoles.Duality, "#8D6F64" },
{ CustomRoles.Thanos, "#F9D401" },
{ CustomRoles.SerialKiller, "#233fcc" },
+ { CustomRoles.Explosivist, "#D20103" },
{ CustomRoles.Slenderman, "#2c2e00" },
{ CustomRoles.Amogus, "#ff0000" },
{ CustomRoles.Weatherman, "#347deb" },
@@ -668,6 +671,8 @@ public class Main : BasePlugin
{ CustomRoles.Physicist, "#87e9ff" },
{ CustomRoles.Finder, "#32a879" },
{ CustomRoles.Noisy, "#e34fb2" },
+ { CustomRoles.Examiner, "#625EEE" },
+ { CustomRoles.Venom, "#ff1919" },
{ CustomRoles.Torch, "#eee5be" },
{ CustomRoles.Seer, "#61b26c" },
{ CustomRoles.Brakar, "#1447af" },
@@ -793,9 +798,9 @@ public class Main : BasePlugin
ClassInjector.RegisterTypeInIl2Cpp();
#endif
- NormalGameOptionsV09.RecommendedImpostors = NormalGameOptionsV09.MaxImpostors = Enumerable.Repeat(128, 128).ToArray();
- NormalGameOptionsV09.MinPlayers = Enumerable.Repeat(4, 128).ToArray();
- HideNSeekGameOptionsV09.MinPlayers = Enumerable.Repeat(4, 128).ToArray();
+ NormalGameOptionsV10.RecommendedImpostors = NormalGameOptionsV10.MaxImpostors = Enumerable.Repeat(128, 128).ToArray();
+ NormalGameOptionsV10.MinPlayers = Enumerable.Repeat(4, 128).ToArray();
+ HideNSeekGameOptionsV10.MinPlayers = Enumerable.Repeat(4, 128).ToArray();
PrivateTagManager.LoadTagsFromFile();
@@ -1017,6 +1022,7 @@ public enum CustomWinner
Necromancer = CustomRoles.Necromancer,
Wraith = CustomRoles.Wraith,
SerialKiller = CustomRoles.SerialKiller,
+ Explosivist = CustomRoles.Explosivist,
Thanos = CustomRoles.Thanos,
Duality = CustomRoles.Duality,
Slenderman = CustomRoles.Slenderman,
diff --git a/Modules/AURoleOptions.cs b/Modules/AURoleOptions.cs
index f3b85cc82..e00464255 100644
--- a/Modules/AURoleOptions.cs
+++ b/Modules/AURoleOptions.cs
@@ -120,6 +120,18 @@ public static class AURoleOptions
set => Opt.SetFloat(FloatOptionNames.TrackerDelay, value);
}
+ public static float ViperDissolveTime
+ {
+ get => Opt.GetFloat(FloatOptionNames.ViperDissolveTime);
+ set => Opt.SetFloat(FloatOptionNames.ViperDissolveTime, value);
+ }
+
+ public static float DetectiveSuspectLimit
+ {
+ get => Opt.GetFloat(FloatOptionNames.DetectiveSuspectLimit);
+ set => Opt.SetFloat(FloatOptionNames.DetectiveSuspectLimit, value);
+ }
+
public static void SetOpt(IGameOptions opt)
{
Opt = opt;
diff --git a/Modules/CustomNetObject.cs b/Modules/CustomNetObject.cs
index c0c31ef6c..d80523140 100644
--- a/Modules/CustomNetObject.cs
+++ b/Modules/CustomNetObject.cs
@@ -624,6 +624,7 @@ namespace EHR
protected override void OnFixedUpdate()
{
base.OnFixedUpdate();
+ if (Options.CurrentGameMode != CustomGameMode.BedWars) return;
timer -= Time.fixedDeltaTime;
if (timer <= 0f)
diff --git a/Modules/CustomRoleSelector.cs b/Modules/CustomRoleSelector.cs
index e971de868..728c3ae99 100644
--- a/Modules/CustomRoleSelector.cs
+++ b/Modules/CustomRoleSelector.cs
@@ -18,6 +18,8 @@ internal static class CustomRoleSelector
public static int AddNoisemakerNum;
public static int AddTrackerNum;
public static int AddPhantomNum;
+ public static int AddViperNum;
+ public static int AddDetectiveNum;
public static readonly Dictionary GameModeRoles = new()
{
@@ -905,6 +907,8 @@ internal static class CustomRoleSelector
AddNoisemakerNum = 0;
AddTrackerNum = 0;
AddPhantomNum = 0;
+ AddViperNum = 0;
+ AddDetectiveNum = 0;
foreach (CustomRoles role in RoleResult.Values)
{
@@ -930,6 +934,12 @@ internal static class CustomRoleSelector
case CustomRoles.Phantom:
AddPhantomNum++;
break;
+ case CustomRoles.Viper:
+ AddViperNum++;
+ break;
+ case CustomRoles.Detective:
+ AddDetectiveNum++;
+ break;
}
}
catch (Exception e) { Utils.ThrowException(e); }
@@ -955,7 +965,7 @@ internal static class CustomRoleSelector
case CustomRoles.Mare or CustomRoles.Glow or CustomRoles.Sleep when Main.CurrentMap == MapNames.Fungle:
case CustomRoles.Madmate when Options.MadmateSpawnMode.GetInt() != 0:
case CustomRoles.Lovers or CustomRoles.LastImpostor or CustomRoles.Workhorse or CustomRoles.Undead or CustomRoles.Insane:
- case CustomRoles.Nimble or CustomRoles.Physicist or CustomRoles.Bloodlust or CustomRoles.Finder or CustomRoles.Noisy: // Assigned at a different function due to role base change
+ case CustomRoles.Nimble or CustomRoles.Physicist or CustomRoles.Bloodlust or CustomRoles.Finder or CustomRoles.Noisy or CustomRoles.Examiner or CustomRoles.Venom: // Assigned at a different function due to role base change
continue;
}
diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs
index 953e8cd0b..103624d69 100644
--- a/Modules/CustomRolesHelper.cs
+++ b/Modules/CustomRolesHelper.cs
@@ -39,6 +39,7 @@ internal static class CustomRolesHelper
CustomRoles.PortalMaker,
CustomRoles.Gardener,
CustomRoles.Farmer,
+ CustomRoles.Explosivist,
// Add-ons
CustomRoles.Energetic,
@@ -279,7 +280,7 @@ internal static class CustomRolesHelper
CustomRoles.Escapee => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter,
CustomRoles.NiceGuesser => CustomRoles.Crewmate,
CustomRoles.EvilGuesser => CustomRoles.Impostor,
- CustomRoles.Detective => CustomRoles.Crewmate,
+ CustomRoles.Forensic => CustomRoles.Crewmate,
CustomRoles.God => CustomRoles.Crewmate,
CustomRoles.Tank => CustomRoles.Engineer,
CustomRoles.Technician => Technician.CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate,
@@ -339,7 +340,7 @@ internal static class CustomRolesHelper
CustomRoles.Devourer => CustomRoles.Shapeshifter,
CustomRoles.Deathpact => CustomRoles.Shapeshifter,
CustomRoles.Bloodhound => CustomRoles.Crewmate,
- CustomRoles.Tracker => CustomRoles.Crewmate,
+ CustomRoles.Scout => CustomRoles.Crewmate,
CustomRoles.Merchant => CustomRoles.Crewmate,
CustomRoles.Lookout => CustomRoles.Crewmate,
CustomRoles.Guardian => CustomRoles.Crewmate,
@@ -365,6 +366,8 @@ internal static class CustomRolesHelper
CustomRoles.ScientistEHR => CustomRoles.Scientist,
CustomRoles.TrackerEHR => CustomRoles.Tracker,
CustomRoles.NoisemakerEHR => CustomRoles.Noisemaker,
+ CustomRoles.DetectiveEHR => CustomRoles.Detective,
+ CustomRoles.ViperEHR => CustomRoles.Viper,
// Hide And Seek
CustomRoles.Hider => CustomRoles.Crewmate,
@@ -418,6 +421,8 @@ internal static class CustomRolesHelper
CustomRoles.Impostor => CustomRoles.ImpostorEHR,
CustomRoles.Phantom => CustomRoles.PhantomEHR,
CustomRoles.Shapeshifter => CustomRoles.ShapeshifterEHR,
+ CustomRoles.Detective => CustomRoles.DetectiveEHR,
+ CustomRoles.Viper => CustomRoles.ViperEHR,
_ => role.IsImpostor() ? CustomRoles.ImpostorEHR : CustomRoles.CrewmateEHR
};
}
@@ -482,6 +487,7 @@ internal static class CustomRolesHelper
CustomRoles.Poisoner => RoleTypes.Impostor,
CustomRoles.Duality => RoleTypes.Impostor,
CustomRoles.SerialKiller => RoleTypes.Impostor,
+ CustomRoles.Explosivist => RoleTypes.Impostor,
CustomRoles.Thanos => RoleTypes.Shapeshifter,
CustomRoles.Slenderman => RoleTypes.Impostor,
CustomRoles.Amogus => RoleTypes.Impostor,
@@ -611,6 +617,7 @@ internal static class CustomRolesHelper
CustomRoles.Rogue or
CustomRoles.Parasite or
CustomRoles.SerialKiller or
+ CustomRoles.Explosivist or
CustomRoles.Thanos or
CustomRoles.Duality or
CustomRoles.Slenderman or
@@ -673,6 +680,8 @@ internal static class CustomRolesHelper
CustomRoles.PhantomEHR or
CustomRoles.Shapeshifter or
CustomRoles.ShapeshifterEHR or
+ CustomRoles.Viper or
+ CustomRoles.ViperEHR or
CustomRoles.LovingImpostor or
CustomRoles.Godfather or
CustomRoles.EvilDiviner or
@@ -1029,6 +1038,7 @@ internal static class CustomRolesHelper
CustomRoles.Wraith or
CustomRoles.RouleteGrandeur or
CustomRoles.Enderman or
+ CustomRoles.Explosivist or
CustomRoles.Hookshot or
CustomRoles.Mycologist or
CustomRoles.Magician or
@@ -1052,12 +1062,12 @@ internal static class CustomRolesHelper
{
return role.IsAdditionRole() && (!Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> neverList) || !neverList.TryGetValue(pc.GetCustomRole(), out List bannedAddonList) || !bannedAddonList.Contains(role)) && pc.GetCustomRole() is not CustomRoles.GuardianAngelEHR and not CustomRoles.God && !pc.Is(CustomRoles.Madmate) && !pc.Is(CustomRoles.GM) && role is not CustomRoles.Lovers && !pc.Is(CustomRoles.Needy) && (!pc.HasSubRole() || pc.GetCustomSubRoles().Count < Options.NoLimitAddonsNumMax.GetInt()) && (!Options.AddonCanBeSettings.TryGetValue(role, out (OptionItem Imp, OptionItem Neutral, OptionItem Crew, OptionItem Coven) o) || ((o.Imp.GetBool() || !pc.GetCustomRole().IsImpostor()) && (o.Neutral.GetBool() || !pc.GetCustomRole().IsNeutral()) && (o.Crew.GetBool() || !pc.IsCrewmate()) && (o.Coven.GetBool() || !pc.Is(Team.Coven)))) && (!role.IsImpOnlyAddon() || (pc.IsImpostor() && !pc.Is(CustomRoles.DoubleAgent)) || (pc.Is(CustomRoles.Traitor) && Traitor.CanGetImpostorOnlyAddons.GetBool())) && role switch
{
- CustomRoles.Commited when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter) => false,
+ CustomRoles.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,
CustomRoles.Blocked when !pc.CanUseVent() => false,
CustomRoles.Aide when pc.IsMadmate() || pc.Is(CustomRoles.Saboteur) => false,
- CustomRoles.Sleuth when pc.GetCustomRole() is CustomRoles.NecroGuesser or CustomRoles.Imitator or CustomRoles.Detective => false,
+ CustomRoles.Sleuth when pc.GetCustomRole() is CustomRoles.NecroGuesser or CustomRoles.Imitator or CustomRoles.Forensic => false,
CustomRoles.Introvert when pc.GetCustomRole() is CustomRoles.Leery or CustomRoles.Samurai or CustomRoles.Arsonist or CustomRoles.Revolutionist or CustomRoles.Farseer or CustomRoles.Scavenger or CustomRoles.Analyst => false,
CustomRoles.Circumvent when pc.GetCustomRole() is CustomRoles.Swooper or CustomRoles.RiftMaker=> false,
CustomRoles.Oblivious when pc.Is(CustomRoles.Altruist) => false,
@@ -1070,7 +1080,7 @@ internal static class CustomRolesHelper
CustomRoles.Magnet when pc.Is(Team.Impostor) => false,
CustomRoles.Swift when pc.Is(CustomRoles.Magnet) => false,
CustomRoles.Oblivious when pc.Is(CustomRoles.Amnesiac) && Amnesiac.RememberMode.GetValue() == 0 => false,
- CustomRoles.Rookie when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter) => false,
+ CustomRoles.Rookie when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter or RoleTypes.Viper) => false,
CustomRoles.Madmate when pc.Is(CustomRoles.Sidekick) => false,
CustomRoles.Autopsy when pc.Is(CustomRoles.Doctor) || pc.Is(CustomRoles.Tracefinder) || pc.Is(CustomRoles.Scientist) || pc.Is(CustomRoles.ScientistEHR) || pc.Is(CustomRoles.Sunnyboy) => false,
CustomRoles.Necroview when pc.Is(CustomRoles.Doctor) => false,
@@ -1088,11 +1098,11 @@ internal static class CustomRolesHelper
CustomRoles.Sunglasses when pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.GuardianAngelEHR) || pc.Is(CustomRoles.Bewilder) => false,
CustomRoles.Guesser when pc.GetCustomRole() is CustomRoles.EvilGuesser or CustomRoles.NiceGuesser or CustomRoles.Doomsayer or CustomRoles.CopyCat => false,
CustomRoles.Madmate when !pc.CanBeMadmate() || pc.Is(CustomRoles.Egoist) || pc.Is(CustomRoles.Rascal) => false,
- CustomRoles.Oblivious when pc.Is(CustomRoles.Detective) || pc.Is(CustomRoles.Cleaner) || pc.Is(CustomRoles.Medusa) || pc.Is(CustomRoles.Mortician) || pc.Is(CustomRoles.Mediumshiper) || pc.Is(CustomRoles.GuardianAngelEHR) => false,
+ CustomRoles.Oblivious when pc.Is(CustomRoles.Forensic) || pc.Is(CustomRoles.Cleaner) || pc.Is(CustomRoles.Medusa) || pc.Is(CustomRoles.Mortician) || pc.Is(CustomRoles.Mediumshiper) || pc.Is(CustomRoles.GuardianAngelEHR) => false,
CustomRoles.Youtuber when !pc.IsCrewmate() || pc.Is(CustomRoles.Madmate) || pc.Is(CustomRoles.Sheriff) || pc.Is(CustomRoles.GuardianAngelEHR) => false,
CustomRoles.Egoist when !pc.IsImpostor() => false,
CustomRoles.Damocles when pc.GetCustomRole() is CustomRoles.Bomber or CustomRoles.Nuker or CustomRoles.Mercenary or CustomRoles.Cantankerous => false,
- CustomRoles.Damocles when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter) => false,
+ CustomRoles.Damocles when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter or RoleTypes.Viper) => false,
CustomRoles.Flashman when pc.Is(CustomRoles.Swiftclaw) || pc.Is(CustomRoles.Giant) || pc.Is(CustomRoles.Spurt) || pc.GetCustomRole() is CustomRoles.Tank or CustomRoles.Zombie => false,
CustomRoles.Giant when pc.Is(CustomRoles.Flashman) || pc.Is(CustomRoles.Spurt) || pc.Is(CustomRoles.RoomRusher) => false,
CustomRoles.Necroview when pc.Is(CustomRoles.Visionary) => false,
@@ -1140,9 +1150,9 @@ internal static class CustomRolesHelper
CustomRoles.Swift when pc.Is(CustomRoles.Mafia) => false,
CustomRoles.Reach when pc.GetCustomRole() is CustomRoles.Mafioso or CustomRoles.Evolver => false,
CustomRoles.Trapper when pc.Is(CustomRoles.GuardianAngelEHR) => false,
- CustomRoles.Reach when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter) => false,
- CustomRoles.Magnet when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter) => false,
- CustomRoles.Haste when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter) || !pc.CanUseImpostorVentButton() => false,
+ CustomRoles.Reach when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter or RoleTypes.Viper) => false,
+ CustomRoles.Magnet when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter or RoleTypes.Viper) => false,
+ CustomRoles.Haste when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter or RoleTypes.Viper) || !pc.CanUseImpostorVentButton() => false,
CustomRoles.Diseased when pc.Is(CustomRoles.Antidote) => false,
CustomRoles.Antidote when pc.Is(CustomRoles.Diseased) => false,
CustomRoles.Flashman or CustomRoles.Giant when pc.GetCustomRole() is CustomRoles.Swooper or CustomRoles.Wraith or CustomRoles.Chameleon or CustomRoles.Alchemist or CustomRoles.Ankylosaurus => false,
@@ -1153,6 +1163,8 @@ internal static class CustomRolesHelper
CustomRoles.Physicist when !pc.IsCrewmate() || pc.GetCustomRole().IsDesyncRole() || (pc.Is(CustomRoles.TimeMaster) && !TimeMaster.TimeMasterCanUseVitals.GetBool()) => false,
CustomRoles.Finder when !pc.IsCrewmate() || pc.GetCustomRole().IsDesyncRole() => false,
CustomRoles.Noisy when !pc.IsCrewmate() || pc.GetCustomRole().IsDesyncRole() => false,
+ CustomRoles.Examiner when !pc.IsCrewmate() || pc.GetCustomRole().IsDesyncRole() => false,
+ CustomRoles.Venom when !pc.IsImpostor() => false,
CustomRoles.Unreportable when pc.Is(CustomRoles.Bait) => false,
CustomRoles.Oblivious when pc.Is(CustomRoles.Bloodhound) => false,
CustomRoles.Oblivious when pc.Is(CustomRoles.Vulture) => false,
@@ -1284,6 +1296,8 @@ internal static class CustomRolesHelper
CustomRoles.Scientist or
CustomRoles.GuardianAngel or
CustomRoles.Impostor or
+ CustomRoles.Detective or
+ CustomRoles.Viper or
CustomRoles.Phantom or
CustomRoles.Shapeshifter;
}
@@ -1314,30 +1328,6 @@ internal static class CustomRolesHelper
return Options.GetRoleSpawnMode(role);
}
- public static float GetChance(this CustomRoles role)
- {
- if (role.IsVanilla())
- {
- IRoleOptionsCollection roleOpt = Main.NormalOptions.RoleOptions;
-
- return role switch
- {
- CustomRoles.Engineer => roleOpt.GetChancePerGame(RoleTypes.Engineer),
- CustomRoles.Noisemaker => roleOpt.GetChancePerGame(RoleTypes.Noisemaker),
- CustomRoles.Tracker => roleOpt.GetChancePerGame(RoleTypes.Tracker),
- CustomRoles.Scientist => roleOpt.GetChancePerGame(RoleTypes.Scientist),
- CustomRoles.Impostor => roleOpt.GetChancePerGame(RoleTypes.Impostor),
- CustomRoles.Phantom => roleOpt.GetChancePerGame(RoleTypes.Phantom),
- CustomRoles.Shapeshifter => roleOpt.GetChancePerGame(RoleTypes.Shapeshifter),
- CustomRoles.GuardianAngel => roleOpt.GetChancePerGame(RoleTypes.GuardianAngel),
- CustomRoles.Crewmate => roleOpt.GetChancePerGame(RoleTypes.Crewmate),
- _ => 0
- } / 100f;
- }
-
- return Options.GetRoleChance(role);
- }
-
public static bool IsEnable(this CustomRoles role)
{
return role switch
@@ -1441,7 +1431,7 @@ internal static class CustomRolesHelper
RoleOptionType.Impostor_Concealing => Utils.GetRoleColor(CustomRoles.CopyCat),
RoleOptionType.Impostor_Miscellaneous => Palette.ImpostorRed,
RoleOptionType.Crewmate_Miscellaneous => Palette.CrewmateBlue,
- RoleOptionType.Crewmate_Investigate => Utils.GetRoleColor(CustomRoles.Detective),
+ RoleOptionType.Crewmate_Investigate => Utils.GetRoleColor(CustomRoles.Forensic),
RoleOptionType.Crewmate_Support => Utils.GetRoleColor(CustomRoles.NiceEraser),
RoleOptionType.Crewmate_Killing => Utils.GetRoleColor(CustomRoles.Sheriff),
RoleOptionType.Crewmate_Power => Utils.GetRoleColor(CustomRoles.Electric),
@@ -1650,6 +1640,8 @@ internal static class CustomRolesHelper
CustomRoles.ShapeshifterEHR => RoleOptionType.Impostor_Miscellaneous,
CustomRoles.Phantom => RoleOptionType.Impostor_Miscellaneous,
CustomRoles.PhantomEHR => RoleOptionType.Impostor_Miscellaneous,
+ CustomRoles.Viper => RoleOptionType.Impostor_Miscellaneous,
+ CustomRoles.ViperEHR => RoleOptionType.Impostor_Miscellaneous,
CustomRoles.DoubleAgent => RoleOptionType.Crewmate_Investigate,
_ => role.IsCrewmate() ? RoleOptionType.Crewmate_Miscellaneous : RoleOptionType.Neutral_Benign
};
@@ -1669,6 +1661,8 @@ internal static class CustomRolesHelper
CustomRoles.TrackerEHR => RoleOptionType.Crewmate_Miscellaneous,
CustomRoles.Noisemaker => RoleOptionType.Crewmate_Miscellaneous,
CustomRoles.NoisemakerEHR => RoleOptionType.Crewmate_Miscellaneous,
+ CustomRoles.Detective => RoleOptionType.Crewmate_Miscellaneous,
+ CustomRoles.DetectiveEHR => RoleOptionType.Crewmate_Miscellaneous,
CustomRoles.Addict => RoleOptionType.Crewmate_Miscellaneous,
CustomRoles.CameraMan => RoleOptionType.Crewmate_Miscellaneous,
CustomRoles.Demolitionist => RoleOptionType.Crewmate_Miscellaneous,
@@ -1692,7 +1686,7 @@ internal static class CustomRolesHelper
CustomRoles.Chameleon => RoleOptionType.Crewmate_Investigate,
CustomRoles.Clairvoyant => RoleOptionType.Crewmate_Investigate,
CustomRoles.Bloodhound => RoleOptionType.Crewmate_Investigate,
- CustomRoles.Detective => RoleOptionType.Crewmate_Investigate,
+ CustomRoles.Forensic => RoleOptionType.Crewmate_Investigate,
CustomRoles.Doctor => RoleOptionType.Crewmate_Investigate,
CustomRoles.DoubleAgent => RoleOptionType.Crewmate_Investigate,
CustomRoles.Druid => RoleOptionType.Crewmate_Investigate,
@@ -1863,6 +1857,7 @@ public enum CountTypes
HexMaster,
Wraith,
SerialKiller,
+ Explosivist,
Thanos,
Duality,
Slenderman,
diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs
index 7072d4da1..46113568e 100644
--- a/Modules/ExtendedPlayerControl.cs
+++ b/Modules/ExtendedPlayerControl.cs
@@ -477,6 +477,8 @@ internal static class ExtendedPlayerControl
CustomRoles.Physicist when newRoleType == RoleTypes.Crewmate => RoleTypes.Scientist,
CustomRoles.Finder when newRoleType is RoleTypes.Crewmate or RoleTypes.Scientist or RoleTypes.Noisemaker => RoleTypes.Tracker,
CustomRoles.Noisy when newRoleType == RoleTypes.Crewmate => RoleTypes.Noisemaker,
+ CustomRoles.Examiner when newRoleType is RoleTypes.Crewmate or RoleTypes.Scientist or RoleTypes.Noisemaker => RoleTypes.Detective,
+ CustomRoles.Venom when newRoleType == RoleTypes.Impostor => RoleTypes.Viper,
_ => newRoleType
},
_ => newRoleType
@@ -1337,7 +1339,7 @@ internal static class ExtendedPlayerControl
{
CustomRoles role = pc.GetCustomRole();
if (pc.Data.Role.Role == RoleTypes.GuardianAngel) return false;
- if (role.GetVNRole(true) is CustomRoles.Impostor or CustomRoles.ImpostorEHR or CustomRoles.Shapeshifter or CustomRoles.ShapeshifterEHR or CustomRoles.Phantom or CustomRoles.PhantomEHR) return true;
+ if (role.GetVNRole(true) is CustomRoles.Impostor or CustomRoles.ImpostorEHR or CustomRoles.Shapeshifter or CustomRoles.ShapeshifterEHR or CustomRoles.Phantom or CustomRoles.PhantomEHR or CustomRoles.Viper or CustomRoles.ViperEHR) return true;
if (pc.GetRoleTypes() is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom) return true;
if (pc.Is(CustomRoles.Bloodlust)) return true;
return !HasTasks(pc.Data, false);
@@ -2112,6 +2114,8 @@ internal static class ExtendedPlayerControl
{ } x when x.Contains(CustomRoles.Physicist) => RoleTypes.Scientist,
{ } x when x.Contains(CustomRoles.Finder) => RoleTypes.Tracker,
{ } x when x.Contains(CustomRoles.Noisy) => RoleTypes.Noisemaker,
+ { } x when x.Contains(CustomRoles.Examiner) => RoleTypes.Detective,
+ { } x when x.Contains(CustomRoles.Venom) => RoleTypes.Viper,
_ => pc.GetCustomRole().GetRoleTypes()
};
}
diff --git a/Modules/Extensions/IGameManagerEx.cs b/Modules/Extensions/IGameManagerEx.cs
index 276413e32..380052bf2 100644
--- a/Modules/Extensions/IGameManagerEx.cs
+++ b/Modules/Extensions/IGameManagerEx.cs
@@ -9,13 +9,13 @@ public static class IGameManagerEx
opt.SetBool(name, value);
}
- public static void Set(this BoolOptionNames name, bool value, NormalGameOptionsV09 opt)
+ public static void Set(this BoolOptionNames name, bool value, NormalGameOptionsV10 opt)
{
if (name is not BoolOptionNames.GhostsDoTasks and not BoolOptionNames.Roles)
opt.SetBool(name, value);
}
- public static void Set(this BoolOptionNames name, bool value, HideNSeekGameOptionsV09 opt)
+ public static void Set(this BoolOptionNames name, bool value, HideNSeekGameOptionsV10 opt)
{
opt.SetBool(name, value);
}
@@ -25,12 +25,12 @@ public static class IGameManagerEx
opt.SetInt(name, value);
}
- public static void Set(this Int32OptionNames name, int value, NormalGameOptionsV09 opt)
+ public static void Set(this Int32OptionNames name, int value, NormalGameOptionsV10 opt)
{
opt.SetInt(name, value);
}
- public static void Set(this Int32OptionNames name, int value, HideNSeekGameOptionsV09 opt)
+ public static void Set(this Int32OptionNames name, int value, HideNSeekGameOptionsV10 opt)
{
opt.SetInt(name, value);
}
@@ -40,12 +40,12 @@ public static class IGameManagerEx
opt.SetFloat(name, value);
}
- public static void Set(this FloatOptionNames name, float value, NormalGameOptionsV09 opt)
+ public static void Set(this FloatOptionNames name, float value, NormalGameOptionsV10 opt)
{
opt.SetFloat(name, value);
}
- public static void Set(this FloatOptionNames name, float value, HideNSeekGameOptionsV09 opt)
+ public static void Set(this FloatOptionNames name, float value, HideNSeekGameOptionsV10 opt)
{
opt.SetFloat(name, value);
}
@@ -55,12 +55,12 @@ public static class IGameManagerEx
opt.SetByte(name, value);
}
- public static void Set(this ByteOptionNames name, byte value, NormalGameOptionsV09 opt)
+ public static void Set(this ByteOptionNames name, byte value, NormalGameOptionsV10 opt)
{
opt.SetByte(name, value);
}
- public static void Set(this ByteOptionNames name, byte value, HideNSeekGameOptionsV09 opt)
+ public static void Set(this ByteOptionNames name, byte value, HideNSeekGameOptionsV10 opt)
{
opt.SetByte(name, value);
}
@@ -70,12 +70,12 @@ public static class IGameManagerEx
opt.SetUInt(name, value);
}
- public static void Set(this UInt32OptionNames name, uint value, NormalGameOptionsV09 opt)
+ public static void Set(this UInt32OptionNames name, uint value, NormalGameOptionsV10 opt)
{
opt.SetUInt(name, value);
}
- public static void Set(this UInt32OptionNames name, uint value, HideNSeekGameOptionsV09 opt)
+ public static void Set(this UInt32OptionNames name, uint value, HideNSeekGameOptionsV10 opt)
{
opt.SetUInt(name, value);
}
diff --git a/Modules/GameOptionsSender/GameOptionsSender.cs b/Modules/GameOptionsSender/GameOptionsSender.cs
index c899e4cd6..a104c62c5 100644
--- a/Modules/GameOptionsSender/GameOptionsSender.cs
+++ b/Modules/GameOptionsSender/GameOptionsSender.cs
@@ -24,10 +24,10 @@ public abstract class GameOptionsSender
writer.StartMessage(0);
writer.Write((byte)opt.GameMode);
- if (opt.TryCast(out NormalGameOptionsV09 normalOpt))
- NormalGameOptionsV09.Serialize(writer, normalOpt);
- else if (opt.TryCast(out HideNSeekGameOptionsV09 hnsOpt))
- HideNSeekGameOptionsV09.Serialize(writer, hnsOpt);
+ if (opt.TryCast(out NormalGameOptionsV10 normalOpt))
+ NormalGameOptionsV10.Serialize(writer, normalOpt);
+ else if (opt.TryCast(out HideNSeekGameOptionsV10 hnsOpt))
+ HideNSeekGameOptionsV10.Serialize(writer, hnsOpt);
else
{
writer.Recycle();
diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs
index 55eef6f89..9cfec7608 100644
--- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs
+++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs
@@ -4,6 +4,7 @@ using AmongUs.GameOptions;
using EHR.AddOns.Common;
using EHR.AddOns.Crewmate;
using EHR.AddOns.GhostRoles;
+using EHR.AddOns.Impostor;
using EHR.Coven;
using EHR.Crewmate;
using EHR.Impostor;
@@ -22,7 +23,7 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS
public PlayerControl player = player;
private static IGameOptions BasedGameOptions =>
- Main.RealOptionsData.Restore(new NormalGameOptionsV09(new UnityLogger().CastFast()).CastFast());
+ Main.RealOptionsData.Restore(new NormalGameOptionsV10(new UnityLogger().CastFast()).CastFast());
protected override bool IsDirty { get; set; }
@@ -254,6 +255,12 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS
AURoleOptions.TrackerDuration = CrewmateVanillaRoles.TrackerDuration.GetFloat();
AURoleOptions.TrackerDelay = CrewmateVanillaRoles.TrackerDelay.GetFloat();
break;
+ case CustomRoles.DetectiveEHR:
+ AURoleOptions.DetectiveSuspectLimit = CrewmateVanillaRoles.DetectiveSuspectLimit.GetFloat();
+ break;
+ case CustomRoles.ViperEHR:
+ AURoleOptions.ViperDissolveTime = ImpostorVanillaRoles.ViperDissolveTime.GetFloat();
+ break;
}
// When impostor alert is off, and the player is a desync crewmate, set impostor alert as true
@@ -425,6 +432,12 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS
AURoleOptions.NoisemakerImpostorAlert = Noisy.NoisyImpostorAlert.GetBool();
AURoleOptions.NoisemakerAlertDuration = Noisy.NoisyAlertDuration.GetFloat();
break;
+ case CustomRoles.Examiner when roleTypes == RoleTypes.Detective:
+ AURoleOptions.DetectiveSuspectLimit = Examiner.ExaminerSuspectLimit.GetFloat();
+ break;
+ case CustomRoles.Venom when roleTypes == RoleTypes.Viper:
+ AURoleOptions.ViperDissolveTime = Venom.VenomDissolveTime.GetFloat();
+ break;
}
}
@@ -458,6 +471,12 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS
case RoleTypes.Tracker:
AURoleOptions.TrackerCooldown *= 0.75f;
break;
+ case RoleTypes.Viper:
+ AURoleOptions.ViperDissolveTime *= 0.75f;
+ break;
+ case RoleTypes.Detective:
+ AURoleOptions.DetectiveSuspectLimit *= 1.25f;
+ goto default;
case RoleTypes.CrewmateGhost:
case RoleTypes.ImpostorGhost:
break;
diff --git a/Modules/OptionBackup/OptionBackupData.cs b/Modules/OptionBackup/OptionBackupData.cs
index 5ce8661a9..a22c795b9 100644
--- a/Modules/OptionBackup/OptionBackupData.cs
+++ b/Modules/OptionBackup/OptionBackupData.cs
@@ -42,7 +42,7 @@ public class OptionBackupData
// Since TryGetUInt is not implemented, get it separately
AllValues.Add(new UIntOptionBackupValue(UInt32OptionNames.Keywords, (uint)option.Keywords));
- RoleTypes[] array = [RoleTypes.Scientist, RoleTypes.Engineer, RoleTypes.GuardianAngel, RoleTypes.Shapeshifter, RoleTypes.Noisemaker, RoleTypes.Phantom, RoleTypes.Tracker];
+ RoleTypes[] array = [RoleTypes.Scientist, RoleTypes.Engineer, RoleTypes.GuardianAngel, RoleTypes.Shapeshifter, RoleTypes.Noisemaker, RoleTypes.Phantom, RoleTypes.Tracker, RoleTypes.Viper, RoleTypes.Detective];
foreach (RoleTypes role in array) AllValues.Add(new RoleRateBackupValue(role, option.RoleOptions.GetNumPerGame(role), option.RoleOptions.GetChancePerGame(role)));
}
diff --git a/Modules/Utils.cs b/Modules/Utils.cs
index b57c6dfce..be898b186 100644
--- a/Modules/Utils.cs
+++ b/Modules/Utils.cs
@@ -839,6 +839,7 @@ public static class Utils
case CustomRoles.Eclipse:
case CustomRoles.Pyromaniac:
case CustomRoles.SerialKiller:
+ case CustomRoles.Explosivist:
case CustomRoles.Thanos:
case CustomRoles.Slenderman:
case CustomRoles.Amogus:
@@ -3269,6 +3270,7 @@ public static class Utils
CustomRoles.Tiger => Tiger.EnrageCooldown.GetInt() + (includeDuration ? Tiger.EnrageDuration.GetInt() : 0),
CustomRoles.Nonplus => Nonplus.BlindCooldown.GetInt() + (includeDuration ? Nonplus.BlindDuration.GetInt() : 0),
CustomRoles.Amogus => Amogus.AbilityCooldown.GetInt() + (includeDuration ? Amogus.AbilityDuration.GetInt() : 0),
+ CustomRoles.Explosivist => Explosivist.AbilityCooldown.GetInt() + (includeDuration ? Explosivist.ExplosionDelay.GetInt() : 0),
CustomRoles.Cherokious => Cherokious.KillCooldown.GetInt(),
CustomRoles.Shifter => Shifter.KillCooldown.GetInt(),
CustomRoles.NoteKiller => NoteKiller.AbilityCooldown.GetInt(),
@@ -4105,7 +4107,7 @@ public static class Utils
{
int baseColorId = deadBodyParent.Data.DefaultOutfit.ColorId;
deadBodyParent.Data.DefaultOutfit.ColorId = colorId;
- DeadBody deadBody = Object.Instantiate(GameManager.Instance.DeadBodyPrefab);
+ DeadBody deadBody = Object.Instantiate(GameManager.Instance.deadBodyPrefab[0]);
deadBody.enabled = false;
deadBody.ParentId = deadBodyParent.PlayerId;
foreach (SpriteRenderer b in deadBody.bodyRenderers)
diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs
index 73d4a9109..19553edaf 100644
--- a/Patches/ChatBubblePatch.cs
+++ b/Patches/ChatBubblePatch.cs
@@ -23,7 +23,8 @@ internal static class ChatBubbleSetNamePatch
if (GameStates.IsInGame && !voted && seer.PlayerId == target.PlayerId) __instance.NameText.color = seer.GetRoleColor();
- if (seer.GetCustomRole().GetDYRole() is RoleTypes.Shapeshifter or RoleTypes.Phantom) __instance.NameText.color = Color.white;
+ if (seer.GetCustomRole().GetDYRole() is RoleTypes.Shapeshifter or RoleTypes.Phantom or RoleTypes.Viper)
+ __instance.NameText.color = Color.white;
if (Main.DarkTheme.Value)
{
diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs
index fb3094d32..6ff7e380c 100644
--- a/Patches/ChatCommandPatch.cs
+++ b/Patches/ChatCommandPatch.cs
@@ -3238,6 +3238,30 @@ internal static class ChatCommands
subArgs = args.Length < 3 ? "" : args[2];
GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.TrackerDelay, float.Parse(subArgs));
break;
+ case "vipercount":
+ subArgs = args.Length < 3 ? "" : args[2];
+ GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Viper, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Viper));
+ break;
+ case "viperchance":
+ subArgs = args.Length < 3 ? "" : args[2];
+ GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Viper, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Viper), int.Parse(subArgs));
+ break;
+ case "viperdissolvetime":
+ subArgs = args.Length < 3 ? "" : args[2];
+ GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ViperDissolveTime, float.Parse(subArgs));
+ break;
+ case "detectivecount":
+ subArgs = args.Length < 3 ? "" : args[2];
+ GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Detective, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Detective));
+ break;
+ case "detectivechance":
+ subArgs = args.Length < 3 ? "" : args[2];
+ GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Detective, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Detective), int.Parse(subArgs));
+ break;
+ case "detectivesuspectlimit":
+ subArgs = args.Length < 3 ? "" : args[2];
+ GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.DetectiveSuspectLimit, float.Parse(subArgs));
+ break;
default:
Utils.SendMessage(GetString("Commands.ChangeSettingHelp"), player.PlayerId);
break;
@@ -3328,7 +3352,7 @@ internal static class ChatCommands
"擺爛人" or "摆烂" => GetString("Needy"),
"獨裁者" or "独裁" or "dict" => GetString("Dictator"),
"法醫" or "doc" => GetString("Doctor"),
- "偵探" or "det" => GetString("Detective"),
+ "偵探" or "det" => GetString("Forensic"),
"幸運兒" or "幸运" => GetString("Luckey"),
"大明星" or "明星" or "ss" => GetString("SuperStar"),
"demo" => GetString("Demolitionist"),
diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs
index 740520d37..6ae72f7bf 100644
--- a/Patches/CheckGameEndPatch.cs
+++ b/Patches/CheckGameEndPatch.cs
@@ -1101,7 +1101,7 @@ internal static class GameEndChecker
if ((GameData.Instance.TotalTasks == 0 && GameData.Instance.CompletedTasks == 0) || !Main.PlayerStates.Values.Any(x => x.TaskState.HasTasks)) return false;
if (Options.DisableTaskWinIfAllCrewsAreDead.GetBool() && !Main.AllAlivePlayerControls.Any(x => x.Is(CustomRoleTypes.Crewmate))) return false;
- if (Options.DisableTaskWinIfAllCrewsAreConverted.GetBool() && Main.AllAlivePlayerControls.Where(x => x.Is(Team.Crewmate) && x.GetRoleTypes() is RoleTypes.Crewmate or RoleTypes.Engineer or RoleTypes.Scientist or RoleTypes.Noisemaker or RoleTypes.Tracker or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel).All(x => x.IsConverted())) return false;
+ if (Options.DisableTaskWinIfAllCrewsAreConverted.GetBool() && Main.AllAlivePlayerControls.Where(x => x.Is(Team.Crewmate) && x.GetRoleTypes() is RoleTypes.Crewmate or RoleTypes.Engineer or RoleTypes.Scientist or RoleTypes.Noisemaker or RoleTypes.Tracker or RoleTypes.Detective or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel).All(x => x.IsConverted())) return false;
if (GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks)
{
diff --git a/Patches/Crowded.cs b/Patches/Crowded.cs
index efcb58a63..d8b99a502 100644
--- a/Patches/Crowded.cs
+++ b/Patches/Crowded.cs
@@ -266,11 +266,11 @@ internal static class Crowded
}
}
- [HarmonyPatch(typeof(NormalGameOptionsV09), nameof(NormalGameOptionsV09.AreInvalid))]
+ [HarmonyPatch(typeof(NormalGameOptionsV10), nameof(NormalGameOptionsV10.AreInvalid))]
public static class InvalidOptionsPatches
{
[SuppressMessage("ReSharper", "UnusedMember.Global")]
- public static bool Prefix(NormalGameOptionsV09 __instance, [HarmonyArgument(0)] int maxExpectedPlayers)
+ public static bool Prefix(NormalGameOptionsV10 __instance, [HarmonyArgument(0)] int maxExpectedPlayers)
{
return __instance.MaxPlayers > maxExpectedPlayers ||
__instance.NumImpostors < 1 ||
diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs
index a79ba7323..997e167be 100644
--- a/Patches/HudPatch.cs
+++ b/Patches/HudPatch.cs
@@ -219,6 +219,7 @@ internal static class HudManagerPatch
RoleTypes roleTypes = player.GetRoleTypes();
__instance.AbilityButton?.OverrideText(GetString($"AbilityButtonText.{roleTypes}"));
+ __instance.SecondaryAbilityButton?.OverrideText(GetString($"SecondaryAbilityButtonText.{roleTypes}"));
PlayerState state = Main.PlayerStates[player.PlayerId];
diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs
index ebf77fd39..61e50f264 100644
--- a/Patches/IntroPatch.cs
+++ b/Patches/IntroPatch.cs
@@ -711,6 +711,14 @@ internal static class BeginCrewmatePatch
or CustomRoles.EvilTracker
or CustomRoles.Scout
=> GetIntroSound(RoleTypes.Tracker),
+
+ CustomRoles.Viper
+ or CustomRoles.ViperEHR
+ => GetIntroSound(RoleTypes.Viper),
+
+ CustomRoles.Detective
+ or CustomRoles.DetectiveEHR
+ => GetIntroSound(RoleTypes.Detective),
CustomRoles.Noisemaker
or CustomRoles.NoisemakerEHR
@@ -921,7 +929,7 @@ internal static class BeginCrewmatePatch
private static AudioClip GetIntroSound(RoleTypes roleType)
{
- return RoleManager.Instance.AllRoles.FirstOrDefault(role => role.Role == roleType)?.IntroSound;
+ return RoleManager.Instance.AllRoles.Find((Il2CppSystem.Predicate)(role => role.Role == roleType))?.IntroSound;
}
}
diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs
index 97c420e1e..a4d49ff38 100644
--- a/Patches/MeetingHudPatch.cs
+++ b/Patches/MeetingHudPatch.cs
@@ -776,7 +776,7 @@ internal static class MeetingHudStartPatch
AddMsg(string.Format(GetString("SilencerDead"), playername, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Silencer), GetString("SilencerKillTitle"))));
}
- if (Detective.DetectiveNotify.TryGetValue(pc.PlayerId, out string value1)) AddMsg(value1, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Detective), GetString("DetectiveNoticeTitle")));
+ if (Forensic.ForensicNotify.TryGetValue(pc.PlayerId, out string value1)) AddMsg(value1, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Forensic), GetString("ForensicNoticeTitle")));
if (Main.SleuthMsgs.TryGetValue(pc.PlayerId, out string msg)) AddMsg(msg, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Sleuth), GetString("Sleuth")));
if (pc.Is(CustomRoles.Mimic) && !pc.IsAlive())
@@ -815,7 +815,7 @@ internal static class MeetingHudStartPatch
Main.SuperStarDead.Clear();
Express.SpeedNormal.Clear();
Express.SpeedUp.Clear();
- Detective.DetectiveNotify.Clear();
+ Forensic.ForensicNotify.Clear();
Main.SleuthMsgs.Clear();
Virus.VirusNotify.Clear();
Mortician.MsgToSend.Clear();
diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs
index 83a1666ab..843e72589 100644
--- a/Patches/OnGameStartedPatch.cs
+++ b/Patches/OnGameStartedPatch.cs
@@ -170,7 +170,9 @@ internal static class ChangeRoleSettings
RoleTypes.Shapeshifter,
RoleTypes.Noisemaker,
RoleTypes.Phantom,
- RoleTypes.Tracker
+ RoleTypes.Tracker,
+ RoleTypes.Detective,
+ RoleTypes.Viper
}.Do(x => Main.NormalOptions.roleOptions.SetRoleRate(x, 0, 0));
if (Main.NormalOptions.MapId > 5 && !(Main.NormalOptions.MapId == 6 && SubmergedCompatibility.Loaded))
@@ -241,7 +243,7 @@ internal static class ChangeRoleSettings
Mafia.MafiaRevenged = [];
Warlock.IsCurseAndKill = [];
Warlock.IsCursed = false;
- Detective.DetectiveNotify = [];
+ Forensic.ForensicNotify = [];
Provocateur.Provoked = [];
Crusader.ForCrusade = [];
Godfather.GodfatherTarget = byte.MaxValue;
@@ -472,7 +474,7 @@ internal static class StartGameHostPatch
public static readonly Dictionary DataDisconnected = [];
- private static RoleOptionsCollectionV09 RoleOpt => Main.NormalOptions.roleOptions;
+ private static RoleOptionsCollectionV10 RoleOpt => Main.NormalOptions.roleOptions;
private static void UpdateRoleTypeNums()
{
@@ -483,7 +485,9 @@ internal static class StartGameHostPatch
{ RoleTypes.Shapeshifter, AddShapeshifterNum },
{ RoleTypes.Noisemaker, AddNoisemakerNum },
{ RoleTypes.Phantom, AddPhantomNum },
- { RoleTypes.Tracker, AddTrackerNum }
+ { RoleTypes.Tracker, AddTrackerNum },
+ { RoleTypes.Detective, AddDetectiveNum },
+ { RoleTypes.Viper, AddViperNum }
};
}
@@ -690,6 +694,8 @@ internal static class StartGameHostPatch
bool nimbleSpawn = random.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Nimble, out IntegerOptionItem option2) ? option2.GetFloat() : 0) && CustomRoles.Nimble.IsEnable();
bool finderSpawn = random.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Finder, out IntegerOptionItem option3) ? option3.GetFloat() : 0) && CustomRoles.Finder.IsEnable();
bool noisySpawn = random.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Noisy, out IntegerOptionItem option4) ? option4.GetFloat() : 0) && CustomRoles.Noisy.IsEnable();
+ bool examinerSpawn = random.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Examiner, out IntegerOptionItem option5) ? option5.GetFloat() : 0) && CustomRoles.Examiner.IsEnable();
+ bool venomSpawn = random.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Venom, out IntegerOptionItem option6) ? option6.GetFloat() : 0) && CustomRoles.Venom.IsEnable();
if (Options.EveryoneCanVent.GetBool())
{
@@ -697,12 +703,13 @@ internal static class StartGameHostPatch
physicistSpawn = false;
finderSpawn = false;
noisySpawn = false;
+ examinerSpawn = false;
}
- HashSet bloodlustList = [], nimbleList = [], physicistList = [], finderList = [], noisyList = [];
+ HashSet bloodlustList = [], nimbleList = [], physicistList = [], finderList = [], noisyList = [], examinerList = [], venomList = [];
bool hasBanned = Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> banned);
- if (nimbleSpawn || physicistSpawn || finderSpawn || noisySpawn || bloodlustSpawn)
+ if (nimbleSpawn || physicistSpawn || finderSpawn || noisySpawn || bloodlustSpawn || examinerSpawn || venomSpawn)
{
foreach (PlayerControl player in Main.AllPlayerControls)
{
@@ -715,6 +722,8 @@ internal static class StartGameHostPatch
bool physicistBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Physicist));
bool finderBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Finder));
bool noisyBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Noisy));
+ bool examinerBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Examiner));
+ bool venomBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Venom));
if (kp.Value.IsCrewmate())
{
@@ -726,8 +735,12 @@ internal static class StartGameHostPatch
if (!physicistBanned) physicistList.Add(player.PlayerId);
if (!finderBanned) finderList.Add(player.PlayerId);
if (!noisyBanned) noisyList.Add(player.PlayerId);
+ if (!examinerBanned) examinerList.Add(player.PlayerId);
}
}
+
+ if (kp.Value.IsImpostor() && kp.Value.GetRoleTypes() == RoleTypes.Impostor && !venomBanned)
+ venomList.Add(player.PlayerId);
}
}
@@ -737,7 +750,9 @@ internal static class StartGameHostPatch
{ CustomRoles.Nimble, (nimbleSpawn, nimbleList) },
{ CustomRoles.Physicist, (physicistSpawn, physicistList) },
{ CustomRoles.Finder, (finderSpawn, finderList) },
- { CustomRoles.Noisy, (noisySpawn, noisyList) }
+ { CustomRoles.Noisy, (noisySpawn, noisyList) },
+ { CustomRoles.Examiner, (examinerSpawn, examinerList) },
+ { CustomRoles.Venom, (venomSpawn, venomList) }
};
for (var i = 0; i < roleSpawnMapping.Count; i++)
@@ -875,7 +890,7 @@ internal static class StartGameHostPatch
{
foreach (CustomRoles role in item.Value)
{
- if (role is CustomRoles.Nimble or CustomRoles.Physicist or CustomRoles.Bloodlust or CustomRoles.Finder or CustomRoles.Noisy) continue;
+ if (role is CustomRoles.Nimble or CustomRoles.Physicist or CustomRoles.Bloodlust or CustomRoles.Finder or CustomRoles.Noisy or CustomRoles.Examiner or CustomRoles.Venom) continue;
state.SetSubRole(role);
if (overrideLovers && role == CustomRoles.Lovers) Main.LoversPlayers.Add(Utils.GetPlayerById(item.Key));
@@ -1238,10 +1253,10 @@ internal static class StartGameHostPatch
if (Options.CurrentGameMode == CustomGameMode.Standard)
{
- if (target.Is(Team.Crewmate) && roleType is not (RoleTypes.Crewmate or RoleTypes.Scientist or RoleTypes.Engineer or RoleTypes.Noisemaker or RoleTypes.Tracker or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel))
+ if (target.Is(Team.Crewmate) && roleType is not (RoleTypes.Crewmate or RoleTypes.Scientist or RoleTypes.Engineer or RoleTypes.Noisemaker or RoleTypes.Tracker or RoleTypes.Detective or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel))
displayRole = RoleTypes.Crewmate;
- if (target.Is(Team.Impostor) && roleType is not (RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom or RoleTypes.ImpostorGhost))
+ if (target.Is(Team.Impostor) && roleType is not (RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom or RoleTypes.Viper or RoleTypes.ImpostorGhost))
displayRole = RoleTypes.Impostor;
}
@@ -1399,6 +1414,8 @@ internal static class StartGameHostPatch
CustomRoles.Physicist => RoleTypes.Scientist,
CustomRoles.Finder => RoleTypes.Tracker,
CustomRoles.Noisy => RoleTypes.Noisemaker,
+ CustomRoles.Examiner => RoleTypes.Detective,
+ CustomRoles.Venom => RoleTypes.Viper,
_ => roleType
};
}
@@ -1536,6 +1553,8 @@ internal static class StartGameHostPatch
CustomRoles.Physicist when roleTypes == RoleTypes.Crewmate => RoleTypes.Scientist,
CustomRoles.Finder when roleTypes is RoleTypes.Crewmate or RoleTypes.Scientist or RoleTypes.Noisemaker => RoleTypes.Tracker,
CustomRoles.Noisy when roleTypes == RoleTypes.Crewmate => RoleTypes.Noisemaker,
+ CustomRoles.Examiner when roleTypes is RoleTypes.Crewmate or RoleTypes.Scientist or RoleTypes.Noisemaker => RoleTypes.Detective,
+ CustomRoles.Venom when roleTypes == RoleTypes.Impostor => RoleTypes.Viper,
_ => roleTypes
}
: roleTypes;
diff --git a/Patches/PhantomRolePatch.cs b/Patches/PhantomRolePatch.cs
index 0c8b8cf4e..a4484fa76 100644
--- a/Patches/PhantomRolePatch.cs
+++ b/Patches/PhantomRolePatch.cs
@@ -326,7 +326,7 @@ public static class PhantomRoleUseAbilityPatch
FastDestroyableSingleton.Instance.AbilityButton.SetSecondImage(__instance.Ability);
FastDestroyableSingleton.Instance.AbilityButton.OverrideText(FastDestroyableSingleton.Instance.GetString(StringNames.PhantomAbilityUndo, new Il2CppReferenceArray(0)));
- __instance.Player.CmdCheckVanish(GameManager.Instance.LogicOptions.GetPhantomDuration());
+ __instance.Player.CmdCheckVanish(GameManager.Instance.LogicOptions.GetRoleFloat(FloatOptionNames.PhantomDuration));
return false;
}
}
diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs
index 7adc94a97..33839c21b 100644
--- a/Patches/PlayerControlPatch.cs
+++ b/Patches/PlayerControlPatch.cs
@@ -1258,8 +1258,8 @@ internal static class ReportDeadBodyPatch
if (tpc != null && !tpc.IsAlive())
{
- if (player.Is(CustomRoles.Detective) && player.PlayerId != target.PlayerId)
- Detective.OnReportDeadBody(player, target.Object);
+ if (player.Is(CustomRoles.Forensic) && player.PlayerId != target.PlayerId)
+ Forensic.OnReportDeadBody(player, target.Object);
else if (player.Is(CustomRoles.Sleuth) && player.PlayerId != target.PlayerId)
{
string msg = string.Format(GetString("SleuthMsg"), tpc.GetRealName(), tpc.GetDisplayRoleName());
@@ -2444,6 +2444,8 @@ internal static class PlayerControlLocalSetRolePatch
RoleTypes.Noisemaker => CustomRoles.NoisemakerEHR,
RoleTypes.Scientist => CustomRoles.ScientistEHR,
RoleTypes.Tracker => CustomRoles.TrackerEHR,
+ RoleTypes.Detective => CustomRoles.DetectiveEHR,
+ RoleTypes.Viper => CustomRoles.ViperEHR,
_ => CustomRoles.NotAssigned
};
diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json
index 2f2f711bb..9accaf76f 100644
--- a/Resources/Lang/en_US.json
+++ b/Resources/Lang/en_US.json
@@ -165,6 +165,8 @@
"Noisemaker": "Noisemaker",
"Tracker": "Tracker",
"Phantom": "Phantom",
+ "Detective": "Detective",
+ "Viper": "Viper",
"EngineerEHR": "Engineer",
"ScientistEHR": "Scientist",
"ShapeshifterEHR": "Shapeshifter",
@@ -174,6 +176,8 @@
"NoisemakerEHR": "Noisemaker",
"TrackerEHR": "Tracker",
"PhantomEHR": "Phantom",
+ "DetectiveEHR": "Detective",
+ "ViperEHR": "Viper",
"BountyHunter": "Bounty Hunter",
"Mare": "Mare",
"FireWorks": "Fireworker",
@@ -249,7 +253,7 @@
"SpeedBooster": "Speed Booster",
"Doctor": "Doctor",
"Dictator": "Dictator",
- "Detective": "Detective",
+ "Forensic": "Forensic",
"NiceGuesser": "Nice Guesser",
"Transporter": "Transporter",
"TimeManager": "Time Manager",
@@ -528,7 +532,7 @@
"SpeedBoosterInfo": "Boost your speed",
"DoctorInfo": "Know how each player died",
"DictatorInfo": "Exile a player based on your own judgment",
- "DetectiveInfo": "Gain extra info from your body reports",
+ "ForensicInfo": "Gain extra info from your body reports",
"UndercoverInfo": "Impostors see you as their partner",
"SwordsManInfo": "You can kill 1 player",
"NiceGuesserInfo": "Guess evil roles in meetings to kill",
@@ -757,7 +761,7 @@
"SpeedBoosterInfoLong": "(Crewmates):\\nSpeed Booster increases a random player's movement speed every time they complete a task.",
"DoctorInfoLong": "(Crewmates):\\nThe Doctor can view players' cause of death and access their vitals as long as their battery is still operational.",
"DictatorInfoLong": "(Crewmates):\\nDictator can end the meeting by voting someone out, which will also result in their death.",
- "DetectiveInfoLong": "(Crewmate):\\nAfter reporting the body, the Detective receives a clue about the victim's role. The host may reveal the murderer's role to the Detective based on the setting. Note: Detective won't be Oblivious.",
+ "ForensicInfoLong": "(Crewmate):\\nAfter reporting the body, the Forensic receives a clue about the victim's role. The host may reveal the murderer's role to the Forensic based on the setting. Note: Forensic won't be Oblivious.",
"UndercoverInfoLong": "(Crewmates):\\nThe Impostors knows who Undercover is and sees him as a teammate, but Undercover himself does not know who the Impostors are.",
"NiceGuesserInfoLong": "(Crewmates):\\nNice Guesser can guess a player's role during the meeting. Correct guess results in killing the target, while a wrong one leads to suicide. Use the command /bt [player id] [role] to guess. You can see player IDs before their names or use the /id command.",
"SwordsManInfoLong": "(Crewmates):\\nVigilante can kill anyone, but only once. After killing, Vigilante can do tasks. Depending on settings, Vigilante can vent.",
@@ -854,7 +858,7 @@
"TorchInfoLong": "(Add-ons):\\nOnly Crewmates will get Torch, who have increased vision and may not be affected by Lights Sabotage.",
"SeerInfoLong": "(Add-ons):\\nWhen a player dies, the Seer sees a red flash (with an alarm sound like sabotage).",
"BrakarInfoLong": "(Add-ons):\\nIn case of a tie, the target voted by the Tiebreaker has priority. However, if multiple Tiebreakers choose different targets simultaneously, their skills will not take effect.",
- "ObliviousInfoLong": "(Add-ons):\\nDetective and Cleaners won't be Oblivious. Oblivious cannot report dead bodies. Note: Bait killed by Oblivious will still be reported automatically, and Oblivious can still be used as a scapegoat for Anonymous.",
+ "ObliviousInfoLong": "(Add-ons):\\nForensic and Cleaners won't be Oblivious. Oblivious cannot report dead bodies. Note: Bait killed by Oblivious will still be reported automatically, and Oblivious can still be used as a scapegoat for Anonymous.",
"BewilderInfoLong": "(Add-ons):\\nBewilder's vision changes killer's vision on death.",
"TruantInfoLong": "(Add-ons):\\nAfter each meeting, you freeze in place for a set amount of seconds.",
"WorkhorseInfoLong": "(Add-ons):\\nComplete all tasks to become Workhorse and get extra tasks set by the host.",
@@ -1524,10 +1528,10 @@
"CrewKillingRed": "Crewmate Killings can be red",
"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",
+ "ForensicCanknowKiller": "Can find the killer's role",
+ "ForensicCanknowDeathReason": "Can find the cause of death",
+ "ForensicCanknowAddons": "Can find the dead player's add-ons",
+ "ForensicCanknowKillTime": "Can find how long ago the player died",
"ParanoiaVentCooldown": "Vent Cooldown",
"ParanoiaNumOfUseButton": "Max number of portable buttons",
"EveryOneKnowSuperStar": "Everyone knows the Super Star",
@@ -2113,12 +2117,12 @@
"MafiaKillSucceed": "{0} was killed by the Nemesis!",
"MafiaKillDisable": "Sorry, but according to Host's settings, Nemesis revenge is prohibited in this game",
"SuperStarDead": "Shock! Super Star {0} has unfortunately been mercilessly killed in the recent period of time!",
- "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",
+ "ForensicNoticeVictim": "According to your investigation,\\nthe victim ({0}) had the role [{1}]",
+ "ForensicNoticeKiller": "\\nThe killer's role is {0}",
+ "ForensicNoticeKillerNotFound": "The Forensic couldn't find evidence leading to a murderer, this is most likely suicide.",
+ "ForensicNoticeDeathReason": "\\nThe victim's cause of death is {0}",
+ "ForensicNoticeAddons": "\\nThe victim's add-ons are: {0}",
+ "ForensicNoticeKillTime": "\\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.",
@@ -2934,7 +2938,7 @@
"MessageFromTheHost": "★ Host message ★",
"SayTitle": "★ Moderator message ★",
"MessageFromEAC": "★ EAC ★",
- "DetectiveNoticeTitle": "★ Message for the Detective ★",
+ "ForensicNoticeTitle": "★ Message for the Forensic ★",
"GuessKillTitle": "★ Message about Guessing ★",
"SuperStarNewsTitle": "★ Urgent news ★",
"GodAliveTitle": "★ Message about God ★",
@@ -3022,6 +3026,8 @@
"EnterVentWinCountDown": "Enter vent within {0} seconds to win!",
"InvalidArgs": "Invalid Args",
"InvalidArguments": "Invalid Arguments",
+ "DetectiveSuspectLimit": "Suspects Per Case",
+ "ViperDissolveTime": "Body Dissolve Time",
"On": "Always",
"Off": "OFF",
"ColoredOn": "✓",
@@ -3123,6 +3129,9 @@
"AbilityButtonText.Impostor": "Haunt",
"AbilityButtonText.Shapeshifter": "Shift",
"AbilityButtonText.Phantom": "Vanish",
+ "AbilityButtonText.Viper": "Acid",
+ "AbilityButtonText.Detective": "Notes",
+ "SecondaryAbilityButtonText.Detective": "Interrogate",
"GauloisKillButtonText": "Speed Up",
"SniperSnipeButtonText": "Snipe",
"FireWorksExplosionButtonText": "Detonate",
@@ -9100,6 +9109,16 @@
"AntidoteCDOpt": "Decrease the cooldown by",
"AntidoteCDReset": "Cooldown returns to normal after a meeting",
"HackButtonText": "Mimic",
+ "DetectiveEHRInfo": "Gather Info, Solve Murders",
+ "DetectiveEHRInfoLong": "(Crewmates):\\nAs the Detective, you can write down notes about each crime scene and interrogate suspects.",
+ "ViperEHRInfo": "Kill to Dissolve Bodies",
+ "ViperEHRInfoLong": "(Impostors):\\nAs the Viper, the bodies you leave behind slowly dissolve due to your acid, making them disappear and unreportable.",
+ "Explosivist": "Explosivist",
+ "ExplosivistInfo": "Explode Time Bombs",
+ "ExplosivistInfoLong": "(Neutrals):\\nAs the Explosivist, you cannot kill normally. Instead, you can pet/vanish to develop a bomb and start carrying it. Others will only see the bomb itself, and you standing in place where you've started to use your ability. After a few seconds, the bomb explodes, killing everyone nearby, and you teleport back to where you started carrying the bomb.\\nKill everyone to win!",
+ "Explosivist.ExplosionDelay": "Explosion Delay",
+ "Explosivist.ExplosionRadius": "Explosion Radius",
+ "ExplosivistSuffix": "Carrying the bomb\\n<#ffff00>Explodes in {0}s",
"BomberCooldownsResetEachOther": "Kill Cooldown & Bomb Cooldown Reset Each Other When Used",
"BomberDiesInExplosion": "Bomber dies in their explosion",
"ImpostorsSurviveBombs": "Impostors survive bombs",
diff --git a/Roles/AddOns/Crewmate/Examiner.cs b/Roles/AddOns/Crewmate/Examiner.cs
new file mode 100644
index 000000000..3cf4e3015
--- /dev/null
+++ b/Roles/AddOns/Crewmate/Examiner.cs
@@ -0,0 +1,17 @@
+namespace EHR.AddOns.Crewmate;
+
+public class Examiner : IAddon
+{
+ public AddonTypes Type => AddonTypes.Helpful;
+
+ public static OptionItem ExaminerSuspectLimit;
+
+ public void SetupCustomOption()
+ {
+ Options.SetupAdtRoleOptions(654400, CustomRoles.Examiner, canSetNum: true);
+
+ ExaminerSuspectLimit = new FloatOptionItem(654410, "DetectiveSuspectLimit", new(1f, 30f, 1f), 4f, TabGroup.Addons)
+ .SetParent(Options.CustomRoleSpawnChances[CustomRoles.DetectiveEHR])
+ .SetValueFormat(OptionFormat.Players);
+ }
+}
\ No newline at end of file
diff --git a/Roles/AddOns/Crewmate/Workhorse.cs b/Roles/AddOns/Crewmate/Workhorse.cs
index 56c4732fe..a893d282f 100644
--- a/Roles/AddOns/Crewmate/Workhorse.cs
+++ b/Roles/AddOns/Crewmate/Workhorse.cs
@@ -68,7 +68,7 @@ public class Workhorse : IAddon
{
if (!pc.IsAlive() || IsThisRole(pc.PlayerId)) return false;
if (pc.Is(CustomRoles.Needy) || pc.Is(CustomRoles.Lazy) || pc.Is(CustomRoles.Bloodlust)) return false;
- if (pc.GetCustomRole().GetVNRole(true) is CustomRoles.Impostor or CustomRoles.ImpostorEHR or CustomRoles.Shapeshifter or CustomRoles.ShapeshifterEHR or CustomRoles.Phantom or CustomRoles.PhantomEHR) return false;
+ if (pc.GetCustomRole().GetVNRole(true) is CustomRoles.Impostor or CustomRoles.ImpostorEHR or CustomRoles.Shapeshifter or CustomRoles.ShapeshifterEHR or CustomRoles.Phantom or CustomRoles.PhantomEHR or CustomRoles.Viper or CustomRoles.ViperEHR) return false;
TaskState taskState = pc.GetTaskState();
if (taskState.CompletedTasksCount + 1 < taskState.AllTasksCount) return false;
diff --git a/Roles/AddOns/Impostor/Venom.cs b/Roles/AddOns/Impostor/Venom.cs
new file mode 100644
index 000000000..a6a5eba10
--- /dev/null
+++ b/Roles/AddOns/Impostor/Venom.cs
@@ -0,0 +1,17 @@
+namespace EHR.AddOns.Impostor;
+
+public class Venom : IAddon
+{
+ public AddonTypes Type => AddonTypes.Helpful;
+
+ public static OptionItem VenomDissolveTime;
+
+ public void SetupCustomOption()
+ {
+ Options.SetupAdtRoleOptions(654500, CustomRoles.Venom, canSetNum: true);
+
+ VenomDissolveTime = new FloatOptionItem(654510, "ViperDissolveTime", new(0f, 60f, 0.5f), 10f, TabGroup.Addons)
+ .SetParent(Options.CustomRoleSpawnChances[CustomRoles.ViperEHR])
+ .SetValueFormat(OptionFormat.Seconds);
+ }
+}
\ No newline at end of file
diff --git a/Roles/Crewmate/Analyst.cs b/Roles/Crewmate/Analyst.cs
index e4cfc1694..17765dfa7 100644
--- a/Roles/Crewmate/Analyst.cs
+++ b/Roles/Crewmate/Analyst.cs
@@ -76,11 +76,13 @@ internal class Analyst : RoleBase
CustomRoles.Impostor or CustomRoles.ImpostorEHR => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("Impostor")),
CustomRoles.Shapeshifter or CustomRoles.ShapeshifterEHR => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Speedrunner), GetString("Shapeshifter")),
CustomRoles.Phantom or CustomRoles.PhantomEHR => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Witness), GetString("Phantom")),
+ CustomRoles.Viper or CustomRoles.ViperEHR => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Poisoner), GetString("Viper")),
CustomRoles.Crewmate or CustomRoles.CrewmateEHR => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Crewmate), GetString("Crewmate")),
CustomRoles.Engineer or CustomRoles.EngineerEHR => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Autocrat), GetString("Engineer")),
CustomRoles.Scientist or CustomRoles.ScientistEHR => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doctor), GetString("Scientist")),
CustomRoles.Noisemaker or CustomRoles.NoisemakerEHR => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Bubble), GetString("Noisemaker")),
CustomRoles.Tracker or CustomRoles.TrackerEHR => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Scout), GetString("Tracker")),
+ CustomRoles.Detective or CustomRoles.DetectiveEHR => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Forensic), GetString("Detective")),
_ => string.Empty
}
: string.Empty;
diff --git a/Roles/Crewmate/CrewmateVanillaRoles.cs b/Roles/Crewmate/CrewmateVanillaRoles.cs
index 2aeec10e5..05a72cf76 100644
--- a/Roles/Crewmate/CrewmateVanillaRoles.cs
+++ b/Roles/Crewmate/CrewmateVanillaRoles.cs
@@ -14,6 +14,7 @@ internal class CrewmateVanillaRoles : IVanillaSettingHolder
public static OptionItem TrackerCooldown;
public static OptionItem TrackerDuration;
public static OptionItem TrackerDelay;
+ public static OptionItem DetectiveSuspectLimit;
public TabGroup Tab => TabGroup.CrewmateRoles;
public void SetupCustomOption()
@@ -65,5 +66,11 @@ internal class CrewmateVanillaRoles : IVanillaSettingHolder
TrackerDelay = new FloatOptionItem(5064, "TrackerDelay", new(1f, 250f, 1f), 5f, Tab)
.SetParent(CustomRoleSpawnChances[CustomRoles.TrackerEHR])
.SetValueFormat(OptionFormat.Seconds);
+
+ SetupRoleOptions(5080, Tab, CustomRoles.DetectiveEHR);
+
+ DetectiveSuspectLimit = new FloatOptionItem(5082, "DetectiveSuspectLimit", new(1f, 30f, 1f), 4f, Tab)
+ .SetParent(CustomRoleSpawnChances[CustomRoles.DetectiveEHR])
+ .SetValueFormat(OptionFormat.Players);
}
}
\ No newline at end of file
diff --git a/Roles/Crewmate/Detective.cs b/Roles/Crewmate/Detective.cs
deleted file mode 100644
index 64e100236..000000000
--- a/Roles/Crewmate/Detective.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-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;
-
- public override void SetupCustomOption()
- {
- Options.SetupRoleOptions(6600, TabGroup.CrewmateRoles, CustomRoles.Detective);
-
- 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]);
- }
-
- public override void Init() { }
-
- public override void Add(byte playerId) { }
-
- public static void OnReportDeadBody(PlayerControl player, PlayerControl tpc)
- {
- string msg = string.Format(Translator.GetString("DetectiveNoticeVictim"), tpc.GetRealName(), tpc.GetCustomRole().ToColoredString());
-
- if (DetectiveCanknowKiller.GetBool())
- {
- PlayerControl 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())
- {
- DateTime deathTimeStamp = Main.PlayerStates[tpc.PlayerId].RealKiller.TimeStamp;
- DateTime now = DateTime.Now;
- double timeSpanSeconds = (now - deathTimeStamp).TotalSeconds;
- msg += ";" + string.Format(Translator.GetString("DetectiveNoticeKillTime"), (int)timeSpanSeconds);
- }
-
- DetectiveNotify[player.PlayerId] = msg;
- }
-}
\ No newline at end of file
diff --git a/Roles/Crewmate/Farseer.cs b/Roles/Crewmate/Farseer.cs
index 1ec5d844e..060b539e0 100644
--- a/Roles/Crewmate/Farseer.cs
+++ b/Roles/Crewmate/Farseer.cs
@@ -128,7 +128,7 @@ public class Farseer : RoleBase
}
else
{
- float range = NormalGameOptionsV09.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f;
+ float range = NormalGameOptionsV10.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f;
float dis = Vector2.Distance(player.Pos(), arTarget.Pos());
if (dis <= range)
diff --git a/Roles/Crewmate/Forensic.cs b/Roles/Crewmate/Forensic.cs
new file mode 100644
index 000000000..fe527aa19
--- /dev/null
+++ b/Roles/Crewmate/Forensic.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace EHR.Crewmate;
+
+internal class Forensic : RoleBase
+{
+ public static OptionItem ForensicCanknowKiller;
+ public static OptionItem ForensicCanknowDeathReason;
+ public static OptionItem ForensicCanknowAddons;
+ public static OptionItem ForensicCanknowKillTime;
+
+ public static Dictionary ForensicNotify = [];
+
+ public override bool IsEnable => false;
+
+ public override void SetupCustomOption()
+ {
+ Options.SetupRoleOptions(6600, TabGroup.CrewmateRoles, CustomRoles.Forensic);
+
+ ForensicCanknowKiller = new BooleanOptionItem(6610, "ForensicCanknowKiller", true, TabGroup.CrewmateRoles)
+ .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Forensic]);
+
+ ForensicCanknowDeathReason = new BooleanOptionItem(6611, "ForensicCanknowDeathReason", true, TabGroup.CrewmateRoles)
+ .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Forensic]);
+
+ ForensicCanknowAddons = new BooleanOptionItem(6612, "ForensicCanknowAddons", true, TabGroup.CrewmateRoles)
+ .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Forensic]);
+
+ ForensicCanknowKillTime = new BooleanOptionItem(6613, "ForensicCanknowKillTime", true, TabGroup.CrewmateRoles)
+ .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Forensic]);
+ }
+
+ public override void Init() { }
+
+ public override void Add(byte playerId) { }
+
+ public static void OnReportDeadBody(PlayerControl player, PlayerControl tpc)
+ {
+ string msg = string.Format(Translator.GetString("ForensicNoticeVictim"), tpc.GetRealName(), tpc.GetCustomRole().ToColoredString());
+
+ if (ForensicCanknowKiller.GetBool())
+ {
+ PlayerControl realKiller = tpc.GetRealKiller();
+
+ if (realKiller == null)
+ msg += ";" + Translator.GetString("ForensicNoticeKillerNotFound");
+ else
+ msg += ";" + string.Format(Translator.GetString("ForensicNoticeKiller"), realKiller.GetCustomRole().ToColoredString());
+ }
+
+ if (ForensicCanknowDeathReason.GetBool()) msg += ";" + string.Format(Translator.GetString("ForensicNoticeDeathReason"), Translator.GetString($"DeathReason.{Main.PlayerStates[tpc.PlayerId].deathReason}"));
+
+ if (ForensicCanknowAddons.GetBool()) msg += ";" + string.Format(Translator.GetString("ForensicNoticeAddons"), string.Join(", ", tpc.GetCustomSubRoles().Select(x => x.ToColoredString())));
+
+ if (ForensicCanknowKillTime.GetBool())
+ {
+ DateTime deathTimeStamp = Main.PlayerStates[tpc.PlayerId].RealKiller.TimeStamp;
+ DateTime now = DateTime.Now;
+ double timeSpanSeconds = (now - deathTimeStamp).TotalSeconds;
+ msg += ";" + string.Format(Translator.GetString("ForensicNoticeKillTime"), (int)timeSpanSeconds);
+ }
+
+ ForensicNotify[player.PlayerId] = msg;
+ }
+}
\ No newline at end of file
diff --git a/Roles/Crewmate/Scout.cs b/Roles/Crewmate/Scout.cs
index c10402780..d311922dd 100644
--- a/Roles/Crewmate/Scout.cs
+++ b/Roles/Crewmate/Scout.cs
@@ -18,7 +18,7 @@ public class Scout : RoleBase
private static OptionItem OptionCanSeeLastRoomInMeeting;
private static OptionItem CanGetColoredArrow;
public static OptionItem HideVote;
- public static OptionItem TrackerAbilityUseGainWithEachTaskCompleted;
+ public static OptionItem ScoutAbilityUseGainWithEachTaskCompleted;
public static OptionItem AbilityChargesWhenFinishedTasks;
public static OptionItem CancelVote;
@@ -48,7 +48,7 @@ public class Scout : RoleBase
HideVote = new BooleanOptionItem(Id + 8, "TrackerHideVote", false, TabGroup.CrewmateRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Scout]);
- TrackerAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 9, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles)
+ ScoutAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 9, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Scout])
.SetValueFormat(OptionFormat.Times);
diff --git a/Roles/Impostor/Deathpact.cs b/Roles/Impostor/Deathpact.cs
index 3557de6fe..caf730ce7 100644
--- a/Roles/Impostor/Deathpact.cs
+++ b/Roles/Impostor/Deathpact.cs
@@ -191,7 +191,7 @@ public class Deathpact : RoleBase
foreach (PlayerControl player in dp.PlayersInDeathpact)
{
- float range = NormalGameOptionsV09.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f;
+ float range = NormalGameOptionsV10.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f;
cancelDeathpact = dp.PlayersInDeathpact.Where(a => a.PlayerId != player.PlayerId).Select(otherPlayerInPact => Vector2.Distance(player.Pos(), otherPlayerInPact.Pos())).Aggregate(cancelDeathpact, (current, dis) => current && dis <= range);
}
diff --git a/Roles/Impostor/ImpostorVanillaRoles.cs b/Roles/Impostor/ImpostorVanillaRoles.cs
index 03a18e4fe..4444d1fbf 100644
--- a/Roles/Impostor/ImpostorVanillaRoles.cs
+++ b/Roles/Impostor/ImpostorVanillaRoles.cs
@@ -8,6 +8,7 @@ internal class ImpostorVanillaRoles : IVanillaSettingHolder
public static OptionItem PhantomDuration;
public static OptionItem ShapeshiftCD;
public static OptionItem ShapeshiftDur;
+ public static OptionItem ViperDissolveTime;
public TabGroup Tab => TabGroup.ImpostorRoles;
public void SetupCustomOption()
@@ -32,5 +33,11 @@ internal class ImpostorVanillaRoles : IVanillaSettingHolder
ShapeshiftDur = new FloatOptionItem(403, "ShapeshiftDuration", new(1f, 60f, 1f), 10f, Tab)
.SetParent(CustomRoleSpawnChances[CustomRoles.ShapeshifterEHR])
.SetValueFormat(OptionFormat.Seconds);
+
+ SetupRoleOptions(410, Tab, CustomRoles.ViperEHR);
+
+ ViperDissolveTime = new FloatOptionItem(412, "ViperDissolveTime", new(0f, 60f, 0.5f), 10f, Tab)
+ .SetParent(CustomRoleSpawnChances[CustomRoles.ViperEHR])
+ .SetValueFormat(OptionFormat.Seconds);
}
}
\ No newline at end of file
diff --git a/Roles/Impostor/Psychopath.cs b/Roles/Impostor/Psychopath.cs
index b921e0766..5562bbd0b 100644
--- a/Roles/Impostor/Psychopath.cs
+++ b/Roles/Impostor/Psychopath.cs
@@ -35,7 +35,7 @@ public class Psychopath : RoleBase
{
if (!GameStates.IsInTask || ExileController.Instance || AntiBlackout.SkipTasks || Main.KillTimers[pc.PlayerId] > 0f || Count++ < 10) return;
var pos = pc.Pos();
- var killRange = NormalGameOptionsV09.KillDistances[Mathf.Clamp(Main.NormalOptions.KillDistance, 0, 2)];
+ var killRange = NormalGameOptionsV10.KillDistances[Mathf.Clamp(Main.NormalOptions.KillDistance, 0, 2)];
var closestPlayer = Main.AllAlivePlayerControls.Select(x => (pc: x, distance: Vector2.Distance(x.Pos(), pos))).Where(x => x.distance <= killRange).MinBy(x => x.distance).pc;
if (closestPlayer == null) return;
Count = 0;
diff --git a/Roles/Impostor/Puppeteer.cs b/Roles/Impostor/Puppeteer.cs
index 9fb73cd5c..dae374e48 100644
--- a/Roles/Impostor/Puppeteer.cs
+++ b/Roles/Impostor/Puppeteer.cs
@@ -202,7 +202,7 @@ internal class Puppeteer : RoleBase
{
KeyValuePair min = targetDistance.OrderBy(c => c.Value).FirstOrDefault();
PlayerControl target = Utils.GetPlayerById(min.Key);
- float killRange = NormalGameOptionsV09.KillDistances[Mathf.Clamp(Main.NormalOptions.KillDistance, 0, 2)];
+ float killRange = NormalGameOptionsV10.KillDistances[Mathf.Clamp(Main.NormalOptions.KillDistance, 0, 2)];
if (min.Value <= killRange && player.CanMove && target.CanMove)
{
diff --git a/Roles/Neutral/Agitater.cs b/Roles/Neutral/Agitater.cs
index 5884da65d..96cb842a3 100644
--- a/Roles/Neutral/Agitater.cs
+++ b/Roles/Neutral/Agitater.cs
@@ -199,7 +199,7 @@ public class Agitater : RoleBase
{
KeyValuePair min = targetDistance.OrderBy(c => c.Value).FirstOrDefault();
PlayerControl target = Utils.GetPlayerById(min.Key);
- float KillRange = NormalGameOptionsV09.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 2)];
+ float KillRange = NormalGameOptionsV10.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 2)];
if (min.Value <= KillRange && player.CanMove && target.CanMove) PassBomb(player, target);
}
}
diff --git a/Roles/Neutral/Arsonist.cs b/Roles/Neutral/Arsonist.cs
index 6e3bf7b8b..f31f1a45b 100644
--- a/Roles/Neutral/Arsonist.cs
+++ b/Roles/Neutral/Arsonist.cs
@@ -201,7 +201,7 @@ internal class Arsonist : RoleBase
}
else
{
- float range = NormalGameOptionsV09.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f;
+ float range = NormalGameOptionsV10.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f;
float dis = Vector2.Distance(player.Pos(), arTarget.Pos());
if (dis <= range)
diff --git a/Roles/Neutral/Explosivist.cs b/Roles/Neutral/Explosivist.cs
new file mode 100644
index 000000000..c51c1dae5
--- /dev/null
+++ b/Roles/Neutral/Explosivist.cs
@@ -0,0 +1,166 @@
+using System.Linq;
+using AmongUs.GameOptions;
+using Hazel;
+
+namespace EHR.Neutral;
+
+public class Explosivist : RoleBase
+{
+ public static bool On;
+
+ public static OptionItem AbilityCooldown;
+ private static OptionItem ImpostorVision;
+ public static OptionItem ExplosionDelay;
+ private static OptionItem ExplosionRadius;
+
+ public override bool IsEnable => On;
+
+ private TNT Explosive;
+ private Vector2 RealPosition;
+ private long ExplodeTS;
+ private byte ExplosivistId;
+
+ public override void SetupCustomOption()
+ {
+ StartSetup(654300)
+ .AutoSetupOption(ref AbilityCooldown, 22.5f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds)
+ .AutoSetupOption(ref ImpostorVision, true)
+ .AutoSetupOption(ref ExplosionDelay, 10, new IntegerValueRule(1, 60, 1), OptionFormat.Seconds)
+ .AutoSetupOption(ref ExplosionRadius, 4f, new FloatValueRule(0.25f, 10f, 0.25f), OptionFormat.Multiplier);
+ }
+
+ public override void Init()
+ {
+ On = false;
+ }
+
+ public override void Add(byte playerId)
+ {
+ On = true;
+ Explosive = null;
+ RealPosition = Vector2.zero;
+ ExplodeTS = 0;
+ ExplosivistId = playerId;
+ }
+
+ public override void SetKillCooldown(byte id)
+ {
+ Main.AllPlayerKillCooldown[id] = AbilityCooldown.GetFloat();
+ }
+
+ public override void ApplyGameOptions(IGameOptions opt, byte id)
+ {
+ opt.SetVision(ImpostorVision.GetBool());
+
+ if (Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool())
+ {
+ AURoleOptions.PhantomCooldown = AbilityCooldown.GetFloat();
+ AURoleOptions.PhantomDuration = 1f;
+ }
+ }
+
+ public override bool CanUseKillButton(PlayerControl pc)
+ {
+ return false;
+ }
+
+ public override bool OnVanish(PlayerControl pc)
+ {
+ UseAbility(pc);
+ return false;
+ }
+
+ public override void OnPet(PlayerControl pc)
+ {
+ UseAbility(pc);
+ }
+
+ void UseAbility(PlayerControl player)
+ {
+ if (Explosive != null) return;
+
+ Vector2 pos = player.Pos();
+ Explosive = new TNT(pos);
+ RealPosition = pos;
+
+ // From https://github.com/Rabek009/MoreGamemodes/blob/master/Roles/Impostor/Concealing/Droner.cs
+ foreach (var pc in PlayerControl.AllPlayerControls)
+ {
+ if (pc == player || pc.AmOwner) continue;
+ CustomRpcSender sender = CustomRpcSender.Create("Explosivist", SendOption.Reliable);
+ sender.StartMessage(pc.GetClientId());
+ sender.StartRpc(player.NetTransform.NetId, (byte)RpcCalls.SnapTo)
+ .WriteVector2(player.transform.position)
+ .Write(player.NetTransform.lastSequenceId)
+ .EndRpc();
+ sender.StartRpc(player.NetTransform.NetId, (byte)RpcCalls.SnapTo)
+ .WriteVector2(player.transform.position)
+ .Write((ushort)(player.NetTransform.lastSequenceId + 16383))
+ .EndRpc();
+ sender.EndMessage();
+ sender.SendMessage();
+ Utils.NumSnapToCallsThisRound += 2;
+ }
+
+ if (player.AmOwner) player.Visible = false;
+
+ ExplodeTS = Utils.TimeStamp + ExplosionDelay.GetInt();
+ }
+
+ public override void OnFixedUpdate(PlayerControl pc)
+ {
+ if (!GameStates.IsInTask || ExileController.Instance || AntiBlackout.SkipTasks) return;
+
+ Explosive?.TP(pc.transform.position);
+
+ if (Explosive != null && ExplodeTS >= Utils.TimeStamp)
+ {
+ Utils.GetPlayersInRadius(ExplosionRadius.GetFloat(), Explosive.Position).Without(pc).Do(x => x.Suicide(PlayerState.DeathReason.Bombed, pc));
+
+ RevertFreeze(pc);
+ if (!Options.UsePets.GetBool()) pc.RpcResetAbilityCooldown();
+
+ Explosive.Despawn();
+ Explosive = null;
+ RealPosition = Vector2.zero;
+ ExplodeTS = 0;
+ }
+ }
+
+ public override void OnReportDeadBody()
+ {
+ if (Explosive != null)
+ {
+ PlayerControl pc = ExplosivistId.GetPlayer();
+ RevertFreeze(pc);
+ }
+ }
+
+ private void RevertFreeze(PlayerControl pc)
+ {
+ if (pc.AmOwner) pc.Visible = true;
+ pc.NetTransform.SnapTo(RealPosition, (ushort)(pc.NetTransform.lastSequenceId + 128));
+ CustomRpcSender sender = CustomRpcSender.Create("Explosivist Revert", SendOption.Reliable);
+ sender.StartMessage();
+ sender.StartRpc(pc.NetTransform.NetId, (byte)RpcCalls.SnapTo)
+ .WriteVector2(pc.transform.position)
+ .Write((ushort)(pc.NetTransform.lastSequenceId + 32767))
+ .EndRpc();
+ sender.StartRpc(pc.NetTransform.NetId, (byte)RpcCalls.SnapTo)
+ .WriteVector2(pc.transform.position)
+ .Write((ushort)(pc.NetTransform.lastSequenceId + 32767 + 16383))
+ .EndRpc();
+ sender.StartRpc(pc.NetTransform.NetId, (byte)RpcCalls.SnapTo)
+ .WriteVector2(pc.transform.position)
+ .Write(pc.NetTransform.lastSequenceId)
+ .EndRpc();
+ sender.EndMessage();
+ sender.SendMessage();
+ }
+
+ public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
+ {
+ if (seer.PlayerId != ExplosivistId || seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || meeting || ExplodeTS == 0) return string.Empty;
+ return string.Format(Translator.GetString("ExplosivistSuffix"), ExplodeTS - Utils.TimeStamp);
+ }
+}
\ No newline at end of file
diff --git a/Roles/Neutral/Revolutionist.cs b/Roles/Neutral/Revolutionist.cs
index 05e36a915..2aa2f6979 100644
--- a/Roles/Neutral/Revolutionist.cs
+++ b/Roles/Neutral/Revolutionist.cs
@@ -176,7 +176,7 @@ internal class Revolutionist : RoleBase
}
else
{
- float range = NormalGameOptionsV09.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f;
+ float range = NormalGameOptionsV10.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f;
float dis = Vector2.Distance(player.Pos(), rvTarget.Pos());
if (dis <= range)
diff --git a/Roles/Neutral/Samurai.cs b/Roles/Neutral/Samurai.cs
index e3ff0ab25..942dabac8 100644
--- a/Roles/Neutral/Samurai.cs
+++ b/Roles/Neutral/Samurai.cs
@@ -114,7 +114,7 @@ internal class Samurai : RoleBase
PlayerControl target = Utils.GetPlayerById(Target.Id);
if (target == null) return;
- if (Vector2.Distance(target.Pos(), pc.Pos()) > NormalGameOptionsV09.KillDistances[Mathf.Clamp(pc.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f)
+ if (Vector2.Distance(target.Pos(), pc.Pos()) > NormalGameOptionsV10.KillDistances[Mathf.Clamp(pc.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f)
{
Target = (byte.MaxValue, 0);
pc.RpcCheckAndMurder(target);