diff --git a/GameMode/CTFManager.cs b/GameMode/CTFManager.cs index 8486f71c7..f7997fa8c 100644 --- a/GameMode/CTFManager.cs +++ b/GameMode/CTFManager.cs @@ -358,21 +358,28 @@ namespace EHR public void Update() { - if (FlagCarrier == byte.MaxValue) return; - var flagCarrierPc = FlagCarrier.GetPlayer(); - if (flagCarrierPc == null || !flagCarrierPc.IsAlive()) + try { - DropFlag(); - return; + if (FlagCarrier == byte.MaxValue) return; + var flagCarrierPc = FlagCarrier.GetPlayer(); + if (flagCarrierPc == null || !flagCarrierPc.IsAlive()) + { + DropFlag(); + return; + } + + Flag.TP(flagCarrierPc.Pos()); + PlayerData[FlagCarrier].FlagTime += Time.fixedDeltaTime; + Utils.NotifyRoles(SpecifySeer: flagCarrierPc, SpecifyTarget: flagCarrierPc); + + CTFTeam enemy = team.GetOppositeTeam(); + if (Translator.GetString(Flag.playerControl.GetPlainShipRoom().RoomId.ToString()) == enemy.GetFlagBase().RoomName) + TeamData[enemy].SetAsWinner(); + } + catch + { + } - - Flag.TP(flagCarrierPc.Pos()); - PlayerData[FlagCarrier].FlagTime += Time.fixedDeltaTime; - Utils.NotifyRoles(SpecifySeer: flagCarrierPc, SpecifyTarget: flagCarrierPc); - - CTFTeam enemy = team.GetOppositeTeam(); - if (Translator.GetString(Flag.playerControl.GetPlainShipRoom().RoomId.ToString()) == enemy.GetFlagBase().RoomName) - TeamData[enemy].SetAsWinner(); } public void PickUpFlag(byte id) diff --git a/Modules/CustomRoleSelector.cs b/Modules/CustomRoleSelector.cs index d19158d3f..76d3509ca 100644 --- a/Modules/CustomRoleSelector.cs +++ b/Modules/CustomRoleSelector.cs @@ -201,7 +201,7 @@ internal static class CustomRoleSelector } // Pre-Assigned Roles By Host Are Selected First - foreach ((byte id, CustomRoles role) in Main.SetRoles) + foreach ((byte id, CustomRoles role) in Main.SetRoles.AddRange(ChatCommands.DraftResult, overrideExistingKeys: false)) { PlayerControl pc = AllPlayers.FirstOrDefault(x => x.PlayerId == id); if (pc == null) continue; diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 18e3f0164..84b76a484 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -819,6 +819,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.Circumvent when pc.GetCustomRole() is CustomRoles.Swooper or CustomRoles.RiftMaker => false, CustomRoles.Oblivious when pc.Is(CustomRoles.Altruist) => false, CustomRoles.AntiTP when pc.Is(CustomRoles.Transmitter) => false, CustomRoles.Swift when pc.Is(CustomRoles.Stealth) => false, diff --git a/Modules/Debugger.cs b/Modules/Debugger.cs index ff35c8dfe..650f11490 100644 --- a/Modules/Debugger.cs +++ b/Modules/Debugger.cs @@ -27,7 +27,7 @@ class Webhook } } -class Logger +static class Logger { private static bool IsEnable; private static readonly List DisableList = []; diff --git a/Modules/DevManager.cs b/Modules/DevManager.cs index 9137e0aef..5f2a34c82 100644 --- a/Modules/DevManager.cs +++ b/Modules/DevManager.cs @@ -109,6 +109,7 @@ public static class DevManager new(code: "onlyfax#3941", color: "#ff0000", tag: "YouTuber", isUp: true, isDev: false, deBug: false, colorCmd: false, upName: "The Nick AG"), new(code: "somewallet#5521", color: "#ffff00", tag: "Artist", isUp: true, isDev: false, deBug: false, colorCmd: true, upName: "YoshiBertil"), new(code: "pagersane#4064", color: "null", tag: "null", isUp: true, isDev: false, deBug: false, colorCmd: false, upName: "Bluejava3"), + new(code: "pocketdoor#9080", color: "null", tag: "null", isUp: true, isDev: false, deBug: false, colorCmd: false, upName: "Imnot"), // Sponsor new(code: "recentduct#6068", color: "#FF00FF", tag: "高冷男模法师", isUp: false, isDev: false, colorCmd: false, deBug: true, upName: null), diff --git a/Modules/Extensions/CollectionExtensions.cs b/Modules/Extensions/CollectionExtensions.cs index 74d2a6f03..a6a11c043 100644 --- a/Modules/Extensions/CollectionExtensions.cs +++ b/Modules/Extensions/CollectionExtensions.cs @@ -194,7 +194,8 @@ namespace EHR /// Whether to override existing keys in the with the same keys in the dictionary. If true, the same keys in the will be overwritten with the values from the dictionary. If false, the same keys in the will be kept and the values from the dictionary will be ignored /// The type of the keys in the dictionaries /// The type of the values in the dictionaries - public static void AddRange(this Dictionary dictionary, Dictionary other, bool overrideExistingKeys = true) + /// The with the elements from the dictionary added + public static Dictionary AddRange(this Dictionary dictionary, Dictionary other, bool overrideExistingKeys = true) { foreach ((TKey key, TValue value) in other) { @@ -203,6 +204,8 @@ namespace EHR dictionary[key] = value; } } + + return dictionary; } /// diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 22b946492..40f1d2a9a 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -100,6 +100,9 @@ internal static class ChatCommands public static readonly Dictionary MutedPlayers = []; + private static Dictionary> DraftRoles = []; + public static Dictionary DraftResult = []; + public static void LoadCommands() { AllCommands = @@ -293,6 +296,12 @@ internal static class ChatCommands // --------------------------------------------------------------------------------------------------------------------------------------------- + private static void DraftStartCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!player.IsHost() || !GameStates.IsLobby) return; + DraftResult = []; + } + private static void MuteCommand(ChatController __instance, PlayerControl player, string text, string[] args) { if (!player.IsHost() && (GameStates.InGame || MutedPlayers.ContainsKey(player.PlayerId))) return; diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index dd9c5ac34..c10d6d360 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -302,7 +302,7 @@ static class CheckForEndVotingPatch } catch (Exception ex) { - Logger.SendInGame(string.Format(GetString("Error.MeetingException"), ex.Message) /*, true*/); + Utils.ThrowException(ex); throw; } } @@ -498,16 +498,30 @@ static class CheckForEndVotingPatch private static void CheckForDeathOnExile(PlayerState.DeathReason deathReason, params byte[] playerIds) { - Witch.OnCheckForEndVoting(deathReason, playerIds); - Virus.OnCheckForEndVoting(deathReason, playerIds); - if (deathReason == PlayerState.DeathReason.Vote) Gaslighter.OnExile(playerIds); - foreach (var playerId in playerIds) + try { - var id = playerId; - if (CustomRoles.Lovers.IsEnable() && !Main.IsLoversDead && Main.LoversPlayers.Exists(lp => lp.PlayerId == id)) - FixedUpdatePatch.LoversSuicide(playerId, exile: true, force: true); - if (Main.PlayerStates.TryGetValue(id, out var state) && state.SubRoles.Contains(CustomRoles.Avanger)) - RevengeOnExile(playerId /*, deathReason*/); + Witch.OnCheckForEndVoting(deathReason, playerIds); + Virus.OnCheckForEndVoting(deathReason, playerIds); + if (deathReason == PlayerState.DeathReason.Vote) Gaslighter.OnExile(playerIds); + foreach (var playerId in playerIds) + { + try + { + var id = playerId; + if (CustomRoles.Lovers.IsEnable() && !Main.IsLoversDead && Main.LoversPlayers.Exists(lp => lp.PlayerId == id)) + FixedUpdatePatch.LoversSuicide(playerId, exile: true, force: true); + if (Main.PlayerStates.TryGetValue(id, out var state) && state.SubRoles.Contains(CustomRoles.Avanger)) + RevengeOnExile(playerId /*, deathReason*/); + } + catch (Exception e) + { + Utils.ThrowException(e); + } + } + } + catch (Exception e) + { + Utils.ThrowException(e); } } diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs index c7b9a25d8..a1182e9ca 100644 --- a/Patches/OnGameStartedPatch.cs +++ b/Patches/OnGameStartedPatch.cs @@ -364,7 +364,7 @@ internal class ChangeRoleSettings [HarmonyPatch] internal static class StartGameHostPatch { - private static AmongUsClient thiz; + private static AmongUsClient AUClient; private static readonly Dictionary> BasisChangingAddons = []; private static Dictionary RoleTypeNums = []; @@ -390,12 +390,12 @@ internal static class StartGameHostPatch [HarmonyPrefix] public static bool CoStartGameHost_Prefix(AmongUsClient __instance, ref IEnumerator __result) { - thiz = __instance; + AUClient = __instance; __result = StartGameHost().WrapToIl2Cpp(); return false; } - public static System.Collections.IEnumerator StartGameHost() + private static System.Collections.IEnumerator StartGameHost() { if (LobbyBehaviour.Instance) { @@ -405,11 +405,11 @@ internal static class StartGameHostPatch if (!ShipStatus.Instance) { int num = Mathf.Clamp(GameOptionsManager.Instance.CurrentGameOptions.MapId, 0, Constants.MapNames.Length - 1); - thiz.ShipLoadingAsyncHandle = thiz.ShipPrefabs.ToArray()[num].InstantiateAsync(); - yield return thiz.ShipLoadingAsyncHandle; - GameObject result = thiz.ShipLoadingAsyncHandle.Result; + AUClient.ShipLoadingAsyncHandle = AUClient.ShipPrefabs.ToArray()[num].InstantiateAsync(); + yield return AUClient.ShipLoadingAsyncHandle; + GameObject result = AUClient.ShipLoadingAsyncHandle.Result; ShipStatus.Instance = result.GetComponent(); - thiz.Spawn(ShipStatus.Instance); + AUClient.Spawn(ShipStatus.Instance); } float timer = 0f; @@ -422,12 +422,12 @@ internal static class StartGameHostPatch maxTimer = 15; } - var allClients = thiz.allClients; // Possibly .Toarray().ToList() is needed + var allClients = AUClient.allClients; // Possibly .ToArray().ToList() is needed lock (allClients) { - foreach (ClientData clientData in thiz.allClients) + foreach (ClientData clientData in AUClient.allClients) { - if (clientData.Id != thiz.ClientId && !clientData.IsReady) + if (clientData.Id != AUClient.ClientId && !clientData.IsReady) { if (timer < maxTimer) { @@ -435,9 +435,9 @@ internal static class StartGameHostPatch } else { - thiz.SendLateRejection(clientData.Id, DisconnectReasons.ClientTimeout); + AUClient.SendLateRejection(clientData.Id, DisconnectReasons.ClientTimeout); clientData.IsReady = true; - thiz.OnPlayerLeft(clientData, DisconnectReasons.ClientTimeout); + AUClient.OnPlayerLeft(clientData, DisconnectReasons.ClientTimeout); } } } @@ -452,13 +452,12 @@ internal static class StartGameHostPatch timer += Time.deltaTime; } - thiz.SendClientReady(); + AUClient.SendClientReady(); yield return new WaitForSeconds(2f); yield return AssignRoles(); - //ShipStatus.Instance.Begin(); // Tasks sets in IntroPatch } - public static System.Collections.IEnumerator AssignRoles() + private static System.Collections.IEnumerator AssignRoles() { if (AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndChecker.ShowAllRolesWhenGameEnd) yield break; @@ -841,6 +840,7 @@ internal static class StartGameHostPatch { Main.SetRoles = []; Main.SetAddOns = []; + ChatCommands.DraftResult = []; }, 7f, log: false); if ((MapNames)Main.NormalOptions.MapId == MapNames.Airship && AmongUsClient.Instance.AmHost && Main.GM.Value) @@ -1183,16 +1183,23 @@ internal static class StartGameHostPatch Senders = null; OverriddenSenderList = null; StoragedData = null; - - LateTask.New(() => - { - if (CoShowIntroPatch.IntroStarted) return; - Logger.Warn("Starting intro manually", "StartGameHostPatch"); - PlayerControl.AllPlayerControls.ForEach((Action)(PlayerNameColor.Set)); - PlayerControl.LocalPlayer.StopAllCoroutines(); - DestroyableSingleton.Instance.StartCoroutine(DestroyableSingleton.Instance.CoShowIntro()); - DestroyableSingleton.Instance.HideGameLoader(); - }, 3f, log: false); } } +} + +[HarmonyPatch(typeof(RoleManager), nameof(RoleManager.SelectRoles))] +static class FixIntroPatch +{ + public static void Postfix() + { + LateTask.New(() => + { + if (CoShowIntroPatch.IntroStarted) return; + Logger.Warn("Starting intro manually", "StartGameHostPatch"); + PlayerControl.AllPlayerControls.ForEach((Action)(PlayerNameColor.Set)); + PlayerControl.LocalPlayer.StopAllCoroutines(); + DestroyableSingleton.Instance.StartCoroutine(DestroyableSingleton.Instance.CoShowIntro()); + DestroyableSingleton.Instance.HideGameLoader(); + }, 3f, log: false); + } } \ No newline at end of file diff --git a/Patches/OutroPatch.cs b/Patches/OutroPatch.cs index d22b9e40c..b3885f828 100644 --- a/Patches/OutroPatch.cs +++ b/Patches/OutroPatch.cs @@ -26,6 +26,7 @@ static class EndGamePatch Logger.Info("-----------Game over-----------", "Phase"); + ChatCommands.DraftResult = []; Main.SetRoles = []; Main.SetAddOns = []; SummaryText = []; diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 9f69ced0e..a9a6cbd24 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -996,8 +996,7 @@ static class ReportDeadBodyPatch } catch (Exception e) { - Logger.Exception(e, "ReportDeadBodyPatch"); - Logger.SendInGame("Error: " + e); + Utils.ThrowException(e); } return true; diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 7d2d04fec..98d34103d 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -61,6 +61,7 @@ class OnGameJoinedPatch Main.SetRoles = []; Main.SetAddOns = []; + ChatCommands.DraftResult = []; } } } @@ -85,9 +86,8 @@ static class OnPlayerJoinedPatch static bool IsDisconnected(this ClientData client) { var __instance = AmongUsClient.Instance; - for (int i = 0; i < __instance.allClients.Count; i++) + foreach (ClientData clientData in __instance.allClients) { - ClientData clientData = __instance.allClients[i]; if (clientData.Id == client.Id) { return true; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index a06e4eab9..3ff0f3a50 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2409,6 +2409,7 @@ "ERR-000-930-3": "Test Error Lv.3", "ERR-000-804-1": "EHR does not support the Vanilla HnS, so unloaded.", "ERR-001-000-3": "Main dictionary has duplicated keys.", + "ERR-002-000-1": "Your Among Us version is outdated.", "DeathCommand": "<#ffffff>You were killed by {0}<#ffffff>\\nTheir role is {1}", "DeathCommandFail": "<#ff0000>The game could not determine your real killer.", "DefaultSystemMessageTitle": "➡ SYSTEM MESSAGE ⬅", @@ -3536,7 +3537,7 @@ "EclipseVisionIncrease": "Increase Vision per each kill by", "Mastermind": "Mastermind", "MastermindInfo": "Make others kill for you", - "MastermindInfoLong": "(Impostors):\\nAs the Mastermind, you can use your kill button on a player once to manipulate them. This does nothing if the target doesn't have a kill button and they don't have tasks, and the setting to use pets isn't enabled. But if the target has tasks or a kill button of any type, they'll be told after a delay that they were manipulated and they must complete a task or kill someone within a limited time to survive. If the time limit expires or a meeting gets called before completing a task or killing someone, they die.\\nDouble click on someone to kill them normally.", + "MastermindInfoLong": "(Impostors):\\nAs the Mastermind, you can use your kill button on a player once to manipulate them. They'll be told after a delay that they were manipulated and they must kill someone within a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\\nDouble click on someone to kill them normally.", "MastermindCD": "Manipulate Cooldown", "MastermindTimeLimit": "Time Limit to Kill Someone / Complete a Task", "MastermindDelay": "Manipulation Notify Delay", @@ -4446,14 +4447,14 @@ "RG.Suffix": "<#00ffa5>Bullets: {0}", "Agitater": "Agitator", "AgitaterInfo": "Pass a Bomb onto others", - "AgitaterInfoLong": "(Neutrals):\\nAs the Agitater, your premise is essentially Hot Potato.\\n\\nUse your kill button on a player to pass the bomb.\\nThis can only be done once per round.\\n\\nThe player who receives the bomb will be notified when receiving said bomb, in which they need to pass it to another player by getting near a player.\\n\\nWhen a meeting is called, the player with the bomb dies.\\n\\nIf trying to pass to Pestilence or a Veteran on alert, the bombed player dies instead.\\nOptionally, the Agitater cannot receive the bomb.", - "AgitaterBombCooldown": "Agitater bomb coolodown", + "AgitaterInfoLong": "(Neutrals):\\nAs the Agitator, your premise is essentially Hot Potato.\\n\\nUse your kill button on a player to pass the bomb.\\nThis can only be done once per round.\\n\\nThe player who receives the bomb will be notified when receiving said bomb, in which they need to pass it to another player by getting near a player.\\n\\nWhen a meeting is called, the player with the bomb dies.\\n\\nIf trying to pass to Pestilence or a Veteran on alert, the bombed player dies instead.\\nOptionally, the Agitator cannot receive the bomb.", + "AgitaterBombCooldown": "Agitator bomb coolodown", "AgitaterPassCooldown": "Bomb pass cooldown", "BombExplodeCooldown": "Bomb explode cooldown", "AgitaterPassNotify": "Bomb successfully passed", "AgitaterTargetNotify": "YOU HAVE THE BOMB!! Pass it to someone else", - "AgitaterCanGetBombed": "Agitater can get bomb", - "AgitaterAutoReportBait": "Agitater Auto Report Bait", + "AgitaterCanGetBombed": "Agitator can get bomb", + "AgitaterAutoReportBait": "Agitator Auto Report Bait", "Cleansed": "Cleansed", "Prefix.Cleansed": "Cleansed", "Cleansed-": "Cleansed ", @@ -4496,24 +4497,24 @@ "NukeCooldown": "Nuke Cooldown", "Minion": "Minion", "MinionInfo": "Help the Impostors", - "MinionInfoLong": "(Impostors):\\nAs the Minion, you can protect someone (optimally a crewmate) to blind them for a few seconds.", + "MinionInfoLong": "(Impostor Ghost):\\nAs the Minion, you can protect someone (optimally a crewmate) to blind them for a few seconds.", "MinionBlindDuration": "Blind Duration", "Prefix.Minion": "Minion", "Warden": "Warden", "WardenInfo": "Boost the Crewmates", - "WardenInfoLong": "(Crewmates):\\nAs the Warden, you can protect someone (optimally a crewmate) to notify them about being in danger and to give them a temporary speed boost.", + "WardenInfoLong": "(Crewmate Ghost):\\nAs the Warden, you can protect someone (optimally a crewmate) to notify them about being in danger and to give them a temporary speed boost.", "WardenNotify": "The Warden warns you that you're in danger!", "WardenAdditionalSpeed": "Additional Speed", "WardenNotifyTarget": "Notify Target About Danger", "Prefix.Warden": "Warden", "Specter": "Phantasm", "SpecterInfo": "Finish Tasks to Win!", - "SpecterInfoLong": "(Crewmates):\\nAs the Phantasm, you must finish all of your tasks to win! Depending on a setting, you may also end the game and win alone if you do so!", + "SpecterInfoLong": "(Neutral Ghost):\\nAs the Phantasm, you must finish all of your tasks to win! Depending on a setting, you may also end the game and win alone if you do so!", "SnatchWin": "Snatches Win (Win With Winning Team if Off)", "Prefix.Specter": "Phantasm", "Bloodmoon": "Bloodmoon", "BloodmoonInfo": "Help the Impostors Kill!", - "BloodmoonInfoLong": "(Impostors):\\nAs the Bloodmoon, you can use your protect button on someone to make them die after a set amount of seconds. They know about this.", + "BloodmoonInfoLong": "(Impostor Ghost):\\nAs the Bloodmoon, you can use your protect button on someone to make them die after a set amount of seconds. They know about this.", "Bloodmoon.Duration": "Time After Target Dies", "Bloodmoon.Suffix": "<{1}>You will die in <{2}>{0}s", "Bloodmoon.Speed": "<#ff1919>Bloodmoon Speed", @@ -4521,11 +4522,11 @@ "Prefix.Bloodmoon": "Bloodmoon", "GA": "Guardian Angel", "GAInfo": "Protect Crewmates", - "GAInfoLong": "(Crewmates):\\nAs the Guardian Angel, you can protect someone to save them from being killed. This is the vanilla Guardian Angel role, but as an add-on.", + "GAInfoLong": "(Crewmate Ghost):\\nAs the Guardian Angel, you can protect someone to save them from being killed. This is the vanilla Guardian Angel role, but as an add-on.", "Prefix.GA": "Guardian Angel", "Haunter": "Haunter", "HaunterInfo": "You're the Impostors' Worst Nightmare", - "HaunterInfoLong": "(Neutrals):\\nAs the Haunter, when you complete all of your tasks, a random living impostor's name will show in red to everyone in meetings. When you have 1 task left, all impostors will get an arrow pointing towards you, and if any of them come close to you, you lose this role.", + "HaunterInfoLong": "(Neutral Ghost):\\nAs the Haunter, when you complete all of your tasks, a random living impostor's name will show in red to everyone in meetings. When you have 1 task left, all impostors will get an arrow pointing towards you, and if any of them come close to you, you lose this role.", "Haunter1TaskLeft": "<#ffff00>The Haunter has almost finished their tasks!\\nGo near them or you might be revealed!", "HaunterRevealedYou": "You have been revealed by the Haunter!", "HaunterFinishedTasks": "The Haunter completed all tasks, an impostor was revealed! (But it wasn't you)", @@ -4959,7 +4960,7 @@ "WinnerRoleText.Gamer": "Demon Wins!", "WinnerRoleText.DarkHide": "Stalker Wins!", "WinnerRoleText.Workaholic": "Workaholic Wins!", - "WinnerRoleText.Agitater": "Agitater Wins!", + "WinnerRoleText.Agitater": "Agitator Wins!", "WinnerRoleText.Collector": "Collector Wins!", "WinnerRoleText.BloodKnight": "Blood Knight Wins!", "WinnerRoleText.Poisoner": "Poisoner Wins!", diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 0cf0587f5..f313ea200 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -17,7 +17,7 @@ namespace EHR.Crewmate private static OptionItem ReviveTargetsKillerGetsAlert; private static OptionItem ReviveTargetsKillerGetsArrow; - private static HashSet RevivedPlayers; + private static HashSet RevivedPlayers = []; private byte AlturistId; @@ -42,6 +42,7 @@ namespace EHR.Crewmate { On = false; Instances = []; + RevivedPlayers = []; } public override void Add(byte playerId) diff --git a/Roles/Impostor/Witch.cs b/Roles/Impostor/Witch.cs index 33c89e266..da5160c7c 100644 --- a/Roles/Impostor/Witch.cs +++ b/Roles/Impostor/Witch.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Text; using EHR.Crewmate; @@ -187,39 +188,46 @@ public class Witch : RoleBase public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { - if (deathReason != PlayerState.DeathReason.Vote) return; - foreach (byte id in exileIds) + try { - if (playerIdList.Contains(id)) + if (deathReason != PlayerState.DeathReason.Vote) return; + foreach (byte id in exileIds) { - if (Main.PlayerStates[id].Role is not Witch wc) continue; - wc.SpelledPlayer.Clear(); - } - } - - var spelledIdList = new List(); - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - foreach (var witchId in playerIdList) - { - if (Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) continue; - if (Main.PlayerStates[witchId].Role is not Witch wc) continue; - - var witch = Utils.GetPlayerById(witchId); - if (wc.SpelledPlayer.Contains(pc.PlayerId) && witch != null && witch.IsAlive()) + if (playerIdList.Contains(id)) { - pc.SetRealKiller(witch); - spelledIdList.Add(pc.PlayerId); - } - else - { - Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); + if (Main.PlayerStates[id].Role is not Witch wc) continue; + wc.SpelledPlayer.Clear(); } } - } - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Spell, [.. spelledIdList]); - RemoveSpelledPlayer(); + var spelledIdList = new List(); + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + foreach (var witchId in playerIdList) + { + if (Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) continue; + if (Main.PlayerStates[witchId].Role is not Witch wc) continue; + + var witch = Utils.GetPlayerById(witchId); + if (wc.SpelledPlayer.Contains(pc.PlayerId) && witch != null && witch.IsAlive()) + { + pc.SetRealKiller(witch); + spelledIdList.Add(pc.PlayerId); + } + else + { + Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); + } + } + } + + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Spell, [.. spelledIdList]); + RemoveSpelledPlayer(); + } + catch (Exception e) + { + Utils.ThrowException(e); + } } public static string GetSpelledMark(byte target, bool isMeeting) diff --git a/Roles/Neutral/Gaslighter.cs b/Roles/Neutral/Gaslighter.cs index 2bc554231..92e8e7cc4 100644 --- a/Roles/Neutral/Gaslighter.cs +++ b/Roles/Neutral/Gaslighter.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using AmongUs.GameOptions; using EHR.Crewmate; using EHR.Patches; @@ -71,32 +72,39 @@ namespace EHR.Neutral public static void OnExile(byte[] exileIds) { - foreach (Gaslighter instance in Instances) - { - foreach (byte id in exileIds) - { - if (id == instance.GaslighterId) - instance.CursedPlayers.Clear(); - } - } - - List curseDeathList = []; - foreach (PlayerControl pc in Main.AllAlivePlayerControls) + try { 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()) + foreach (byte id in exileIds) { - pc.SetRealKiller(gaslighter); - curseDeathList.Add(pc.PlayerId); + if (id == instance.GaslighterId) + instance.CursedPlayers.Clear(); } } - } - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Spell, [.. curseDeathList]); + List 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]); + } + catch (Exception e) + { + Utils.ThrowException(e); + } } public override void AfterMeetingTasks() @@ -155,7 +163,8 @@ namespace EHR.Neutral } public static bool IsShielded(PlayerControl target) => On && Instances.Exists(i => i.ShieldedPlayers.Contains(target.PlayerId)); - + public static bool IsCursed(PlayerControl target) => On && Instances.Exists(i => i.CursedPlayers.Contains(target.PlayerId)); + public override string GetProgressText(byte playerId, bool comms) { return CurrentRound is Round.Knight or Round.Shield @@ -165,9 +174,12 @@ namespace EHR.Neutral public static string GetMark(PlayerControl seer, PlayerControl target, bool meeting = false) { - if (!IsShielded(target)) return string.Empty; - if (!seer.Is(CustomRoles.Gaslighter) && seer.PlayerId != target.PlayerId) return string.Empty; - return $" ●"; + bool seerIsGaslighter = seer.Is(CustomRoles.Gaslighter); + if (!seerIsGaslighter && seer.PlayerId != target.PlayerId) return string.Empty; + var sb = new System.Text.StringBuilder(); + if (IsShielded(target)) sb.Append($" ●"); + if (IsCursed(target) && (meeting || seerIsGaslighter)) sb.Append(Utils.ColorString(Palette.ImpostorRed, "†")); + return sb.ToString(); } public bool AddAsAdditionalWinner() => WinCondition.GetValue() switch diff --git a/Roles/Neutral/Virus.cs b/Roles/Neutral/Virus.cs index 6d394c354..22ffe4377 100644 --- a/Roles/Neutral/Virus.cs +++ b/Roles/Neutral/Virus.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; using EHR.Patches; @@ -104,40 +105,46 @@ namespace EHR.Neutral public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) { - if (!KillInfectedPlayerAfterMeeting.GetBool()) return; - - PlayerControl virus = - Main.AllAlivePlayerControls.FirstOrDefault(a => a.GetCustomRole() == CustomRoles.Virus); - if (virus == null || deathReason != PlayerState.DeathReason.Vote) return; - - if (exileIds.Contains(virus.PlayerId)) + try { - InfectedPlayer.Clear(); - return; - } + if (!KillInfectedPlayerAfterMeeting.GetBool()) return; - var infectedIdList = new List(); - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - bool isInfected = InfectedPlayer.Contains(pc.PlayerId); - if (!isInfected) continue; + PlayerControl virus = Main.AllAlivePlayerControls.FirstOrDefault(a => a.GetCustomRole() == CustomRoles.Virus); + if (virus == null || deathReason != PlayerState.DeathReason.Vote) return; - if (virus.IsAlive()) + if (exileIds.Contains(virus.PlayerId)) { - if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) + InfectedPlayer.Clear(); + return; + } + + var infectedIdList = new List(); + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + bool isInfected = InfectedPlayer.Contains(pc.PlayerId); + if (!isInfected) continue; + + if (virus.IsAlive()) { - pc.SetRealKiller(virus); - infectedIdList.Add(pc.PlayerId); + if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) + { + pc.SetRealKiller(virus); + infectedIdList.Add(pc.PlayerId); + } + } + else + { + Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); } } - else - { - Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); - } - } - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Infected, [.. infectedIdList]); - InfectedPlayer.Clear(); + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Infected, [.. infectedIdList]); + InfectedPlayer.Clear(); + } + catch (Exception e) + { + Utils.ThrowException(e); + } } public override bool KnowRole(PlayerControl player, PlayerControl target) diff --git a/main.cs b/main.cs index 761201327..4d3b92eae 100644 --- a/main.cs +++ b/main.cs @@ -45,7 +45,7 @@ public class Main : BasePlugin public const string ModColor = "#00ffff"; public const bool AllowPublicRoom = true; public const string ForkId = "EHR"; - public const string SupportedAUVersion = "2024.9.4"; + public const string SupportedAUVersion = "2024.8.13"; public static readonly Version Version = Version.Parse(PluginVersion); public static ManualLogSource Logger; public static bool HasArgumentException;