Add 13 Coven Roles

This commit is contained in:
Gurge44 committed 2025-01-08 20:21:37 +01:00
1 parent 46c86b5962
commit ced2804d26
29 files changed
+1200 -79

No files matched your search

+2
View File
@@ -366,6 +366,7 @@ public enum CustomRoles
PotionMaster,
Poache,
Reaper,
Death, // Reaper changes to Death
VoodooMaster,
Goddess,
Augur,
@@ -462,6 +463,7 @@ public enum CustomRoles
Guesser,
Haste,
Haunter, // Ghost role
Insane,
Introvert,
Knighted,
LastImpostor,
+2
View File
@@ -923,6 +923,8 @@ public enum CustomWinner
Cherokious = CustomRoles.Cherokious,
Specter = CustomRoles.Specter,
Coven = CustomRoles.CovenLeader,
Bloodlust = CustomRoles.Bloodlust
}
+3 -10
View File
@@ -8,17 +8,16 @@ using EHR.Neutral;
namespace EHR.Modules;
// ReSharper disable AccessToModifiedClosure
internal static class CustomRoleSelector
{
public static Dictionary<byte, CustomRoles> RoleResult;
public static int AddScientistNum;
public static int AddEngineerNum;
public static int AddShapeshifterNum;
public static int AddNoisemakerNum;
public static int AddTrackerNum;
public static int AddPhantomNum;
public static List<CustomRoles> AddonRolesList = [];
public static void SelectCustomRoles()
@@ -113,6 +112,7 @@ internal static class CustomRoleSelector
case CustomRoles.Convict:
case CustomRoles.Refugee:
case CustomRoles.CovenLeader:
case CustomRoles.Death:
case CustomRoles.GM:
case CustomRoles.NotAssigned:
continue;
@@ -194,10 +194,7 @@ internal static class CustomRoleSelector
var impLimits = subCategoryLimits.Where(x => x.Key.GetTabFromOptionType() == TabGroup.ImpostorRoles).ToDictionary(x => x.Key, x => x.Value);
if (impLimits.Count > 0 && impLimits.Sum(x => x.Value) < optImpNum)
{
// ReSharper disable once AccessToModifiedClosure
impLimits.Keys.Do(x => subCategoryLimits[x] = Options.RoleSubCategoryLimits[x][2].GetInt());
}
}
catch (Exception e) { Utils.ThrowException(e); }
@@ -895,7 +892,7 @@ internal static class CustomRoleSelector
case CustomRoles.Autopsy when Options.EveryoneSeesDeathReasons.GetBool():
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:
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
continue;
}
@@ -917,13 +914,9 @@ internal static class CustomRoleSelector
private class RoleAssignInfo(CustomRoles role, int spawnChance, int maxCount)
{
public CustomRoles Role => role;
public int SpawnChance => spawnChance;
public int MaxCount => maxCount;
public int AssignedCount { get; set; }
public RoleOptionType OptionType { get; } = role.GetRoleOptionType();
}
}
+24 -3
View File
@@ -327,6 +327,7 @@ internal static class CustomRolesHelper
CustomRoles.Godfather => CustomRoles.Impostor,
CustomRoles.Silencer => Silencer.SilenceMode.GetValue() == 1 ? CustomRoles.Shapeshifter : CustomRoles.Impostor,
CustomRoles.NoteKiller => CustomRoles.Crewmate,
CustomRoles.Augur => CustomRoles.Crewmate,
// Vanilla roles (just in case)
CustomRoles.ImpostorEHR => CustomRoles.Impostor,
@@ -502,6 +503,21 @@ internal static class CustomRolesHelper
CustomRoles.Spiritcaller => RoleTypes.Impostor,
CustomRoles.Doppelganger => RoleTypes.Impostor,
CustomRoles.Wizard => RoleTypes.Shapeshifter,
CustomRoles.CovenLeader => RoleTypes.Impostor,
CustomRoles.SpellCaster => RoleTypes.Impostor,
CustomRoles.PotionMaster => RoleTypes.Impostor,
CustomRoles.Poache => RoleTypes.Impostor,
CustomRoles.Reaper => RoleTypes.Impostor,
CustomRoles.Death => RoleTypes.Impostor,
CustomRoles.VoodooMaster => RoleTypes.Impostor,
CustomRoles.Goddess => RoleTypes.Phantom,
CustomRoles.Dreamweaver => RoleTypes.Impostor,
CustomRoles.Banshee => RoleTypes.Phantom,
CustomRoles.Illusionist => RoleTypes.Shapeshifter,
CustomRoles.Timelord => RoleTypes.Impostor,
CustomRoles.Enchanter => RoleTypes.Impostor,
_ => RoleTypes.GuardianAngel
};
}
@@ -588,7 +604,7 @@ internal static class CustomRolesHelper
public static bool IsSnitchTarget(this CustomRoles role)
{
return role.IsNK() || role.Is(Team.Impostor);
return role.IsNK() || role.Is(Team.Impostor) || role.IsCoven();
}
public static bool IsGhostRole(this CustomRoles role)
@@ -896,6 +912,7 @@ internal static class CustomRolesHelper
public static bool ForceCancelShapeshift(this CustomRoles role)
{
return role is
CustomRoles.Illusionist or
CustomRoles.Swapster or
CustomRoles.Echo or
CustomRoles.Hangman or
@@ -1296,7 +1313,9 @@ internal static class CustomRolesHelper
? type
: role.Is(Team.Impostor) || role == CustomRoles.Trickster
? CountTypes.Impostor
: CountTypes.Crew
: role.IsCoven()
? CountTypes.Coven
: CountTypes.Crew
};
}
@@ -1781,5 +1800,7 @@ public enum CountTypes
Glitch,
Arsonist,
Cherokious,
Sheriff
Sheriff,
Coven
}
+9 -21
View File
@@ -493,7 +493,7 @@ internal static class EAC
internal static bool CheckInvalidSabotage(SystemTypes systemType, PlayerControl player, byte amount)
{
if (player.IsHost() || !AmongUsClient.Instance.AmHost) return false;
if (player.IsModClient() || !AmongUsClient.Instance.AmHost) return false;
if ((GameStates.IsMeeting && MeetingHud.Instance.state != MeetingHud.VoteStates.Animating) || ExileController.Instance)
{
@@ -504,22 +504,19 @@ internal static class EAC
return true;
}
byte Mapid = Main.NormalOptions.MapId;
byte mapid = Main.NormalOptions.MapId;
switch (systemType)
{
case SystemTypes.LifeSupp:
if (Mapid != 0 && Mapid != 1 && Mapid != 3) goto Cheat;
if (mapid != 0 && mapid != 1 && mapid != 3) goto Cheat;
if (amount != 64 && amount != 65) goto Cheat;
break;
case SystemTypes.Comms:
switch (amount)
{
case 0:
if (Mapid is 1 or 5) goto Cheat;
if (mapid is 1 or 5) goto Cheat;
break;
case 64:
case 65:
@@ -527,8 +524,7 @@ internal static class EAC
case 33:
case 16:
case 17:
if (Mapid is not (1 or 5)) goto Cheat;
if (mapid is not (1 or 5)) goto Cheat;
break;
default:
goto Cheat;
@@ -536,28 +532,20 @@ internal static class EAC
break;
case SystemTypes.Electrical:
if (Mapid == 5) goto Cheat;
if (mapid == 5) goto Cheat;
if (amount >= 5) goto Cheat;
break;
case SystemTypes.Laboratory:
if (Mapid != 2) goto Cheat;
if (mapid != 2) goto Cheat;
if (amount is not (64 or 65 or 32 or 33)) goto Cheat;
break;
case SystemTypes.Reactor:
if (Mapid is 2 or 4) goto Cheat;
if (mapid is 2 or 4) goto Cheat;
if (amount is not (64 or 65 or 32 or 33)) goto Cheat;
break;
case SystemTypes.HeliSabotage:
if (Mapid != 4) goto Cheat;
if (mapid != 4) goto Cheat;
if (amount is not (64 or 65 or 16 or 17 or 32 or 33)) goto Cheat;
break;
case SystemTypes.MushroomMixupSabotage:
goto Cheat;
+2 -1
View File
@@ -7,6 +7,7 @@ using System.Reflection;
using AmongUs.GameOptions;
using EHR.AddOns.Crewmate;
using EHR.AddOns.Impostor;
using EHR.Coven;
using EHR.Crewmate;
using EHR.Impostor;
using EHR.Modules;
@@ -1660,7 +1661,7 @@ internal static class ExtendedPlayerControl
public static bool IsSnitchTarget(this PlayerControl player)
{
return player.Is(CustomRoles.Bloodlust) || Framer.FramedPlayers.Contains(player.PlayerId) || player.GetCustomRole().IsSnitchTarget();
return player.Is(CustomRoles.Bloodlust) || Framer.FramedPlayers.Contains(player.PlayerId) || Enchanter.EnchantedPlayers.Contains(player.PlayerId) || player.GetCustomRole().IsSnitchTarget();
}
public static bool IsMadmate(this PlayerControl player)
+6 -2
View File
@@ -5,6 +5,7 @@ using AmongUs.GameOptions;
using EHR.AddOns.Common;
using EHR.AddOns.Crewmate;
using EHR.AddOns.GhostRoles;
using EHR.Coven;
using EHR.Crewmate;
using EHR.Modules;
using EHR.Neutral;
@@ -331,6 +332,9 @@ public class PlayerState(byte playerId)
if (AmongUsClient.Instance.AmHost)
{
if (Enchanter.EnchantedPlayers.Contains(PlayerId))
deathReason = Enum.GetValues<DeathReason>().RandomElement();
RPC.SendDeathReason(PlayerId, deathReason);
Utils.CheckAndSpawnAdditionalRefugee(Utils.GetPlayerInfoById(PlayerId));
@@ -354,9 +358,9 @@ public class PlayerState(byte playerId)
return IsDead && RealKiller.TimeStamp != DateTime.MinValue ? RealKiller.ID : byte.MaxValue;
}
public int GetKillCount(bool ExcludeSelfKill = false)
public int GetKillCount(bool excludeSelfKill = false)
{
return Main.PlayerStates.Values.Where(state => !(ExcludeSelfKill && state.PlayerId == PlayerId) && state.GetRealKiller() == PlayerId).ToArray().Length;
return Main.PlayerStates.Values.Count(state => !(excludeSelfKill && state.PlayerId == PlayerId) && state.GetRealKiller() == PlayerId);
}
}
+2 -1
View File
@@ -141,7 +141,8 @@ public static class GuessManager
(pc.IsNeutralKiller() && !Options.NeutralKillersCanGuess.GetBool() && !pc.Is(CustomRoles.Guesser)) ||
(pc.GetCustomRole().IsNonNK() && !Options.PassiveNeutralsCanGuess.GetBool() && !pc.Is(CustomRoles.Guesser) && pc.GetCustomRole() is not CustomRoles.Doomsayer and not CustomRoles.NecroGuesser) ||
(pc.Is(CustomRoles.Lyncher) && Lyncher.GuessMode.GetValue() == 0) ||
(Options.GuesserNumRestrictions.GetBool() && !Guessers.Contains(pc.PlayerId)))
(Options.GuesserNumRestrictions.GetBool() && !Guessers.Contains(pc.PlayerId)) ||
(pc.Is(Team.Coven) && !pc.Is(CustomRoles.Augur)))
{
if ((pc.Is(CustomRoles.Madmate) || pc.IsConverted()) && Options.BetrayalAddonsCanGuess.GetBool()) goto SkipCheck;
+3 -2
View File
@@ -1,5 +1,6 @@
using System;
using AmongUs.GameOptions;
using EHR.Coven;
using EHR.Crewmate;
using EHR.Impostor;
using HarmonyLib;
@@ -57,6 +58,8 @@ public static class MeetingTimeManager
BonusMeetingTime += TimeThief.TotalDecreasedMeetingTime();
}
if (Timelord.On) { BonusMeetingTime += -Timelord.GetTotalStolenTime(); }
if (TimeManager.PlayerIdList.Count > 0)
{
MeetingTimeMinTimeManager = TimeManager.MadMinMeetingTimeLimit.GetInt();
@@ -67,9 +70,7 @@ public static class MeetingTimeManager
int TotalMeetingTime = DiscussionTime + VotingTime;
if (TimeManager.PlayerIdList.Count > 0) BonusMeetingTime = Math.Clamp(TotalMeetingTime + BonusMeetingTime, MeetingTimeMinTimeManager, MeetingTimeMax) - TotalMeetingTime;
if (TimeThief.PlayerIdList.Count > 0) BonusMeetingTime = Math.Clamp(TotalMeetingTime + BonusMeetingTime, MeetingTimeMinTimeThief, MeetingTimeMax) - TotalMeetingTime;
if (TimeManager.PlayerIdList.Count == 0 && TimeThief.PlayerIdList.Count == 0) BonusMeetingTime = Math.Clamp(TotalMeetingTime + BonusMeetingTime, MeetingTimeMinTimeThief, MeetingTimeMax) - TotalMeetingTime;
if (BonusMeetingTime >= 0)
+6
View File
@@ -1,6 +1,7 @@
using System.Linq;
using EHR.AddOns.Common;
using EHR.AddOns.GhostRoles;
using EHR.Coven;
using EHR.Crewmate;
using EHR.Impostor;
using EHR.Modules;
@@ -157,6 +158,11 @@ public static class NameColorManager
CustomRoles.Wasp when seerRoleClass is Wasp wasp && (wasp.DelayedKills.ContainsKey(target.PlayerId) || wasp.MeetingKills.Contains(target.PlayerId)) => "000000",
CustomRoles.God when God.KnowInfo.GetValue() == 1 => target.GetTeam().GetTextColor(),
CustomRoles.Curser when ((Curser)seerRoleClass).KnownFactionPlayers.Contains(target.PlayerId) => target.GetTeam().GetTextColor(),
CustomRoles.Poache when Poache.PoachedPlayers.Contains(target.PlayerId) => "000000",
CustomRoles.Reaper when ((Reaper)seerRoleClass).CursedPlayers.Contains(target.PlayerId) => "000000",
CustomRoles.Dreamweaver when ((Dreamweaver)seerRoleClass).InsanePlayers.Contains(target.PlayerId) || target.Is(CustomRoles.Insane) => "000000",
CustomRoles.Banshee when ((Banshee)seerRoleClass).ScreechedPlayers.Contains(target.PlayerId) => "000000",
CustomRoles.Illusionist when ((Illusionist)seerRoleClass).SampledPlayerId == target.PlayerId => "000000",
_ => color
};
+8
View File
@@ -14,6 +14,7 @@ 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;
using EHR.Modules;
@@ -2116,6 +2117,7 @@ public static class Utils
{
SelfMark.Append(Witch.GetSpelledMark(seer.PlayerId, isForMeeting));
if (isForMeeting) SelfMark.Append(Wasp.GetStungMark(seer.PlayerId));
if (isForMeeting) SelfMark.Append(SpellCaster.IsSpelled(seer.PlayerId) ? ColorString(Team.Coven.GetColor(), "\u25c0") : string.Empty);
}
GameMode:
@@ -2276,6 +2278,7 @@ public static class Utils
TargetMark.Append(Witch.GetSpelledMark(target.PlayerId, isForMeeting));
if (isForMeeting) TargetMark.Append(Wasp.GetStungMark(target.PlayerId));
if (isForMeeting) TargetMark.Append(SpellCaster.IsSpelled(seer.PlayerId) ? ColorString(Team.Coven.GetColor(), "\u25c0") : string.Empty);
if (target.Is(CustomRoles.SuperStar) && Options.EveryOneKnowSuperStar.GetBool())
TargetMark.Append(ColorString(GetRoleColor(CustomRoles.SuperStar), "★"));
@@ -2895,6 +2898,10 @@ public static class Utils
case CustomRoles.Medic:
Medic.IsDead(target);
break;
case CustomRoles.Dreamweaver:
((Dreamweaver)Main.PlayerStates[target.PlayerId].Role).InsanePlayers.Clear();
Main.PlayerStates.Values.Do(x => x.RemoveSubRole(CustomRoles.Insane));
break;
}
if (target == null) return;
@@ -2930,6 +2937,7 @@ public static class Utils
Adventurer.OnAnyoneDead(target);
Whisperer.OnAnyoneDied(target);
Reaper.OnAnyoneDead(target);
if (QuizMaster.On) QuizMaster.Data.NumPlayersDeadThisRound++;
+23 -2
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using AmongUs.Data;
using EHR.Coven;
using EHR.Impostor;
using EHR.Patches;
using HarmonyLib;
@@ -203,6 +204,9 @@ public static class ChatManager
if (CustomGameMode.FFA.IsActiveOrIntegrated() && GameStates.InGame && !message.StartsWith('/'))
FFAManager.UpdateLastChatMessage(player.GetRealName(), message);
if (CustomGameMode.Standard.IsActiveOrIntegrated() && GameStates.InGame && operate != 1 && Banshee.On)
Banshee.OnReceiveChat();
}
public static void SendPreviousMessagesToAll(bool clear = false)
@@ -252,10 +256,10 @@ public static class ChatManager
finally { ChatUpdatePatch.DoBlockChat = false; }
}
private static void SendRPC(InnerNetObject senderPlayer, string senderMessage)
private static void SendRPC(InnerNetObject senderPlayer, string senderMessage, int targetClientId = -1)
{
var writer = CustomRpcSender.Create("MessagesToSend");
writer.StartMessage();
writer.StartMessage(targetClientId);
writer.StartRpc(senderPlayer.NetId, (byte)RpcCalls.SendChat)
.Write(senderMessage)
@@ -264,4 +268,21 @@ public static class ChatManager
writer.EndMessage();
writer.SendMessage();
}
public static void ClearChatForSpecificPlayer(PlayerControl target)
{
var sender = Main.AllAlivePlayerControls.MinBy(x => x.PlayerId) ?? Main.AllPlayerControls.MinBy(x => x.PlayerId) ?? PlayerControl.LocalPlayer;
if (sender == null) return;
if (!target.IsLocalPlayer())
{
var clientId = target.GetClientId();
if (clientId == -1) return;
Loop.Times(20, _ => SendRPC(sender, Utils.EmptyMessage, clientId));
return;
}
Loop.Times(20, _ => DestroyableSingleton<HudManager>.Instance.Chat.AddChat(sender, Utils.EmptyMessage));
}
}
+39 -30
View File
@@ -505,15 +505,16 @@ internal static class GameEndChecker
int Imp = AlivePlayersCount(CountTypes.Impostor);
int Crew = AlivePlayersCount(CountTypes.Crew) + sheriffCount;
int Coven = AlivePlayersCount(CountTypes.Coven);
Dictionary<(CustomRoles? ROLE, CustomWinner WINNER), int> roleCounts = [];
Dictionary<(CustomRoles? Role, CustomWinner Winner), int> roleCounts = [];
foreach (CustomRoles role in Enum.GetValues<CustomRoles>())
{
if ((!role.IsNK() && role is not CustomRoles.Bloodlust and not CustomRoles.Gaslighter) || role.IsMadmate() || role is CustomRoles.Sidekick) continue;
CountTypes countTypes = role.GetCountTypes();
if (countTypes is CountTypes.Crew or CountTypes.Impostor or CountTypes.None or CountTypes.OutOfGame or CountTypes.CustomTeam) continue;
if (countTypes is CountTypes.Crew or CountTypes.Impostor or CountTypes.None or CountTypes.OutOfGame or CountTypes.CustomTeam or CountTypes.Coven) continue;
CustomRoles? keyRole = role.IsRecruitingRole() ? null : role;
var keyWinner = (CustomWinner)role;
@@ -528,18 +529,14 @@ internal static class GameEndChecker
{
if (!x.Is(CustomRoles.DualPersonality)) continue;
if (x.Is(Team.Impostor))
Imp++;
if (x.Is(Team.Impostor)) Imp++;
else if (x.Is(Team.Crewmate)) Crew++;
else if (x.Is(Team.Coven)) Coven++;
if (x.Is(CustomRoles.Charmed)) roleCounts[(null, CustomWinner.Succubus)]++;
if (x.Is(CustomRoles.Undead)) roleCounts[(null, CustomWinner.Necromancer)]++;
if (x.Is(CustomRoles.Sidekick)) roleCounts[(null, CustomWinner.Jackal)]++;
if (x.Is(CustomRoles.Recruit)) roleCounts[(null, CustomWinner.Jackal)]++;
if (x.Is(CustomRoles.Contagious)) roleCounts[(null, CustomWinner.Virus)]++;
}
}
@@ -551,26 +548,39 @@ internal static class GameEndChecker
if (totalNKAlive == 0)
{
if (Crew == 0 && Imp == 0)
if (Coven == 0)
{
reason = GameOverReason.ImpostorByKill;
winner = CustomWinner.None;
}
else if (Crew <= Imp)
{
reason = GameOverReason.ImpostorByKill;
winner = CustomWinner.Impostor;
}
else if (Imp == 0)
{
reason = GameOverReason.HumansByVote;
winner = CustomWinner.Crewmate;
if (Crew == 0 && Imp == 0)
{
reason = GameOverReason.ImpostorByKill;
winner = CustomWinner.None;
}
else if (Crew <= Imp)
{
reason = GameOverReason.ImpostorByKill;
winner = CustomWinner.Impostor;
}
else if (Imp == 0)
{
reason = GameOverReason.HumansByVote;
winner = CustomWinner.Crewmate;
}
else
return false;
Logger.Info($"Crew: {Crew}, Imp: {Imp}, Coven: {Coven}", "CheckGameEndPatch.CheckGameEndByLivingPlayers");
ResetAndSetWinner((CustomWinner)winner);
}
else
return false;
{
if (Imp >= 1) return false;
if (Crew > Coven) return false;
Logger.Info($"Crew: {Crew}, Imp: {Imp}, Coven: {Coven}", "CheckGameEndPatch.CheckGameEndByLivingPlayers");
reason = GameOverReason.ImpostorByKill;
ResetAndSetWinner(CustomWinner.Coven);
}
Logger.Info($"Crew: {Crew}, Imp: {Imp}", "CheckGameEndPatch.CheckGameEndByLivingPlayers");
ResetAndSetWinner((CustomWinner)winner);
return true;
}
@@ -583,10 +593,9 @@ internal static class GameEndChecker
switch (aliveCounts.Count)
{
// There are multiple types of NKs alive, game must continue
case > 1:
return false;
// There are multiple types of NKs alive, the game must continue
// If the Sheriff keeps the game going, the game must continue
case > 1:
case 1 when Sheriff.KeepsGameGoing.GetBool() && sheriffCount > 0:
return false;
// There is only one type of NK alive, they've won
@@ -594,11 +603,11 @@ internal static class GameEndChecker
{
if (aliveCounts[0] != roleCounts.Values.Max()) Logger.Warn("There is something wrong here.", "CheckGameEndPatch");
foreach (KeyValuePair<(CustomRoles? ROLE, CustomWinner WINNER), int> keyValuePair in roleCounts.Where(keyValuePair => keyValuePair.Value == aliveCounts[0]))
foreach (KeyValuePair<(CustomRoles? Role, CustomWinner Winner), int> keyValuePair in roleCounts.Where(keyValuePair => keyValuePair.Value == aliveCounts[0]))
{
reason = GameOverReason.ImpostorByKill;
winner = keyValuePair.Key.WINNER;
rl = keyValuePair.Key.ROLE;
winner = keyValuePair.Key.Winner;
rl = keyValuePair.Key.Role;
break;
}
+6
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
using EHR.AddOns.Common;
using EHR.Coven;
using EHR.Crewmate;
using EHR.Impostor;
using EHR.Modules;
@@ -169,6 +170,7 @@ internal static class CheckForEndVotingPatch
bool canVote = !(CheckRole(ps.TargetPlayerId, CustomRoles.Glitch) && !Glitch.CanVote.GetBool());
if (CheckRole(ps.TargetPlayerId, CustomRoles.Shifter) && !Shifter.CanVote.GetBool()) canVote = false;
if (ps.VotedFor.GetPlayer() != null && CheckRole(ps.VotedFor, CustomRoles.Zombie)) canVote = false;
if (Poache.PoachedPlayers.Contains(ps.TargetPlayerId)) canVote = false;
switch (Main.PlayerStates[ps.TargetPlayerId].Role)
{
@@ -563,6 +565,7 @@ internal static class CheckForEndVotingPatch
if (Virus.PlayerIdList.Count > 0) Virus.OnCheckForEndVoting(deathReason, playerIds);
if (deathReason == PlayerState.DeathReason.Vote) Gaslighter.OnExile(playerIds);
if (Wasp.On && deathReason == PlayerState.DeathReason.Vote) Wasp.OnExile(playerIds);
if (CustomRoles.SpellCaster.RoleExist()) SpellCaster.OnExile(playerIds);
foreach (byte playerId in playerIds)
{
@@ -638,6 +641,8 @@ internal static class ExtendedMeetingHud
Collector.CollectorVotes(target, ps);
}
if (Poache.PoachedPlayers.Contains(ps.TargetPlayerId)) VoteNum = 0;
if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Mayor)) VoteNum += Mayor.MayorAdditionalVote.GetInt();
if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Knighted)) VoteNum += 1;
if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Glitch) && !Glitch.CanVote.GetBool()) VoteNum = 0;
@@ -1024,6 +1029,7 @@ internal static class MeetingHudStartPatch
sb.Append(Witch.GetSpelledMark(target.PlayerId, true));
sb.Append(Wasp.GetStungMark(target.PlayerId));
sb.Append(SpellCaster.IsSpelled(seer.PlayerId) ? Utils.ColorString(Team.Coven.GetColor(), "\u25c0") : string.Empty);
if (target.Is(CustomRoles.SuperStar) && Options.EveryOneKnowSuperStar.GetBool()) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.SuperStar), "★"));
+6 -5
View File
@@ -6,6 +6,7 @@ using EHR.AddOns.Common;
using EHR.AddOns.Crewmate;
using EHR.AddOns.GhostRoles;
using EHR.AddOns.Impostor;
using EHR.Coven;
using EHR.Crewmate;
using EHR.GameMode.HideAndSeekRoles;
using EHR.Impostor;
@@ -396,6 +397,7 @@ internal static class CheckMurderPatch
Randomizer.IsShielded(target) ||
Aid.ShieldedPlayers.ContainsKey(target.PlayerId) ||
Gaslighter.IsShielded(target) ||
!PotionMaster.OnAnyoneCheckMurder(target) ||
!Grappler.OnAnyoneCheckMurder(target) ||
!Adventurer.OnAnyoneCheckMurder(target) ||
!Sentinel.OnAnyoneCheckMurder(killer) ||
@@ -968,6 +970,9 @@ internal static class ReportDeadBodyPatch
return false;
if (!Librarian.OnAnyoneReport(__instance)) return false;
if (!Occultist.OnAnyoneReportDeadBody(target)) return false;
if (!Altruist.OnAnyoneCheckReportDeadBody(__instance, target)) return false;
if (!BoobyTrap.OnAnyoneCheckReportDeadBody(__instance, target)) return false;
if (!Hypnotist.OnAnyoneReport())
{
@@ -977,11 +982,7 @@ internal static class ReportDeadBodyPatch
return false;
}
if (!Occultist.OnAnyoneReportDeadBody(target)) return false;
if (!Altruist.OnAnyoneCheckReportDeadBody(__instance, target)) return false;
if (!BoobyTrap.OnAnyoneCheckReportDeadBody(__instance, target)) return false;
if (killer != null && killer.Is(CustomRoles.Goddess)) return false;
if (!Main.PlayerStates[__instance.PlayerId].Role.CheckReportDeadBody(__instance, target, killer)) return false;
+25
View File
@@ -0,0 +1,25 @@
namespace EHR.Coven;
public class Augur : Coven
{
public static bool On;
public override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Never;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(650070);
}
public override void Init()
{
On = false;
}
public override void Add(byte playerId)
{
On = true;
}
}
+103
View File
@@ -0,0 +1,103 @@
using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
using EHR.Modules;
using Hazel;
namespace EHR.Coven;
public class Banshee : Coven
{
public static bool On;
private static List<Banshee> Instances = [];
private static OptionItem AbilityCooldown;
private static OptionItem Radius;
private byte BansheeId;
public HashSet<byte> ScreechedPlayers = [];
public override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Random;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(650090)
.AutoSetupOption(ref AbilityCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref Radius, 3f, new FloatValueRule(0.25f, 10f, 0.25f), OptionFormat.Multiplier);
}
public override void Init()
{
On = false;
Instances = [];
}
public override void Add(byte playerId)
{
On = true;
ScreechedPlayers = [];
BansheeId = playerId;
Instances.Add(this);
}
public override bool OnVanish(PlayerControl pc)
{
var radius = Radius.GetFloat();
var pos = pc.Pos();
IEnumerable<PlayerControl> nearbyPlayers = Utils.GetPlayersInRadius(radius, pos).Without(pc);
if (!HasNecronomicon) ScreechedPlayers = nearbyPlayers.Select(x => x.PlayerId).ToHashSet();
else nearbyPlayers.Do(x => x.Suicide(realKiller: pc));
if (ScreechedPlayers.Count > 0)
{
var w = Utils.CreateRPC(CustomRPC.SyncRoleData);
w.Write(BansheeId);
w.WritePacked(1);
w.WritePacked(ScreechedPlayers.Count);
ScreechedPlayers.Do(x => w.Write(x));
Utils.NotifyRoles(SpecifySeer: pc);
}
return false;
}
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
{
AURoleOptions.PhantomCooldown = AbilityCooldown.GetFloat();
AURoleOptions.PhantomDuration = 1f;
}
public override void AfterMeetingTasks()
{
ScreechedPlayers.Clear();
Utils.SendRPC(CustomRPC.SyncRoleData, BansheeId, 2);
}
public void ReceiveRPC(MessageReader reader)
{
switch (reader.ReadPackedInt32())
{
case 1:
ScreechedPlayers.Clear();
int count = reader.ReadPackedInt32();
Loop.Times(count, _ => ScreechedPlayers.Add(reader.ReadByte()));
break;
case 2:
ScreechedPlayers.Clear();
break;
}
}
public static void OnReceiveChat()
{
var screechedPlayers = Instances.SelectMany(x => x.ScreechedPlayers).ToValidPlayers().ToHashSet();
if (screechedPlayers.Count == 0) return;
screechedPlayers.Do(ChatManager.ClearChatForSpecificPlayer);
}
}
+15 -1
View File
@@ -6,11 +6,19 @@ public class CovenLeader : Coven
{
public static bool On;
private static OptionItem KillCooldown;
private byte CovenLeaderId;
public override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.First;
public override bool IsEnable => On;
public override void SetupCustomOption() { }
public override void SetupCustomOption()
{
StartSetup(650000)
.AutoSetupOption(ref KillCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds);
}
public override void Init()
{
@@ -20,6 +28,7 @@ public class CovenLeader : Coven
public override void Add(byte playerId)
{
On = true;
CovenLeaderId = playerId;
}
public override bool CanUseKillButton(PlayerControl pc)
@@ -36,4 +45,9 @@ public class CovenLeader : Coven
{
Main.AllPlayerKillCooldown[id] = HasNecronomicon ? Options.DefaultKillCooldown / 2f : Options.DefaultKillCooldown;
}
public override void OnReceiveNecronomicon()
{
CovenLeaderId.GetPlayer().ResetKillCooldown();
}
}
+100
View File
@@ -0,0 +1,100 @@
using System.Collections.Generic;
using System.Linq;
using EHR.Modules;
using Hazel;
namespace EHR.Coven;
public class Dreamweaver : Coven
{
public static bool On;
private static OptionItem AbilityCooldown;
private byte DreamweaverId;
public HashSet<byte> InsanePlayers = [];
public override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Random;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(650080)
.AutoSetupOption(ref AbilityCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds);
}
public override void Init()
{
On = false;
}
public override void Add(byte playerId)
{
On = true;
InsanePlayers = [];
DreamweaverId = playerId;
}
public override bool CanUseKillButton(PlayerControl pc)
{
return pc.IsAlive();
}
public override void SetKillCooldown(byte id)
{
Main.AllPlayerKillCooldown[id] = AbilityCooldown.GetFloat();
}
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
InsanePlayers.Add(target.PlayerId);
Utils.SendRPC(CustomRPC.SyncRoleData, DreamweaverId, 1, target.PlayerId);
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
killer.SetKillCooldown(AbilityCooldown.GetFloat());
return false;
}
public override void AfterMeetingTasks()
{
InsanePlayers.ToValidPlayers().Do(x => x.RpcSetCustomRole(CustomRoles.Insane));
InsanePlayers.Clear();
Utils.SendRPC(CustomRPC.SyncRoleData, DreamweaverId, 2);
}
public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad)
{
if (lowLoad || GameStates.IsMeeting || ExileController.Instance || !Main.IntroDestroyed || !pc.IsAlive() || !pc.Is(CustomRoles.Insane) || Main.KillTimers[pc.PlayerId] > 0f) return;
var pos = pc.Pos();
var nearbyPlayers = Utils.GetPlayersInRadius(1.5f, pos).Without(pc).ToArray();
if (nearbyPlayers.Length == 0) return;
var nearestPlayer = nearbyPlayers.MinBy(x => Vector2.Distance(x.Pos(), pos));
RoleBase roleBase = Main.PlayerStates[pc.PlayerId].Role;
var type = roleBase.GetType();
if (type.GetMethod("OnCheckMurder")?.DeclaringType == type)
{
roleBase.OnCheckMurder(pc, nearestPlayer);
pc.SetKillCooldown();
}
if (HasNecronomicon) pc.Suicide(realKiller: DreamweaverId.GetPlayer());
}
public void ReceiveRPC(MessageReader reader)
{
switch (reader.ReadPackedInt32())
{
case 1:
InsanePlayers.Add(reader.ReadByte());
break;
case 2:
InsanePlayers.Clear();
break;
}
}
}
+55
View File
@@ -0,0 +1,55 @@
using System.Collections.Generic;
using EHR.Modules;
namespace EHR.Coven;
public class Enchanter : Coven
{
public static bool On;
private static OptionItem AbilityCooldown;
public static HashSet<byte> EnchantedPlayers = [];
public override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Random;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(650120)
.AutoSetupOption(ref AbilityCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds);
}
public override void Init()
{
On = false;
}
public override void Add(byte playerId)
{
On = true;
EnchantedPlayers = [];
}
public override bool CanUseKillButton(PlayerControl pc)
{
return pc.IsAlive();
}
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
if (EnchantedPlayers.Contains(target.PlayerId)) return HasNecronomicon;
if (HasNecronomicon) return killer.CheckDoubleTrigger(target, EnchantTarget);
EnchantTarget();
return false;
void EnchantTarget()
{
EnchantedPlayers.Add(target.PlayerId);
Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 1, target.PlayerId);
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
killer.SetKillCooldown(AbilityCooldown.GetFloat());
}
}
}
+109
View File
@@ -0,0 +1,109 @@
using AmongUs.GameOptions;
using EHR.Modules;
using Hazel;
namespace EHR.Coven;
public class Goddess : Coven
{
public static bool On;
private static OptionItem AbilityCooldown;
private static OptionItem AbilityDuration;
private long AbilityEndTS;
private byte GoddessId;
private long LastNotifyTS;
public override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Random;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(650060)
.AutoSetupOption(ref AbilityCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref AbilityDuration, 10, new IntegerValueRule(1, 60, 1), OptionFormat.Seconds);
}
public override void Init()
{
On = false;
}
public override void Add(byte playerId)
{
On = true;
AbilityEndTS = 0;
LastNotifyTS = 0;
GoddessId = playerId;
}
public override bool CanUseKillButton(PlayerControl pc)
{
return HasNecronomicon;
}
public override bool OnVanish(PlayerControl pc)
{
AbilityEndTS = Utils.TimeStamp + AbilityDuration.GetInt();
Utils.SendRPC(CustomRPC.SyncRoleData, GoddessId, AbilityEndTS);
return false;
}
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
{
AURoleOptions.PhantomCooldown = AbilityCooldown.GetFloat();
AURoleOptions.PhantomDuration = 1f;
}
public override void OnReportDeadBody()
{
AbilityEndTS = 0;
Utils.SendRPC(CustomRPC.SyncRoleData, GoddessId, AbilityEndTS);
}
public override void OnFixedUpdate(PlayerControl pc)
{
if (AbilityEndTS > 0)
{
long now = Utils.TimeStamp;
bool notify = now != LastNotifyTS;
if (now >= AbilityEndTS)
{
AbilityEndTS = 0;
Utils.SendRPC(CustomRPC.SyncRoleData, GoddessId, AbilityEndTS);
pc.RpcResetAbilityCooldown();
notify = true;
}
if (notify)
{
LastNotifyTS = now;
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
}
}
}
public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target)
{
if (AbilityEndTS == 0 || killer.Is(CustomRoles.Pestilence)) return true;
killer.SetRealKiller(target);
target.Kill(killer);
return false;
}
public void ReceiveRPC(MessageReader reader)
{
AbilityEndTS = long.Parse(reader.ReadString());
}
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (seer.PlayerId != GoddessId || seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud) || meeting || AbilityEndTS == 0) return string.Empty;
return string.Format(Translator.GetString("Goddess.Suffix"), AbilityEndTS - Utils.TimeStamp);
}
}
+94
View File
@@ -0,0 +1,94 @@
using AmongUs.GameOptions;
using EHR.Modules;
using Hazel;
namespace EHR.Coven;
public class Illusionist : Coven
{
public static bool On;
private static OptionItem AbilityCooldown;
private static OptionItem ShapeshiftAnimation;
public byte SampledPlayerId;
public override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Random;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(650100)
.AutoSetupOption(ref AbilityCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref ShapeshiftAnimation, false);
}
public override void Init()
{
On = false;
}
public override void Add(byte playerId)
{
On = true;
SampledPlayerId = byte.MaxValue;
}
public override bool CanUseKillButton(PlayerControl pc)
{
return pc.IsAlive();
}
public override void SetKillCooldown(byte id)
{
Main.AllPlayerKillCooldown[id] = AbilityCooldown.GetFloat();
}
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
if (HasNecronomicon && SampledPlayerId == target.PlayerId)
return true;
SampledPlayerId = target.PlayerId;
Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 1, target.PlayerId);
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
if (!HasNecronomicon) killer.SetKillCooldown(AbilityCooldown.GetFloat());
return false;
}
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
{
AURoleOptions.ShapeshifterCooldown = 1f;
AURoleOptions.ShapeshifterDuration = 1f;
}
public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting)
{
var sampledPlayer = SampledPlayerId.GetPlayer();
if (sampledPlayer == null) return false;
target.RpcShapeshift(sampledPlayer, ShapeshiftAnimation.GetBool());
if (HasNecronomicon) shapeshifter.SetKillCooldown(AbilityCooldown.GetFloat());
SampledPlayerId = byte.MaxValue;
Utils.SendRPC(CustomRPC.SyncRoleData, shapeshifter.PlayerId, 2);
return false;
}
public override void AfterMeetingTasks()
{
SampledPlayerId = byte.MaxValue;
Utils.SendRPC(CustomRPC.SyncRoleData, byte.MaxValue, 2);
}
public void ReceiveRPC(MessageReader reader)
{
SampledPlayerId = reader.ReadPackedInt32() switch
{
1 => reader.ReadByte(),
2 => byte.MaxValue,
_ => SampledPlayerId
};
}
}
+93
View File
@@ -0,0 +1,93 @@
using System.Collections.Generic;
using EHR.Modules;
using Hazel;
namespace EHR.Coven;
public class Poache : Coven
{
public static bool On;
private static OptionItem AbilityCooldown;
private static OptionItem KillDelay;
public static HashSet<byte> PoachedPlayers = [];
private static List<(byte ID, long KillTimeStamp)> KillDelays = [];
private byte PoacheId;
public override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Random;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(650030)
.AutoSetupOption(ref AbilityCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref KillDelay, 3, new IntegerValueRule(1, 30, 1), OptionFormat.Seconds);
}
public override void Init()
{
On = false;
PoachedPlayers = [];
KillDelays = [];
}
public override void Add(byte playerId)
{
On = true;
PoacheId = playerId;
}
public override bool CanUseKillButton(PlayerControl pc)
{
return pc.IsAlive();
}
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
if (!HasNecronomicon)
{
PoachedPlayers.Add(target.PlayerId);
Utils.SendRPC(CustomRPC.SyncRoleData, PoacheId, 1, target.PlayerId);
}
else KillDelays.Add((killer.PlayerId, Utils.TimeStamp + KillDelay.GetInt()));
killer.SetKillCooldown(AbilityCooldown.GetFloat());
return false;
}
public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad)
{
if (lowLoad || GameStates.IsMeeting || ExileController.Instance || !Main.IntroDestroyed || !pc.IsAlive() || !KillDelays.FindFirst(x => x.ID == pc.PlayerId, out var killData) || Utils.TimeStamp < killData.KillTimeStamp) return;
pc.Suicide(realKiller: PoacheId.GetPlayer());
KillDelays.Remove(killData);
}
public override void AfterMeetingTasks()
{
PoachedPlayers.Clear();
Utils.SendRPC(CustomRPC.SyncRoleData, PoacheId, 2);
}
public override void OnReportDeadBody()
{
KillDelays.ForEach(x => x.ID.GetPlayer()?.Suicide(realKiller: PoacheId.GetPlayer()));
KillDelays.Clear();
}
public void ReceiveRPC(MessageReader reader)
{
switch (reader.ReadPackedInt32())
{
case 1:
PoachedPlayers.Add(reader.ReadByte());
break;
case 2:
PoachedPlayers.Clear();
break;
}
}
}
+138
View File
@@ -0,0 +1,138 @@
using System.Collections.Generic;
using System.Linq;
using EHR.Modules;
using Hazel;
namespace EHR.Coven;
public class PotionMaster : Coven
{
public static bool On;
private static List<PotionMaster> Instances = [];
private static OptionItem AbilityCooldown;
private static OptionItem KillCooldown;
private static OptionItem ShieldDuration;
private byte PotionMasterId;
private HashSet<byte> RevealedPlayers = [];
private Dictionary<byte, long> ShieldedPlayers = [];
public override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Random;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(650020)
.AutoSetupOption(ref AbilityCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref KillCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref ShieldDuration, 20, new IntegerValueRule(0, 600, 1), OptionFormat.Seconds);
}
public override void Init()
{
On = false;
Instances = [];
}
public override void Add(byte playerId)
{
On = true;
ShieldedPlayers = [];
RevealedPlayers = [];
PotionMasterId = playerId;
Instances.Add(this);
}
public override bool CanUseKillButton(PlayerControl pc)
{
return pc.IsAlive();
}
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
if (target.Is(Team.Coven))
{
long shieldEndTS = Utils.TimeStamp + ShieldDuration.GetInt();
ShieldedPlayers[target.PlayerId] = shieldEndTS;
Utils.SendRPC(CustomRPC.SyncRoleData, PotionMasterId, 1, target.PlayerId, shieldEndTS);
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target);
killer.SetKillCooldown(AbilityCooldown.GetFloat());
return false;
}
if (HasNecronomicon) return true;
RevealedPlayers.Add(target.PlayerId);
Main.AllAlivePlayerControls.DoIf(x => x.Is(Team.Coven), x => Utils.NotifyRoles(SpecifySeer: x, SpecifyTarget: target));
killer.SetKillCooldown(AbilityCooldown.GetFloat());
return false;
}
public override void OnReceiveNecronomicon()
{
PotionMasterId.GetPlayer().ResetKillCooldown();
}
public override void SetKillCooldown(byte id)
{
Main.AllPlayerKillCooldown[id] = HasNecronomicon ? KillCooldown.GetFloat() : AbilityCooldown.GetFloat();
}
public override void OnFixedUpdate(PlayerControl pc)
{
if (GameStates.IsMeeting || ExileController.Instance || !Main.IntroDestroyed || ShieldedPlayers.Count == 0) return;
long now = Utils.TimeStamp;
List<byte> toRemove = ShieldedPlayers.Where(x => now >= x.Value).Select(x => x.Key).ToList();
toRemove.ForEach(x =>
{
ShieldedPlayers.Remove(x);
var player = x.GetPlayer();
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: player);
Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
Utils.SendRPC(CustomRPC.SyncRoleData, PotionMasterId, 2, x);
});
foreach (byte shieldedId in ShieldedPlayers.Keys)
{
var player = shieldedId.GetPlayer();
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: player);
Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
}
}
public override bool KnowRole(PlayerControl seer, PlayerControl target)
{
if (base.KnowRole(seer, target)) return true;
return seer.Is(Team.Coven) && RevealedPlayers.Contains(target.PlayerId);
}
public static bool OnAnyoneCheckMurder(PlayerControl target) => !Instances.Any(x => x.ShieldedPlayers.ContainsKey(target.PlayerId));
public void ReceiveRPC(MessageReader reader)
{
switch (reader.ReadPackedInt32())
{
case 1:
ShieldedPlayers[reader.ReadByte()] = long.Parse(reader.ReadString());
break;
case 2:
ShieldedPlayers.Remove(reader.ReadByte());
break;
}
}
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (seer.PlayerId == target.PlayerId && seer.IsModClient() && !hud) return string.Empty;
if (seer.PlayerId != target.PlayerId && seer.PlayerId != PotionMasterId) return string.Empty;
if (!ShieldedPlayers.TryGetValue(target.PlayerId, out var shieldExpireTS)) return string.Empty;
return string.Format(Translator.GetString("PotionMaster.Suffix"), shieldExpireTS - Utils.TimeStamp);
}
}
+125
View File
@@ -0,0 +1,125 @@
using System.Collections.Generic;
using EHR.Modules;
using Hazel;
namespace EHR.Coven;
public class Reaper : Coven
{
public static bool On;
private static List<Reaper> Instances = [];
private static OptionItem AbilityCooldown;
private static OptionItem SoulsRequired;
public static OptionItem KillCooldown;
public HashSet<byte> CursedPlayers = [];
private byte ReaperId;
private int Souls;
public override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Never;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(650040)
.AutoSetupOption(ref AbilityCooldown, 10f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref SoulsRequired, 3, new IntegerValueRule(1, 10, 1))
.AutoSetupOption(ref KillCooldown, 15f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds);
}
public override void Init()
{
On = false;
Instances = [];
}
public override void Add(byte playerId)
{
On = true;
Instances.Add(this);
ReaperId = playerId;
CursedPlayers = [];
Souls = 0;
}
public override bool CanUseKillButton(PlayerControl pc)
{
return pc.IsAlive();
}
public override void SetKillCooldown(byte id)
{
Main.AllPlayerKillCooldown[id] = AbilityCooldown.GetFloat();
}
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
CursedPlayers.Add(target.PlayerId);
Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, target.PlayerId);
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
killer.SetKillCooldown(AbilityCooldown.GetFloat());
return false;
}
public void ReceiveRPC(MessageReader reader)
{
CursedPlayers.Add(reader.ReadByte());
}
public static void OnAnyoneDead(PlayerControl target)
{
Instances.DoIf(x => x.CursedPlayers.Contains(target.PlayerId) && ++x.Souls >= SoulsRequired.GetInt(), x =>
{
PlayerControl reaperPc = x.ReaperId.GetPlayer();
if (reaperPc == null) return;
reaperPc.RpcSetCustomRole(CustomRoles.Death);
reaperPc.SetKillCooldown(KillCooldown.GetFloat());
reaperPc.ResetKillCooldown();
Utils.NotifyRoles(SpecifySeer: reaperPc);
Utils.NotifyRoles(SpecifyTarget: reaperPc);
});
Instances.RemoveAll(x => x.Souls >= SoulsRequired.GetInt());
}
}
public class Death : Coven
{
public static bool On;
public override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Never;
public override bool IsEnable => On;
public override void SetupCustomOption() { }
public override void Init()
{
On = false;
}
public override void Add(byte playerId)
{
On = true;
}
public override bool CanUseKillButton(PlayerControl pc)
{
return pc.IsAlive();
}
public override void SetKillCooldown(byte id)
{
Main.AllPlayerKillCooldown[id] = Reaper.KillCooldown.GetFloat();
}
public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target)
{
return false;
}
}
+100
View File
@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using EHR.Patches;
namespace EHR.Coven;
public class SpellCaster : Coven
{
public static bool On;
private static OptionItem AbilityCooldown;
private static Dictionary<byte, bool> HexedPlayers = [];
private static HashSet<byte> PlayerIdList = [];
public override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Random;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(650010)
.AutoSetupOption(ref AbilityCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds);
}
public override void Init()
{
On = false;
PlayerIdList = [];
}
public override void Add(byte playerId)
{
On = true;
PlayerIdList.Add(playerId);
HexedPlayers = [];
}
public override bool CanUseKillButton(PlayerControl pc)
{
return pc.IsAlive();
}
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
HexedPlayers[target.PlayerId] = HasNecronomicon;
killer.SetKillCooldown(AbilityCooldown.GetFloat());
return false;
}
public override void OnReceiveNecronomicon()
{
HexedPlayers.SetAllValues(true);
}
public override void SetKillCooldown(byte id)
{
Main.AllPlayerKillCooldown[id] = AbilityCooldown.GetFloat();
}
public static void OnExile(byte[] exileIds)
{
try
{
if (exileIds.Any(PlayerIdList.Contains))
HexedPlayers.Clear();
List<byte> curseDeathList = [];
PlayerControl spellCaster = Main.AllAlivePlayerControls.First(x => x.Is(CustomRoles.SpellCaster));
foreach (PlayerControl pc in Main.AllAlivePlayerControls)
{
if (Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) continue;
if (IsSpelled(pc.PlayerId))
{
pc.SetRealKiller(spellCaster);
curseDeathList.Add(pc.PlayerId);
}
}
CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Spell, [.. curseDeathList]);
}
catch (Exception e) { Utils.ThrowException(e); }
}
public static bool IsSpelled(byte playerId)
{
return HexedPlayers.TryGetValue(playerId, out bool spell) && spell;
}
public override void OnFixedUpdate(PlayerControl pc)
{
if (pc.IsAlive() && Main.AllAlivePlayerControls.All(x => x.Is(Team.Coven) || HexedPlayers.ContainsKey(x.PlayerId)))
{
CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Coven);
CustomWinnerHolder.WinnerIds.UnionWith(Main.AllAlivePlayerControls.Where(x => x.Is(Team.Coven)).Select(x => x.PlayerId));
}
}
}
+50
View File
@@ -0,0 +1,50 @@
using System.Linq;
namespace EHR.Coven;
public class Timelord : Coven
{
public static bool On;
private static OptionItem KillCooldown;
private static OptionItem TimeStolenWithEachKill;
public override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Random;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(650110)
.AutoSetupOption(ref KillCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref TimeStolenWithEachKill, 10, new IntegerValueRule(0, 60, 1), OptionFormat.Seconds);
}
public override void Init()
{
On = false;
}
public override void Add(byte playerId)
{
On = true;
}
public override bool CanUseKillButton(PlayerControl pc)
{
return pc.IsAlive();
}
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
if (target.Is(Team.Coven)) target.SetKillCooldown(0.01f);
else target.SetKillCooldown();
return HasNecronomicon;
}
public static int GetTotalStolenTime()
{
return Main.PlayerStates.Values.Where(x => x.MainRole == CustomRoles.Timelord).Sum(x => x.GetKillCount()) * TimeStolenWithEachKill.GetInt();
}
}
+50
View File
@@ -0,0 +1,50 @@
namespace EHR.Coven;
public class VoodooMaster : Coven
{
public static bool On;
private static OptionItem AbilityCooldown;
public override NecronomiconReceivePriorities NecronomiconReceivePriority => NecronomiconReceivePriorities.Random;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(650050)
.AutoSetupOption(ref AbilityCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds);
}
public override void Init()
{
On = false;
}
public override void Add(byte playerId)
{
On = true;
}
public override bool CanUseKillButton(PlayerControl pc)
{
return pc.IsAlive();
}
public override void SetKillCooldown(byte id)
{
Main.AllPlayerKillCooldown[id] = AbilityCooldown.GetFloat();
}
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
RoleBase roleBase = Main.PlayerStates[target.PlayerId].Role;
var type = roleBase.GetType();
if (type.GetMethod("OnCheckMurder")?.DeclaringType == type) roleBase.OnCheckMurder(target, target);
else if (type.GetMethod("OnPet")?.DeclaringType == type) roleBase.OnPet(target);
if (!HasNecronomicon) killer.SetKillCooldown(AbilityCooldown.GetFloat());
return HasNecronomicon;
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using EHR.Coven;
using EHR.Impostor;
using EHR.Modules;
using Hazel;
@@ -107,7 +108,7 @@ public class Psychic : RoleBase
if (!IsEnable || !AmongUsClient.Instance.AmHost) return;
List<PlayerControl> BadListPc = Main.AllAlivePlayerControls.Where(x =>
(x.Is(CustomRoleTypes.Impostor) && !x.Is(CustomRoles.Trickster)) || x.Is(CustomRoles.Madmate) || x.Is(CustomRoles.Rascal) || Framer.FramedPlayers.Contains(x.PlayerId) || x.IsConverted() ||
(x.Is(CustomRoleTypes.Impostor) && !x.Is(CustomRoles.Trickster)) || x.Is(CustomRoles.Madmate) || x.Is(CustomRoles.Rascal) || Framer.FramedPlayers.Contains(x.PlayerId) || Enchanter.EnchantedPlayers.Contains(x.PlayerId) || x.IsConverted() ||
(x.GetCustomRole().GetCrewmateRoleCategory() == RoleOptionType.Crewmate_Killing && CkshowEvil.GetBool()) ||
(x.GetCustomRole().GetNeutralRoleCategory() == RoleOptionType.Neutral_Evil && NEshowEvil.GetBool()) ||
(x.GetCustomRole().GetNeutralRoleCategory() == RoleOptionType.Neutral_Benign && NBshowEvil.GetBool())