From 405c0e9c93421aa8271bb43139db7d4ce091f922 Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Sun, 9 Jun 2024 18:27:25 +0200 Subject: [PATCH] Save & Load Combo Data + Fix Bugs --- GameMode/HnSManager.cs | 2 +- Modules/CustomTeamManager.cs | 25 +++++--- Modules/Debugger.cs | 2 +- Modules/DisableDevice.cs | 16 ++--- Modules/Extensions/CollectionExtensions.cs | 16 ++++- .../PlayerGameOptionsSender.cs | 3 + Modules/GameState.cs | 3 +- Modules/GuessManager.cs | 14 ++--- Modules/NameColorManager.cs | 4 +- Modules/Utils.cs | 33 ++++++---- Modules/Zoom.cs | 3 +- Patches/ChatCommandPatch.cs | 29 +++++++-- Patches/ChatControlPatch.cs | 45 +++++++------- Patches/CheckGameEndPatch.cs | 2 +- Patches/ExilePatch.cs | 5 +- Patches/GameOptionsMenuPatch.cs | 4 ++ Patches/MeetingHudPatch.cs | 4 +- Patches/OnGameStartedPatch.cs | 13 +++- Patches/PlayerControlPatch.cs | 60 ++++++++++++------- Resources/Lang/en_US.json | 3 +- Roles/AddOns/Common/Lovers.cs | 11 +++- Roles/AddOns/GhostRoles/GA.cs | 2 +- Roles/AddOns/GhostRoles/Haunter.cs | 14 +++++ Roles/Crewmate/NiceSwapper.cs | 4 +- Roles/Crewmate/ToiletMaster.cs | 4 +- Roles/Impostor/Blackmailer.cs | 2 +- Roles/Impostor/Echo.cs | 54 +++++++++++++---- Roles/Impostor/Witch.cs | 2 +- Roles/Neutral/Tremor.cs | 5 +- 29 files changed, 262 insertions(+), 122 deletions(-) diff --git a/GameMode/HnSManager.cs b/GameMode/HnSManager.cs index 4955c8157..99c4de71d 100644 --- a/GameMode/HnSManager.cs +++ b/GameMode/HnSManager.cs @@ -223,7 +223,7 @@ namespace EHR if (result.ContainsValue(CustomRoles.Agent)) { var agent = result.GetKeyByValue(CustomRoles.Agent).PlayerId; - PlayerRoles.DoIf(x => x.Value.Role != CustomRoles.Agent && x.Value.Interface.Team == Team.Impostor, x => TargetArrow.Add(x.Key, agent)); + PlayerRoles.DoIf(x => x.Value.Role != CustomRoles.Agent && x.Value.Interface.Team == Team.Impostor, x => TargetArrow.Add(x.Key, agent), fast: true); } } diff --git a/Modules/CustomTeamManager.cs b/Modules/CustomTeamManager.cs index c8afb61cb..ee2c6a280 100644 --- a/Modules/CustomTeamManager.cs +++ b/Modules/CustomTeamManager.cs @@ -75,7 +75,7 @@ namespace EHR.Modules foreach ((CustomTeam team, HashSet players) in CustomTeamPlayerIds) { - if (!IsSettingEnabledForTeam(team, "Arrows")) continue; + if (!IsSettingEnabledForTeam(team, CTAOption.Arrows)) continue; foreach (byte player in players) { @@ -90,7 +90,7 @@ namespace EHR.Modules public static string GetSuffix(PlayerControl seer) { - if (seer == null || EnabledCustomTeams.Count == 0 || !IsSettingEnabledForPlayerTeam(seer.PlayerId, "Arrows")) return string.Empty; + if (seer == null || EnabledCustomTeams.Count == 0 || !IsSettingEnabledForPlayerTeam(seer.PlayerId, CTAOption.Arrows)) return string.Empty; return CustomTeamPlayerIds[GetCustomTeam(seer.PlayerId)].Aggregate(string.Empty, (s, id) => s + Utils.ColorString(Main.PlayerColors.GetValueOrDefault(id, Color.white), TargetArrow.GetArrows(seer, id))); } @@ -142,18 +142,17 @@ namespace EHR.Modules return team1 != null && team2 != null && team1.Equals(team2); } - public static bool IsSettingEnabledForPlayerTeam(byte id, string settingName) + public static bool IsSettingEnabledForPlayerTeam(byte id, CTAOption setting) { var team = GetCustomTeam(id); - return team != null && IsSettingEnabledForTeam(team, settingName); + return team != null && IsSettingEnabledForTeam(team, setting); } - public static bool IsSettingEnabledForTeam(CustomTeam team, string settingName) + public static bool IsSettingEnabledForTeam(CustomTeam team, CTAOption setting) { var optionsGroup = CustomTeamOptions.First(x => x.Team.Equals(team)); - var setting = optionsGroup.GetType().GetFields().FirstOrDefault(x => x.Name.Contains(settingName)); - if (setting == null) return false; - return setting.GetValue(optionsGroup) as bool? ?? false; + var values = optionsGroup.AllOptions.ConvertAll(x => x.GetBool()); + return values[(int)setting]; } internal class CustomTeam @@ -210,4 +209,14 @@ namespace EHR.Modules public BooleanOptionItem Arrows { get; set; } = arrows; } } + + public enum CTAOption + { + Enabled, + KnowRoles, + WinWithOriginalTeam, + KillEachOther, + GuessEachOther, + Arrows + } } \ No newline at end of file diff --git a/Modules/Debugger.cs b/Modules/Debugger.cs index aa1448ed7..710fcd3f4 100644 --- a/Modules/Debugger.cs +++ b/Modules/Debugger.cs @@ -79,7 +79,7 @@ class Logger string memberName = stack.GetMethod()?.Name; log_text = $"[{t}][{className}.{memberName}({Path.GetFileName(fileName)}:{lineNumber})][{tag}]{text}"; NowDetailedErrorLog.Add(tag); - LateTask.New(() => NowDetailedErrorLog.Remove(tag), 5f, log: false); + LateTask.New(() => NowDetailedErrorLog.Remove(tag), 3f, log: false); } else { diff --git a/Modules/DisableDevice.cs b/Modules/DisableDevice.cs index eef81f04f..edc63b87d 100644 --- a/Modules/DisableDevice.cs +++ b/Modules/DisableDevice.cs @@ -178,21 +178,21 @@ public class RemoveDisableDevicesPatch if (Options.DisableSkeldAdmin.GetBool() || rogueForce) admins[0].gameObject.GetComponent().enabled = ignore; if (Options.DisableSkeldCamera.GetBool() || rogueForce) - consoles.DoIf(x => x.name == "SurvConsole", x => x.gameObject.GetComponent().enabled = ignore); + consoles.DoIf(x => x.name == "SurvConsole", x => x.gameObject.GetComponent().enabled = ignore, fast: true); break; case 1: if (Options.DisableMiraHQAdmin.GetBool() || rogueForce) admins[0].gameObject.GetComponent().enabled = ignore; if (Options.DisableMiraHQDoorLog.GetBool() || rogueForce) - consoles.DoIf(x => x.name == "SurvLogConsole", x => x.gameObject.GetComponent().enabled = ignore); + consoles.DoIf(x => x.name == "SurvLogConsole", x => x.gameObject.GetComponent().enabled = ignore, fast: true); break; case 2: if (Options.DisablePolusAdmin.GetBool() || rogueForce) admins.Do(x => x.gameObject.GetComponent().enabled = ignore); if (Options.DisablePolusCamera.GetBool() || rogueForce) - consoles.DoIf(x => x.name == "Surv_Panel", x => x.gameObject.GetComponent().enabled = ignore); + consoles.DoIf(x => x.name == "Surv_Panel", x => x.gameObject.GetComponent().enabled = ignore, fast: true); if (Options.DisablePolusVital.GetBool() || rogueForce) - consoles.DoIf(x => x.name == "panel_vitals", x => x.gameObject.GetComponent().enabled = ignore); + consoles.DoIf(x => x.name == "panel_vitals", x => x.gameObject.GetComponent().enabled = ignore, fast: true); break; case 4: admins.Do(x => @@ -202,15 +202,15 @@ public class RemoveDisableDevicesPatch x.gameObject.GetComponent().enabled = ignore; }); if (Options.DisableAirshipCamera.GetBool() || rogueForce) - consoles.DoIf(x => x.name == "task_cams", x => x.gameObject.GetComponent().enabled = ignore); + consoles.DoIf(x => x.name == "task_cams", x => x.gameObject.GetComponent().enabled = ignore, fast: true); if (Options.DisableAirshipVital.GetBool() || rogueForce) - consoles.DoIf(x => x.name == "panel_vitals", x => x.gameObject.GetComponent().enabled = ignore); + consoles.DoIf(x => x.name == "panel_vitals", x => x.gameObject.GetComponent().enabled = ignore, fast: true); break; case 5: if (Options.DisableFungleCamera.GetBool() || rogueForce) - consoles.DoIf(x => x.name == "BinocularsSecurityConsole", x => x.gameObject.GetComponent().enabled = ignore); + consoles.DoIf(x => x.name == "BinocularsSecurityConsole", x => x.gameObject.GetComponent().enabled = ignore, fast: true); if (Options.DisableFungleCamera.GetBool() || rogueForce) - consoles.DoIf(x => x.name == "VitalsConsole", x => x.gameObject.GetComponent().enabled = ignore); + consoles.DoIf(x => x.name == "VitalsConsole", x => x.gameObject.GetComponent().enabled = ignore, fast: true); break; } } diff --git a/Modules/Extensions/CollectionExtensions.cs b/Modules/Extensions/CollectionExtensions.cs index db0845b18..5a56cabc9 100644 --- a/Modules/Extensions/CollectionExtensions.cs +++ b/Modules/Extensions/CollectionExtensions.cs @@ -83,11 +83,25 @@ namespace EHR /// Executes an action for each element in a collection if the predicate is true /// /// The collection to iterate over + /// Whether to use a fast loop or linq /// The predicate to check for each element /// The action to execute for each element that satisfies the predicate /// The type of the elements in the collection - public static void DoIf(this IEnumerable collection, Func predicate, Action action) + public static void DoIf(this IEnumerable collection, Func predicate, Action action, bool fast = false) { + if (fast) + { + foreach (T element in collection) + { + if (predicate(element)) + { + action(element); + } + } + + return; + } + var partitioner = Partitioner.Create(collection.Where(predicate)); partitioner.GetDynamicPartitions().Do(action); } diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index e35bf6c4b..d19552f9b 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using AmongUs.GameOptions; +using EHR.Neutral; using EHR.Roles.AddOns.GhostRoles; using EHR.Roles.Crewmate; using EHR.Roles.Impostor; @@ -195,6 +196,8 @@ public class PlayerGameOptionsSender(PlayerControl player) : GameOptionsSender break; } + if (Shifter.WasShifter.Contains(player.PlayerId) && role.IsImpostor()) opt.SetVision(true); + Main.PlayerStates[player.PlayerId].Role.ApplyGameOptions(opt, player.PlayerId); if (Main.AllPlayerControls.Any(x => x.Is(CustomRoles.Bewilder) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == player.PlayerId && !x.Is(CustomRoles.Hangman))) diff --git a/Modules/GameState.cs b/Modules/GameState.cs index f2f11f4e5..042b91406 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -250,8 +250,7 @@ public class PlayerState(byte playerId) public void RemoveSubRole(CustomRoles role) { - if (SubRoles.Contains(role)) - SubRoles.Remove(role); + SubRoles.Remove(role); if (role is CustomRoles.Flashman or CustomRoles.Dynamo) { diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 2d580dc11..fca69a439 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -186,7 +186,7 @@ public static class GuessManager return true; } - if (CustomTeamManager.AreInSameCustomTeam(pc.PlayerId, targetId) && !CustomTeamManager.IsSettingEnabledForPlayerTeam(targetId, "GuessEachOther")) + if (CustomTeamManager.AreInSameCustomTeam(pc.PlayerId, targetId) && !CustomTeamManager.IsSettingEnabledForPlayerTeam(targetId, CTAOption.GuessEachOther)) { if (!isUI) Utils.SendMessage(GetString("GuessSameCTAPlayer"), pc.PlayerId); else pc.ShowPopUp(GetString("GuessSameCTAPlayer")); @@ -237,8 +237,8 @@ public static class GuessManager case CustomRoles.Doomsayer: if (Doomsayer.CantGuess) { - if (!isUI) Utils.SendMessage(GetString("DommsayerCantGuess"), pc.PlayerId); - else pc.ShowPopUp(GetString("DommsayerCantGuess")); + if (!isUI) Utils.SendMessage(GetString("DoomsayerCantGuess"), pc.PlayerId); + else pc.ShowPopUp(GetString("DoomsayerCantGuess")); return true; } @@ -357,8 +357,8 @@ public static class GuessManager if (Jailor.playerIdList.Any(x => Main.PlayerStates[x].Role is Jailor { IsEnable: true } jl && jl.JailorTarget == target.PlayerId)) { - if (!isUI) Utils.SendMessage(GetString("Can'tGuessJailed"), pc.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))); - else pc.ShowPopUp(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle")) + "\n" + GetString("Can'tGuessJailed")); + if (!isUI) Utils.SendMessage(GetString("CantGuessJailed"), pc.PlayerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))); + else pc.ShowPopUp(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle")) + "\n" + GetString("CantGuessJailed")); return true; } @@ -482,8 +482,8 @@ public static class GuessManager { if (Doomsayer.GuessesCountPerMeeting >= Doomsayer.MaxNumberOfGuessesPerMeeting.GetInt() && pc.PlayerId != dp.PlayerId) { - if (!isUI) Utils.SendMessage(GetString("DommsayerCantGuess"), pc.PlayerId); - else pc.ShowPopUp(GetString("DommsayerCantGuess")); + if (!isUI) Utils.SendMessage(GetString("DoomsayerCantGuess"), pc.PlayerId); + else pc.ShowPopUp(GetString("DoomsayerCantGuess")); return true; } diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index d2406e7c4..83c92eb2e 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -71,7 +71,7 @@ public static class NameColorManager } // Custom Teams - if (CustomTeamManager.AreInSameCustomTeam(seer.PlayerId, target.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(seer.PlayerId, "KnowRoles")) color = Main.RoleColors[target.GetCustomRole()]; + if (CustomTeamManager.AreInSameCustomTeam(seer.PlayerId, target.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(seer.PlayerId, CTAOption.KnowRoles)) color = Main.RoleColors[target.GetCustomRole()]; // Add-ons if (target.Is(CustomRoles.Glow) && Utils.IsActive(SystemTypes.Electrical)) color = Main.RoleColors[CustomRoles.Glow]; @@ -199,7 +199,7 @@ public static class NameColorManager || seer.Is(CustomRoles.GM) || seer.Is(CustomRoles.God) || (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoleTypes.Impostor)) - || (seer.Is(CustomRoles.Traitor) && target.Is(CustomRoleTypes.Impostor)) + || (seer.Is(CustomRoles.Traitor) && target.Is(Team.Impostor)) || (seer.Is(CustomRoles.Jackal) && target.Is(CustomRoles.Sidekick)) || (seer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Sidekick)) || (seer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Jackal)) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index e01ac1837..ab0a4c168 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -313,17 +313,21 @@ public static class Utils public static void SaveComboInfo() { - SaveFile("./EHR_Data/AlwaysCombos.json"); - SaveFile("./EHR_Data/NeverCombos.json"); + SaveFile("./EHR_DATA/AlwaysCombos.json"); + SaveFile("./EHR_DATA/NeverCombos.json"); return; void SaveFile(string path) { try { - var data = new Il2CppSystem.Collections.Generic.Dictionary>(); + var data = new Il2CppSystem.Collections.Generic.Dictionary>(); var dict = path.Contains("Always") ? Main.AlwaysSpawnTogetherCombos : Main.NeverSpawnTogetherCombos; - dict.Do(pair => data[pair.Key.ToString()] = pair.Value.ConvertAll(x => (int)x)); + dict.Do(pair => + { + data[pair.Key.ToString()] = new(); + pair.Value.Do(x => data[pair.Key.ToString()].Add((int)x)); + }); File.WriteAllText(path, JsonConvert.SerializeObject(data, Formatting.Indented)); } catch (Exception e) @@ -336,8 +340,8 @@ public static class Utils public static void LoadComboInfo() { - LoadFile("./EHR_Data/AlwaysCombos.json"); - LoadFile("./EHR_Data/NeverCombos.json"); + LoadFile("./EHR_DATA/AlwaysCombos.json"); + LoadFile("./EHR_DATA/NeverCombos.json"); return; void LoadFile(string path) @@ -345,13 +349,17 @@ public static class Utils try { if (!File.Exists(path)) return; - var data = JsonConvert.DeserializeObject>>(File.ReadAllText(path)); + var data = JsonConvert.DeserializeObject>>(File.ReadAllText(path)); var dict = path.Contains("Always") ? Main.AlwaysSpawnTogetherCombos : Main.NeverSpawnTogetherCombos; dict.Clear(); foreach (var pair in data) { var key = Enum.Parse(pair.Key); - dict[key] = pair.Value.ConvertAll(x => (CustomRoles)x); + dict[key] = []; + foreach (var n in pair.Value) + { + dict[key].Add((CustomRoles)n); + } } } catch (Exception e) @@ -423,12 +431,12 @@ public static class Utils var self = seerId == targetId || Main.PlayerStates[seerId].IsDead; bool isHnsAgentOverride = Options.CurrentGameMode == CustomGameMode.HideAndSeek && targetMainRole == CustomRoles.Agent && HnSManager.PlayerRoles[seerId].Interface.Team != Team.Impostor; - if (targetMainRole == CustomRoles.LovingImpostor) + if (!GameStates.IsEnded && targetMainRole == CustomRoles.LovingImpostor && !self && seerMainRole != CustomRoles.LovingCrewmate && !seerSubRoles.Contains(CustomRoles.Lovers)) { targetMainRole = Lovers.LovingImpostorRoleForOtherImps.GetValue() switch { 0 => CustomRoles.ImpostorEHR, - 1 => Enum.GetValues().Where(x => x.IsEnable() && x.IsImpostor() && x != CustomRoles.LovingImpostor && !HnSManager.AllHnSRoles.Contains(x)).Shuffle()[0], + 1 => Lovers.LovingImpostorRole, _ => CustomRoles.LovingImpostor }; } @@ -826,7 +834,7 @@ public static class Utils __instance.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.Is(CustomRoles.Crewpostor) && Options.AlliesKnowCrewpostor.GetBool() || __instance.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && Options.ImpKnowAlliesRole.GetBool() || __instance.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.Is(CustomRoles.Madmate) && Options.MadmateKnowWhosImp.GetBool() || - CustomTeamManager.AreInSameCustomTeam(__instance.PlayerId, PlayerControl.LocalPlayer.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(__instance.PlayerId, "KnowRoles") || + CustomTeamManager.AreInSameCustomTeam(__instance.PlayerId, PlayerControl.LocalPlayer.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(__instance.PlayerId, CTAOption.KnowRoles) || Main.PlayerStates.Values.Any(x => x.Role.KnowRole(PlayerControl.LocalPlayer, __instance)) || PlayerControl.LocalPlayer.IsRevealedPlayer(__instance) || PlayerControl.LocalPlayer.Is(CustomRoles.God) || @@ -1984,6 +1992,7 @@ public static class Utils if (seer.Is(CustomRoles.Sonar)) SelfSuffix.Append(Sonar.GetSuffix(seer, isForMeeting)); SelfSuffix.Append(Bloodmoon.GetSuffix(seer)); + SelfSuffix.Append(Haunter.GetSuffix(seer)); switch (seer.GetCustomRole()) { @@ -2230,7 +2239,7 @@ public static class Utils (target.Is(CustomRoles.Mayor) && Mayor.MayorRevealWhenDoneTasks.GetBool() && target.GetTaskState().IsTaskFinished) || (seer.Is(CustomRoleTypes.Crewmate) && target.Is(CustomRoles.Marshall) && target.GetTaskState().IsTaskFinished) || (Main.PlayerStates[target.PlayerId].deathReason == PlayerState.DeathReason.Vote && Options.SeeEjectedRolesInMeeting.GetBool()) || - CustomTeamManager.AreInSameCustomTeam(seer.PlayerId, target.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(seer.PlayerId, "KnowRoles") || + CustomTeamManager.AreInSameCustomTeam(seer.PlayerId, target.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(seer.PlayerId, CTAOption.KnowRoles) || Main.PlayerStates.Values.Any(x => x.Role.KnowRole(seer, target)) || Markseeker.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Markseeker { IsEnable: true, TargetRevealed: true } ms && ms.MarkedId == target.PlayerId) || Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.MoveAndStop or CustomGameMode.HotPotato || diff --git a/Modules/Zoom.cs b/Modules/Zoom.cs index a6df99b24..80e919977 100644 --- a/Modules/Zoom.cs +++ b/Modules/Zoom.cs @@ -55,9 +55,8 @@ public static class Zoom Flag.Run(() => { SetZoomSize(reset: true); }, "Zoom"); } } - catch (Exception e) + catch { - Utils.ThrowException(e); } } diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 934a3f2db..ace2b15ab 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -755,7 +755,20 @@ internal class ChatCommands goto Canceled; } - if ((PlayerControl.LocalPlayer.IsAlive() || ExileController.Instance) && Lovers.PrivateChat.GetBool() && (ExileController.Instance || !GameStates.IsMeeting)) goto Canceled; + if (GameStates.IsInGame && ((PlayerControl.LocalPlayer.IsAlive() || ExileController.Instance) && Lovers.PrivateChat.GetBool() && (ExileController.Instance || !GameStates.IsMeeting))) + { + if (PlayerControl.LocalPlayer.Is(CustomRoles.Lovers) || PlayerControl.LocalPlayer.GetCustomRole() is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor) + { + var otherLover = Main.LoversPlayers.First(x => x.PlayerId != PlayerControl.LocalPlayer.PlayerId); + var title = PlayerControl.LocalPlayer.GetRealName(); + ChatUpdatePatch.LoversMessage = true; + Utils.SendMessage(text, otherLover.PlayerId, title); + Utils.SendMessage(text, PlayerControl.LocalPlayer.PlayerId, title); + LateTask.New(() => ChatUpdatePatch.LoversMessage = false, 1f, log: false); + } + + goto Canceled; + } goto Skip; Canceled: @@ -1382,14 +1395,21 @@ internal class ChatCommands return; } - if (GameStates.IsInGame && ((player.IsAlive() || ExileController.Instance) && Lovers.PrivateChat.GetBool() && (ExileController.Instance || !GameStates.IsMeeting))) + if (GameStates.IsInGame && !ChatUpdatePatch.LoversMessage && ((player.IsAlive() || ExileController.Instance) && Lovers.PrivateChat.GetBool() && (ExileController.Instance || !GameStates.IsMeeting))) { - ChatManager.SendPreviousMessagesToAll(); + ChatManager.SendPreviousMessagesToAll(clear: true); canceled = true; if (player.Is(CustomRoles.Lovers) || player.GetCustomRole() is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor) { var otherLover = Main.LoversPlayers.First(x => x.PlayerId != player.PlayerId); - LateTask.New(() => Utils.SendMessage(text, otherLover.PlayerId, player.GetRealName()), 0.5f, log: false); + LateTask.New(() => + { + var title = player.GetRealName(); + ChatUpdatePatch.LoversMessage = true; + Utils.SendMessage(text, otherLover.PlayerId, title); + Utils.SendMessage(text, player.PlayerId, title); + LateTask.New(() => ChatUpdatePatch.LoversMessage = false, 1f, log: false); + }, 0.5f, log: false); } } @@ -1402,6 +1422,7 @@ internal class ChatCommands internal class ChatUpdatePatch { public static bool DoBlockChat; + public static bool LoversMessage; public static void Postfix(ChatController __instance) { diff --git a/Patches/ChatControlPatch.cs b/Patches/ChatControlPatch.cs index f6d8b1072..4c7dd9a77 100644 --- a/Patches/ChatControlPatch.cs +++ b/Patches/ChatControlPatch.cs @@ -198,7 +198,7 @@ public static class ChatManager } } - public static void SendPreviousMessagesToAll(bool realMessagesOnly = false) + public static void SendPreviousMessagesToAll(bool clear = false) { if (!AmongUsClient.Instance.AmHost || !GameStates.IsModHost) return; ChatUpdatePatch.DoBlockChat = true; @@ -209,36 +209,31 @@ public static class ChatManager var filtered = ChatHistory.Where(a => Utils.GetPlayerById(Convert.ToByte(a.Split(':')[0].Trim())).IsAlive()).ToArray(); - switch (realMessagesOnly) + for (int i = clear ? 0 : filtered.Length; i < 20; i++) { - case true when filtered.Length < 5: - return; - case false: - for (int i = filtered.Length; i < 20; i++) - { - var player = x[r.Next(0, totalAlive)]; - DestroyableSingleton.Instance.Chat.AddChat(player, msg); - SendRPC(player, msg); - } - - break; + var player = x[r.Next(0, totalAlive)]; + DestroyableSingleton.Instance.Chat.AddChat(player, msg); + SendRPC(player, msg); } - foreach (string str in filtered) + if (!clear) { - var entryParts = str.Split(':'); - var senderId = entryParts[0].Trim(); - var senderMessage = entryParts[1].Trim(); - for (int j = 2; j < entryParts.Length; j++) + foreach (string str in filtered) { - senderMessage += ':' + entryParts[j].Trim(); + var entryParts = str.Split(':'); + var senderId = entryParts[0].Trim(); + var senderMessage = entryParts[1].Trim(); + for (int j = 2; j < entryParts.Length; j++) + { + senderMessage += ':' + entryParts[j].Trim(); + } + + var senderPlayer = Utils.GetPlayerById(Convert.ToByte(senderId)); + if (senderPlayer == null) continue; + + DestroyableSingleton.Instance.Chat.AddChat(senderPlayer, senderMessage); + SendRPC(senderPlayer, senderMessage); } - - var senderPlayer = Utils.GetPlayerById(Convert.ToByte(senderId)); - if (senderPlayer == null) continue; - - DestroyableSingleton.Instance.Chat.AddChat(senderPlayer, senderMessage); - SendRPC(senderPlayer, senderMessage); } ChatUpdatePatch.DoBlockChat = false; diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 6af31f77a..ac4840441 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -237,7 +237,7 @@ class GameEndChecker .Where(x => x.Team != null) .GroupBy(x => x.Team) .ToDictionary(x => x.Key, x => x.Select(y => y.Player.PlayerId)) - .DoIf(x => !CustomTeamManager.IsSettingEnabledForTeam(x.Key, "WinWithOriginalTeam"), x => WinnerIds.ExceptWith(x.Value)); + .DoIf(x => !CustomTeamManager.IsSettingEnabledForTeam(x.Key, CTAOption.WinWithOriginalTeam), x => WinnerIds.ExceptWith(x.Value), fast: true); } if ((WinnerTeam == CustomWinner.Lovers || WinnerIds.Any(x => Main.PlayerStates[x].SubRoles.Contains(CustomRoles.Lovers))) && Main.LoversPlayers.All(x => x.IsAlive())) diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 45f73e35d..c7c4137bf 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using AmongUs.Data; +using EHR.Roles.AddOns.Common; using EHR.Roles.AddOns.Crewmate; using EHR.Roles.AddOns.Impostor; using EHR.Roles.Crewmate; @@ -184,6 +185,8 @@ class ExileControllerWrapUpPatch SoundManager.Instance.ChangeAmbienceVolume(DataManager.Settings.Audio.AmbienceVolume); Logger.Info("Start task phase", "Phase"); + if (!Lovers.IsChatActivated && Lovers.PrivateChat.GetBool()) return; + bool showRemainingKillers = Options.EnableKillerLeftCommand.GetBool() && Options.ShowImpRemainOnEject.GetBool(); bool appendEjectionNotify = CheckForEndVotingPatch.EjectionText != string.Empty; Logger.Warn($"Ejection Text: {CheckForEndVotingPatch.EjectionText}", "ExilePatch"); @@ -214,7 +217,7 @@ class ExileControllerWrapUpPatch }, 0.5f, log: false); } - LateTask.New(() => { ChatManager.SendPreviousMessagesToAll(); }, 3f, log: false); + LateTask.New(() => ChatManager.SendPreviousMessagesToAll(), 3f, log: false); } [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))] diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 3faa1710e..8c9fc81d0 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -45,20 +45,24 @@ public static class GameOptionsMenuPatch { case StringNames.GameVotingTime: ob.Cast().ValidRange = new(0, 600); + ob.Cast().Value = (float)Math.Round(ob.Cast().Value, 2); break; case StringNames.GameShortTasks: case StringNames.GameLongTasks: case StringNames.GameCommonTasks: ob.Cast().ValidRange = new(0, 90); + ob.Cast().Value = (float)Math.Round(ob.Cast().Value, 2); break; case StringNames.GameKillCooldown: ob.Cast().ValidRange = new(0, 180); ob.Cast().Increment = 0.5f; + ob.Cast().Value = (float)Math.Round(ob.Cast().Value, 2); break; case StringNames.GamePlayerSpeed: case StringNames.GameCrewLight: case StringNames.GameImpostorLight: ob.Cast().Increment = 0.05f; + ob.Cast().Value = (float)Math.Round(ob.Cast().Value, 2); break; } } diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 03a33cdfe..74bdd4bab 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -810,7 +810,7 @@ class MeetingHudStartPatch (pc.Is(CustomRoles.Mayor) && Mayor.MayorRevealWhenDoneTasks.GetBool() && pc.GetTaskState().IsTaskFinished) || (pc.Is(CustomRoles.Marshall) && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Crewmate) && pc.GetTaskState().IsTaskFinished) || (Main.PlayerStates[pc.PlayerId].deathReason == PlayerState.DeathReason.Vote && Options.SeeEjectedRolesInMeeting.GetBool()) || - CustomTeamManager.AreInSameCustomTeam(pc.PlayerId, PlayerControl.LocalPlayer.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(pc.PlayerId, "KnowRoles") || + CustomTeamManager.AreInSameCustomTeam(pc.PlayerId, PlayerControl.LocalPlayer.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(pc.PlayerId, CTAOption.KnowRoles) || Main.PlayerStates.Values.Any(x => x.Role.KnowRole(PlayerControl.LocalPlayer, pc)) || Markseeker.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Markseeker { IsEnable: true, TargetRevealed: true } ms && ms.MarkedId == pc.PlayerId) || PlayerControl.LocalPlayer.IsRevealedPlayer(pc) || @@ -1034,7 +1034,7 @@ class MeetingHudUpdatePatch Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()}を処刑しました", "Execution"); __instance.CheckForEndVoting(); } - }); + }, fast: true); } if (!GameStates.IsVoting && __instance.lastSecond < 1) diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs index 906762798..ddd318f1b 100644 --- a/Patches/OnGameStartedPatch.cs +++ b/Patches/OnGameStartedPatch.cs @@ -300,6 +300,7 @@ internal class ChangeRoleSettings Asthmatic.Init(); DoubleShot.Init(); Circumvent.Init(); + Lovers.Init(); } catch (Exception ex) { @@ -603,13 +604,23 @@ internal class SelectRolesPatch if (!overrideLovers && CustomRoles.Lovers.IsEnable() && (CustomRoles.FFF.IsEnable() ? -1 : rd.Next(1, 100)) <= Lovers.LoverSpawnChances.GetInt()) AssignLoversRolesFromList(); + // Add-on assignment var aapc = Main.AllAlivePlayerControls; + var addonNum = aapc.ToDictionary(x => x, _ => 0); AddonRolesList .Where(x => x.IsEnable()) .SelectMany(x => Enumerable.Repeat(x, Math.Clamp(x.GetCount(), 0, aapc.Length))) .Shuffle() .Chunk(aapc.Length) - .Do(c => c.Zip(aapc).DoIf(x => CustomRolesHelper.CheckAddonConflict(x.First, x.Second) && IRandom.Instance.Next(1, 100) <= (Options.CustomAdtRoleSpawnRate.TryGetValue(x.First, out var sc) ? sc.GetFloat() : 0), x => Main.PlayerStates[x.Second.PlayerId].SetSubRole(x.First))); + .SelectMany(a => a.Select(x => + { + var suitablePlayer = aapc + .OrderBy(p => addonNum[p]) + .FirstOrDefault(p => CustomRolesHelper.CheckAddonConflict(x, p)); + if (suitablePlayer != null) addonNum[suitablePlayer]++; + return (Role: x, SuitablePlayer: suitablePlayer); + })) + .DoIf(x => x.SuitablePlayer != null && IRandom.Instance.Next(1, 100) <= (Options.CustomAdtRoleSpawnRate.TryGetValue(x.Role, out var sc) ? sc.GetFloat() : 0), x => Main.PlayerStates[x.SuitablePlayer.PlayerId].SetSubRole(x.Role), fast: true); foreach (var state in Main.PlayerStates.Values) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 6d4ff82e1..c98b3f692 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -302,7 +302,7 @@ class CheckMurderPatch if (!AmongUsClient.Instance.AmHost) return false; if (target == null) target = killer; - if (CustomTeamManager.AreInSameCustomTeam(killer.PlayerId, target.PlayerId) && !CustomTeamManager.IsSettingEnabledForPlayerTeam(killer.PlayerId, "KillEachOther")) return false; + if (CustomTeamManager.AreInSameCustomTeam(killer.PlayerId, target.PlayerId) && !CustomTeamManager.IsSettingEnabledForPlayerTeam(killer.PlayerId, CTAOption.KillEachOther)) return false; if (killer.Is(CustomRoles.Jackal) && target.Is(CustomRoles.Sidekick) && !Options.JackalCanKillSidekick.GetBool()) return false; if (killer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Jackal) && !Options.SidekickCanKillJackal.GetBool()) return false; @@ -328,7 +328,19 @@ class CheckMurderPatch if (killer.Is(CustomRoles.Refugee) && target.Is(CustomRoleTypes.Impostor)) return false; - if (GhostRolesManager.AssignedGhostRoles.TryGetValue(target.PlayerId, out var ghostRole) && ghostRole.Instance is GA ga && ga.ProtectionList.Contains(target.PlayerId)) return false; + if (Echo.On) + { + foreach (Echo echo in Echo.Instances) + { + if (echo.EchoPC.shapeshiftTargetPlayerId == target.PlayerId) + { + echo.OnTargetCheckMurder(killer, target); + return false; + } + } + } + + if (GhostRolesManager.AssignedGhostRoles.Values.Any(x => x.Instance is GA ga && ga.ProtectionList.Contains(target.PlayerId))) return false; if (SoulHunter.IsSoulHunterTarget(killer.PlayerId) && target.Is(CustomRoles.SoulHunter)) { @@ -1002,7 +1014,7 @@ class FixedUpdatePatch if (id.IsPlayerRoleBlocked()) { __instance.Notify(BlockedAction.Report.GetBlockNotify()); - Logger.Info("Dead Body Report Blocked (player is hacked by Glitch)", "FixedUpdate.ReportDeadBody"); + Logger.Info("Dead Body Report Blocked (player role blocked)", "FixedUpdate.ReportDeadBody"); ReportDeadBodyPatch.WaitReport[id].Clear(); } else @@ -1014,12 +1026,32 @@ class FixedUpdatePatch } } + if (AmongUsClient.Instance.AmHost) + { + if (GhostRolesManager.AssignedGhostRoles.TryGetValue(__instance.PlayerId, out var ghostRole)) + { + switch (ghostRole.Instance) + { + case Haunter haunter: + haunter.Update(__instance); + break; + case Bloodmoon: + Bloodmoon.Update(__instance); + break; + } + } + else if (!Main.HasJustStarted && GameStates.IsInTask && GhostRolesManager.ShouldHaveGhostRole(__instance)) + { + GhostRolesManager.AssignGhostRole(__instance); + } + } + if (Options.DontUpdateDeadPlayers.GetBool() && !__instance.IsAlive()) { - DeadBufferTime.TryAdd(id, IRandom.Instance.Next(50, 70)); + DeadBufferTime.TryAdd(id, IRandom.Instance.Next(20, 40)); DeadBufferTime[id]--; if (DeadBufferTime[id] > 0) return; - DeadBufferTime[id] = IRandom.Instance.Next(50, 70); + DeadBufferTime[id] = IRandom.Instance.Next(20, 40); } if (Options.LowLoadMode.GetBool()) @@ -1155,23 +1187,6 @@ class FixedUpdatePatch } } - if (GhostRolesManager.AssignedGhostRoles.TryGetValue(player.PlayerId, out var ghostRole)) - { - switch (ghostRole.Instance) - { - case Haunter haunter: - haunter.Update(player); - break; - case Bloodmoon when !lowLoad: - Bloodmoon.Update(player); - break; - } - } - else if (!lowLoad && !Main.HasJustStarted && GameStates.IsInTask && GhostRolesManager.ShouldHaveGhostRole(player)) - { - GhostRolesManager.AssignGhostRole(player); - } - long now = TimeStamp; if (!lowLoad && Options.UsePets.GetBool() && GameStates.IsInTask && (!LastUpdate.TryGetValue(playerId, out var lastPetNotify) || lastPetNotify < now)) { @@ -1486,6 +1501,7 @@ class FixedUpdatePatch if (self) { Suffix.Append(Bloodmoon.GetSuffix(seer)); + Suffix.Append(Haunter.GetSuffix(seer)); if (seer.Is(CustomRoles.Asthmatic)) Suffix.Append(Asthmatic.GetSuffixText(seer.PlayerId)); if (seer.Is(CustomRoles.Sonar)) Suffix.Append(Sonar.GetSuffix(seer, GameStates.IsMeeting)); } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 71eab4187..c2ba3dfbf 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3100,7 +3100,7 @@ "LobbyCloseTimer": "Lobby Closing Timer: {0}", "HorseMode": "Horse Mode", "LongMode": "Long Mode", - "CustomTeamHelp": "You are in the {0}!{1}\\nWork with your team members to win together!", + "CustomTeamHelp": "You are in the {0}! ({1})\\nWork with your team members to win together!", "NoPetActionMsg1": "You just ate a cookie", "NoPetActionMsg2": "You look around", "NoPetActionMsg3": "You start wondering whether you are sus or not", @@ -3809,6 +3809,7 @@ "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.", + "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.", diff --git a/Roles/AddOns/Common/Lovers.cs b/Roles/AddOns/Common/Lovers.cs index 9f2c7ff7a..b13950645 100644 --- a/Roles/AddOns/Common/Lovers.cs +++ b/Roles/AddOns/Common/Lovers.cs @@ -1,4 +1,6 @@ -using static EHR.Options; +using System; +using System.Linq; +using static EHR.Options; namespace EHR.Roles.AddOns.Common { @@ -32,6 +34,8 @@ namespace EHR.Roles.AddOns.Common "LovingImpostor" ]; + public static CustomRoles LovingImpostorRole; + public AddonTypes Type => AddonTypes.Mixed; public void SetupCustomOption() @@ -103,5 +107,10 @@ namespace EHR.Roles.AddOns.Common CustomAdtRoleSpawnRate.Add(role, rateOption); } + + public static void Init() + { + LovingImpostorRole = Enum.GetValues().Where(x => x.IsEnable() && x.IsImpostor() && x != CustomRoles.LovingImpostor && !x.RoleExist(countDead: true) && !HnSManager.AllHnSRoles.Contains(x)).Shuffle()[0]; + } } } \ No newline at end of file diff --git a/Roles/AddOns/GhostRoles/GA.cs b/Roles/AddOns/GhostRoles/GA.cs index c9e662ff5..5ca193df7 100644 --- a/Roles/AddOns/GhostRoles/GA.cs +++ b/Roles/AddOns/GhostRoles/GA.cs @@ -2,7 +2,7 @@ namespace EHR.Roles.AddOns.GhostRoles { - public class GA : IGhostRole, ISettingHolder + internal class GA : IGhostRole, ISettingHolder { private static OptionItem ProtectDuration; private static OptionItem CD; diff --git a/Roles/AddOns/GhostRoles/Haunter.cs b/Roles/AddOns/GhostRoles/Haunter.cs index 6b1b6f488..fb2e05f57 100644 --- a/Roles/AddOns/GhostRoles/Haunter.cs +++ b/Roles/AddOns/GhostRoles/Haunter.cs @@ -14,6 +14,7 @@ namespace EHR.Roles.AddOns.GhostRoles private static OptionItem RevealNeutralKillers; private static OptionItem RevealMadmates; private static OptionItem NumberOfReveals; + private byte HaunterId; private List WarnedImps = []; @@ -27,6 +28,7 @@ namespace EHR.Roles.AddOns.GhostRoles public void OnAssign(PlayerControl pc) { + HaunterId = pc.PlayerId; LateTask.New(() => { var taskState = pc.GetTaskState(); @@ -123,5 +125,17 @@ namespace EHR.Roles.AddOns.GhostRoles pc.Notify(Translator.GetString("HaunterStoppedSelf"), 7f); } } + + public static string GetSuffix(PlayerControl seer) + { + foreach (var role in GhostRolesManager.AssignedGhostRoles.Values) + { + if (role.Instance is not Haunter haunter) continue; + if (!haunter.WarnedImps.Contains(seer.PlayerId)) continue; + return TargetArrow.GetArrows(seer, haunter.HaunterId); + } + + return string.Empty; + } } } \ No newline at end of file diff --git a/Roles/Crewmate/NiceSwapper.cs b/Roles/Crewmate/NiceSwapper.cs index 10f53560e..f98af07a1 100644 --- a/Roles/Crewmate/NiceSwapper.cs +++ b/Roles/Crewmate/NiceSwapper.cs @@ -175,14 +175,14 @@ public class NiceSwapper : RoleBase if (x.TargetPlayerId.IsHost()) MeetingHud.Instance.CmdCastVote(x.TargetPlayerId, SwapTargets.Item2); else MeetingHud.Instance.CastVote(x.TargetPlayerId, SwapTargets.Item2); x.VotedFor = SwapTargets.Item2; - }); + }, fast: true); playerStates.DoIf(votedFor2.Contains, x => { x.UnsetVote(); if (x.TargetPlayerId.IsHost()) MeetingHud.Instance.CmdCastVote(x.TargetPlayerId, SwapTargets.Item1); else MeetingHud.Instance.CastVote(x.TargetPlayerId, SwapTargets.Item1); x.VotedFor = SwapTargets.Item1; - }); + }, fast: true); PlayerControl Target1 = Utils.GetPlayerById(SwapTargets.Item1); PlayerControl Target2 = Utils.GetPlayerById(SwapTargets.Item2); diff --git a/Roles/Crewmate/ToiletMaster.cs b/Roles/Crewmate/ToiletMaster.cs index d20fbc889..7449ebf2d 100644 --- a/Roles/Crewmate/ToiletMaster.cs +++ b/Roles/Crewmate/ToiletMaster.cs @@ -141,11 +141,11 @@ namespace EHR.Crewmate break; case Poop.Red: var defaultSpeed = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); - ((List)activePoop.Data).DoIf(x => x, x => + ((List)activePoop.Data).DoIf(x => x != null, x => { Main.AllPlayerSpeed[x.PlayerId] = defaultSpeed; x.MarkDirtySettings(); - }); + }, fast: true); break; } } diff --git a/Roles/Impostor/Blackmailer.cs b/Roles/Impostor/Blackmailer.cs index 724753f17..b4a9340ce 100644 --- a/Roles/Impostor/Blackmailer.cs +++ b/Roles/Impostor/Blackmailer.cs @@ -53,7 +53,7 @@ namespace EHR.Roles.Impostor if (x.TargetPlayerId.IsHost()) MeetingHud.Instance.CmdCastVote(x.TargetPlayerId, bmVotedFor); else MeetingHud.Instance.CastVote(x.TargetPlayerId, bmVotedFor); x.VotedFor = bmVotedFor; - }); + }, fast: true); } } } \ No newline at end of file diff --git a/Roles/Impostor/Echo.cs b/Roles/Impostor/Echo.cs index e5804bca5..e2f0ef51f 100644 --- a/Roles/Impostor/Echo.cs +++ b/Roles/Impostor/Echo.cs @@ -1,11 +1,16 @@ -namespace EHR.Roles.Impostor +using System.Collections.Generic; + +namespace EHR.Roles.Impostor { public class Echo : RoleBase { public static bool On; + public static List Instances = []; private static OptionItem ShapeshiftCooldown; private static OptionItem ShapeshiftDuration; + + public PlayerControl EchoPC; public override bool IsEnable => On; public static void SetupCustomOption() @@ -23,14 +28,17 @@ .SetValueFormat(OptionFormat.Seconds); } + public override void Init() + { + Instances = []; + On = false; + } + public override void Add(byte playerId) { On = true; - } - - public override void Init() - { - On = false; + EchoPC = Utils.GetPlayerById(playerId); + Instances.Add(this); } public override void SetButtonTexts(HudManager hud, byte id) @@ -57,14 +65,40 @@ { target = Utils.GetPlayerById(shapeshifter.shapeshiftTargetPlayerId); if (target == null) return true; - target.RpcShapeshift(target, false); - var pos = shapeshifter.Pos(); - shapeshifter.TP(target); - target.TP(pos); + RevertSwap(shapeshifter, target); LateTask.New(() => target.Suicide(PlayerState.DeathReason.Kill, shapeshifter), 0.2f, "Echo Unshift Kill"); } return true; } + + private static void RevertSwap(PlayerControl echo, PlayerControl target) + { + target.RpcShapeshift(target, false); + var pos = echo.Pos(); + echo.TP(target); + target.TP(pos); + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (!target.IsShifted()) return true; + var ssTarget = Utils.GetPlayerById(target.shapeshiftTargetPlayerId); + if (ssTarget == null || !killer.RpcCheckAndMurder(ssTarget, check: true)) return true; + + RevertSwap(target, ssTarget); + LateTask.New(() => killer.Kill(ssTarget), 0.2f, log: false); + return false; + } + + public void OnTargetCheckMurder(PlayerControl killer, PlayerControl target) + { + RevertSwap(EchoPC, target); + LateTask.New(() => + { + killer.RpcCheckAndMurder(EchoPC); + target.Suicide(PlayerState.DeathReason.Kill, EchoPC); + }, 0.2f, log: false); + } } } \ No newline at end of file diff --git a/Roles/Impostor/Witch.cs b/Roles/Impostor/Witch.cs index a85df2b83..3b1ab48bd 100644 --- a/Roles/Impostor/Witch.cs +++ b/Roles/Impostor/Witch.cs @@ -231,7 +231,7 @@ public class Witch : RoleBase public override string GetSuffix(PlayerControl witch, PlayerControl target, bool hud = false, bool isMeeting = false) { - if (witch == null || isMeeting || witch.PlayerId != target.PlayerId) return string.Empty; + if (witch == null || isMeeting || witch.PlayerId != target.PlayerId || !witch.Is(CustomRoles.Witch)) return string.Empty; var str = new StringBuilder(); if (hud) diff --git a/Roles/Neutral/Tremor.cs b/Roles/Neutral/Tremor.cs index 803ef7198..5632d415a 100644 --- a/Roles/Neutral/Tremor.cs +++ b/Roles/Neutral/Tremor.cs @@ -18,11 +18,10 @@ public class Tremor : RoleBase private static OptionItem TimerStart; private static OptionItem TimerDecrease; private static OptionItem DoomTime; + int Count; int DoomTimer; - long LastUpdate = Utils.TimeStamp; - int Timer; public override bool IsEnable => On; @@ -71,7 +70,7 @@ public class Tremor : RoleBase public override void OnFixedUpdate(PlayerControl pc) { - if (!GameStates.IsInTask || !pc.IsAlive()) return; + if (!GameStates.IsInTask || ExileController.Instance || !pc.IsAlive()) return; bool wasDoom = IsDoom;