Add Gaslighter & Technician, add more settings

This commit is contained in:
Gurge44 committed 2024-08-27 15:18:35 +02:00
1 parent 3ecbbdd256
commit ccd0d7a45d
28 files changed
+642 -138

No files matched your search

+2
View File
@@ -273,6 +273,7 @@ public enum CustomRoles
Evolver,
Executioner,
Totocalcio, // Follower
Gaslighter,
Glitch,
God,
FFF, // Hater
@@ -329,6 +330,7 @@ public enum CustomRoles
DarkHide, // Stalker
Succubus,
Sunnyboy,
Technician,
Tank,
Terrorist,
Tiger,
+1
View File
@@ -17,6 +17,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GA/@EntryIndexedValue">GA</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GM/@EntryIndexedValue">GM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HP/@EntryIndexedValue">HP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HQ/@EntryIndexedValue">HQ</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HUD/@EntryIndexedValue">HUD</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=KC/@EntryIndexedValue">KC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=KCD/@EntryIndexedValue">KCD</s:String>
+2 -1
View File
@@ -125,7 +125,8 @@ namespace EHR
if (LastUpdate == now) return;
LastUpdate = now;
Timers.Keys.ToArray().Do(x => Timers[x]--);
//Timers.Keys.ToArray().Do(x => Timers[x]--);
Timers.AdjustAllValues(x => x - 1);
Utils.NotifyRoles();
}
}
+7
View File
@@ -248,6 +248,7 @@ internal static class CustomRolesHelper
CustomRoles.Detective => CustomRoles.Crewmate,
CustomRoles.God => CustomRoles.Crewmate,
CustomRoles.Tank => CustomRoles.Engineer,
CustomRoles.Technician => Technician.CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate,
CustomRoles.GuardianAngelEHR => CustomRoles.GuardianAngel,
CustomRoles.Zombie => CustomRoles.Impostor,
CustomRoles.Mario => CustomRoles.Engineer,
@@ -445,6 +446,7 @@ internal static class CustomRolesHelper
CustomRoles.SchrodingersCat => RoleTypes.Impostor,
CustomRoles.Shifter => RoleTypes.Impostor,
CustomRoles.Impartial => RoleTypes.Impostor,
CustomRoles.Gaslighter => RoleTypes.Impostor,
CustomRoles.Backstabber => RoleTypes.Impostor,
CustomRoles.Predator => RoleTypes.Impostor,
CustomRoles.Reckless => RoleTypes.Impostor,
@@ -823,6 +825,7 @@ internal static class CustomRolesHelper
public static bool CheckAddonConflict(CustomRoles role, PlayerControl pc) => role.IsAdditionRole() && (!Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out var neverList) || !neverList.TryGetValue(pc.GetCustomRole(), out var bannedAddonList) || !bannedAddonList.Contains(role)) && pc.GetCustomRole() is not CustomRoles.GuardianAngelEHR and not CustomRoles.God && !pc.Is(CustomRoles.Madmate) && !pc.Is(CustomRoles.GM) && role is not CustomRoles.Lovers && !pc.Is(CustomRoles.Needy) && (!pc.HasSubRole() || pc.GetCustomSubRoles().Count < Options.NoLimitAddonsNumMax.GetInt()) && (!Options.AddonCanBeSettings.TryGetValue(role, out var o) || ((o.Imp.GetBool() || !pc.GetCustomRole().IsImpostor()) && (o.Neutral.GetBool() || !pc.GetCustomRole().IsNeutral()) && (o.Crew.GetBool() || !pc.IsCrewmate()))) && (!role.IsImpOnlyAddon() || pc.IsImpostor()) && role switch
{
CustomRoles.Swift when pc.Is(CustomRoles.Stealth) => false,
CustomRoles.Disco when pc.GetCustomRole() is CustomRoles.Chameleon or CustomRoles.Swooper or CustomRoles.Wraith or CustomRoles.Alchemist => false,
CustomRoles.Egoist when pc.Is(CustomRoles.Gangster) => false,
CustomRoles.Nimble when pc.Is(CustomRoles.Oxyman) => false,
@@ -1063,6 +1066,7 @@ internal static class CustomRolesHelper
CustomRoles.Parasite => CountTypes.Impostor,
CustomRoles.Crewpostor => CountTypes.Impostor,
CustomRoles.Refugee => CountTypes.Impostor,
CustomRoles.Gaslighter => Gaslighter.WinCondition.GetValue() == 2 ? CountTypes.Gaslighter : CountTypes.Crew,
CustomRoles.DarkHide when DarkHide.SnatchesWin.GetBool() => CountTypes.Crew,
CustomRoles.Arsonist when !Options.ArsonistKeepsGameGoing.GetBool() => CountTypes.Crew,
CustomRoles.SchrodingersCat => SchrodingersCat.WinsWithCrewIfNotAttacked.GetBool() ? CountTypes.Crew : CountTypes.OutOfGame,
@@ -1166,9 +1170,11 @@ internal static class CustomRolesHelper
CustomRoles.Phantasm => RoleOptionType.Neutral_Benign,
CustomRoles.Provocateur => RoleOptionType.Neutral_Benign,
CustomRoles.SoulHunter => RoleOptionType.Neutral_Benign,
CustomRoles.Technician => RoleOptionType.Neutral_Benign,
CustomRoles.Tank => RoleOptionType.Neutral_Benign,
CustomRoles.Totocalcio => RoleOptionType.Neutral_Benign,
CustomRoles.Jester => RoleOptionType.Neutral_Evil,
CustomRoles.Gaslighter => RoleOptionType.Neutral_Evil,
CustomRoles.God => RoleOptionType.Neutral_Evil,
CustomRoles.Executioner => RoleOptionType.Neutral_Evil,
CustomRoles.Doomsayer => RoleOptionType.Neutral_Evil,
@@ -1493,6 +1499,7 @@ public enum CountTypes
Eclipse,
Vengeance,
HeadHunter,
Gaslighter,
Imitator,
Werewolf,
Juggernaut,
@@ -30,6 +30,36 @@ namespace EHR
return default;
}
/// <summary>
/// Sets the value for all existing keys in a dictionary to a specific value
/// </summary>
/// <param name="dictionary"></param>
/// <param name="value"></param>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
public static void SetAllValues<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TValue value)
{
foreach (TKey key in dictionary.Keys.ToArray())
{
dictionary[key] = value;
}
}
/// <summary>
/// Adjusts the value for all existing keys in a dictionary
/// </summary>
/// <param name="dictionary">The dictionary to adjust the values of</param>
/// <param name="adjust">The function to adjust the values with</param>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
public static void AdjustAllValues<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, Func<TValue, TValue> adjust)
{
foreach (TKey key in dictionary.Keys.ToArray())
{
dictionary[key] = adjust(dictionary[key]);
}
}
/// <summary>
/// Returns a random element from a collection
/// </summary>
+10 -16
View File
@@ -689,7 +689,9 @@ public static class Utils
case CustomRoles.Postman:
case CustomRoles.SchrodingersCat:
case CustomRoles.Shifter:
case CustomRoles.Technician:
case CustomRoles.Tank:
case CustomRoles.Gaslighter:
case CustomRoles.Impartial:
case CustomRoles.Backstabber:
case CustomRoles.Predator:
@@ -1873,6 +1875,7 @@ public static class Utils
if (Main.LoversPlayers.Any(x => x.PlayerId == seer.PlayerId)) SelfMark.Append(ColorString(GetRoleColor(CustomRoles.Lovers), " ♥"));
if (BallLightning.IsGhost(seer)) SelfMark.Append(ColorString(GetRoleColor(CustomRoles.BallLightning), "■"));
SelfMark.Append(Medic.GetMark(seer, seer));
SelfMark.Append(Gaslighter.GetMark(seer, seer));
SelfMark.Append(Gamer.TargetMark(seer, seer));
SelfMark.Append(Sniper.GetShotNotify(seer.PlayerId));
if (Silencer.ForSilencer.Contains(seer.PlayerId)) SelfMark.Append(ColorString(GetRoleColor(CustomRoles.Silencer), "╳"));
@@ -2262,6 +2265,7 @@ public static class Utils
TargetMark.Append(Executioner.TargetMark(seer, target));
TargetMark.Append(Gamer.TargetMark(seer, target));
TargetMark.Append(Medic.GetMark(seer, target));
TargetMark.Append(Gaslighter.GetMark(seer, target));
TargetMark.Append(Totocalcio.TargetMark(seer, target));
TargetMark.Append(Romantic.TargetMark(seer, target));
TargetMark.Append(Lawyer.LawyerMark(seer, target));
@@ -2289,7 +2293,7 @@ public static class Utils
Main.PlayerStates.Values.Do(x => TargetSuffix.Append(x.Role.GetSuffix(seer, target, isMeeting: isForMeeting)));
if (MeetingStates.FirstMeeting && Main.FirstDied != string.Empty && Main.FirstDied == target.FriendCode && Main.ShieldPlayer != string.Empty && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.SoloKombat or CustomGameMode.FFA)
if (MeetingStates.FirstMeeting && Main.ShieldPlayer == target.FriendCode && !string.IsNullOrEmpty(target.FriendCode) && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.SoloKombat or CustomGameMode.FFA)
TargetSuffix.Append(GetString("DiedR1Warning"));
TargetSuffix.Append(AFKDetector.GetSuffix(seer, target));
@@ -2577,25 +2581,15 @@ public static class Utils
if (Options.DiseasedCDReset.GetBool())
{
var array = Main.KilledDiseased.Keys.ToArray();
foreach (var pid in array)
{
Main.KilledDiseased[pid] = 0;
GetPlayerById(pid)?.ResetKillCooldown();
}
Main.KilledDiseased.SetAllValues(0);
Main.KilledDiseased.Keys.Select(x => x.GetPlayer()).Do(x => x?.ResetKillCooldown());
Main.KilledDiseased.Clear();
}
if (Options.AntidoteCDReset.GetBool())
{
var array = Main.KilledAntidote.Keys.ToArray();
foreach (var pid in array)
{
Main.KilledAntidote[pid] = 0;
GetPlayerById(pid)?.ResetKillCooldown();
}
Main.KilledAntidote.SetAllValues(0);
Main.KilledAntidote.Keys.Select(x => x.GetPlayer()).Do(x => x?.ResetKillCooldown());
Main.KilledAntidote.Clear();
}
@@ -2717,7 +2711,7 @@ public static class Utils
if (QuizMaster.On) QuizMaster.Data.NumPlayersDeadThisRound++;
FixedUpdatePatch.LoversSuicide(target.PlayerId, onMeeting);
FixedUpdatePatch.LoversSuicide(target.PlayerId, guess: onMeeting);
if (!target.HasGhostRole())
{
Main.AllPlayerSpeed[target.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod);
+9 -1
View File
@@ -181,6 +181,10 @@ class GameEndChecker
WinnerIds.Add(pc.PlayerId);
AdditionalWinnerTeams.Add(AdditionalWinners.Tank);
break;
case CustomRoles.Technician when (Main.PlayerStates[pc.PlayerId].Role as Technician).IsWon:
WinnerIds.Add(pc.PlayerId);
AdditionalWinnerTeams.Add(AdditionalWinners.Technician);
break;
case CustomRoles.Backstabber when (Main.PlayerStates[pc.PlayerId].Role as Backstabber).CheckWin():
WinnerIds.Add(pc.PlayerId);
AdditionalWinnerTeams.Add(AdditionalWinners.Backstabber);
@@ -189,6 +193,10 @@ class GameEndChecker
WinnerIds.Add(pc.PlayerId);
AdditionalWinnerTeams.Add(AdditionalWinners.Predator);
break;
case CustomRoles.Gaslighter when (Main.PlayerStates[pc.PlayerId].Role as Gaslighter).AddAsAdditionalWinner():
WinnerIds.Add(pc.PlayerId);
AdditionalWinnerTeams.Add(AdditionalWinners.Gaslighter);
break;
case CustomRoles.SoulHunter when (Main.PlayerStates[pc.PlayerId].Role as SoulHunter).Souls >= SoulHunter.NumOfSoulsToWin.GetInt():
WinnerIds.Add(pc.PlayerId);
AdditionalWinnerTeams.Add(AdditionalWinners.SoulHunter);
@@ -410,7 +418,7 @@ class GameEndChecker
foreach (var role in Enum.GetValues<CustomRoles>())
{
if ((!role.IsNK() && role != CustomRoles.Bloodlust) || role.IsMadmate() || role is CustomRoles.Sidekick) continue;
if ((!role.IsNK() && role is not CustomRoles.Bloodlust and not CustomRoles.Gaslighter) || role.IsMadmate() || role is CustomRoles.Sidekick) continue;
var countTypes = role.GetCountTypes();
if (countTypes is CountTypes.Crew or CountTypes.Impostor or CountTypes.None or CountTypes.OutOfGame or CountTypes.CustomTeam) continue;
+2 -1
View File
@@ -503,11 +503,12 @@ static class CheckForEndVotingPatch
{
Witch.OnCheckForEndVoting(deathReason, playerIds);
Virus.OnCheckForEndVoting(deathReason, playerIds);
if (deathReason == PlayerState.DeathReason.Vote) Gaslighter.OnExile(playerIds);
foreach (var playerId in playerIds)
{
var id = playerId;
if (CustomRoles.Lovers.IsEnable() && !Main.IsLoversDead && Main.LoversPlayers.Any(lp => lp.PlayerId == id))
FixedUpdatePatch.LoversSuicide(playerId, true, true);
FixedUpdatePatch.LoversSuicide(playerId, exile: true, force: true);
if (Main.PlayerStates.TryGetValue(id, out var state) && state.SubRoles.Contains(CustomRoles.Avanger))
RevengeOnExile(playerId /*, deathReason*/);
}
+6 -4
View File
@@ -372,6 +372,7 @@ class CheckMurderPatch
Medic.OnAnyoneCheckMurder(killer, target) ||
Randomizer.IsShielded(target) ||
Aid.ShieldedPlayers.ContainsKey(target.PlayerId) ||
!Gaslighter.IsShielded(target) ||
!Grappler.OnAnyoneCheckMurder(target) ||
!Adventurer.OnAnyoneCheckMurder(target) ||
!Sentinel.OnAnyoneCheckMurder(killer) ||
@@ -510,7 +511,7 @@ class CheckMurderPatch
break;
}
if (Main.ShieldPlayer != string.Empty && Main.ShieldPlayer == target.FriendCode && IsAllAlive)
if (MeetingStates.FirstMeeting && Main.ShieldPlayer == target.FriendCode && !string.IsNullOrEmpty(target.FriendCode))
{
Main.ShieldPlayer = string.Empty;
killer.SetKillCooldown(15f);
@@ -1658,6 +1659,7 @@ static class FixedUpdatePatch
if (BallLightning.IsGhost(target)) Mark.Append(ColorString(GetRoleColor(CustomRoles.BallLightning), "■"));
Mark.Append(Medic.GetMark(seer, target));
Mark.Append(Gaslighter.GetMark(seer, target));
Mark.Append(Snitch.GetWarningArrow(seer, target));
Mark.Append(Snitch.GetWarningMark(seer, target));
Mark.Append(Deathpact.GetDeathpactMark(seer, target));
@@ -1704,7 +1706,7 @@ static class FixedUpdatePatch
if (Options.CurrentGameMode == CustomGameMode.SoloKombat)
Suffix.Append(SoloKombatManager.GetDisplayHealth(target));
if (MeetingStates.FirstMeeting && Main.FirstDied != string.Empty && Main.FirstDied == target.FriendCode && !self && Main.ShieldPlayer != string.Empty && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.SoloKombat or CustomGameMode.FFA)
if (MeetingStates.FirstMeeting && Main.ShieldPlayer == target.FriendCode && !string.IsNullOrEmpty(target.FriendCode) && !self && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.SoloKombat or CustomGameMode.FFA)
Suffix.Append(GetString("DiedR1Warning"));
// Devourer
@@ -1777,7 +1779,7 @@ static class FixedUpdatePatch
}
}
public static void LoversSuicide(byte deathId = 0x7f, bool exile = false, bool force = false)
public static void LoversSuicide(byte deathId = 0x7f, bool exile = false, bool force = false, bool guess = false)
{
if (Lovers.LoverDieConsequence.GetValue() == 0 || Main.IsLoversDead || (!Main.LoversPlayers.Any(player => player.Data.IsDead && player.PlayerId == deathId) && !force)) return;
@@ -1791,7 +1793,7 @@ static class FixedUpdatePatch
return;
}
if (Lovers.LoverSuicideTime.GetValue() != 0 && !exile) return;
if (Lovers.LoverSuicideTime.GetValue() != 0 && !exile && !guess) return;
if (exile) CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.FollowingSuicide, partnerPlayer.PlayerId);
else partnerPlayer.Suicide(PlayerState.DeathReason.FollowingSuicide);
+22 -1
View File
@@ -96,13 +96,17 @@ class RepairSystemPatch
switch (player.GetCustomRole())
{
case CustomRoles.SabotageMaster:
SabotageMaster.RepairSystem(player.PlayerId, __instance, systemType, amount);
SabotageMaster.RepairSystem(player.PlayerId, systemType, amount);
Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
break;
case CustomRoles.Alchemist when Main.PlayerStates[player.PlayerId].Role is Alchemist { IsEnable: true, FixNextSabo: true }:
Alchemist.RepairSystem(player, systemType, amount);
Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
break;
case CustomRoles.Technician:
Technician.RepairSystem(player.PlayerId, systemType, amount);
Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
break;
}
switch (systemType)
@@ -170,30 +174,44 @@ class RepairSystemPatch
switch (systemType)
{
case SystemTypes.Electrical when amount <= 4:
{
var SwitchSystem = ShipStatus.Instance?.Systems?[SystemTypes.Electrical]?.Cast<SwitchSystem>();
if (SwitchSystem is { IsActive: true })
{
switch (Main.PlayerStates[player.PlayerId].Role)
{
case SabotageMaster:
{
Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} instant-fix-lights", "SwitchSystem");
SabotageMaster.SwitchSystemRepair(player.PlayerId, SwitchSystem, amount);
Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
break;
}
case Alchemist { FixNextSabo: true } am:
{
Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} instant-fix-lights", "SwitchSystem");
SwitchSystem.ActualSwitches = 0;
SwitchSystem.ExpectedSwitches = 0;
am.FixNextSabo = false;
Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
break;
}
case Adventurer av:
{
Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} instant-fix-lights", "SwitchSystem");
SwitchSystem.ActualSwitches = 0;
SwitchSystem.ExpectedSwitches = 0;
av.OnLightsFix();
Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
break;
}
case Technician:
{
Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} instant-fix-lights", "SwitchSystem");
Technician.SwitchSystemRepair(player.PlayerId, SwitchSystem, amount);
Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
break;
}
}
if (player.Is(CustomRoles.Damocles) && Damocles.countRepairSabotage) Damocles.OnRepairSabotage(player.PlayerId);
@@ -201,16 +219,19 @@ class RepairSystemPatch
}
break;
}
case SystemTypes.Reactor:
case SystemTypes.LifeSupp:
case SystemTypes.Comms:
case SystemTypes.Laboratory:
case SystemTypes.HeliSabotage:
case SystemTypes.Electrical:
{
if (player.Is(CustomRoles.Damocles) && Damocles.countRepairSabotage) Damocles.OnRepairSabotage(player.PlayerId);
if (player.Is(CustomRoles.Stressed) && Stressed.countRepairSabotage) Stressed.OnRepairSabotage(player);
if (Main.PlayerStates[player.PlayerId].Role is Rogue rg) rg.OnFixSabotage();
break;
}
}
}
+16
View File
@@ -1846,8 +1846,21 @@
"RomanticProtectDuration": "Protect Duration",
"RomanticKnowTargetRole": "<color=#ff1493>Romantic</color> knows their target's role",
"RomanticBetTargetKnowRomantic": "Target knows who the <color=#ff1493>Romantic</color> is",
"RomanticGetsPartnerConvertedAddons": "<color=#ff1493>Romantic</color> gets the converted add-ons of their partner",
"RomanticArrows": "<color=#ff1493>Romantic</color> has arrows pointing to their Partner",
"RomanticPartnerHasArrows": "Partner has arrows pointing to the <color=#FF1493>Romantic</color>",
"Gaslighter": "Gaslighter",
"GaslighterInfo": "Make others doubt their own memory",
"GaslighterInfoLong": "(Neutrals):\\nAs the Gaslighter, you do different actions through your kill button every round.<size=80%>\\n\\nRound 1: Normal kills\\nRound 2: Knight players\\nRound 3: Curse players (like Witch)\\nRound 4: Shield players\\n\\n</size><size=90%>Then the cycle repeats, or you stay on normally killing until the end of the game depending on the settings.\\nSettings determine your win condition.</size>",
"Gaslighter.WinCondition": "Win Condition",
"GaslighterWinCondition.CrewLoses": "<#8cffff>Crewmates</color> <#ff0000>lose</color>",
"GaslighterWinCondition.IfAlive": "<#00ff00>Alive</color> at the end",
"GaslighterWinCondition.LastStanding": "<#ffab1b>Last one standing</color>",
"Gaslighter.CycleRepeats": "Kill Button Actions Cycle Repeats",
"Gaslighter.Kill": "In this round, you <#ff1919>kill normally</color>",
"Gaslighter.Knight": "In this round, you <#ffa500>knight players</color>",
"Gaslighter.Curse": "In this round, you <#ff00ff>curse players</color>",
"Gaslighter.Shield": "In this round, you <#00ff97>shield players</color>",
"Speedrunner": "Speedrunner",
"SpeedrunnerInfo": "Crew wins if you finish your tasks",
"SpeedrunnerInfoLong": "(Crewmates):\\nWhen you complete all of your tasks (and you're still alive), the game instantly ends and everyone on the crewmate team will win.",
@@ -2983,6 +2996,7 @@
"Hypnotist": "Hypnotist",
"HypnotistInfo": "Prevent Body Reports",
"HypnotistInfoLong": "(Impostors):\\nAs the Hypnotist, you can prevent all players from reporting bodies by using your ability (shapeshift/pet).",
"Hypnotist.DoReportAfterHypnosisEnds": "Process reports that were blocked during hypnosis after the hypnosis expires",
"Altruist": "Altruist",
"AltruistInfo": "Erase your killer's role",
"AltruistInfoLong": "(Crewmates):\\nAs the Altruist, if an impostor kills you, they will lose their abilities and become a regular impostor or shapeshifter.",
@@ -4473,6 +4487,7 @@
"Bloodmoon.Duration": "Time After Target Dies",
"Bloodmoon.Suffix": "<{1}>You will die in <{2}>{0}s</color></color>",
"Bloodmoon.Speed": "<#ff1919>Bloodmoon</color> Speed",
"Bloodmoon.DieOnMeetingCall": "Targets Die on Meeting Call",
"Prefix.Bloodmoon": "Bloodmoon",
"GA": "Guardian Angel",
"GAInfo": "Protect Crewmates",
@@ -4643,6 +4658,7 @@
"President.UsedDecreeMessage.Everyone": "The <#30916f>President</color> has used the <#ffab1b>{0}</color>!",
"President.DecreeAlreadyUsedMessage": "You have already used this decree or it isn't enabled!",
"President.DecreeAlreadyChosenThisMeetingMessage": "You have already chosen a decree this meeting!",
"President.UsedDecreeMessage.RevealMessage": "{0} is the <#30916f>President</color>!",
"GuessPresident": "You cannot guess a revealed <#30916f>President</color>.",
"GuessErased": "You cannot guess a player's role that has been erased.",
"Alchemist": "Alchemist",
+1 -5
View File
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using EHR.Modules;
using Hazel;
using static EHR.Options;
@@ -205,10 +204,7 @@ namespace EHR.AddOns.Crewmate
private static void AdjustTime(int change)
{
if (!IsEnable) return;
foreach (var x in Timers.Keys.ToArray())
{
Timers[x] += change;
}
Timers.AdjustAllValues(x => x + change);
}
}
}
+10 -4
View File
@@ -7,6 +7,7 @@ namespace EHR.AddOns.GhostRoles
private static OptionItem CD;
private static OptionItem Duration;
private static OptionItem Speed;
private static OptionItem DieOnMeetingCall;
private static readonly Dictionary<byte, long> ScheduledDeaths = [];
public Team Team => Team.Impostor | Team.Neutral;
@@ -36,6 +37,8 @@ namespace EHR.AddOns.GhostRoles
Speed = new FloatOptionItem(649404, "Bloodmoon.Speed", new(0.05f, 5f, 0.05f), 1f, TabGroup.OtherRoles)
.SetParent(Options.CustomRoleSpawnChances[CustomRoles.Bloodmoon])
.SetValueFormat(OptionFormat.Multiplier);
DieOnMeetingCall = new BooleanOptionItem(649405, "Bloodmoon.DieOnMeetingCall", true, TabGroup.OtherRoles)
.SetParent(Options.CustomRoleSpawnChances[CustomRoles.Bloodmoon]);
}
public static void Update(PlayerControl pc)
@@ -59,12 +62,15 @@ namespace EHR.AddOns.GhostRoles
public static void OnMeetingStart()
{
foreach (var id in ScheduledDeaths.Keys)
if (DieOnMeetingCall.GetBool())
{
var pc = Utils.GetPlayerById(id);
if (pc == null || !pc.IsAlive()) continue;
foreach (var id in ScheduledDeaths.Keys)
{
var pc = Utils.GetPlayerById(id);
if (pc == null || !pc.IsAlive()) continue;
pc.Suicide();
pc.Suicide();
}
}
ScheduledDeaths.Clear();
+1 -6
View File
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
namespace EHR.AddOns.Impostor
{
@@ -68,11 +67,7 @@ namespace EHR.AddOns.Impostor
public static void AfterMeetingTasks()
{
if (VentPreventionMode.GetValue() != 2) return;
foreach (var playerId in Limits.Keys.ToArray())
{
Limits[playerId] = Limit.GetInt();
}
Limits.SetAllValues(Limit.GetInt());
}
public static string GetProgressText(byte playerId)
+2 -9
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using EHR.Modules;
using Hazel;
using static EHR.Options;
@@ -164,18 +163,12 @@ namespace EHR.AddOns.Impostor
private static void AdjustTime(int change)
{
foreach (var item in Timer.Keys.ToArray())
{
Timer[item] += change;
}
Timer.AdjustAllValues(x => x + change);
}
private static void AdjustTimeByPercent(double percent)
{
foreach (var item in Timer.Keys.ToArray())
{
Timer[item] = (int)Math.Round(Timer[item] * percent);
}
Timer.AdjustAllValues(x => (int)Math.Round(x * percent));
}
public static string GetProgressText(byte id) => string.Format(GetString("DamoclesTimeLeft"), Timer.GetValueOrDefault(id, StartingTime));
+2 -2
View File
@@ -115,7 +115,7 @@ namespace EHR.Crewmate
{
if (!IsEnable) return;
shieldedPlayers.Clear();
foreach (var playerId in TaskMarkPerRound.Keys)
foreach (var playerId in TaskMarkPerRound.Keys.ToArray())
{
TaskMarkPerRound[playerId] = 0;
if (taskIndex.ContainsKey(playerId)) taskIndex[playerId].Clear();
@@ -156,7 +156,7 @@ namespace EHR.Crewmate
}
else
{
foreach (var benefactorId in taskIndex.Keys)
foreach (var benefactorId in taskIndex.Keys.ToArray())
{
if (taskIndex[benefactorId].Contains(task.Index))
{
+1 -1
View File
@@ -111,7 +111,7 @@ namespace EHR.Crewmate
}
else if (arTime >= FarseerRevealTime.GetFloat())
{
if (UsePets.GetBool()) player.AddKCDAsAbilityCD();
if (UsePets.GetBool() && UsePet.GetBool()) player.AddKCDAsAbilityCD();
else player.SetKillCooldown();
FarseerTimer.Remove(player.PlayerId);
IsRevealed[(player.PlayerId, arTarget.PlayerId)] = true;
+5 -4
View File
@@ -66,13 +66,13 @@ namespace EHR.Crewmate
switch (decree)
{
case Decree.Reveal:
DecreeSettings[decree].AddRange(new[]
{
DecreeSettings[decree].AddRange(
[
new BooleanOptionItem(++id, "President.Reveal.CanBeConvertedAfterRevealing", false, tab)
.SetParent(DecreeSettings[decree][0]),
new BooleanOptionItem(++id, "President.Reveal.ConvertedPresidentCanReveal", false, tab)
.SetParent(DecreeSettings[decree][0])
});
]);
break;
case Decree.GovernmentRecruiting:
DecreeSettings[decree].Add(new StringOptionItem(++id, "President.GovernmentRecruiting.RecruitedRole", GovernmentRecruitRoles.Select(x => x.ToColoredString()).ToArray(), 0, tab, noTranslation: true)
@@ -145,8 +145,9 @@ namespace EHR.Crewmate
{
case Decree.Reveal:
if (!DecreeSettings[decree][1].GetBool()) pc.RpcSetCustomRole(CustomRoles.Loyal);
if (!DecreeSettings[decree][2].GetBool() && pc.GetCustomSubRoles().Any(x => x.IsConverted())) return;
if (!DecreeSettings[decree][2].GetBool() && pc.IsConverted() || pc.Is(CustomRoles.Bloodlust)) return;
Utils.SendMessage(string.Format(Translator.GetString("President.UsedDecreeMessage.Everyone"), Translator.GetString($"President.Decree.{decree}")));
Utils.SendMessage(string.Format(Translator.GetString("President.UsedDecreeMessage.RevealMessage"), pc.PlayerId.ColoredPlayerName()));
break;
case Decree.Finish:
MeetingHud.Instance?.RpcClose();
+99 -47
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using AmongUs.GameOptions;
using EHR.Modules;
using Hazel;
@@ -24,6 +25,7 @@ public class SabotageMaster : RoleBase
public static OptionItem UsesUsedWhenFixingLightsOrComms;
private static bool DoorsProgressing;
private bool fixedSabotage;
private byte SMId;
public float UsedSkillCount;
@@ -97,106 +99,156 @@ public class SabotageMaster : RoleBase
sm.UsedSkillCount = reader.ReadSingle();
}
public static void RepairSystem(byte playerId, ShipStatus __instance, SystemTypes systemType, byte amount)
public static void RepairSystem(byte playerId, SystemTypes systemType, byte amount)
{
if (Main.PlayerStates[playerId].Role is not SabotageMaster sm) return;
switch (systemType)
{
case SystemTypes.Reactor:
if (!FixesReactors.GetBool()) break;
if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingReactorOrO2.GetFloat() - 1 >= SkillLimit.GetFloat()) break;
if (amount is 64 or 65)
{
ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Reactor, 16);
ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Reactor, 17);
sm.UsedSkillCount += UsesUsedWhenFixingReactorOrO2.GetFloat();
sm.SendRPC();
}
break;
case SystemTypes.Laboratory:
{
if (!FixesReactors.GetBool()) break;
if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingReactorOrO2.GetFloat() - 1 >= SkillLimit.GetFloat()) break;
if (amount is 64 or 65)
if (amount.HasAnyBit(ReactorSystemType.AddUserOp))
{
ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Laboratory, 67);
ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Laboratory, 66);
ShipStatus.Instance.UpdateSystem((MapNames)Main.NormalOptions.MapId == MapNames.Polus ? SystemTypes.Laboratory : SystemTypes.Reactor, playerId.GetPlayer(), ReactorSystemType.ClearCountdown);
sm.UsedSkillCount += UsesUsedWhenFixingReactorOrO2.GetFloat();
sm.SendRPC();
}
break;
}
case SystemTypes.HeliSabotage:
{
if (!FixesReactors.GetBool()) break;
if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingReactorOrO2.GetFloat() - 1 >= SkillLimit.GetFloat()) break;
var tags = (HeliSabotageSystem.Tags)(amount & HeliSabotageSystem.TagMask);
if (tags == HeliSabotageSystem.Tags.ActiveBit)
{
sm.fixedSabotage = false;
}
if (!sm.fixedSabotage && tags == HeliSabotageSystem.Tags.FixBit)
{
sm.fixedSabotage = true;
var consoleId = amount & HeliSabotageSystem.IdMask;
var otherConsoleId = (consoleId + 1) % 2;
ShipStatus.Instance.UpdateSystem(SystemTypes.HeliSabotage, playerId.GetPlayer(), (byte)(otherConsoleId | (int)HeliSabotageSystem.Tags.FixBit));
sm.UsedSkillCount += UsesUsedWhenFixingReactorOrO2.GetFloat();
sm.SendRPC();
}
break;
}
case SystemTypes.LifeSupp:
{
if (!FixesOxygens.GetBool()) break;
if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingReactorOrO2.GetFloat() - 1 >= SkillLimit.GetFloat()) break;
if (amount is 64 or 65)
if (amount.HasAnyBit(LifeSuppSystemType.AddUserOp))
{
ShipStatus.Instance.RpcUpdateSystem(SystemTypes.LifeSupp, 67);
ShipStatus.Instance.RpcUpdateSystem(SystemTypes.LifeSupp, 66);
ShipStatus.Instance.UpdateSystem(SystemTypes.LifeSupp, playerId.GetPlayer(), LifeSuppSystemType.ClearCountdown);
sm.UsedSkillCount += UsesUsedWhenFixingReactorOrO2.GetFloat();
sm.SendRPC();
}
break;
case SystemTypes.Comms:
}
case SystemTypes.Comms when Main.CurrentMap == MapNames.Mira:
{
if (!FixesComms.GetBool()) break;
if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingLightsOrComms.GetFloat() - 1 >= SkillLimit.GetFloat()) break;
if (amount is 64 or 65)
var tags = (HqHudSystemType.Tags)(amount & HqHudSystemType.TagMask);
if (tags == HqHudSystemType.Tags.ActiveBit)
{
ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Comms, 16);
ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Comms, 17);
sm.fixedSabotage = false;
}
if (!sm.fixedSabotage && tags == HqHudSystemType.Tags.FixBit)
{
sm.fixedSabotage = true;
var consoleId = amount & HqHudSystemType.IdMask;
var otherConsoleId = (consoleId + 1) % 2;
ShipStatus.Instance.UpdateSystem(SystemTypes.Comms, playerId.GetPlayer(), (byte)(otherConsoleId | (int)HqHudSystemType.Tags.FixBit));
sm.UsedSkillCount += UsesUsedWhenFixingLightsOrComms.GetFloat();
sm.SendRPC();
}
break;
}
case SystemTypes.Doors:
{
if (!FixesDoors.GetBool()) break;
if (DoorsProgressing) break;
int mapId = Main.NormalOptions.MapId;
if (AmongUsClient.Instance.NetworkMode == NetworkModes.FreePlay) mapId = AmongUsClient.Instance.TutorialMapId;
var shipStatus = ShipStatus.Instance;
DoorsProgressing = true;
switch (mapId)
{
case 2:
//Polus
RepairSystemPatch.CheckAndOpenDoorsRange(__instance, amount, 71, 72);
RepairSystemPatch.CheckAndOpenDoorsRange(__instance, amount, 67, 68);
RepairSystemPatch.CheckAndOpenDoorsRange(__instance, amount, 64, 66);
RepairSystemPatch.CheckAndOpenDoorsRange(__instance, amount, 73, 74);
case 2: // Polus
{
RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 71, 72);
RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 67, 68);
RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 64, 66);
RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 73, 74);
break;
case 4:
//Airship
RepairSystemPatch.CheckAndOpenDoorsRange(__instance, amount, 64, 67);
RepairSystemPatch.CheckAndOpenDoorsRange(__instance, amount, 71, 73);
RepairSystemPatch.CheckAndOpenDoorsRange(__instance, amount, 74, 75);
RepairSystemPatch.CheckAndOpenDoorsRange(__instance, amount, 76, 78);
RepairSystemPatch.CheckAndOpenDoorsRange(__instance, amount, 68, 70);
RepairSystemPatch.CheckAndOpenDoorsRange(__instance, amount, 83, 84);
}
case 4: // Airship
{
RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 64, 67);
RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 71, 73);
RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 74, 75);
RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 76, 78);
RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 68, 70);
RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 83, 84);
break;
}
case 5: // Fungle
{
var openedDoorId = amount & DoorsSystemType.IdMask;
var openedDoor = shipStatus.AllDoors.FirstOrDefault(door => door.Id == openedDoorId);
if (openedDoor == null)
{
Logger.Warn($"An unknown door has been opened: {openedDoorId}", nameof(SabotageMaster));
}
else
{
var room = openedDoor.Room;
foreach (var door in shipStatus.AllDoors)
{
if (door.Id != openedDoorId && door.Room == room)
{
door.SetDoorway(true);
}
}
}
break;
}
}
DoorsProgressing = false;
break;
}
}
}
public static void SwitchSystemRepair(byte playerId, SwitchSystem __instance, byte amount)
public static void SwitchSystemRepair(byte playerId, SwitchSystem switchSystem, byte amount)
{
if (!FixesElectrical.GetBool() || Main.PlayerStates[playerId].Role is not SabotageMaster sm) return;
if (SkillLimit.GetFloat() > 0 &&
sm.UsedSkillCount + UsesUsedWhenFixingLightsOrComms.GetFloat() - 1 >= SkillLimit.GetFloat())
return;
if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingLightsOrComms.GetFloat() - 1 >= SkillLimit.GetFloat()) return;
if (amount <= 4)
{
__instance.ActualSwitches = 0;
__instance.ExpectedSwitches = 0;
sm.UsedSkillCount += UsesUsedWhenFixingLightsOrComms.GetFloat();
sm.SendRPC();
}
if (amount.HasBit(SwitchSystem.DamageSystem)) return;
var fixbit = 1 << amount;
switchSystem.ActualSwitches = (byte)(switchSystem.ExpectedSwitches ^ fixbit);
sm.UsedSkillCount += UsesUsedWhenFixingLightsOrComms.GetFloat();
sm.SendRPC();
}
}
+6 -1
View File
@@ -15,6 +15,7 @@ namespace EHR.Impostor
public static OptionItem AbilityDuration;
public static OptionItem AbilityUseLimit;
public static OptionItem AbilityUseGainWithEachKill;
private static OptionItem DoReportAfterHypnosisEnds;
private long ActivateTS;
private int Count;
@@ -28,7 +29,8 @@ namespace EHR.Impostor
.AutoSetupOption(ref AbilityCooldown, 30, new IntegerValueRule(0, 60, 1), OptionFormat.Seconds)
.AutoSetupOption(ref AbilityDuration, 15, new IntegerValueRule(0, 30, 1), OptionFormat.Seconds)
.AutoSetupOption(ref AbilityUseLimit, 1, new IntegerValueRule(0, 5, 1), OptionFormat.Times)
.AutoSetupOption(ref AbilityUseGainWithEachKill, 0.5f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times);
.AutoSetupOption(ref AbilityUseGainWithEachKill, 0.5f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times)
.AutoSetupOption(ref DoReportAfterHypnosisEnds, true);
}
public override void Init()
@@ -67,6 +69,8 @@ namespace EHR.Impostor
ActivateTS = Utils.TimeStamp;
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
Utils.SendRPC(CustomRPC.SyncRoleData, HypnotistId, ActivateTS);
if (DoReportAfterHypnosisEnds.GetBool()) ReportDeadBodyPatch.CanReport.SetAllValues(false);
}
public override void OnFixedUpdate(PlayerControl pc)
@@ -78,6 +82,7 @@ namespace EHR.Impostor
ActivateTS = 0;
notify = true;
pc.RpcResetAbilityCooldown();
if (DoReportAfterHypnosisEnds.GetBool()) ReportDeadBodyPatch.CanReport.SetAllValues(true);
}
else if (ActivateTS != 0 && Count++ >= 30 && timeLeft <= 6)
{
+15 -4
View File
@@ -111,6 +111,12 @@ public class Witch : RoleBase
}
}
public override void OnPet(PlayerControl pc)
{
if (NowSwitchTrigger == SwitchTrigger.DoubleTrigger) return;
SwitchMode(pc.PlayerId);
}
void SwitchSpellMode(byte playerId, bool kill)
{
bool needSwitch = NowSwitchTrigger switch
@@ -121,13 +127,18 @@ public class Witch : RoleBase
};
if (needSwitch)
{
SpellMode = !SpellMode;
SendRPC(false, playerId);
var pc = Utils.GetPlayerById(playerId);
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
SwitchMode(playerId);
}
}
void SwitchMode(byte playerId)
{
SpellMode = !SpellMode;
SendRPC(false, playerId);
var pc = Utils.GetPlayerById(playerId);
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
}
bool IsSpelled(byte target) => SpelledPlayer.Contains(target);
void SetSpelled(PlayerControl killer, PlayerControl target)
+3
View File
@@ -191,6 +191,9 @@ public class Amnesiac : RoleBase
target.RpcGuardAndKill(amnesiac);
target.RpcGuardAndKill(target);
if (role.IsRecruitingRole())
amnesiac.SetAbilityUseLimit(0);
if (role.GetRoleTypes() == RoleTypes.Engineer)
WasAmnesiac.Add(amnesiac.PlayerId);
}
+2 -1
View File
@@ -150,7 +150,8 @@ namespace EHR.Neutral
public override void OnReportDeadBody()
{
if (!IsEnable) return;
foreach (var pc in EncasedPlayers.Keys.Select(x => GetPlayerById(x)).Where(x => x != null && x.IsAlive())) pc.Suicide(PlayerState.DeathReason.Bombed, Bubble_);
foreach (var pc in EncasedPlayers.Keys.Select(x => GetPlayerById(x)).Where(x => x != null && x.IsAlive()))
pc.Suicide(PlayerState.DeathReason.Bombed, Bubble_);
EncasedPlayers.Clear();
SendRPC(clear: true);
}
+183
View File
@@ -0,0 +1,183 @@
using System.Collections.Generic;
using AmongUs.GameOptions;
using EHR.Crewmate;
using EHR.Patches;
namespace EHR.Neutral
{
public class Gaslighter : RoleBase
{
public static bool On;
private static List<Gaslighter> Instances = [];
private static OptionItem KillCooldown;
public static OptionItem WinCondition;
public static OptionItem CycleRepeats;
static readonly string[] WinConditionOptions =
[
"GaslighterWinCondition.CrewLoses",
"GaslighterWinCondition.IfAlive",
"GaslighterWinCondition.LastStanding"
];
private Round CurrentRound;
private HashSet<byte> CursedPlayers;
private bool CycleFinished;
private byte GaslighterId;
private HashSet<byte> ShieldedPlayers;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(648350, TabGroup.NeutralRoles, CustomRoles.Gaslighter)
.AutoSetupOption(ref KillCooldown, 22.5f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref WinCondition, 0, WinConditionOptions)
.AutoSetupOption(ref CycleRepeats, false);
}
public override void Init()
{
On = false;
Instances = [];
}
public override void Add(byte playerId)
{
On = true;
Instances.Add(this);
GaslighterId = playerId;
CurrentRound = default;
CursedPlayers = [];
ShieldedPlayers = [];
CycleFinished = false;
}
public override void SetKillCooldown(byte id)
{
Main.AllPlayerKillCooldown[id] = CurrentRound switch
{
Round.Kill => KillCooldown.GetFloat(),
Round.Knight => Monarch.KnightCooldown.GetFloat(),
Round.Curse => Main.RealOptionsData.GetFloat(FloatOptionNames.KillCooldown),
Round.Shield => Medic.CD.GetFloat(),
_ => Options.DefaultKillCooldown
};
}
public static void OnExile(byte[] exileIds)
{
foreach (Gaslighter instance in Instances)
{
foreach (byte id in exileIds)
{
if (id == instance.GaslighterId)
instance.CursedPlayers.Clear();
}
}
List<byte> curseDeathList = [];
foreach (PlayerControl pc in Main.AllAlivePlayerControls)
{
foreach (Gaslighter instance in Instances)
{
if (Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) continue;
var gaslighter = instance.GaslighterId.GetPlayer();
if (instance.CursedPlayers.Contains(pc.PlayerId) && gaslighter != null && gaslighter.IsAlive())
{
pc.SetRealKiller(gaslighter);
curseDeathList.Add(pc.PlayerId);
}
}
}
CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Spell, [.. curseDeathList]);
}
public override void AfterMeetingTasks()
{
ShieldedPlayers.Clear();
CursedPlayers.Clear();
if (CurrentRound == Round.Shield)
{
CycleFinished = true;
CurrentRound = Round.Kill;
}
else if (!CycleFinished || CycleRepeats.GetBool())
{
CurrentRound++;
}
float limit = CurrentRound switch
{
Round.Knight => Monarch.KnightMax.GetFloat(),
Round.Shield => Medic.SkillLimit,
_ => 0
};
GaslighterId.SetAbilityUseLimit(limit);
var pc = GaslighterId.GetPlayer();
pc?.ResetKillCooldown();
pc?.Notify(Translator.GetString($"Gaslighter.{CurrentRound}"));
}
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
switch (CurrentRound)
{
case Round.Kill:
return true;
case Round.Knight when killer.GetAbilityUseLimit() > 0 && !target.Is(CustomRoles.Knighted):
target.RpcSetCustomRole(CustomRoles.Knighted);
killer.RpcRemoveAbilityUse();
killer.SetKillCooldown();
return false;
case Round.Curse:
CursedPlayers.Add(target.PlayerId);
killer.SetKillCooldown();
return false;
case Round.Shield:
ShieldedPlayers.Add(target.PlayerId);
killer.SetKillCooldown();
return false;
}
return false;
}
public static bool IsShielded(PlayerControl target) => Instances.Exists(i => i.ShieldedPlayers.Contains(target.PlayerId));
public override string GetProgressText(byte playerId, bool comms)
{
return CurrentRound is Round.Knight or Round.Shield
? base.GetProgressText(playerId, comms)
: Utils.GetTaskCount(playerId, comms);
}
public static string GetMark(PlayerControl seer, PlayerControl target)
{
if (!IsShielded(target)) return string.Empty;
if (!seer.Is(CustomRoles.Gaslighter) && seer.PlayerId != target.PlayerId) return string.Empty;
return $"<color={Utils.GetRoleColorCode(CustomRoles.Medic)}> ●</color>";
}
public bool AddAsAdditionalWinner() => WinCondition.GetValue() switch
{
0 => CustomWinnerHolder.WinnerTeam != CustomWinner.Crewmate,
1 => GaslighterId.GetPlayer()?.IsAlive() == true,
_ => false
};
enum Round
{
Kill,
Knight,
Curse,
Shield
}
}
}
+15 -9
View File
@@ -25,6 +25,7 @@ public class Romantic : RoleBase
private static OptionItem ProtectDuration;
private static OptionItem KnowTargetRole;
private static OptionItem BetTargetKnowRomantic;
private static OptionItem RomanticGetsPartnerConvertedAddons;
private static OptionItem Arrows;
private static OptionItem PartnerHasArrows;
public static OptionItem VengefulKCD;
@@ -63,23 +64,25 @@ public class Romantic : RoleBase
.SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]);
BetTargetKnowRomantic = new BooleanOptionItem(Id + 14, "RomanticBetTargetKnowRomantic", true, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]);
Arrows = new BooleanOptionItem(Id + 15, "RomanticArrows", true, TabGroup.NeutralRoles)
RomanticGetsPartnerConvertedAddons = new BooleanOptionItem(Id + 15, "RomanticGetsPartnerConvertedAddons", true, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]);
PartnerHasArrows = new BooleanOptionItem(Id + 16, "RomanticPartnerHasArrows", true, TabGroup.NeutralRoles)
Arrows = new BooleanOptionItem(Id + 16, "RomanticArrows", true, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]);
VengefulKCD = new FloatOptionItem(Id + 17, "VengefulKCD", new(0f, 60f, 2.5f), 22.5f, TabGroup.NeutralRoles)
PartnerHasArrows = new BooleanOptionItem(Id + 17, "RomanticPartnerHasArrows", true, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]);
VengefulKCD = new FloatOptionItem(Id + 18, "VengefulKCD", new(0f, 60f, 2.5f), 22.5f, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Romantic])
.SetValueFormat(OptionFormat.Seconds);
VengefulCanVent = new BooleanOptionItem(Id + 18, "VengefulCanVent", true, TabGroup.NeutralRoles)
VengefulCanVent = new BooleanOptionItem(Id + 19, "VengefulCanVent", true, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]);
VengefulHasImpVision = new BooleanOptionItem(Id + 19, "VengefulHasImpVision", true, TabGroup.NeutralRoles)
VengefulHasImpVision = new BooleanOptionItem(Id + 20, "VengefulHasImpVision", true, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]);
RuthlessKCD = new FloatOptionItem(Id + 20, "RuthlessKCD", new(0f, 60f, 2.5f), 22.5f, TabGroup.NeutralRoles)
RuthlessKCD = new FloatOptionItem(Id + 21, "RuthlessKCD", new(0f, 60f, 2.5f), 22.5f, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Romantic])
.SetValueFormat(OptionFormat.Seconds);
RuthlessCanVent = new BooleanOptionItem(Id + 21, "RuthlessCanVent", true, TabGroup.NeutralRoles)
RuthlessCanVent = new BooleanOptionItem(Id + 22, "RuthlessCanVent", true, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]);
RuthlessHasImpVision = new BooleanOptionItem(Id + 22, "RuthlessHasImpVision", true, TabGroup.NeutralRoles)
RuthlessHasImpVision = new BooleanOptionItem(Id + 23, "RuthlessHasImpVision", true, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]);
}
@@ -150,13 +153,16 @@ public class Romantic : RoleBase
RomanticPC.Notify(GetString("RomanticBetPlayer"));
if (BetTargetKnowRomantic.GetBool()) target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Romantic), GetString("RomanticBetOnYou")));
if (RomanticGetsPartnerConvertedAddons.GetBool() && Partner.IsConverted())
Partner.GetCustomSubRoles().DoIf(x => x.IsConverted() && !Partner.Is(x), x => RomanticPC.RpcSetCustomRole(x));
if (Arrows.GetBool())
{
TargetArrow.Add(RomanticId, PartnerId);
if (PartnerHasArrows.GetBool() && BetTargetKnowRomantic.GetBool()) TargetArrow.Add(PartnerId, RomanticId);
}
Logger.Info($"Partner picked: {RomanticPC.GetNameWithRole().RemoveHtmlTags()} => {target.GetNameWithRole().RemoveHtmlTags()}", "Romantic");
Logger.Info($"Partner picked: {RomanticPC.GetNameWithRole().RemoveHtmlTags()} => {target.GetNameWithRole().RemoveHtmlTags()}", "Romantic");
}
else if (!IsPartnerProtected)
{
+173
View File
@@ -0,0 +1,173 @@
using System.Collections.Generic;
using AmongUs.GameOptions;
namespace EHR.Neutral
{
public class Technician : RoleBase
{
public static bool On;
private static OptionItem WinsAlone;
private static OptionItem RequiredPoints;
public static OptionItem CanVent;
private static OptionItem VentCooldown;
private static OptionItem MaxInVentTime;
private static readonly Dictionary<SystemTypes, OptionItem> PointGains = [];
private bool fixedSabotage;
public bool IsWon;
private PlayerControl TechnicianPC;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(646950, TabGroup.NeutralRoles, CustomRoles.Technician)
.AutoSetupOption(ref WinsAlone, false)
.AutoSetupOption(ref RequiredPoints, 5, new IntegerValueRule(1, 50, 1))
.AutoSetupOption(ref CanVent, false)
.AutoSetupOption(ref VentCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds, overrideParent: CanVent)
.AutoSetupOption(ref MaxInVentTime, 5f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds, overrideParent: CanVent);
int i = 0;
foreach (var system in new[] { SystemTypes.Electrical, SystemTypes.Comms, SystemTypes.LifeSupp, SystemTypes.Reactor })
{
PointGains[system] = new IntegerOptionItem(646960 + i, $"TechnicianPointGain.{system}", new(0, 10, 1), 1, TabGroup.NeutralRoles)
.SetParent(Options.CustomRoleSpawnChances[CustomRoles.Technician]);
i++;
}
}
public override void Init()
{
On = false;
}
public override void Add(byte playerId)
{
On = true;
IsWon = false;
TechnicianPC = playerId.GetPlayer();
fixedSabotage = false;
playerId.SetAbilityUseLimit(0);
}
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
{
if (CanVent.GetBool())
{
AURoleOptions.EngineerCooldown = VentCooldown.GetFloat();
AURoleOptions.EngineerInVentMaxTime = MaxInVentTime.GetFloat();
}
}
private static SystemTypes GetActualSystemType(SystemTypes systemTypes) => systemTypes switch
{
SystemTypes.Laboratory => SystemTypes.Reactor,
SystemTypes.HeliSabotage => SystemTypes.Reactor,
_ => systemTypes
};
private static bool GetsAnyPoint(SystemTypes systemType) => PointGains.TryGetValue(GetActualSystemType(systemType), out var pointGain) && pointGain.GetInt() > 0;
void IncreasePoints(SystemTypes systemType)
{
var actualSystemType = GetActualSystemType(systemType);
TechnicianPC.RpcIncreaseAbilityUseLimitBy(PointGains[actualSystemType].GetInt());
if (TechnicianPC.GetAbilityUseLimit() >= RequiredPoints.GetInt())
{
if (WinsAlone.GetBool())
{
CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Technician);
CustomWinnerHolder.WinnerIds.Add(TechnicianPC.PlayerId);
}
else IsWon = true;
}
}
public static void RepairSystem(byte playerId, SystemTypes systemType, byte amount)
{
if (Main.PlayerStates[playerId].Role is not Technician technician) return;
if (!GetsAnyPoint(systemType) || technician.IsWon) return;
switch (systemType)
{
case SystemTypes.Reactor:
case SystemTypes.Laboratory:
{
if (amount.HasAnyBit(ReactorSystemType.AddUserOp))
{
ShipStatus.Instance.UpdateSystem((MapNames)Main.NormalOptions.MapId == MapNames.Polus ? SystemTypes.Laboratory : SystemTypes.Reactor, playerId.GetPlayer(), ReactorSystemType.ClearCountdown);
technician.IncreasePoints(systemType);
}
break;
}
case SystemTypes.HeliSabotage:
{
var tags = (HeliSabotageSystem.Tags)(amount & HeliSabotageSystem.TagMask);
if (tags == HeliSabotageSystem.Tags.ActiveBit)
{
technician.fixedSabotage = false;
}
if (!technician.fixedSabotage && tags == HeliSabotageSystem.Tags.FixBit)
{
technician.fixedSabotage = true;
var consoleId = amount & HeliSabotageSystem.IdMask;
var otherConsoleId = (consoleId + 1) % 2;
ShipStatus.Instance.UpdateSystem(SystemTypes.HeliSabotage, playerId.GetPlayer(), (byte)(otherConsoleId | (int)HeliSabotageSystem.Tags.FixBit));
technician.IncreasePoints(systemType);
}
break;
}
case SystemTypes.LifeSupp:
{
if (amount.HasAnyBit(LifeSuppSystemType.AddUserOp))
{
ShipStatus.Instance.UpdateSystem(SystemTypes.LifeSupp, playerId.GetPlayer(), LifeSuppSystemType.ClearCountdown);
technician.IncreasePoints(systemType);
}
break;
}
case SystemTypes.Comms:
{
if (Main.CurrentMap == MapNames.Mira)
{
var tags = (HqHudSystemType.Tags)(amount & HqHudSystemType.TagMask);
if (tags == HqHudSystemType.Tags.ActiveBit)
{
technician.fixedSabotage = false;
}
if (!technician.fixedSabotage && tags == HqHudSystemType.Tags.FixBit)
{
technician.fixedSabotage = true;
var consoleId = amount & HqHudSystemType.IdMask;
var otherConsoleId = (consoleId + 1) % 2;
ShipStatus.Instance.UpdateSystem(SystemTypes.Comms, playerId.GetPlayer(), (byte)(otherConsoleId | (int)HqHudSystemType.Tags.FixBit));
}
}
technician.IncreasePoints(systemType);
break;
}
}
}
public static void SwitchSystemRepair(byte playerId, SwitchSystem switchSystem, byte amount)
{
if (!GetsAnyPoint(SystemTypes.Electrical) || Main.PlayerStates[playerId].Role is not Technician technician) return;
if (amount.HasBit(SwitchSystem.DamageSystem)) return;
var fixbit = 1 << amount;
switchSystem.ActualSwitches = (byte)(switchSystem.ExpectedSwitches ^ fixbit);
technician.IncreasePoints(SystemTypes.Electrical);
}
}
}
+12 -21
View File
@@ -6,6 +6,7 @@ using System.Text;
using AmongUs.GameOptions;
using EHR.AddOns.Crewmate;
using EHR.AddOns.Impostor;
using EHR.GameMode.HideAndSeekRoles;
using EHR.Neutral;
@@ -80,7 +81,7 @@ namespace EHR
{
if (Options.CurrentGameMode == CustomGameMode.HideAndSeek && HnSManager.PlayerRoles[pc.PlayerId].Interface.Team == Team.Crewmate && pc.IsAlive())
{
int time = GameMode.HideAndSeekRoles.Hider.TimeDecreaseOnTaskComplete.GetInt();
int time = Hider.TimeDecreaseOnTaskComplete.GetInt();
HnSManager.TimeLeft -= time;
pc.Notify(Translator.GetString("TimeDecreased"));
if (60 - (HnSManager.TimeLeft % 60) <= time) Utils.NotifyRoles();
@@ -203,33 +204,23 @@ namespace EHR
public class OptionSetupHandler(int id, TabGroup tab, CustomRoles role)
{
private readonly OptionItem Parent = Options.CustomRoleSpawnChances[role];
private int _id = id;
public OptionSetupHandler AutoSetupOption(ref OptionItem field, object defaultValue, object valueRule = null, OptionFormat format = OptionFormat.None, [CallerArgumentExpression("field")] string fieldName = "", string overrideName = "", OptionItem overrideParent = null, bool noTranslation = false)
{
try
{
var name = overrideName == "" ? IsGeneralOption() ? fieldName : $"{role}.{fieldName}" : overrideName;
switch (valueRule, defaultValue)
field = (valueRule, defaultValue) switch
{
case (null, bool bdv):
field = new BooleanOptionItem(++id, name, bdv, tab);
field.SetParent(overrideParent ?? Parent);
break;
case (IntegerValueRule ivr, int idv):
field = new IntegerOptionItem(++id, name, ivr, idv, tab);
field.SetParent(overrideParent ?? Parent);
break;
case (FloatValueRule fvr, float fdv):
field = new FloatOptionItem(++id, name, fvr, fdv, tab);
field.SetParent(overrideParent ?? Parent);
break;
case (IList<string> selections, int index):
field = new StringOptionItem(++id, name, selections, index, tab, noTranslation: noTranslation);
field.SetParent(overrideParent ?? Parent);
break;
default:
throw new ArgumentException("The valueRule and defaultValue combination is not supported.");
}
(null, bool bdv) => new BooleanOptionItem(++_id, name, bdv, tab),
(IntegerValueRule ivr, int idv) => new IntegerOptionItem(++_id, name, ivr, idv, tab),
(FloatValueRule fvr, float fdv) => new FloatOptionItem(++_id, name, fvr, fdv, tab),
(IList<string> selections, int index) => new StringOptionItem(++_id, name, selections, index, tab, noTranslation: noTranslation),
_ => throw new ArgumentException("The valueRule and defaultValue combination is not supported.")
};
field.SetParent(overrideParent ?? Parent);
if (format != OptionFormat.None) field?.SetValueFormat(format);
}
+5
View File
@@ -496,7 +496,9 @@ public class Main : BasePlugin
{ CustomRoles.SchrodingersCat, "#616161" },
{ CustomRoles.Shifter, "#777777" },
{ CustomRoles.Impartial, "#4287f5" },
{ CustomRoles.Gaslighter, "#b6aa82" },
{ CustomRoles.Tank, "#176320" },
{ CustomRoles.Technician, "#4e96f5" },
{ CustomRoles.Backstabber, "#fcba03" },
{ CustomRoles.Predator, "#c73906" },
{ CustomRoles.Reckless, "#6e000d" },
@@ -778,6 +780,7 @@ public enum CustomWinner
Executioner = CustomRoles.Executioner,
Arsonist = CustomRoles.Arsonist,
Revolutionist = CustomRoles.Revolutionist,
Technician = CustomRoles.Technician,
Jackal = CustomRoles.Jackal,
God = CustomRoles.God,
Mario = CustomRoles.Mario,
@@ -873,7 +876,9 @@ public enum AdditionalWinners
Maverick = CustomRoles.Maverick,
Postman = CustomRoles.Postman,
Impartial = CustomRoles.Impartial,
Gaslighter = CustomRoles.Gaslighter,
Tank = CustomRoles.Tank,
Technician = CustomRoles.Technician,
Backstabber = CustomRoles.Backstabber,
Predator = CustomRoles.Predator,
SoulHunter = CustomRoles.SoulHunter,