From e329f5e6e0ddb9ce27fae03c8d4eee70a5ef2019 Mon Sep 17 00:00:00 2001 From: Gurge44 <90392754+Gurge44@users.noreply.github.com> Date: Mon, 14 Aug 2023 18:06:55 +0200 Subject: [PATCH] Add files via upload --- Patches/AirShipElectricalDoors.cs | 61 + Patches/AirshipStatus.cs | 13 + Patches/AnnouncementPatch.cs | 126 + Patches/ChatBubblePatch.cs | 21 + Patches/ChatCommandPatch.cs | 977 ++++++++ Patches/ChatControlPatch.cs | 41 + Patches/CheckGameEndPatch.cs | 737 ++++++ Patches/ClientOptionsPatch.cs | 99 + Patches/ClientPatch.cs | 131 ++ Patches/ControlPatch.cs | 340 +++ Patches/CredentialsPatch.cs | 226 ++ Patches/DisconnectPatch.cs | 73 + Patches/ExilePatch.cs | 209 ++ Patches/GameManagerPatch.cs | 43 + Patches/GameOptionsMenuPatch.cs | 372 +++ Patches/GameOptionsPatch.cs | 62 + Patches/GameStartManagerPatch.cs | 282 +++ Patches/GuardAngelPatch.cs | 17 + Patches/HashRandomPatch.cs | 36 + Patches/HideBanButtonPatch.cs | 16 + Patches/HudPatch.cs | 710 ++++++ Patches/HudSpritePatch.cs | 128 ++ Patches/IntroPatch.cs | 382 ++++ Patches/JoinGameButtonPatch.cs | 19 + Patches/LadderPatch.cs | 70 + Patches/LobbyListPatch.cs | 68 + Patches/LobbyPatch.cs | 25 + Patches/LogoAndStampPatch.cs | 154 ++ Patches/MainMenuManagerPatch.cs | 175 ++ Patches/MeetingHudPatch.cs | 1133 +++++++++ Patches/MovingPlatformBehaviourPatch.cs | 37 + Patches/NoBlackoutPatch.cs | 12 + Patches/OutroPatch.cs | 272 +++ Patches/PlayerContorolPatch.cs | 2788 +++++++++++++++++++++++ Patches/PlayerJoinAndLeftPatch.cs | 288 +++ Patches/RandomSpawnPatch.cs | 167 ++ Patches/RecomputeTaskPatch.cs | 40 + Patches/SabotageSystemPatch.cs | 55 + Patches/ServerVersionPatch.cs | 22 + Patches/ShipStatusPatch.cs | 227 ++ Patches/TOHEOnlySearch.cs | 50 + Patches/TaskAdderPatch.cs | 103 + Patches/TaskAssignPatch.cs | 216 ++ Patches/UsablesPatch.cs | 95 + Patches/onGameStartedPatch.cs | 1010 ++++++++ 45 files changed, 12128 insertions(+) create mode 100644 Patches/AirShipElectricalDoors.cs create mode 100644 Patches/AirshipStatus.cs create mode 100644 Patches/AnnouncementPatch.cs create mode 100644 Patches/ChatBubblePatch.cs create mode 100644 Patches/ChatCommandPatch.cs create mode 100644 Patches/ChatControlPatch.cs create mode 100644 Patches/CheckGameEndPatch.cs create mode 100644 Patches/ClientOptionsPatch.cs create mode 100644 Patches/ClientPatch.cs create mode 100644 Patches/ControlPatch.cs create mode 100644 Patches/CredentialsPatch.cs create mode 100644 Patches/DisconnectPatch.cs create mode 100644 Patches/ExilePatch.cs create mode 100644 Patches/GameManagerPatch.cs create mode 100644 Patches/GameOptionsMenuPatch.cs create mode 100644 Patches/GameOptionsPatch.cs create mode 100644 Patches/GameStartManagerPatch.cs create mode 100644 Patches/GuardAngelPatch.cs create mode 100644 Patches/HashRandomPatch.cs create mode 100644 Patches/HideBanButtonPatch.cs create mode 100644 Patches/HudPatch.cs create mode 100644 Patches/HudSpritePatch.cs create mode 100644 Patches/IntroPatch.cs create mode 100644 Patches/JoinGameButtonPatch.cs create mode 100644 Patches/LadderPatch.cs create mode 100644 Patches/LobbyListPatch.cs create mode 100644 Patches/LobbyPatch.cs create mode 100644 Patches/LogoAndStampPatch.cs create mode 100644 Patches/MainMenuManagerPatch.cs create mode 100644 Patches/MeetingHudPatch.cs create mode 100644 Patches/MovingPlatformBehaviourPatch.cs create mode 100644 Patches/NoBlackoutPatch.cs create mode 100644 Patches/OutroPatch.cs create mode 100644 Patches/PlayerContorolPatch.cs create mode 100644 Patches/PlayerJoinAndLeftPatch.cs create mode 100644 Patches/RandomSpawnPatch.cs create mode 100644 Patches/RecomputeTaskPatch.cs create mode 100644 Patches/SabotageSystemPatch.cs create mode 100644 Patches/ServerVersionPatch.cs create mode 100644 Patches/ShipStatusPatch.cs create mode 100644 Patches/TOHEOnlySearch.cs create mode 100644 Patches/TaskAdderPatch.cs create mode 100644 Patches/TaskAssignPatch.cs create mode 100644 Patches/UsablesPatch.cs create mode 100644 Patches/onGameStartedPatch.cs diff --git a/Patches/AirShipElectricalDoors.cs b/Patches/AirShipElectricalDoors.cs new file mode 100644 index 000000000..74132986f --- /dev/null +++ b/Patches/AirShipElectricalDoors.cs @@ -0,0 +1,61 @@ +using HarmonyLib; +using System.Collections.Generic; + +namespace TOHE; + +public class AirshipElectricalDoors +{ + private static ElectricalDoors Instance + => ShipStatus.Instance.Systems[SystemTypes.Decontamination].Cast(); + + public static void Initialize() + { + if (Main.NormalOptions.MapId != 4) return; + Instance.Initialize(); + } + public static byte[] GetClosedDoors() + { + List DoorsArray = new(); + if (Instance.Doors == null || Instance.Doors.Count == 0) return DoorsArray.ToArray(); + for (byte i = 0; i < Instance.Doors.Count; i++) + { + var door = Instance.Doors[i]; + if (door != null && !door.IsOpen) + DoorsArray.Add(i); + } + return DoorsArray?.ToArray(); + } + // 0: BottomRightHort + // 1: BottomHort + // 2: TopRightHort + // 3: TopCenterHort + // 4: TopLeftHort + // 5: LeftVert + // 6: RightVert + // 7: TopRightVert + // 8: TopLeftVert + // 9: BottomRightVert + // 10: LeftDoorTop + // 11: LeftDoorBottom +} +[HarmonyPatch(typeof(ElectricalDoors), nameof(ElectricalDoors.Initialize))] +class ElectricalDoorsInitializePatch +{ + public static void Postfix(ElectricalDoors __instance) + { + if (!GameStates.IsInGame) return; + var closedoors = ""; + bool isFirst = true; + foreach (var num in AirshipElectricalDoors.GetClosedDoors()) + { + if (isFirst) + { + isFirst = false; + closedoors += num.ToString(); + } + else + closedoors += $", {num}"; + } + Logger.Info($"ClosedDoors:{closedoors}", "ElectricalDoors Initialize"); + } +} \ No newline at end of file diff --git a/Patches/AirshipStatus.cs b/Patches/AirshipStatus.cs new file mode 100644 index 000000000..195d5da52 --- /dev/null +++ b/Patches/AirshipStatus.cs @@ -0,0 +1,13 @@ +using HarmonyLib; + +namespace TOHE; + +//参考元:https://github.com/yukieiji/ExtremeRoles/blob/master/ExtremeRoles/Patches/AirshipStatusPatch.cs +[HarmonyPatch(typeof(AirshipStatus), nameof(AirshipStatus.PrespawnStep))] +public static class AirshipStatusPrespawnStepPatch +{ + public static bool Prefix() + { + return !PlayerControl.LocalPlayer.Is(CustomRoles.GM); // GMは湧き画面をスキップ + } +} \ No newline at end of file diff --git a/Patches/AnnouncementPatch.cs b/Patches/AnnouncementPatch.cs new file mode 100644 index 000000000..2539bef0b --- /dev/null +++ b/Patches/AnnouncementPatch.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AmongUs.Data; +using AmongUs.Data.Player; +using Assets.InnerNet; +using HarmonyLib; +using Il2CppInterop.Runtime.InteropTypes.Arrays; + +namespace TOHE; + +// ##https://github.com/Yumenopai/TownOfHost_Y +public class ModNews +{ + public int Number; + public int BeforeNumber; + public string Title; + public string SubTitle; + public string ShortTitle; + public string Text; + public string Date; + + public Announcement ToAnnouncement() + { + var result = new Announcement + { + Number = Number, + Title = Title, + SubTitle = SubTitle, + ShortTitle = ShortTitle, + Text = Text, + Language = (uint)DataManager.Settings.Language.CurrentLanguage, + Date = Date, + Id = "ModNews" + }; + + return result; + } +} +[HarmonyPatch] +public class ModNewsHistory +{ + public static List AllModNews = new(); + + // + public static void Init() + { + // When creating new news, you can not delete old news + { + // TOHE v2.4.3 + var news = new ModNews + { + Number = 100001, + Title = "TownOfHostEdited v2.5.0", + SubTitle = "★★★★Another big update, maybe bigger?★★★★", + ShortTitle = "★TOHE v2.5.0", + Text = "Welcome to TOHE v2.5.0.\n\nSupport for Among Us v2023.7.11 and v2023.7.12\n" + + + "\n【Base】\n - Base on TOH v4.1.2\r\n" + + "\n【Fixes】\n - Various bug fixes\n\r" + + "\n【Changes】\n - Hex Master hex icon changed to separate it from Spellcaster\n - Fortune Teller moved to Experimentals due to a planned and unfinished rework\n\r" + + + "\n【New Features】\n - New role: Twister (role by papercut on Discord)\n\r - New role: Chameleon (from Project: Lotus)\n\r - New role: Morphling\n\r - New role: Inspector (role by ryuk on Discord)\n\r - New role: Medusa\n\r - New add-on: Lazy\n\r - New add-on: Gravestone\n\r - New add-on: Autopsy (from TOHY)\n\r - New add-on: Loyal\n\r - New add-on: Visionary\n\r- New experimental role: Spiritcaller (role by papercut on Discord)\n\r" + + + "\n【Role Changes】\n - Various changes were made, such as an update to Opportunist\n\r", + + Date = "2023-7-14T00:00:00Z" + + }; + AllModNews.Add(news); + } + + { + // TOHE v2.4.2 + var news = new ModNews + { + Number = 100000, + Title = "TownOfHostEdited v2.4.2", + SubTitle = "★★★★Ooooh bigger update★★★★", + ShortTitle = "★TOHE v2.4.2", + Text = "Added in some new stuff, along with some bug fixes.\r\nAmong Us v2023.3.28 is recommended so the roles work correctly.\n" + + + "\n【Base】\n - Base on TOH v4.1.2\r\n" + + "\n【Fixes】\n - Fixed various black screen bugs (some still exist but should be less common)\r\n - Other various bug fixes (they're hard to keep track of)\r\n" + + "\n【Changes】\n - Judge now supports Guesser Mode\r\n - Background image reverted to use the AU v2023.3.28 size due to the recommended Among Us version being v2023.3.28\r\n - Many other unlisted changes\r\n - Mario renamed to Vector due to copyright concerns\r\n" + + + "\n【New Features】\n - ###Impostors\n - Councillor\r\n - Deathpact (role by papercut on Discord)\r\n - Saboteur (25% chance to replace Inhibitor)\r\n - Consigliere (by Yumeno from TOHY)\r\n - Dazzler (role by papercut on Discord)\r\n - Devourer (role by papercut on Discord)\r\n" + + "\n ### Crewmates\n - Addict (role by papercut on Discord)\r\n - Tracefinder\r\n - Deputy\r\n - Merchant (role by papercut on Discord)\r\n - Oracle\r\n - Spiritualist (role by papercut on Discord)\r\n - Retributionist\r\n- Guardian\r\n - Monarch\r\n" + + "\n ### Neutrals\n - Maverick\r\n - Cursed Soul\r\n - Vulture (role by ryuk on Discord)\r\n - Jinx\r\n - Pickpocket\r\n - Ritualist\r\n - Traitor\r\n" + + "\n ### Add-ons\n - Double Shot (add-on by TommyXL)\r\n - Rascal\r\n" + + + "\n【Role Changes】\n - Mimic now has a setting to see the roles of dead players, due to how useless this add-on was\r\n - A revealed Workaholic can no longer be guessed\r\n - Doctor has a new setting like Workaholic to be revealed to all (currently exposes evil Doctors, use at your own risk)\r\n - Mayor has a setting for a TOS mechanic to reveal themselves\r\n - Warlock balancing\r\n - Cleaner balancing (resets kill cooldown to value set in Cleaner settings)\r\n - Updated Monarch\r\n- Removed speed boost from Mare\r\n" + + "\n【Removals】\n - Removed Flash\r\n - Removed Speed Booster\r\n - Temporarily removed Oblivious", + + Date = "2023-7-5T00:00:00Z" + + }; + AllModNews.Add(news); + } + } + + [HarmonyPatch(typeof(PlayerAnnouncementData), nameof(PlayerAnnouncementData.SetAnnouncements)), HarmonyPrefix] + public static bool SetModAnnouncements(PlayerAnnouncementData __instance, [HarmonyArgument(0)] ref Il2CppReferenceArray aRange) + { + if (AllModNews.Count < 1) + { + Init(); + AllModNews.Sort((a1, a2) => { return DateTime.Compare(DateTime.Parse(a2.Date), DateTime.Parse(a1.Date)); }); + } + + List FinalAllNews = new(); + AllModNews.Do(n => FinalAllNews.Add(n.ToAnnouncement())); + foreach (var news in aRange) + { + if (!AllModNews.Any(x => x.Number == news.Number)) + FinalAllNews.Add(news); + } + FinalAllNews.Sort((a1, a2) => { return DateTime.Compare(DateTime.Parse(a2.Date), DateTime.Parse(a1.Date)); }); + + aRange = new(FinalAllNews.Count); + for (int i = 0; i < FinalAllNews.Count; i++) + aRange[i] = FinalAllNews[i]; + + return true; + } +} \ No newline at end of file diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs new file mode 100644 index 000000000..6980e677f --- /dev/null +++ b/Patches/ChatBubblePatch.cs @@ -0,0 +1,21 @@ +using HarmonyLib; + +namespace TOHE.Patches; + +[HarmonyPatch(typeof(ChatBubble), nameof(ChatBubble.SetRight))] +class ChatBubbleSetRightPatch +{ + public static void Postfix(ChatBubble __instance) + { + if (Main.isChatCommand) __instance.SetLeft(); + } +} +[HarmonyPatch(typeof(ChatBubble), nameof(ChatBubble.SetName))] +class ChatBubbleSetNamePatch +{ + public static void Postfix(ChatBubble __instance) + { + if (GameStates.IsInGame && __instance.playerInfo.PlayerId == PlayerControl.LocalPlayer.PlayerId) + __instance.NameText.color = PlayerControl.LocalPlayer.GetRoleColor(); + } +} \ No newline at end of file diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs new file mode 100644 index 000000000..33ed44575 --- /dev/null +++ b/Patches/ChatCommandPatch.cs @@ -0,0 +1,977 @@ +using Assets.CoreScripts; +using HarmonyLib; +using Hazel; +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using TOHE.Modules; +using TOHE.Roles.Crewmate; +using UnityEngine; +using static TOHE.Translator; +using TOHE.Roles.Impostor; +using TOHE.Roles.Neutral; + + +namespace TOHE; + +[HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))] +internal class ChatCommands +{ + // Function to check if a player is a moderator + private static bool IsPlayerModerator(string friendCode) + { + var friendCodesFilePath = @"./TOHE_DATA/Moderators.txt"; + var friendCodes = File.ReadAllLines(friendCodesFilePath); + return friendCodes.Contains(friendCode); + } + + public static List ChatHistory = new(); + + public static bool Prefix(ChatController __instance) + { + if (__instance.freeChatField.textArea.text == "") return false; + __instance.timeSinceLastMessage = 3f; + var text = __instance.freeChatField.textArea.text; + if (ChatHistory.Count == 0 || ChatHistory[^1] != text) ChatHistory.Add(text); + ChatControllerUpdatePatch.CurrentHistorySelection = ChatHistory.Count; + string[] args = text.Split(' '); + string subArgs = ""; + var canceled = false; + var cancelVal = ""; + Main.isChatCommand = true; + Logger.Info(text, "SendChat"); + if (text.Length >= 3) if (text[..2] == "/r" && text[..3] != "/rn") args[0] = "/r"; + if (text.Length >= 4) if (text[..3] == "/up") args[0] = "/up"; + if (GuessManager.GuesserMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Judge.TrialMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (ParityCop.ParityCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Pirate.DuelCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Councillor.MurderMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Mediumshiper.MsMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (MafiaRevengeManager.MafiaMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; + if (RetributionistRevengeManager.RetributionistMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; + switch (args[0]) + { + case "/dump": + canceled = true; + Utils.DumpLog(); + break; + case "/v": + case "/version": + canceled = true; + string version_text = ""; + foreach (var kvp in Main.playerVersion.OrderBy(pair => pair.Key)) + { + version_text += $"{kvp.Key}:{Main.AllPlayerNames[kvp.Key]}:{kvp.Value.forkId}/{kvp.Value.version}({kvp.Value.tag})\n"; + } + if (version_text != "") HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + version_text); + break; + default: + Main.isChatCommand = false; + break; + } + if (AmongUsClient.Instance.AmHost) + { + Main.isChatCommand = true; + switch (args[0]) + { + case "/win": + case "/winner": + canceled = true; + if (Main.winnerNameList.Count < 1) Utils.SendMessage(GetString("NoInfoExists")); + else Utils.SendMessage("Winner: " + string.Join(",", Main.winnerNameList)); + break; + + case "/l": + case "/lastresult": + canceled = true; + Utils.ShowKillLog(); + Utils.ShowLastRoles(); + Utils.ShowLastResult(); + break; + + case "/rn": + case "/rename": + canceled = true; + if (args.Length < 1) break; + if (args[1].Length is > 10 or < 1) + Utils.SendMessage(GetString("Message.AllowNameLength"), PlayerControl.LocalPlayer.PlayerId); + else Main.nickName = args[1]; + break; + + case "/hn": + case "/hidename": + canceled = true; + Main.HideName.Value = args.Length > 1 ? args.Skip(1).Join(delimiter: " ") : Main.HideName.DefaultValue.ToString(); + GameStartManagerPatch.GameStartManagerStartPatch.HideName.text = + ColorUtility.TryParseHtmlString(Main.HideColor.Value, out _) + ? $"{Main.HideName.Value}" + : $"{Main.HideName.Value}"; + break; + + case "/level": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + Utils.SendMessage(string.Format(GetString("Message.SetLevel"), subArgs), PlayerControl.LocalPlayer.PlayerId); + int.TryParse(subArgs, out int input); + if (input is < 1 or > 999) + { + Utils.SendMessage(GetString("Message.AllowLevelRange"), PlayerControl.LocalPlayer.PlayerId); + break; + } + var number = Convert.ToUInt32(input); + PlayerControl.LocalPlayer.RpcSetLevel(number - 1); + break; + + case "/n": + case "/now": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + switch (subArgs) + { + case "r": + case "roles": + Utils.ShowActiveRoles(); + break; + case "a": + case "all": + Utils.ShowAllActiveSettings(); + break; + default: + Utils.ShowActiveSettings(); + break; + } + break; + + case "/dis": + case "/disconnect": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + switch (subArgs) + { + case "crew": + GameManager.Instance.enabled = false; + GameManager.Instance.RpcEndGame(GameOverReason.HumansDisconnect, false); + break; + + case "imp": + GameManager.Instance.enabled = false; + GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); + break; + + default: + __instance.AddChat(PlayerControl.LocalPlayer, "crew | imp"); + cancelVal = "/dis"; + break; + } + ShipStatus.Instance.RpcRepairSystem(SystemTypes.Admin, 0); + break; + + case "/r": + canceled = true; + subArgs = text.Remove(0, 2); + SendRolesInfo(subArgs, 255, PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug); + break; + + case "/up": + canceled = true; + subArgs = text.Remove(0, 3); + if (!PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp) break; + if (!Options.EnableUpMode.GetBool()) + { + Utils.SendMessage(string.Format(GetString("Message.YTPlanDisabled"), GetString("EnableYTPlan"))); + break; + } + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby")); + break; + } + SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId, isUp: true); + break; + + case "/h": + case "/help": + canceled = true; + Utils.ShowHelp(PlayerControl.LocalPlayer.PlayerId); + break; + + case "/m": + case "/myrole": + canceled = true; + var role = PlayerControl.LocalPlayer.GetCustomRole(); + if (GameStates.IsInGame) + { + var lp = PlayerControl.LocalPlayer; + var sb = new StringBuilder(); + sb.Append(GetString(role.ToString()) + Utils.GetRoleMode(role) + lp.GetRoleInfo(true)); + if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) + Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref sb, command: true); + var txt = sb.ToString(); + sb.Clear().Append(txt.RemoveHtmlTags()); + foreach (var subRole in Main.PlayerStates[lp.PlayerId].SubRoles) + sb.Append($"\n\n" + GetString($"{subRole}") + Utils.GetRoleMode(subRole) + GetString($"{subRole}InfoLong")); + if (CustomRolesHelper.RoleExist(CustomRoles.Ntr) && (role is not CustomRoles.GM and not CustomRoles.Ntr)) + sb.Append($"\n\n" + GetString($"Lovers") + Utils.GetRoleMode(CustomRoles.Lovers) + GetString($"LoversInfoLong")); + Utils.SendMessage(sb.ToString(), lp.PlayerId); + } + else + Utils.SendMessage((PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/t": + case "/template": + canceled = true; + if (args.Length > 1) TemplateManager.SendTemplate(args[1]); + else HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{GetString("ForExample")}:\n{args[0]} test"); + break; + + case "/mw": + case "/messagewait": + canceled = true; + if (args.Length > 1 && int.TryParse(args[1], out int sec)) + { + Main.MessageWait.Value = sec; + Utils.SendMessage(string.Format(GetString("Message.SetToSeconds"), sec), 0); + } + else Utils.SendMessage($"{GetString("Message.MessageWaitHelp")}\n{GetString("ForExample")}:\n{args[0]} 3", 0); + break; + + case "/say": + case "/s": + canceled = true; + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromTheHost")}"); + break; + + case "/kick": + canceled = true; + // Check if the kick command is enabled in the settings + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("KickCommandDisabled"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + // Check if the player has the necessary privileges to use the command + if (!IsPlayerModerator(PlayerControl.LocalPlayer.FriendCode)) + { + Utils.SendMessage(GetString("KickCommandNoAccess"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte kickPlayerId)) + { + Utils.SendMessage(GetString("KickCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (kickPlayerId == 0) + { + Utils.SendMessage(GetString("KickCommandKickHost"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + var kickedPlayer = Utils.GetPlayerById(kickPlayerId); + if (kickedPlayer == null) + { + Utils.SendMessage(GetString("KickCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + // Prevent moderators from kicking other moderators + if (IsPlayerModerator(kickedPlayer.FriendCode)) + { + Utils.SendMessage(GetString("KickCommandKickMod"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + // Kick the specified player + AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), true); + string kickedPlayerName = kickedPlayer.GetRealName(); + string textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")}"; + if (GameStates.IsInGame) + { + textToSend += $"{GetString("KickCommandKickedRole")} {GetString(kickedPlayer.GetCustomRole().ToString())}"; + } + Utils.SendMessage(textToSend); + break; + case "/exe": + canceled = true; + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (args.Length < 2 || !int.TryParse(args[1], out int id)) break; + var player = Utils.GetPlayerById(id); + if (player != null) + { + player.Data.IsDead = true; + Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.etc; + player.RpcExileV2(); + Main.PlayerStates[player.PlayerId].SetDead(); + if (player.AmOwner) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); + else Utils.SendMessage(string.Format(GetString("Message.Executed"), player.Data.PlayerName)); + } + break; + + case "/kill": + canceled = true; + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (args.Length < 2 || !int.TryParse(args[1], out int id2)) break; + var target = Utils.GetPlayerById(id2); + if (target != null) + { + target.RpcMurderPlayerV3(target); + if (target.AmOwner) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); + else Utils.SendMessage(string.Format(GetString("Message.Executed"), target.Data.PlayerName)); + } + break; + + case "/colour": + case "/color": + canceled = true; + if (GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + subArgs = args.Length < 2 ? "" : args[1]; + var color = Utils.MsgToColor(subArgs, true); + if (color == byte.MaxValue) + { + Utils.SendMessage(GetString("IllegalColor"), PlayerControl.LocalPlayer.PlayerId); + break; + } + PlayerControl.LocalPlayer.RpcSetColor(color); + Utils.SendMessage(string.Format(GetString("Message.SetColor"), subArgs), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/quit": + case "/qt": + canceled = true; + Utils.SendMessage(GetString("Message.CanNotUseByHost"), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/xf": + canceled = true; + if (!GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + foreach (var pc in Main.AllPlayerControls) + { + pc.RpcSetNameEx(pc.GetRealName(isMeeting: true)); + } + ChatUpdatePatch.DoBlockChat = false; + Utils.NotifyRoles(isForMeeting: GameStates.IsMeeting, NoCache: true); + Utils.SendMessage(GetString("Message.TryFixName"), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/id": + canceled = true; + string msgText = GetString("PlayerIdList"); + foreach (var pc in Main.AllPlayerControls) + msgText += "\n" + pc.PlayerId.ToString() + " → " + Main.AllPlayerNames[pc.PlayerId]; + Utils.SendMessage(msgText, PlayerControl.LocalPlayer.PlayerId); + break; + + /* + case "/qq": + canceled = true; + if (Main.newLobby) Cloud.ShareLobby(true); + else Utils.SendMessage("很抱歉,每个房间车队姬只会发一次", PlayerControl.LocalPlayer.PlayerId); + break; + */ + + case "/changerole": + if (!DebugModeManager.AmDebugger) break; + canceled = true; + subArgs = text.Remove(0, 8); + var setRole = FixRoleNameInput(subArgs.Trim()); + foreach (CustomRoles rl in Enum.GetValues(typeof(CustomRoles))) + { + if (rl.IsVanilla()) continue; + var roleName = GetString(rl.ToString()).ToLower().Trim(); + if (setRole.Contains(roleName)) + { + PlayerControl.LocalPlayer.RpcSetRole(rl.GetRoleTypes()); + PlayerControl.LocalPlayer.RpcSetCustomRole(rl); + Utils.NotifyRoles(); + Utils.MarkEveryoneDirtySettings(); + } + } + break; + + case "/end": + canceled = true; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); + GameManager.Instance.LogicFlow.CheckEndCriteria(); + break; + case "/cosid": + canceled = true; + var of = PlayerControl.LocalPlayer.Data.DefaultOutfit; + Logger.Warn($"ColorId: {of.ColorId}", "Get Cos Id"); + Logger.Warn($"PetId: {of.PetId}", "Get Cos Id"); + Logger.Warn($"HatId: {of.HatId}", "Get Cos Id"); + Logger.Warn($"SkinId: {of.SkinId}", "Get Cos Id"); + Logger.Warn($"VisorId: {of.VisorId}", "Get Cos Id"); + Logger.Warn($"NamePlateId: {of.NamePlateId}", "Get Cos Id"); + break; + + case "/mt": + case "/hy": + canceled = true; + if (GameStates.IsMeeting) MeetingHud.Instance.RpcClose(); + else PlayerControl.LocalPlayer.NoCheckStartMeeting(null, true); + break; + + case "/cs": + canceled = true; + subArgs = text.Remove(0, 3); + PlayerControl.LocalPlayer.RPCPlayCustomSound(subArgs.Trim()); + break; + + case "/sd": + canceled = true; + subArgs = text.Remove(0, 3); + if (args.Length < 1 || !int.TryParse(args[1], out int sound1)) break; + RPC.PlaySoundRPC(PlayerControl.LocalPlayer.PlayerId, (Sounds)sound1); + break; + + default: + Main.isChatCommand = false; + break; + } + } + goto Skip; + Canceled: + Main.isChatCommand = false; + canceled = true; + Skip: + if (canceled) + { + + Logger.Info("Command Canceled", "ChatCommand"); + __instance.freeChatField.textArea.Clear(); + __instance.freeChatField.textArea.SetText(cancelVal); + } + return !canceled; + } + + public static string FixRoleNameInput(string text) + { + text = text.Replace("着", "者").Trim().ToLower(); + return text switch + { + "管理員" or "管理" or "gm" => GetString("GM"), + "賞金獵人" or "赏金" => GetString("BountyHunter"), + "自爆兵" or "自爆" => GetString("Bomber"), + "邪惡的追踪者" or "邪恶追踪者" or "追踪" => GetString("EvilTracker"), + "煙花商人" or "烟花" => GetString("FireWorks"), + "夢魘" or "夜魇" => GetString("Mare"), + "詭雷" => GetString("BoobyTrap"), + "黑手黨" or "黑手" => GetString("Mafia"), + "嗜血殺手" or "嗜血" => GetString("SerialKiller"), + "千面鬼" or "千面" => GetString("ShapeMaster"), + "狂妄殺手" or "狂妄" => GetString("Sans"), + "殺戮機器" or "杀戮" or "机器" or "杀戮兵器" => GetString("Minimalism"), + "蝕時者" or "蚀时" or "偷时" => GetString("TimeThief"), + "狙擊手" or "狙击" => GetString("Sniper"), + "傀儡師" or "傀儡" => GetString("Puppeteer"), + "殭屍" or "丧尸" => GetString("Zombie"), + "吸血鬼" or "吸血" => GetString("Vampire"), + "術士" => GetString("Warlock"), + "駭客" or "黑客" => GetString("Hacker"), + "刺客" or "忍者" => GetString("Assassin"), + "礦工" => GetString("Miner"), + "逃逸者" or "逃逸" => GetString("Escapee"), + "女巫" => GetString("Witch"), + "監視者" or "监管" => GetString("AntiAdminer"), + "清道夫" or "清道" => GetString("Scavenger"), + "窺視者" or "窥视" => GetString("Watcher"), + "誘餌" or "大奖" or "头奖" => GetString("Bait"), + "擺爛人" or "摆烂" => GetString("Needy"), + "獨裁者" or "独裁" => GetString("Dictator"), + "法醫" => GetString("Doctor"), + "偵探" => GetString("Detective"), + "幸運兒" or "幸运" => GetString("Luckey"), + "大明星" or "明星" => GetString("SuperStar"), + "網紅" => GetString("CyberStar"), + "俠客" => GetString("SwordsMan"), + "正義賭怪" or "正义的赌怪" or "好赌" or "正义赌" => GetString("NiceGuesser"), + "邪惡賭怪" or "邪恶的赌怪" or "坏赌" or "恶赌" or "邪恶赌" or "赌怪" => GetString("EvilGuesser"), + "市長" or "逝长" => GetString("Mayor"), + "被害妄想症" or "被害妄想" or "被迫害妄想症" or "被害" or "妄想" or "妄想症" => GetString("Paranoia"), + "愚者" or "愚" => GetString("Psychic"), + "修理大师" or "修理" or "维修" => GetString("SabotageMaster"), + "警長" => GetString("Sheriff"), + "告密者" or "告密" => GetString("Snitch"), + "增速者" or "增速" => GetString("SpeedBooster"), + "時間操控者" or "时间操控人" or "时间操控" => GetString("TimeManager"), + "陷阱師" or "陷阱" or "小奖" => GetString("Trapper"), + "傳送師" or "传送" => GetString("Transporter"), + "縱火犯" or "纵火" => GetString("Arsonist"), + "處刑人" or "处刑" => GetString("Executioner"), + "小丑" or "丑皇" => GetString("Jester"), + "投機者" or "投机" => GetString("Opportunist"), + "馬里奧" or "马力欧" => GetString("Mario"), + "恐怖分子" or "恐怖" => GetString("Terrorist"), + "豺狼" or "蓝狼" or "狼" => GetString("Jackal"), + "神" or "上帝" => GetString("God"), + "情人" or "愛人" or "链子" or "老婆" or "老公" => GetString("Lovers"), + "絕境者" or "绝境" => GetString("LastImpostor"), + "閃電俠" or "闪电" => GetString("Flashman"), + "靈媒" => GetString("Seer"), + "破平者" or "破平" => GetString("Brakar"), + "執燈人" or "执灯" or "灯人" => GetString("Lighter"), + "膽小" or "胆小" => GetString("Oblivious"), + "迷惑者" or "迷幻" => GetString("Bewilder"), + "sun" => GetString("Sunglasses"), + "蠢蛋" or "笨蛋" or "蠢狗" or "傻逼" => GetString("Fool"), + "冤罪師" or "冤罪" => GetString("Innocent"), + "資本家" or "资本主义" or "资本" => GetString("Capitalism"), + "老兵" => GetString("Veteran"), + "加班狂" or "加班" => GetString("Workhorse"), + "復仇者" or "复仇" => GetString("Avanger"), + "鵜鶘" => GetString("Pelican"), + "保鏢" => GetString("Bodyguard"), + "up" or "up主" => GetString("Youtuber"), + "利己主義者" or "利己主义" or "利己" => GetString("Egoist"), + "贗品商" or "赝品" => GetString("Counterfeiter"), + "擲雷兵" or "掷雷" or "闪光弹" => GetString("Grenadier"), + "竊票者" or "偷票" or "偷票者" or "窃票师" or "窃票" => GetString("TicketsStealer"), + "教父" => GetString("Gangster"), + "革命家" or "革命" => GetString("Revolutionist"), + "fff團" or "fff" or "fff团" => GetString("FFF"), + "清理工" or "清潔工" or "清洁工" or "清理" or "清洁" => GetString("Cleaner"), + "醫生" => GetString("Medicaler"), + "占卜師" or "占卜" => GetString("Divinator"), + "雙重人格" or "双重" or "双人格" or "人格" => GetString("DualPersonality"), + "玩家" => GetString("Gamer"), + "情報販子" or "情报" or "贩子" => GetString("Messenger"), + "球狀閃電" or "球闪" or "球状" => GetString("BallLightning"), + "潛藏者" or "潜藏" => GetString("DarkHide"), + "貪婪者" or "贪婪" => GetString("Greedier"), + "工作狂" or "工作" => GetString("Workaholic"), + "呪狼" or "咒狼" => GetString("CursedWolf"), + "寶箱怪" or "宝箱" => GetString("Mimic"), + "集票者" or "集票" or "寄票" or "机票" => GetString("Collector"), + "活死人" or "活死" => GetString("Glitch"), + "奪魂者" or "多混" or "夺魂" => GetString("ImperiusCurse"), + "自爆卡車" or "自爆" or "卡车" => GetString("Provocateur"), + "快槍手" or "快枪" => GetString("QuickShooter"), + "隱蔽者" or "隐蔽" or "小黑人" => GetString("Concealer"), + "抹除者" or "抹除" => GetString("Eraser"), + "肢解者" or "肢解" => GetString("OverKiller"), + "劊子手" or "侩子手" or "柜子手" => GetString("Hangman"), + "陽光開朗大男孩" or "阳光" or "开朗" or "大男孩" or "阳光开朗" or "开朗大男孩" or "阳光大男孩" => GetString("Sunnyboy"), + "法官" or "审判" => GetString("Judge"), + "入殮師" or "入检师" or "入殓" => GetString("Mortician"), + "通靈師" or "通灵" => GetString("Mediumshiper"), + "吟游詩人" or "诗人" => GetString("Bard"), + "隱匿者" or "隐匿" or "隐身" or "隐身人" or "印尼" => GetString("Swooper"), + "船鬼" => GetString("Crewpostor"), + "嗜血騎士" or "血骑" or "骑士" or "bk" => GetString("BloodKnight"), + "賭徒" => GetString("Totocalcio"), + "分散机" => GetString("Disperser"), + "和平之鸽" or "和平之鴿" or "和平的鸽子" or "和平" => GetString("DovesOfNeace"), + "持槍" or "持械" or "手长" => GetString("Reach"), + "monarch" => GetString("Monarch"), + _ => text, + }; + } + + public static bool GetRoleByName(string name, out CustomRoles role) + { + role = new(); + if (name == "" || name == string.Empty) return false; + + if ((TranslationController.InstanceExists ? TranslationController.Instance.currentLanguage.languageID : SupportedLangs.SChinese) == SupportedLangs.SChinese) + { + Regex r = new("[\u4e00-\u9fa5]+$"); + MatchCollection mc = r.Matches(name); + string result = string.Empty; + for (int i = 0; i < mc.Count; i++) + { + if (mc[i].ToString() == "是") continue; + result += mc[i]; //匹配结果是完整的数字,此处可以不做拼接的 + } + name = FixRoleNameInput(result.Replace("是", string.Empty).Trim()); + } + else name = name.Trim().ToLower(); + + foreach (CustomRoles rl in Enum.GetValues(typeof(CustomRoles))) + { + if (rl.IsVanilla()) continue; + var roleName = GetString(rl.ToString()).ToLower().Trim(); + if (name.Contains(roleName)) + { + role = rl; + return true; + } + } + return false; + } + public static void SendRolesInfo(string role, byte playerId, bool isDev = false, bool isUp = false) + { + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) + { + Utils.SendMessage(GetString("ModeDescribe.SoloKombat"), playerId); + return; + } + + role = role.Trim().ToLower(); + if (role.StartsWith("/r")) role.Replace("/r", string.Empty); + if (role.StartsWith("/up")) role.Replace("/up", string.Empty); + if (role.EndsWith("\r\n")) role.Replace("\r\n", string.Empty); + if (role.EndsWith("\n")) role.Replace("\n", string.Empty); + + if (role == "" || role == string.Empty) + { + Utils.ShowActiveRoles(playerId); + return; + } + + role = FixRoleNameInput(role).ToLower().Trim().Replace(" ", string.Empty); + + foreach (CustomRoles rl in Enum.GetValues(typeof(CustomRoles))) + { + if (rl.IsVanilla()) continue; + var roleName = GetString(rl.ToString()); + if (role == roleName.ToLower().Trim().TrimStart('*').Replace(" ", string.Empty)) + { + string devMark = ""; + if ((isDev || isUp) && GameStates.IsLobby) + { + devMark = "▲"; + if (CustomRolesHelper.IsAdditionRole(rl) || rl is CustomRoles.GM) devMark = ""; + if (rl.GetCount() < 1 || rl.GetMode() == 0) devMark = ""; + if (isUp) + { + if (devMark == "▲") Utils.SendMessage(string.Format(GetString("Message.YTPlanSelected"), roleName), playerId); + else Utils.SendMessage(string.Format(GetString("Message.YTPlanSelectFailed"), roleName), playerId); + } + if (devMark == "▲") + { + byte pid = playerId == 255 ? (byte)0 : playerId; + Main.DevRole.Remove(pid); + Main.DevRole.Add(pid, rl); + } + if (isUp) return; + } + var sb = new StringBuilder(); + sb.Append(devMark + roleName + Utils.GetRoleMode(rl) + GetString($"{rl}InfoLong")); + if (Options.CustomRoleSpawnChances.ContainsKey(rl)) + { + Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[rl], ref sb, command: true); + var txt = sb.ToString(); + sb.Clear().Append(txt.RemoveHtmlTags()); + } + Utils.SendMessage(sb.ToString(), playerId); + return; + } + } + if (isUp) Utils.SendMessage(GetString("Message.YTPlanCanNotFindRoleThePlayerEnter"), playerId); + else Utils.SendMessage(GetString("Message.CanNotFindRoleThePlayerEnter"), playerId); + return; + } + public static void OnReceiveChat(PlayerControl player, string text, out bool canceled) + { + canceled = false; + if (!AmongUsClient.Instance.AmHost) return; + if (text.StartsWith("\n")) text = text[1..]; + //if (!text.StartsWith("/")) return; + string[] args = text.Split(' '); + string subArgs = ""; + //if (text.Length >= 3) if (text[..2] == "/r" && text[..3] != "/rn") args[0] = "/r"; + // if (SpamManager.CheckSpam(player, text)) return; + if (GuessManager.GuesserMsg(player, text)) { canceled = true; return; } + if (Judge.TrialMsg(player, text)) { canceled = true; return; } + if (ParityCop.ParityCheckMsg(player, text)) { canceled = true; return; } + if (Pirate.DuelCheckMsg(player, text)) { canceled = true; return; } + if (Councillor.MurderMsg(player, text)) { canceled = true; return; } + if (Mediumshiper.MsMsg(player, text)) return; + if (MafiaRevengeManager.MafiaMsgCheck(player, text)) return; + if (RetributionistRevengeManager.RetributionistMsgCheck(player, text)) return; + switch (args[0]) + { + case "/l": + case "/lastresult": + Utils.ShowKillLog(player.PlayerId); + Utils.ShowLastRoles(player.PlayerId); + Utils.ShowLastResult(player.PlayerId); + break; + + case "/n": + case "/now": + subArgs = args.Length < 2 ? "" : args[1]; + switch (subArgs) + { + case "r": + case "roles": + Utils.ShowActiveRoles(player.PlayerId); + break; + case "a": + case "all": + Utils.ShowAllActiveSettings(player.PlayerId); + break; + default: + Utils.ShowActiveSettings(player.PlayerId); + break; + } + break; + + case "/r": + subArgs = text.Remove(0, 2); + SendRolesInfo(subArgs, player.PlayerId, player.FriendCode.GetDevUser().DeBug); + break; + + case "/h": + case "/help": + Utils.ShowHelpToClient(player.PlayerId); + break; + + case "/m": + case "/myrole": + var role = player.GetCustomRole(); + if (GameStates.IsInGame) + { + var sb = new StringBuilder(); + sb.Append(GetString(role.ToString()) + Utils.GetRoleMode(role) + player.GetRoleInfo(true)); + if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) + Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref sb, command: true); + var txt = sb.ToString(); + sb.Clear().Append(txt.RemoveHtmlTags()); + foreach (var subRole in Main.PlayerStates[player.PlayerId].SubRoles) + sb.Append($"\n\n" + GetString($"{subRole}") + Utils.GetRoleMode(subRole) + GetString($"{subRole}InfoLong")); + if (CustomRolesHelper.RoleExist(CustomRoles.Ntr) && (role is not CustomRoles.GM and not CustomRoles.Ntr)) + sb.Append($"\n\n" + GetString($"Lovers") + Utils.GetRoleMode(CustomRoles.Lovers) + GetString($"LoversInfoLong")); + Utils.SendMessage(sb.ToString(), player.PlayerId); + } + else + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + + case "/t": + case "/template": + if (args.Length > 1) TemplateManager.SendTemplate(args[1], player.PlayerId); + else Utils.SendMessage($"{GetString("ForExample")}:\n{args[0]} test", player.PlayerId); + break; + + case "/colour": + case "/color": + if (Options.PlayerCanSetColor.GetBool() || player.FriendCode.GetDevUser().IsDev || player.FriendCode.GetDevUser().ColorCmd) + { + if (GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); + break; + } + subArgs = args.Length < 2 ? "" : args[1]; + var color = Utils.MsgToColor(subArgs); + if (color == byte.MaxValue) + { + Utils.SendMessage(GetString("IllegalColor"), player.PlayerId); + break; + } + player.RpcSetColor(color); + Utils.SendMessage(string.Format(GetString("Message.SetColor"), subArgs), player.PlayerId); + } + else + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + } + break; + + case "/quit": + case "/qt": + subArgs = args.Length < 2 ? "" : args[1]; + var cid = player.PlayerId.ToString(); + cid = cid.Length != 1 ? cid.Substring(1, 1) : cid; + if (subArgs.Equals(cid)) + { + string name = player.GetRealName(); + Utils.SendMessage(string.Format(GetString("Message.PlayerQuitForever"), name)); + AmongUsClient.Instance.KickPlayer(player.GetClientId(), true); + } + else + { + Utils.SendMessage(string.Format(GetString("SureUse.quit"), cid), player.PlayerId); + } + break; + case "/id": + if (Options.ApplyModeratorList.GetValue() == 0 || !IsPlayerModerator(player.FriendCode)) break; + + string msgText = GetString("PlayerIdList"); + foreach (var pc in Main.AllPlayerControls) + msgText += "\n" + pc.PlayerId.ToString() + " → " + Main.AllPlayerNames[pc.PlayerId]; + Utils.SendMessage(msgText, player.PlayerId); + break; + case "/kick": + // Check if the kick command is enabled in the settings + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("KickCommandDisabled"), player.PlayerId); + break; + } + + // Check if the player has the necessary privileges to use the command + if (!IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("KickCommandNoAccess"), player.PlayerId); + break; + } + + subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte kickPlayerId)) + { + Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId); + break; + } + + if (kickPlayerId == 0) + { + Utils.SendMessage(GetString("KickCommandKickHost"), player.PlayerId); + break; + } + + var kickedPlayer = Utils.GetPlayerById(kickPlayerId); + if (kickedPlayer == null) + { + Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId); + break; + } + + // Prevent moderators from kicking other moderators + if (IsPlayerModerator(kickedPlayer.FriendCode)) + { + Utils.SendMessage(GetString("KickCommandKickMod"), player.PlayerId); + break; + } + + // Kick the specified player + AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), true); + string kickedPlayerName = kickedPlayer.GetRealName(); + string textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")}"; + if (GameStates.IsInGame) + { + textToSend += $"{GetString("KickCommandKickedRole")} {GetString(kickedPlayer.GetCustomRole().ToString())}"; + } + Utils.SendMessage(textToSend); + break; + + case "/xf": + if (!GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + } + ChatUpdatePatch.DoBlockChat = false; + Utils.NotifyRoles(isForMeeting: GameStates.IsMeeting, NoCache: true); + Utils.SendMessage(GetString("Message.TryFixName"), player.PlayerId); + break; + + case "/say": + case "/s": + if (player.FriendCode.GetDevUser().IsDev) + { + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromDev")}"); + } + else if (player.FriendCode.IsDevUser()) + { + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromSponsor")}"); + } + break; + + default: + break; + } + if (SpamManager.CheckSpam(player, text)) return; + } +} +[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] +internal class ChatUpdatePatch +{ + public static bool DoBlockChat = false; + public static void Postfix(ChatController __instance) + { + if (!AmongUsClient.Instance.AmHost || Main.MessagesToSend.Count < 1 || (Main.MessagesToSend[0].Item2 == byte.MaxValue && Main.MessageWait.Value > __instance.timeSinceLastMessage)) return; + if (DoBlockChat) return; + var player = Main.AllAlivePlayerControls.OrderBy(x => x.PlayerId).FirstOrDefault() ?? Main.AllPlayerControls.OrderBy(x => x.PlayerId).FirstOrDefault(); + if (player == null) return; + (string msg, byte sendTo, string title) = Main.MessagesToSend[0]; + Main.MessagesToSend.RemoveAt(0); + int clientId = sendTo == byte.MaxValue ? -1 : Utils.GetPlayerById(sendTo).GetClientId(); + var name = player.Data.PlayerName; + if (clientId == -1) + { + player.SetName(title); + DestroyableSingleton.Instance.Chat.AddChat(player, msg); + player.SetName(name); + } + var writer = CustomRpcSender.Create("MessagesToSend", SendOption.None); + writer.StartMessage(clientId); + writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(title) + .EndRpc(); + writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) + .Write(msg) + .EndRpc(); + writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(player.Data.PlayerName) + .EndRpc(); + writer.EndMessage(); + writer.SendMessage(); + __instance.timeSinceLastMessage = 0f; + } +} + +[HarmonyPatch(typeof(ChatController), nameof(ChatController.AddChat))] +internal class AddChatPatch +{ + public static void Postfix(string chatText) + { + switch (chatText) + { + default: + break; + } + if (!AmongUsClient.Instance.AmHost) return; + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSendChat))] +internal class RpcSendChatPatch +{ + public static bool Prefix(PlayerControl __instance, string chatText, ref bool __result) + { + if (string.IsNullOrWhiteSpace(chatText)) + { + __result = false; + return false; + } + int return_count = PlayerControl.LocalPlayer.name.Count(x => x == '\n'); + chatText = new StringBuilder(chatText).Insert(0, "\n", return_count).ToString(); + if (AmongUsClient.Instance.AmClient && DestroyableSingleton.Instance) + DestroyableSingleton.Instance.Chat.AddChat(__instance, chatText); + if (chatText.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0) + DestroyableSingleton.Instance.SendWho(); + MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(__instance.NetId, (byte)RpcCalls.SendChat, SendOption.None); + messageWriter.Write(chatText); + messageWriter.EndMessage(); + __result = true; + return false; + } +} \ No newline at end of file diff --git a/Patches/ChatControlPatch.cs b/Patches/ChatControlPatch.cs new file mode 100644 index 000000000..b7ef8d794 --- /dev/null +++ b/Patches/ChatControlPatch.cs @@ -0,0 +1,41 @@ +using AmongUs.Data; +using HarmonyLib; +using UnityEngine; + +namespace TOHE; + +[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] +class ChatControllerUpdatePatch +{ + public static int CurrentHistorySelection = -1; + public static void Prefix() + { + if (AmongUsClient.Instance.AmHost && DataManager.Settings.Multiplayer.ChatMode == InnerNet.QuickChatModes.QuickChatOnly) + DataManager.Settings.Multiplayer.ChatMode = InnerNet.QuickChatModes.FreeChatOrQuickChat; //コマンドを打つためにホストのみ常時フリーチャット開放 + } + public static void Postfix(ChatController __instance) + { + if (!__instance.freeChatField.textArea.hasFocus) return; + if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.C)) + ClipboardHelper.PutClipboardString(__instance.freeChatField.textArea.text); + if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.V)) + __instance.freeChatField.textArea.SetText(__instance.freeChatField.textArea.text + GUIUtility.systemCopyBuffer); + if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.X)) + { + ClipboardHelper.PutClipboardString(__instance.freeChatField.textArea.text); + __instance.freeChatField.textArea.SetText(""); + } + if (Input.GetKeyDown(KeyCode.UpArrow) && ChatCommands.ChatHistory.Count > 0) + { + CurrentHistorySelection = Mathf.Clamp(--CurrentHistorySelection, 0, ChatCommands.ChatHistory.Count - 1); + __instance.freeChatField.textArea.SetText(ChatCommands.ChatHistory[CurrentHistorySelection]); + } + if (Input.GetKeyDown(KeyCode.DownArrow) && ChatCommands.ChatHistory.Count > 0) + { + CurrentHistorySelection++; + if (CurrentHistorySelection < ChatCommands.ChatHistory.Count) + __instance.freeChatField.textArea.SetText(ChatCommands.ChatHistory[CurrentHistorySelection]); + else __instance.freeChatField.textArea.SetText(""); + } + } +} \ No newline at end of file diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs new file mode 100644 index 000000000..7c9503038 --- /dev/null +++ b/Patches/CheckGameEndPatch.cs @@ -0,0 +1,737 @@ +using AmongUs.GameOptions; +using HarmonyLib; +using Hazel; +using System.Collections.Generic; +using System.Linq; +using TOHE.Roles.Neutral; +using static TOHE.Translator; + +namespace TOHE; + +[HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlowNormal.CheckEndCriteria))] +class GameEndChecker +{ + private static GameEndPredicate predicate; + public static bool Prefix() + { + if (!AmongUsClient.Instance.AmHost) return true; + + //ゲーム終了判定済みなら中断 + if (predicate == null) return false; + + //ゲーム終了しないモードで廃村以外の場合は中断 + if (Options.NoGameEnd.GetBool() && CustomWinnerHolder.WinnerTeam is not CustomWinner.Draw and not CustomWinner.Error) return false; + + //廃村用に初期値を設定 + var reason = GameOverReason.ImpostorByKill; + + //ゲーム終了判定 + predicate.CheckForEndGame(out reason); + + // SoloKombat + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) + { + if (CustomWinnerHolder.WinnerIds.Count > 0 || CustomWinnerHolder.WinnerTeam != CustomWinner.Default) + { + ShipStatus.Instance.enabled = false; + StartEndGame(reason); + predicate = null; + } + return false; + } + + //ゲーム終了時 + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) + { + //カモフラージュ強制解除 + Main.AllPlayerControls.Do(pc => Camouflage.RpcSetSkin(pc, ForceRevert: true, RevertToDefault: true)); + + if (reason == GameOverReason.ImpostorBySabotage && (CustomRoles.Jackal.RoleExist() || CustomRoles.Sidekick.RoleExist()) && Jackal.CanWinBySabotageWhenNoImpAlive.GetBool() && !Main.AllAlivePlayerControls.Any(x => x.GetCustomRole().IsImpostorTeam())) + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.WinnerIds.Clear(); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Jackal); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Jackal); + } + + switch (CustomWinnerHolder.WinnerTeam) + { + case CustomWinner.Crewmate: + Main.AllPlayerControls + .Where(pc => pc.Is(CustomRoleTypes.Crewmate) && !pc.Is(CustomRoles.Lovers) && !pc.Is(CustomRoles.Madmate) && !pc.Is(CustomRoles.Rogue) && !pc.Is(CustomRoles.Charmed) && !pc.Is(CustomRoles.Recruit) && !pc.Is(CustomRoles.Infected) && !pc.Is(CustomRoles.Contagious) && !pc.Is(CustomRoles.EvilSpirit) && !pc.Is(CustomRoles.Recruit) || pc.Is(CustomRoles.Admired)) + .Do(pc => CustomWinnerHolder.WinnerIds.Add(pc.PlayerId)); + break; + case CustomWinner.Impostor: + Main.AllPlayerControls + .Where(pc => (pc.Is(CustomRoleTypes.Impostor) || pc.Is(CustomRoles.Madmate)) && !pc.Is(CustomRoles.Lovers) && !pc.Is(CustomRoles.Rogue) && !pc.Is(CustomRoles.Charmed) && !pc.Is(CustomRoles.Recruit) && !pc.Is(CustomRoles.Infected) && !pc.Is(CustomRoles.Contagious) && !pc.Is(CustomRoles.EvilSpirit) && !pc.Is(CustomRoles.Recruit) && !pc.Is(CustomRoles.Admired)) + .Do(pc => CustomWinnerHolder.WinnerIds.Add(pc.PlayerId)); + break; + case CustomWinner.Succubus: + Main.AllPlayerControls + .Where(pc => pc.Is(CustomRoles.Succubus) || pc.Is(CustomRoles.Charmed) && !pc.Is(CustomRoles.Rogue) && !pc.Is(CustomRoles.Admired)) + .Do(pc => CustomWinnerHolder.WinnerIds.Add(pc.PlayerId)); + break; + case CustomWinner.CursedSoul: + Main.AllPlayerControls + .Where(pc => pc.Is(CustomRoles.CursedSoul) || pc.Is(CustomRoles.Soulless) && !pc.Is(CustomRoles.Rogue) && !pc.Is(CustomRoles.Admired)) + .Do(pc => CustomWinnerHolder.WinnerIds.Add(pc.PlayerId)); + break; + case CustomWinner.Infectious: + Main.AllPlayerControls + .Where(pc => pc.Is(CustomRoles.Infectious) || pc.Is(CustomRoles.Infected) && !pc.Is(CustomRoles.Rogue) && !pc.Is(CustomRoles.Admired)) + .Do(pc => CustomWinnerHolder.WinnerIds.Add(pc.PlayerId)); + break; + case CustomWinner.Virus: + Main.AllPlayerControls + .Where(pc => pc.Is(CustomRoles.Virus) || pc.Is(CustomRoles.Contagious) && !pc.Is(CustomRoles.Rogue) && !pc.Is(CustomRoles.Admired)) + .Do(pc => CustomWinnerHolder.WinnerIds.Add(pc.PlayerId)); + break; + case CustomWinner.Jackal: + Main.AllPlayerControls + .Where(pc => (pc.Is(CustomRoles.Jackal) || pc.Is(CustomRoles.Sidekick) || pc.Is(CustomRoles.Recruit)) && !pc.Is(CustomRoles.Lovers) && !pc.Is(CustomRoles.Infected) && !pc.Is(CustomRoles.Rogue) && !pc.Is(CustomRoles.Admired)) + .Do(pc => CustomWinnerHolder.WinnerIds.Add(pc.PlayerId)); + break; + case CustomWinner.Spiritcaller: + Main.AllPlayerControls + .Where(pc => (pc.Is(CustomRoles.Spiritcaller) || pc.Is(CustomRoles.EvilSpirit))) + .Do(pc => CustomWinnerHolder.WinnerIds.Add(pc.PlayerId)); + break; + } + if (CustomWinnerHolder.WinnerTeam is not CustomWinner.Draw and not CustomWinner.None and not CustomWinner.Error) + { + + //潜藏者抢夺胜利 + foreach (var pc in Main.AllPlayerControls) + { + if (pc.Is(CustomRoles.DarkHide) && !pc.Data.IsDead + && ((CustomWinnerHolder.WinnerTeam == CustomWinner.Impostor && !reason.Equals(GameOverReason.ImpostorBySabotage)) || CustomWinnerHolder.WinnerTeam == CustomWinner.DarkHide + || (CustomWinnerHolder.WinnerTeam == CustomWinner.Crewmate && !reason.Equals(GameOverReason.HumansByTask) && (DarkHide.IsWinKill[pc.PlayerId] == true && DarkHide.SnatchesWin.GetBool())))) + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.DarkHide); + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + } + } + foreach (var pc in Main.AllPlayerControls) + { + if (pc.Is(CustomRoles.Phantom) && pc.GetPlayerTaskState().IsTaskFinished && pc.Data.IsDead + && (((CustomWinnerHolder.WinnerTeam == CustomWinner.Impostor || CustomWinnerHolder.WinnerTeam == CustomWinner.Crewmate || CustomWinnerHolder.WinnerTeam == CustomWinner.Jackal || CustomWinnerHolder.WinnerTeam == CustomWinner.BloodKnight || CustomWinnerHolder.WinnerTeam == CustomWinner.SerialKiller || CustomWinnerHolder.WinnerTeam == CustomWinner.Juggernaut || CustomWinnerHolder.WinnerTeam == CustomWinner.Ritualist || CustomWinnerHolder.WinnerTeam == CustomWinner.Poisoner || CustomWinnerHolder.WinnerTeam == CustomWinner.Succubus || CustomWinnerHolder.WinnerTeam == CustomWinner.Infectious || CustomWinnerHolder.WinnerTeam == CustomWinner.Jinx || CustomWinnerHolder.WinnerTeam == CustomWinner.Virus || CustomWinnerHolder.WinnerTeam == CustomWinner.Arsonist || CustomWinnerHolder.WinnerTeam == CustomWinner.Pelican || CustomWinnerHolder.WinnerTeam == CustomWinner.HexMaster || CustomWinnerHolder.WinnerTeam == CustomWinner.Wraith || CustomWinnerHolder.WinnerTeam == CustomWinner.Pestilence || CustomWinnerHolder.WinnerTeam == CustomWinner.Rogue || CustomWinnerHolder.WinnerTeam == CustomWinner.Spiritcaller) && (Options.PhantomSnatchesWin.GetBool())))) + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Phantom); + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + } + } + foreach (var pc in Main.AllPlayerControls) + { + if (pc.Is(CustomRoles.Famine) && !pc.Data.IsDead + && (((CustomWinnerHolder.WinnerTeam == CustomWinner.Impostor || CustomWinnerHolder.WinnerTeam == CustomWinner.Crewmate || CustomWinnerHolder.WinnerTeam == CustomWinner.Jackal || CustomWinnerHolder.WinnerTeam == CustomWinner.BloodKnight || CustomWinnerHolder.WinnerTeam == CustomWinner.SerialKiller || CustomWinnerHolder.WinnerTeam == CustomWinner.Juggernaut || CustomWinnerHolder.WinnerTeam == CustomWinner.Ritualist || CustomWinnerHolder.WinnerTeam == CustomWinner.Poisoner || CustomWinnerHolder.WinnerTeam == CustomWinner.Succubus || CustomWinnerHolder.WinnerTeam == CustomWinner.Infectious || CustomWinnerHolder.WinnerTeam == CustomWinner.Jinx || CustomWinnerHolder.WinnerTeam == CustomWinner.Virus || CustomWinnerHolder.WinnerTeam == CustomWinner.Arsonist || CustomWinnerHolder.WinnerTeam == CustomWinner.Pelican || CustomWinnerHolder.WinnerTeam == CustomWinner.HexMaster || CustomWinnerHolder.WinnerTeam == CustomWinner.Wraith || CustomWinnerHolder.WinnerTeam == CustomWinner.Pestilence || CustomWinnerHolder.WinnerTeam == CustomWinner.Rogue)))) + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Famine); + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + } + } + foreach (var pc in Main.AllPlayerControls) + { + if (pc.Is(CustomRoles.CursedSoul) && !pc.Data.IsDead + && (((CustomWinnerHolder.WinnerTeam == CustomWinner.Impostor || CustomWinnerHolder.WinnerTeam == CustomWinner.Crewmate || CustomWinnerHolder.WinnerTeam == CustomWinner.Jackal || CustomWinnerHolder.WinnerTeam == CustomWinner.BloodKnight || CustomWinnerHolder.WinnerTeam == CustomWinner.SerialKiller || CustomWinnerHolder.WinnerTeam == CustomWinner.Juggernaut || CustomWinnerHolder.WinnerTeam == CustomWinner.Ritualist || CustomWinnerHolder.WinnerTeam == CustomWinner.Poisoner || CustomWinnerHolder.WinnerTeam == CustomWinner.Succubus || CustomWinnerHolder.WinnerTeam == CustomWinner.Infectious || CustomWinnerHolder.WinnerTeam == CustomWinner.Jinx || CustomWinnerHolder.WinnerTeam == CustomWinner.Virus || CustomWinnerHolder.WinnerTeam == CustomWinner.Arsonist || CustomWinnerHolder.WinnerTeam == CustomWinner.Pelican || CustomWinnerHolder.WinnerTeam == CustomWinner.HexMaster || CustomWinnerHolder.WinnerTeam == CustomWinner.Wraith || CustomWinnerHolder.WinnerTeam == CustomWinner.Pestilence || CustomWinnerHolder.WinnerTeam == CustomWinner.Rogue || CustomWinnerHolder.WinnerTeam == CustomWinner.Jester || CustomWinnerHolder.WinnerTeam == CustomWinner.Executioner)))) + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.CursedSoul); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Soulless); + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + } + } + + //利己主义者抢夺胜利(船员) + if (CustomWinnerHolder.WinnerTeam == CustomWinner.Crewmate) + { + foreach (var pc in Main.AllAlivePlayerControls.Where(x => x.GetCustomRole().IsCrewmate())) + if (pc.Is(CustomRoles.Egoist)) + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Egoist); + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + } + } + + //利己主义者抢夺胜利(内鬼) + if (CustomWinnerHolder.WinnerTeam == CustomWinner.Impostor) + { + foreach (var pc in Main.AllAlivePlayerControls.Where(x => x.GetCustomRole().IsImpostor())) + if (pc.Is(CustomRoles.Egoist)) + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Egoist); + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + } + } + + //神抢夺胜利 + if (CustomRolesHelper.RoleExist(CustomRoles.God)) + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.God); + Main.AllPlayerControls + .Where(p => p.Is(CustomRoles.God) && p.IsAlive()) + .Do(p => CustomWinnerHolder.WinnerIds.Add(p.PlayerId)); + } + + //恋人抢夺胜利 + else if (CustomRolesHelper.RoleExist(CustomRoles.Lovers) && !reason.Equals(GameOverReason.HumansByTask)) + { + if (!(!Main.LoversPlayers.ToArray().All(p => p.IsAlive()) && Options.LoverSuicide.GetBool())) + { + if (CustomWinnerHolder.WinnerTeam is CustomWinner.Crewmate or CustomWinner.Impostor or CustomWinner.Jackal or CustomWinner.Pelican) + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Lovers); + Main.AllPlayerControls + .Where(p => p.Is(CustomRoles.Lovers)) + .Do(p => CustomWinnerHolder.WinnerIds.Add(p.PlayerId)); + } + } + } + + //追加胜利 + foreach (var pc in Main.AllPlayerControls) + { + //Opportunist + if (pc.Is(CustomRoles.Opportunist) && pc.IsAlive()) + { + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Opportunist); + } + //Witch + if (pc.Is(CustomRoles.NWitch) && pc.IsAlive() && CustomWinnerHolder.WinnerTeam != CustomWinner.Crewmate && CustomWinnerHolder.WinnerTeam != CustomWinner.Lovers) + { + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Witch); + } + if (pc.Is(CustomRoles.Pursuer) && pc.IsAlive() && CustomWinnerHolder.WinnerTeam != CustomWinner.Jester && CustomWinnerHolder.WinnerTeam != CustomWinner.Lovers && CustomWinnerHolder.WinnerTeam != CustomWinner.Terrorist && CustomWinnerHolder.WinnerTeam != CustomWinner.Executioner && CustomWinnerHolder.WinnerTeam != CustomWinner.Collector && CustomWinnerHolder.WinnerTeam != CustomWinner.Innocent && CustomWinnerHolder.WinnerTeam != CustomWinner.Youtuber) + { + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Pursuer); + } + //Sunnyboy + if (pc.Is(CustomRoles.Sunnyboy) && !pc.IsAlive()) + { + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Sunnyboy); + } + if (pc.Is(CustomRoles.Maverick) && pc.IsAlive()) + { + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Maverick); + } + if (!Options.PhantomSnatchesWin.GetBool()) + { + if (pc.Is(CustomRoles.Phantom) && !pc.IsAlive() && pc.GetPlayerTaskState().IsTaskFinished) + { + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Phantom); + } + } + //自爆卡车来咯 + if (pc.Is(CustomRoles.Provocateur) && Main.Provoked.TryGetValue(pc.PlayerId, out var tar)) + { + if (!CustomWinnerHolder.WinnerIds.Contains(tar)) + { + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Provocateur); + } + } + } + + //Lovers follow winner + if (CustomWinnerHolder.WinnerTeam is not CustomWinner.Lovers and not CustomWinner.Crewmate and not CustomWinner.Impostor) + { + foreach (var pc in Main.AllPlayerControls.Where(x => x.Is(CustomRoles.Lovers))) + { + if (CustomWinnerHolder.WinnerIds.Where(x => Utils.GetPlayerById(x).Is(CustomRoles.Lovers)).Count() > 0) + { + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Lovers); + } + } + } + + + //FFF + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Lovers && !CustomWinnerHolder.AdditionalWinnerTeams.Contains(AdditionalWinners.Lovers) && !CustomRolesHelper.RoleExist(CustomRoles.Lovers) && !CustomRolesHelper.RoleExist(CustomRoles.Ntr)) + { + foreach (var pc in Main.AllPlayerControls.Where(x => x.Is(CustomRoles.FFF))) + { + if (Main.AllPlayerControls.Where(x => (x.Is(CustomRoles.Lovers) || x.Is(CustomRoles.Ntr)) && x.GetRealKiller()?.PlayerId == pc.PlayerId).Count() > 0) + { + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.FFF); + } + } + } + + foreach (var pc in Main.AllPlayerControls.Where(x => x.Is(CustomRoles.Totocalcio))) + { + if (Totocalcio.BetPlayer.TryGetValue(pc.PlayerId, out var betTarget) && ( + CustomWinnerHolder.WinnerIds.Contains(betTarget) || + (Main.PlayerStates.TryGetValue(betTarget, out var ps) && CustomWinnerHolder.WinnerRoles.Contains(ps.MainRole) + ))) + { + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Totocalcio); + } + } + //Lawyer win cond + foreach (var pc in Main.AllPlayerControls.Where(x => x.Is(CustomRoles.Lawyer))) + { + if (Lawyer.Target.TryGetValue(pc.PlayerId, out var lawyertarget) && ( + CustomWinnerHolder.WinnerIds.Contains(lawyertarget) || + (Main.PlayerStates.TryGetValue(lawyertarget, out var ps) && CustomWinnerHolder.WinnerRoles.Contains(ps.MainRole) + ))) + { + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Lawyer); + } + } + //中立共同胜利 + if (Options.NeutralWinTogether.GetBool() && CustomWinnerHolder.WinnerIds.Where(x => Utils.GetPlayerById(x) != null && Utils.GetPlayerById(x).GetCustomRole().IsNeutral()).Count() >= 1) + { + foreach (var pc in Main.AllPlayerControls) + if (pc.GetCustomRole().IsNeutral() && !CustomWinnerHolder.WinnerIds.Contains(pc.PlayerId)) + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + } + else if (Options.NeutralRoleWinTogether.GetBool()) + { + foreach (var id in CustomWinnerHolder.WinnerIds) + { + var pc = Utils.GetPlayerById(id); + if (pc == null || !pc.GetCustomRole().IsNeutral()) continue; + foreach (var tar in Main.AllPlayerControls) + if (!CustomWinnerHolder.WinnerIds.Contains(tar.PlayerId) && tar.GetCustomRole() == pc.GetCustomRole()) + CustomWinnerHolder.WinnerIds.Add(tar.PlayerId); + } + } + + //补充恋人胜利名单 + if (CustomWinnerHolder.WinnerTeam == CustomWinner.Lovers || CustomWinnerHolder.AdditionalWinnerTeams.Contains(AdditionalWinners.Lovers)) + { + Main.AllPlayerControls + .Where(p => p.Is(CustomRoles.Lovers) && !CustomWinnerHolder.WinnerIds.Contains(p.PlayerId)) + .Do(p => CustomWinnerHolder.WinnerIds.Add(p.PlayerId)); + } + + + } + ShipStatus.Instance.enabled = false; + StartEndGame(reason); + predicate = null; + } + return false; + } + public static void StartEndGame(GameOverReason reason) + { + var sender = new CustomRpcSender("EndGameSender", SendOption.Reliable, true); + sender.StartMessage(-1); // 5: GameData + MessageWriter writer = sender.stream; + + //ゴーストロール化 + List ReviveRequiredPlayerIds = new(); + var winner = CustomWinnerHolder.WinnerTeam; + foreach (var pc in Main.AllPlayerControls) + { + if (winner == CustomWinner.Draw) + { + SetGhostRole(ToGhostImpostor: true); + continue; + } + bool canWin = CustomWinnerHolder.WinnerIds.Contains(pc.PlayerId) || + CustomWinnerHolder.WinnerRoles.Contains(pc.GetCustomRole()); + bool isCrewmateWin = reason.Equals(GameOverReason.HumansByVote) || reason.Equals(GameOverReason.HumansByTask); + SetGhostRole(ToGhostImpostor: canWin ^ isCrewmateWin); + + void SetGhostRole(bool ToGhostImpostor) + { + if (!pc.Data.IsDead) ReviveRequiredPlayerIds.Add(pc.PlayerId); + if (ToGhostImpostor) + { + Logger.Info($"{pc.GetNameWithRole()}: ImpostorGhostに変更", "ResetRoleAndEndGame"); + sender.StartRpc(pc.NetId, RpcCalls.SetRole) + .Write((ushort)RoleTypes.ImpostorGhost) + .EndRpc(); + pc.SetRole(RoleTypes.ImpostorGhost); + } + else + { + Logger.Info($"{pc.GetNameWithRole()}: CrewmateGhostに変更", "ResetRoleAndEndGame"); + sender.StartRpc(pc.NetId, RpcCalls.SetRole) + .Write((ushort)RoleTypes.CrewmateGhost) + .EndRpc(); + pc.SetRole(RoleTypes.Crewmate); + } + } + SetEverythingUpPatch.LastWinsReason = winner is CustomWinner.Crewmate or CustomWinner.Impostor ? GetString($"GameOverReason.{reason}") : ""; + } + + // CustomWinnerHolderの情報の同期 + sender.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.EndGame); + CustomWinnerHolder.WriteTo(sender.stream); + sender.EndRpc(); + + // GameDataによる蘇生処理 + writer.StartMessage(1); // Data + { + writer.WritePacked(GameData.Instance.NetId); // NetId + foreach (var info in GameData.Instance.AllPlayers) + { + if (ReviveRequiredPlayerIds.Contains(info.PlayerId)) + { + // 蘇生&メッセージ書き込み + info.IsDead = false; + writer.StartMessage(info.PlayerId); + info.Serialize(writer); + writer.EndMessage(); + } + } + writer.EndMessage(); + } + + sender.EndMessage(); + + // バニラ側のゲーム終了RPC + writer.StartMessage(8); //8: EndGame + { + writer.Write(AmongUsClient.Instance.GameId); //GameId + writer.Write((byte)reason); //GameoverReason + writer.Write(false); //showAd + } + writer.EndMessage(); + + sender.SendMessage(); + } + + public static void SetPredicateToNormal() => predicate = new NormalGameEndPredicate(); + public static void SetPredicateToSoloKombat() => predicate = new SoloKombatGameEndPredicate(); + + // ===== ゲーム終了条件 ===== + // 通常ゲーム用 + class NormalGameEndPredicate : GameEndPredicate + { + public override bool CheckForEndGame(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return false; + if (CheckGameEndByLivingPlayers(out reason)) return true; + if (CheckGameEndByTask(out reason)) return true; + if (CheckGameEndBySabotage(out reason)) return true; + + return false; + } + + public bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + + if (CustomRolesHelper.RoleExist(CustomRoles.Sunnyboy) && Main.AllAlivePlayerControls.Count() > 1) return false; + + int Imp = Utils.AlivePlayersCount(CountTypes.Impostor); + int Jackal = Utils.AlivePlayersCount(CountTypes.Jackal); + int Pel = Utils.AlivePlayersCount(CountTypes.Pelican); + int Crew = Utils.AlivePlayersCount(CountTypes.Crew); + int Gam = Utils.AlivePlayersCount(CountTypes.Gamer); + int BK = Utils.AlivePlayersCount(CountTypes.BloodKnight); + int Pois = Utils.AlivePlayersCount(CountTypes.Poisoner); + int CM = Utils.AlivePlayersCount(CountTypes.Succubus); + int Hex = Utils.AlivePlayersCount(CountTypes.HexMaster); + int Wraith = Utils.AlivePlayersCount(CountTypes.Wraith); + int Pestilence = Utils.AlivePlayersCount(CountTypes.Pestilence); + int PB = Utils.AlivePlayersCount(CountTypes.PlagueBearer); + int SK = Utils.AlivePlayersCount(CountTypes.NSerialKiller); + int Witch = Utils.AlivePlayersCount(CountTypes.NWitch); + int Juggy = Utils.AlivePlayersCount(CountTypes.Juggernaut); + int Vamp = Utils.AlivePlayersCount(CountTypes.Infectious); + int Virus = Utils.AlivePlayersCount(CountTypes.Virus); + int Rogue = Utils.AlivePlayersCount(CountTypes.Rogue); + int DH = Utils.AlivePlayersCount(CountTypes.DarkHide); + int Jinx = Utils.AlivePlayersCount(CountTypes.Jinx); + int Rit = Utils.AlivePlayersCount(CountTypes.Ritualist); + int PP = Utils.AlivePlayersCount(CountTypes.Pickpocket); + int Traitor = Utils.AlivePlayersCount(CountTypes.Traitor); + int Med = Utils.AlivePlayersCount(CountTypes.Medusa); + int SC = Utils.AlivePlayersCount(CountTypes.Spiritcaller); + int Glitch = Utils.AlivePlayersCount(CountTypes.Glitch); + int Arso = Utils.AlivePlayersCount(CountTypes.Arsonist); + + Imp += Main.AllAlivePlayerControls.Count(x => x.GetCustomRole().IsImpostor() && x.Is(CustomRoles.DualPersonality)); + Crew += Main.AllAlivePlayerControls.Count(x => x.GetCustomRole().IsCrewmate() && x.Is(CustomRoles.DualPersonality)); + Crew += Main.AllAlivePlayerControls.Count(x => (x.GetCustomRole().IsImpostor() && x.Is(CustomRoles.Admired)) && x.Is(CustomRoles.DualPersonality)); + Crew += Main.AllAlivePlayerControls.Count(x => (x.GetCustomRole().IsNeutral() && x.Is(CustomRoles.Admired)) && x.Is(CustomRoles.DualPersonality)); + Crew += Main.AllAlivePlayerControls.Count(x => (x.GetCustomRole().IsCrewmate() && x.Is(CustomRoles.Admired)) && x.Is(CustomRoles.DualPersonality)); + CM += Main.AllAlivePlayerControls.Count(x => x.Is(CustomRoles.Charmed) && x.Is(CustomRoles.DualPersonality)); + Jackal += Main.AllAlivePlayerControls.Count(x => x.Is(CustomRoles.Sidekick) && x.Is(CustomRoles.DualPersonality)); + Jackal += Main.AllAlivePlayerControls.Count(x => x.Is(CustomRoles.Recruit) && x.Is(CustomRoles.DualPersonality)); + Vamp += Main.AllAlivePlayerControls.Count(x => x.Is(CustomRoles.Infected) && x.Is(CustomRoles.DualPersonality)); + Virus += Main.AllAlivePlayerControls.Count(x => x.Is(CustomRoles.Contagious) && x.Is(CustomRoles.DualPersonality)); + Imp += Main.AllAlivePlayerControls.Count(x => x.Is(CustomRoles.Madmate) && x.Is(CustomRoles.DualPersonality)); + + if (Imp == 0 && Crew == 0 && Rit == 0 && Traitor == 0 && Med == 0 && PP == 0 && Jackal == 0 && Vamp == 0 && DH == 0 && Rogue == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Jinx == 0 && SK == 0 && Hex == 0 && Pel == 0 && Gam == 0 && BK == 0 && Pois == 0 && Virus == 0 && SC == 0 && CM == 0) //全灭 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.None); + } + else if (Main.AllAlivePlayerControls.All(p => p.Is(CustomRoles.Lovers))) //恋人胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Lovers); + } + else if (Jackal == 0 && Pel == 0 && Traitor == 0 && Med == 0 && PP == 0 && Rit == 0 && Gam == 0 && Vamp == 0 && DH == 0 && Rogue == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Jinx == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && SK == 0 && Hex == 0 && BK == 0 && Pois == 0 && Virus == 0 && SC == 0 && CM == 0 && Crew <= Imp) //内鬼胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Impostor); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Parasite); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Refugee); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Crewpostor); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Convict); + } + else if (Imp == 0 && Pel == 0 && Traitor == 0 && Med == 0 && Rit == 0 && PP == 0 && Gam == 0 && Vamp == 0 && DH == 0 && Rogue == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Jinx == 0 && SK == 0 && Hex == 0 && BK == 0 && Pois == 0 && Virus == 0 && SC == 0 && CM == 0 && Crew <= Jackal) //豺狼胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Jackal); + // CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Jackal); + // CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Sidekick); + } + else if (Imp == 0 && Jackal == 0 && Rit == 0 && Traitor == 0 && Med == 0 && PP == 0 && Gam == 0 && Vamp == 0 && DH == 0 && Rogue == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && SK == 0 && Jinx == 0 && Hex == 0 && Pois == 0 && Virus == 0 && SC == 0 && CM == 0 && BK == 0 && Crew <= Pel) //鹈鹕胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Pelican); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Pelican); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Rit == 0 && Pel == 0 && Vamp == 0 && DH == 0 && Rogue == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && SK == 0 && Hex == 0 && Jinx == 0 && Pois == 0 && Virus == 0 && SC == 0 && CM == 0 && BK == 0 && Crew <= Gam) //玩家胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Gamer); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Gamer); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Rit == 0 && Pel == 0 && Vamp == 0 && DH == 0 && Rogue == 0 && Jinx == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && SK == 0 && Hex == 0 && Pois == 0 && Virus == 0 && SC == 0 && CM == 0 && Gam == 0 && Crew <= BK) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.BloodKnight); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.BloodKnight); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Pel == 0 && Rit == 0 && Vamp == 0 && DH == 0 && Rogue == 0 && Jinx == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && SK == 0 && Hex == 0 && BK == 0 && Gam == 0 && Virus == 0 && SC == 0 && CM == 0 && Crew <= Pois) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Poisoner); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Poisoner); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Pel == 0 && Vamp == 0 && Rit == 0 && Jinx == 0 && DH == 0 && Rogue == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && SK == 0 && Pois == 0 && Virus == 0 && SC == 0 && BK == 0 && Gam == 0 && CM == 0 && Crew <= Hex) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.HexMaster); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.HexMaster); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Pel == 0 && Vamp == 0 && DH == 0 && Rit == 0 && Jinx == 0 && Rogue == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Hex == 0 && Pestilence == 0 && PB == 0 && SK == 0 && Pois == 0 && Virus == 0 && SC == 0 && BK == 0 && Gam == 0 && CM == 0 && Crew <= Wraith) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Wraith); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Wraith); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Pel == 0 && Vamp == 0 && DH == 0 && Rit == 0 && Jinx == 0 && Rogue == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Hex == 0 && Wraith == 0 && PB == 0 && SK == 0 && Pois == 0 && Virus == 0 && SC == 0 && BK == 0 && Gam == 0 && CM == 0 && Crew <= Pestilence) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Pestilence); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Pestilence); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Pel == 0 && Vamp == 0 && DH == 0 && Rit == 0 && Jinx == 0 && Rogue == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Hex == 0 && Wraith == 0 && Pestilence == 0 && SK == 0 && Pois == 0 && Virus == 0 && SC == 0 && BK == 0 && Gam == 0 && CM == 0 && Crew <= PB) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Plaguebearer); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.PlagueBearer); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Pel == 0 && Vamp == 0 && DH == 0 && Rogue == 0 && Rit == 0 && Jinx == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Hex == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && Pois == 0 && Virus == 0 && SC == 0 && BK == 0 && Gam == 0 && CM == 0 && Crew <= SK) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.SerialKiller); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.NSerialKiller); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Arso == 0 && Pel == 0 && Vamp == 0 && DH == 0 && Rogue == 0 && Glitch == 0 && SK == 0 && Rit == 0 && Jinx == 0 && Hex == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && Pois == 0 && Virus == 0 && SC == 0 && BK == 0 && Gam == 0 && CM == 0 && Crew <= Juggy) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Juggernaut); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Juggernaut); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Juggy == 0 && Pel == 0 && Vamp == 0 && DH == 0 && Rogue == 0 && Glitch == 0 && SK == 0 && Rit == 0 && Jinx == 0 && Hex == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && Pois == 0 && Virus == 0 && SC == 0 && BK == 0 && Gam == 0 && CM == 0 && Crew <= Arso) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Arsonist); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Arsonist); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Pel == 0 && Rit == 0 && Vamp == 0 && DH == 0 && Rogue == 0 && Jinx == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Hex == 0 && SK == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && BK == 0 && Gam == 0 && Pois == 0 && Virus == 0 && SC == 0 && Crew <= CM) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Succubus); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Pel == 0 && CM == 0 && Rit == 0 && DH == 0 && Rogue == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Hex == 0 && SK == 0 && Jinx == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && BK == 0 && Gam == 0 && Pois == 0 && Virus == 0 && SC == 0 && Crew <= Vamp) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Infectious); + } + else if (Imp == 0 && Jackal == 0 && Pel == 0 && Traitor == 0 && Med == 0 && PP == 0 && Vamp == 0 && DH == 0 && Rit == 0 && Rogue == 0 && CM == 0 && Jinx == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Hex == 0 && SK == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && BK == 0 && Gam == 0 && Pois == 0 && SC == 0 && Crew <= Virus) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Virus); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Pel == 0 && Vamp == 0 && Virus == 0 && SC == 0 && Rit == 0 && Rogue == 0 && CM == 0 && Jinx == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Hex == 0 && SK == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && BK == 0 && Gam == 0 && Pois == 0 && Crew <= DH) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.DarkHide); + } + else if (Imp == 0 && Jackal == 0 && Pel == 0 && Traitor == 0 && PP == 0 && Vamp == 0 && DH == 0 && Rit == 0 && Rogue == 0 && CM == 0 && Jinx == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Hex == 0 && SK == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && BK == 0 && Gam == 0 && Pois == 0 && Virus == 0 && Med == 0 && Crew <= SC) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Spiritcaller); + } + else if (Jackal == 0 && Pel == 0 && PP == 0 && Traitor == 0 && Med == 0 && Vamp == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && DH == 0 && SK == 0 && Rit == 0 && Hex == 0 && Jinx == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && Pois == 0 && Virus == 0 && SC == 0 && BK == 0 && Gam == 0 && CM == 0 && Imp == 0 && Crew <= Rogue) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Rogue); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Rogue); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Pel == 0 && Vamp == 0 && Rit == 0 && DH == 0 && Rogue == 0 && Pois == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && SK == 0 && Hex == 0 && BK == 0 && Gam == 0 && Virus == 0 && SC == 0 && CM == 0 && Crew <= Jinx) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Jinx); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Jinx); + } + else if (Imp == 0 && Jackal == 0 && PP == 0 && Traitor == 0 && Med == 0 && Pel == 0 && Vamp == 0 && Jinx == 0 && DH == 0 && Rogue == 0 && Pois == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && SK == 0 && Hex == 0 && BK == 0 && Gam == 0 && Virus == 0 && SC == 0 && CM == 0 && Crew <= Rit) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Ritualist); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Ritualist); + } + else if (Imp == 0 && Jackal == 0 && Rit == 0 && Traitor == 0 && Med == 0 && Pel == 0 && Vamp == 0 && Jinx == 0 && DH == 0 && Rogue == 0 && Pois == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && SK == 0 && Hex == 0 && BK == 0 && Gam == 0 && Virus == 0 && SC == 0 && CM == 0 && Crew <= PP) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Pickpocket); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Pickpocket); + } + else if (Imp == 0 && Jackal == 0 && Rit == 0 && PP == 0 && Med == 0 && Pel == 0 && Vamp == 0 && Jinx == 0 && DH == 0 && Rogue == 0 && Pois == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && SK == 0 && Hex == 0 && BK == 0 && Gam == 0 && Virus == 0 && SC == 0 && CM == 0 && Crew <= Traitor) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Traitor); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Traitor); + } + else if (Imp == 0 && Jackal == 0 && Rit == 0 && PP == 0 && Traitor == 0 && Pel == 0 && Vamp == 0 && Jinx == 0 && DH == 0 && Rogue == 0 && Pois == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && SK == 0 && Hex == 0 && BK == 0 && Gam == 0 && Virus == 0 && SC == 0 && CM == 0 && Crew <= Traitor) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Medusa); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Medusa); + } + else if (Imp == 0 && Jackal == 0 && Rit == 0 && PP == 0 && Traitor == 0 && Pel == 0 && Vamp == 0 && Jinx == 0 && DH == 0 && Rogue == 0 && Pois == 0 && Juggy == 0 && Arso == 0 && Med == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && SK == 0 && Hex == 0 && BK == 0 && Gam == 0 && Virus == 0 && SC == 0 && CM == 0 && Crew == 0) //嗜血骑士胜利 + { + reason = GameOverReason.ImpostorByKill; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Glitch); + CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Glitch); + } + else if (Jackal == 0 && Pel == 0 && PP == 0 && Traitor == 0 && Med == 0 && Imp == 0 && Vamp == 0 && DH == 0 && Rit == 0 && Rogue == 0 && Juggy == 0 && Arso == 0 && Glitch == 0 && Jinx == 0 && Hex == 0 && SK == 0 && Wraith == 0 && Pestilence == 0 && PB == 0 && Pois == 0 && BK == 0 && Gam == 0 && CM == 0 && Virus == 0 && SC == 0) //船员胜利 + { + reason = GameOverReason.HumansByVote; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Crewmate); + // CustomWinnerHolder.WinnerRoles.Add(CustomRoles.Admired); + } + + else return false; //胜利条件未达成 + + return true; + } + } + + // 个人竞技模式用 + class SoloKombatGameEndPredicate : GameEndPredicate + { + public override bool CheckForEndGame(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + if (CustomWinnerHolder.WinnerIds.Count > 0) return false; + if (CheckGameEndByLivingPlayers(out reason)) return true; + return false; + } + + public bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + + if (SoloKombatManager.RoundTime > 0) return false; + + var list = Main.AllPlayerControls.Where(x => !x.Is(CustomRoles.GM) && SoloKombatManager.GetRankOfScore(x.PlayerId) == 1); + var winner = list.FirstOrDefault(); + + CustomWinnerHolder.WinnerIds = new() + { + winner.PlayerId + }; + + Main.DoBlockNameChange = true; + + return true; + } + } +} + +public abstract class GameEndPredicate +{ + /// ゲームの終了条件をチェックし、CustomWinnerHolderに値を格納します。 + /// バニラのゲーム終了処理に使用するGameOverReason + /// ゲーム終了の条件を満たしているかどうか + public abstract bool CheckForEndGame(out GameOverReason reason); + + /// GameData.TotalTasksとCompletedTasksをもとにタスク勝利が可能かを判定します。 + public virtual bool CheckGameEndByTask(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + if (Options.DisableTaskWin.GetBool() || TaskState.InitialTotalTasks == 0) return false; + + if (GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks) + { + reason = GameOverReason.HumansByTask; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Crewmate); + return true; + } + return false; + } + /// ShipStatus.Systems内の要素をもとにサボタージュ勝利が可能かを判定します。 + public virtual bool CheckGameEndBySabotage(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + if (ShipStatus.Instance.Systems == null) return false; + + // TryGetValueは使用不可 + var systems = ShipStatus.Instance.Systems; + LifeSuppSystemType LifeSupp; + if (systems.ContainsKey(SystemTypes.LifeSupp) && // サボタージュ存在確認 + (LifeSupp = systems[SystemTypes.LifeSupp].TryCast()) != null && // キャスト可能確認 + LifeSupp.Countdown < 0f) // タイムアップ確認 + { + // 酸素サボタージュ + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Impostor); + reason = GameOverReason.ImpostorBySabotage; + LifeSupp.Countdown = 10000f; + return true; + } + + ISystemType sys = null; + if (systems.ContainsKey(SystemTypes.Reactor)) sys = systems[SystemTypes.Reactor]; + else if (systems.ContainsKey(SystemTypes.Laboratory)) sys = systems[SystemTypes.Laboratory]; + + ICriticalSabotage critical; + if (sys != null && // サボタージュ存在確認 + (critical = sys.TryCast()) != null && // キャスト可能確認 + critical.Countdown < 0f) // タイムアップ確認 + { + // リアクターサボタージュ + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Impostor); + reason = GameOverReason.ImpostorBySabotage; + critical.ClearSabotage(); + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/Patches/ClientOptionsPatch.cs b/Patches/ClientOptionsPatch.cs new file mode 100644 index 000000000..18ab17908 --- /dev/null +++ b/Patches/ClientOptionsPatch.cs @@ -0,0 +1,99 @@ +using HarmonyLib; +using UnityEngine; + +namespace TOHE; + +//��Դ��https://github.com/tukasa0001/TownOfHost/pull/1265 +[HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Start))] +public static class OptionsMenuBehaviourStartPatch +{ + private static ClientOptionItem UnlockFPS; + private static ClientOptionItem AutoStart; + private static ClientOptionItem ForceOwnLanguage; + private static ClientOptionItem ForceOwnLanguageRoleName; + private static ClientOptionItem EnableCustomButton; + private static ClientOptionItem EnableCustomSoundEffect; + private static ClientOptionItem SwitchVanilla; + //private static ClientOptionItem VersionCheat; + //private static ClientOptionItem GodMode; + + public static void Postfix(OptionsMenuBehaviour __instance) + { + if (__instance.DisableMouseMovement == null) return; + + Main.SwitchVanilla.Value = false; + if (Main.ResetOptions || !DebugModeManager.AmDebugger) + { + Main.ResetOptions = false; + Main.VersionCheat.Value = false; + Main.GodMode.Value = false; + } + + if (UnlockFPS == null || UnlockFPS.ToggleButton == null) + { + UnlockFPS = ClientOptionItem.Create("UnlockFPS", Main.UnlockFPS, __instance, UnlockFPSButtonToggle); + static void UnlockFPSButtonToggle() + { + Application.targetFrameRate = Main.UnlockFPS.Value ? 165 : 60; + Logger.SendInGame(string.Format(Translator.GetString("FPSSetTo"), Application.targetFrameRate)); + } + } + if (AutoStart == null || AutoStart.ToggleButton == null) + { + AutoStart = ClientOptionItem.Create("AutoStart", Main.AutoStart, __instance, AutoStartButtonToggle); + static void AutoStartButtonToggle() + { + if (Main.AutoStart.Value == false && GameStates.IsCountDown) + { + GameStartManager.Instance.ResetStartState(); + Logger.SendInGame(Translator.GetString("CancelStartCountDown")); + } + } + } + if (ForceOwnLanguage == null || ForceOwnLanguage.ToggleButton == null) + { + ForceOwnLanguage = ClientOptionItem.Create("ForceOwnLanguage", Main.ForceOwnLanguage, __instance); + } + if (ForceOwnLanguageRoleName == null || ForceOwnLanguageRoleName.ToggleButton == null) + { + ForceOwnLanguageRoleName = ClientOptionItem.Create("ForceOwnLanguageRoleName", Main.ForceOwnLanguageRoleName, __instance); + } + if (EnableCustomButton == null || EnableCustomButton.ToggleButton == null) + { + EnableCustomButton = ClientOptionItem.Create("EnableCustomButton", Main.EnableCustomButton, __instance); + } + if (EnableCustomSoundEffect == null || EnableCustomSoundEffect.ToggleButton == null) + { + EnableCustomSoundEffect = ClientOptionItem.Create("EnableCustomSoundEffect", Main.EnableCustomSoundEffect, __instance); + } + if (SwitchVanilla == null || SwitchVanilla.ToggleButton == null) + { + SwitchVanilla = ClientOptionItem.Create("SwitchVanilla", Main.SwitchVanilla, __instance, SwitchVanillaButtonToggle); + static void SwitchVanillaButtonToggle() + { + Harmony.UnpatchAll(); + Main.Instance.Unload(); + } + } + /* if ((VersionCheat == null || VersionCheat.ToggleButton == null) && DebugModeManager.AmDebugger) + { + VersionCheat = ClientOptionItem.Create("VersionCheat", Main.VersionCheat, __instance); + } + if ((GodMode == null || GodMode.ToggleButton == null) && DebugModeManager.AmDebugger) + { + GodMode = ClientOptionItem.Create("GodMode", Main.GodMode, __instance); + } */ + } +} + +[HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Close))] +public static class OptionsMenuBehaviourClosePatch +{ + public static void Postfix() + { + if (ClientOptionItem.CustomBackground != null) + { + ClientOptionItem.CustomBackground.gameObject.SetActive(false); + } + } +} \ No newline at end of file diff --git a/Patches/ClientPatch.cs b/Patches/ClientPatch.cs new file mode 100644 index 000000000..973547b17 --- /dev/null +++ b/Patches/ClientPatch.cs @@ -0,0 +1,131 @@ +using HarmonyLib; +using InnerNet; +using TOHE.Modules; +using UnityEngine; +using static TOHE.Translator; + +namespace TOHE; + +[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.MakePublic))] +internal class MakePublicPatch +{ + public static bool Prefix(GameStartManager __instance) + { + // 定数設定による公開ルームブロック + if (!Main.AllowPublicRoom) + { + var message = GetString("DisabledByProgram"); + Logger.Info(message, "MakePublicPatch"); + Logger.SendInGame(message); + return false; + } + if (ModUpdater.isBroken || (ModUpdater.hasUpdate && ModUpdater.forceUpdate)) + { + var message = ""; + if (ModUpdater.isBroken) message = GetString("ModBrokenMessage"); + if (ModUpdater.hasUpdate) message = GetString("CanNotJoinPublicRoomNoLatest"); + Logger.Info(message, "MakePublicPatch"); + Logger.SendInGame(message); + return false; + } + return true; + } +} +[HarmonyPatch(typeof(MMOnlineManager), nameof(MMOnlineManager.Start))] +internal class MMOnlineManagerStartPatch +{ + public static void Postfix(MMOnlineManager __instance) + { + if (!((ModUpdater.hasUpdate && ModUpdater.forceUpdate) || ModUpdater.isBroken)) return; + var obj = GameObject.Find("FindGameButton"); + if (obj) + { + obj?.SetActive(false); + var parentObj = obj.transform.parent.gameObject; + var textObj = Object.Instantiate(obj.transform.FindChild("Text_TMP").GetComponent()); + textObj.transform.position = new Vector3(1f, -0.3f, 0); + textObj.name = "CanNotJoinPublic"; + var message = ModUpdater.isBroken ? $"{Utils.ColorString(Color.red, GetString("ModBrokenMessage"))}" + : $"{Utils.ColorString(Color.red, GetString("CanNotJoinPublicRoomNoLatest"))}"; + new LateTask(() => { textObj.text = message; }, 0.01f, "CanNotJoinPublic"); + } + } +} +[HarmonyPatch(typeof(SplashManager), nameof(SplashManager.Update))] +internal class SplashLogoAnimatorPatch +{ + public static void Prefix(SplashManager __instance) + { + if (DebugModeManager.AmDebugger) + { + __instance.sceneChanger.AllowFinishLoadingScene(); + __instance.startedSceneLoad = true; + } + } +} +[HarmonyPatch(typeof(EOSManager), nameof(EOSManager.IsAllowedOnline))] +internal class RunLoginPatch +{ + public static int ClickCount = 0; + public static void Prefix(ref bool canOnline) + { +#if DEBUG + if (ClickCount < 10) canOnline = true; + if (ClickCount >= 10) ModUpdater.forceUpdate = false; +#endif + } +} +[HarmonyPatch(typeof(BanMenu), nameof(BanMenu.SetVisible))] +internal class BanMenuSetVisiblePatch +{ + public static bool Prefix(BanMenu __instance, bool show) + { + if (!AmongUsClient.Instance.AmHost) return true; + show &= PlayerControl.LocalPlayer && PlayerControl.LocalPlayer.Data != null; + __instance.BanButton.gameObject.SetActive(AmongUsClient.Instance.CanBan()); + __instance.KickButton.gameObject.SetActive(AmongUsClient.Instance.CanKick()); + __instance.MenuButton.gameObject.SetActive(show); + return false; + } +} +[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNet.InnerNetClient.CanBan))] +internal class InnerNetClientCanBanPatch +{ + public static bool Prefix(InnerNet.InnerNetClient __instance, ref bool __result) + { + __result = __instance.AmHost; + return false; + } +} +[HarmonyPatch(typeof(InnerNet.InnerNetClient), nameof(InnerNet.InnerNetClient.KickPlayer))] +internal class KickPlayerPatch +{ + public static void Prefix(InnerNet.InnerNetClient __instance, int clientId, bool ban) + { + if (!AmongUsClient.Instance.AmHost) return; + if (ban) BanManager.AddBanPlayer(AmongUsClient.Instance.GetRecentClient(clientId)); + } +} +[HarmonyPatch(typeof(ResolutionManager), nameof(ResolutionManager.SetResolution))] +internal class SetResolutionManager +{ + public static void Postfix() + { + //if (MainMenuManagerPatch.qqButton != null) + // MainMenuManagerPatch.qqButton.transform.localPosition = Vector3.Reflect(MainMenuManagerPatch.template.transform.localPosition, Vector3.left); + //if (MainMenuManagerPatch.discordButton != null) + // MainMenuManagerPatch.discordButton.transform.localPosition = Vector3.Reflect(MainMenuManagerPatch.template.transform.localPosition, Vector3.left); + if (MainMenuManagerPatch.updateButton != null) + MainMenuManagerPatch.updateButton.transform.localPosition = MainMenuManagerPatch.template.transform.localPosition + new Vector3(0.25f, 0.75f); + } +} + +[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendAllStreamedObjects))] +internal class InnerNetObjectSerializePatch +{ + public static void Prefix() + { + if (AmongUsClient.Instance.AmHost) + GameOptionsSender.SendAllGameOptions(); + } +} \ No newline at end of file diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs new file mode 100644 index 000000000..8b44965d0 --- /dev/null +++ b/Patches/ControlPatch.cs @@ -0,0 +1,340 @@ +using HarmonyLib; +using Hazel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using static TOHE.Translator; +using Object = UnityEngine.Object; + +namespace TOHE; + +[HarmonyPatch(typeof(ControllerManager), nameof(ControllerManager.Update))] +internal class ControllerManagerUpdatePatch +{ + private static readonly (int, int)[] resolutions = { (480, 270), (640, 360), (800, 450), (1280, 720), (1600, 900), (1920, 1080) }; + private static int resolutionIndex = 0; + + public static List addDes = new(); + public static int addonIndex = -1; + + public static void Postfix(ControllerManager __instance) + { + //切换自定义设置的页面 + if (GameStates.IsLobby) + { + if (Input.GetKeyDown(KeyCode.Tab)) + { + OptionShower.Next(); + } + for (var i = 0; i < 9; i++) + { + if (ORGetKeysDown(KeyCode.Alpha1 + i, KeyCode.Keypad1 + i) && OptionShower.pages.Count >= i + 1) + OptionShower.currentPage = i; + } + } + //捕捉全屏快捷键 + if (GetKeysDown(KeyCode.LeftAlt, KeyCode.Return)) + { + new LateTask(SetResolutionManager.Postfix, 0.01f, "Fix Button Position"); + } + //职业介绍 + if (Input.GetKeyDown(KeyCode.F1) && GameStates.InGame && Options.CurrentGameMode == CustomGameMode.Standard) + { + try + { + var role = PlayerControl.LocalPlayer.GetCustomRole(); + var lp = PlayerControl.LocalPlayer; + var sb = new StringBuilder(); + sb.Append(GetString(role.ToString()) + Utils.GetRoleMode(role) + lp.GetRoleInfo(true)); + if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) + Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref sb, command: true); + HudManager.Instance.ShowPopUp(sb.ToString()); + } + catch (Exception ex) + { + Logger.Exception(ex, "ControllerManagerUpdatePatch"); + throw; + } + } + //附加职业介绍 + if (Input.GetKeyDown(KeyCode.F2) && GameStates.InGame && Options.CurrentGameMode == CustomGameMode.Standard) + { + try + { + var role = PlayerControl.LocalPlayer.GetCustomRole(); + var lp = PlayerControl.LocalPlayer; + if (Main.PlayerStates[lp.PlayerId].SubRoles.Count < 1) return; + + addDes = new(); + foreach (var subRole in Main.PlayerStates[lp.PlayerId].SubRoles.Where(x => x is not CustomRoles.Charmed)) + addDes.Add(GetString($"{subRole}") + Utils.GetRoleMode(subRole) + GetString($"{subRole}InfoLong")); + if (CustomRolesHelper.RoleExist(CustomRoles.Ntr) && (role is not CustomRoles.GM and not CustomRoles.Ntr)) + addDes.Add(GetString($"Lovers") + Utils.GetRoleMode(CustomRoles.Lovers) + GetString($"LoversInfoLong")); + + addonIndex++; + if (addonIndex >= addDes.Count) addonIndex = 0; + HudManager.Instance.ShowPopUp(addDes[addonIndex]); + } + catch (Exception ex) + { + Logger.Exception(ex, "ControllerManagerUpdatePatch"); + throw; + } + } + //更改分辨率 + if (Input.GetKeyDown(KeyCode.F11)) + { + resolutionIndex++; + if (resolutionIndex >= resolutions.Length) resolutionIndex = 0; + ResolutionManager.SetResolution(resolutions[resolutionIndex].Item1, resolutions[resolutionIndex].Item2, false); + SetResolutionManager.Postfix(); + } + //重新加载自定义翻译 + if (GetKeysDown(KeyCode.F5, KeyCode.T)) + { + Logger.Info("加载自定义翻译文件", "KeyCommand"); + Translator.LoadLangs(); + Logger.SendInGame("Reloaded Custom Translation File"); + } + if (GetKeysDown(KeyCode.F5, KeyCode.X)) + { + Logger.Info("导出自定义翻译文件", "KeyCommand"); + Translator.ExportCustomTranslation(); + Logger.SendInGame("Exported Custom Translation File"); + } + //日志文件转储 + if (GetKeysDown(KeyCode.F1, KeyCode.LeftControl)) + { + Logger.Info("输出日志", "KeyCommand"); + Utils.DumpLog(); + } + //将当前设置复制为文本 + if (GetKeysDown(KeyCode.LeftAlt, KeyCode.C) && !Input.GetKey(KeyCode.LeftShift) && !GameStates.IsNotJoined) + { + Utils.CopyCurrentSettings(); + } + //打开游戏目录 + if (GetKeysDown(KeyCode.F10)) + { + System.Diagnostics.Process.Start(System.Environment.CurrentDirectory); + } + + //-- 下面是主机专用的命令--// + if (!AmongUsClient.Instance.AmHost) return; + // 强制显示聊天框 + if (GetKeysDown(KeyCode.Return, KeyCode.C, KeyCode.LeftShift)) + { + HudManager.Instance.Chat.SetVisible(true); + } + //强制结束游戏 + if (GetKeysDown(KeyCode.Return, KeyCode.L, KeyCode.LeftShift) && GameStates.IsInGame) + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); + GameManager.Instance.LogicFlow.CheckEndCriteria(); + } + //强制结束会议或召开会议 + if (GetKeysDown(KeyCode.Return, KeyCode.M, KeyCode.LeftShift) && GameStates.IsInGame) + { + if (GameStates.IsMeeting) MeetingHud.Instance.RpcClose(); + else PlayerControl.LocalPlayer.NoCheckStartMeeting(null, true); + } + //立即开始 + if (Input.GetKeyDown(KeyCode.LeftShift) && GameStates.IsCountDown) + { + Logger.Info("倒计时修改为0", "KeyCommand"); + GameStartManager.Instance.countDownTimer = 0; + } + + //倒计时取消 + if (Input.GetKeyDown(KeyCode.C) && GameStates.IsCountDown) + { + Logger.Info("重置倒计时", "KeyCommand"); + GameStartManager.Instance.ResetStartState(); + Logger.SendInGame(Translator.GetString("CancelStartCountDown")); + } + //显示当前有效设置的说明 + if (GetKeysDown(KeyCode.N, KeyCode.LeftShift, KeyCode.LeftControl)) + { + Main.isChatCommand = true; + Utils.ShowActiveSettingsHelp(); + } + //显示当前有效设置 + if (GetKeysDown(KeyCode.N, KeyCode.LeftControl) && !Input.GetKey(KeyCode.LeftShift)) + { + Main.isChatCommand = true; + Utils.ShowActiveSettings(); + } + //将 TOHE 选项设置为默认值 + if (GetKeysDown(KeyCode.Delete, KeyCode.LeftControl)) + { + OptionItem.AllOptions.ToArray().Where(x => x.Id > 0).Do(x => x.SetValueNoRpc(x.DefaultValue)); + Logger.SendInGame(Translator.GetString("RestTOHESetting")); + if (!(!AmongUsClient.Instance.AmHost || PlayerControl.AllPlayerControls.Count <= 1 || (AmongUsClient.Instance.AmHost == false && PlayerControl.LocalPlayer == null))) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RestTOHESetting, SendOption.Reliable, -1); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + OptionShower.GetText(); + } + //放逐自己 + if (GetKeysDown(KeyCode.Return, KeyCode.E, KeyCode.LeftShift) && GameStates.IsInGame) + { + PlayerControl.LocalPlayer.Data.IsDead = true; + Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason = PlayerState.DeathReason.etc; + PlayerControl.LocalPlayer.RpcExileV2(); + Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].SetDead(); + Utils.SendMessage(Translator.GetString("HostKillSelfByCommand"), title: $"{Translator.GetString("DefaultSystemMessageTitle")}"); + } + //切换日志是否也在游戏中输出 + if (GetKeysDown(KeyCode.F2, KeyCode.LeftControl)) + { + Logger.isAlsoInGame = !Logger.isAlsoInGame; + Logger.SendInGame($"游戏中输出日志:{Logger.isAlsoInGame}"); + } + + //--下面是调试模式的命令--// + if (!DebugModeManager.IsDebugMode) return; + + //杀戮闪烁 + if (GetKeysDown(KeyCode.Return, KeyCode.F, KeyCode.LeftShift)) + { + Utils.FlashColor(new(1f, 0f, 0f, 0.3f)); + if (Constants.ShouldPlaySfx()) RPC.PlaySound(PlayerControl.LocalPlayer.PlayerId, Sounds.KillSound); + } + + //实名投票 + if (GetKeysDown(KeyCode.Return, KeyCode.V, KeyCode.LeftShift) && GameStates.IsMeeting && !GameStates.IsOnlineGame) + { + MeetingHud.Instance.RpcClearVote(AmongUsClient.Instance.ClientId); + } + + //打开飞艇所有的门 + if (GetKeysDown(KeyCode.Return, KeyCode.D, KeyCode.LeftShift) && GameStates.IsInGame) + { + ShipStatus.Instance.RpcRepairSystem(SystemTypes.Doors, 79); + ShipStatus.Instance.RpcRepairSystem(SystemTypes.Doors, 80); + ShipStatus.Instance.RpcRepairSystem(SystemTypes.Doors, 81); + ShipStatus.Instance.RpcRepairSystem(SystemTypes.Doors, 82); + } + + //将击杀冷却设定为0秒 + if (GetKeysDown(KeyCode.Return, KeyCode.K, KeyCode.LeftShift) && GameStates.IsInGame) + { + PlayerControl.LocalPlayer.Data.Object.SetKillTimer(0f); + } + + //完成你的所有任务 + if (GetKeysDown(KeyCode.Return, KeyCode.T, KeyCode.LeftShift) && GameStates.IsInGame) + { + foreach (var task in PlayerControl.LocalPlayer.myTasks) + PlayerControl.LocalPlayer.RpcCompleteTask(task.Id); + } + + //同步设置 + if (Input.GetKeyDown(KeyCode.Y)) + { + RPC.SyncCustomSettingsRPC(); + Logger.SendInGame(Translator.GetString("SyncCustomSettingsRPC")); + } + + //入门测试 + if (Input.GetKeyDown(KeyCode.G)) + { + HudManager.Instance.StartCoroutine(HudManager.Instance.CoFadeFullScreen(Color.clear, Color.black)); + HudManager.Instance.StartCoroutine(DestroyableSingleton.Instance.CoShowIntro()); + } + //任务数显示切换 + if (Input.GetKeyDown(KeyCode.Equals)) + { + Main.VisibleTasksCount = !Main.VisibleTasksCount; + DestroyableSingleton.Instance.Notifier.AddItem("VisibleTaskCountが" + Main.VisibleTasksCount.ToString() + "に変更されました。"); + } + + //获取现在的坐标 + if (Input.GetKeyDown(KeyCode.I)) + Logger.Info(PlayerControl.LocalPlayer.GetTruePosition().ToString(), "GetLocalPlayerPos"); + + //マスゲーム用コード + if (Input.GetKeyDown(KeyCode.C)) + { + foreach (var pc in PlayerControl.AllPlayerControls) + { + if (!pc.AmOwner) pc.MyPhysics.RpcEnterVent(2); + } + } + if (Input.GetKeyDown(KeyCode.V)) + { + Vector2 pos = PlayerControl.LocalPlayer.NetTransform.transform.position; + foreach (var pc in PlayerControl.AllPlayerControls) + { + if (!pc.AmOwner) + { + pc.NetTransform.RpcSnapTo(pos); + pos.x += 0.5f; + } + } + } + if (Input.GetKeyDown(KeyCode.B)) + { + foreach (var pc in PlayerControl.AllPlayerControls) + { + if (!pc.AmOwner) pc.MyPhysics.RpcExitVent(2); + } + } + if (Input.GetKeyDown(KeyCode.N)) + { + VentilationSystem.Update(VentilationSystem.Operation.StartCleaning, 0); + } + //マスゲーム用コード終わり + } + + private static bool GetKeysDown(params KeyCode[] keys) + { + if (keys.Any(k => Input.GetKeyDown(k)) && keys.All(k => Input.GetKey(k))) + { + Logger.Info($"快捷键:{keys.Where(k => Input.GetKeyDown(k)).First()} in [{string.Join(",", keys)}]", "GetKeysDown"); + return true; + } + return false; + } + + private static bool ORGetKeysDown(params KeyCode[] keys) => keys.Any(k => Input.GetKeyDown(k)); +} + +[HarmonyPatch(typeof(ConsoleJoystick), nameof(ConsoleJoystick.HandleHUD))] +internal class ConsoleJoystickHandleHUDPatch +{ + public static void Postfix() + { + HandleHUDPatch.Postfix(ConsoleJoystick.player); + } +} +[HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.HandleHud))] +internal class KeyboardJoystickHandleHUDPatch +{ + public static void Postfix() + { + HandleHUDPatch.Postfix(KeyboardJoystick.player); + } +} + +internal class HandleHUDPatch +{ + public static void Postfix(Rewired.Player player) + { + if (player.GetButtonDown(8) && // 8:キルボタンのactionId + PlayerControl.LocalPlayer.Data?.Role?.IsImpostor == false && + PlayerControl.LocalPlayer.CanUseKillButton()) + { + DestroyableSingleton.Instance.KillButton.DoClick(); + } + if (player.GetButtonDown(50) && // 50:インポスターのベントボタンのactionId + PlayerControl.LocalPlayer.Data?.Role?.IsImpostor == false && + PlayerControl.LocalPlayer.CanUseImpostorVentButton()) + { + DestroyableSingleton.Instance.ImpostorVentButton.DoClick(); + } + } +} \ No newline at end of file diff --git a/Patches/CredentialsPatch.cs b/Patches/CredentialsPatch.cs new file mode 100644 index 000000000..29872849a --- /dev/null +++ b/Patches/CredentialsPatch.cs @@ -0,0 +1,226 @@ +using HarmonyLib; +using System.Text; +using TMPro; +using UnityEngine; + +using static TOHE.Translator; + +namespace TOHE; + +[HarmonyPatch(typeof(PingTracker), nameof(PingTracker.Update))] +internal class PingTrackerUpdatePatch +{ + private static readonly StringBuilder sb = new(); + + private static void Postfix(PingTracker __instance) + { + __instance.text.alignment = TMPro.TextAlignmentOptions.TopRight; + + sb.Clear(); + + sb.Append(Main.credentialsText); + + var ping = AmongUsClient.Instance.Ping; + string color = "#ff4500"; + if (ping < 30) color = "#44dfcc"; + else if (ping < 100) color = "#7bc690"; + else if (ping < 200) color = "#f3920e"; + else if (ping < 400) color = "#ff146e"; + sb.Append($"\r\n").Append($"Ping: {ping} ms"); + + if (Options.NoGameEnd.GetBool()) sb.Append($"\r\n").Append(Utils.ColorString(Color.red, GetString("NoGameEnd"))); + if (Options.AllowConsole.GetBool()) sb.Append($"\r\n").Append(Utils.ColorString(Color.red, GetString("AllowConsole"))); + if (!GameStates.IsModHost) sb.Append($"\r\n").Append(Utils.ColorString(Color.red, GetString("Warning.NoModHost"))); + if (DebugModeManager.IsDebugMode) sb.Append("\r\n").Append(Utils.ColorString(Color.green, GetString("DebugMode"))); + if (Options.LowLoadMode.GetBool()) sb.Append("\r\n").Append(Utils.ColorString(Color.green, GetString("LowLoadMode"))); + if (Options.GuesserMode.GetBool()) sb.Append("\r\n").Append(Utils.ColorString(Color.yellow, GetString("GuesserMode"))); + + var offset_x = 1.2f; //右端からのオフセット + if (HudManager.InstanceExists && HudManager._instance.Chat.chatButton.active) offset_x += 0.8f; //チャットボタンがある場合の追加オフセット + if (FriendsListManager.InstanceExists && FriendsListManager._instance.FriendsListButton.Button.active) offset_x += 0.8f; //フレンドリストボタンがある場合の追加オフセット + __instance.GetComponent().DistanceFromEdge = new Vector3(offset_x, 0f, 0f); + + __instance.text.text = sb.ToString(); + } +} +[HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))] +internal class VersionShowerStartPatch +{ + //public static GameObject OVersionShower; + //private static TextMeshPro SpecialEventText; + //private static TextMeshPro VisitText; + + private static void Postfix(VersionShower __instance) + { + Main.credentialsText = $"\r\n{Main.ModName} v{Main.PluginDisplayVersion}"; + // Main.credentialsText = $"\r\nTOHE SolarLoonieEdit v{Main.PluginDisplayVersion}"; + if (Main.IsAprilFools) Main.credentialsText = $"\r\nTown Of Host v11.45.14"; +#if DEBUG + // Main.credentialsText += $"\r\nModified by Loonie"; + Main.credentialsText += $"\r\nBy KARPED1EM & Loonie"; +#endif + +#if RELEASE + string additionalCredentials = GetString("TextBelowVersionText"); + if (additionalCredentials != null && additionalCredentials != "*TextBelowVersionText") + { + Main.credentialsText += $"\n{additionalCredentials}"; + } +#endif + //var credentials = Object.Instantiate(__instance.text); + //credentials.text = Main.credentialsText; + //credentials.alignment = TextAlignmentOptions.TopRight; + //credentials.transform.position = new Vector3(3.2f, 2.5f, 0); + + //ErrorText.Create(__instance.text); + //if (Main.hasArgumentException && ErrorText.Instance != null) + // ErrorText.Instance.AddError(ErrorCode.Main_DictionaryError); + + //if (SpecialEventText == null) + //{ + // SpecialEventText = Object.Instantiate(__instance.text); + // SpecialEventText.text = ""; + // SpecialEventText.color = Color.white; + // SpecialEventText.fontSize += 2.5f; + // SpecialEventText.alignment = TextAlignmentOptions.Top; + // SpecialEventText.transform.position = new Vector3(0, 0.5f, 0); + //} + //SpecialEventText.enabled = TitleLogoPatch.amongUsLogo != null; + //if (Main.IsInitialRelease) + //{ + // SpecialEventText.text = $"Happy Birthday to {Main.ModName}!"; + // ColorUtility.TryParseHtmlString(Main.ModColor, out var col); + // SpecialEventText.color = col; + //} + /*else if (!Main.IsAprilFools) + { + SpecialEventText.text = $"{Main.MainMenuText}"; + SpecialEventText.fontSize = 0.9f; + SpecialEventText.color = Color.white; + SpecialEventText.alignment = TextAlignmentOptions.TopRight; + SpecialEventText.transform.position = new Vector3(4.6f, 2.725f, 0); + } + + if ((OVersionShower = GameObject.Find("VersionShower")) != null && !Main.IsAprilFools) + { + OVersionShower.transform.localScale = new Vector3(0.6f, 0.6f, 1f); + OVersionShower.transform.position = new Vector3(-7.3f, 3.9f, 0f); + if (TitleLogoPatch.amongUsLogo != null) + { + if (VisitText == null && ModUpdater.visit > 0) + { + VisitText = Object.Instantiate(__instance.text); + VisitText.text = string.Format(GetString("TOHEVisitorCount"), Main.ModColor, ModUpdater.visit); + VisitText.color = Color.white; + VisitText.fontSize = 1.2f; + //VisitText.alignment = TMPro.TextAlignmentOptions.Top; + OVersionShower.transform.localScale = new Vector3(0.6f, 0.6f, 1f); + VisitText.transform.position = new Vector3(-5.3f, 2.75f, 0f); + } + } + }*/ + } +} + +[HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] +internal class TitleLogoPatch +{ + public static GameObject Ambience; + public static GameObject amongUsLogo; + //public static GameObject PlayLocalButton; + //public static GameObject PlayOnlineButton; + //public static GameObject HowToPlayButton; + //public static GameObject FreePlayButton; + //public static GameObject BottomButtons; + + private static void Postfix(MainMenuManager __instance) + { + /*if (Main.IsAprilFools) + { + if ((amongUsLogo = GameObject.Find("bannerLogo_AmongUs")) != null) + { + amongUsLogo.transform.localScale *= 0.4f; + amongUsLogo.transform.position += Vector3.up * 0.25f; + } + + var tohLogo = new GameObject("titleLogo_TOH"); + tohLogo.transform.position = Vector3.up; + tohLogo.transform.localScale *= 1.2f; + var renderer = tohLogo.AddComponent(); + renderer.sprite = Utils.LoadSprite("TOHE.Resources.Images.TownOfHost-Logo.png", 300f); + + return; + }*/ + + if ((amongUsLogo = GameObject.Find("bannerLogo_AmongUs")) != null) + { + amongUsLogo.transform.localScale *= 0.4f; + amongUsLogo.transform.position += Vector3.up * 0.25f; + } + + /* if ((PlayLocalButton = GameObject.Find("PlayLocalButton")) != null) + { + PlayLocalButton.transform.localScale = new Vector3(0.7f, 0.7f, 0.7f); + PlayLocalButton.transform.position = new Vector3(-0.76f, -2.1f, 0f); + } + + if ((PlayOnlineButton = GameObject.Find("PlayOnlineButton")) != null) + { + PlayOnlineButton.transform.localScale = new Vector3(0.7f, 0.7f, 0.7f); + PlayOnlineButton.transform.position = new Vector3(0.725f, -2.1f, 0f); + } + + if ((HowToPlayButton = GameObject.Find("HowToPlayButton")) != null) + { + HowToPlayButton.transform.localScale = new Vector3(0.7f, 0.7f, 0.7f); + HowToPlayButton.transform.position = new Vector3(-2.225f, -2.175f, 0f); + } + + if ((FreePlayButton = GameObject.Find("FreePlayButton")) != null) + { + FreePlayButton.transform.localScale = new Vector3(0.7f, 0.7f, 0.7f); + FreePlayButton.transform.position = new Vector3(2.1941f, -2.175f, 0f); + } + + if ((BottomButtons = GameObject.Find("BottomButtons")) != null) + { + BottomButtons.transform.localScale = new Vector3(0.7f, 0.7f, 1f); + BottomButtons.transform.position = new Vector3(0f, -2.71f, 0f); + }*/ + + /* if ((Ambience = GameObject.Find("Ambience")) != null) + { + Ambience.SetActive(false); + var CustomBG = new GameObject("CustomBG"); + CustomBG.transform.position = new Vector3(2.095f, -0.25f, 520f); + var bgRenderer = CustomBG.AddComponent(); + bgRenderer.sprite = Utils.LoadSprite("TOHE.Resources.Images.TOHE-BG.jpg", 245f); + } */ + if ((Ambience = GameObject.Find("Ambience")) != null) + { + Ambience.SetActive(false); + var CustomBG = new GameObject("CustomBG"); + CustomBG.transform.position = new Vector3(2.095f, -0.25f, 520f); + var bgRenderer = CustomBG.AddComponent(); + bgRenderer.sprite = Utils.LoadSprite("TOHE.Resources.Images.TOHE-BG.jpg", 245f); + } + } +} +[HarmonyPatch(typeof(ModManager), nameof(ModManager.LateUpdate))] +internal class ModManagerLateUpdatePatch +{ + public static void Prefix(ModManager __instance) + { + __instance.ShowModStamp(); + + LateTask.Update(Time.deltaTime); + CheckMurderPatch.Update(); + } + public static void Postfix(ModManager __instance) + { + var offset_y = HudManager.InstanceExists ? 1.6f : 0.9f; + __instance.ModStamp.transform.position = AspectPosition.ComputeWorldPosition( + __instance.localCamera, AspectPosition.EdgeAlignments.RightTop, + new Vector3(0.4f, offset_y, __instance.localCamera.nearClipPlane + 0.1f)); + } +} \ No newline at end of file diff --git a/Patches/DisconnectPatch.cs b/Patches/DisconnectPatch.cs new file mode 100644 index 000000000..ed58f70f4 --- /dev/null +++ b/Patches/DisconnectPatch.cs @@ -0,0 +1,73 @@ +using HarmonyLib; +using static TOHE.Translator; + +namespace TOHE; + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnDisconnected))] +internal class OnDisconnectedPatch +{ + public static void Postfix(AmongUsClient __instance) + { + Main.VisibleTasksCount = false; + } +} + +/* [HarmonyPatch(typeof(DisconnectPopup), nameof(DisconnectPopup.DoShow))] +internal class ShowDisconnectPopupPatch +{ + public static DisconnectReasons Reason; + public static string StringReason; + public static void Postfix(DisconnectPopup __instance) + { + new LateTask(() => + { + if (__instance != null) + { + switch (Reason) + { + case DisconnectReasons.Hacking: + __instance.SetText(GetString("DCNotify.Hacking")); + break; + case DisconnectReasons.Banned: + __instance.SetText(GetString("DCNotify.Banned")); + break; + case DisconnectReasons.Kicked: + __instance.SetText(GetString("DCNotify.Kicked")); + break; + case DisconnectReasons.GameNotFound: + __instance.SetText(GetString("DCNotify.GameNotFound")); + break; + case DisconnectReasons.GameStarted: + __instance.SetText(GetString("DCNotify.GameStarted")); + break; + case DisconnectReasons.GameFull: + __instance.SetText(GetString("DCNotify.GameFull")); + break; + case DisconnectReasons.IncorrectVersion: + __instance.SetText(GetString("DCNotify.IncorrectVersion")); + break; + case DisconnectReasons.LobbyInactivity: + __instance.SetText(GetString("DCNotify.Inactivity")); + break; + case DisconnectReasons.NotAuthorized: + __instance.SetText(GetString("DCNotify.Auth")); + break; + case DisconnectReasons.DuplicateConnectionDetected: + __instance.SetText(GetString("DCNotify.DupeLogin")); + break; + case DisconnectReasons.InvalidGameOptions: + __instance.SetText(GetString("DCNotify.InvalidSettings")); + break; + case DisconnectReasons.Error: + //if (StringReason.Contains("Couldn't find self")) __instance.SetText(GetString("DCNotify.DCFromServer")); + if (StringReason.Contains("Failed to send message")) __instance.SetText(GetString("DCNotify.DCFromServer")); + break; + case DisconnectReasons.Custom: + if (StringReason.Contains("Reliable packet")) __instance.SetText(GetString("DCNotify.DCFromServer")); + else if (StringReason.Contains("remote has not responded to")) __instance.SetText(GetString("DCNotify.DCFromServer")); + break; + } + } + }, 0.01f, "Override Disconnect Text"); + } +} */ \ No newline at end of file diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs new file mode 100644 index 000000000..092d373e1 --- /dev/null +++ b/Patches/ExilePatch.cs @@ -0,0 +1,209 @@ +using AmongUs.Data; +using HarmonyLib; +using System.Linq; +using TOHE.Roles.Impostor; +using TOHE.Roles.Neutral; + +namespace TOHE; + +class ExileControllerWrapUpPatch +{ + public static GameData.PlayerInfo AntiBlackout_LastExiled; + [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))] + class BaseExileControllerPatch + { + public static void Postfix(ExileController __instance) + { + try + { + WrapUpPostfix(__instance.exiled); + } + finally + { + WrapUpFinalizer(__instance.exiled); + } + } + } + + [HarmonyPatch(typeof(AirshipExileController), nameof(AirshipExileController.WrapUpAndSpawn))] + class AirshipExileControllerPatch + { + public static void Postfix(AirshipExileController __instance) + { + try + { + WrapUpPostfix(__instance.exiled); + } + finally + { + WrapUpFinalizer(__instance.exiled); + } + } + } + static void WrapUpPostfix(GameData.PlayerInfo exiled) + { + if (AntiBlackout.OverrideExiledPlayer) + { + exiled = AntiBlackout_LastExiled; + } + + bool DecidedWinner = false; + if (!AmongUsClient.Instance.AmHost) return; //ホスト以外はこれ以降の処理を実行しません + AntiBlackout.RestoreIsDead(doSend: false); + if (!Collector.CollectorWin(false) && exiled != null) //判断集票者胜利 + { + //霊界用暗転バグ対処 + if (!AntiBlackout.OverrideExiledPlayer && Main.ResetCamPlayerList.Contains(exiled.PlayerId)) + exiled.Object?.ResetPlayerCam(1f); + + exiled.IsDead = true; + Main.PlayerStates[exiled.PlayerId].deathReason = PlayerState.DeathReason.Vote; + var role = exiled.GetCustomRole(); + + //判断冤罪师胜利 + if (Main.AllPlayerControls.Any(x => x.Is(CustomRoles.Innocent) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == exiled.PlayerId)) + { + if (!Options.InnocentCanWinByImp.GetBool() && role.IsImpostor()) + { + Logger.Info("冤罪的目标是内鬼,非常可惜啊", "Exeiled Winner Check"); + } + else + { + if (DecidedWinner) CustomWinnerHolder.ShiftWinnerAndSetWinner(CustomWinner.Innocent); + else CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Innocent); + Main.AllPlayerControls.Where(x => x.Is(CustomRoles.Innocent) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == exiled.PlayerId) + .Do(x => CustomWinnerHolder.WinnerIds.Add(x.PlayerId)); + DecidedWinner = true; + } + } + + //判断小丑胜利 (EAC封禁名单成为小丑达成胜利条件无法胜利) + if (role == CustomRoles.Jester) + { + if (DecidedWinner) CustomWinnerHolder.ShiftWinnerAndSetWinner(CustomWinner.Jester); + else CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Jester); + CustomWinnerHolder.WinnerIds.Add(exiled.PlayerId); + DecidedWinner = true; + } + + //判断处刑人胜利 + if (Executioner.CheckExileTarget(exiled, DecidedWinner)) DecidedWinner = true; + if (Lawyer.CheckExileTarget(exiled, DecidedWinner)) DecidedWinner = false; + + //判断恐怖分子胜利 + if (role == CustomRoles.Terrorist) Utils.CheckTerroristWin(exiled); + + if (role == CustomRoles.Devourer) Devourer.OnDevourerDied(exiled.PlayerId); + + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Terrorist) Main.PlayerStates[exiled.PlayerId].SetDead(); + } + if (AmongUsClient.Instance.AmHost && Main.IsFixedCooldown) + Main.RefixCooldownDelay = Options.DefaultKillCooldown - 3f; + + Witch.RemoveSpelledPlayer(); + HexMaster.RemoveHexedPlayer(); + + foreach (var pc in Main.AllPlayerControls) + { + pc.ResetKillCooldown(); + if (Options.MayorHasPortableButton.GetBool() && pc.Is(CustomRoles.Mayor)) + pc.RpcResetAbilityCooldown(); + if (pc.Is(CustomRoles.Warlock)) + { + Main.CursedPlayers[pc.PlayerId] = null; + Main.isCurseAndKill[pc.PlayerId] = false; + //RPC.RpcSyncCurseAndKill(); + } + if (pc.GetCustomRole() is + CustomRoles.Paranoia or + CustomRoles.Veteran or + CustomRoles.Greedier or + CustomRoles.DovesOfNeace or + CustomRoles.QuickShooter or + CustomRoles.Addict or + CustomRoles.ShapeshifterTOHE or + CustomRoles.Wildling or + CustomRoles.Twister or + CustomRoles.Deathpact or + CustomRoles.Dazzler or + CustomRoles.Devourer or + CustomRoles.Nuker or + CustomRoles.Bomber + ) pc.RpcResetAbilityCooldown(); + } + if (Options.RandomSpawn.GetBool() || Options.CurrentGameMode == CustomGameMode.SoloKombat) + { + RandomSpawn.SpawnMap map; + switch (Main.NormalOptions.MapId) + { + case 0: + map = new RandomSpawn.SkeldSpawnMap(); + Main.AllPlayerControls.Do(map.RandomTeleport); + break; + case 1: + map = new RandomSpawn.MiraHQSpawnMap(); + Main.AllPlayerControls.Do(map.RandomTeleport); + break; + case 2: + map = new RandomSpawn.PolusSpawnMap(); + Main.AllPlayerControls.Do(map.RandomTeleport); + break; + } + } + FallFromLadder.Reset(); + Utils.CountAlivePlayers(true); + Utils.AfterMeetingTasks(); + Utils.SyncAllSettings(); + Utils.NotifyRoles(); + } + + static void WrapUpFinalizer(GameData.PlayerInfo exiled) + { + //WrapUpPostfixで例外が発生しても、この部分だけは確実に実行されます。 + if (AmongUsClient.Instance.AmHost) + { + new LateTask(() => + { + exiled = AntiBlackout_LastExiled; + AntiBlackout.SendGameData(); + if (AntiBlackout.OverrideExiledPlayer && // 追放対象が上書きされる状態 (上書きされない状態なら実行不要) + exiled != null && //exiledがnullでない + exiled.Object != null) //exiled.Objectがnullでない + { + exiled.Object.RpcExileV2(); + } + }, 0.5f, "Restore IsDead Task"); + new LateTask(() => + { + Main.AfterMeetingDeathPlayers.Do(x => + { + var player = Utils.GetPlayerById(x.Key); + Logger.Info($"{player.GetNameWithRole()}を{x.Value}で死亡させました", "AfterMeetingDeath"); + Main.PlayerStates[x.Key].deathReason = x.Value; + Main.PlayerStates[x.Key].SetDead(); + player?.RpcExileV2(); + if (x.Value == PlayerState.DeathReason.Suicide) + player?.SetRealKiller(player, true); + if (Main.ResetCamPlayerList.Contains(x.Key)) + player?.ResetPlayerCam(1f); + Utils.AfterPlayerDeathTasks(player); + }); + Main.AfterMeetingDeathPlayers.Clear(); + }, 0.5f, "AfterMeetingDeathPlayers Task"); + } + + GameStates.AlreadyDied |= !Utils.IsAllAlive; + RemoveDisableDevicesPatch.UpdateDisableDevices(); + SoundManager.Instance.ChangeAmbienceVolume(DataManager.Settings.Audio.AmbienceVolume); + Logger.Info("タスクフェイズ開始", "Phase"); + } +} + +[HarmonyPatch(typeof(PbExileController), nameof(PbExileController.PlayerSpin))] +class PolusExileHatFixPatch +{ + public static void Prefix(PbExileController __instance) + { + __instance.Player.cosmetics.hat.transform.localPosition = new(-0.2f, 0.6f, 1.1f); + } +} \ No newline at end of file diff --git a/Patches/GameManagerPatch.cs b/Patches/GameManagerPatch.cs new file mode 100644 index 000000000..b86b437fa --- /dev/null +++ b/Patches/GameManagerPatch.cs @@ -0,0 +1,43 @@ +using HarmonyLib; +using Hazel; + +namespace TOHE; + +[HarmonyPatch(typeof(GameManager), nameof(GameManager.Serialize))] +class GameManagerSerializeFix +{ + public static bool Prefix(GameManager __instance, [HarmonyArgument(0)] MessageWriter writer, [HarmonyArgument(1)] bool initialState, ref bool __result) + { + bool flag = false; + for (int index = 0; index < __instance.LogicComponents.Count; ++index) + { + GameLogicComponent logicComponent = __instance.LogicComponents[index]; + if (initialState || logicComponent.IsDirty) + { + flag = true; + writer.StartMessage((byte)index); + var hasBody = logicComponent.Serialize(writer, initialState); + if (hasBody) writer.EndMessage(); + else writer.CancelMessage(); + logicComponent.ClearDirtyFlag(); + } + } + __instance.ClearDirtyBits(); + __result = flag; + return false; + } +} +[HarmonyPatch(typeof(LogicOptions), nameof(LogicOptions.Serialize))] +class LogicOptionsSerializePatch +{ + public static bool Prefix(LogicOptions __instance, ref bool __result, MessageWriter writer, bool initialState) + { + // 初回以外はブロックし、CustomSyncSettingsでのみ同期する + if (!initialState) + { + __result = false; + return false; + } + else return true; + } +} \ No newline at end of file diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs new file mode 100644 index 000000000..976c98566 --- /dev/null +++ b/Patches/GameOptionsMenuPatch.cs @@ -0,0 +1,372 @@ +using AmongUs.GameOptions; +using HarmonyLib; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using static TOHE.Translator; +using Object = UnityEngine.Object; + +namespace TOHE; + +[HarmonyPatch(typeof(GameSettingMenu), nameof(GameSettingMenu.InitializeOptions))] +public static class GameSettingMenuPatch +{ + public static void Prefix(GameSettingMenu __instance) + { + // Unlocks map/impostor amount changing in online (for testing on your custom servers) + // オンラインモードで部屋を立て直さなくてもマップを変更できるように変更 + __instance.HideForOnline = new Il2CppReferenceArray(0); + } +} + +[HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Start))] +[HarmonyPriority(Priority.First)] +public static class GameOptionsMenuPatch +{ + public static void Postfix(GameOptionsMenu __instance) + { + foreach (var ob in __instance.Children) + { + switch (ob.Title) + { + case StringNames.GameVotingTime: + ob.Cast().ValidRange = new FloatRange(0, 600); + break; + case StringNames.GameShortTasks: + case StringNames.GameLongTasks: + case StringNames.GameCommonTasks: + ob.Cast().ValidRange = new FloatRange(0, 99); + break; + case StringNames.GameKillCooldown: + ob.Cast().ValidRange = new FloatRange(0, 180); + break; + default: + break; + } + } + var template = Object.FindObjectsOfType().FirstOrDefault(); + if (template == null) return; + + var Tint = GameObject.Find("Tint"); + Tint?.SetActive(false); + + var gameSettings = GameObject.Find("Game Settings"); + if (gameSettings == null) return; + gameSettings.transform.FindChild("GameGroup").GetComponent().ScrollWheelSpeed = 1.3f; + + var gameSettingMenu = Object.FindObjectsOfType().FirstOrDefault(); + if (gameSettingMenu == null) return; + List menus = new() { gameSettingMenu.RegularGameSettings, gameSettingMenu.RolesSettings.gameObject }; + List highlights = new() { gameSettingMenu.GameSettingsHightlight, gameSettingMenu.RolesSettingsHightlight }; + + var roleTab = GameObject.Find("RoleTab"); + var gameTab = GameObject.Find("GameTab"); + List tabs = new() { gameTab, roleTab }; + + foreach (var tab in Enum.GetValues(typeof(TabGroup))) + { + var obj = gameSettings.transform.parent.Find(tab + "Tab"); + if (obj != null) + { + obj.transform.FindChild("../../GameGroup/Text").GetComponent().SetText(GetString("TabGroup." + tab)); + continue; + } + + var tohSettings = Object.Instantiate(gameSettings, gameSettings.transform.parent); + tohSettings.name = tab + "Tab"; + tohSettings.transform.FindChild("BackPanel").transform.localScale = + tohSettings.transform.FindChild("Bottom Gradient").transform.localScale = new Vector3(1.6f, 1f, 1f); + tohSettings.transform.FindChild("BackPanel").transform.localPosition += new Vector3(0.2f, 0f, 0f); + tohSettings.transform.FindChild("Bottom Gradient").transform.localPosition += new Vector3(0.2f, 0f, 0f); + tohSettings.transform.FindChild("Background").transform.localScale = new Vector3(1.8f, 1f, 1f); + tohSettings.transform.FindChild("UI_Scrollbar").transform.localPosition += new Vector3(1.4f, 0f, 0f); + tohSettings.transform.FindChild("UI_ScrollbarTrack").transform.localPosition += new Vector3(1.4f, 0f, 0f); + tohSettings.transform.FindChild("GameGroup/SliderInner").transform.localPosition += new Vector3(-0.3f, 0f, 0f); + var tohMenu = tohSettings.transform.FindChild("GameGroup/SliderInner").GetComponent(); + + //OptionBehaviourを破棄 + tohMenu.GetComponentsInChildren().Do(x => Object.Destroy(x.gameObject)); + + var scOptions = new List(); + foreach (var option in OptionItem.AllOptions) + { + if (option.Tab != (TabGroup)tab) continue; + if (option.OptionBehaviour == null) + { + float yoffset = option.IsText ? 100f : 0f; + var stringOption = Object.Instantiate(template, tohMenu.transform); + scOptions.Add(stringOption); + stringOption.OnValueChanged = new Action((o) => { }); + stringOption.TitleText.text = option.Name; + stringOption.Value = stringOption.oldValue = option.CurrentValue; + stringOption.ValueText.text = option.GetString(); + stringOption.name = option.Name; + stringOption.transform.FindChild("Background").localScale = new Vector3(1.6f, 1f, 1f); + stringOption.transform.FindChild("Plus_TMP").localPosition += new Vector3(1.4f, yoffset, 0f); + stringOption.transform.FindChild("Minus_TMP").localPosition += new Vector3(1.0f, yoffset, 0f); + stringOption.transform.FindChild("Value_TMP").localPosition += new Vector3(1.2f, yoffset, 0f); + stringOption.transform.FindChild("Value_TMP").GetComponent().sizeDelta = new Vector2(1.6f, 0.26f); + stringOption.transform.FindChild("Title_TMP").localPosition += new Vector3(option.IsText ? 0.25f : 0.1f, option.IsText ? -0.1f : 0f, 0f); + stringOption.transform.FindChild("Title_TMP").GetComponent().sizeDelta = new Vector2(5.5f, 0.37f); + + option.OptionBehaviour = stringOption; + } + option.OptionBehaviour.gameObject.SetActive(true); + } + tohMenu.Children = scOptions.ToArray(); + tohSettings.gameObject.SetActive(false); + menus.Add(tohSettings.gameObject); + + var tohTab = Object.Instantiate(roleTab, roleTab.transform.parent); + tohTab.transform.FindChild("Hat Button").FindChild("Icon").GetComponent().sprite = Utils.LoadSprite($"TOHE.Resources.Images.TabIcon_{tab}.png", 100f); + tabs.Add(tohTab); + var tohTabHighlight = tohTab.transform.FindChild("Hat Button").FindChild("Tab Background").GetComponent(); + highlights.Add(tohTabHighlight); + } + + for (var i = 0; i < tabs.Count; i++) + { + tabs[i].transform.localPosition = new(0.8f * (i - 1) - tabs.Count / 3f, tabs[i].transform.localPosition.y, tabs[i].transform.localPosition.z); + var button = tabs[i].GetComponentInChildren(); + if (button == null) continue; + var copiedIndex = i; + button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent(); + Action value = () => + { + for (var j = 0; j < menus.Count; j++) + { + menus[j].SetActive(j == copiedIndex); + highlights[j].enabled = j == copiedIndex; + } + }; + button.OnClick.AddListener(value); + } + } +} + +[HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Update))] +public class GameOptionsMenuUpdatePatch +{ + private static float _timer = 1f; + + public static void Postfix(GameOptionsMenu __instance) + { + if (__instance.transform.parent.parent.name == "Game Settings") return; + foreach (var tab in Enum.GetValues(typeof(TabGroup))) + { + string tabcolor = ""; + tabcolor = tab switch + { + TabGroup.SystemSettings => Main.ModColor, + TabGroup.GameSettings => "#59ef83", + TabGroup.TaskSettings => "#EF59AF", + TabGroup.ImpostorRoles => "#f74631", + TabGroup.CrewmateRoles => "#8cffff", + TabGroup.NeutralRoles => "#7f8c8d", + TabGroup.Addons => "#ff9ace", + TabGroup.OtherRoles => "#76b8e0", + // TabGroup.CovenRoles => "#8e44ad", + _ => "#ffffff", + }; + if (__instance.transform.parent.parent.name != tab + "Tab") continue; + __instance.transform.FindChild("../../GameGroup/Text").GetComponent().SetText($"" + GetString("TabGroup." + tab) + ""); + + _timer += Time.deltaTime; + if (_timer < 0.1f) return; + _timer = 0f; + + float numItems = __instance.Children.Length; + var offset = 2.7f; + + foreach (var option in OptionItem.AllOptions) + { + if ((TabGroup)tab != option.Tab) continue; + if (option?.OptionBehaviour == null || option.OptionBehaviour.gameObject == null) continue; + + var enabled = true; + var parent = option.Parent; + + enabled = AmongUsClient.Instance.AmHost && + !option.IsHiddenOn(Options.CurrentGameMode); + + var opt = option.OptionBehaviour.transform.Find("Background").GetComponent(); + opt.size = new(5.0f, 0.45f); + while (parent != null && enabled) + { + enabled = parent.GetBool() && !parent.IsHiddenOn(Options.CurrentGameMode); + parent = parent.Parent; + opt.color = new(0f, 1f, 0f); + opt.size = new(4.8f, 0.45f); + opt.transform.localPosition = new Vector3(0.11f, 0f); + option.OptionBehaviour.transform.Find("Title_TMP").transform.localPosition = new Vector3(-1.08f, 0f); + option.OptionBehaviour.transform.FindChild("Title_TMP").GetComponent().sizeDelta = new Vector2(5.1f, 0.28f); + if (option.Parent?.Parent != null) + { + opt.color = new(0f, 0f, 1f); + opt.size = new(4.6f, 0.45f); + opt.transform.localPosition = new Vector3(0.24f, 0f); + option.OptionBehaviour.transform.Find("Title_TMP").transform.localPosition = new Vector3(-0.88f, 0f); + option.OptionBehaviour.transform.FindChild("Title_TMP").GetComponent().sizeDelta = new Vector2(4.9f, 0.28f); + if (option.Parent?.Parent?.Parent != null) + { + opt.color = new(1f, 0f, 0f); + opt.size = new(4.4f, 0.45f); + opt.transform.localPosition = new Vector3(0.37f, 0f); + option.OptionBehaviour.transform.Find("Title_TMP").transform.localPosition = new Vector3(-0.68f, 0f); + option.OptionBehaviour.transform.FindChild("Title_TMP").GetComponent().sizeDelta = new Vector2(4.7f, 0.28f); + } + } + } + + if (option.IsText) + { + opt.color = new(0, 0, 0); + opt.transform.localPosition = new(100f, 100f, 100f); + } + + option.OptionBehaviour.gameObject.SetActive(enabled); + if (enabled) + { + offset -= option.IsHeader ? 0.7f : 0.5f; + option.OptionBehaviour.transform.localPosition = new Vector3( + option.OptionBehaviour.transform.localPosition.x, + offset, + option.OptionBehaviour.transform.localPosition.z); + + if (option.IsHeader) + { + numItems += 0.3f; + } + } + else + { + numItems--; + } + } + __instance.GetComponentInParent().ContentYBounds.max = (-offset) - 1.5f; + } + } +} + +[HarmonyPatch(typeof(StringOption), nameof(StringOption.OnEnable))] +public class StringOptionEnablePatch +{ + public static bool Prefix(StringOption __instance) + { + var option = OptionItem.AllOptions.FirstOrDefault(opt => opt.OptionBehaviour == __instance); + if (option == null) return true; + + __instance.OnValueChanged = new Action((o) => { }); + __instance.TitleText.text = option.GetName(); + __instance.Value = __instance.oldValue = option.CurrentValue; + __instance.ValueText.text = option.GetString(); + + return false; + } +} + +[HarmonyPatch(typeof(StringOption), nameof(StringOption.Increase))] +public class StringOptionIncreasePatch +{ + public static bool Prefix(StringOption __instance) + { + var option = OptionItem.AllOptions.FirstOrDefault(opt => opt.OptionBehaviour == __instance); + if (option == null) return true; + + option.SetValue(option.CurrentValue + 1); + return false; + } + public static void Postfix(StringOption __instance) => OptionShower.GetText(); +} + +[HarmonyPatch(typeof(StringOption), nameof(StringOption.Decrease))] +public class StringOptionDecreasePatch +{ + public static bool Prefix(StringOption __instance) + { + var option = OptionItem.AllOptions.FirstOrDefault(opt => opt.OptionBehaviour == __instance); + if (option == null) return true; + + option.SetValue(option.CurrentValue - 1); + return false; + } + public static void Postfix(StringOption __instance) => OptionShower.GetText(); +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSyncSettings))] +public class RpcSyncSettingsPatch +{ + public static void Postfix() + { + OptionItem.SyncAllOptions(); + } +} +[HarmonyPatch(typeof(RolesSettingsMenu), nameof(RolesSettingsMenu.Start))] +public static class RolesSettingsMenuPatch +{ + public static void Postfix(RolesSettingsMenu __instance) + { + foreach (var ob in __instance.Children) + { + switch (ob.Title) + { + case StringNames.EngineerCooldown: + ob.Cast().ValidRange = new FloatRange(0, 180); + break; + case StringNames.ShapeshifterCooldown: + ob.Cast().ValidRange = new FloatRange(0, 180); + break; + default: + break; + } + } + } +} +[HarmonyPatch(typeof(NormalGameOptionsV07), nameof(NormalGameOptionsV07.SetRecommendations))] +public static class SetRecommendationsPatch +{ + public static bool Prefix(NormalGameOptionsV07 __instance, int numPlayers, bool isOnline) + { + numPlayers = Mathf.Clamp(numPlayers, 4, 15); + __instance.PlayerSpeedMod = __instance.MapId == 4 ? 1.5f : 1.25f; + __instance.CrewLightMod = 1.0f; + __instance.ImpostorLightMod = 1.75f; + __instance.KillCooldown = 27.5f; + __instance.NumCommonTasks = 2; + __instance.NumLongTasks = 1; + __instance.NumShortTasks = 2; + __instance.NumEmergencyMeetings = 3; + if (!isOnline) + __instance.NumImpostors = NormalGameOptionsV07.RecommendedImpostors[numPlayers]; + __instance.KillDistance = 0; + __instance.DiscussionTime = 0; + __instance.VotingTime = 120; + __instance.IsDefaults = true; + __instance.ConfirmImpostor = false; + __instance.VisualTasks = false; + + __instance.roleOptions.SetRoleRate(RoleTypes.Shapeshifter, 0, 0); + __instance.roleOptions.SetRoleRate(RoleTypes.Scientist, 0, 0); + __instance.roleOptions.SetRoleRate(RoleTypes.GuardianAngel, 0, 0); + __instance.roleOptions.SetRoleRate(RoleTypes.Engineer, 0, 0); + __instance.roleOptions.SetRoleRecommended(RoleTypes.Shapeshifter); + __instance.roleOptions.SetRoleRecommended(RoleTypes.Scientist); + __instance.roleOptions.SetRoleRecommended(RoleTypes.GuardianAngel); + __instance.roleOptions.SetRoleRecommended(RoleTypes.Engineer); + + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) //SoloKombat + { + __instance.PlayerSpeedMod = 1.75f; + __instance.CrewLightMod = 1f; + __instance.ImpostorLightMod = 1f; + __instance.NumImpostors = 3; + __instance.NumCommonTasks = 0; + __instance.NumLongTasks = 0; + __instance.NumShortTasks = 0; + __instance.KillCooldown = 0f; + } + return false; + } +} \ No newline at end of file diff --git a/Patches/GameOptionsPatch.cs b/Patches/GameOptionsPatch.cs new file mode 100644 index 000000000..d6c1bfeed --- /dev/null +++ b/Patches/GameOptionsPatch.cs @@ -0,0 +1,62 @@ +using AmongUs.GameOptions; +using HarmonyLib; +using static TOHE.Translator; + +namespace TOHE; + +[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.UpdateValuesAndText))] +class ChanceChangePatch +{ + public static void Postfix(RoleOptionSetting __instance) + { + bool forced = true; + string DisableText = $" ({GetString("Disabled")})"; + if (__instance.Role.Role == RoleTypes.Scientist) + { + __instance.TitleText.color = Utils.GetRoleColor(CustomRoles.Scientist); + } + if (__instance.Role.Role == RoleTypes.Engineer) + { + __instance.TitleText.color = Utils.GetRoleColor(CustomRoles.Engineer); + } + if (__instance.Role.Role == RoleTypes.GuardianAngel) + { + //+-ボタン, 設定値, 詳細設定ボタンを非表示 + var tf = __instance.transform; + tf.Find("Count Plus_TMP").gameObject.active + = tf.Find("Chance Minus_TMP").gameObject.active + = tf.Find("Chance Value_TMP").gameObject.active + = tf.Find("Chance Plus_TMP").gameObject.active + = tf.Find("More Options").gameObject.active + = false; + + if (!__instance.TitleText.text.Contains(DisableText)) + __instance.TitleText.text += DisableText; + __instance.TitleText.color = Utils.GetRoleColor(CustomRoles.GuardianAngel); + } + if (__instance.Role.Role == RoleTypes.Shapeshifter) + { + __instance.TitleText.color = Utils.GetRoleColor(CustomRoles.Shapeshifter); + } + + if (forced) + { + __instance.ChanceText.text = DisableText; + } + } +} + +[HarmonyPatch(typeof(GameOptionsManager), nameof(GameOptionsManager.SwitchGameMode))] +class SwitchGameModePatch +{ + public static void Postfix(GameModes gameMode) + { + if (gameMode == GameModes.HideNSeek) + { + ErrorText.Instance.HnSFlag = true; + ErrorText.Instance.AddError(ErrorCode.HnsUnload); + Harmony.UnpatchAll(); + Main.Instance.Unload(); + } + } +} \ No newline at end of file diff --git a/Patches/GameStartManagerPatch.cs b/Patches/GameStartManagerPatch.cs new file mode 100644 index 000000000..f8bf562e2 --- /dev/null +++ b/Patches/GameStartManagerPatch.cs @@ -0,0 +1,282 @@ +using AmongUs.Data; +using AmongUs.GameOptions; +using HarmonyLib; +using InnerNet; +using System; +using System.Collections.Generic; +using System.Linq; +using TMPro; +using UnityEngine; +using TOHE.Roles.Neutral; +using Object = UnityEngine.Object; +using static TOHE.Translator; + +namespace TOHE; + +[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))] +public static class GameStartManagerUpdatePatch +{ + public static void Prefix(GameStartManager __instance) + { + __instance.MinPlayers = 1; + } +} +//タイマーとコード隠し +public class GameStartManagerPatch +{ + private static SpriteRenderer cancelButton; + private static float timer = 600f; + [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Start))] + public class GameStartManagerStartPatch + { + public static TMPro.TextMeshPro HideName; + public static void Postfix(GameStartManager __instance) + { + __instance.GameRoomNameCode.text = GameCode.IntToGameName(AmongUsClient.Instance.GameId); + // Reset lobby countdown timer + timer = 600f; + + HideName = UnityEngine.Object.Instantiate(__instance.GameRoomNameCode, __instance.GameRoomNameCode.transform); + HideName.text = ColorUtility.TryParseHtmlString(Main.HideColor.Value, out _) + ? $"{Main.HideName.Value}" + : $"{Main.HideName.Value}"; + + cancelButton = Object.Instantiate(__instance.StartButton, __instance.transform); + cancelButton.name = "CancelButton"; + var cancelLabel = cancelButton.GetComponentInChildren(); + Object.Destroy(cancelLabel.GetComponent()); + cancelLabel.text = GetString("Cancel"); + cancelButton.transform.localScale = new(0.4f, 0.4f, 1f); + cancelButton.color = Color.red; + cancelButton.transform.localPosition = new(0f, -0.36f, 0f); //new(0f, 0.1f, 0f); + var buttonComponent = cancelButton.GetComponent(); + buttonComponent.OnClick = new(); + buttonComponent.OnClick.AddListener((Action)(() => __instance.ResetStartState())); + cancelButton.gameObject.SetActive(false); + + if (!AmongUsClient.Instance.AmHost) return; + + // Make Public Button + if (ModUpdater.isBroken || (ModUpdater.hasUpdate && ModUpdater.forceUpdate) || !Main.AllowPublicRoom) + { + __instance.MakePublicButton.color = Palette.DisabledClear; + __instance.privatePublicText.color = Palette.DisabledClear; + } + + if (Main.NormalOptions.KillCooldown == 0f) + Main.NormalOptions.KillCooldown = Main.LastKillCooldown.Value; + + AURoleOptions.SetOpt(Main.NormalOptions.Cast()); + if (AURoleOptions.ShapeshifterCooldown == 0f) + AURoleOptions.ShapeshifterCooldown = Main.LastShapeshifterCooldown.Value; + + AURoleOptions.GuardianAngelCooldown = Spiritcaller.SpiritAbilityCooldown.GetFloat(); + } + } + + [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))] + public class GameStartManagerUpdatePatch + { + private static bool update = false; + private static string currentText = ""; + public static float exitTimer = -1f; + private static float minWait, maxWait; + private static int minPlayer; + public static void Prefix(GameStartManager __instance) + { + minWait = Options.MinWaitAutoStart.GetFloat(); + maxWait = Options.MaxWaitAutoStart.GetFloat(); + minPlayer = Options.PlayerAutoStart.GetInt(); + minWait = 600f - minWait * 60f; + maxWait = maxWait * 60f; + // Lobby code + if (DataManager.Settings.Gameplay.StreamerMode) + { + __instance.GameRoomNameCode.color = new(255, 255, 255, 0); + GameStartManagerStartPatch.HideName.enabled = true; + } + else + { + __instance.GameRoomNameCode.color = new(255, 255, 255, 255); + GameStartManagerStartPatch.HideName.enabled = false; + } + if (!AmongUsClient.Instance.AmHost || !GameData.Instance || AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame) return; // Not host or no instance or LocalGame + update = GameData.Instance.PlayerCount != __instance.LastPlayerCount; + + if (Main.AutoStart.Value) + { + Main.updateTime++; + if (Main.updateTime >= 50) + { + Main.updateTime = 0; + if (((GameData.Instance.PlayerCount >= minPlayer && timer <= minWait) || timer <= maxWait) && !GameStates.IsCountDown) + { + GameStartManager.Instance.startState = GameStartManager.StartingStates.Countdown; + GameStartManager.Instance.countDownTimer = 10; + } + } + } + } + public static void Postfix(GameStartManager __instance) + { + if (!AmongUsClient.Instance) return; + + string warningMessage = ""; + if (AmongUsClient.Instance.AmHost) + { + bool canStartGame = true; + List mismatchedPlayerNameList = new(); + foreach (var client in AmongUsClient.Instance.allClients.ToArray()) + { + if (client.Character == null) continue; + var dummyComponent = client.Character.GetComponent(); + if (dummyComponent != null && dummyComponent.enabled) + continue; + if (!MatchVersions(client.Character.PlayerId, true)) + { + canStartGame = false; + mismatchedPlayerNameList.Add(Utils.ColorString(Palette.PlayerColors[client.ColorId], client.Character.Data.PlayerName)); + } + } + if (!canStartGame) + { + __instance.StartButton.gameObject.SetActive(false); + warningMessage = Utils.ColorString(Color.red, string.Format(GetString("Warning.MismatchedVersion"), string.Join(" ", mismatchedPlayerNameList), $"{Main.ModName}")); + } + cancelButton.gameObject.SetActive(__instance.startState == GameStartManager.StartingStates.Countdown); + } + else + { + if (MatchVersions(0, true) || Main.VersionCheat.Value) + exitTimer = 0; + else + { + exitTimer += Time.deltaTime; + if (exitTimer >= 5) + { + exitTimer = 0; + AmongUsClient.Instance.ExitGame(DisconnectReasons.ExitGame); + SceneChanger.ChangeScene("MainMenu"); + } + if (exitTimer != 0) + warningMessage = Utils.ColorString(Color.red, string.Format(GetString("Warning.AutoExitAtMismatchedVersion"), $"{Main.ModName}", Math.Round(5 - exitTimer).ToString())); + } + } + if (warningMessage != "") + { + __instance.GameStartText.text = warningMessage; + __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition + Vector3.up * 2; + } + else + { + __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition; + } + + // Lobby timer + if (!AmongUsClient.Instance.AmHost || !GameData.Instance || AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame) return; + + if (update) currentText = __instance.PlayerCounter.text; + + timer = Mathf.Max(0f, timer -= Time.deltaTime); + int minutes = (int)timer / 60; + int seconds = (int)timer % 60; + string suffix = $" ({minutes:00}:{seconds:00})"; + if (timer <= 60) suffix = Utils.ColorString(Color.red, suffix); + + __instance.PlayerCounter.text = currentText + suffix; + __instance.PlayerCounter.autoSizeTextContainer = true; + } + private static bool MatchVersions(byte playerId, bool acceptVanilla = false) + { + if (!Main.playerVersion.TryGetValue(playerId, out var version)) return acceptVanilla; + return Main.ForkId == version.forkId + && Main.version.CompareTo(version.version) == 0 + && version.tag == $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})"; + } + } + [HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.SetText))] + public static class HiddenTextPatch + { + private static void Postfix(TextBoxTMP __instance) + { + if (__instance.name == "GameIdText") __instance.outputText.text = new string('*', __instance.text.Length); + } + } +} +[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.BeginGame))] +public class GameStartRandomMap +{ + public static bool Prefix(GameStartManager __instance) + { + var invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId); + if (invalidColor.Count() != 0) + { + Logger.SendInGame(GetString("Error.InvalidColorPreventStart")); + var msg = GetString("Error.InvalidColor"); + msg += "\n" + string.Join(",", invalidColor.Select(p => $"{p.GetRealName()}")); + Utils.SendMessage(msg); + return false; + } + + + Options.DefaultKillCooldown = Main.NormalOptions.KillCooldown; + Main.LastKillCooldown.Value = Main.NormalOptions.KillCooldown; + Main.NormalOptions.KillCooldown = 0f; + + var opt = Main.NormalOptions.Cast(); + AURoleOptions.SetOpt(opt); + Main.LastShapeshifterCooldown.Value = AURoleOptions.ShapeshifterCooldown; + AURoleOptions.ShapeshifterCooldown = 0f; + + PlayerControl.LocalPlayer.RpcSyncSettings(GameOptionsManager.Instance.gameOptionsFactory.ToBytes(opt)); + + __instance.ReallyBegin(false); + return false; + } + public static bool Prefix(GameStartRandomMap __instance) + { + bool continueStart = true; + if (Options.RandomMapsMode.GetBool()) + { + var rand = IRandom.Instance; + List RandomMaps = new(); + /*TheSkeld = 0 + MIRAHQ = 1 + Polus = 2 + Dleks = 3 + TheAirship = 4*/ + if (Options.AddedTheSkeld.GetBool()) RandomMaps.Add(0); + if (Options.AddedMiraHQ.GetBool()) RandomMaps.Add(1); + if (Options.AddedPolus.GetBool()) RandomMaps.Add(2); + // if (Options.AddedDleks.GetBool()) RandomMaps.Add(3); + if (Options.AddedTheAirship.GetBool()) RandomMaps.Add(4); + + if (RandomMaps.Count <= 0) return true; + var MapsId = RandomMaps[rand.Next(RandomMaps.Count)]; + Main.NormalOptions.MapId = MapsId; + + } + return continueStart; + } +} +[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.ResetStartState))] +class ResetStartStatePatch +{ + public static void Prefix() + { + if (GameStates.IsCountDown) + { + Main.NormalOptions.KillCooldown = Options.DefaultKillCooldown; + PlayerControl.LocalPlayer.RpcSyncSettings(GameOptionsManager.Instance.gameOptionsFactory.ToBytes(GameOptionsManager.Instance.CurrentGameOptions)); + } + } +} +[HarmonyPatch(typeof(IGameOptionsExtensions), nameof(IGameOptionsExtensions.GetAdjustedNumImpostors))] +class UnrestrictedNumImpostorsPatch +{ + public static bool Prefix(ref int __result) + { + __result = Main.NormalOptions.NumImpostors; + return false; + } +} \ No newline at end of file diff --git a/Patches/GuardAngelPatch.cs b/Patches/GuardAngelPatch.cs new file mode 100644 index 000000000..ae48b017d --- /dev/null +++ b/Patches/GuardAngelPatch.cs @@ -0,0 +1,17 @@ +using HarmonyLib; + +namespace TOHE; +internal class GuardAngelPatch +{ + [HarmonyPatch(typeof(MeetingIntroAnimation), nameof(MeetingIntroAnimation.Start))] + public static class ProtectedRecentlyPatch + { + public static bool Prefix(MeetingIntroAnimation __instance) + { + __instance.ProtectedRecently.active = false; + __instance.ProtectedRecently.transform.localPosition = new UnityEngine.Vector3(100f, 100f, 100f); + __instance.ProtectedRecentlySound = new(); + return true; + } + } +} diff --git a/Patches/HashRandomPatch.cs b/Patches/HashRandomPatch.cs new file mode 100644 index 000000000..d056e6148 --- /dev/null +++ b/Patches/HashRandomPatch.cs @@ -0,0 +1,36 @@ +using HarmonyLib; +using System; + +namespace TOHE; + +[HarmonyPatch(typeof(HashRandom))] +class HashRandomPatch +{ + [HarmonyPatch(nameof(HashRandom.FastNext)), HarmonyPrefix] + static bool FastNext([HarmonyArgument(0)] int maxInt, ref int __result) + { + if (IRandom.Instance is HashRandomWrapper) return true; + + __result = IRandom.Instance.Next(maxInt); + + return false; + } + [HarmonyPatch(nameof(HashRandom.Next), new Type[] { typeof(int) }), HarmonyPrefix] + static bool MaxNext([HarmonyArgument(0)] int maxInt, ref int __result) + { + if (IRandom.Instance is HashRandomWrapper) return true; + + __result = IRandom.Instance.Next(maxInt); + + return false; + } + [HarmonyPatch(nameof(HashRandom.Next), new Type[] { typeof(int), typeof(int) }), HarmonyPrefix] + static bool MinMaxNext([HarmonyArgument(0)] int minInt, [HarmonyArgument(1)] int maxInt, ref int __result) + { + if (IRandom.Instance is HashRandomWrapper) return true; + + __result = IRandom.Instance.Next(minInt, maxInt); + + return false; + } +} \ No newline at end of file diff --git a/Patches/HideBanButtonPatch.cs b/Patches/HideBanButtonPatch.cs new file mode 100644 index 000000000..5c735312f --- /dev/null +++ b/Patches/HideBanButtonPatch.cs @@ -0,0 +1,16 @@ +using HarmonyLib; + +namespace TOHE; + +[HarmonyPatch(typeof(ChatController), nameof(ChatController.Toggle))] +class CancelBanMenuStuckPatch +{ + public static void Prefix(ChatController __instance) + { + if (__instance.IsOpenOrOpening && !__instance.IsAnimating) // (IsOpen==true) == 今から閉じないといけない + { + // BanButtonを非表示にする + __instance.banButton.SetVisible(false); + } + } +} \ No newline at end of file diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs new file mode 100644 index 000000000..35c750502 --- /dev/null +++ b/Patches/HudPatch.cs @@ -0,0 +1,710 @@ +using HarmonyLib; +using Il2CppSystem.Text; +using System.Collections.Generic; +using System.Linq; +using TOHE.Roles.Crewmate; +using TOHE.Roles.Impostor; +using TOHE.Roles.Neutral; +using UnityEngine; +using static TOHE.Translator; + +namespace TOHE; + +[HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] +class HudManagerPatch +{ + public static bool ShowDebugText = false; + public static int LastCallNotifyRolesPerSecond = 0; + public static int NowCallNotifyRolesCount = 0; + public static int LastSetNameDesyncCount = 0; + public static int LastFPS = 0; + public static int NowFrameCount = 0; + public static float FrameRateTimer = 0.0f; + public static TMPro.TextMeshPro LowerInfoText; + public static void Postfix(HudManager __instance) + { + if (!GameStates.IsModHost) return; + var player = PlayerControl.LocalPlayer; + if (player == null) return; + //壁抜け + if (Input.GetKeyDown(KeyCode.LeftControl)) + { + if ((!AmongUsClient.Instance.IsGameStarted || !GameStates.IsOnlineGame) + && player.CanMove) + { + player.Collider.offset = new Vector2(0f, 127f); + } + } + //壁抜け解除 + if (player.Collider.offset.y == 127f) + { + if (!Input.GetKey(KeyCode.LeftControl) || (AmongUsClient.Instance.IsGameStarted && GameStates.IsOnlineGame)) + { + player.Collider.offset = new Vector2(0f, -0.3636f); + } + } + if (GameStates.IsLobby) + { + var POM = GameObject.Find("PlayerOptionsMenu(Clone)"); + __instance.GameSettings.text = POM != null ? "" : OptionShower.GetTextNoFresh(); + __instance.GameSettings.fontSizeMin = + __instance.GameSettings.fontSizeMax = 1f; + } + //ゲーム中でなければ以下は実行されない + if (!AmongUsClient.Instance.IsGameStarted) return; + + Utils.CountAlivePlayers(); + + bool shapeshifting = Main.CheckShapeshift.TryGetValue(player.PlayerId, out bool ss) && ss; + + if (SetHudActivePatch.IsActive) + { + if (player.IsAlive()) + { + //MOD入り用のボタン下テキスト変更 + switch (player.GetCustomRole()) + { + case CustomRoles.Sniper: + Sniper.OverrideShapeText(player.PlayerId); + break; + case CustomRoles.FireWorks: + if (FireWorks.nowFireWorksCount[player.PlayerId] == 0) + __instance.AbilityButton.OverrideText(GetString("FireWorksExplosionButtonText")); + else + __instance.AbilityButton.OverrideText(GetString("FireWorksInstallAtionButtonText")); + break; + case CustomRoles.SerialKiller: + SerialKiller.GetAbilityButtonText(__instance, player); + break; + case CustomRoles.Warlock: + bool curse = Main.isCurseAndKill.TryGetValue(player.PlayerId, out bool wcs) && wcs; + if (!shapeshifting && !curse) + __instance.KillButton.OverrideText(GetString("WarlockCurseButtonText")); + else + __instance.KillButton.OverrideText(GetString("KillButtonText")); + if (!shapeshifting && curse) + __instance.AbilityButton.OverrideText(GetString("WarlockShapeshiftButtonText")); + break; + case CustomRoles.Miner: + __instance.AbilityButton.OverrideText(GetString("MinerTeleButtonText")); + break; + case CustomRoles.Pestilence: + __instance.KillButton.OverrideText(GetString("KillButtonText")); + break; + case CustomRoles.PlagueBearer: + __instance.KillButton.OverrideText(GetString("InfectiousKillButtonText")); + break; + case CustomRoles.Witch: + Witch.GetAbilityButtonText(__instance); + break; + case CustomRoles.HexMaster: + HexMaster.GetAbilityButtonText(__instance); + break; + case CustomRoles.Vampire: + Vampire.SetKillButtonText(); + break; + case CustomRoles.Poisoner: + Poisoner.SetKillButtonText(); + break; + case CustomRoles.Arsonist: + __instance.KillButton.OverrideText(GetString("ArsonistDouseButtonText")); + __instance.ImpostorVentButton.buttonLabelText.text = GetString("ArsonistVentButtonText"); + break; + case CustomRoles.Revolutionist: + __instance.KillButton.OverrideText(GetString("RevolutionistDrawButtonText")); + __instance.ImpostorVentButton.buttonLabelText.text = GetString("RevolutionistVentButtonText"); + break; + case CustomRoles.Farseer: + __instance.KillButton.OverrideText(GetString("FarseerKillButtonText")); + break; + case CustomRoles.Puppeteer: + __instance.KillButton.OverrideText(GetString("PuppeteerOperateButtonText")); + break; + case CustomRoles.NWitch: + __instance.KillButton.OverrideText($"{GetString("WitchControlButtonText")}"); + break; + case CustomRoles.BountyHunter: + BountyHunter.SetAbilityButtonText(__instance); + break; + case CustomRoles.EvilTracker: + EvilTracker.GetAbilityButtonText(__instance, player.PlayerId); + break; + case CustomRoles.Innocent: + __instance.KillButton.OverrideText(GetString("InnocentButtonText")); + break; + case CustomRoles.Capitalism: + __instance.KillButton.OverrideText(GetString("CapitalismButtonText")); + break; + case CustomRoles.Pelican: + __instance.KillButton.OverrideText(GetString("PelicanButtonText")); + break; + case CustomRoles.Counterfeiter: + __instance.KillButton.OverrideText(GetString("CounterfeiterButtonText")); + break; + case CustomRoles.Pursuer: + __instance.KillButton.OverrideText(GetString("PursuerButtonText")); + break; + case CustomRoles.Gangster: + Gangster.SetKillButtonText(player.PlayerId); + break; + case CustomRoles.NSerialKiller: + case CustomRoles.Juggernaut: + case CustomRoles.Jackal: + case CustomRoles.Virus: + case CustomRoles.BloodKnight: + case CustomRoles.SwordsMan: + case CustomRoles.Parasite: + case CustomRoles.Refugee: + case CustomRoles.Traitor: + case CustomRoles.Ritualist: + case CustomRoles.Spiritcaller: + case CustomRoles.DarkHide: + case CustomRoles.Maverick: + __instance.KillButton.OverrideText(GetString("KillButtonText")); + break; + case CustomRoles.Glitch: + __instance.SabotageButton.OverrideText(GetString("HackButtonText")); + break; + case CustomRoles.FFF: + __instance.KillButton.OverrideText(GetString("FFFButtonText")); + break; + case CustomRoles.Medic: + __instance.KillButton.OverrideText(GetString("MedicalerButtonText")); + break; + case CustomRoles.Gamer: + __instance.KillButton.OverrideText(GetString("GamerButtonText")); + break; + case CustomRoles.BallLightning: + __instance.KillButton.OverrideText(GetString("BallLightningButtonText")); + break; + case CustomRoles.Bomber: + case CustomRoles.Nuker: + __instance.AbilityButton.OverrideText(GetString("BomberShapeshiftText")); + break; + case CustomRoles.Twister: + __instance.AbilityButton.OverrideText(GetString("TwisterButtonText")); + break; + case CustomRoles.ImperiusCurse: + __instance.AbilityButton.OverrideText(GetString("ImperiusCurseButtonText")); + break; + case CustomRoles.QuickShooter: + __instance.AbilityButton.OverrideText(GetString("QuickShooterShapeshiftText")); + __instance.AbilityButton.SetUsesRemaining(QuickShooter.ShotLimit.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var qx) ? qx : 0); + break; + case CustomRoles.Provocateur: + __instance.KillButton.OverrideText(GetString("ProvocateurButtonText")); + break; + case CustomRoles.Camouflager: + __instance.AbilityButton.OverrideText(GetString("CamouflagerShapeshiftText")); + break; + case CustomRoles.OverKiller: + __instance.KillButton.OverrideText(GetString("OverKillerButtonText")); + break; + case CustomRoles.Assassin: + Assassin.SetKillButtonText(player.PlayerId); + Assassin.GetAbilityButtonText(__instance, player.PlayerId); + break; + case CustomRoles.Hacker: + Hacker.GetAbilityButtonText(__instance, player.PlayerId); + break; + case CustomRoles.KB_Normal: + __instance.KillButton.OverrideText(GetString("GamerButtonText")); + break; + case CustomRoles.Cleaner: + __instance.ReportButton.OverrideText(GetString("CleanerReportButtonText")); + break; + case CustomRoles.Medusa: + __instance.KillButton.OverrideText(GetString("KillButtonText")); + __instance.ReportButton.OverrideText(GetString("MedusaReportButtonText")); + break; + case CustomRoles.Vulture: + __instance.ReportButton.OverrideText(GetString("VultureEatButtonText")); + break; + case CustomRoles.Swooper: + __instance.ImpostorVentButton.OverrideText(GetString(Swooper.IsInvis(PlayerControl.LocalPlayer.PlayerId) ? "SwooperRevertVentButtonText" : "SwooperVentButtonText")); + break; + case CustomRoles.Wraith: + __instance.KillButton.OverrideText(GetString("KillButtonText")); + __instance.ImpostorVentButton.OverrideText(GetString(Wraith.IsInvis(PlayerControl.LocalPlayer.PlayerId) ? "WraithRevertVentButtonText" : "WraithVentButtonText")); + break; + case CustomRoles.Chameleon: + __instance.AbilityButton.OverrideText(GetString(Chameleon.IsInvis(PlayerControl.LocalPlayer.PlayerId) ? "ChameleonRevertDisguise" : "ChameleonDisguise")); + break; + case CustomRoles.Mario: + __instance.AbilityButton.buttonLabelText.text = GetString("MarioVentButtonText"); + __instance.AbilityButton.SetUsesRemaining(Options.MarioVentNumWin.GetInt() - (Main.MarioVentCount.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var mx) ? mx : 0)); + break; + case CustomRoles.Veteran: + __instance.AbilityButton.buttonLabelText.text = GetString("VeteranVentButtonText"); + break; + case CustomRoles.TimeMaster: + __instance.AbilityButton.buttonLabelText.text = GetString("TimeMasterVentButtonText"); + break; + case CustomRoles.Grenadier: + __instance.AbilityButton.buttonLabelText.text = GetString("GrenadierVentButtonText"); + break; + case CustomRoles.Mayor: + __instance.AbilityButton.buttonLabelText.text = GetString("MayorVentButtonText"); + break; + case CustomRoles.Paranoia: + __instance.AbilityButton.buttonLabelText.text = GetString("ParanoiaVentButtonText"); + break; + case CustomRoles.Sheriff: + __instance.KillButton.OverrideText(GetString("SheriffKillButtonText")); + break; + case CustomRoles.Crusader: + __instance.KillButton.OverrideText(GetString("CrusaderKillButtonText")); + break; + case CustomRoles.Totocalcio: + __instance.KillButton.OverrideText(GetString("TotocalcioKillButtonText")); + break; + case CustomRoles.Succubus: + __instance.KillButton.OverrideText(GetString("SuccubusKillButtonText")); + break; + case CustomRoles.CursedSoul: + __instance.KillButton.OverrideText(GetString("CursedSoulKillButtonText")); + break; + case CustomRoles.Admirer: + __instance.KillButton.OverrideText(GetString("AdmireButtonText")); + break; + case CustomRoles.Amnesiac: + __instance.KillButton.OverrideText(GetString("RememberButtonText")); + break; + case CustomRoles.DovesOfNeace: + __instance.AbilityButton.buttonLabelText.text = GetString("DovesOfNeaceVentButtonText"); + break; + case CustomRoles.Infectious: + __instance.KillButton.OverrideText(GetString("InfectiousKillButtonText")); + break; + case CustomRoles.Monarch: + __instance.KillButton.OverrideText(GetString("MonarchKillButtonText")); + break; + case CustomRoles.Deputy: + __instance.KillButton.OverrideText(GetString("DeputyHandcuffText")); + break; + case CustomRoles.Sidekick: + __instance.KillButton.OverrideText(GetString("KillButtonText")); + __instance.ImpostorVentButton.OverrideText(GetString("ReportButtonText")); + __instance.SabotageButton.OverrideText(GetString("SabotageButtonText")); + break; + case CustomRoles.Addict: + __instance.AbilityButton.OverrideText(GetString("AddictVentButtonText")); + break; + case CustomRoles.Dazzler: + __instance.AbilityButton.OverrideText(GetString("DazzleButtonText")); + break; + case CustomRoles.Deathpact: + __instance.AbilityButton.OverrideText(GetString("DeathpactButtonText")); + break; + case CustomRoles.Devourer: + __instance.AbilityButton.OverrideText(GetString("DevourerButtonText")); + break; + } + + //バウンティハンターのターゲットテキスト + if (LowerInfoText == null) + { + LowerInfoText = Object.Instantiate(__instance.KillButton.buttonLabelText); + LowerInfoText.transform.parent = __instance.transform; + LowerInfoText.transform.localPosition = new Vector3(0, -2f, 0); + LowerInfoText.alignment = TMPro.TextAlignmentOptions.Center; + LowerInfoText.overflowMode = TMPro.TextOverflowModes.Overflow; + LowerInfoText.enableWordWrapping = false; + LowerInfoText.color = Palette.EnabledColor; + LowerInfoText.fontSizeMin = 2.0f; + LowerInfoText.fontSizeMax = 2.0f; + } + + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) + { + LowerInfoText.text = SoloKombatManager.GetHudText(); + } + else if (player.Is(CustomRoles.BountyHunter)) + { + LowerInfoText.text = BountyHunter.GetTargetText(player, true); + } + else if (player.Is(CustomRoles.Witch)) + { + LowerInfoText.text = Witch.GetSpellModeText(player, true); + } + else if (player.Is(CustomRoles.HexMaster)) + { + LowerInfoText.text = HexMaster.GetHexModeText(player, true); + } + else if (player.Is(CustomRoles.FireWorks)) + { + var stateText = FireWorks.GetStateText(player); + LowerInfoText.text = stateText; + } + else if (player.Is(CustomRoles.Swooper)) + { + LowerInfoText.text = Swooper.GetHudText(player); + } + else if (player.Is(CustomRoles.Wraith)) + { + LowerInfoText.text = Wraith.GetHudText(player); + } + else if (player.Is(CustomRoles.Chameleon)) + { + LowerInfoText.text = Chameleon.GetHudText(player); + } + else if (player.Is(CustomRoles.BloodKnight)) + { + LowerInfoText.text = BloodKnight.GetHudText(player); + } + else if (player.Is(CustomRoles.Wildling)) + { + LowerInfoText.text = Wildling.GetHudText(player); + } + else + { + LowerInfoText.text = ""; + } + LowerInfoText.enabled = LowerInfoText.text != ""; + + if ((!AmongUsClient.Instance.IsGameStarted && AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay) || GameStates.IsMeeting) + { + LowerInfoText.enabled = false; + } + + if (player.CanUseKillButton()) + { + __instance.KillButton.ToggleVisible(player.IsAlive() && GameStates.IsInTask); + player.Data.Role.CanUseKillButton = true; + } + else + { + __instance.KillButton.SetDisabled(); + __instance.KillButton.ToggleVisible(false); + } + + bool CanUseVent = player.CanUseImpostorVentButton(); + __instance.ImpostorVentButton.ToggleVisible(CanUseVent); + player.Data.Role.CanVent = CanUseVent; + } + else + { + __instance.ReportButton.Hide(); + __instance.ImpostorVentButton.Hide(); + __instance.KillButton.Hide(); + __instance.AbilityButton.Show(); + __instance.AbilityButton.OverrideText(GetString(StringNames.HauntAbilityName)); + } + } + + + if (Input.GetKeyDown(KeyCode.Y) && AmongUsClient.Instance.NetworkMode == NetworkModes.FreePlay) + { + __instance.ToggleMapVisible(new MapOptions() + { + Mode = MapOptions.Modes.Sabotage, + AllowMovementWhileMapOpen = true + }); + if (player.AmOwner) + { + player.MyPhysics.inputHandler.enabled = true; + ConsoleJoystick.SetMode_Task(); + } + } + + if (AmongUsClient.Instance.NetworkMode == NetworkModes.OnlineGame) RepairSender.enabled = false; + if (Input.GetKeyDown(KeyCode.RightShift) && AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) + { + RepairSender.enabled = !RepairSender.enabled; + RepairSender.Reset(); + } + if (RepairSender.enabled && AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) + { + if (Input.GetKeyDown(KeyCode.Alpha0)) RepairSender.Input(0); + if (Input.GetKeyDown(KeyCode.Alpha1)) RepairSender.Input(1); + if (Input.GetKeyDown(KeyCode.Alpha2)) RepairSender.Input(2); + if (Input.GetKeyDown(KeyCode.Alpha3)) RepairSender.Input(3); + if (Input.GetKeyDown(KeyCode.Alpha4)) RepairSender.Input(4); + if (Input.GetKeyDown(KeyCode.Alpha5)) RepairSender.Input(5); + if (Input.GetKeyDown(KeyCode.Alpha6)) RepairSender.Input(6); + if (Input.GetKeyDown(KeyCode.Alpha7)) RepairSender.Input(7); + if (Input.GetKeyDown(KeyCode.Alpha8)) RepairSender.Input(8); + if (Input.GetKeyDown(KeyCode.Alpha9)) RepairSender.Input(9); + if (Input.GetKeyDown(KeyCode.Return)) RepairSender.InputEnter(); + } + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ToggleHighlight))] +class ToggleHighlightPatch +{ + public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] bool active, [HarmonyArgument(1)] RoleTeamTypes team) + { + var player = PlayerControl.LocalPlayer; + if (!GameStates.IsInTask) return; + + if (player.CanUseKillButton()) + { + __instance.cosmetics.currentBodySprite.BodySprite.material.SetColor("_OutlineColor", Utils.GetRoleColor(player.GetCustomRole())); + } + } +} +[HarmonyPatch(typeof(Vent), nameof(Vent.SetOutline))] +class SetVentOutlinePatch +{ + public static void Postfix(Vent __instance, [HarmonyArgument(1)] ref bool mainTarget) + { + var player = PlayerControl.LocalPlayer; + Color color = PlayerControl.LocalPlayer.GetRoleColor(); + __instance.myRend.material.SetColor("_OutlineColor", color); + __instance.myRend.material.SetColor("_AddColor", mainTarget ? color : Color.clear); + } +} +[HarmonyPatch(typeof(HudManager), nameof(HudManager.SetHudActive), new System.Type[] { typeof(PlayerControl), typeof(RoleBehaviour), typeof(bool) })] +class SetHudActivePatch +{ + public static bool IsActive = false; + public static void Prefix(HudManager __instance, [HarmonyArgument(2)] ref bool isActive) + { + isActive &= !GameStates.IsMeeting; + return; + } + public static void Postfix(HudManager __instance, [HarmonyArgument(2)] bool isActive) + { + __instance.ReportButton.ToggleVisible(!GameStates.IsLobby && isActive); + if (!GameStates.IsModHost) return; + IsActive = isActive; + if (!isActive) return; + + var player = PlayerControl.LocalPlayer; + if (player == null) return; + switch (player.GetCustomRole()) + { + case CustomRoles.Sheriff: + case CustomRoles.SwordsMan: + case CustomRoles.Deputy: + case CustomRoles.Monarch: + case CustomRoles.Arsonist: + case CustomRoles.NWitch: + case CustomRoles.Innocent: + case CustomRoles.Reverie: + case CustomRoles.Pelican: + case CustomRoles.Revolutionist: + case CustomRoles.FFF: + case CustomRoles.Medic: + case CustomRoles.Gamer: + case CustomRoles.DarkHide: + case CustomRoles.Provocateur: + case CustomRoles.Farseer: + case CustomRoles.Crusader: + __instance.SabotageButton.ToggleVisible(false); + __instance.AbilityButton.ToggleVisible(false); + __instance.ImpostorVentButton.ToggleVisible(false); + break; + + case CustomRoles.Minimalism: + case CustomRoles.KB_Normal: + __instance.SabotageButton.ToggleVisible(false); + __instance.AbilityButton.ToggleVisible(false); + __instance.ReportButton.ToggleVisible(false); + break; + case CustomRoles.Parasite: + case CustomRoles.Refugee: + __instance.SabotageButton.ToggleVisible(true); + break; + case CustomRoles.Jackal: + Jackal.SetHudActive(__instance, isActive); + break; + case CustomRoles.Sidekick: + Sidekick.SetHudActive(__instance, isActive); + break; + case CustomRoles.Traitor: + Traitor.SetHudActive(__instance, isActive); + break; + case CustomRoles.Glitch: + Glitch.SetHudActive(__instance, isActive); + break; + + } + + foreach (var subRole in Main.PlayerStates[player.PlayerId].SubRoles) + { + switch (subRole) + { + case CustomRoles.Oblivious: + __instance.ReportButton.ToggleVisible(false); + break; + case CustomRoles.Mare: + if (!Utils.IsActive(SystemTypes.Electrical)) + __instance.KillButton.ToggleVisible(false); + break; + } + } + __instance.KillButton.ToggleVisible(player.CanUseKillButton()); + __instance.ImpostorVentButton.ToggleVisible(player.CanUseImpostorVentButton()); + } +} +[HarmonyPatch(typeof(VentButton), nameof(VentButton.DoClick))] +class VentButtonDoClickPatch +{ + public static bool Prefix(VentButton __instance) + { + var pc = PlayerControl.LocalPlayer; + { + if (!pc.Is(CustomRoles.Swooper) || !pc.Is(CustomRoles.Wraith) || !pc.Is(CustomRoles.Chameleon) || pc.inVent || __instance.currentTarget == null || !pc.CanMove || !__instance.isActiveAndEnabled) return true; + pc?.MyPhysics?.RpcEnterVent(__instance.currentTarget.Id); + return false; + } + } +} +[HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.Show))] +class MapBehaviourShowPatch +{ + public static void Prefix(MapBehaviour __instance, ref MapOptions opts) + { + if (GameStates.IsMeeting) return; + + if (opts.Mode is MapOptions.Modes.Normal or MapOptions.Modes.Sabotage) + { + var player = PlayerControl.LocalPlayer; + if (player.Is(CustomRoleTypes.Impostor) || (player.Is(CustomRoles.Parasite)) || (player.Is(CustomRoles.Glitch)) || (player.Is(CustomRoles.Refugee)) || (player.Is(CustomRoles.Jackal) && Jackal.CanUseSabotage.GetBool()) || (player.Is(CustomRoles.Traitor) && Traitor.CanUseSabotage.GetBool())) + opts.Mode = MapOptions.Modes.Sabotage; + else + opts.Mode = MapOptions.Modes.Normal; + } + } +} +[HarmonyPatch(typeof(TaskPanelBehaviour), nameof(TaskPanelBehaviour.SetTaskText))] +class TaskPanelBehaviourPatch +{ + // タスク表示の文章が更新・適用された後に実行される + public static void Postfix(TaskPanelBehaviour __instance) + { + if (!GameStates.IsModHost) return; + PlayerControl player = PlayerControl.LocalPlayer; + + var taskText = __instance.taskText.text; + if (taskText == "None") return; + + // 役職説明表示 + if (!player.GetCustomRole().IsVanilla()) + { + var RoleWithInfo = $"{player.GetDisplayRoleName()}:\r\n"; + RoleWithInfo += player.GetRoleInfo(); + + var AllText = Utils.ColorString(player.GetRoleColor(), RoleWithInfo); + + switch (Options.CurrentGameMode) + { + case CustomGameMode.Standard: + + var lines = taskText.Split("\r\n\n")[0].Split("\r\n\n")[0].Split("\r\n"); + StringBuilder sb = new(); + foreach (var eachLine in lines) + { + var line = eachLine.Trim(); + if ((line.StartsWith("") || line.StartsWith("")) && sb.Length < 1 && !line.Contains('(')) continue; + sb.Append(line + "\r\n"); + } + if (sb.Length > 1) + { + var text = sb.ToString().TrimEnd('\n').TrimEnd('\r'); + if (!Utils.HasTasks(player.Data, false) && sb.ToString().Count(s => (s == '\n')) >= 2) + text = $"{ Utils.ColorString(Utils.GetRoleColor(player.GetCustomRole()).ShadeColor(0.2f), GetString("FakeTask"))}\r\n{text}"; + AllText += $"\r\n\r\n{text}"; + } + + if (MeetingStates.FirstMeeting) + { + AllText += $"\r\n\r\n{GetString("PressF1ShowMainRoleDes")}"; + if (Main.PlayerStates.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var ps) && ps.SubRoles.Count >= 1) + AllText += $"\r\n{GetString("PressF2ShowAddRoleDes")}"; + AllText += ""; + } + + break; + + case CustomGameMode.SoloKombat: + + var lpc = PlayerControl.LocalPlayer; + + AllText += "\r\n"; + AllText += $"\r\n{GetString("PVP.ATK")}: {lpc.ATK()}"; + AllText += $"\r\n{GetString("PVP.DF")}: {lpc.DF()}"; + AllText += $"\r\n{GetString("PVP.RCO")}: {lpc.HPRECO()}"; + AllText += "\r\n"; + + Dictionary SummaryText = new(); + foreach (var id in Main.PlayerStates.Keys) + { + string name = Main.AllPlayerNames[id].RemoveHtmlTags().Replace("\r\n", string.Empty); + string summary = $"{Utils.GetProgressText(id)} {Utils.ColorString(Main.PlayerColors[id], name)}"; + if (Utils.GetProgressText(id).Trim() == "") continue; + SummaryText[id] = summary; + } + + List<(int, byte)> list = new(); + foreach (var id in Main.PlayerStates.Keys) list.Add((SoloKombatManager.GetRankOfScore(id), id)); + list.Sort(); + foreach (var id in list.Where(x => SummaryText.ContainsKey(x.Item2))) AllText += "\r\n" + SummaryText[id.Item2]; + + AllText = $"{AllText}"; + + break; + } + + __instance.taskText.text = AllText; + } + + // RepairSenderの表示 + if (RepairSender.enabled && AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) + __instance.taskText.text = RepairSender.GetText(); + } +} + +class RepairSender +{ + public static bool enabled = false; + public static bool TypingAmount = false; + + public static int SystemType; + public static int amount; + + public static void Input(int num) + { + if (!TypingAmount) + { + //SystemType入力中 + SystemType *= 10; + SystemType += num; + } + else + { + //Amount入力中 + amount *= 10; + amount += num; + } + } + public static void InputEnter() + { + if (!TypingAmount) + { + //SystemType入力中 + TypingAmount = true; + } + else + { + //Amount入力中 + Send(); + } + } + public static void Send() + { + ShipStatus.Instance.RpcRepairSystem((SystemTypes)SystemType, amount); + Reset(); + } + public static void Reset() + { + TypingAmount = false; + SystemType = 0; + amount = 0; + } + public static string GetText() + { + return SystemType.ToString() + "(" + ((SystemTypes)SystemType).ToString() + ")\r\n" + amount; + } +} \ No newline at end of file diff --git a/Patches/HudSpritePatch.cs b/Patches/HudSpritePatch.cs new file mode 100644 index 000000000..24404f70a --- /dev/null +++ b/Patches/HudSpritePatch.cs @@ -0,0 +1,128 @@ +using HarmonyLib; +using TOHE.Modules; +using TOHE.Roles.Impostor; +using UnityEngine; + +namespace TOHE; + +public static class CustomButton +{ + public static Sprite Get(string name) => Utils.LoadSprite($"TOHE.Resources.Images.Skills.{name}.png", 115f); +} + +[HarmonyPriority(520)] +[HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] +public static class HudSpritePatch +{ + private static Sprite Kill; + private static Sprite Ability; + private static Sprite Vent; + public static void Postfix(HudManager __instance) + { + var player = PlayerControl.LocalPlayer; + if (player == null || !GameStates.IsModHost) return; + if (!SetHudActivePatch.IsActive || !player.IsAlive()) return; + if (!AmongUsClient.Instance.IsGameStarted || !Main.introDestroyed) + { + Kill = null; + Ability = null; + Vent = null; + return; + } + + bool shapeshifting = Main.CheckShapeshift.TryGetValue(player.PlayerId, out bool ss) && ss; + + if (!Kill) Kill = __instance.KillButton.graphic.sprite; + if (!Ability) Ability = __instance.AbilityButton.graphic.sprite; + if (!Vent) Vent = __instance.ImpostorVentButton.graphic.sprite; + + Sprite newKillButton = Kill; + Sprite newAbilityButton = Ability; + Sprite newVentButton = Vent; + + if (!Main.EnableCustomButton.Value) goto EndOfSelectImg; + + switch (player.GetCustomRole()) + { + case CustomRoles.Assassin: + if (!shapeshifting) + { + newKillButton = CustomButton.Get("Mark"); + if (Assassin.MarkedPlayer.ContainsKey(player.PlayerId)) + newAbilityButton = CustomButton.Get("Assassinate"); + } + break; + case CustomRoles.Bomber: + case CustomRoles.Nuker: + newAbilityButton = CustomButton.Get("Bomb"); + break; + case CustomRoles.Camouflager: + newAbilityButton = CustomButton.Get("Camo"); + break; + case CustomRoles.Arsonist: + newKillButton = CustomButton.Get("Douse"); + if (player.IsDouseDone()) newVentButton = CustomButton.Get("Ignite"); + break; + case CustomRoles.FireWorks: + if (FireWorks.nowFireWorksCount[player.PlayerId] == 0) + newAbilityButton = CustomButton.Get("FireworkD"); + else + newAbilityButton = CustomButton.Get("FireworkP"); + break; + case CustomRoles.Hacker: + newAbilityButton = CustomButton.Get("Hack"); + break; + case CustomRoles.Hangman: + if (shapeshifting) newAbilityButton = CustomButton.Get("Hangman"); + break; + case CustomRoles.Paranoia: + newAbilityButton = CustomButton.Get("Paranoid"); + break; + case CustomRoles.Puppeteer: + newKillButton = CustomButton.Get("Puttpuer"); + break; + case CustomRoles.Medic: + newKillButton = CustomButton.Get("Shield"); + break; + case CustomRoles.Gangster: + if (Gangster.CanRecruit(player.PlayerId)) newKillButton = CustomButton.Get("Sidekick"); + break; + case CustomRoles.Succubus: + newKillButton = CustomButton.Get("Subbus"); + break; + case CustomRoles.Innocent: + newKillButton = CustomButton.Get("Suidce"); + break; + case CustomRoles.EvilTracker: + newAbilityButton = CustomButton.Get("Track"); + break; + case CustomRoles.Vampire: + newKillButton = CustomButton.Get("Bite"); + break; + case CustomRoles.Veteran: + newAbilityButton = CustomButton.Get("Veteran"); + break; + case CustomRoles.Pelican: + newKillButton = CustomButton.Get("Vulture"); + break; + case CustomRoles.CursedSoul: + newKillButton = CustomButton.Get("Soul"); + break; + case CustomRoles.Warlock: + if (!shapeshifting) + { + newKillButton = CustomButton.Get("Curse"); + if (Main.isCurseAndKill.TryGetValue(player.PlayerId, out bool curse) && curse) + newAbilityButton = CustomButton.Get("CurseKill"); + } + break; + } + + EndOfSelectImg: + + __instance.KillButton.graphic.sprite = newKillButton; + __instance.AbilityButton.graphic.sprite = newAbilityButton; + __instance.ImpostorVentButton.graphic.sprite = newVentButton; + + } +} diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs new file mode 100644 index 000000000..e8141afdb --- /dev/null +++ b/Patches/IntroPatch.cs @@ -0,0 +1,382 @@ +using AmongUs.GameOptions; +using HarmonyLib; +using System; +using System.Linq; +using System.Threading.Tasks; +using UnityEngine; +using static TOHE.Translator; + +namespace TOHE; + +[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.ShowRole))] +class SetUpRoleTextPatch +{ + public static void Postfix(IntroCutscene __instance) + { + if (!GameStates.IsModHost) return; + new LateTask(() => + { + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) + { + var color = ColorUtility.TryParseHtmlString("#f55252", out var c) ? c : new(255, 255, 255, 255); + CustomRoles role = PlayerControl.LocalPlayer.GetCustomRole(); + __instance.YouAreText.color = color; + __instance.RoleText.text = Utils.GetRoleName(role); + __instance.RoleText.color = Utils.GetRoleColor(role); + __instance.RoleBlurbText.color = color; + __instance.RoleBlurbText.text = PlayerControl.LocalPlayer.GetRoleInfo(); + } + else + { + CustomRoles role = PlayerControl.LocalPlayer.GetCustomRole(); + if (!role.IsVanilla()) + { + __instance.YouAreText.color = Utils.GetRoleColor(role); + __instance.RoleText.text = Utils.GetRoleName(role); + __instance.RoleText.color = Utils.GetRoleColor(role); + __instance.RoleBlurbText.color = Utils.GetRoleColor(role); + __instance.RoleBlurbText.text = PlayerControl.LocalPlayer.GetRoleInfo(); + } + foreach (var subRole in Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].SubRoles) + __instance.RoleBlurbText.text += "\n" + Utils.ColorString(Utils.GetRoleColor(subRole), GetString($"{subRole}Info")); + if (!PlayerControl.LocalPlayer.Is(CustomRoles.Lovers) && !PlayerControl.LocalPlayer.Is(CustomRoles.Ntr) && CustomRolesHelper.RoleExist(CustomRoles.Ntr)) + __instance.RoleBlurbText.text += "\n" + Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lovers), GetString($"{CustomRoles.Lovers}Info")); + __instance.RoleText.text += Utils.GetSubRolesText(PlayerControl.LocalPlayer.PlayerId, false, true); + } + }, 0.01f, "Override Role Text"); + } +} +[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.CoBegin))] +class CoBeginPatch +{ + public static void Prefix() + { + var logger = Logger.Handler("Info"); + logger.Info("------------显示名称------------"); + foreach (var pc in Main.AllPlayerControls) + { + logger.Info($"{(pc.AmOwner ? "[*]" : ""),-3}{pc.PlayerId,-2}:{pc.name.PadRightV2(20)}:{pc.cosmetics.nameText.text}({Palette.ColorNames[pc.Data.DefaultOutfit.ColorId].ToString().Replace("Color", "")})"); + pc.cosmetics.nameText.text = pc.name; + } + logger.Info("------------职业分配------------"); + foreach (var pc in Main.AllPlayerControls) + { + logger.Info($"{(pc.AmOwner ? "[*]" : ""),-3}{pc.PlayerId,-2}:{pc?.Data?.PlayerName?.PadRightV2(20)}:{pc.GetAllRoleName().RemoveHtmlTags()}"); + } + logger.Info("------------运行环境------------"); + foreach (var pc in Main.AllPlayerControls) + { + try + { + var text = pc.AmOwner ? "[*]" : " "; + text += $"{pc.PlayerId,-2}:{pc.Data?.PlayerName?.PadRightV2(20)}:{pc.GetClient()?.PlatformData?.Platform.ToString()?.Replace("Standalone", ""),-11}"; + if (Main.playerVersion.TryGetValue(pc.PlayerId, out PlayerVersion pv)) + text += $":Mod({pv.forkId}/{pv.version}:{pv.tag})"; + else text += ":Vanilla"; + logger.Info(text); + } + catch (Exception ex) + { + Logger.Exception(ex, "Platform"); + } + } + logger.Info("------------基本设置------------"); + var tmp = GameOptionsManager.Instance.CurrentGameOptions.ToHudString(GameData.Instance ? GameData.Instance.PlayerCount : 10).Split("\r\n").Skip(1); + foreach (var t in tmp) logger.Info(t); + logger.Info("------------详细设置------------"); + foreach (var o in OptionItem.AllOptions) + if (!o.IsHiddenOn(Options.CurrentGameMode) && (o.Parent == null ? !o.GetString().Equals("0%") : o.Parent.GetBool())) + logger.Info($"{(o.Parent == null ? o.GetName(true, true).RemoveHtmlTags().PadRightV2(40) : $"┗ {o.GetName(true, true).RemoveHtmlTags()}".PadRightV2(41))}:{o.GetString().RemoveHtmlTags()}"); + logger.Info("-------------其它信息-------------"); + logger.Info($"玩家人数: {Main.AllPlayerControls.Count()}"); + Main.AllPlayerControls.Do(x => Main.PlayerStates[x.PlayerId].InitTask(x)); + GameData.Instance.RecomputeTaskCounts(); + TaskState.InitialTotalTasks = GameData.Instance.TotalTasks; + + Utils.NotifyRoles(); + + GameStates.InGame = true; + } +} +[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginCrewmate))] +class BeginCrewmatePatch +{ + public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List teamToDisplay) + { + if (PlayerControl.LocalPlayer.Is(CustomRoleTypes.Neutral) && !PlayerControl.LocalPlayer.Is(CustomRoles.Parasite)) + { + teamToDisplay = new Il2CppSystem.Collections.Generic.List(); + teamToDisplay.Add(PlayerControl.LocalPlayer); + } + if (PlayerControl.LocalPlayer.Is(CustomRoleTypes.Neutral) && !PlayerControl.LocalPlayer.Is(CustomRoles.Crewpostor)) + { + teamToDisplay = new Il2CppSystem.Collections.Generic.List(); + teamToDisplay.Add(PlayerControl.LocalPlayer); + } + else if (PlayerControl.LocalPlayer.Is(CustomRoles.Madmate)) + { + teamToDisplay = new Il2CppSystem.Collections.Generic.List(); + teamToDisplay.Add(PlayerControl.LocalPlayer); + __instance.BeginImpostor(teamToDisplay); + __instance.overlayHandle.color = Palette.ImpostorRed; + return false; + } + else if (PlayerControl.LocalPlayer.Is(CustomRoles.Crewpostor)) + { + teamToDisplay = new Il2CppSystem.Collections.Generic.List(); + teamToDisplay.Add(PlayerControl.LocalPlayer); + __instance.BeginImpostor(teamToDisplay); + __instance.overlayHandle.color = Palette.ImpostorRed; + return false; + } + else if (PlayerControl.LocalPlayer.GetCustomRole().IsMadmate()) + { + teamToDisplay = new Il2CppSystem.Collections.Generic.List(); + teamToDisplay.Add(PlayerControl.LocalPlayer); + __instance.BeginImpostor(teamToDisplay); + __instance.overlayHandle.color = Palette.ImpostorRed; + return false; + } + + return true; + } + public static void Postfix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List teamToDisplay) + { + //チーム表示変更 + CustomRoles role = PlayerControl.LocalPlayer.GetCustomRole(); + + __instance.ImpostorText.gameObject.SetActive(false); + switch (role.GetCustomRoleTypes()) + { + case CustomRoleTypes.Impostor: + __instance.TeamTitle.text = GetString("TeamImpostor"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 25, 25, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Impostor"); + break; + case CustomRoleTypes.Crewmate: + __instance.TeamTitle.text = GetString("TeamCrewmate"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(140, 255, 255, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Crewmate); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Crewmate"); + break; + case CustomRoleTypes.Neutral: + __instance.TeamTitle.text = GetString("TeamNeutral"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(127, 140, 141, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Neutral"); + break; + } + switch (role) + { + case CustomRoles.Terrorist: + var sound = ShipStatus.Instance.CommonTasks.Where(task => task.TaskType == TaskTypes.FixWiring).FirstOrDefault() + .MinigamePrefab.OpenSound; + PlayerControl.LocalPlayer.Data.Role.IntroSound = sound; + break; + + case CustomRoles.Workaholic: + PlayerControl.LocalPlayer.Data.Role.IntroSound = DestroyableSingleton.Instance.TaskCompleteSound; + break; + + case CustomRoles.Opportunist: + case CustomRoles.FFF: + case CustomRoles.Revolutionist: + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Crewmate); + break; + + case CustomRoles.SabotageMaster: + case CustomRoles.Provocateur: + PlayerControl.LocalPlayer.Data.Role.IntroSound = ShipStatus.Instance.SabotageSound; + break; + + case CustomRoles.Doctor: + case CustomRoles.Medic: + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Scientist); + break; + + case CustomRoles.GM: + __instance.TeamTitle.text = Utils.GetRoleName(role); + __instance.TeamTitle.color = Utils.GetRoleColor(role); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(role); + __instance.ImpostorText.gameObject.SetActive(false); + PlayerControl.LocalPlayer.Data.Role.IntroSound = DestroyableSingleton.Instance.TaskCompleteSound; + break; + } + + if (PlayerControl.LocalPlayer.Is(CustomRoles.Madmate)) + { + __instance.TeamTitle.text = GetString("TeamMadmate"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 25, 25, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Madmate"); + } + + if (PlayerControl.LocalPlayer.Is(CustomRoles.Parasite)) + { + __instance.TeamTitle.text = GetString("TeamMadmate"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 25, 25, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Madmate"); + } + + if (PlayerControl.LocalPlayer.Is(CustomRoles.Crewpostor)) + { + __instance.TeamTitle.text = GetString("TeamMadmate"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 25, 25, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Madmate"); + } + + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) + { + var color = ColorUtility.TryParseHtmlString("#f55252", out var c) ? c : new(255, 255, 255, 255); + __instance.TeamTitle.text = Utils.GetRoleName(role); + __instance.TeamTitle.color = Utils.GetRoleColor(role); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("ModeSoloKombat"); + __instance.BackgroundBar.material.color = color; + PlayerControl.LocalPlayer.Data.Role.IntroSound = DestroyableSingleton.Instance.HnSOtherImpostorTransformSfx; + } + + if (Input.GetKey(KeyCode.RightShift)) + { + __instance.TeamTitle.text = "明天就跑路啦"; + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = "嘿嘿嘿嘿嘿嘿"; + __instance.TeamTitle.color = Color.cyan; + StartFadeIntro(__instance, Color.cyan, Color.yellow); + } + if (Input.GetKey(KeyCode.RightControl)) + { + __instance.TeamTitle.text = "警告"; + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = "请远离无知的玩家"; + __instance.TeamTitle.color = Color.magenta; + StartFadeIntro(__instance, Color.magenta, Color.magenta); + } + } + public static AudioClip GetIntroSound(RoleTypes roleType) + { + return RoleManager.Instance.AllRoles.Where((role) => role.Role == roleType).FirstOrDefault().IntroSound; + } + private static async void StartFadeIntro(IntroCutscene __instance, Color start, Color end) + { + await Task.Delay(1000); + int milliseconds = 0; + while (true) + { + await Task.Delay(20); + milliseconds += 20; + float time = milliseconds / (float)500; + Color LerpingColor = Color.Lerp(start, end, time); + if (__instance == null || milliseconds > 500) + { + Logger.Info("ループを終了します", "StartFadeIntro"); + break; + } + __instance.BackgroundBar.material.color = LerpingColor; + } + } +} +[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginImpostor))] +class BeginImpostorPatch +{ + public static bool Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List yourTeam) + { + var role = PlayerControl.LocalPlayer.GetCustomRole(); + if (role is CustomRoles.Crewpostor) + { + yourTeam = new Il2CppSystem.Collections.Generic.List(); + yourTeam.Add(PlayerControl.LocalPlayer); + __instance.overlayHandle.color = Palette.ImpostorRed; + return true; + } + else if (PlayerControl.LocalPlayer.Is(CustomRoles.Madmate)) + { + yourTeam = new Il2CppSystem.Collections.Generic.List(); + yourTeam.Add(PlayerControl.LocalPlayer); + __instance.overlayHandle.color = Palette.ImpostorRed; + return true; + } + else if (PlayerControl.LocalPlayer.Is(CustomRoles.Parasite)) + { + yourTeam = new Il2CppSystem.Collections.Generic.List(); + yourTeam.Add(PlayerControl.LocalPlayer); + __instance.overlayHandle.color = Palette.ImpostorRed; + return true; + } + else if (PlayerControl.LocalPlayer.Is(CustomRoles.Crewpostor)) + { + yourTeam = new Il2CppSystem.Collections.Generic.List(); + yourTeam.Add(PlayerControl.LocalPlayer); + __instance.overlayHandle.color = Palette.ImpostorRed; + return true; + } + else if (role is CustomRoles.Sheriff or CustomRoles.SwordsMan or CustomRoles.Medic or CustomRoles.Counterfeiter or CustomRoles.Monarch or CustomRoles.Farseer or CustomRoles.Admirer or CustomRoles.Deputy) + { + yourTeam = new Il2CppSystem.Collections.Generic.List(); + yourTeam.Add(PlayerControl.LocalPlayer); + foreach (var pc in Main.AllPlayerControls.Where(x => !x.AmOwner)) yourTeam.Add(pc); + __instance.BeginCrewmate(yourTeam); + __instance.overlayHandle.color = Palette.CrewmateBlue; + return false; + } + BeginCrewmatePatch.Prefix(__instance, ref yourTeam); + return true; + } + public static void Postfix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List yourTeam) + { + BeginCrewmatePatch.Postfix(__instance, ref yourTeam); + } +} +[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))] +class IntroCutsceneDestroyPatch +{ + public static void Postfix(IntroCutscene __instance) + { + if (!GameStates.IsInGame) return; + Main.introDestroyed = true; + if (AmongUsClient.Instance.AmHost) + { + if (Main.NormalOptions.MapId != 4) + { + Main.AllPlayerControls.Do(pc => pc.RpcResetAbilityCooldown()); + if (Options.FixFirstKillCooldown.GetBool() && Options.CurrentGameMode != CustomGameMode.SoloKombat) + new LateTask(() => + { + Main.AllPlayerControls.Do(x => x.ResetKillCooldown()); + Main.AllPlayerControls.Where(x => (Main.AllPlayerKillCooldown[x.PlayerId] - 2f) > 0f).Do(pc => pc.SetKillCooldown(Main.AllPlayerKillCooldown[pc.PlayerId] - 2f)); + }, 2f, "FixKillCooldownTask"); + } + new LateTask(() => Main.AllPlayerControls.Do(pc => pc.RpcSetRoleDesync(RoleTypes.Shapeshifter, -3)), 2f, "SetImpostorForServer"); + if (PlayerControl.LocalPlayer.Is(CustomRoles.GM)) + { + PlayerControl.LocalPlayer.RpcExile(); + Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].SetDead(); + } + if (Options.RandomSpawn.GetBool() || Options.CurrentGameMode == CustomGameMode.SoloKombat) + { + RandomSpawn.SpawnMap map; + switch (Main.NormalOptions.MapId) + { + case 0: + map = new RandomSpawn.SkeldSpawnMap(); + Main.AllPlayerControls.Do(map.RandomTeleport); + break; + case 1: + map = new RandomSpawn.MiraHQSpawnMap(); + Main.AllPlayerControls.Do(map.RandomTeleport); + break; + } + } + } + Logger.Info("OnDestroy", "IntroCutscene"); + } +} \ No newline at end of file diff --git a/Patches/JoinGameButtonPatch.cs b/Patches/JoinGameButtonPatch.cs new file mode 100644 index 000000000..a3896aace --- /dev/null +++ b/Patches/JoinGameButtonPatch.cs @@ -0,0 +1,19 @@ +using HarmonyLib; +using System.Text.RegularExpressions; +using UnityEngine; + +namespace TOHE; + +[HarmonyPatch(typeof(JoinGameButton), nameof(JoinGameButton.OnClick))] +class JoinGameButtonPatch +{ + public static void Prefix(JoinGameButton __instance) + { + if (__instance.GameIdText == null) return; + if (__instance.GameIdText.text == "" && Regex.IsMatch(GUIUtility.systemCopyBuffer.Trim('\r', '\n'), @"^[A-Z]{6}$")) + { + Logger.Info($"{GUIUtility.systemCopyBuffer}", "ClipBoard"); + __instance.GameIdText.SetText(GUIUtility.systemCopyBuffer.Trim('\r', '\n')); + } + } +} \ No newline at end of file diff --git a/Patches/LadderPatch.cs b/Patches/LadderPatch.cs new file mode 100644 index 000000000..609186035 --- /dev/null +++ b/Patches/LadderPatch.cs @@ -0,0 +1,70 @@ +using HarmonyLib; +using System.Collections.Generic; +using UnityEngine; + +namespace TOHE; + +public class FallFromLadder +{ + public static Dictionary TargetLadderData; + private static int Chance => (Options.LadderDeathChance as StringOptionItem).GetChance(); + public static void Reset() + { + TargetLadderData = new(); + } + public static void OnClimbLadder(PlayerPhysics player, Ladder source) + { + if (!Options.LadderDeath.GetBool()) return; + var sourcePos = source.transform.position; + var targetPos = source.Destination.transform.position; + //降りているのかを検知 + if (sourcePos.y > targetPos.y) + { + int chance = IRandom.Instance.Next(1, 101); + if (chance <= Chance) + { + TargetLadderData[player.myPlayer.PlayerId] = targetPos; + } + } + } + public static void FixedUpdate(PlayerControl player) + { + if (player.Data.Disconnected) return; + if (TargetLadderData.ContainsKey(player.PlayerId)) + { + if (Vector2.Distance(TargetLadderData[player.PlayerId], player.transform.position) < 0.5f) + { + if (player.Data.IsDead) return; + //LateTaskを入れるため、先に死亡判定を入れておく + player.Data.IsDead = true; + new LateTask(() => + { + Vector2 targetPos = (Vector2)TargetLadderData[player.PlayerId] + new Vector2(0.1f, 0f); + ushort num = (ushort)(NetHelpers.XRange.ReverseLerp(targetPos.x) * 65535f); + ushort num2 = (ushort)(NetHelpers.YRange.ReverseLerp(targetPos.y) * 65535f); + CustomRpcSender sender = CustomRpcSender.Create("LadderFallRpc", sendOption: Hazel.SendOption.None); + sender.AutoStartRpc(player.NetTransform.NetId, (byte)RpcCalls.SnapTo) + .Write(num) + .Write(num2) + .EndRpc(); + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.MurderPlayer) + .WriteNetObject(player) + .EndRpc(); + sender.SendMessage(); + player.NetTransform.SnapTo(targetPos); + player.MurderPlayer(player); + Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.Fall; + Main.PlayerStates[player.PlayerId].SetDead(); + }, 0.05f, "LadderFallTask"); + } + } + } +} +[HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.ClimbLadder))] +class LadderPatch +{ + public static void Postfix(PlayerPhysics __instance, Ladder source, byte climbLadderSid) + { + FallFromLadder.OnClimbLadder(__instance, source); + } +} \ No newline at end of file diff --git a/Patches/LobbyListPatch.cs b/Patches/LobbyListPatch.cs new file mode 100644 index 000000000..659dbd860 --- /dev/null +++ b/Patches/LobbyListPatch.cs @@ -0,0 +1,68 @@ +using HarmonyLib; +using Il2CppSystem; +using Il2CppSystem.Collections.Generic; +using InnerNet; +//using UnityEngine; + +namespace TOHE; + +//[HarmonyPatch(typeof(FindAGameManager), nameof(FindAGameManager.Update))] +//public static class FindAGameManagerUpdatePatch +//{ +// private static int buffer = 80; +// private static GameObject RefreshButton; +// private static GameObject InputDisplayGlyph; +// public static void Postfix(FindAGameManager __instance) +// { +// if ((RefreshButton = GameObject.Find("RefreshButton")) != null) +// RefreshButton.transform.localPosition = new Vector3(100f, 100f, 100f); +// if ((InputDisplayGlyph = GameObject.Find("InputDisplayGlyph")) != null) +// InputDisplayGlyph.transform.localPosition = new Vector3(100f, 100f, 100f); + +// buffer--; if (buffer > 0) return; buffer = 80; +// __instance.RefreshList(); +// } +//} + +/*[HarmonyPatch(typeof(FindAGameManager), nameof(FindAGameManager.HandleList))] +public static class FindAGameManagerHandleListPatch +{ + public static void Prefix(FindAGameManager __instance, [HarmonyArgument(0)] InnerNetClient.TotalGameData totalGames, [HarmonyArgument(1)] ref List games) + { + List newList = new(); + + var nameList = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.SChinese or SupportedLangs.TChinese ? Main.TName_Snacks_CN : Main.TName_Snacks_EN; + + foreach (var game in games) + { + if (game.Language.ToString().Length > 9) continue; + + var color = game.Platform switch + { + Platforms.StandaloneItch or + Platforms.StandaloneWin10 or + Platforms.StandaloneEpicPC or + Platforms.StandaloneSteamPC => "#00a4ff", + + Platforms.Xbox or + Platforms.Switch or + Platforms.Playstation => "#dd001b", + + Platforms.IPhone or + Platforms.Android => "#68bc71", + + Platforms.Unknown or + _ => "#ffffff" + }; + + string str = Math.Abs(game.GameId).ToString(); + int id = Math.Min(Math.Max(int.Parse(str.Substring(str.Length - 2, 2)), 1) * nameList.Count / 100, nameList.Count); + + game.HostName = $"{nameList[id]}"; + game.HostName += $" ({Math.Max(0, 100 - game.Age / 100)}%)"; + + newList.Add(game); + } + games = newList; + } +}*/ diff --git a/Patches/LobbyPatch.cs b/Patches/LobbyPatch.cs new file mode 100644 index 000000000..7be6ec74a --- /dev/null +++ b/Patches/LobbyPatch.cs @@ -0,0 +1,25 @@ +using HarmonyLib; +using UnityEngine; + +namespace TOHE; + +[HarmonyPatch(typeof(LobbyBehaviour), nameof(LobbyBehaviour.FixedUpdate))] +public class LobbyFixedUpdatePatch +{ + private static GameObject Paint; + public static void Postfix() + { + if (Paint == null) + { + var LeftBox = GameObject.Find("Leftbox"); + if (LeftBox != null) + { + Paint = Object.Instantiate(LeftBox, LeftBox.transform.parent.transform); + Paint.name = "Lobby Paint"; + Paint.transform.localPosition = new Vector3(0.042f, -2.59f, -10.5f); + SpriteRenderer renderer = Paint.GetComponent(); + renderer.sprite = Utils.LoadSprite("TOHE.Resources.Images.LobbyPaint.png", 290f); + } + } + } +} diff --git a/Patches/LogoAndStampPatch.cs b/Patches/LogoAndStampPatch.cs new file mode 100644 index 000000000..e6de73c1b --- /dev/null +++ b/Patches/LogoAndStampPatch.cs @@ -0,0 +1,154 @@ +using BepInEx.Unity.IL2CPP.Utils; +using HarmonyLib; +using System.Collections; +using TMPro; +using UnityEngine; +using static TOHE.Translator; + +namespace TOHE; + +//参考:https://github.com/ykundesu/SuperNewRoles/blob/master/SuperNewRoles/Patches/LogoAndStampPatch.cs +[HarmonyPatch] +public static class CredentialsPatch +{ + public static GenericPopup popup; + + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] + public static class LogoPatch + { + static IEnumerator ViewCredentialsCoro(MainMenuManager __instance) + { + while (true) + { + yield return new WaitForSeconds(1f); + if (__instance != null) + { + ViewBoosterPatch(__instance); + } + break; + } + } + public static string BoosterData = ""; + public static string SponsersData = ""; + public static string DevsData = ""; + public static string TransData = ""; + + public static void InitCredentialsData() + { + BoosterData = ""; + SponsersData = ""; + DevsData = ""; + TransData = ""; + + DevsData += $"♥咔皮呆 - {GetString("MainDev")}"; + DevsData += $"\n♥IRIDESCENT - {GetString("Art")}"; + DevsData += $"\nNCSIMON - {GetString("RoleDev")}"; + DevsData += $"\n天寸梦初 - {GetString("RoleDev")}&{GetString("TechSup")}"; + DevsData += $"\nCommandf1 - {GetString("RoleDev")}&{GetString("FeatureDev")}"; + DevsData += $"\n喜 - {GetString("RoleDev")}"; + DevsData += $"\nSHAAARKY - {GetString("RoleDev")}"; + + TransData += $"Tommy-XL - {GetString("TranEN")}&{GetString("TranRU")}"; + TransData += $"\nTem - {GetString("TranEN")}&{GetString("TranRU")}"; + TransData += $"\n阿龍 - {GetString("TranCHT")}"; + TransData += $"\nGurge44 - {GetString("TranEN")}"; + TransData += $"\n法官 - {GetString("TranCHT")}"; + TransData += $"\nSolarFlare - {GetString("TranEN")}"; + TransData += $"\nchill_ultimated - {GetString("TranRU")}"; + + BoosterData += $"bunny"; + BoosterData += $"\nNamra"; + BoosterData += $"\nKNIGHT"; + BoosterData += $"\nSolarFlare"; + BoosterData += $"\nLoonie"; + + SponsersData += $"罗寄"; + SponsersData += $"\n鬼"; + SponsersData += $"\n喜"; + SponsersData += $"\n小叨院长"; + SponsersData += $"\n波奇酱"; + SponsersData += $"\n法师"; + SponsersData += $"\n沐煊"; + SponsersData += $"\nSolarFlare"; + SponsersData += $"\n林林林"; + SponsersData += $"\n撒币"; + SponsersData += $"\n斯卡蒂Skadi"; + SponsersData += $"\nltemten"; + SponsersData += $"\n\n({GetString("OnlyShowPart")})"; + + } + + public static GameObject CreditsPopup; + static void ViewBoosterPatch(MainMenuManager __instance) + { + var template = __instance.transform.FindChild("StatsPopup"); + var obj = Object.Instantiate(template, template.transform.parent).gameObject; + CreditsPopup = obj; + Object.Destroy(obj.GetComponent()); + + var devtitletext = obj.transform.FindChild("StatNumsText_TMP"); + devtitletext.GetComponent().text = GetString("Developer"); + devtitletext.GetComponent().alignment = TextAlignmentOptions.Center; + devtitletext.localPosition = new Vector3(-2.4f, 1.65f, -2f); + devtitletext.localScale = new Vector3(0.8f, 0.8f, 0.8f); + + var devtext = obj.transform.FindChild("StatsText_TMP"); + devtext.GetComponent().text = DevsData; + devtext.GetComponent().alignment = TextAlignmentOptions.Capline; + devtext.localPosition = new Vector3(-2.4f, 1.27f, -2f); + devtext.localScale = new Vector3(0.5f, 0.5f, 1f); + + var transtitletext = Object.Instantiate(devtitletext, obj.transform); + transtitletext.GetComponent().text = GetString("Translator"); + transtitletext.GetComponent().alignment = TextAlignmentOptions.Center; + transtitletext.localPosition = new Vector3(0f, 1.65f, -2f); + transtitletext.localScale = new Vector3(0.8f, 0.8f, 1f); + + var transtext = Object.Instantiate(devtext, obj.transform); + transtext.GetComponent().text = TransData; + transtext.GetComponent().alignment = TextAlignmentOptions.Capline; + transtext.localPosition = new Vector3(0f, 1.27f, -2f); + transtext.localScale = new Vector3(0.5f, 0.5f, 1f); + + var boostertitletext = Object.Instantiate(devtitletext, obj.transform); + boostertitletext.GetComponent().text = GetString("Booster"); + boostertitletext.GetComponent().alignment = TextAlignmentOptions.Center; + boostertitletext.localPosition = new Vector3(-2.4f, -1f, -2f); + boostertitletext.localScale = new Vector3(0.8f, 0.8f, 1f); + + var boostertext = Object.Instantiate(devtext, obj.transform); + boostertext.GetComponent().text = BoosterData; + boostertext.GetComponent().alignment = TextAlignmentOptions.Capline; + boostertext.localPosition = new Vector3(-2.4f, -1.38f, -2f); + boostertext.localScale = new Vector3(0.5f, 0.5f, 1f); + + var sponsortitletext = Object.Instantiate(devtitletext, obj.transform); + sponsortitletext.GetComponent().text = GetString("Sponsor"); + sponsortitletext.GetComponent().alignment = TextAlignmentOptions.Center; + sponsortitletext.localPosition = new Vector3(2.4f, 1.65f, -2f); + sponsortitletext.localScale = new Vector3(0.8f, 0.8f, 1f); + + var sponsortext = Object.Instantiate(devtext, obj.transform); + sponsortext.GetComponent().text = SponsersData; + sponsortext.GetComponent().alignment = TextAlignmentOptions.Capline; + sponsortext.localPosition = new Vector3(2.4f, 1.27f, -2f); + sponsortext.localScale = new Vector3(0.5f, 0.5f, 1f); + + var textobj = obj.transform.FindChild("Title_TMP"); + Object.Destroy(textobj.GetComponent()); + textobj.GetComponent().text = GetString("DevAndSpnTitle"); + textobj.GetComponent().alignment = TextAlignmentOptions.Center; + textobj.localScale = new Vector3(1.2f, 1.2f, 1f); + textobj.localPosition = new Vector3(0f, 2.2f, -2f); + obj.transform.FindChild("Background").localScale = new Vector3(1.5f, 1f, 1f); + obj.transform.FindChild("CloseButton").localPosition = new Vector3(-3.75f, 2.65f, 0); + } + public static MainMenuManager instance; + public static void Postfix(MainMenuManager __instance) + { + InitCredentialsData(); + instance = __instance; + AmongUsClient.Instance.StartCoroutine(ViewCredentialsCoro(__instance)); + } + } +} \ No newline at end of file diff --git a/Patches/MainMenuManagerPatch.cs b/Patches/MainMenuManagerPatch.cs new file mode 100644 index 000000000..aa41f2a90 --- /dev/null +++ b/Patches/MainMenuManagerPatch.cs @@ -0,0 +1,175 @@ +using HarmonyLib; +using System; +using TOHE.Modules; +using UnityEngine; +using static UnityEngine.UI.Button; +using Object = UnityEngine.Object; + +namespace TOHE; + +[HarmonyPatch] +public class MainMenuManagerPatch +{ + public static GameObject template; + //public static GameObject qqButton; + //public static GameObject discordButton; + public static GameObject updateButton; + + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start)), HarmonyPrefix] + public static void Start_Prefix(MainMenuManager __instance) + { + if (template == null) template = GameObject.Find("/MainUI/ExitGameButton"); + if (template == null) return; + + //if (CultureInfo.CurrentCulture.Name == "zh-CN") + //{ + // //生成QQ群按钮 + // if (qqButton == null) qqButton = Object.Instantiate(template, template.transform.parent); + // qqButton.name = "qqButton"; + // qqButton.transform.position = Vector3.Reflect(template.transform.position, Vector3.left); + + // var qqText = qqButton.transform.GetChild(0).GetComponent(); + // Color qqColor = new Color32(0, 164, 255, byte.MaxValue); + // PassiveButton qqPassiveButton = qqButton.GetComponent(); + // SpriteRenderer qqButtonSprite = qqButton.GetComponent(); + // qqPassiveButton.OnClick = new(); + // qqPassiveButton.OnClick.AddListener((Action)(() => Application.OpenURL(Main.QQInviteUrl))); + // qqPassiveButton.OnMouseOut.AddListener((Action)(() => qqButtonSprite.color = qqText.color = qqColor)); + // __instance.StartCoroutine(Effects.Lerp(0.01f, new Action((p) => qqText.SetText("QQ群")))); + // qqButtonSprite.color = qqText.color = qqColor; + // qqButton.gameObject.SetActive(Main.ShowQQButton && !Main.IsAprilFools); + //} + //else + //{ + // //Discordボタンを生成 + // if (discordButton == null) discordButton = Object.Instantiate(template, template.transform.parent); + // discordButton.name = "DiscordButton"; + // discordButton.transform.position = Vector3.Reflect(template.transform.position, Vector3.left); + + // var discordText = discordButton.transform.GetChild(0).GetComponent(); + // Color discordColor = new Color32(86, 98, 246, byte.MaxValue); + // PassiveButton discordPassiveButton = discordButton.GetComponent(); + // SpriteRenderer discordButtonSprite = discordButton.GetComponent(); + // discordPassiveButton.OnClick = new(); + // discordPassiveButton.OnClick.AddListener((Action)(() => Application.OpenURL(Main.DiscordInviteUrl))); + // discordPassiveButton.OnMouseOut.AddListener((Action)(() => discordButtonSprite.color = discordText.color = discordColor)); + // __instance.StartCoroutine(Effects.Lerp(0.01f, new Action((p) => discordText.SetText("Discord")))); + // discordButtonSprite.color = discordText.color = discordColor; + // discordButton.gameObject.SetActive(Main.ShowDiscordButton && !Main.IsAprilFools); + //} + + ////Updateボタンを生成 + if (updateButton == null) updateButton = Object.Instantiate(template, template.transform.parent); + updateButton.name = "UpdateButton"; + updateButton.transform.position = template.transform.position + new Vector3(0.25f, 0.75f); + updateButton.transform.GetChild(0).GetComponent().localScale *= 1.5f; + + var updateText = updateButton.transform.GetChild(0).GetComponent(); + Color updateColor = new Color32(247, 56, 23, byte.MaxValue); + PassiveButton updatePassiveButton = updateButton.GetComponent(); + SpriteRenderer updateButtonSprite = updateButton.GetComponent(); + updatePassiveButton.OnClick = new(); + updatePassiveButton.OnClick.AddListener((Action)(() => + { + updateButton.SetActive(false); + ModUpdater.StartUpdate(ModUpdater.downloadUrl); + })); + updatePassiveButton.OnMouseOut.AddListener((Action)(() => updateButtonSprite.color = updateText.color = updateColor)); + updateButtonSprite.color = updateText.color = updateColor; + updateButtonSprite.size *= 1.5f; + updateButton.SetActive(false); + +#if RELEASE + //フリープレイの無効化 + var freeplayButton = GameObject.Find("/MainUI/FreePlayButton"); + if (freeplayButton != null) + { + freeplayButton.GetComponent().OnClick = new(); + freeplayButton.GetComponent().OnClick.AddListener((Action)(() => Application.OpenURL("https://tohe.cc"))); + __instance.StartCoroutine(Effects.Lerp(0.01f, new Action((p) => freeplayButton.transform.GetChild(0).GetComponent().SetText(Translator.GetString("Website"))))); + } +#endif + + if (Main.IsAprilFools) return; + + var bottomTemplate = GameObject.Find("InventoryButton"); + if (bottomTemplate == null) return; + + var HorseButton = Object.Instantiate(bottomTemplate, bottomTemplate.transform.parent); + var passiveHorseButton = HorseButton.GetComponent(); + var spriteHorseButton = HorseButton.GetComponent(); + if (HorseModePatch.isHorseMode) spriteHorseButton.transform.localScale *= -1; + + spriteHorseButton.sprite = Utils.LoadSprite($"TOHE.Resources.Images.HorseButton.png", 75f); + passiveHorseButton.OnClick = new ButtonClickedEvent(); + passiveHorseButton.OnClick.AddListener((Action)(() => + { + RunLoginPatch.ClickCount++; + if (RunLoginPatch.ClickCount == 10) PlayerControl.LocalPlayer.RPCPlayCustomSound("Gunload", true); + if (RunLoginPatch.ClickCount == 20) PlayerControl.LocalPlayer.RPCPlayCustomSound("AWP", true); + + spriteHorseButton.transform.localScale *= -1; + HorseModePatch.isHorseMode = !HorseModePatch.isHorseMode; + var particles = Object.FindObjectOfType(); + if (particles != null) + { + particles.pool.ReclaimAll(); + particles.Start(); + } + })); + var DleksButton = Object.Instantiate(bottomTemplate, bottomTemplate.transform.parent); + var passiveDleksButton = DleksButton.GetComponent(); + var spriteDleksButton = DleksButton.GetComponent(); + if (DleksPatch.isDleks) spriteDleksButton.transform.localScale *= -1; + + spriteDleksButton.sprite = Utils.LoadSprite($"TOHE.Resources.Images.DleksButton.png", 75f); + passiveDleksButton.OnClick = new ButtonClickedEvent(); + passiveDleksButton.OnClick.AddListener((Action)(() => + { + RunLoginPatch.ClickCount++; + spriteDleksButton.transform.localScale *= -1; + DleksPatch.isDleks = !DleksPatch.isDleks; + var particles = Object.FindObjectOfType(); + if (particles != null) + { + particles.pool.ReclaimAll(); + particles.Start(); + } + })); + + var CreditsButton = Object.Instantiate(bottomTemplate, bottomTemplate.transform.parent); + var passiveCreditsButton = CreditsButton.GetComponent(); + var spriteCreditsButton = CreditsButton.GetComponent(); + + spriteCreditsButton.sprite = Utils.LoadSprite($"TOHE.Resources.Images.CreditsButton.png", 75f); + passiveCreditsButton.OnClick = new ButtonClickedEvent(); + passiveCreditsButton.OnClick.AddListener((Action)(() => + { + CredentialsPatch.LogoPatch.CreditsPopup?.SetActive(true); + })); + + Application.targetFrameRate = Main.UnlockFPS.Value ? 165 : 60; + } +} + +// 来源:https://github.com/ykundesu/SuperNewRoles/blob/master/SuperNewRoles/Patches/HorseModePatch.cs +[HarmonyPatch(typeof(Constants), nameof(Constants.ShouldHorseAround))] +public static class HorseModePatch +{ + public static bool isHorseMode = false; + public static bool Prefix(ref bool __result) + { + __result = isHorseMode; + return false; + } +} +[HarmonyPatch(typeof(Constants), nameof(Constants.ShouldFlipSkeld))] +public static class DleksPatch +{ + public static bool isDleks = false; + public static bool Prefix(ref bool __result) + { + __result = isDleks; + return false; + } +} diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs new file mode 100644 index 000000000..0dcd1604c --- /dev/null +++ b/Patches/MeetingHudPatch.cs @@ -0,0 +1,1133 @@ +using AmongUs.GameOptions; +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TOHE.Roles.Crewmate; +using TOHE.Roles.Impostor; +using TOHE.Roles.Neutral; +using UnityEngine; +using static TOHE.Translator; + +namespace TOHE; + +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CheckForEndVoting))] +class CheckForEndVotingPatch +{ + public static bool Prefix(MeetingHud __instance) + { + if (!AmongUsClient.Instance.AmHost) return true; + if (Medic.IsEnable) Medic.OnCheckMark(); + //Meeting Skip with vote counting on keystroke (m + delete) + var shouldSkip = false; + if (Input.GetKeyDown(KeyCode.F6)) + { + shouldSkip = true; + } + // + var voteLog = Logger.Handler("Vote"); + try + { + List statesList = new(); + MeetingHud.VoterState[] states; + foreach (var pva in __instance.playerStates) + { + if (pva == null) continue; + PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); + if (pc == null) continue; + //死んでいないディクテーターが投票済み + + //主动叛变 + if (pva.DidVote && pc.PlayerId == pva.VotedFor && pva.VotedFor < 253 && !pc.Data.IsDead) + { + if (Options.MadmateSpawnMode.GetInt() == 2 && Main.MadmateNum < CustomRoles.Madmate.GetCount() && Utils.CanBeMadmate(pc)) + { + Main.MadmateNum++; + pc.RpcSetCustomRole(CustomRoles.Madmate); + ExtendedPlayerControl.RpcSetCustomRole(pc.PlayerId, CustomRoles.Madmate); + Utils.NotifyRoles(true, pc, true); + Logger.Info("设置职业:" + pc?.Data?.PlayerName + " = " + pc.GetCustomRole().ToString() + " + " + CustomRoles.Madmate.ToString(), "Assign " + CustomRoles.Madmate.ToString()); + } + } + +//催眠师催眠 + + if (pc.Is(CustomRoles.Dictator) && pva.DidVote && pc.PlayerId != pva.VotedFor && pva.VotedFor < 253 && !pc.Data.IsDead) + { + var voteTarget = Utils.GetPlayerById(pva.VotedFor); + TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Suicide, pc.PlayerId); + statesList.Add(new() + { + VoterId = pva.TargetPlayerId, + VotedForId = pva.VotedFor + }); + states = statesList.ToArray(); + if (AntiBlackout.OverrideExiledPlayer) + { + __instance.RpcVotingComplete(states, null, true); + ExileControllerWrapUpPatch.AntiBlackout_LastExiled = voteTarget.Data; + } + else __instance.RpcVotingComplete(states, voteTarget.Data, false); //通常処理 + + Logger.Info($"{voteTarget.GetNameWithRole()} 被独裁者驱逐", "Dictator"); + CheckForDeathOnExile(PlayerState.DeathReason.Vote, pva.VotedFor); + Logger.Info("独裁投票,会议强制结束", "Special Phase"); + voteTarget.SetRealKiller(pc); + Main.LastVotedPlayerInfo = voteTarget.Data; + if (Main.LastVotedPlayerInfo != null) + ConfirmEjections(Main.LastVotedPlayerInfo); + return true; + } + + if (pva.DidVote && pva.VotedFor < 253 && !pc.Data.IsDead) + { + var voteTarget = Utils.GetPlayerById(pva.VotedFor); + if (voteTarget != null) + { + switch (pc.GetCustomRole()) + { + case CustomRoles.Divinator: + Divinator.OnVote(pc, voteTarget); + break; + case CustomRoles.Oracle: + Oracle.OnVote(pc, voteTarget); + break; + case CustomRoles.Eraser: + Eraser.OnVote(pc, voteTarget); + break; + case CustomRoles.Tracker: + Tracker.OnVote(pc, voteTarget); + break; + case CustomRoles.Godfather: + if (pc == null || voteTarget == null) break; + Main.GodfatherTarget = voteTarget.PlayerId; + break; + } + } + else if (pc.GetCustomRole() == CustomRoles.Godfather) Main.GodfatherTarget = byte.MaxValue; + } + } + foreach (var ps in __instance.playerStates) + { + if (shouldSkip) break; //Meeting Skip with vote counting on keystroke (m + delete) + + //死んでいないプレイヤーが投票していない + if (!(Main.PlayerStates[ps.TargetPlayerId].IsDead || ps.DidVote)) return false; + } + + GameData.PlayerInfo exiledPlayer = PlayerControl.LocalPlayer.Data; + bool tie = false; + + for (var i = 0; i < __instance.playerStates.Length; i++) + { + PlayerVoteArea ps = __instance.playerStates[i]; + if (ps == null) continue; + voteLog.Info(string.Format("{0,-2}{1}:{2,-3}{3}", ps.TargetPlayerId, Utils.PadRightV2($"({Utils.GetVoteName(ps.TargetPlayerId)})", 40), ps.VotedFor, $"({Utils.GetVoteName(ps.VotedFor)})")); + var voter = Utils.GetPlayerById(ps.TargetPlayerId); + if (voter == null || voter.Data == null || voter.Data.Disconnected) continue; + if (Options.VoteMode.GetBool()) + { + if (ps.VotedFor == 253 && !voter.Data.IsDead && //スキップ + !(Options.WhenSkipVoteIgnoreFirstMeeting.GetBool() && MeetingStates.FirstMeeting) && //初手会議を除く + !(Options.WhenSkipVoteIgnoreNoDeadBody.GetBool() && !MeetingStates.IsExistDeadBody) && //死体がない時を除く + !(Options.WhenSkipVoteIgnoreEmergency.GetBool() && MeetingStates.IsEmergencyMeeting) //緊急ボタンを除く + ) + { + switch (Options.GetWhenSkipVote()) + { + case VoteMode.Suicide: + TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Suicide, ps.TargetPlayerId); + voteLog.Info($"{voter.GetNameWithRole()}因跳过投票自杀"); + break; + case VoteMode.SelfVote: + ps.VotedFor = ps.TargetPlayerId; + voteLog.Info($"{voter.GetNameWithRole()}因跳过投票自票"); + break; + default: + break; + } + } + if (ps.VotedFor == 254 && !voter.Data.IsDead)//無投票 + { + switch (Options.GetWhenNonVote()) + { + case VoteMode.Suicide: + TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Suicide, ps.TargetPlayerId); + voteLog.Info($"{voter.GetNameWithRole()}因未投票自杀"); + break; + case VoteMode.SelfVote: + ps.VotedFor = ps.TargetPlayerId; + voteLog.Info($"{voter.GetNameWithRole()}因未投票自票"); + break; + case VoteMode.Skip: + ps.VotedFor = 253; + voteLog.Info($"{voter.GetNameWithRole()}因未投票跳过"); + break; + default: + break; + } + } + } + + //隐藏占卜师的票 + if (CheckRole(ps.TargetPlayerId, CustomRoles.Divinator) && Divinator.HideVote.GetBool()) continue; + //隐藏抹除者的票 + if (CheckRole(ps.TargetPlayerId, CustomRoles.Eraser) && Eraser.HideVote.GetBool()) continue; + + if (CheckRole(ps.TargetPlayerId, CustomRoles.Tracker) && Tracker.HideVote.GetBool()) continue; + + if (CheckRole(ps.TargetPlayerId, CustomRoles.Oracle) && Oracle.HideVote.GetBool()) continue; + + //主动叛变模式下自票无效 + if (ps.TargetPlayerId == ps.VotedFor && Options.MadmateSpawnMode.GetInt() == 2) continue; + + statesList.Add(new MeetingHud.VoterState() + { + VoterId = ps.TargetPlayerId, + VotedForId = ps.VotedFor + }); + + if (CheckRole(ps.TargetPlayerId, CustomRoles.Mayor) && !Options.MayorHideVote.GetBool()) //Mayorの投票数 + { + for (var i2 = 0; i2 < Options.MayorAdditionalVote.GetFloat(); i2++) + { + statesList.Add(new MeetingHud.VoterState() + { + VoterId = ps.TargetPlayerId, + VotedForId = ps.VotedFor + }); + } + } + if (CheckRole(ps.TargetPlayerId, CustomRoles.Vindicator) && !Options.VindicatorHideVote.GetBool()) //Vindicator + { + for (var i2 = 0; i2 < Options.VindicatorAdditionalVote.GetFloat(); i2++) + { + statesList.Add(new MeetingHud.VoterState() + { + VoterId = ps.TargetPlayerId, + VotedForId = ps.VotedFor + }); + } + } + } + states = statesList.ToArray(); + + var VotingData = __instance.CustomCalculateVotes(); + byte exileId = byte.MaxValue; + int max = 0; + voteLog.Info("===决定驱逐玩家处理开始==="); + foreach (var data in VotingData) + { + voteLog.Info($"{data.Key}({Utils.GetVoteName(data.Key)}):{data.Value}票"); + if (data.Value > max) + { + voteLog.Info(data.Key + "拥有更高票数(" + data.Value + ")"); + exileId = data.Key; + max = data.Value; + tie = false; + } + else if (data.Value == max) + { + voteLog.Info(data.Key + "与" + exileId + "的票数相同(" + data.Value + ")"); + exileId = byte.MaxValue; + tie = true; + } + voteLog.Info($"驱逐ID: {exileId}, 最大: {max}票"); + } + + voteLog.Info($"决定驱逐玩家: {exileId}({Utils.GetVoteName(exileId)})"); + + bool braked = false; + if (tie) //破平者判断 + { + byte target = byte.MaxValue; + foreach (var data in VotingData.Where(x => x.Key < 15 && x.Value == max)) + { + if (Main.BrakarVoteFor.Contains(data.Key)) + { + if (target != byte.MaxValue) + { + target = byte.MaxValue; + break; + } + target = data.Key; + } + } + if (target != byte.MaxValue) + { + Logger.Info("破平者覆盖驱逐玩家", "Brakar Vote"); + exiledPlayer = Utils.GetPlayerInfoById(target); + tie = false; + braked = true; + } + } + + Collector.CollectAmount(VotingData, __instance); + + if (Options.VoteMode.GetBool() && Options.WhenTie.GetBool() && tie) + { + switch ((TieMode)Options.WhenTie.GetValue()) + { + case TieMode.Default: + exiledPlayer = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(info => info.PlayerId == exileId); + break; + case TieMode.All: + var exileIds = VotingData.Where(x => x.Key < 15 && x.Value == max).Select(kvp => kvp.Key).ToArray(); + foreach (var playerId in exileIds) + Utils.GetPlayerById(playerId).SetRealKiller(null); + TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Vote, exileIds); + exiledPlayer = null; + break; + case TieMode.Random: + exiledPlayer = GameData.Instance.AllPlayers.ToArray().OrderBy(_ => Guid.NewGuid()).FirstOrDefault(x => VotingData.TryGetValue(x.PlayerId, out int vote) && vote == max); + tie = false; + break; + } + } + else if (!braked) + exiledPlayer = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(info => !tie && info.PlayerId == exileId); + exiledPlayer?.Object.SetRealKiller(null); + + //RPC + if (AntiBlackout.OverrideExiledPlayer) + { + __instance.RpcVotingComplete(states, null, true); + ExileControllerWrapUpPatch.AntiBlackout_LastExiled = exiledPlayer; + } + else __instance.RpcVotingComplete(states, exiledPlayer, tie); //通常処理 + + CheckForDeathOnExile(PlayerState.DeathReason.Vote, exileId); + + Main.LastVotedPlayerInfo = exiledPlayer; + if (Main.LastVotedPlayerInfo != null) + ConfirmEjections(Main.LastVotedPlayerInfo); + + return false; + } + catch (Exception ex) + { + Logger.SendInGame(string.Format(GetString("Error.MeetingException"), ex.Message), true); + throw; + } + } + + // 参考:https://github.com/music-discussion/TownOfHost-TheOtherRoles + private static void ConfirmEjections(GameData.PlayerInfo exiledPlayer) + { + if (!AmongUsClient.Instance.AmHost) return; + if (exiledPlayer == null) return; + var exileId = exiledPlayer.PlayerId; + if (exileId is < 0 or > 254) return; + var realName = exiledPlayer.Object.GetRealName(isMeeting: true); + Main.LastVotedPlayer = realName; + + var player = Utils.GetPlayerById(exiledPlayer.PlayerId); + var role = GetString(exiledPlayer.GetCustomRole().ToString()); + var crole = exiledPlayer.GetCustomRole(); + var coloredRole = Utils.GetDisplayRoleName(exileId, true); + if (Options.ConfirmEgoistOnEject.GetBool() && player.Is(CustomRoles.Egoist)) + coloredRole = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Egoist), GetRoleString("Temp.Blank") + coloredRole.RemoveHtmlTags()); + // if (Options.ConfirmSidekickOnEject.GetBool() && player.Is(CustomRoles.Sidekick)) + // coloredRole = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Sidekick), GetRoleString("Temp.Blank") + coloredRole.RemoveHtmlTags()); + if (Options.ConfirmLoversOnEject.GetBool() && player.Is(CustomRoles.Lovers)) + coloredRole = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lovers), GetRoleString("Temp.Blank") + coloredRole.RemoveHtmlTags()); + if (Options.RascalAppearAsMadmate.GetBool() && player.Is(CustomRoles.Rascal)) + coloredRole = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Madmate), GetRoleString("Mad-") + coloredRole.RemoveHtmlTags()); + var name = ""; + int impnum = 0; + int neutralnum = 0; + + if (CustomRolesHelper.RoleExist(CustomRoles.Bard)) + { + Main.BardCreations++; + try { name = ModUpdater.Get("https://v1.hitokoto.cn/?encode=text"); } + catch { name = GetString("ByBardGetFailed"); } + name += "\n\t\t——" + GetString("ByBard"); + goto EndOfSession; + } + + foreach (var pc in Main.AllAlivePlayerControls) + { + var pc_role = pc.GetCustomRole(); + if (pc_role.IsImpostor() && pc != exiledPlayer.Object) + impnum++; + else if (pc_role.IsNeutralKilling() && pc != exiledPlayer.Object) + neutralnum++; + } + switch (Options.CEMode.GetInt()) + { + case 0: + name = string.Format(GetString("PlayerExiled"), realName); + break; + case 1: + if (player.GetCustomRole().IsImpostor() || player.Is(CustomRoles.Parasite) || player.Is(CustomRoles.Crewpostor) || player.Is(CustomRoles.Refugee) || player.Is(CustomRoles.Convict)) + name = string.Format(GetString("BelongTo"), realName, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("TeamImpostor"))); + else if (player.GetCustomRole().IsCrewmate()) + name = string.Format(GetString("IsGood"), realName); + else if (player.GetCustomRole().IsNeutral() && !player.Is(CustomRoles.Parasite) && !player.Is(CustomRoles.Refugee) && !player.Is(CustomRoles.Crewpostor) && !player.Is(CustomRoles.Convict)) + name = string.Format(GetString("BelongTo"), realName, Utils.ColorString(new Color32(127, 140, 141, byte.MaxValue), GetString("TeamNeutral"))); + break; + case 2: + name = string.Format(GetString("PlayerIsRole"), realName, coloredRole); + if (Options.ShowTeamNextToRoleNameOnEject.GetBool()) + { + name += " ("; + if (player.GetCustomRole().IsImpostor() || player.Is(CustomRoles.Madmate)) + name += Utils.ColorString(new Color32(255, 25, 25, byte.MaxValue), GetString("TeamImpostor")); + else if (player.GetCustomRole().IsNeutral() || player.Is(CustomRoles.Charmed)) + name += Utils.ColorString(new Color32(127, 140, 141, byte.MaxValue), GetString("TeamNeutral")); + else if (player.GetCustomRole().IsCrewmate()) + name += Utils.ColorString(new Color32(140, 255, 255, byte.MaxValue), GetString("TeamCrewmate")); + name += ")"; + } + break; + } + var DecidedWinner = false; + //小丑胜利 + /* if (crole == CustomRoles.Jester) + { + name = string.Format(GetString("ExiledJester"), realName, coloredRole); + DecidedWinner = true; + } + //处刑人胜利 + if (Executioner.CheckExileTarget(exiledPlayer, DecidedWinner, true)) + { + name = string.Format(GetString("ExiledExeTarget"), realName, coloredRole); + DecidedWinner = true; + } + //冤罪师胜利 + if (Main.AllPlayerControls.Any(x => x.Is(CustomRoles.Innocent) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == exileId)) + { + if (!(!Options.InnocentCanWinByImp.GetBool() && crole.IsImpostor())) + { + if (DecidedWinner) name += string.Format(GetString("ExiledInnocentTargetAddBelow")); + else name = string.Format(GetString("ExiledInnocentTargetInOneLine"), realName, coloredRole); + DecidedWinner = true; + } + } */ + + if (DecidedWinner) name += ""; + if (Options.ShowImpRemainOnEject.GetBool() && !DecidedWinner) + { + name += "\n"; + string comma = neutralnum > 0 ? "," : ""; + if (impnum == 0) name += GetString("NoImpRemain") + comma; + if (impnum == 1) name += GetString("OneImpRemain") + comma; + if (impnum == 2) name += GetString("TwoImpRemain") + comma; + if (impnum == 3) name += GetString("ThreeImpRemain") + comma; + // else name += string.Format(GetString("ImpRemain"), impnum) + comma; + if (Options.ShowNKRemainOnEject.GetBool() && neutralnum > 0) + name += string.Format(GetString("NeutralRemain"), neutralnum); + } + + EndOfSession: + + + name += ""; + new LateTask(() => + { + Main.DoBlockNameChange = true; + if (GameStates.IsInGame) player.RpcSetName(name); + }, 3.0f, "Change Exiled Player Name"); + new LateTask(() => + { + if (GameStates.IsInGame && !player.Data.Disconnected) + { + player.RpcSetName(realName); + Main.DoBlockNameChange = false; + } + }, 11.5f, "Change Exiled Player Name Back"); + } + public static bool CheckRole(byte id, CustomRoles role) + { + var player = Main.AllPlayerControls.Where(pc => pc.PlayerId == id).FirstOrDefault(); + return player != null && player.Is(role); + } + public static void TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason deathReason, params byte[] playerIds) + { + var AddedIdList = new List(); + foreach (var playerId in playerIds) + if (Main.AfterMeetingDeathPlayers.TryAdd(playerId, deathReason)) + AddedIdList.Add(playerId); + CheckForDeathOnExile(deathReason, AddedIdList.ToArray()); + } + public static void CheckForDeathOnExile(PlayerState.DeathReason deathReason, params byte[] playerIds) + { + Witch.OnCheckForEndVoting(deathReason, playerIds); + HexMaster.OnCheckForEndVoting(deathReason, playerIds); + Virus.OnCheckForEndVoting(deathReason, playerIds); + foreach (var playerId in playerIds) + { + // Baker.OnCheckForEndVoting(playerId); + + //Loversの後追い + if (CustomRoles.Lovers.IsEnable() && !Main.isLoversDead && Main.LoversPlayers.Find(lp => lp.PlayerId == playerId) != null) + FixedUpdatePatch.LoversSuicide(playerId, true); + //道連れチェック + RevengeOnExile(playerId, deathReason); + } + } + private static void RevengeOnExile(byte playerId, PlayerState.DeathReason deathReason) + { + var player = Utils.GetPlayerById(playerId); + if (player == null) return; + var target = PickRevengeTarget(player, deathReason); + if (target == null) return; + TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Revenge, target.PlayerId); + target.SetRealKiller(player); + Logger.Info($"{player.GetNameWithRole()}の道連れ先:{target.GetNameWithRole()}", "RevengeOnExile"); + } + private static PlayerControl PickRevengeTarget(PlayerControl exiledplayer, PlayerState.DeathReason deathReason)//道連れ先選定 + { + List TargetList = new(); + foreach (var candidate in Main.AllAlivePlayerControls) + { + if (candidate == exiledplayer || Main.AfterMeetingDeathPlayers.ContainsKey(candidate.PlayerId)) continue; + } + if (TargetList == null || TargetList.Count == 0) return null; + var rand = IRandom.Instance; + var target = TargetList[rand.Next(TargetList.Count)]; + return target; + } +} + +static class ExtendedMeetingHud +{ + public static Dictionary CustomCalculateVotes(this MeetingHud __instance) + { + Logger.Info("===计算票数处理开始===", "Vote"); + Dictionary dic = new(); + Main.BrakarVoteFor = new(); + Collector.CollectorVoteFor = new(); + //| 投票された人 | 投票された回数 | + for (int i = 0; i < __instance.playerStates.Length; i++) + { + PlayerVoteArea ps = __instance.playerStates[i];//该玩家面板里的所有会议中的玩家 + if (ps == null) continue; + if (ps.VotedFor is not 252 and not byte.MaxValue and not 254)//该玩家面板里是否投了该玩家 + { + // 默认票数1票 + int VoteNum = 1; + + // 投票给有效玩家时才进行的判断 + var target = Utils.GetPlayerById(ps.VotedFor); + if (target != null) + { + // 僵尸、活死人无法被票 + if (target.Is(CustomRoles.Zombie)) VoteNum = 0; + // 记录破平者投票 + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Brakar)) + if (!Main.BrakarVoteFor.Contains(target.PlayerId)) + Main.BrakarVoteFor.Add(target.PlayerId); + // 集票者记录数据 + Collector.CollectorVotes(target, ps); + } + + //市长附加票数 + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Mayor) + && ps.TargetPlayerId != ps.VotedFor + ) VoteNum += Options.MayorAdditionalVote.GetInt(); + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Knighted) + && ps.TargetPlayerId != ps.VotedFor + ) VoteNum += 1; + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Glitch) + && ps.TargetPlayerId != ps.VotedFor + ) VoteNum = 0; + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Vindicator) + && ps.TargetPlayerId != ps.VotedFor + ) VoteNum += Options.VindicatorAdditionalVote.GetInt(); + if (Options.DualVotes.GetBool()) + { + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.DualPersonality) + && ps.TargetPlayerId != ps.VotedFor + ) VoteNum += VoteNum; + } + //窃票者附加票数 + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.TicketsStealer)) + VoteNum += (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == ps.TargetPlayerId) * Options.TicketsPerKill.GetFloat()); + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Pickpocket)) + VoteNum += (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == ps.TargetPlayerId) * Pickpocket.VotesPerKill.GetFloat()); + + // 主动叛变模式下自票无效 + if (ps.TargetPlayerId == ps.VotedFor && Options.MadmateSpawnMode.GetInt() == 2) VoteNum = 0; + + //投票を1追加 キーが定義されていない場合は1で上書きして定義 + dic[ps.VotedFor] = !dic.TryGetValue(ps.VotedFor, out int num) ? VoteNum : num + VoteNum;//统计该玩家被投的数量 + } + } + return dic; + } +} +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] +class MeetingHudStartPatch +{ + public static void NotifyRoleSkillOnMeetingStart() + { + if (!AmongUsClient.Instance.AmHost) return; + + List<(string, byte, string)> msgToSend = new(); + + void AddMsg(string text, byte sendTo = 255, string title = "") + => msgToSend.Add((text, sendTo, title)); + + //首次会议技能提示 + if (Options.SendRoleDescriptionFirstMeeting.GetBool() && MeetingStates.FirstMeeting) + foreach (var pc in Main.AllAlivePlayerControls.Where(x => !x.IsModClient())) + { + var role = pc.GetCustomRole(); + var sb = new StringBuilder(); + sb.Append(GetString(role.ToString()) + Utils.GetRoleMode(role) + pc.GetRoleInfo(true)); + if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) + Utils.ShowChildrenSettings(opt, ref sb, command: true); + var txt = sb.ToString(); + sb.Clear().Append(txt.RemoveHtmlTags()); + foreach (var subRole in Main.PlayerStates[pc.PlayerId].SubRoles) + sb.Append($"\n\n" + GetString($"{subRole}") + Utils.GetRoleMode(subRole) + GetString($"{subRole}InfoLong")); + if (CustomRolesHelper.RoleExist(CustomRoles.Ntr) && (role is not CustomRoles.GM and not CustomRoles.Ntr)) + sb.Append($"\n\n" + GetString($"Lovers") + Utils.GetRoleMode(CustomRoles.Lovers) + GetString($"LoversInfoLong")); + AddMsg(sb.ToString(), pc.PlayerId); + } + if (msgToSend.Count >= 1) + { + var msgTemp = msgToSend.ToList(); + new LateTask(() => { msgTemp.Do(x => Utils.SendMessage(x.Item1, x.Item2, x.Item3)); }, 3f, "Skill Description First Meeting"); + } + msgToSend = new(); + + //主动叛变模式提示 + if (Options.MadmateSpawnMode.GetInt() == 2 && CustomRoles.Madmate.GetCount() > 0) + AddMsg(string.Format(GetString("Message.MadmateSelfVoteModeNotify"), GetString("MadmateSpawnMode.SelfVote"))); + //提示神存活 + if (CustomRoles.God.RoleExist() && Options.NotifyGodAlive.GetBool()) + AddMsg(GetString("GodNoticeAlive"), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.God), GetString("GodAliveTitle"))); + //工作狂的生存技巧 + if (MeetingStates.FirstMeeting && CustomRoles.Workaholic.RoleExist() && Options.WorkaholicGiveAdviceAlive.GetBool() && !Options.WorkaholicCannotWinAtDeath.GetBool() && !Options.GhostIgnoreTasks.GetBool()) + { + foreach (var pc in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Workaholic))) + Main.WorkaholicAlive.Add(pc.PlayerId); + List workaholicAliveList = new(); + foreach (var whId in Main.WorkaholicAlive) + workaholicAliveList.Add(Main.AllPlayerNames[whId]); + string separator = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.English or SupportedLangs.Russian ? "], [" : "】, 【"; + AddMsg(string.Format(GetString("WorkaholicAdviceAlive"), string.Join(separator, workaholicAliveList)), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Workaholic), GetString("WorkaholicAliveTitle"))); + } + //Bait Notify + if (MeetingStates.FirstMeeting && CustomRoles.Bait.RoleExist() && Options.BaitNotification.GetBool()) + { + foreach (var pc in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Bait))) + Main.BaitAlive.Add(pc.PlayerId); + List baitAliveList = new(); + foreach (var whId in Main.BaitAlive) + baitAliveList.Add(Main.AllPlayerNames[whId]); + string separator = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.English or SupportedLangs.Russian ? "], [" : "】, 【"; + AddMsg(string.Format(GetString("BaitAdviceAlive"), string.Join(separator, baitAliveList)), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Bait), GetString("BaitAliveTitle"))); + } + string MimicMsg = ""; + foreach (var pc in Main.AllPlayerControls) + { + //黑手党死后技能提示 + if (pc.Is(CustomRoles.Mafia) && !pc.IsAlive()) + AddMsg(GetString("MafiaDeadMsg"), pc.PlayerId); + if (pc.Is(CustomRoles.Retributionist) && !pc.IsAlive()) + AddMsg(GetString("RetributionistDeadMsg"), pc.PlayerId); + //网红死亡消息提示 + foreach (var csId in Main.CyberStarDead) + { + if (!Options.ImpKnowCyberStarDead.GetBool() && pc.GetCustomRole().IsImpostor()) continue; + if (!Options.NeutralKnowCyberStarDead.GetBool() && pc.GetCustomRole().IsNeutral()) continue; + AddMsg(string.Format(GetString("CyberStarDead"), Main.AllPlayerNames[csId]), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.CyberStar), GetString("CyberStarNewsTitle"))); + } + + //侦探报告线索 + if (Main.DetectiveNotify.ContainsKey(pc.PlayerId)) + AddMsg(Main.DetectiveNotify[pc.PlayerId], pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Detective), GetString("DetectiveNoticeTitle"))); + //宝箱怪的消息(记录) + if (pc.Is(CustomRoles.Mimic) && !pc.IsAlive()) + Main.AllAlivePlayerControls.Where(x => x.GetRealKiller()?.PlayerId == pc.PlayerId).Do(x => MimicMsg += $"\n{x.GetNameWithRole(true)}"); + //入殓师的检查 + if (Mortician.msgToSend.ContainsKey(pc.PlayerId)) + AddMsg(Mortician.msgToSend[pc.PlayerId], pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mortician), GetString("MorticianCheckTitle"))); + //通灵师的提示(自己) + if (Mediumshiper.ContactPlayer.ContainsValue(pc.PlayerId)) + AddMsg(string.Format(GetString("MediumshipNotifySelf"), Main.AllPlayerNames[Mediumshiper.ContactPlayer.Where(x => x.Value == pc.PlayerId).FirstOrDefault().Key], Mediumshiper.ContactLimit[pc.PlayerId]), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mediumshiper), GetString("MediumshipTitle"))); + //通灵师的提示(目标) + if (Mediumshiper.ContactPlayer.ContainsKey(pc.PlayerId) && (!Mediumshiper.OnlyReceiveMsgFromCrew.GetBool() || pc.GetCustomRole().IsCrewmate())) + AddMsg(string.Format(GetString("MediumshipNotifyTarget"), Main.AllPlayerNames[Mediumshiper.ContactPlayer[pc.PlayerId]]), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mediumshiper), GetString("MediumshipTitle"))); + if (Main.VirusNotify.ContainsKey(pc.PlayerId)) + AddMsg(Main.VirusNotify[pc.PlayerId], pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Virus), GetString("VirusNoticeTitle"))); + if (Tracker.msgToSend.ContainsKey(pc.PlayerId)) + AddMsg(Tracker.msgToSend[pc.PlayerId], pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Tracker), GetString("TrackerTitle"))); + } + //宝箱怪的消息(合并) + if (MimicMsg != "") + { + MimicMsg = GetString("MimicDeadMsg") + "\n" + MimicMsg; + foreach (var ipc in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsImpostorTeam())) + AddMsg(MimicMsg, ipc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mimic), GetString("MimicMsgTitle"))); + } + + msgToSend.Do(x => Logger.Info($"To:{x.Item2} {x.Item3} => {x.Item1}", "Skill Notice OnMeeting Start")); + + //总体延迟发送 + new LateTask(() => { msgToSend.Do(x => Utils.SendMessage(x.Item1, x.Item2, x.Item3)); }, 3f, "Skill Notice OnMeeting Start"); + + Main.CyberStarDead.Clear(); + Main.DetectiveNotify.Clear(); + Main.VirusNotify.Clear(); + Mortician.msgToSend.Clear(); + Tracker.msgToSend.Clear(); + Pirate.OnMeetingStart(); + } + public static void Prefix(MeetingHud __instance) + { + Logger.Info("------------会议开始------------", "Phase"); + ChatUpdatePatch.DoBlockChat = true; + GameStates.AlreadyDied |= !Utils.IsAllAlive; + Main.AllPlayerControls.Do(x => ReportDeadBodyPatch.WaitReport[x.PlayerId].Clear()); + MeetingStates.MeetingCalled = true; + } + public static void Postfix(MeetingHud __instance) + { + SoundManager.Instance.ChangeAmbienceVolume(0f); + if (!GameStates.IsModHost) return; + + //提前储存赌怪游戏组件的模板 + GuessManager.textTemplate = UnityEngine.Object.Instantiate(__instance.playerStates[0].NameText); + GuessManager.textTemplate.enabled = false; + + foreach (var pva in __instance.playerStates) + { + var pc = Utils.GetPlayerById(pva.TargetPlayerId); + if (pc == null) continue; + var RoleTextData = Utils.GetRoleText(PlayerControl.LocalPlayer.PlayerId, pc.PlayerId); + var roleTextMeeting = UnityEngine.Object.Instantiate(pva.NameText); + roleTextMeeting.transform.SetParent(pva.NameText.transform); + roleTextMeeting.transform.localPosition = new Vector3(0f, -0.18f, 0f); + roleTextMeeting.fontSize = 1.5f; + roleTextMeeting.text = RoleTextData.Item1; + if (Main.VisibleTasksCount) roleTextMeeting.text += Utils.GetProgressText(pc); + roleTextMeeting.color = RoleTextData.Item2; + roleTextMeeting.gameObject.name = "RoleTextMeeting"; + roleTextMeeting.enableWordWrapping = false; + roleTextMeeting.enabled = + pc.AmOwner || //対象がLocalPlayer + (Main.VisibleTasksCount && PlayerControl.LocalPlayer.Data.IsDead && Options.GhostCanSeeOtherRoles.GetBool()) || //LocalPlayerが死亡していて幽霊が他人の役職を見れるとき + (PlayerControl.LocalPlayer.Is(CustomRoles.Mimic) && Main.VisibleTasksCount && pc.Data.IsDead && Options.MimicCanSeeDeadRoles.GetBool()) || //LocalPlayerが死亡していて幽霊が他人の役職を見れるとき + (pc.Is(CustomRoles.Gravestone) && Main.VisibleTasksCount && pc.Data.IsDead) || //LocalPlayerが死亡していて幽霊が他人の役職を見れるとき + (pc.Is(CustomRoles.Lovers) && PlayerControl.LocalPlayer.Is(CustomRoles.Lovers) && Options.LoverKnowRoles.GetBool()) || + (pc.Is(CustomRoles.Ntr) && Options.LoverKnowRoles.GetBool()) || + (pc.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && Options.ImpKnowAlliesRole.GetBool()) || + (pc.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.Is(CustomRoles.Madmate) && Options.MadmateKnowWhosImp.GetBool()) || + (pc.Is(CustomRoles.Madmate) && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && Options.ImpKnowWhosMadmate.GetBool()) || + (pc.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.Is(CustomRoles.Crewpostor) && Options.AlliesKnowCrewpostor.GetBool()) || + (pc.Is(CustomRoles.Crewpostor) && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && Options.CrewpostorKnowsAllies.GetBool()) || + (pc.Is(CustomRoles.Madmate) && PlayerControl.LocalPlayer.Is(CustomRoles.Madmate) && Options.MadmateKnowWhosMadmate.GetBool()) || + (pc.Is(CustomRoles.Rogue) && PlayerControl.LocalPlayer.Is(CustomRoles.Rogue) && Options.RogueKnowEachOther.GetBool() && Options.RogueKnowEachOtherRoles.GetBool()) || + (pc.Is(CustomRoles.Jackal) && PlayerControl.LocalPlayer.Is(CustomRoles.Sidekick)) || + (pc.Is(CustomRoles.Jackal) && PlayerControl.LocalPlayer.Is(CustomRoles.Recruit)) || + (pc.Is(CustomRoles.Sidekick) && PlayerControl.LocalPlayer.Is(CustomRoles.Sidekick)) || + (pc.Is(CustomRoles.Sidekick) && PlayerControl.LocalPlayer.Is(CustomRoles.Jackal)) || + (pc.Is(CustomRoles.Workaholic) && Options.WorkaholicVisibleToEveryone.GetBool()) || + (pc.Is(CustomRoles.Doctor) && !pc.GetCustomRole().IsEvilAddons() && Options.DoctorVisibleToEveryone.GetBool()) || + (pc.Is(CustomRoles.Mayor) && Options.MayorRevealWhenDoneTasks.GetBool() && pc.GetPlayerTaskState().IsTaskFinished) || + (pc.Is(CustomRoles.Marshall) && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Crewmate) && pc.GetPlayerTaskState().IsTaskFinished) || + (Totocalcio.KnowRole(PlayerControl.LocalPlayer, pc)) || + (EvilDiviner.IsShowTargetRole(PlayerControl.LocalPlayer, pc)) || + (Ritualist.IsShowTargetRole(PlayerControl.LocalPlayer, pc)) || + (Lawyer.KnowRole(PlayerControl.LocalPlayer, pc)) || + (Executioner.KnowRole(PlayerControl.LocalPlayer, pc)) || + (Succubus.KnowRole(PlayerControl.LocalPlayer, pc)) || + (CursedSoul.KnowRole(PlayerControl.LocalPlayer, pc)) || + (Admirer.KnowRole(PlayerControl.LocalPlayer, pc)) || + (Amnesiac.KnowRole(PlayerControl.LocalPlayer, pc)) || + (Infectious.KnowRole(PlayerControl.LocalPlayer, pc)) || + (Virus.KnowRole(PlayerControl.LocalPlayer, pc)) || + PlayerControl.LocalPlayer.IsRevealedPlayer(pc) || + PlayerControl.LocalPlayer.Is(CustomRoles.God) || + PlayerControl.LocalPlayer.Is(CustomRoles.GM) || + Main.GodMode.Value; + + Baker.SendAliveMessage(pc); + + if (!PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.LocalPlayer.IsRevealedPlayer(pc) && pc.Is(CustomRoles.Trickster)) + { + roleTextMeeting.text = Farseer.RandomRole[PlayerControl.LocalPlayer.PlayerId]; + roleTextMeeting.text += Farseer.GetTaskState(); + } + + if (EvilTracker.IsTrackTarget(PlayerControl.LocalPlayer, pc) && EvilTracker.CanSeeLastRoomInMeeting) + { + roleTextMeeting.text = EvilTracker.GetArrowAndLastRoom(PlayerControl.LocalPlayer, pc); + roleTextMeeting.enabled = true; + } + if (Tracker.IsTrackTarget(PlayerControl.LocalPlayer, pc) && Tracker.CanSeeLastRoomInMeeting) + { + roleTextMeeting.text = Tracker.GetArrowAndLastRoom(PlayerControl.LocalPlayer, pc); + roleTextMeeting.enabled = true; + } + } + + if (Options.SyncButtonMode.GetBool()) + { + Utils.SendMessage(string.Format(GetString("Message.SyncButtonLeft"), Options.SyncedButtonCount.GetFloat() - Options.UsedButtonCount)); + Logger.Info("紧急会议剩余 " + (Options.SyncedButtonCount.GetFloat() - Options.UsedButtonCount) + " 次使用次数", "SyncButtonMode"); + } + if (AntiBlackout.OverrideExiledPlayer) + { + new LateTask(() => + { + Utils.SendMessage(GetString("Warning.OverrideExiledPlayer"), 255, Utils.ColorString(Color.red, GetString("DefaultSystemMessageTitle"))); + }, 5f, "Warning OverrideExiledPlayer"); + } + if (MeetingStates.FirstMeeting) TemplateManager.SendTemplate("OnFirstMeeting", noErr: true); + TemplateManager.SendTemplate("OnMeeting", noErr: true); + + if (AmongUsClient.Instance.AmHost) + NotifyRoleSkillOnMeetingStart(); + + if (AmongUsClient.Instance.AmHost) + { + _ = new LateTask(() => + { + foreach (var pc in Main.AllPlayerControls) + { + pc.RpcSetNameEx(pc.GetRealName(isMeeting: true)); + } + ChatUpdatePatch.DoBlockChat = false; + }, 3f, "SetName To Chat"); + } + + foreach (var pva in __instance.playerStates) + { + if (pva == null) continue; + PlayerControl seer = PlayerControl.LocalPlayer; + PlayerControl target = Utils.GetPlayerById(pva.TargetPlayerId); + if (target == null) continue; + + var sb = new StringBuilder(); + + //会議画面での名前変更 + //自分自身の名前の色を変更 + //NameColorManager準拠の処理 + pva.NameText.text = pva.NameText.text.ApplyNameColorData(seer, target, true); + + + // Guesser Mode // + if (Options.GuesserMode.GetBool()) + { + if (Options.CrewmatesCanGuess.GetBool() && seer.GetCustomRole().IsCrewmate() && !seer.Is(CustomRoles.Judge) && !seer.Is(CustomRoles.ParityCop)) + if (!seer.Data.IsDead && !target.Data.IsDead) + pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; + if (Options.ImpostorsCanGuess.GetBool() && seer.GetCustomRole().IsImpostor() && !seer.Is(CustomRoles.Councillor)) + if (!seer.Data.IsDead && !target.Data.IsDead) + pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; + if (Options.NeutralKillersCanGuess.GetBool() && seer.GetCustomRole().IsNK()) + if (!seer.Data.IsDead && !target.Data.IsDead) + pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; + if (Options.PassiveNeutralsCanGuess.GetBool() && seer.GetCustomRole().IsNonNK() && !seer.Is(CustomRoles.Doomsayer)) + if (!seer.Data.IsDead && !target.Data.IsDead) + pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; + + } + //とりあえずSnitchは会議中にもインポスターを確認することができる仕様にしていますが、変更する可能性があります。 + + if (seer.KnowDeathReason(target)) + sb.Append($"({Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doctor), Utils.GetVitalText(target.PlayerId))})"); + /* if (seer.KnowDeadTeam(target)) + { + if (target.Is(CustomRoleTypes.Crewmate) && !(target.Is(CustomRoles.Madmate) || target.Is(CustomRoles.Egoist) || target.Is(CustomRoles.Charmed) || target.Is(CustomRoles.Recruit) || target.Is(CustomRoles.Infected) || target.Is(CustomRoles.Contagious) || target.Is(CustomRoles.Rogue) || target.Is(CustomRoles.Rascal) || target.Is(CustomRoles.Soulless) || !target.Is(CustomRoles.Admired))) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Bait), "★")); + + if (target.Is(CustomRoleTypes.Impostor) || target.Is(CustomRoles.Madmate) || target.Is(CustomRoles.Rascal) || target.Is(CustomRoles.Parasite) || target.Is(CustomRoles.Refugee) || target.Is(CustomRoles.Crewpostor) || target.Is(CustomRoles.Convict) || !target.Is(CustomRoles.Admired)) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), "★")); + + if (target.Is(CustomRoleTypes.Neutral) || target.Is(CustomRoles.Rogue) || target.Is(CustomRoles.Contagious) || target.Is(CustomRoles.Charmed) || target.Is(CustomRoles.Recruit) || target.Is(CustomRoles.Infected) || target.Is(CustomRoles.Egoist) || target.Is(CustomRoles.Soulless) || !target.Is(CustomRoles.Admired) || !target.Is(CustomRoles.Parasite) || !target.Is(CustomRoles.Refugee) || !target.Is(CustomRoles.Crewpostor) || !target.Is(CustomRoles.Convict)) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Executioner), "★")); + + + + } + if (seer.KnowLivingTeam(target)) + { + if (target.Is(CustomRoleTypes.Crewmate) && !(target.Is(CustomRoles.Madmate) || target.Is(CustomRoles.Egoist) || target.Is(CustomRoles.Charmed) || target.Is(CustomRoles.Recruit) || target.Is(CustomRoles.Infected) || target.Is(CustomRoles.Contagious) || target.Is(CustomRoles.Rogue) || target.Is(CustomRoles.Rascal) || target.Is(CustomRoles.Admired))) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Bait), "★")); + + if (target.Is(CustomRoleTypes.Impostor) || target.Is(CustomRoles.Madmate) || target.Is(CustomRoles.Rascal) || target.Is(CustomRoles.Parasite) || target.Is(CustomRoles.Refugee) || target.Is(CustomRoles.Crewpostor) || target.Is(CustomRoles.Convict) || !target.Is(CustomRoles.Admired)) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), "★")); + + if (target.Is(CustomRoleTypes.Neutral) || target.Is(CustomRoles.Rogue) || target.Is(CustomRoles.Contagious) || target.Is(CustomRoles.Charmed) || target.Is(CustomRoles.Recruit) || target.Is(CustomRoles.Infected) || target.Is(CustomRoles.Egoist) || target.Is(CustomRoles.Soulless) || !target.Is(CustomRoles.Admired) || !target.Is(CustomRoles.Parasite) || !target.Is(CustomRoles.Refugee) || !target.Is(CustomRoles.Crewpostor) || !target.Is(CustomRoles.Convict)) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Executioner), "★")); + + + + } */ + //インポスター表示 + switch (seer.GetCustomRole().GetCustomRoleTypes()) + { + case CustomRoleTypes.Impostor: + if (target.Is(CustomRoles.Snitch) && target.Is(CustomRoles.Madmate) && target.GetPlayerTaskState().IsTaskFinished) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), "★")); //変更対象にSnitchマークをつける + sb.Append(Snitch.GetWarningMark(seer, target)); + break; + case CustomRoleTypes.Crewmate: + if (target.Is(CustomRoles.Marshall) && target.GetPlayerTaskState().IsTaskFinished) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Marshall), "★")); //変更対象にSnitchマークをつける + sb.Append(Marshall.GetWarningMark(seer, target)); + break; + } + switch (seer.GetCustomRole()) + { + case CustomRoles.Arsonist: + if (seer.IsDousedPlayer(target)) //seerがtargetに既にオイルを塗っている(完了) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Arsonist), "▲")); + break; + case CustomRoles.Executioner: + sb.Append(Executioner.TargetMark(seer, target)); + break; + case CustomRoles.Lawyer: + // sb.Append(Lawyer.TargetMark(seer, target)); + break; + // case CustomRoles.Jackal: + // case CustomRoles.Sidekick: + case CustomRoles.Poisoner: + case CustomRoles.NSerialKiller: + case CustomRoles.Pelican: + case CustomRoles.DarkHide: + case CustomRoles.BloodKnight: + case CustomRoles.Infectious: + case CustomRoles.Virus: + case CustomRoles.Medusa: + case CustomRoles.Succubus: + case CustomRoles.Pickpocket: + case CustomRoles.Ritualist: + case CustomRoles.Traitor: + case CustomRoles.Spiritcaller: + sb.Append(Snitch.GetWarningMark(seer, target)); + break; + case CustomRoles.Jackal: + case CustomRoles.Sidekick: + // if (target.Is(CustomRoles.Sidekick)) + // sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jackal), " ♥")); //変更対象にSnitchマークをつける + sb.Append(Snitch.GetWarningMark(seer, target)); + break; + /* case CustomRoles.Monarch: + if (target.Is(CustomRoles.Knighted)) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Knighted), " 亗")); //変更対象にSnitchマークをつける + break; */ + case CustomRoles.EvilTracker: + sb.Append(EvilTracker.GetTargetMark(seer, target)); + break; + case CustomRoles.Revolutionist: + if (seer.IsDrawPlayer(target)) //seerがtargetに既にオイルを塗っている(完了) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Revolutionist), "●")); + break; + case CustomRoles.Psychic: + if (target.IsRedForPsy(seer) && !seer.Data.IsDead) + pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), pva.NameText.text); + break; + case CustomRoles.Mafia: + if (seer.Data.IsDead && !target.Data.IsDead) + pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mafia), target.PlayerId.ToString()) + " " + pva.NameText.text; + break; + case CustomRoles.Retributionist: + if (seer.Data.IsDead && !target.Data.IsDead) + pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Retributionist), target.PlayerId.ToString()) + " " + pva.NameText.text; + break; + case CustomRoles.NiceGuesser: + case CustomRoles.EvilGuesser: + if (!seer.Data.IsDead && !target.Data.IsDead) + pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(seer.Is(CustomRoles.NiceGuesser) ? CustomRoles.NiceGuesser : CustomRoles.EvilGuesser), target.PlayerId.ToString()) + " " + pva.NameText.text; + break; + // case CustomRoles.Guesser: + case CustomRoles.Judge: + if (!seer.Data.IsDead && !target.Data.IsDead) + pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Judge), target.PlayerId.ToString()) + " " + pva.NameText.text; + break; + case CustomRoles.Doomsayer: + if (!seer.Data.IsDead && !target.Data.IsDead) + pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doomsayer), target.PlayerId.ToString()) + " " + pva.NameText.text; + break; + case CustomRoles.ParityCop: + if (!seer.Data.IsDead && !target.Data.IsDead) + pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), target.PlayerId.ToString()) + " " + pva.NameText.text; + break; + + case CustomRoles.Councillor: + if (!seer.Data.IsDead && !target.Data.IsDead) + pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Councillor), target.PlayerId.ToString()) + " " + pva.NameText.text; + + break; + + case CustomRoles.Gamer: + sb.Append(Gamer.TargetMark(seer, target)); + sb.Append(Snitch.GetWarningMark(seer, target)); + break; + case CustomRoles.Tracker: + sb.Append(Tracker.GetTargetMark(seer, target)); + break; + } + + bool isLover = false; + foreach (var subRole in target.GetCustomSubRoles()) + { + switch (subRole) + { + case CustomRoles.Lovers: + if (seer.Is(CustomRoles.Lovers) || seer.Data.IsDead) + { + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lovers), "♥")); + isLover = true; + } + break; + /* case CustomRoles.Sidekick: + if (seer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Sidekick) && Options.SidekickKnowOtherSidekick.GetBool()) + { + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jackal), " ♥")); //変更対象にSnitchマークをつける + sb.Append(Snitch.GetWarningMark(seer, target)); + } + break; */ + // case CustomRoles.Guesser: + // if (!seer.Data.IsDead && !target.Data.IsDead) + // pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), target.PlayerId.ToString()) + " " + pva.NameText.text; + + // break; + } + } + + //海王相关显示 + if ((seer.Is(CustomRoles.Ntr) || target.Is(CustomRoles.Ntr)) && !seer.Data.IsDead && !isLover) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lovers), "♥")); + else if (seer == target && CustomRolesHelper.RoleExist(CustomRoles.Ntr) && !isLover) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lovers), "♥")); + + //呪われている場合 + sb.Append(Witch.GetSpelledMark(target.PlayerId, true)); + sb.Append(HexMaster.GetHexedMark(target.PlayerId, true)); + if (Baker.IsPoisoned(target)) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Famine), "θ")); + + + //如果是大明星 + if (target.Is(CustomRoles.SuperStar) && Options.EveryOneKnowSuperStar.GetBool()) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.SuperStar), "★")); + + //球状闪电提示 + if (BallLightning.IsGhost(target)) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.BallLightning), "■")); + + //医生护盾提示 + if (seer.PlayerId == target.PlayerId && (Medic.InProtect(seer.PlayerId) || Medic.TempMarkProtected == seer.PlayerId) && (Medic.WhoCanSeeProtect.GetInt() == 0 || Medic.WhoCanSeeProtect.GetInt() == 2)) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medic), " ●")); + + if (seer.Is(CustomRoles.Medic) && (Medic.InProtect(target.PlayerId) || Medic.TempMarkProtected == target.PlayerId) && (Medic.WhoCanSeeProtect.GetInt() == 0 || Medic.WhoCanSeeProtect.GetInt() == 1)) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medic), " ●")); + + if (seer.Data.IsDead && Medic.InProtect(target.PlayerId) && !seer.Is(CustomRoles.Medic)) + sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medic), " ●")); + + //赌徒提示 + sb.Append(Totocalcio.TargetMark(seer, target)); + sb.Append(Lawyer.LawyerMark(seer, target)); + + //会議画面ではインポスター自身の名前にSnitchマークはつけません。 + + pva.NameText.text += sb.ToString(); + pva.ColorBlindName.transform.localPosition -= new Vector3(1.35f, 0f, 0f); + } + } +} +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Update))] +class MeetingHudUpdatePatch +{ + private static int bufferTime = 10; + private static void ClearShootButton(MeetingHud __instance, bool forceAll = false) + => __instance.playerStates.ToList().ForEach(x => { if ((forceAll || (!Main.PlayerStates.TryGetValue(x.TargetPlayerId, out var ps) || ps.IsDead)) && x.transform.FindChild("ShootButton") != null) UnityEngine.Object.Destroy(x.transform.FindChild("ShootButton").gameObject); }); + + public static void Postfix(MeetingHud __instance) + { + //Meeting Skip with vote counting on keystroke (m + delete) + if (AmongUsClient.Instance.AmHost && Input.GetKeyDown(KeyCode.F6)) + { + __instance.CheckForEndVoting(); + } + // + + if (AmongUsClient.Instance.AmHost && Input.GetMouseButtonUp(1) && Input.GetKey(KeyCode.LeftControl)) + { + __instance.playerStates.DoIf(x => x.HighlightedFX.enabled, x => + { + var player = Utils.GetPlayerById(x.TargetPlayerId); + if (player != null && !player.Data.IsDead) + { + Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.Execution; + player.RpcExileV2(); + Main.PlayerStates[player.PlayerId].SetDead(); + Utils.SendMessage(string.Format(GetString("Message.Executed"), player.Data.PlayerName)); + Logger.Info($"{player.GetNameWithRole()}を処刑しました", "Execution"); + __instance.CheckForEndVoting(); + } + }); + } + + //投票结束时销毁全部技能按钮 + if (!GameStates.IsVoting && __instance.lastSecond < 1) + { + if (GameObject.Find("ShootButton") != null) ClearShootButton(__instance, true); + return; + } + + //会议技能UI处理 + bufferTime--; + if (bufferTime < 0 && __instance.discussionTimer > 0) + { + bufferTime = 10; + var myRole = PlayerControl.LocalPlayer.GetCustomRole(); + + //若某玩家死亡则修复会议该玩家状态 + __instance.playerStates.Where(x => (!Main.PlayerStates.TryGetValue(x.TargetPlayerId, out var ps) || ps.IsDead) && !x.AmDead).Do(x => x.SetDead(x.DidReport, true)); + + //若玩家死亡则销毁技能按钮 + if (myRole is CustomRoles.NiceGuesser or CustomRoles.EvilGuesser or CustomRoles.Judge or CustomRoles.Councillor or CustomRoles.Guesser && !PlayerControl.LocalPlayer.IsAlive()) + ClearShootButton(__instance, true); + + //若黑手党死亡则创建技能按钮 + if (myRole is CustomRoles.Mafia && !PlayerControl.LocalPlayer.IsAlive() && GameObject.Find("ShootButton") == null) + MafiaRevengeManager.CreateJudgeButton(__instance); + if (myRole is CustomRoles.Retributionist && !PlayerControl.LocalPlayer.IsAlive() && GameObject.Find("ShootButton") == null) + RetributionistRevengeManager.CreateJudgeButton(__instance); + + //销毁死亡玩家身上的技能按钮 + ClearShootButton(__instance); + + } + } +} +[HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.SetHighlighted))] +class SetHighlightedPatch +{ + public static bool Prefix(PlayerVoteArea __instance, bool value) + { + if (!AmongUsClient.Instance.AmHost) return true; + if (!__instance.HighlightedFX) return false; + __instance.HighlightedFX.enabled = value; + return false; + } +} +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.OnDestroy))] +class MeetingHudOnDestroyPatch +{ + public static void Postfix() + { + MeetingStates.FirstMeeting = false; + Logger.Info("------------会议结束------------", "Phase"); + if (AmongUsClient.Instance.AmHost) + { + AntiBlackout.SetIsDead(); + Main.AllPlayerControls.Do(pc => RandomSpawn.CustomNetworkTransformPatch.NumOfTP[pc.PlayerId] = 0); + + Main.LastVotedPlayerInfo = null; + EAC.MeetingTimes = 0; + } + } +} \ No newline at end of file diff --git a/Patches/MovingPlatformBehaviourPatch.cs b/Patches/MovingPlatformBehaviourPatch.cs new file mode 100644 index 000000000..384daae7f --- /dev/null +++ b/Patches/MovingPlatformBehaviourPatch.cs @@ -0,0 +1,37 @@ +using HarmonyLib; + +namespace TOHE; + +// https://github.com/tukasa0001/TownOfHost/pull/1274/commits/164d1463e46f0ec453e136c7a2f28a8039cd7fc4 + +[HarmonyPatch(typeof(MovingPlatformBehaviour))] +public static class MovingPlatformBehaviourPatch +{ + private static bool isDisabled = false; + + [HarmonyPatch(nameof(MovingPlatformBehaviour.Start)), HarmonyPrefix] + public static void StartPrefix(MovingPlatformBehaviour __instance) + { + isDisabled = Options.DisableAirshipMovingPlatform.GetBool(); + + if (isDisabled) + { + __instance.transform.localPosition = __instance.DisabledPosition; + ShipStatus.Instance.Cast().outOfOrderPlat.SetActive(true); + } + } + [HarmonyPatch(nameof(MovingPlatformBehaviour.IsDirty), MethodType.Getter), HarmonyPrefix] + public static bool GetIsDirtyPrefix(ref bool __result) + { + if (isDisabled) + { + __result = false; + return false; + } + return true; + } + [HarmonyPatch(nameof(MovingPlatformBehaviour.Use), typeof(PlayerControl)), HarmonyPrefix] + public static bool UsePrefix() => !isDisabled; + [HarmonyPatch(nameof(MovingPlatformBehaviour.SetSide)), HarmonyPrefix] + public static bool SetSidePrefix() => !isDisabled; +} diff --git a/Patches/NoBlackoutPatch.cs b/Patches/NoBlackoutPatch.cs new file mode 100644 index 000000000..3fa6df56d --- /dev/null +++ b/Patches/NoBlackoutPatch.cs @@ -0,0 +1,12 @@ +using HarmonyLib; + +namespace TOHE; + +[HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlowNormal.IsGameOverDueToDeath))] +class DontBlackoutPatch +{ + public static void Postfix(ref bool __result) + { + __result = false; + } +} \ No newline at end of file diff --git a/Patches/OutroPatch.cs b/Patches/OutroPatch.cs new file mode 100644 index 000000000..f5a0a5c20 --- /dev/null +++ b/Patches/OutroPatch.cs @@ -0,0 +1,272 @@ +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TOHE.Modules; +using TOHE.Roles.Impostor; +using UnityEngine; +using static TOHE.Translator; + +namespace TOHE; + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))] +class EndGamePatch +{ + public static Dictionary SummaryText = new(); + public static string KillLog = ""; + public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ref EndGameResult endGameResult) + { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + GameStates.InGame = false; + + Logger.Info("-----------游戏结束-----------", "Phase"); + if (!GameStates.IsModHost) return; + SummaryText = new(); + foreach (var id in Main.PlayerStates.Keys) + SummaryText[id] = Utils.SummaryTexts(id, disableColor: false); + + var sb = new StringBuilder(GetString("KillLog") + ":"); + foreach (var kvp in Main.PlayerStates.OrderBy(x => x.Value.RealKiller.Item1.Ticks)) + { + var date = kvp.Value.RealKiller.Item1; + if (date == DateTime.MinValue) continue; + var killerId = kvp.Value.GetRealKiller(); + var targetId = kvp.Key; + sb.Append($"\n{date:T} {Main.AllPlayerNames[targetId]}({Utils.GetDisplayRoleName(targetId, true)}{Utils.GetSubRolesText(targetId, summary: true)}) [{Utils.GetVitalText(kvp.Key)}]"); + if (killerId != byte.MaxValue && killerId != targetId) + sb.Append($"\n\t⇐ {Main.AllPlayerNames[killerId]}({Utils.GetDisplayRoleName(killerId, true)}{Utils.GetSubRolesText(killerId, summary: true)})"); + } + KillLog = sb.ToString(); + if (!KillLog.Contains("\n")) KillLog = ""; + + Main.NormalOptions.KillCooldown = Options.DefaultKillCooldown; + //winnerListリセット + TempData.winners = new Il2CppSystem.Collections.Generic.List(); + var winner = new List(); + foreach (var pc in Main.AllPlayerControls) + { + if (CustomWinnerHolder.WinnerIds.Contains(pc.PlayerId)) winner.Add(pc); + } + foreach (var team in CustomWinnerHolder.WinnerRoles) + { + winner.AddRange(Main.AllPlayerControls.Where(p => p.Is(team) && !winner.Contains(p))); + } + + Main.winnerNameList = new(); + Main.winnerList = new(); + foreach (var pc in winner) + { + if (CustomWinnerHolder.WinnerTeam is not CustomWinner.Draw && pc.Is(CustomRoles.GM)) continue; + + TempData.winners.Add(new WinningPlayerData(pc.Data)); + Main.winnerList.Add(pc.PlayerId); + Main.winnerNameList.Add(pc.GetRealName()); + } + + BountyHunter.ChangeTimer = new(); + Main.isDoused = new Dictionary<(byte, byte), bool>(); + Main.isDraw = new Dictionary<(byte, byte), bool>(); + Main.isRevealed = new Dictionary<(byte, byte), bool>(); + + Main.VisibleTasksCount = false; + if (AmongUsClient.Instance.AmHost) + { + Main.RealOptionsData.Restore(GameOptionsManager.Instance.CurrentGameOptions); + GameOptionsSender.AllSenders.Clear(); + GameOptionsSender.AllSenders.Add(new NormalGameOptionsSender()); + /* Send SyncSettings RPC */ + } + } +} +[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))] +class SetEverythingUpPatch +{ + public static string LastWinsText = ""; + public static string LastWinsReason = ""; + + public static void Postfix(EndGameManager __instance) + { + if (!Main.playerVersion.ContainsKey(0)) return; + //####################################### + // ==勝利陣営表示== + //####################################### + + __instance.WinText.alignment = TMPro.TextAlignmentOptions.Right; + var WinnerTextObject = UnityEngine.Object.Instantiate(__instance.WinText.gameObject); + WinnerTextObject.transform.position = new(__instance.WinText.transform.position.x + 2.4f, __instance.WinText.transform.position.y - 0.5f, __instance.WinText.transform.position.z); + WinnerTextObject.transform.localScale = new(0.6f, 0.6f, 0.6f); + var WinnerText = WinnerTextObject.GetComponent(); //WinTextと同じ型のコンポーネントを取得 + WinnerText.fontSizeMin = 3f; + WinnerText.text = ""; + + string CustomWinnerText = ""; + string AdditionalWinnerText = ""; + string CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Crewmate); + + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) + { + var winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); + __instance.WinText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); + __instance.WinText.fontSize -= 5f; + __instance.WinText.color = Main.PlayerColors[winnerId]; + __instance.BackgroundBar.material.color = new Color32(245, 82, 82, 255); + WinnerText.text = $"{GetString("ModeSoloKombat")}"; + WinnerText.color = Color.red; + goto EndOfText; + } + + var winnerRole = (CustomRoles)CustomWinnerHolder.WinnerTeam; + if (winnerRole >= 0) + { + CustomWinnerText = GetWinnerRoleName(winnerRole); + CustomWinnerColor = Utils.GetRoleColorCode(winnerRole); + // __instance.WinText.color = Utils.GetRoleColor(winnerRole); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(winnerRole); + if (winnerRole.IsNeutral()) + { + __instance.BackgroundBar.material.color = Utils.GetRoleColor(winnerRole); + } + } + if (AmongUsClient.Instance.AmHost && Main.PlayerStates[0].MainRole == CustomRoles.GM) + { + __instance.WinText.text = GetString("GameOver"); + __instance.WinText.color = Utils.GetRoleColor(CustomRoles.GM); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(winnerRole); + } + switch (CustomWinnerHolder.WinnerTeam) + { + //通常勝利 + case CustomWinner.Crewmate: + CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Engineer); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Engineer); + break; + case CustomWinner.Impostor: + CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Impostor); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Impostor); + break; + case CustomWinner.Rogue: + CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Rogue); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Rogue); + break; + case CustomWinner.Egoist: + CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Egoist); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Egoist); + break; + //特殊勝利 + case CustomWinner.Terrorist: + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Terrorist); + break; + case CustomWinner.Lovers: + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Lovers); + break; + //引き分け処理 + case CustomWinner.Draw: + __instance.WinText.text = GetString("ForceEnd"); + __instance.WinText.color = Color.white; + __instance.BackgroundBar.material.color = Color.gray; + WinnerText.text = GetString("ForceEndText"); + WinnerText.color = Color.gray; + break; + case CustomWinner.Neutrals: + __instance.WinText.text = GetString("DefeatText"); + __instance.WinText.color = Utils.GetRoleColor(CustomRoles.Impostor); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Executioner); + WinnerText.text = GetString("NeutralsLeftText"); + WinnerText.color = Utils.GetRoleColor(CustomRoles.Executioner); + break; + //全滅 + case CustomWinner.None: + __instance.WinText.text = ""; + __instance.WinText.color = Color.black; + __instance.BackgroundBar.material.color = Color.gray; + WinnerText.text = GetString("EveryoneDied"); + WinnerText.color = Color.gray; + break; + case CustomWinner.Error: + __instance.WinText.text = GetString("ErrorEndText"); + __instance.WinText.color = Color.red; + __instance.BackgroundBar.material.color = Color.red; + WinnerText.text = GetString("ErrorEndTextDescription"); + WinnerText.color = Color.white; + break; + } + + foreach (var additionalWinners in CustomWinnerHolder.AdditionalWinnerTeams) + { + var addWinnerRole = (CustomRoles)additionalWinners; + AdditionalWinnerText += "+" + Utils.ColorString(Utils.GetRoleColor(addWinnerRole), GetAdditionalWinnerRoleName(addWinnerRole)); + } + if (CustomWinnerHolder.WinnerTeam is not CustomWinner.Draw and not CustomWinner.None and not CustomWinner.Error) + { + if (AdditionalWinnerText == "") WinnerText.text = $"{CustomWinnerText}"; + else WinnerText.text = $"{CustomWinnerText}\n{AdditionalWinnerText}"; + } + + static string GetWinnerRoleName(CustomRoles role) + { + var name = GetString($"WinnerRoleText.{Enum.GetName(typeof(CustomRoles), role)}"); + if (name == "" || name.StartsWith("*") || name.StartsWith(" cloneRoles = new(Main.PlayerStates.Keys); + foreach (var id in Main.winnerList) + { + if (EndGamePatch.SummaryText[id].Contains("")) continue; + sb.Append($"\n★ ").Append(EndGamePatch.SummaryText[id]); + cloneRoles.Remove(id); + } + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) + { + List<(int, byte)> list = new(); + foreach (var id in cloneRoles) list.Add((SoloKombatManager.GetRankOfScore(id), id)); + list.Sort(); + foreach (var id in list.Where(x => EndGamePatch.SummaryText.ContainsKey(x.Item2))) + sb.Append($"\n  ").Append(EndGamePatch.SummaryText[id.Item2]); + } + else + { + foreach (var id in cloneRoles) + { + if (EndGamePatch.SummaryText[id].Contains("")) continue; + sb.Append($"\n  ").Append(EndGamePatch.SummaryText[id]); + } + } + var RoleSummary = RoleSummaryObject.GetComponent(); + RoleSummary.alignment = TMPro.TextAlignmentOptions.TopLeft; + RoleSummary.color = Color.white; + RoleSummary.outlineWidth *= 1.2f; + RoleSummary.fontSizeMin = RoleSummary.fontSizeMax = RoleSummary.fontSize = 1.25f; + + var RoleSummaryRectTransform = RoleSummary.GetComponent(); + RoleSummaryRectTransform.anchoredPosition = new Vector2(Pos.x + 3.5f, Pos.y - 0.1f); + RoleSummary.text = sb.ToString(); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + //Utils.ApplySuffix(); + } +} \ No newline at end of file diff --git a/Patches/PlayerContorolPatch.cs b/Patches/PlayerContorolPatch.cs new file mode 100644 index 000000000..520868001 --- /dev/null +++ b/Patches/PlayerContorolPatch.cs @@ -0,0 +1,2788 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AmongUs.GameOptions; +using HarmonyLib; +using Hazel; +using InnerNet; +using TOHE.Modules; +using TOHE.Roles.AddOns.Crewmate; +using TOHE.Roles.Crewmate; +using TOHE.Roles.Impostor; +using TOHE.Roles.Neutral; +using UnityEngine; +using static TOHE.Translator; + +namespace TOHE; + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckProtect))] +class CheckProtectPatch +{ + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) + { + if (!AmongUsClient.Instance.AmHost) return false; + Logger.Info("CheckProtect発生: " + __instance.GetNameWithRole() + "=>" + target.GetNameWithRole(), "CheckProtect"); + + if (__instance.Is(CustomRoles.EvilSpirit)) + { + if (target.Is(CustomRoles.Spiritcaller)) + { + Spiritcaller.ProtectSpiritcaller(); + } + else + { + Spiritcaller.HauntPlayer(target); + } + + __instance.RpcResetAbilityCooldown(); + return true; + } + + if (__instance.Is(CustomRoles.Sheriff)) + { + if (__instance.Data.IsDead) + { + Logger.Info("守護をブロックしました。", "CheckProtect"); + return false; + } + } + return true; + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckMurder))] +class CheckMurderPatch +{ + public static Dictionary TimeSinceLastKill = new(); + public static void Update() + { + for (byte i = 0; i < 15; i++) + { + if (TimeSinceLastKill.ContainsKey(i)) + { + TimeSinceLastKill[i] += Time.deltaTime; + if (15f < TimeSinceLastKill[i]) TimeSinceLastKill.Remove(i); + } + } + } + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) + { + if (!AmongUsClient.Instance.AmHost) return false; + + var killer = __instance; //読み替え変数 + + Logger.Info($"{killer.GetNameWithRole()} => {target.GetNameWithRole()}", "CheckMurder"); + + //死人はキルできない + if (killer.Data.IsDead) + { + Logger.Info($"{killer.GetNameWithRole()}は死亡しているためキャンセルされました。", "CheckMurder"); + return false; + } + + //不正キル防止処理 + if (target.Data == null || //PlayerDataがnullじゃないか確認 + target.inVent || target.inMovingPlat //targetの状態をチェック + ) + { + Logger.Info("目标处于无法被击杀状态,击杀被取消", "CheckMurder"); + return false; + } + if (target.Data.IsDead) //同じtargetへの同時キルをブロック + { + Logger.Info("目标处于死亡状态,击杀被取消", "CheckMurder"); + return false; + } + if (MeetingHud.Instance != null) //会議中でないかの判定 + { + Logger.Info("会议中,击杀被取消", "CheckMurder"); + return false; + } + + var divice = Options.CurrentGameMode == CustomGameMode.SoloKombat ? 3000f : 2000f; + float minTime = Mathf.Max(0.02f, AmongUsClient.Instance.Ping / divice * 6f); //※AmongUsClient.Instance.Pingの値はミリ秒(ms)なので÷1000 + //TimeSinceLastKillに値が保存されていない || 保存されている時間がminTime以上 => キルを許可 + //↓許可されない場合 + if (TimeSinceLastKill.TryGetValue(killer.PlayerId, out var time) && time < minTime) + { + Logger.Info("击杀间隔过短,击杀被取消", "CheckMurder"); + return false; + } + TimeSinceLastKill[killer.PlayerId] = 0f; + if (target.Is(CustomRoles.Diseased)) + { + if (Main.KilledDiseased.ContainsKey(killer.PlayerId)) + { + // Key already exists, update the value + Main.KilledDiseased[killer.PlayerId] += 1; + } + else + { + // Key doesn't exist, add the key-value pair + Main.KilledDiseased.Add(killer.PlayerId, 1); + } + } + if (target.Is(CustomRoles.Antidote)) + { + if (Main.KilledAntidote.ContainsKey(killer.PlayerId)) + { + // Key already exists, update the value + Main.KilledAntidote[killer.PlayerId] += 1;// Main.AllPlayerKillCooldown.TryGetValue(killer.PlayerId, out float kcd) ? (kcd - Options.AntidoteCDOpt.GetFloat() > 0 ? kcd - Options.AntidoteCDOpt.GetFloat() : 0f) : 0f; + } + else + { + // Key doesn't exist, add the key-value pair + Main.KilledAntidote.Add(killer.PlayerId, 1);// Main.AllPlayerKillCooldown.TryGetValue(killer.PlayerId, out float kcd) ? (kcd - Options.AntidoteCDOpt.GetFloat() > 0 ? kcd - Options.AntidoteCDOpt.GetFloat() : 0f) : 0f); + } + + } + + killer.ResetKillCooldown(); + + //キル可能判定 + if (killer.PlayerId != target.PlayerId && !killer.CanUseKillButton()) + { + Logger.Info(killer.GetNameWithRole() + "击杀者不被允许使用击杀键,击杀被取消", "CheckMurder"); + return false; + } + + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) + { + SoloKombatManager.OnPlayerAttack(killer, target); + return false; + } + + //実際のキラーとkillerが違う場合の入れ替え処理 + if (Sniper.IsEnable) Sniper.TryGetSniper(target.PlayerId, ref killer); + if (killer != __instance) Logger.Info($"Real Killer={killer.GetNameWithRole()}", "CheckMurder"); + + //鹈鹕肚子里的人无法击杀 + if (Pelican.IsEaten(target.PlayerId)) + return false; + + //阻止对活死人的操作 + + // 赝品检查 + if (Counterfeiter.OnClientMurder(killer)) return false; + if (Pursuer.OnClientMurder(killer)) return false; + if (Addict.IsImmortal(target)) return false; + + //判定凶手技能 + if (killer.PlayerId != target.PlayerId) + { + //非自杀场景下才会触发 + switch (killer.GetCustomRole()) + { + //==========内鬼阵营==========// + case CustomRoles.BountyHunter: //必须在击杀发生前处理 + BountyHunter.OnCheckMurder(killer, target); + break; + case CustomRoles.SerialKiller: + SerialKiller.OnCheckMurder(killer); + break; + case CustomRoles.Vampire: + if (!Vampire.OnCheckMurder(killer, target)) return false; + break; + case CustomRoles.Poisoner: + if (!Poisoner.OnCheckMurder(killer, target)) return false; + break; + case CustomRoles.Warlock: + if (!Main.CheckShapeshift[killer.PlayerId] && !Main.isCurseAndKill[killer.PlayerId]) + { //Warlockが変身時以外にキルしたら、呪われる処理 + if (target.Is(CustomRoles.Needy) || target.Is(CustomRoles.Lazy)) return false; + Main.isCursed = true; + killer.SetKillCooldown(); + //RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); + killer.RPCPlayCustomSound("Line"); + Main.CursedPlayers[killer.PlayerId] = target; + Main.WarlockTimer.Add(killer.PlayerId, 0f); + Main.isCurseAndKill[killer.PlayerId] = true; + //RPC.RpcSyncCurseAndKill(); + return false; + } + if (Main.CheckShapeshift[killer.PlayerId]) + {//呪われてる人がいないくて変身してるときに通常キルになる + killer.RpcCheckAndMurder(target); + return false; + } + if (Main.isCurseAndKill[killer.PlayerId]) killer.RpcGuardAndKill(target); + return false; + case CustomRoles.Assassin: + if (!Assassin.OnCheckMurder(killer, target)) return false; + break; + case CustomRoles.Famine: + Baker.FamineKilledTasks(target.PlayerId); + break; + + case CustomRoles.Witch: + if (!Witch.OnCheckMurder(killer, target)) return false; + break; + case CustomRoles.HexMaster: + if (!HexMaster.OnCheckMurder(killer, target)) return false; + break; + case CustomRoles.Puppeteer: + if (target.Is(CustomRoles.Needy) || target.Is(CustomRoles.Lazy) || Medic.ProtectList.Contains(target.PlayerId)) return false; + Main.PuppeteerList[target.PlayerId] = killer.PlayerId; + killer.SetKillCooldown(); + killer.RPCPlayCustomSound("Line"); + Utils.NotifyRoles(SpecifySeer: killer); + return false; + case CustomRoles.NWitch: + // if (target.Is(CustomRoles.Needy) || target.Is(CustomRoles.Lazy)) return false; + Main.TaglockedList[target.PlayerId] = killer.PlayerId; + killer.SetKillCooldown(); + Utils.NotifyRoles(SpecifySeer: killer); + return false; + case CustomRoles.Capitalism: + if (!Main.CapitalismAddTask.ContainsKey(target.PlayerId)) + Main.CapitalismAddTask.Add(target.PlayerId, 0); + Main.CapitalismAddTask[target.PlayerId]++; + if (!Main.CapitalismAssignTask.ContainsKey(target.PlayerId)) + Main.CapitalismAssignTask.Add(target.PlayerId, 0); + Main.CapitalismAssignTask[target.PlayerId]++; + Logger.Info($"资本主义 {killer.GetRealName()} 又开始祸害人了:{target.GetRealName()}", "Capitalism Add Task"); + killer.RpcGuardAndKill(killer); + killer.SetKillCooldown(); + return false; + /* case CustomRoles.Bomber: + return false; */ + case CustomRoles.Gangster: + if (Gangster.OnCheckMurder(killer, target)) + return false; + break; + case CustomRoles.BallLightning: + if (BallLightning.CheckBallLightningMurder(killer, target)) + return false; + break; + case CustomRoles.Greedier: + Greedier.OnCheckMurder(killer); + break; + case CustomRoles.QuickShooter: + QuickShooter.QuickShooterKill(killer); + break; + case CustomRoles.Sans: + Sans.OnCheckMurder(killer); + break; + case CustomRoles.Juggernaut: + Juggernaut.OnCheckMurder(killer); + break; + case CustomRoles.Reverie: + Reverie.OnCheckMurder(killer); + break; + case CustomRoles.Hangman: + if (!Hangman.OnCheckMurder(killer, target)) return false; + break; + case CustomRoles.Swooper: + if (!Swooper.OnCheckMurder(killer, target)) return false; + break; + case CustomRoles.Wraith: + if (!Wraith.OnCheckMurder(killer, target)) return false; + break; + case CustomRoles.Lurker: + Lurker.OnCheckMurder(killer); + break; + case CustomRoles.Crusader: + Crusader.OnCheckMurder(killer, target); + return false; + + //==========中立阵营==========// + case CustomRoles.PlagueBearer: + if (!PlagueBearer.OnCheckMurder(killer, target)) + return false; + break; + case CustomRoles.Pirate: + if (!Pirate.OnCheckMurder(killer, target)) + return false; + break; + + case CustomRoles.Arsonist: + killer.SetKillCooldown(Options.ArsonistDouseTime.GetFloat()); + if (!Main.isDoused[(killer.PlayerId, target.PlayerId)] && !Main.ArsonistTimer.ContainsKey(killer.PlayerId)) + { + Main.ArsonistTimer.Add(killer.PlayerId, (target, 0f)); + Utils.NotifyRoles(SpecifySeer: __instance); + RPC.SetCurrentDousingTarget(killer.PlayerId, target.PlayerId); + } + return false; + case CustomRoles.Revolutionist: + killer.SetKillCooldown(Options.RevolutionistDrawTime.GetFloat()); + if (!Main.isDraw[(killer.PlayerId, target.PlayerId)] && !Main.RevolutionistTimer.ContainsKey(killer.PlayerId)) + { + Main.RevolutionistTimer.TryAdd(killer.PlayerId, (target, 0f)); + Utils.NotifyRoles(SpecifySeer: __instance); + RPC.SetCurrentDrawTarget(killer.PlayerId, target.PlayerId); + } + return false; + case CustomRoles.Farseer: + killer.SetKillCooldown(Farseer.FarseerRevealTime.GetFloat()); + if (!Main.isRevealed[(killer.PlayerId, target.PlayerId)] && !Main.FarseerTimer.ContainsKey(killer.PlayerId)) + { + Main.FarseerTimer.TryAdd(killer.PlayerId, (target, 0f)); + Utils.NotifyRoles(SpecifySeer: __instance); + RPC.SetCurrentRevealTarget(killer.PlayerId, target.PlayerId); + } + return false; + case CustomRoles.Innocent: + target.RpcMurderPlayerV3(killer); + return false; + case CustomRoles.Pelican: + if (Pelican.CanEat(killer, target.PlayerId)) + { + Pelican.EatPlayer(killer, target); + killer.RpcGuardAndKill(killer); + killer.SetKillCooldown(); + killer.RPCPlayCustomSound("Eat"); + target.RPCPlayCustomSound("Eat"); + } + return false; + case CustomRoles.FFF: + if (!target.Is(CustomRoles.Lovers) && !target.Is(CustomRoles.Ntr)) + { + killer.Data.IsDead = true; + Main.PlayerStates[killer.PlayerId].deathReason = PlayerState.DeathReason.Sacrifice; + killer.RpcMurderPlayerV3(killer); + Main.PlayerStates[killer.PlayerId].SetDead(); + Logger.Info($"{killer.GetRealName()} 击杀了非目标玩家,壮烈牺牲了(bushi)", "FFF"); + return false; + } + break; + case CustomRoles.Gamer: + Gamer.CheckGamerMurder(killer, target); + return false; + case CustomRoles.DarkHide: + DarkHide.OnCheckMurder(killer, target); + break; + case CustomRoles.Provocateur: + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.PissedOff; + killer.RpcMurderPlayerV3(target); + killer.RpcMurderPlayerV3(killer); + killer.SetRealKiller(target); + Main.Provoked.TryAdd(killer.PlayerId, target.PlayerId); + return false; + case CustomRoles.Totocalcio: + Totocalcio.OnCheckMurder(killer, target); + return false; + case CustomRoles.Succubus: + Succubus.OnCheckMurder(killer, target); + return false; + case CustomRoles.CursedSoul: + CursedSoul.OnCheckMurder(killer, target); + return false; + case CustomRoles.Admirer: + Admirer.OnCheckMurder(killer, target); + return false; + case CustomRoles.Amnesiac: + Amnesiac.OnCheckMurder(killer, target); + return false; + case CustomRoles.Infectious: + Infectious.OnCheckMurder(killer, target); + return false; + case CustomRoles.Monarch: + Monarch.OnCheckMurder(killer, target); + return false; + case CustomRoles.Deputy: + Deputy.OnCheckMurder(killer, target); + return false; + case CustomRoles.Jackal: + if (Jackal.OnCheckMurder(killer, target)) + return false; + break; + + //==========船员职业==========// + case CustomRoles.Sheriff: + if (!Sheriff.OnCheckMurder(killer, target)) + return false; + break; + case CustomRoles.CopyCat: + if (!CopyCat.OnCheckMurder(killer, target)) + return false; + break; + + case CustomRoles.SwordsMan: + if (!SwordsMan.OnCheckMurder(killer)) + return false; + break; + case CustomRoles.Medic: + Medic.OnCheckMurderFormedicaler(killer, target); + return false; + case CustomRoles.Counterfeiter: + if (Counterfeiter.CanBeClient(target) && Counterfeiter.CanSeel(killer.PlayerId)) + Counterfeiter.SeelToClient(killer, target); + return false; + case CustomRoles.Pursuer: + if (Pursuer.CanBeClient(target) && Pursuer.CanSeel(killer.PlayerId)) + Pursuer.SeelToClient(killer, target); + return false; + } + } + + // 击杀前检查 + if (!killer.RpcCheckAndMurder(target, true)) + return false; + if (Merchant.OnClientMurder(killer, target)) return false; + + + if (killer.Is(CustomRoles.Virus)) Virus.OnCheckMurder(killer, target); + else if (killer.Is(CustomRoles.Spiritcaller)) Spiritcaller.OnCheckMurder(target); + + // Consigliere + if (killer.Is(CustomRoles.EvilDiviner)) + { + + if (!EvilDiviner.OnCheckMurder(killer, target)) + return false; + } + + if (killer.Is(CustomRoles.Unlucky)) + { + var Ue = IRandom.Instance; + if (Ue.Next(0, 100) < Options.UnluckyKillSuicideChance.GetInt()) + { + killer.RpcMurderPlayerV3(killer); + Main.PlayerStates[killer.PlayerId].deathReason = PlayerState.DeathReason.Suicide; + return false; + } + } + + if (killer.Is(CustomRoles.Swift) && !target.Is(CustomRoles.Glitch) && !target.Is(CustomRoles.Pestilence)) + { + target.RpcMurderPlayerV3(target); + killer.RpcGuardAndKill(killer); + // target.RpcGuardAndKill(target); + target.SetRealKiller(killer); + killer.SetKillCooldownV2(); + RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); + return false; + } + if (killer.Is(CustomRoles.Mare) && !Utils.IsActive(SystemTypes.Electrical)) + { + return false; + } + /* if (killer.Is(CustomRoles.Minimalism)) + { + return true; + } */ + + if (killer.Is(CustomRoles.Ritualist)) + { + + if (!Ritualist.OnCheckMurder(killer, target)) + return false; + } + + // 清道夫清理尸体 + if (killer.Is(CustomRoles.Scavenger)) + { + if (!target.Is(CustomRoles.Pestilence)) + { + Utils.TP(target.NetTransform, Pelican.GetBlackRoomPS()); + target.SetRealKiller(killer); + Main.PlayerStates[target.PlayerId].SetDead(); + target.RpcMurderPlayerV3(target); + killer.SetKillCooldown(); + RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); + NameNotifyManager.Notify(target, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Scavenger), GetString("KilledByScavenger"))); + return false; + } + if (target.Is(CustomRoles.Pestilence)) + { + target.RpcMurderPlayerV3(target); + target.SetRealKiller(killer); + return false; + } + + } + // 肢解者肢解受害者 + if (killer.Is(CustomRoles.OverKiller) && killer.PlayerId != target.PlayerId) + { + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Dismembered; + new LateTask(() => + { + if (!Main.OverDeadPlayerList.Contains(target.PlayerId)) Main.OverDeadPlayerList.Add(target.PlayerId); + var ops = target.GetTruePosition(); + var rd = IRandom.Instance; + for (int i = 0; i < 20; i++) + { + Vector2 location = new(ops.x + ((float)(rd.Next(0, 201) - 100) / 100), ops.y + ((float)(rd.Next(0, 201) - 100) / 100)); + location += new Vector2(0, 0.3636f); + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(target.NetTransform.NetId, (byte)RpcCalls.SnapTo, SendOption.None, -1); + NetHelpers.WriteVector2(location, writer); + writer.Write(target.NetTransform.lastSequenceId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + + target.NetTransform.SnapTo(location); + killer.MurderPlayer(target); + + if (target.Is(CustomRoles.Avanger)) + { + var pcList = Main.AllAlivePlayerControls.Where(x => x.PlayerId != target.PlayerId || Pelican.IsEaten(x.PlayerId) || Medic.ProtectList.Contains(x.PlayerId) || target.Is(CustomRoles.Pestilence) || target.Is(CustomRoles.Glitch)).ToList(); + var rp = pcList[IRandom.Instance.Next(0, pcList.Count)]; + Main.PlayerStates[rp.PlayerId].deathReason = PlayerState.DeathReason.Revenge; + rp.SetRealKiller(target); + rp.RpcMurderPlayerV3(rp); + } + + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)RpcCalls.MurderPlayer, SendOption.None, -1); + messageWriter.WriteNetObject(target); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + } + Utils.TP(killer.NetTransform, ops); + }, 0.05f, "OverKiller Murder"); + } + + //==キル処理== + __instance.RpcMurderPlayerV3(target); + //============ + + return false; + } + + public static bool RpcCheckAndMurder(PlayerControl killer, PlayerControl target, bool check = false) + { + if (!AmongUsClient.Instance.AmHost) return false; + if (target == null) target = killer; + + //Jackal can kill Sidekick + if (killer.Is(CustomRoles.Jackal) && target.Is(CustomRoles.Sidekick) && !Options.JackalCanKillSidekick.GetBool()) + return false; + //Sidekick can kill Jackal + if (killer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Jackal) && !Options.SidekickCanKillJackal.GetBool()) + return false; + if (killer.Is(CustomRoles.Jackal) && target.Is(CustomRoles.Recruit) && !Options.JackalCanKillSidekick.GetBool()) + return false; + //Sidekick can kill Jackal + if (killer.Is(CustomRoles.Recruit) && target.Is(CustomRoles.Jackal) && !Options.SidekickCanKillJackal.GetBool()) + return false; + //禁止内鬼刀叛徒 + if (killer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoles.Madmate) && !Options.ImpCanKillMadmate.GetBool()) + return false; + + // Guardian can't die on task completion + if (target.Is(CustomRoles.Guardian) && target.AllTasksCompleted()) + return false; + + if (Options.OppoImmuneToAttacksWhenTasksDone.GetBool()) + { + if (target.Is(CustomRoles.Opportunist) && target.AllTasksCompleted()) + return false; + } + + // Monarch immune to kills when a living player is knighted + if (target.Is(CustomRoles.Monarch) && CustomRoles.Knighted.RoleExist()) + return false; + + + // Traitor can't kill Impostors but Impostors can kill it + if (killer.Is(CustomRoles.Traitor) && target.Is(CustomRoleTypes.Impostor)) + return false; + + + //禁止叛徒刀内鬼 + if (killer.Is(CustomRoles.Madmate) && target.Is(CustomRoleTypes.Impostor) && !Options.MadmateCanKillImp.GetBool()) + return false; + //Bitten players cannot kill Vampire + if (killer.Is(CustomRoles.Infected) && target.Is(CustomRoles.Infectious)) + return false; + //Vampire cannot kill bitten players + if (killer.Is(CustomRoles.Infectious) && target.Is(CustomRoles.Infected)) + return false; + //Bitten players cannot kill each other + if (killer.Is(CustomRoles.Infected) && target.Is(CustomRoles.Infected) && !Infectious.TargetKnowOtherTarget.GetBool()) + return false; + //Sidekick can kill Sidekick + if (killer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Sidekick) && !Options.SidekickCanKillSidekick.GetBool()) + return false; + //Recruit can kill Recruit + if (killer.Is(CustomRoles.Recruit) && target.Is(CustomRoles.Recruit) && !Options.SidekickCanKillSidekick.GetBool()) + return false; + //Sidekick can kill Sidekick + if (killer.Is(CustomRoles.Recruit) && target.Is(CustomRoles.Sidekick) && !Options.SidekickCanKillSidekick.GetBool()) + return false; + //Recruit can kill Recruit + if (killer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Recruit) && !Options.SidekickCanKillSidekick.GetBool()) + return false; + + + //医生护盾检查 + if (Medic.OnCheckMurder(killer, target)) + return false; + + if (target.Is(CustomRoles.Medic)) + Medic.IsDead(target); + if (PlagueBearer.OnCheckMurderPestilence(killer, target)) + return false; + + if (Jackal.ResetKillCooldownWhenSbGetKilled.GetBool() && !killer.Is(CustomRoles.Sidekick) && !target.Is(CustomRoles.Sidekick) && !killer.Is(CustomRoles.Jackal) && !target.Is(CustomRoles.Jackal) && !GameStates.IsMeeting) + Jackal.AfterPlayerDiedTask(killer); + + + if (target.Is(CustomRoles.BoobyTrap) && Options.TrapOnlyWorksOnTheBodyBoobyTrap.GetBool() && !GameStates.IsMeeting) + { + Main.BoobyTrapBody.Add(target.PlayerId); + Main.BoobyTrapKiller.Add(target.PlayerId); + } + + if (target.Is(CustomRoles.Lucky)) + { + var rd = IRandom.Instance; + if (rd.Next(0, 100) < Options.LuckyProbability.GetInt()) + { + killer.RpcGuardAndKill(target); + return false; + } + } + // if (target.Is(CustomRoles.Diseased)) + // { + + //// killer.RpcGuardAndKill(killer); + // // killer.SetKillCooldownV3(Main.AllPlayerKillCooldown[killer.PlayerId] *= Options.DiseasedMultiplier.GetFloat()); + // // killer.ResetKillCooldown(); + // // killer.SyncSettings(); + // } + if (Main.ForCrusade.Contains(target.PlayerId)) + { + foreach (var player in Main.AllPlayerControls) + { + if (player.Is(CustomRoles.Crusader) && player.IsAlive() && !killer.Is(CustomRoles.Pestilence) && !killer.Is(CustomRoles.Glitch) && !killer.Is(CustomRoles.Minimalism)) + { + player.RpcMurderPlayerV3(killer); + Main.ForCrusade.Remove(target.PlayerId); + killer.RpcGuardAndKill(target); + return false; + } + if (player.Is(CustomRoles.Crusader) && player.IsAlive() && killer.Is(CustomRoles.Pestilence)) + { + killer.RpcMurderPlayerV3(player); + Main.ForCrusade.Remove(target.PlayerId); + target.RpcGuardAndKill(killer); + Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.PissedOff; + return false; + } + } + } + + switch (target.GetCustomRole()) + { + //击杀幸运儿 + case CustomRoles.Luckey: + var rd = IRandom.Instance; + if (rd.Next(0, 100) < Options.LuckeyProbability.GetInt()) + { + killer.RpcGuardAndKill(target); + return false; + } + break; + //击杀呪狼 + case CustomRoles.CursedWolf: + if (Main.CursedWolfSpellCount[target.PlayerId] <= 0) break; + if (killer.Is(CustomRoles.Pestilence)) break; + if (killer == target) break; + killer.RpcGuardAndKill(target); + target.RpcGuardAndKill(target); + Main.CursedWolfSpellCount[target.PlayerId] -= 1; + killer.SetRealKiller(target); + RPC.SendRPCCursedWolfSpellCount(target.PlayerId); + Logger.Info($"{target.GetNameWithRole()} : {Main.CursedWolfSpellCount[target.PlayerId]}回目", "CursedWolf"); + Main.PlayerStates[killer.PlayerId].deathReason = PlayerState.DeathReason.Curse; + killer.RpcMurderPlayerV3(killer); + return false; + case CustomRoles.Jinx: + if (Main.JinxSpellCount[target.PlayerId] <= 0) break; + if (killer.Is(CustomRoles.Pestilence)) break; + if (killer == target) break; + killer.RpcGuardAndKill(target); + target.RpcGuardAndKill(target); + Main.JinxSpellCount[target.PlayerId] -= 1; + killer.SetRealKiller(target); + RPC.SendRPCJinxSpellCount(target.PlayerId); + Logger.Info($"{target.GetNameWithRole()} : {Main.JinxSpellCount[target.PlayerId]}回目", "Jinx"); + Main.PlayerStates[killer.PlayerId].deathReason = PlayerState.DeathReason.Jinx; + killer.RpcMurderPlayerV3(killer); + return false; + //击杀老兵 + case CustomRoles.Veteran: + if (Main.VeteranInProtect.ContainsKey(target.PlayerId) && killer.PlayerId != target.PlayerId) + if (Main.VeteranInProtect[target.PlayerId] + Options.VeteranSkillDuration.GetInt() >= Utils.GetTimeStamp()) + { + if (!killer.Is(CustomRoles.Pestilence)) + { + killer.SetRealKiller(target); + target.RpcMurderPlayerV3(killer); + Logger.Info($"{target.GetRealName()} 老兵反弹击杀:{killer.GetRealName()}", "Veteran Kill"); + return false; + } + if (killer.Is(CustomRoles.Pestilence)) + { + target.SetRealKiller(killer); + killer.RpcMurderPlayerV3(target); + Logger.Info($"{target.GetRealName()} 老兵反弹击杀:{target.GetRealName()}", "Pestilence Reflect"); + return false; + } + } + break; + case CustomRoles.TimeMaster: + if (Main.TimeMasterInProtect.ContainsKey(target.PlayerId) && killer.PlayerId != target.PlayerId) + if (Main.TimeMasterInProtect[target.PlayerId] + Options.TimeMasterSkillDuration.GetInt() >= Utils.GetTimeStamp(DateTime.UtcNow)) + { + foreach (var player in Main.AllPlayerControls) + { + if (!killer.Is(CustomRoles.Pestilence)) + { + if (Main.TimeMasterBackTrack.ContainsKey(player.PlayerId)) + { + var position = Main.TimeMasterBackTrack[player.PlayerId]; + Utils.TP(player.NetTransform, position); + } + } + } + killer.SetKillCooldownV2(target: target, forceAnime: true); + return false; + } + break; + case CustomRoles.Masochist: + + killer.SetKillCooldownV2(target: target, forceAnime: true); + Main.MasochistKillMax[target.PlayerId]++; + // killer.RPCPlayCustomSound("DM"); + target.Notify(string.Format(GetString("MasochistKill"), Main.MasochistKillMax[target.PlayerId])); + if (Main.MasochistKillMax[target.PlayerId] >= Options.MasochistKillMax.GetInt()) + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Masochist); + CustomWinnerHolder.WinnerIds.Add(target.PlayerId); + } + return false; + case CustomRoles.Glitch: + if (killer.Is(CustomRoles.Pestilence)) break; + killer.SetRealKiller(target); + target.RpcMurderPlayerV3(killer); + Logger.Info($"{target.GetRealName()} 老兵反弹击杀:{killer.GetRealName()}", "Glitch Kill"); + Main.PlayerStates[killer.PlayerId].deathReason = PlayerState.DeathReason.Hack; + return false; + //检查明星附近是否有人 + case CustomRoles.SuperStar: + if (Main.AllAlivePlayerControls.Where(x => + x.PlayerId != killer.PlayerId && + x.PlayerId != target.PlayerId && + Vector2.Distance(x.GetTruePosition(), target.GetTruePosition()) < 2f + ).ToList().Count >= 1) return false; + break; + //玩家被击杀事件 + case CustomRoles.Gamer: + if (!Gamer.CheckMurder(killer, target)) + return false; + break; + //嗜血骑士技能生效中 + case CustomRoles.BloodKnight: + if (BloodKnight.InProtect(target.PlayerId)) + { + killer.RpcGuardAndKill(target); + target.RpcGuardAndKill(); + target.Notify(GetString("BKOffsetKill")); + return false; + } + break; + case CustomRoles.Wildling: + if (Wildling.InProtect(target.PlayerId)) + { + killer.RpcGuardAndKill(target); + target.RpcGuardAndKill(); + target.Notify(GetString("BKOffsetKill")); + return false; + } + break; + case CustomRoles.Spiritcaller: + if (Spiritcaller.InProtect(target)) + { + killer.RpcGuardAndKill(target); + target.RpcGuardAndKill(); + return false; + } + break; + } + + //保镖保护 + if (killer.PlayerId != target.PlayerId) + { + foreach (var pc in Main.AllAlivePlayerControls.Where(x => x.PlayerId != target.PlayerId)) + { + var pos = target.transform.position; + var dis = Vector2.Distance(pos, pc.transform.position); + if (dis > Options.BodyguardProtectRadius.GetFloat()) continue; + if (pc.Is(CustomRoles.Bodyguard)) + { + if (pc.Is(CustomRoles.Madmate) && killer.GetCustomRole().IsImpostorTeam()) + Logger.Info($"{pc.GetRealName()} 是个叛徒,所以他选择无视杀人现场", "Bodyguard"); + else + { + Main.PlayerStates[pc.PlayerId].deathReason = PlayerState.DeathReason.Sacrifice; + pc.RpcMurderPlayerV3(killer); + pc.SetRealKiller(killer); + pc.RpcMurderPlayerV3(pc); + Logger.Info($"{pc.GetRealName()} 挺身而出与歹徒 {killer.GetRealName()} 同归于尽", "Bodyguard"); + return false; + } + } + } + } + + //首刀保护 + if (Main.ShieldPlayer != byte.MaxValue && Main.ShieldPlayer == target.PlayerId && Utils.IsAllAlive) + { + Main.ShieldPlayer = byte.MaxValue; + killer.SetKillCooldown(); + killer.RpcGuardAndKill(target); + target.RpcGuardAndKill(); + return false; + } + + //首刀叛变 + if (Options.MadmateSpawnMode.GetInt() == 1 && Main.MadmateNum < CustomRoles.Madmate.GetCount() && Utils.CanBeMadmate(target)) + { + Main.MadmateNum++; + target.RpcSetCustomRole(CustomRoles.Madmate); + ExtendedPlayerControl.RpcSetCustomRole(target.PlayerId, CustomRoles.Madmate); + target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Madmate), GetString("BecomeMadmateCuzMadmateMode"))); + killer.SetKillCooldown(); + killer.RpcGuardAndKill(target); + target.RpcGuardAndKill(killer); + target.RpcGuardAndKill(target); + Logger.Info("设置职业:" + target?.Data?.PlayerName + " = " + target.GetCustomRole().ToString() + " + " + CustomRoles.Madmate.ToString(), "Assign " + CustomRoles.Madmate.ToString()); + return false; + } + + if (!check) killer.RpcMurderPlayerV3(target); + return true; + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.MurderPlayer))] +class MurderPlayerPatch +{ + public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) + { + Logger.Info($"{__instance.GetNameWithRole()} => {target.GetNameWithRole()}{(target.protectedByGuardian ? "(Protected)" : "")}", "MurderPlayer"); + + if (RandomSpawn.CustomNetworkTransformPatch.NumOfTP.TryGetValue(__instance.PlayerId, out var num) && num > 2) RandomSpawn.CustomNetworkTransformPatch.NumOfTP[__instance.PlayerId] = 3; + if (!target.protectedByGuardian) + Camouflage.RpcSetSkin(target, ForceRevert: true); + } + public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) + { + if (target.AmOwner) RemoveDisableDevicesPatch.UpdateDisableDevices(); + if (!target.Data.IsDead || !AmongUsClient.Instance.AmHost) return; + + if (Main.OverDeadPlayerList.Contains(target.PlayerId)) return; + + PlayerControl killer = __instance; //読み替え変数 + if (target.PlayerId == Main.GodfatherTarget) killer.RpcSetCustomRole(CustomRoles.Refugee); + + + //実際のキラーとkillerが違う場合の入れ替え処理 + if (Sniper.IsEnable) + { + if (Sniper.TryGetSniper(target.PlayerId, ref killer)) + { + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Sniped; + } + } + if (killer != __instance) + { + Logger.Info($"Real Killer={killer.GetNameWithRole()}", "MurderPlayer"); + + } + if (Main.PlayerStates[target.PlayerId].deathReason == PlayerState.DeathReason.etc) + { + //死因が設定されていない場合は死亡判定 + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Kill; + } + + //看看UP是不是被首刀了 + if (Main.FirstDied == byte.MaxValue && target.Is(CustomRoles.Youtuber)) + { + CustomSoundsManager.RPCPlayCustomSoundAll("Congrats"); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Youtuber); //UP主被首刀了,哈哈哈哈哈 + CustomWinnerHolder.WinnerIds.Add(target.PlayerId); + } + + //记录首刀 + if (Main.FirstDied == byte.MaxValue) + Main.FirstDied = target.PlayerId; + + if (target.Is(CustomRoles.Bait)) + { + if (killer.PlayerId != target.PlayerId || (target.GetRealKiller()?.GetCustomRole() is CustomRoles.Swooper or CustomRoles.Wraith) || !killer.Is(CustomRoles.Oblivious) || (killer.Is(CustomRoles.Oblivious) && !Options.ObliviousBaitImmune.GetBool())) + { + killer.RPCPlayCustomSound("Congrats"); + target.RPCPlayCustomSound("Congrats"); + float delay; + if (Options.BaitDelayMax.GetFloat() < Options.BaitDelayMin.GetFloat()) delay = 0f; + else delay = IRandom.Instance.Next((int)Options.BaitDelayMin.GetFloat(), (int)Options.BaitDelayMax.GetFloat() + 1); + delay = Math.Max(delay, 0.15f); + if (delay > 0.15f && Options.BaitDelayNotify.GetBool()) killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Bait), string.Format(GetString("KillBaitNotify"), (int)delay)), delay); + Logger.Info($"{killer.GetNameWithRole()} 击杀诱饵 => {target.GetNameWithRole()}", "MurderPlayer"); + new LateTask(() => { if (GameStates.IsInTask) killer.CmdReportDeadBody(target.Data); }, delay, "Bait Self Report"); + } + } + + if (target.Is(CustomRoles.Trapper) && killer != target) + killer.TrapperKilled(target); + + switch (target.GetCustomRole()) + { + case CustomRoles.BallLightning: + if (killer != target) + BallLightning.MurderPlayer(killer, target); + break; + } + switch (killer.GetCustomRole()) + { + case CustomRoles.BoobyTrap: + if (!Options.TrapOnlyWorksOnTheBodyBoobyTrap.GetBool() && killer != target) + { + if (!Main.BoobyTrapBody.Contains(target.PlayerId)) Main.BoobyTrapBody.Add(target.PlayerId); + if (!Main.KillerOfBoobyTrapBody.ContainsKey(target.PlayerId)) Main.KillerOfBoobyTrapBody.Add(target.PlayerId, killer.PlayerId); + Main.PlayerStates[killer.PlayerId].deathReason = PlayerState.DeathReason.Misfire; + killer.RpcMurderPlayerV3(killer); + } + break; + case CustomRoles.SwordsMan: + if (killer != target) + SwordsMan.OnMurder(killer); + break; + case CustomRoles.BloodKnight: + BloodKnight.OnMurderPlayer(killer, target); + break; + case CustomRoles.Wildling: + Wildling.OnMurderPlayer(killer, target); + break; + } + + if (killer.Is(CustomRoles.TicketsStealer) && killer.PlayerId != target.PlayerId) + killer.Notify(string.Format(GetString("TicketsStealerGetTicket"), ((Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == killer.PlayerId) + 1) * Options.TicketsPerKill.GetFloat()).ToString("0.0#####"))); + + if (killer.Is(CustomRoles.Pickpocket) && killer.PlayerId != target.PlayerId) + killer.Notify(string.Format(GetString("PickpocketGetVote"), ((Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == killer.PlayerId) + 1) * Pickpocket.VotesPerKill.GetFloat()).ToString("0.0#####"))); + + if (target.Is(CustomRoles.Avanger)) + { + var pcList = Main.AllAlivePlayerControls.Where(x => x.PlayerId != target.PlayerId).ToList(); + var rp = pcList[IRandom.Instance.Next(0, pcList.Count)]; + if (!rp.Is(CustomRoles.Pestilence)) + { + Main.PlayerStates[rp.PlayerId].deathReason = PlayerState.DeathReason.Revenge; + rp.SetRealKiller(target); + rp.RpcMurderPlayerV3(rp); + } + } + + foreach (var pc in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Mediumshiper))) + pc.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mediumshiper), GetString("MediumshiperKnowPlayerDead"))); + + if (Executioner.Target.ContainsValue(target.PlayerId)) + Executioner.ChangeRoleByTarget(target); + if (Lawyer.Target.ContainsValue(target.PlayerId)) + Lawyer.ChangeRoleByTarget(target); + Hacker.AddDeadBody(target); + Mortician.OnPlayerDead(target); + Bloodhound.OnPlayerDead(target); + Tracefinder.OnPlayerDead(target); + Vulture.OnPlayerDead(target); + + Utils.AfterPlayerDeathTasks(target); + + Main.PlayerStates[target.PlayerId].SetDead(); + target.SetRealKiller(killer, true); //既に追加されてたらスキップ + Utils.CountAlivePlayers(true); + + Camouflager.isDead(target); + Utils.TargetDies(__instance, target); + + if (Options.LowLoadMode.GetBool()) + { + __instance.MarkDirtySettings(); + target.MarkDirtySettings(); + Utils.NotifyRoles(killer); + Utils.NotifyRoles(target); + } + else + { + Utils.SyncAllSettings(); + Utils.NotifyRoles(); + } + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Shapeshift))] +class ShapeshiftPatch +{ + public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) + { + Logger.Info($"{__instance?.GetNameWithRole()} => {target?.GetNameWithRole()}", "Shapeshift"); + + var shapeshifter = __instance; + var shapeshifting = shapeshifter.PlayerId != target.PlayerId; + + if (Main.CheckShapeshift.TryGetValue(shapeshifter.PlayerId, out var last) && last == shapeshifting) + { + Logger.Info($"{__instance?.GetNameWithRole()}:Cancel Shapeshift.Prefix", "Shapeshift"); + return; + } + + Main.CheckShapeshift[shapeshifter.PlayerId] = shapeshifting; + Main.ShapeshiftTarget[shapeshifter.PlayerId] = target.PlayerId; + + Sniper.OnShapeshift(shapeshifter, shapeshifting); + + if (!AmongUsClient.Instance.AmHost) return; + if (!shapeshifting) Camouflage.RpcSetSkin(__instance); + + if (Pelican.IsEaten(shapeshifter.PlayerId) || GameStates.IsVoting) + goto End; + + switch (shapeshifter.GetCustomRole()) + { + case CustomRoles.EvilTracker: + EvilTracker.OnShapeshift(shapeshifter, target, shapeshifting); + break; + case CustomRoles.FireWorks: + FireWorks.ShapeShiftState(shapeshifter, shapeshifting); + break; + case CustomRoles.Warlock: + if (Main.CursedPlayers[shapeshifter.PlayerId] != null)//呪われた人がいるか確認 + { + if (shapeshifting && !Main.CursedPlayers[shapeshifter.PlayerId].Data.IsDead)//変身解除の時に反応しない + { + var cp = Main.CursedPlayers[shapeshifter.PlayerId]; + Vector2 cppos = cp.transform.position;//呪われた人の位置 + Dictionary cpdistance = new(); + float dis; + foreach (PlayerControl p in Main.AllAlivePlayerControls) + { + if (p.PlayerId == cp.PlayerId) continue; + if (!Options.WarlockCanKillSelf.GetBool() && p.PlayerId == shapeshifter.PlayerId) continue; + if (!Options.WarlockCanKillAllies.GetBool() && p.GetCustomRole().IsImpostor()) continue; + if (p.Is(CustomRoles.Glitch)) continue; + if (p.Is(CustomRoles.Pestilence)) continue; + if (Pelican.IsEaten(p.PlayerId) || Medic.ProtectList.Contains(p.PlayerId)) continue; + dis = Vector2.Distance(cppos, p.transform.position); + cpdistance.Add(p, dis); + Logger.Info($"{p?.Data?.PlayerName}の位置{dis}", "Warlock"); + } + if (cpdistance.Count >= 1) + { + var min = cpdistance.OrderBy(c => c.Value).FirstOrDefault();//一番小さい値を取り出す + PlayerControl targetw = min.Key; + if (cp.RpcCheckAndMurder(targetw, true)) + { + targetw.SetRealKiller(shapeshifter); + Logger.Info($"{targetw.GetNameWithRole()}was killed", "Warlock"); + cp.RpcMurderPlayerV3(targetw);//殺す + shapeshifter.RpcGuardAndKill(shapeshifter); + shapeshifter.Notify(GetString("WarlockControlKill")); + } + } + else + { + shapeshifter.Notify(GetString("WarlockNoTarget")); + } + Main.isCurseAndKill[shapeshifter.PlayerId] = false; + } + Main.CursedPlayers[shapeshifter.PlayerId] = null; + } + break; + case CustomRoles.Escapee: + if (shapeshifting) + { + if (Main.EscapeeLocation.ContainsKey(shapeshifter.PlayerId)) + { + var position = Main.EscapeeLocation[shapeshifter.PlayerId]; + Main.EscapeeLocation.Remove(shapeshifter.PlayerId); + Logger.Msg($"{shapeshifter.GetNameWithRole()}:{position}", "EscapeeTeleport"); + Utils.TP(shapeshifter.NetTransform, position); + shapeshifter.RPCPlayCustomSound("Teleport"); + } + else + { + Main.EscapeeLocation.Add(shapeshifter.PlayerId, shapeshifter.GetTruePosition()); + } + } + break; + case CustomRoles.Miner: + if (Main.LastEnteredVent.ContainsKey(shapeshifter.PlayerId)) + { + int ventId = Main.LastEnteredVent[shapeshifter.PlayerId].Id; + var vent = Main.LastEnteredVent[shapeshifter.PlayerId]; + var position = Main.LastEnteredVentLocation[shapeshifter.PlayerId]; + Logger.Msg($"{shapeshifter.GetNameWithRole()}:{position}", "MinerTeleport"); + Utils.TP(shapeshifter.NetTransform, new Vector2(position.x, position.y)); + } + break; + case CustomRoles.Bomber: + if (shapeshifting) + { + Logger.Info("炸弹爆炸了", "Boom"); + CustomSoundsManager.RPCPlayCustomSoundAll("Boom"); + foreach (var tg in Main.AllPlayerControls) + { + if (!tg.IsModClient()) tg.KillFlash(); + var pos = shapeshifter.transform.position; + var dis = Vector2.Distance(pos, tg.transform.position); + + if (!tg.IsAlive() || Pelican.IsEaten(tg.PlayerId) || Medic.ProtectList.Contains(tg.PlayerId) || (tg.Is(CustomRoleTypes.Impostor) && Options.ImpostorsSurviveBombs.GetBool()) || tg.inVent || tg.Is(CustomRoles.Glitch) || tg.Is(CustomRoles.Pestilence)) continue; + if (dis > Options.BomberRadius.GetFloat()) continue; + if (tg.PlayerId == shapeshifter.PlayerId) continue; + + Main.PlayerStates[tg.PlayerId].deathReason = PlayerState.DeathReason.Bombed; + tg.SetRealKiller(shapeshifter); + tg.RpcMurderPlayerV3(tg); + Medic.IsDead(tg); + } + new LateTask(() => + { + var totalAlive = Main.AllAlivePlayerControls.Count(); + //自分が最後の生き残りの場合は勝利のために死なない + if (Options.BomberDiesInExplosion.GetBool()) + { + if (totalAlive > 0 && !GameStates.IsEnded) + { + Main.PlayerStates[shapeshifter.PlayerId].deathReason = PlayerState.DeathReason.Bombed; + shapeshifter.RpcMurderPlayerV3(shapeshifter); + } + } + Utils.NotifyRoles(); + }, 1.5f, "Bomber Suiscide"); + } + break; + case CustomRoles.Nuker: + if (shapeshifting) + { + Logger.Info("炸弹爆炸了", "Boom"); + CustomSoundsManager.RPCPlayCustomSoundAll("Boom"); + foreach (var tg in Main.AllPlayerControls) + { + if (!tg.IsModClient()) tg.KillFlash(); + var pos = shapeshifter.transform.position; + var dis = Vector2.Distance(pos, tg.transform.position); + + if (!tg.IsAlive() || Pelican.IsEaten(tg.PlayerId) || Medic.ProtectList.Contains(tg.PlayerId) || tg.inVent || tg.Is(CustomRoles.Glitch) || tg.Is(CustomRoles.Pestilence)) continue; + if (dis > Options.NukeRadius.GetFloat()) continue; + if (tg.PlayerId == shapeshifter.PlayerId) continue; + + Main.PlayerStates[tg.PlayerId].deathReason = PlayerState.DeathReason.Bombed; + tg.SetRealKiller(shapeshifter); + tg.RpcMurderPlayerV3(tg); + Medic.IsDead(tg); + } + new LateTask(() => + { + var totalAlive = Main.AllAlivePlayerControls.Count(); + //自分が最後の生き残りの場合は勝利のために死なない + // if (Options.BomberDiesInExplosion.GetBool()) + { + if (totalAlive > 0 && !GameStates.IsEnded) + { + Main.PlayerStates[shapeshifter.PlayerId].deathReason = PlayerState.DeathReason.Bombed; + shapeshifter.RpcMurderPlayerV3(shapeshifter); + } + } + Utils.NotifyRoles(); + }, 1.5f, "Nuke"); + } + break; + case CustomRoles.Assassin: + Assassin.OnShapeshift(shapeshifter, shapeshifting); + break; + case CustomRoles.ImperiusCurse: + if (shapeshifting) + { + new LateTask(() => + { + if (!(!GameStates.IsInTask || !shapeshifter.IsAlive() || !target.IsAlive() || shapeshifter.inVent || target.inVent)) + { + var originPs = target.GetTruePosition(); + Utils.TP(target.NetTransform, shapeshifter.GetTruePosition()); + Utils.TP(shapeshifter.NetTransform, originPs); + } + }, 1.5f, "ImperiusCurse TP"); + } + break; + case CustomRoles.QuickShooter: + QuickShooter.OnShapeshift(shapeshifter, shapeshifting); + break; + case CustomRoles.Camouflager: + if (shapeshifting) + Camouflager.OnShapeshift(); + if (!shapeshifting) + Camouflager.OnReportDeadBody(); + break; + case CustomRoles.Hacker: + Hacker.OnShapeshift(shapeshifter, shapeshifting, target); + break; + case CustomRoles.Disperser: + if (shapeshifting) + Disperser.DispersePlayers(shapeshifter); + break; + case CustomRoles.Dazzler: + if (shapeshifting) + Dazzler.OnShapeshift(shapeshifter, target); + break; + case CustomRoles.Deathpact: + if (shapeshifting) + Deathpact.OnShapeshift(shapeshifter, target); + break; + case CustomRoles.Devourer: + if (shapeshifting) + Devourer.OnShapeshift(shapeshifter, target); + break; + case CustomRoles.Twister: + Twister.TwistPlayers(shapeshifter); + break; + } + + End: + + //変身解除のタイミングがずれて名前が直せなかった時のために強制書き換え + if (!shapeshifting) + { + new LateTask(() => + { + Utils.NotifyRoles(NoCache: true); + }, + 1.2f, "ShapeShiftNotify"); + } + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ReportDeadBody))] +class ReportDeadBodyPatch +{ + public static Dictionary CanReport; + public static Dictionary> WaitReport = new(); + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] GameData.PlayerInfo target) + { + if (GameStates.IsMeeting) return false; + if (Options.DisableMeeting.GetBool()) return false; + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) return false; + if (!CanReport[__instance.PlayerId]) + { + WaitReport[__instance.PlayerId].Add(target); + Logger.Warn($"{__instance.GetNameWithRole()}:通報禁止中のため可能になるまで待機します", "ReportDeadBody"); + return false; + } + + Logger.Info($"{__instance.GetNameWithRole()} => {target?.Object?.GetNameWithRole() ?? "null"}", "ReportDeadBody"); + + foreach (var kvp in Main.PlayerStates) + { + var pc = Utils.GetPlayerById(kvp.Key); + kvp.Value.LastRoom = pc.GetPlainShipRoom(); + } + + if (!AmongUsClient.Instance.AmHost) return true; + + try + { + //通報者が死んでいる場合、本処理で会議がキャンセルされるのでここで止める + if (__instance.Data.IsDead) return false; + + //============================================= + //以下、检查是否允许本次会议 + //============================================= + + var killer = target?.Object?.GetRealKiller(); + var killerRole = killer?.GetCustomRole(); + + //杀戮机器无法报告或拍灯 + // if (__instance.Is(CustomRoles.Minimalism)) return false; + //禁止小黑人报告 + if (((Utils.IsActive(SystemTypes.Comms) && Options.CommsCamouflage.GetBool()) || Camouflager.IsActive) && Options.DisableReportWhenCC.GetBool()) return false; + + if (target == null) //拍灯事件 + { + if (__instance.Is(CustomRoles.Jester) && !Options.JesterCanUseButton.GetBool()) return false; + } + if (target != null) //拍灯事件 + { + if (Bloodhound.UnreportablePlayers.Contains(target.PlayerId)) return false; + + if (__instance.Is(CustomRoles.Bloodhound)) + { + if (killer != null) + { + Bloodhound.OnReportDeadBody(__instance, target, killer); + } + else + { + __instance.Notify(GetString("BloodhoundNoTrack")); + } + + return false; + } + if (Vulture.UnreportablePlayers.Contains(target.PlayerId)) return false; + + if (__instance.Is(CustomRoles.Vulture)) + { + long now = Utils.GetTimeStamp(); + if ((Vulture.AbilityLeftInRound[__instance.PlayerId] > 0) && (now - Vulture.LastReport[__instance.PlayerId] > (long)Vulture.VultureReportCD.GetFloat())) + { + Vulture.LastReport[__instance.PlayerId] = now; + + Vulture.OnReportDeadBody(__instance, target); + __instance.RpcGuardAndKill(__instance); + __instance.Notify(GetString("VultureReportBody")); + if (Vulture.AbilityLeftInRound[__instance.PlayerId] > 0) + { + new LateTask(() => + { + if (GameStates.IsInTask) + { + __instance.RpcGuardAndKill(__instance); + __instance.Notify(GetString("VultureCooldownUp")); + } + return; + }, Vulture.VultureReportCD.GetFloat(), "Vulture CD"); + } + + Logger.Info($"{__instance.GetRealName()} ate {target.PlayerName} corpse", "Vulture"); + return false; + } + } + + // 清洁工来扫大街咯 + if (__instance.Is(CustomRoles.Cleaner)) + { + Main.CleanerBodies.Remove(target.PlayerId); + Main.CleanerBodies.Add(target.PlayerId); + __instance.RpcGuardAndKill(__instance); + __instance.Notify(GetString("CleanerCleanBody")); + // __instance.ResetKillCooldown(); + __instance.SetKillCooldownV3(Options.KillCooldownAfterCleaning.GetFloat()); + Logger.Info($"{__instance.GetRealName()} 清理了 {target.PlayerName} 的尸体", "Cleaner"); + return false; + } + if (__instance.Is(CustomRoles.Medusa)) + { + Main.MedusaBodies.Remove(target.PlayerId); + Main.MedusaBodies.Add(target.PlayerId); + __instance.RpcGuardAndKill(__instance); + __instance.Notify(GetString("MedusaStoneBody")); + // __instance.ResetKillCooldown(); + __instance.SetKillCooldownV3(Medusa.KillCooldownAfterStoneGazing.GetFloat()); + Logger.Info($"{__instance.GetRealName()} stoned {target.PlayerName} body", "Medusa"); + return false; + } + + // 被赌杀的尸体无法被报告 + if (Main.PlayerStates[target.PlayerId].deathReason == PlayerState.DeathReason.Gambled) return false; + + // 清道夫的尸体无法被报告 + if (killerRole == CustomRoles.Scavenger) return false; + + // 被清理的尸体无法报告 + if (Main.CleanerBodies.Contains(target.PlayerId)) return false; + + if (Main.MedusaBodies.Contains(target.PlayerId)) return false; + + // 胆小鬼不敢报告 + var tpc = Utils.GetPlayerById(target.PlayerId); + if (__instance.Is(CustomRoles.Oblivious)) + { + if (!tpc.Is(CustomRoles.Bait) || (tpc.Is(CustomRoles.Bait) && Options.ObliviousBaitImmune.GetBool())) /* && (target?.Object != null)*/ + { + return false; + } + } + + if (__instance.Is(CustomRoles.Unlucky) && (target?.Object == null || !target.Object.Is(CustomRoles.Bait))) + { + var Ue = IRandom.Instance; + if (Ue.Next(0, 100) < Options.UnluckyReportSuicideChance.GetInt()) + { + __instance.RpcMurderPlayerV3(__instance); + Main.PlayerStates[__instance.PlayerId].deathReason = PlayerState.DeathReason.Suicide; + return false; + } + } + + + // 报告了诡雷尸体 + if (Main.BoobyTrapBody.Contains(target.PlayerId) && __instance.IsAlive()) + { + if (!Options.TrapOnlyWorksOnTheBodyBoobyTrap.GetBool()) + { + var killerID = Main.KillerOfBoobyTrapBody[target.PlayerId]; + Main.PlayerStates[__instance.PlayerId].deathReason = PlayerState.DeathReason.Bombed; + __instance.SetRealKiller(Utils.GetPlayerById(killerID)); + + __instance.RpcMurderPlayerV3(__instance); + RPC.PlaySoundRPC(killerID, Sounds.KillSound); + + if (!Main.BoobyTrapBody.Contains(__instance.PlayerId)) Main.BoobyTrapBody.Add(__instance.PlayerId); + if (!Main.KillerOfBoobyTrapBody.ContainsKey(__instance.PlayerId)) Main.KillerOfBoobyTrapBody.Add(__instance.PlayerId, killerID); + return false; + } + else + { + var killerID2 = target.PlayerId; + Main.PlayerStates[__instance.PlayerId].deathReason = PlayerState.DeathReason.Bombed; + __instance.SetRealKiller(Utils.GetPlayerById(killerID2)); + + __instance.RpcMurderPlayerV3(__instance); + RPC.PlaySoundRPC(killerID2, Sounds.KillSound); + return false; + } + } + + if (target.Object.Is(CustomRoles.Unreportable)) return false; + } + + if (Options.SyncButtonMode.GetBool() && target == null) + { + Logger.Info("最大:" + Options.SyncedButtonCount.GetInt() + ", 現在:" + Options.UsedButtonCount, "ReportDeadBody"); + if (Options.SyncedButtonCount.GetFloat() <= Options.UsedButtonCount) + { + Logger.Info("使用可能ボタン回数が最大数を超えているため、ボタンはキャンセルされました。", "ReportDeadBody"); + return false; + } + else Options.UsedButtonCount++; + if (Options.SyncedButtonCount.GetFloat() == Options.UsedButtonCount) + { + Logger.Info("使用可能ボタン回数が最大数に達しました。", "ReportDeadBody"); + } + } + + AfterReportTasks(__instance, target); + + } + catch (Exception e) + { + Logger.Exception(e, "ReportDeadBodyPatch"); + Logger.SendInGame("Error: " + e.ToString()); + } + + return true; + } + public static void AfterReportTasks(PlayerControl player, GameData.PlayerInfo target) + { + //============================================= + //以下、ボタンが押されることが確定したものとする。 + //============================================= + + if (target == null) //ボタン + { + if (player.Is(CustomRoles.Mayor)) + { + Main.MayorUsedButtonCount[player.PlayerId] += 1; + } + } + else + { + var tpc = Utils.GetPlayerById(target.PlayerId); + if (tpc != null && !tpc.IsAlive()) + { + // 侦探报告 + if (player.Is(CustomRoles.Detective) && player.PlayerId != target.PlayerId) + { + string msg; + msg = string.Format(GetString("DetectiveNoticeVictim"), tpc.GetRealName(), tpc.GetDisplayRoleName()); + if (Options.DetectiveCanknowKiller.GetBool()) + { + var realKiller = tpc.GetRealKiller(); + if (realKiller == null) msg += ";" + GetString("DetectiveNoticeKillerNotFound"); + else msg += ";" + string.Format(GetString("DetectiveNoticeKiller"), realKiller.GetDisplayRoleName()); + } + Main.DetectiveNotify.Add(player.PlayerId, msg); + } + } + + if (Main.InfectedBodies.Contains(target.PlayerId)) Virus.OnKilledBodyReport(player); + } + + Main.LastVotedPlayerInfo = null; + Main.ArsonistTimer.Clear(); + Main.FarseerTimer.Clear(); + Main.PuppeteerList.Clear(); + Main.TaglockedList.Clear(); + Main.GuesserGuessed.Clear(); + Main.VeteranInProtect.Clear(); + Main.GrenadierBlinding.Clear(); + Main.MadGrenadierBlinding.Clear(); + Divinator.didVote.Clear(); + Oracle.didVote.Clear(); + Bloodhound.Clear(); + Vulture.Clear(); + + Camouflager.OnReportDeadBody(); + Psychic.OnReportDeadBody(); + BountyHunter.OnReportDeadBody(); + SerialKiller.OnReportDeadBody(); + Sniper.OnReportDeadBody(); + Vampire.OnStartMeeting(); + Poisoner.OnStartMeeting(); + Pelican.OnReportDeadBody(); + Counterfeiter.OnReportDeadBody(); + QuickShooter.OnReportDeadBody(); + Eraser.OnReportDeadBody(); + Hacker.OnReportDeadBody(); + Judge.OnReportDeadBody(); + // Councillor.OnReportDeadBody(); + Greedier.OnReportDeadBody(); + Tracker.OnReportDeadBody(); + Addict.OnReportDeadBody(); + Deathpact.OnReportDeadBody(); + ParityCop.OnReportDeadBody(); + Doomsayer.OnReportDeadBody(); + BallLightning.OnReportDeadBody(); + + Mortician.OnReportDeadBody(player, target); + Tracefinder.OnReportDeadBody(player, target); + Mediumshiper.OnReportDeadBody(target); + Spiritualist.OnReportDeadBody(target); + + foreach (var x in Main.RevolutionistStart) + { + var tar = Utils.GetPlayerById(x.Key); + if (tar == null) continue; + tar.Data.IsDead = true; + Main.PlayerStates[tar.PlayerId].deathReason = PlayerState.DeathReason.Sacrifice; + tar.RpcExileV2(); + Main.PlayerStates[tar.PlayerId].SetDead(); + Logger.Info($"{tar.GetRealName()} 因会议革命失败", "Revolutionist"); + } + Main.RevolutionistTimer.Clear(); + Main.RevolutionistStart.Clear(); + Main.RevolutionistLastTime.Clear(); + + Main.AllPlayerControls + .Where(pc => Main.CheckShapeshift.ContainsKey(pc.PlayerId)) + .Do(pc => Camouflage.RpcSetSkin(pc, RevertToDefault: true)); + + MeetingTimeManager.OnReportDeadBody(); + + Utils.NotifyRoles(isForMeeting: true, NoCache: true, CamouflageIsForMeeting: true, GuesserIsForMeeting: true); + + Utils.SyncAllSettings(); + } + public static async void ChangeLocalNameAndRevert(string name, int time) + { + //async Taskじゃ警告出るから仕方ないよね。 + var revertName = PlayerControl.LocalPlayer.name; + PlayerControl.LocalPlayer.RpcSetNameEx(name); + await Task.Delay(time); + PlayerControl.LocalPlayer.RpcSetNameEx(revertName); + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] +class FixedUpdatePatch +{ + private static StringBuilder Mark = new(20); + private static StringBuilder Suffix = new(120); + private static int LevelKickBufferTime = 10; + private static Dictionary BufferTime = new(); + public static void Postfix(PlayerControl __instance) + { + var player = __instance; + + if (!GameStates.IsModHost) return; + + bool lowLoad = false; + if (Options.LowLoadMode.GetBool()) + { + BufferTime.TryAdd(player.PlayerId, 10); + BufferTime[player.PlayerId]--; + if (BufferTime[player.PlayerId] > 0) lowLoad = true; + else BufferTime[player.PlayerId] = 10; + } + + Sniper.OnFixedUpdate(player); + Zoom.OnFixedUpdate(); + if (!lowLoad) + { + NameNotifyManager.OnFixedUpdate(player); + TargetArrow.OnFixedUpdate(player); + LocateArrow.OnFixedUpdate(player); + } + + + if (AmongUsClient.Instance.AmHost) + {//実行クライアントがホストの場合のみ実行 + if (GameStates.IsLobby && ((ModUpdater.hasUpdate && ModUpdater.forceUpdate) || ModUpdater.isBroken || !Main.AllowPublicRoom) && AmongUsClient.Instance.IsGamePublic) + AmongUsClient.Instance.ChangeGamePublic(false); + + if (GameStates.IsInTask && ReportDeadBodyPatch.CanReport[__instance.PlayerId] && ReportDeadBodyPatch.WaitReport[__instance.PlayerId].Count > 0) + { + var info = ReportDeadBodyPatch.WaitReport[__instance.PlayerId][0]; + ReportDeadBodyPatch.WaitReport[__instance.PlayerId].Clear(); + Logger.Info($"{__instance.GetNameWithRole()}:通報可能になったため通報処理を行います", "ReportDeadbody"); + __instance.ReportDeadBody(info); + } + + //踢出低等级的人 + if (!lowLoad && GameStates.IsLobby && !player.AmOwner && Options.KickLowLevelPlayer.GetInt() != 0 && ( + (player.Data.PlayerLevel != 0 && player.Data.PlayerLevel < Options.KickLowLevelPlayer.GetInt()) || + player.Data.FriendCode == "" + )) + { + LevelKickBufferTime--; + if (LevelKickBufferTime <= 0) + { + LevelKickBufferTime = 20; + AmongUsClient.Instance.KickPlayer(player.GetClientId(), false); + string msg = string.Format(GetString("KickBecauseLowLevel"), player.GetRealName().RemoveHtmlTags()); + Logger.SendInGame(msg); + Logger.Info(msg, "LowLevel Kick"); + } + } + + DoubleTrigger.OnFixedUpdate(player); + Vampire.OnFixedUpdate(player); + Poisoner.OnFixedUpdate(player); + BountyHunter.FixedUpdate(player); + SerialKiller.FixedUpdate(player); + if (GameStates.IsInTask) + if (player.Is(CustomRoles.PlagueBearer) && PlagueBearer.IsPlaguedAll(player)) + { + player.RpcSetCustomRole(CustomRoles.Pestilence); + player.Notify(GetString("PlagueBearerToPestilence")); + player.RpcGuardAndKill(player); + if (!PlagueBearer.PestilenceList.Contains(player.PlayerId)) + PlagueBearer.PestilenceList.Add(player.PlayerId); + PlagueBearer.SetKillCooldownPestilence(player.PlayerId); + PlagueBearer.playerIdList.Remove(player.PlayerId); + } + + + #region 女巫处理 + if (GameStates.IsInTask && Main.WarlockTimer.ContainsKey(player.PlayerId))//処理を1秒遅らせる + { + if (player.IsAlive()) + { + if (Main.WarlockTimer[player.PlayerId] >= 1f) + { + player.RpcResetAbilityCooldown(); + Main.isCursed = false;//変身クールを1秒に変更 + player.SyncSettings(); + Main.WarlockTimer.Remove(player.PlayerId); + } + else Main.WarlockTimer[player.PlayerId] = Main.WarlockTimer[player.PlayerId] + Time.fixedDeltaTime;//時間をカウント + } + else + { + Main.WarlockTimer.Remove(player.PlayerId); + } + } + //ターゲットのリセット + #endregion + + #region 纵火犯浇油处理 + if (GameStates.IsInTask && Main.ArsonistTimer.ContainsKey(player.PlayerId))//アーソニストが誰かを塗っているとき + { + if (!player.IsAlive() || Pelican.IsEaten(player.PlayerId)) + { + Main.ArsonistTimer.Remove(player.PlayerId); + Utils.NotifyRoles(SpecifySeer: __instance); + RPC.ResetCurrentDousingTarget(player.PlayerId); + } + else + { + var ar_target = Main.ArsonistTimer[player.PlayerId].Item1;//塗られる人 + var ar_time = Main.ArsonistTimer[player.PlayerId].Item2;//塗った時間 + if (!ar_target.IsAlive()) + { + Main.ArsonistTimer.Remove(player.PlayerId); + } + else if (ar_time >= Options.ArsonistDouseTime.GetFloat())//時間以上一緒にいて塗れた時 + { + player.SetKillCooldown(); + Main.ArsonistTimer.Remove(player.PlayerId);//塗が完了したのでDictionaryから削除 + Main.isDoused[(player.PlayerId, ar_target.PlayerId)] = true;//塗り完了 + player.RpcSetDousedPlayer(ar_target, true); + Utils.NotifyRoles(SpecifySeer: player);//名前変更 + RPC.ResetCurrentDousingTarget(player.PlayerId); + } + else + { + + float range = NormalGameOptionsV07.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; + float dis = Vector2.Distance(player.transform.position, ar_target.transform.position);//距離を出す + if (dis <= range)//一定の距離にターゲットがいるならば時間をカウント + { + Main.ArsonistTimer[player.PlayerId] = (ar_target, ar_time + Time.fixedDeltaTime); + } + else//それ以外は削除 + { + Main.ArsonistTimer.Remove(player.PlayerId); + Utils.NotifyRoles(SpecifySeer: player); + RPC.ResetCurrentDousingTarget(player.PlayerId); + + Logger.Info($"Canceled: {player.GetNameWithRole()}", "Arsonist"); + } + } + } + } + #endregion + + #region 革命家拉人处理 + if (GameStates.IsInTask && Main.RevolutionistTimer.ContainsKey(player.PlayerId))//当革命家拉拢一个玩家时 + { + if (!player.IsAlive() || Pelican.IsEaten(player.PlayerId)) + { + Main.RevolutionistTimer.Remove(player.PlayerId); + Utils.NotifyRoles(SpecifySeer: player); + RPC.ResetCurrentDrawTarget(player.PlayerId); + } + else + { + var rv_target = Main.RevolutionistTimer[player.PlayerId].Item1;//拉拢的人 + var rv_time = Main.RevolutionistTimer[player.PlayerId].Item2;//拉拢时间 + if (!rv_target.IsAlive()) + { + Main.RevolutionistTimer.Remove(player.PlayerId); + } + else if (rv_time >= Options.RevolutionistDrawTime.GetFloat())//在一起时间超过多久 + { + player.SetKillCooldown(); + Main.RevolutionistTimer.Remove(player.PlayerId);//拉拢完成从字典中删除 + Main.isDraw[(player.PlayerId, rv_target.PlayerId)] = true;//完成拉拢 + player.RpcSetDrawPlayer(rv_target, true); + Utils.NotifyRoles(SpecifySeer: player); + RPC.ResetCurrentDrawTarget(player.PlayerId); + if (IRandom.Instance.Next(1, 100) <= Options.RevolutionistKillProbability.GetInt()) + { + rv_target.SetRealKiller(player); + Main.PlayerStates[rv_target.PlayerId].deathReason = PlayerState.DeathReason.Sacrifice; + player.RpcMurderPlayerV3(rv_target); + Main.PlayerStates[rv_target.PlayerId].SetDead(); + Logger.Info($"Revolutionist: {player.GetNameWithRole()} killed {rv_target.GetNameWithRole()}", "Revolutionist"); + } + } + else + { + float range = NormalGameOptionsV07.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; + float dis = Vector2.Distance(player.transform.position, rv_target.transform.position);//超出距离 + if (dis <= range)//在一定距离内则计算时间 + { + Main.RevolutionistTimer[player.PlayerId] = (rv_target, rv_time + Time.fixedDeltaTime); + } + else//否则删除 + { + Main.RevolutionistTimer.Remove(player.PlayerId); + Utils.NotifyRoles(SpecifySeer: __instance); + RPC.ResetCurrentDrawTarget(player.PlayerId); + + Logger.Info($"Canceled: {__instance.GetNameWithRole()}", "Revolutionist"); + } + } + } + } + if (GameStates.IsInTask && player.IsDrawDone() && player.IsAlive()) + { + if (Main.RevolutionistStart.ContainsKey(player.PlayerId)) //如果存在字典 + { + if (Main.RevolutionistLastTime.ContainsKey(player.PlayerId)) + { + long nowtime = Utils.GetTimeStamp(); + if (Main.RevolutionistLastTime[player.PlayerId] != nowtime) Main.RevolutionistLastTime[player.PlayerId] = nowtime; + int time = (int)(Main.RevolutionistLastTime[player.PlayerId] - Main.RevolutionistStart[player.PlayerId]); + int countdown = Options.RevolutionistVentCountDown.GetInt() - time; + Main.RevolutionistCountdown.Clear(); + if (countdown <= 0)//倒计时结束 + { + Utils.GetDrawPlayerCount(player.PlayerId, out var y); + foreach (var pc in y.Where(x => x != null && x.IsAlive())) + { + pc.Data.IsDead = true; + Main.PlayerStates[pc.PlayerId].deathReason = PlayerState.DeathReason.Sacrifice; + pc.RpcMurderPlayerV3(pc); + Main.PlayerStates[pc.PlayerId].SetDead(); + Utils.NotifyRoles(SpecifySeer: pc); + } + player.Data.IsDead = true; + Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.Sacrifice; + player.RpcMurderPlayerV3(player); + Main.PlayerStates[player.PlayerId].SetDead(); + } + else + { + Main.RevolutionistCountdown.Add(player.PlayerId, countdown); + } + } + else + { + Main.RevolutionistLastTime.TryAdd(player.PlayerId, Main.RevolutionistStart[player.PlayerId]); + } + } + else //如果不存在字典 + { + Main.RevolutionistStart.TryAdd(player.PlayerId, Utils.GetTimeStamp()); + } + } + #endregion + + Farseer.OnPostFix(player); + Addict.FixedUpdate(player); + Deathpact.OnFixedUpdate(player); + + if (!lowLoad) + { + //检查老兵技能是否失效 + if (GameStates.IsInTask && player.Is(CustomRoles.Veteran)) + { + if (Main.VeteranInProtect.TryGetValue(player.PlayerId, out var vtime) && vtime + Options.VeteranSkillDuration.GetInt() < Utils.GetTimeStamp()) + { + Main.VeteranInProtect.Remove(player.PlayerId); + player.RpcGuardAndKill(); + player.Notify(string.Format(GetString("VeteranOffGuard"), Main.VeteranNumOfUsed[player.PlayerId])); + } + } + + //检查掷雷兵技能是否生效 + if (GameStates.IsInTask && player.Is(CustomRoles.Grenadier)) + { + if (Main.GrenadierBlinding.TryGetValue(player.PlayerId, out var gtime) && gtime + Options.GrenadierSkillDuration.GetInt() < Utils.GetTimeStamp()) + { + Main.GrenadierBlinding.Remove(player.PlayerId); + player.RpcGuardAndKill(); + player.Notify(GetString("GrenadierSkillStop")); + Utils.MarkEveryoneDirtySettings(); + } + if (Main.MadGrenadierBlinding.TryGetValue(player.PlayerId, out var mgtime) && mgtime + Options.GrenadierSkillDuration.GetInt() < Utils.GetTimeStamp()) + { + Main.MadGrenadierBlinding.Remove(player.PlayerId); + player.RpcGuardAndKill(); + player.Notify(GetString("GrenadierSkillStop")); + Utils.MarkEveryoneDirtySettings(); + } + } + + //检查马里奥是否完成 + if (GameStates.IsInTask && player.Is(CustomRoles.Mario) && Main.MarioVentCount[player.PlayerId] > Options.MarioVentNumWin.GetInt()) + { + Main.MarioVentCount[player.PlayerId] = Options.MarioVentNumWin.GetInt(); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Mario); //马里奥这个多动症赢了 + CustomWinnerHolder.WinnerIds.Add(player.PlayerId); + } + if (GameStates.IsInTask && player.Is(CustomRoles.Vulture) && Vulture.BodyReportCount[player.PlayerId] >= Vulture.NumberOfReportsToWin.GetInt()) + { + Vulture.BodyReportCount[player.PlayerId] = Vulture.NumberOfReportsToWin.GetInt(); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Vulture); + CustomWinnerHolder.WinnerIds.Add(player.PlayerId); + } + + + Pelican.OnFixedUpdate(); + BallLightning.OnFixedUpdate(); + Swooper.OnFixedUpdate(player); + Wraith.OnFixedUpdate(player); + Chameleon.OnFixedUpdate(player); + BloodKnight.OnFixedUpdate(player); + Spiritcaller.OnFixedUpdate(player); + + if (GameStates.IsInTask && player.IsAlive() && Options.LadderDeath.GetBool()) FallFromLadder.FixedUpdate(player); + + if (GameStates.IsInGame) LoversSuicide(); + + #region 傀儡师处理 + if (GameStates.IsInTask && Main.PuppeteerList.ContainsKey(player.PlayerId)) + { + if (!player.IsAlive() || Pelican.IsEaten(player.PlayerId)) + { + Main.PuppeteerList.Remove(player.PlayerId); + } + else + { + Vector2 puppeteerPos = player.transform.position;//PuppeteerListのKeyの位置 + Dictionary targetDistance = new(); + float dis; + foreach (var target in Main.AllAlivePlayerControls) + { + + { + if (target.PlayerId != player.PlayerId && !target.Is(CustomRoleTypes.Impostor) && !target.Is(CustomRoles.Glitch) && !target.Is(CustomRoles.Pestilence)) + { + dis = Vector2.Distance(puppeteerPos, target.transform.position); + targetDistance.Add(target.PlayerId, dis); + } + } + } + if (targetDistance.Count() != 0) + { + var min = targetDistance.OrderBy(c => c.Value).FirstOrDefault();//一番値が小さい + PlayerControl target = Utils.GetPlayerById(min.Key); + var KillRange = NormalGameOptionsV07.KillDistances[Mathf.Clamp(Main.NormalOptions.KillDistance, 0, 2)]; + if (min.Value <= KillRange && player.CanMove && target.CanMove) + { + if (player.RpcCheckAndMurder(target, true)) + { + var puppeteerId = Main.PuppeteerList[player.PlayerId]; + RPC.PlaySoundRPC(puppeteerId, Sounds.KillSound); + target.SetRealKiller(Utils.GetPlayerById(puppeteerId)); + player.RpcMurderPlayerV3(target); + Utils.MarkEveryoneDirtySettings(); + Main.PuppeteerList.Remove(player.PlayerId); + Utils.NotifyRoles(); + } + } + } + } + } + if (GameStates.IsInTask && Main.TaglockedList.ContainsKey(player.PlayerId)) + { + if (!player.IsAlive() || Pelican.IsEaten(player.PlayerId)) + { + Main.TaglockedList.Remove(player.PlayerId); + } + else + { + Vector2 puppeteerPos = player.transform.position;//PuppeteerListのKeyの位置 + Dictionary targetDistance = new(); + float dis; + foreach (var target in Main.AllAlivePlayerControls) + { + + { + if (target.PlayerId != player.PlayerId && !target.Is(CustomRoles.NWitch) && !target.Is(CustomRoles.Glitch) && !target.Is(CustomRoles.Pestilence)) + { + dis = Vector2.Distance(puppeteerPos, target.transform.position); + targetDistance.Add(target.PlayerId, dis); + } + } + } + if (targetDistance.Count() != 0) + { + var min = targetDistance.OrderBy(c => c.Value).FirstOrDefault();//一番値が小さい + PlayerControl target = Utils.GetPlayerById(min.Key); + var KillRange = NormalGameOptionsV07.KillDistances[Mathf.Clamp(Main.NormalOptions.KillDistance, 0, 2)]; + if (min.Value <= KillRange && player.CanMove && target.CanMove) + { + if (player.RpcCheckAndMurder(target, true)) + { + var puppeteerId = Main.TaglockedList[player.PlayerId]; + RPC.PlaySoundRPC(puppeteerId, Sounds.KillSound); + target.SetRealKiller(Utils.GetPlayerById(puppeteerId)); + player.RpcMurderPlayerV3(target); + Utils.MarkEveryoneDirtySettings(); + Main.TaglockedList.Remove(player.PlayerId); + Utils.NotifyRoles(); + } + } + } + } + } + #endregion + + if (GameStates.IsInTask && player == PlayerControl.LocalPlayer) + DisableDevice.FixedUpdate(); + if (GameStates.IsInTask && player == PlayerControl.LocalPlayer) + AntiAdminer.FixedUpdate(); + + if (GameStates.IsInGame && Main.RefixCooldownDelay <= 0) + foreach (var pc in Main.AllPlayerControls) + { + if (pc.Is(CustomRoles.Vampire) || pc.Is(CustomRoles.Warlock) || pc.Is(CustomRoles.Assassin)) + Main.AllPlayerKillCooldown[pc.PlayerId] = Options.DefaultKillCooldown * 2; + if (pc.Is(CustomRoles.Poisoner)) + Main.AllPlayerKillCooldown[pc.PlayerId] = Options.DefaultKillCooldown * 2; + } + + if (!Main.DoBlockNameChange && AmongUsClient.Instance.AmHost) + Utils.ApplySuffix(__instance); + } + } + + //LocalPlayer専用 + if (__instance.AmOwner) + { + //キルターゲットの上書き処理 + if (GameStates.IsInTask && !__instance.Is(CustomRoleTypes.Impostor) && __instance.CanUseKillButton() && !__instance.Data.IsDead) + { + var players = __instance.GetPlayersInAbilityRangeSorted(false); + PlayerControl closest = players.Count <= 0 ? null : players[0]; + HudManager.Instance.KillButton.SetTarget(closest); + } + } + + //役職テキストの表示 + var RoleTextTransform = __instance.cosmetics.nameText.transform.Find("RoleText"); + var RoleText = RoleTextTransform.GetComponent(); + + if (RoleText != null && __instance != null && !lowLoad) + { + if (GameStates.IsLobby) + { + if (Main.playerVersion.TryGetValue(__instance.PlayerId, out var ver)) + { + if (Main.ForkId != ver.forkId) // フォークIDが違う場合 + __instance.cosmetics.nameText.text = $"{ver.forkId}\n{__instance?.name}"; + else if (Main.version.CompareTo(ver.version) == 0) + __instance.cosmetics.nameText.text = ver.tag == $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})" ? $"{__instance.name}" : $"{ver.tag}\n{__instance?.name}"; + else __instance.cosmetics.nameText.text = $"v{ver.version}\n{__instance?.name}"; + } + else __instance.cosmetics.nameText.text = __instance?.Data?.PlayerName; + } + if (GameStates.IsInGame) + { + var RoleTextData = Utils.GetRoleText(PlayerControl.LocalPlayer.PlayerId, __instance.PlayerId); + + //if (Options.CurrentGameMode == CustomGameMode.HideAndSeek) + //{ + // var hasRole = main.AllPlayerCustomRoles.TryGetValue(__instance.PlayerId, out var role); + // if (hasRole) RoleTextData = Utils.GetRoleTextHideAndSeek(__instance.Data.Role.Role, role); + //} + RoleText.text = RoleTextData.Item1; + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) RoleText.text = ""; + RoleText.color = RoleTextData.Item2; + if (__instance.AmOwner) RoleText.enabled = true; //自分ならロールを表示 + else if (Options.CurrentGameMode == CustomGameMode.SoloKombat) RoleText.enabled = true; + else if (Main.VisibleTasksCount && PlayerControl.LocalPlayer.Data.IsDead && Options.GhostCanSeeOtherRoles.GetBool()) RoleText.enabled = true; //他プレイヤーでVisibleTasksCountが有効なおかつ自分が死んでいるならロールを表示 + else if (PlayerControl.LocalPlayer.Is(CustomRoles.Mimic) && Main.VisibleTasksCount && __instance.Data.IsDead && Options.MimicCanSeeDeadRoles.GetBool()) RoleText.enabled = true; //他プレイヤーでVisibleTasksCountが有効なおかつ自分が死んでいるならロールを表示 + else if (__instance.Is(CustomRoles.Mimic) && Main.VisibleTasksCount && __instance.Data.IsDead) RoleText.enabled = true; //他プレイヤーでVisibleTasksCountが有効なおかつ自分が死んでいるならロールを表示 + else if (__instance.Is(CustomRoles.Lovers) && PlayerControl.LocalPlayer.Is(CustomRoles.Lovers) && Options.LoverKnowRoles.GetBool()) RoleText.enabled = true; + else if (__instance.Is(CustomRoles.Ntr) && Options.LoverKnowRoles.GetBool()) RoleText.enabled = true; + else if (__instance.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && Options.ImpKnowAlliesRole.GetBool()) RoleText.enabled = true; + else if (__instance.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.Is(CustomRoles.Madmate) && Options.MadmateKnowWhosImp.GetBool()) RoleText.enabled = true; + else if (__instance.Is(CustomRoles.Madmate) && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && Options.ImpKnowWhosMadmate.GetBool()) RoleText.enabled = true; + else if (__instance.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.Is(CustomRoles.Crewpostor) && Options.AlliesKnowCrewpostor.GetBool()) RoleText.enabled = true; + else if (__instance.Is(CustomRoles.Crewpostor) && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && Options.CrewpostorKnowsAllies.GetBool()) RoleText.enabled = true; + else if (__instance.Is(CustomRoles.Madmate) && PlayerControl.LocalPlayer.Is(CustomRoles.Madmate) && Options.MadmateKnowWhosMadmate.GetBool()) RoleText.enabled = true; + else if (__instance.Is(CustomRoles.Rogue) && PlayerControl.LocalPlayer.Is(CustomRoles.Rogue) && Options.RogueKnowEachOther.GetBool() && Options.RogueKnowEachOtherRoles.GetBool()) RoleText.enabled = true; + else if (__instance.Is(CustomRoles.Sidekick) && PlayerControl.LocalPlayer.Is(CustomRoles.Sidekick)) RoleText.enabled = true; + else if (__instance.Is(CustomRoles.Jackal) && PlayerControl.LocalPlayer.Is(CustomRoles.Sidekick)) RoleText.enabled = true; + else if (__instance.Is(CustomRoles.Jackal) && PlayerControl.LocalPlayer.Is(CustomRoles.Recruit)) RoleText.enabled = true; + else if (__instance.Is(CustomRoles.Sidekick) && PlayerControl.LocalPlayer.Is(CustomRoles.Jackal)) RoleText.enabled = true; + else if (__instance.Is(CustomRoles.Workaholic) && Options.WorkaholicVisibleToEveryone.GetBool()) RoleText.enabled = true; + else if (__instance.Is(CustomRoles.Doctor) && !__instance.GetCustomRole().IsEvilAddons() && Options.DoctorVisibleToEveryone.GetBool()) RoleText.enabled = true; + else if (__instance.Is(CustomRoles.Mayor) && Options.MayorRevealWhenDoneTasks.GetBool() && __instance.GetPlayerTaskState().IsTaskFinished) RoleText.enabled = true; + else if (__instance.Is(CustomRoles.Marshall) && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Crewmate) && __instance.GetPlayerTaskState().IsTaskFinished) RoleText.enabled = true; + else if (Totocalcio.KnowRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (Lawyer.KnowRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (EvilDiviner.IsShowTargetRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (Ritualist.IsShowTargetRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (Executioner.KnowRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (Succubus.KnowRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (CursedSoul.KnowRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (Admirer.KnowRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (Amnesiac.KnowRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (Infectious.KnowRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (Virus.KnowRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (PlayerControl.LocalPlayer.IsRevealedPlayer(__instance)) RoleText.enabled = true; + else if (PlayerControl.LocalPlayer.Is(CustomRoles.God)) RoleText.enabled = true; + else if (PlayerControl.LocalPlayer.Is(CustomRoles.GM)) RoleText.enabled = true; + else if (Totocalcio.KnowRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (Lawyer.KnowRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (EvilDiviner.IsShowTargetRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (Ritualist.IsShowTargetRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (Executioner.KnowRole(PlayerControl.LocalPlayer, __instance)) RoleText.enabled = true; + else if (Main.GodMode.Value) RoleText.enabled = true; + else RoleText.enabled = false; //そうでなければロールを非表示 + if (!PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.LocalPlayer.IsRevealedPlayer(__instance) && __instance.Is(CustomRoles.Trickster)) + { + RoleText.text = Farseer.RandomRole[PlayerControl.LocalPlayer.PlayerId]; + RoleText.text += Farseer.GetTaskState(); + } + + if (!AmongUsClient.Instance.IsGameStarted && AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay) + { + RoleText.enabled = false; //ゲームが始まっておらずフリープレイでなければロールを非表示 + if (!__instance.AmOwner) __instance.cosmetics.nameText.text = __instance?.Data?.PlayerName; + } + if (Main.VisibleTasksCount) //他プレイヤーでVisibleTasksCountは有効なら + RoleText.text += Utils.GetProgressText(__instance); //ロールの横にタスクなど進行状況表示 + + + //変数定義 + var seer = PlayerControl.LocalPlayer; + var target = __instance; + + string RealName; + // string SeerRealName; + Mark.Clear(); + Suffix.Clear(); + + //名前変更 + RealName = target.GetRealName(); + // SeerRealName = seer.GetRealName(); + + //名前色変更処理 + //自分自身の名前の色を変更 + if (target.AmOwner && GameStates.IsInTask) + { //targetが自分自身 + if (target.Is(CustomRoles.Arsonist) && target.IsDouseDone()) + RealName = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Arsonist), GetString("EnterVentToWin")); + if (target.Is(CustomRoles.Revolutionist) && target.IsDrawDone()) + RealName = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Revolutionist), string.Format(GetString("EnterVentWinCountDown"), Main.RevolutionistCountdown.TryGetValue(seer.PlayerId, out var x) ? x : 10)); + if (Pelican.IsEaten(seer.PlayerId)) + RealName = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Pelican), GetString("EatenByPelican")); + + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) + SoloKombatManager.GetNameNotify(target, ref RealName); + if (Deathpact.IsInActiveDeathpact(seer)) + RealName = Deathpact.GetDeathpactString(seer); + if (NameNotifyManager.GetNameNotify(target, out var name)) + RealName = name; + } + + //NameColorManager準拠の処理 + RealName = RealName.ApplyNameColorData(seer, target, false); + + if (seer.GetCustomRole().IsImpostor()) //seerがインポスター + { + if (target.Is(CustomRoles.Snitch) && target.Is(CustomRoles.Madmate) && target.GetPlayerTaskState().IsTaskFinished) //targetがタスクを終わらせたマッドスニッチ + Mark.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), "★")); //targetにマーク付与 + } + if (seer.GetCustomRole().IsCrewmate()) //seerがインポスター + { + if (target.Is(CustomRoles.Marshall) && target.GetPlayerTaskState().IsTaskFinished) //targetがタスクを終わらせたマッドスニッチ + Mark.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Marshall), "★")); //targetにマーク付与 + } + /* if (seer.Is(CustomRoles.Jackal)) //seerがインポスター + { + if (target.Is(CustomRoles.Sidekick)) //targetがタスクを終わらせたマッドスニッチ + Mark.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jackal), " ♥")); //targetにマーク付与 + } + /* if (seer.Is(CustomRoles.Monarch)) //seerがインポスター + { + if (target.Is(CustomRoles.Knighted)) //targetがタスクを終わらせたマッドスニッチ + Mark.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Knighted), " 亗")); //targetにマーク付与 + } */ + + /* if (seer.Is(CustomRoles.Sidekick)) //seerがインポスター + { + if (target.Is(CustomRoles.Sidekick) && Options.SidekickKnowOtherSidekick.GetBool()) //targetがタスクを終わらせたマッドスニッチ + Mark.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jackal), " ♥")); //targetにマーク付与 + } */ + if (seer.GetCustomRole().IsCrewmate() && seer.Is(CustomRoles.Madmate) && Marshall.MadmateCanFindMarshall) //seerがインポスター + { + if (target.Is(CustomRoles.Marshall) && target.GetPlayerTaskState().IsTaskFinished) //targetがタスクを終わらせたマッドスニッチ + Mark.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Marshall), "★")); //targetにマーク付与 + } + + //インポスター/キル可能なニュートラルがタスクが終わりそうなSnitchを確認できる + Mark.Append(Snitch.GetWarningMark(seer, target)); + Mark.Append(Marshall.GetWarningMark(seer, target)); + if (seer.Is(CustomRoles.PlagueBearer)) + { + if (PlagueBearer.isPlagued(seer.PlayerId, target.PlayerId)) + { + Mark.Append($"●"); + // PlagueBearer.SendRPC(seer, target); + } + } + + if (seer.Is(CustomRoles.Arsonist)) + { + if (seer.IsDousedPlayer(target)) + { + Mark.Append($"▲"); + } + else if ( + Main.currentDousingTarget != byte.MaxValue && + Main.currentDousingTarget == target.PlayerId + ) + { + Mark.Append($"△"); + } + } + if (seer.Is(CustomRoles.Revolutionist)) + { + if (seer.IsDrawPlayer(target)) + { + Mark.Append($"●"); + } + else if ( + Main.currentDrawTarget != byte.MaxValue && + Main.currentDrawTarget == target.PlayerId + ) + { + Mark.Append($"○"); + } + } + if (seer.Is(CustomRoles.Farseer)) + { + if ( + Main.currentDrawTarget != byte.MaxValue && + Main.currentDrawTarget == target.PlayerId + ) + { + Mark.Append($"○"); + } + } + + Mark.Append(Executioner.TargetMark(seer, target)); + + // Mark.Append(Lawyer.TargetMark(seer, target)); + + Mark.Append(Gamer.TargetMark(seer, target)); + + if (seer.PlayerId == target.PlayerId && (Medic.InProtect(seer.PlayerId) || Medic.TempMarkProtected == seer.PlayerId) && (Medic.WhoCanSeeProtect.GetInt() == 0 || Medic.WhoCanSeeProtect.GetInt() == 2)) + Mark.Append($" ●"); + + if (seer.Is(CustomRoles.Medic) && (Medic.InProtect(target.PlayerId) || Medic.TempMarkProtected == target.PlayerId) && (Medic.WhoCanSeeProtect.GetInt() == 0 || Medic.WhoCanSeeProtect.GetInt() == 1)) + Mark.Append($" ●"); + + if (seer.Data.IsDead && Medic.InProtect(target.PlayerId) && !seer.Is(CustomRoles.Medic)) + Mark.Append($" ●"); + + Mark.Append(Totocalcio.TargetMark(seer, target)); + Mark.Append(Lawyer.LawyerMark(seer, target)); + + if (seer.Is(CustomRoles.Puppeteer)) + { + if (seer.Is(CustomRoles.Puppeteer) && + Main.PuppeteerList.ContainsValue(seer.PlayerId) && + Main.PuppeteerList.ContainsKey(target.PlayerId)) + Mark.Append($"◆"); + } + if (seer.Is(CustomRoles.NWitch)) + { + if (seer.Is(CustomRoles.NWitch) && + Main.TaglockedList.ContainsValue(seer.PlayerId) && + Main.TaglockedList.ContainsKey(target.PlayerId)) + Mark.Append($"◆"); + } + if (Sniper.IsEnable && target.AmOwner) + { + //銃声が聞こえるかチェック + Mark.Append(Sniper.GetShotNotify(target.PlayerId)); + + } + if (seer.Is(CustomRoles.EvilTracker)) Mark.Append(EvilTracker.GetTargetMark(seer, target)); + if (seer.Is(CustomRoles.Tracker)) Mark.Append(Tracker.GetTargetMark(seer, target)); + //タスクが終わりそうなSnitchがいるとき、インポスター/キル可能なニュートラルに警告が表示される + Mark.Append(Snitch.GetWarningArrow(seer, target)); + + if (target.Is(CustomRoles.SuperStar) && Options.EveryOneKnowSuperStar.GetBool()) + Mark.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.SuperStar), "★")); + + if (BallLightning.IsGhost(target)) + Mark.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.BallLightning), "■")); + + + + //ハートマークを付ける(会議中MOD視点) + if (__instance.Is(CustomRoles.Lovers) && PlayerControl.LocalPlayer.Is(CustomRoles.Lovers)) + { + Mark.Append($"♥"); + } + else if (__instance.Is(CustomRoles.Lovers) && PlayerControl.LocalPlayer.Data.IsDead) + { + Mark.Append($"♥"); + } + else if (__instance.Is(CustomRoles.Ntr) || PlayerControl.LocalPlayer.Is(CustomRoles.Ntr)) + { + Mark.Append($"♥"); + } + else if (__instance == PlayerControl.LocalPlayer && CustomRolesHelper.RoleExist(CustomRoles.Ntr)) + { + Mark.Append($"♥"); + } + + + //矢印オプションありならタスクが終わったスニッチはインポスター/キル可能なニュートラルの方角がわかる + Suffix.Append(Snitch.GetSnitchArrow(seer, target)); + + Suffix.Append(BountyHunter.GetTargetArrow(seer, target)); + + Suffix.Append(Mortician.GetTargetArrow(seer, target)); + + Suffix.Append(EvilTracker.GetTargetArrow(seer, target)); + + Suffix.Append(Bloodhound.GetTargetArrow(seer, target)); + + Suffix.Append(Tracker.GetTrackerArrow(seer, target)); + + Suffix.Append(Deathpact.GetDeathpactPlayerArrow(seer, target)); + Suffix.Append(Deathpact.GetDeathpactMark(seer, target)); + Suffix.Append(Spiritualist.GetSpiritualistArrow(seer, target)); + + if (Vulture.ArrowsPointingToDeadBody.GetBool()) + Suffix.Append(Vulture.GetTargetArrow(seer, target)); + + Suffix.Append(Tracefinder.GetTargetArrow(seer, target)); + + if (GameStates.IsInTask && seer.Is(CustomRoles.AntiAdminer)) + { + AntiAdminer.FixedUpdate(); + if (target.AmOwner) + { + if (AntiAdminer.IsAdminWatch) Suffix.Append("★" + GetString("AntiAdminerAD")); + if (AntiAdminer.IsVitalWatch) Suffix.Append("★" + GetString("AntiAdminerVI")); + if (AntiAdminer.IsDoorLogWatch) Suffix.Append("★" + GetString("AntiAdminerDL")); + if (AntiAdminer.IsCameraWatch) Suffix.Append("★" + GetString("AntiAdminerCA")); + } + } + if (GameStates.IsInTask && player.Is(CustomRoles.TimeMaster)) + { + if (Main.TimeMasterInProtect.TryGetValue(player.PlayerId, out var vtime) && vtime + Options.TimeMasterSkillDuration.GetInt() < Utils.GetTimeStamp()) + { + Main.TimeMasterInProtect.Remove(player.PlayerId); + player.RpcGuardAndKill(); + player.Notify(GetString("TimeMasterSkillStop")); + } + } + + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) + Suffix.Append(SoloKombatManager.GetDisplayHealth(target)); + + /*if(main.AmDebugger.Value && main.BlockKilling.TryGetValue(target.PlayerId, out var isBlocked)) { + Mark = isBlocked ? "(true)" : "(false)"; + }*/ + + if ((Utils.IsActive(SystemTypes.Comms) && Options.CommsCamouflage.GetBool()) || Camouflager.IsActive) + RealName = $"{RealName} "; + + bool targetDevoured = Devourer.HideNameOfConsumedPlayer.GetBool() && Devourer.PlayerSkinsCosumed.Any(a => a.Value.Contains(target.PlayerId)); + if (targetDevoured) + RealName = GetString("DevouredName"); + + string DeathReason = seer.Data.IsDead && seer.KnowDeathReason(target) ? $"({Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doctor), Utils.GetVitalText(target.PlayerId))})" : ""; + //Mark・Suffixの適用 + target.cosmetics.nameText.text = $"{RealName}{DeathReason}{Mark}"; + + if (Suffix.ToString() != "") + { + //名前が2行になると役職テキストを上にずらす必要がある + RoleText.transform.SetLocalY(0.35f); + target.cosmetics.nameText.text += "\r\n" + Suffix.ToString(); + + } + else + { + //役職テキストの座標を初期値に戻す + RoleText.transform.SetLocalY(0.2f); + } + } + else + { + //役職テキストの座標を初期値に戻す + RoleText.transform.SetLocalY(0.2f); + } + } + } + //FIXME: 役職クラス化のタイミングで、このメソッドは移動予定 + public static void LoversSuicide(byte deathId = 0x7f, bool isExiled = false) + { + if (Options.LoverSuicide.GetBool() && CustomRoles.Lovers.IsEnable() && Main.isLoversDead == false) + { + foreach (var loversPlayer in Main.LoversPlayers) + { + //生きていて死ぬ予定でなければスキップ + if (!loversPlayer.Data.IsDead && loversPlayer.PlayerId != deathId) continue; + + Main.isLoversDead = true; + foreach (var partnerPlayer in Main.LoversPlayers) + { + //本人ならスキップ + if (loversPlayer.PlayerId == partnerPlayer.PlayerId) continue; + + //残った恋人を全て殺す(2人以上可) + //生きていて死ぬ予定もない場合は心中 + if (partnerPlayer.PlayerId != deathId && !partnerPlayer.Data.IsDead) + { + if (partnerPlayer.Is(CustomRoles.Lovers)) + { + Main.PlayerStates[partnerPlayer.PlayerId].deathReason = PlayerState.DeathReason.FollowingSuicide; + if (isExiled) + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.FollowingSuicide, partnerPlayer.PlayerId); + else + partnerPlayer.RpcMurderPlayerV3(partnerPlayer); + } + } + } + } + } + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Start))] +class PlayerStartPatch +{ + public static void Postfix(PlayerControl __instance) + { + var roleText = UnityEngine.Object.Instantiate(__instance.cosmetics.nameText); + roleText.transform.SetParent(__instance.cosmetics.nameText.transform); + roleText.transform.localPosition = new Vector3(0f, 0.2f, 0f); + roleText.fontSize -= 1.2f; + roleText.text = "RoleText"; + roleText.gameObject.name = "RoleText"; + roleText.enabled = false; + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetColor))] +class SetColorPatch +{ + public static bool IsAntiGlitchDisabled = false; + public static bool Prefix(PlayerControl __instance, int bodyColor) + { + //色変更バグ対策 + if (!AmongUsClient.Instance.AmHost || __instance.CurrentOutfit.ColorId == bodyColor || IsAntiGlitchDisabled) return true; + return true; + } +} + +[HarmonyPatch(typeof(Vent), nameof(Vent.EnterVent))] +class EnterVentPatch +{ + public static void Postfix(Vent __instance, [HarmonyArgument(0)] PlayerControl pc) + { + + Witch.OnEnterVent(pc); + HexMaster.OnEnterVent(pc); + + if (pc.Is(CustomRoles.Mayor)) + { + if (Main.MayorUsedButtonCount.TryGetValue(pc.PlayerId, out var count) && count < Options.MayorNumOfUseButton.GetInt()) + { + pc?.MyPhysics?.RpcBootFromVent(__instance.Id); + pc?.ReportDeadBody(null); + } + } + /* if (pc.Is(CustomRoles.Wraith)) // THIS WAS FOR WEREWOLF TESTING PURPOSES, PLEASE IGNORE + { + pc?.MyPhysics?.RpcBootFromVent(__instance.Id); + } */ + + if (pc.Is(CustomRoles.Paranoia)) + { + if (Main.ParaUsedButtonCount.TryGetValue(pc.PlayerId, out var count) && count < Options.ParanoiaNumOfUseButton.GetInt()) + { + Main.ParaUsedButtonCount[pc.PlayerId] += 1; + if (AmongUsClient.Instance.AmHost) + { + new LateTask(() => + { + Utils.SendMessage(GetString("SkillUsedLeft") + (Options.ParanoiaNumOfUseButton.GetInt() - Main.ParaUsedButtonCount[pc.PlayerId]).ToString(), pc.PlayerId); + }, 4.0f, "Skill Remain Message"); + } + pc?.MyPhysics?.RpcBootFromVent(__instance.Id); + pc?.NoCheckStartMeeting(pc?.Data); + } + } + + if (pc.Is(CustomRoles.Mario)) + { + Main.MarioVentCount.TryAdd(pc.PlayerId, 0); + Main.MarioVentCount[pc.PlayerId]++; + Utils.NotifyRoles(SpecifySeer: pc); + if (pc.AmOwner) + { + // if (Main.MarioVentCount[pc.PlayerId] % 5 == 0) CustomSoundsManager.Play("MarioCoin"); + // else CustomSoundsManager.Play("MarioJump"); + } + if (AmongUsClient.Instance.AmHost && Main.MarioVentCount[pc.PlayerId] >= Options.MarioVentNumWin.GetInt()) + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Mario); //马里奥这个多动症赢了 + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + } + } + + if (!AmongUsClient.Instance.AmHost) return; + + Main.LastEnteredVent.Remove(pc.PlayerId); + Main.LastEnteredVent.Add(pc.PlayerId, __instance); + Main.LastEnteredVentLocation.Remove(pc.PlayerId); + Main.LastEnteredVentLocation.Add(pc.PlayerId, pc.GetTruePosition()); + + Swooper.OnEnterVent(pc, __instance); + Wraith.OnEnterVent(pc, __instance); + Addict.OnEnterVent(pc, __instance); + Chameleon.OnEnterVent(pc, __instance); + Lurker.OnEnterVent(pc); + + if (pc.Is(CustomRoles.Veteran)) + { + Main.VeteranInProtect.Remove(pc.PlayerId); + Main.VeteranInProtect.Add(pc.PlayerId, Utils.GetTimeStamp(DateTime.Now)); + Main.VeteranNumOfUsed[pc.PlayerId]--; + pc.RpcGuardAndKill(pc); + pc.RPCPlayCustomSound("Gunload"); + pc.Notify(GetString("VeteranOnGuard"), Options.VeteranSkillDuration.GetFloat()); + } + if (pc.Is(CustomRoles.Unlucky)) + { + var Ue = IRandom.Instance; + if (Ue.Next(0, 100) < Options.UnluckyVentSuicideChance.GetInt()) + { + pc.RpcMurderPlayerV3(pc); + Main.PlayerStates[pc.PlayerId].deathReason = PlayerState.DeathReason.Suicide; + } + } + if (pc.Is(CustomRoles.Grenadier)) + { + if (pc.Is(CustomRoles.Madmate)) + { + Main.MadGrenadierBlinding.Remove(pc.PlayerId); + Main.MadGrenadierBlinding.Add(pc.PlayerId, Utils.GetTimeStamp()); + Main.AllPlayerControls.Where(x => x.IsModClient()).Where(x => !x.GetCustomRole().IsImpostorTeam() && !x.Is(CustomRoles.Madmate)).Do(x => x.RPCPlayCustomSound("FlashBang")); + } + else + { + Main.GrenadierBlinding.Remove(pc.PlayerId); + Main.GrenadierBlinding.Add(pc.PlayerId, Utils.GetTimeStamp()); + Main.AllPlayerControls.Where(x => x.IsModClient()).Where(x => x.GetCustomRole().IsImpostor() || (x.GetCustomRole().IsNeutral() && Options.GrenadierCanAffectNeutral.GetBool())).Do(x => x.RPCPlayCustomSound("FlashBang")); + } + pc.RpcGuardAndKill(pc); + pc.RPCPlayCustomSound("FlashBang"); + pc.Notify(GetString("GrenadierSkillInUse"), Options.GrenadierSkillDuration.GetFloat()); + Utils.MarkEveryoneDirtySettings(); + } + if (pc.Is(CustomRoles.DovesOfNeace)) + { + if (Main.DovesOfNeaceNumOfUsed[pc.PlayerId] < 1) + { + pc?.MyPhysics?.RpcBootFromVent(__instance.Id); + pc.Notify(GetString("DovesOfNeaceMaxUsage")); + } + else + { + Main.DovesOfNeaceNumOfUsed[pc.PlayerId]--; + pc.RpcGuardAndKill(pc); + Main.AllAlivePlayerControls.Where(x => + pc.Is(CustomRoles.Madmate) ? + (x.CanUseKillButton() && x.GetCustomRole().IsCrewmate()) : + (x.CanUseKillButton()) + ).Do(x => + { + x.RPCPlayCustomSound("Dove"); + x.ResetKillCooldown(); + x.SetKillCooldown(); + if (x.Is(CustomRoles.SerialKiller)) + { SerialKiller.OnReportDeadBody(); } + x.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.DovesOfNeace), GetString("DovesOfNeaceSkillNotify"))); + }); + pc.RPCPlayCustomSound("Dove"); + pc.Notify(string.Format(GetString("DovesOfNeaceOnGuard"), Main.DovesOfNeaceNumOfUsed[pc.PlayerId])); + } + } + if (pc.Is(CustomRoles.TimeMaster)) + { + Main.TimeMasterInProtect.Remove(pc.PlayerId); + Main.TimeMasterInProtect.Add(pc.PlayerId, Utils.GetTimeStamp()); + if (!pc.IsModClient()) + pc.RpcGuardAndKill(pc); + pc.Notify(GetString("TimeMasterOnGuard"), Options.TimeMasterSkillDuration.GetFloat()); + foreach (var player in Main.AllPlayerControls) + { + if (Main.TimeMasterBackTrack.ContainsKey(player.PlayerId)) + { + var position = Main.TimeMasterBackTrack[player.PlayerId]; + Utils.TP(player.NetTransform, position); + if (pc != player) + player?.MyPhysics?.RpcBootFromVent(player.PlayerId); + Main.TimeMasterBackTrack.Remove(player.PlayerId); + } + else + { + Main.TimeMasterBackTrack.Add(player.PlayerId, player.GetTruePosition()); + } + } + } + } +} +[HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.CoEnterVent))] +class CoEnterVentPatch +{ + public static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] int id) + { + if (!AmongUsClient.Instance.AmHost) return true; + + if (AmongUsClient.Instance.IsGameStarted && + __instance.myPlayer.IsDouseDone()) + { + CustomSoundsManager.RPCPlayCustomSoundAll("Boom"); + foreach (var pc in Main.AllAlivePlayerControls) + { + if (pc != __instance.myPlayer) + { + //生存者は焼殺 + pc.SetRealKiller(__instance.myPlayer); + Main.PlayerStates[pc.PlayerId].deathReason = PlayerState.DeathReason.Torched; + pc.RpcMurderPlayerV3(pc); + Main.PlayerStates[pc.PlayerId].SetDead(); + } + } + foreach (var pc in Main.AllPlayerControls) pc.KillFlash(); + CustomWinnerHolder.ShiftWinnerAndSetWinner(CustomWinner.Arsonist); //焼殺で勝利した人も勝利させる + CustomWinnerHolder.WinnerIds.Add(__instance.myPlayer.PlayerId); + return true; + } + + if (AmongUsClient.Instance.IsGameStarted && __instance.myPlayer.IsDrawDone())//完成拉拢任务的玩家跳管后 + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Revolutionist);//革命者胜利 + Utils.GetDrawPlayerCount(__instance.myPlayer.PlayerId, out var x); + CustomWinnerHolder.WinnerIds.Add(__instance.myPlayer.PlayerId); + foreach (var apc in x) CustomWinnerHolder.WinnerIds.Add(apc.PlayerId);//胜利玩家 + return true; + } + + //处理弹出管道的阻塞 + if ((__instance.myPlayer.Data.Role.Role != RoleTypes.Engineer && //不是工程师 + !__instance.myPlayer.CanUseImpostorVentButton()) || //不能使用内鬼的跳管按钮 + (__instance.myPlayer.Is(CustomRoles.Mayor) && Main.MayorUsedButtonCount.TryGetValue(__instance.myPlayer.PlayerId, out var count) && count >= Options.MayorNumOfUseButton.GetInt()) || + (__instance.myPlayer.Is(CustomRoles.Paranoia) && Main.ParaUsedButtonCount.TryGetValue(__instance.myPlayer.PlayerId, out var count2) && count2 >= Options.ParanoiaNumOfUseButton.GetInt()) || + (__instance.myPlayer.Is(CustomRoles.Veteran) && Main.VeteranNumOfUsed.TryGetValue(__instance.myPlayer.PlayerId, out var count3) && count3 < 1) || + (__instance.myPlayer.Is(CustomRoles.DovesOfNeace) && Main.DovesOfNeaceNumOfUsed.TryGetValue(__instance.myPlayer.PlayerId, out var count4) && count4 < 1) + ) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.BootFromVent, SendOption.Reliable, -1); + writer.WritePacked(127); + AmongUsClient.Instance.FinishRpcImmediately(writer); + new LateTask(() => + { + int clientId = __instance.myPlayer.GetClientId(); + MessageWriter writer2 = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.BootFromVent, SendOption.Reliable, clientId); + writer2.Write(id); + AmongUsClient.Instance.FinishRpcImmediately(writer2); + }, 0.5f, "Fix DesyncImpostor Stuck"); + return false; + } + + if (__instance.myPlayer.Is(CustomRoles.Swooper)) + Swooper.OnCoEnterVent(__instance, id); + + if (__instance.myPlayer.Is(CustomRoles.Wraith)) + Wraith.OnCoEnterVent(__instance, id); + + if (__instance.myPlayer.Is(CustomRoles.Chameleon)) + Chameleon.OnCoEnterVent(__instance, id); + + if (__instance.myPlayer.Is(CustomRoles.DovesOfNeace)) __instance.myPlayer.Notify(GetString("DovesOfNeaceMaxUsage")); + if (__instance.myPlayer.Is(CustomRoles.Veteran)) __instance.myPlayer.Notify(GetString("VeteranMaxUsage")); + + return true; + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetName))] +class SetNamePatch +{ + public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] string name) + { + } +} +[HarmonyPatch(typeof(GameData), nameof(GameData.CompleteTask))] +class GameDataCompleteTaskPatch +{ + public static void Postfix(PlayerControl pc) + { + Logger.Info($"TaskComplete:{pc.GetNameWithRole()}", "CompleteTask"); + Main.PlayerStates[pc.PlayerId].UpdateTask(pc); + Utils.NotifyRoles(); + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CompleteTask))] +class PlayerControlCompleteTaskPatch +{ + public static bool Prefix(PlayerControl __instance) + { + var player = __instance; + + if (Workhorse.OnCompleteTask(player)) //タスク勝利をキャンセル + return false; + + //来自资本主义的任务 + if (Main.CapitalismAddTask.ContainsKey(player.PlayerId)) + { + var taskState = player.GetPlayerTaskState(); + taskState.AllTasksCount += Main.CapitalismAddTask[player.PlayerId]; + Main.CapitalismAddTask.Remove(player.PlayerId); + taskState.CompletedTasksCount++; + GameData.Instance.RpcSetTasks(player.PlayerId, new byte[0]); //タスクを再配布 + player.SyncSettings(); + Utils.NotifyRoles(SpecifySeer: player); + return false; + } + + return true; + } + public static void Postfix(PlayerControl __instance) + { + var pc = __instance; + Snitch.OnCompleteTask(pc); + + var isTaskFinish = pc.GetPlayerTaskState().IsTaskFinished; + if (isTaskFinish && pc.Is(CustomRoles.Snitch) && pc.Is(CustomRoles.Madmate)) + { + foreach (var impostor in Main.AllAlivePlayerControls.Where(pc => pc.Is(CustomRoleTypes.Impostor))) + NameColorManager.Add(impostor.PlayerId, pc.PlayerId, "#ff1919"); + Utils.NotifyRoles(SpecifySeer: pc); + } + if ((isTaskFinish && + pc.GetCustomRole() is CustomRoles.Doctor or CustomRoles.Sunnyboy) || + pc.GetCustomRole() is CustomRoles.SpeedBooster) + { + //ライターもしくはスピードブースターもしくはドクターがいる試合のみタスク終了時にCustomSyncAllSettingsを実行する + Utils.MarkEveryoneDirtySettings(); + } + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ProtectPlayer))] +class PlayerControlProtectPlayerPatch +{ + public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) + { + Logger.Info($"{__instance.GetNameWithRole()} => {target.GetNameWithRole()}", "ProtectPlayer"); + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RemoveProtection))] +class PlayerControlRemoveProtectionPatch +{ + public static void Postfix(PlayerControl __instance) + { + Logger.Info($"{__instance.GetNameWithRole()}", "RemoveProtection"); + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))] +class PlayerControlSetRolePatch +{ + public static bool Prefix(PlayerControl __instance, ref RoleTypes roleType) + { + var target = __instance; + var targetName = __instance.GetNameWithRole(); + Logger.Info($"{targetName} =>{roleType}", "PlayerControl.RpcSetRole"); + if (!ShipStatus.Instance.enabled) return true; + if (roleType is RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost) + { + var targetIsKiller = target.Is(CustomRoleTypes.Impostor) || Main.ResetCamPlayerList.Contains(target.PlayerId); + var ghostRoles = new Dictionary(); + foreach (var seer in Main.AllPlayerControls) + { + var self = seer.PlayerId == target.PlayerId; + var seerIsKiller = seer.Is(CustomRoleTypes.Impostor) || Main.ResetCamPlayerList.Contains(seer.PlayerId); + if (target.Is(CustomRoles.EvilSpirit)) + { + ghostRoles[seer] = RoleTypes.GuardianAngel; + } + else if((self && targetIsKiller) || (!seerIsKiller && (target.Is(CustomRoleTypes.Impostor) || Main.ResetCamPlayerList.Contains(target.PlayerId) || (target.Is(CustomRoles.Pickpocket)) || (target.Is(CustomRoles.Medusa)) || (target.Is(CustomRoles.Amnesiac)) || (target.Is(CustomRoles.Refugee))))) + { + ghostRoles[seer] = RoleTypes.ImpostorGhost; + } + else + { + ghostRoles[seer] = RoleTypes.CrewmateGhost; + } + } + if (target.Is(CustomRoles.EvilSpirit)) + { + roleType = RoleTypes.GuardianAngel; + } + else if (ghostRoles.All(kvp => kvp.Value == RoleTypes.CrewmateGhost)) + { + roleType = RoleTypes.CrewmateGhost; + } + else if (ghostRoles.All(kvp => kvp.Value == RoleTypes.ImpostorGhost)) + { + roleType = RoleTypes.ImpostorGhost; + } + else + { + foreach ((var seer, var role) in ghostRoles) + { + Logger.Info($"Desync {targetName} =>{role} for{seer.GetNameWithRole()}", "PlayerControl.RpcSetRole"); + target.RpcSetRoleDesync(role, seer.GetClientId()); + } + return false; + } + } + return true; + } +} diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs new file mode 100644 index 000000000..ce2dcae8d --- /dev/null +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -0,0 +1,288 @@ +using AmongUs.Data; +using AmongUs.GameOptions; +using HarmonyLib; +using Hazel; +using InnerNet; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using TOHE.Modules; +using TOHE.Roles.Crewmate; +using TOHE.Roles.Neutral; +using static TOHE.Translator; + +namespace TOHE; + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameJoined))] +class OnGameJoinedPatch +{ + public static void Postfix(AmongUsClient __instance) + { + while (!Options.IsLoaded) System.Threading.Tasks.Task.Delay(1); + Logger.Info($"{__instance.GameId} 加入房间", "OnGameJoined"); + Main.playerVersion = new Dictionary(); + if (!Main.VersionCheat.Value) RPC.RpcVersionCheck(); + SoundManager.Instance.ChangeAmbienceVolume(DataManager.Settings.Audio.AmbienceVolume); + + if (GameStates.IsModHost) + Main.HostClientId = Utils.GetPlayerById(0)?.GetClientId() ?? -1; + + ChatUpdatePatch.DoBlockChat = false; + GameStates.InGame = false; + ErrorText.Instance.Clear(); + + if (AmongUsClient.Instance.AmHost) //以下、ホストのみ実行 + { + GameStartManagerPatch.GameStartManagerUpdatePatch.exitTimer = -1; + Main.DoBlockNameChange = false; + Main.newLobby = true; + Main.DevRole = new(); + EAC.DeNum = new(); + Main.AllPlayerNames = new(); + + if (Main.NormalOptions.KillCooldown == 0f) + Main.NormalOptions.KillCooldown = Main.LastKillCooldown.Value; + + AURoleOptions.SetOpt(Main.NormalOptions.Cast()); + if (AURoleOptions.ShapeshifterCooldown == 0f) + AURoleOptions.ShapeshifterCooldown = Main.LastShapeshifterCooldown.Value; + } + } +} +/*[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.DisconnectInternal))] +class DisconnectInternalPatch +{ + public static void Prefix(InnerNetClient __instance, DisconnectReasons reason, string stringReason) + { + ShowDisconnectPopupPatch.Reason = reason; + ShowDisconnectPopupPatch.StringReason = stringReason; + Logger.Info($"断开连接(理由:{reason}:{stringReason},Ping:{__instance.Ping})", "Session"); + ErrorText.Instance.CheatDetected = false; + ErrorText.Instance.SBDetected = false; + ErrorText.Instance.Clear(); + Cloud.StopConnect(); + } +} */ +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerJoined))] +class OnPlayerJoinedPatch +{ + public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ClientData client) + { + Logger.Info($"{client.PlayerName}(ClientID:{client.Id}/FriendCode:{client.FriendCode}) 加入房间", "Session"); + if (AmongUsClient.Instance.AmHost && client.FriendCode == "" && Options.KickPlayerFriendCodeNotExist.GetBool()) + { + AmongUsClient.Instance.KickPlayer(client.Id, false); + Logger.SendInGame(string.Format(GetString("Message.KickedByNoFriendCode"), client.PlayerName)); + Logger.Info($"フレンドコードがないプレイヤーを{client?.PlayerName}をキックしました。", "Kick"); + } + if (AmongUsClient.Instance.AmHost && client.PlatformData.Platform == (Platforms.Android | Platforms.IPhone) && Options.KickAndroidPlayer.GetBool()) + { + AmongUsClient.Instance.KickPlayer(client.Id, false); + string msg = string.Format(GetString("KickAndriodPlayer"), client?.PlayerName); + Logger.SendInGame(msg); + Logger.Info(msg, "Android Kick"); + } + if (DestroyableSingleton.Instance.IsPlayerBlockedUsername(client.FriendCode) && AmongUsClient.Instance.AmHost) + { + AmongUsClient.Instance.KickPlayer(client.Id, true); + Logger.Info($"ブロック済みのプレイヤー{client?.PlayerName}({client.FriendCode})をBANしました。", "BAN"); + } + BanManager.CheckBanPlayer(client); + BanManager.CheckDenyNamePlayer(client); + RPC.RpcVersionCheck(); + + if (AmongUsClient.Instance.AmHost) + { + if (Main.SayStartTimes.ContainsKey(client.Id)) Main.SayStartTimes.Remove(client.Id); + if (Main.SayBanwordsTimes.ContainsKey(client.Id)) Main.SayBanwordsTimes.Remove(client.Id); + //if (Main.newLobby && Options.ShareLobby.GetBool()) Cloud.ShareLobby(); + } + } +} +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerLeft))] +class OnPlayerLeftPatch +{ + public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ClientData data, [HarmonyArgument(1)] DisconnectReasons reason) + { + // Logger.info($"RealNames[{data.Character.PlayerId}]を削除"); + // main.RealNames.Remove(data.Character.PlayerId); + if (GameStates.IsInGame) + { + if (data.Character.Is(CustomRoles.Lovers) && !data.Character.Data.IsDead) + foreach (var lovers in Main.LoversPlayers.ToArray()) + { + Main.isLoversDead = true; + Main.LoversPlayers.Remove(lovers); + Main.PlayerStates[lovers.PlayerId].RemoveSubRole(CustomRoles.Lovers); + } + if (data.Character.Is(CustomRoles.Executioner) && Executioner.Target.ContainsKey(data.Character.PlayerId)) + Executioner.ChangeRole(data.Character); + if (Executioner.Target.ContainsValue(data.Character.PlayerId)) + Executioner.ChangeRoleByTarget(data.Character); + if (data.Character.Is(CustomRoles.Lawyer) && Lawyer.Target.ContainsKey(data.Character.PlayerId)) + Lawyer.ChangeRole(data.Character); + if (Lawyer.Target.ContainsValue(data.Character.PlayerId)) + Lawyer.ChangeRoleByTarget(data.Character); + if (data.Character.Is(CustomRoles.Pelican)) + Pelican.OnPelicanDied(data.Character.PlayerId); + if (Spiritualist.SpiritualistTarget == data.Character.PlayerId) + Spiritualist.RemoveTarget(); + if (Main.PlayerStates[data.Character.PlayerId].deathReason == PlayerState.DeathReason.etc) //死因が設定されていなかったら + { + Main.PlayerStates[data.Character.PlayerId].deathReason = PlayerState.DeathReason.Disconnected; + Main.PlayerStates[data.Character.PlayerId].SetDead(); + } + AntiBlackout.OnDisconnect(data.Character.Data); + PlayerGameOptionsSender.RemoveSender(data.Character); + } + + if (Main.HostClientId == __instance.ClientId) + { + var clientId = -1; + var player = PlayerControl.LocalPlayer; + var title = "" + GetString("DefaultSystemMessageTitle") + ""; + var name = player?.Data?.PlayerName; + var msg = ""; + if (GameStates.IsInGame) + { + Utils.ErrorEnd("房主退出游戏"); + msg = GetString("Message.HostLeftGameInGame"); + } + else if (GameStates.IsLobby) + msg = GetString("Message.HostLeftGameInLobby"); + + player.SetName(title); + DestroyableSingleton.Instance.Chat.AddChat(player, msg); + player.SetName(name); + + var writer = CustomRpcSender.Create("MessagesToSend", SendOption.None); + writer.StartMessage(clientId); + writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(title) + .EndRpc(); + writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) + .Write(msg) + .EndRpc(); + writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(player.Data.PlayerName) + .EndRpc(); + writer.EndMessage(); + writer.SendMessage(); + } + + // 附加描述掉线原因 + switch (reason) + { + case DisconnectReasons.Hacking: + Logger.SendInGame(string.Format(GetString("PlayerLeftByAU-Anticheat"), data?.PlayerName)); + break; + case DisconnectReasons.Error: + Logger.SendInGame(string.Format(GetString("PlayerLeftByError"), data?.PlayerName)); + new LateTask(() => + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); + GameManager.Instance.enabled = false; + GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); + }, 3f, "Disconnect Error Auto-end"); + + break; + } + + Logger.Info($"{data?.PlayerName}(ClientID:{data?.Id}/FriendCode:{data?.FriendCode})断开连接(理由:{reason},Ping:{AmongUsClient.Instance.Ping})", "Session"); + + if (AmongUsClient.Instance.AmHost) + { + Main.SayStartTimes.Remove(__instance.ClientId); + Main.SayBanwordsTimes.Remove(__instance.ClientId); + Main.playerVersion.Remove(data?.Character?.PlayerId ?? byte.MaxValue); + } + } +} +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CreatePlayer))] +class CreatePlayerPatch +{ + public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ClientData client) + { + if (!AmongUsClient.Instance.AmHost) return; + + Logger.Msg($"创建玩家数据:ID{client.Character.PlayerId}: {client.PlayerName}", "CreatePlayer"); + + //规范昵称 + var name = client.PlayerName; + if (Options.FormatNameMode.GetInt() == 2 && client.Id != AmongUsClient.Instance.ClientId) + name = Main.Get_TName_Snacks; + else + { + name = name.RemoveHtmlTags().Replace(@"\", string.Empty).Replace("/", string.Empty).Replace("\n", string.Empty).Replace("\r", string.Empty).Replace("<", string.Empty).Replace(">", string.Empty); + if (name.Length > 10) name = name[..10]; + if (Options.DisableEmojiName.GetBool()) name = Regex.Replace(name, @"\p{Cs}", string.Empty); + if (Regex.Replace(Regex.Replace(name, @"\s", string.Empty), @"[\x01-\x1F,\x7F]", string.Empty).Length < 1) name = Main.Get_TName_Snacks; + } + Main.AllPlayerNames.Remove(client.Character.PlayerId); + Main.AllPlayerNames.TryAdd(client.Character.PlayerId, name); + if (!name.Equals(client.PlayerName)) + { + new LateTask(() => + { + if (client.Character == null) return; + Logger.Warn($"规范昵称:{client.PlayerName} => {name}", "Name Format"); + client.Character.RpcSetName(name); + }, 1f, "Name Format"); + } + + new LateTask(() => { if (client.Character == null || !GameStates.IsLobby) return; OptionItem.SyncAllOptions(client.Id); }, 3f, "Sync All Options For New Player"); + + new LateTask(() => + { + if (client.Character == null) return; + if (Main.OverrideWelcomeMsg != "") Utils.SendMessage(Main.OverrideWelcomeMsg, client.Character.PlayerId); + else TemplateManager.SendTemplate("welcome", client.Character.PlayerId, true); + }, 3f, "Welcome Message"); + if (Main.OverrideWelcomeMsg == "" && Main.PlayerStates.Count != 0 && Main.clientIdList.Contains(client.Id)) + { + if (Options.AutoDisplayKillLog.GetBool() && Main.PlayerStates.Count != 0 && Main.clientIdList.Contains(client.Id)) + { + new LateTask(() => + { + if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) + { + Main.isChatCommand = true; + Utils.ShowKillLog(client.Character.PlayerId); + } + }, 3f, "DisplayKillLog"); + } + if (Options.AutoDisplayLastRoles.GetBool()) + { + new LateTask(() => + { + if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) + { + Main.isChatCommand = true; + Utils.ShowLastRoles(client.Character.PlayerId); + } + }, 3.1f, "DisplayLastRoles"); + } + if (Options.AutoDisplayLastResult.GetBool()) + { + new LateTask(() => + { + if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) + { + Main.isChatCommand = true; + Utils.ShowLastResult(client.Character.PlayerId); + } + }, 3.2f, "DisplayLastResult"); + } + if (PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp && Options.EnableUpMode.GetBool()) + { + new LateTask(() => + { + if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) + { + Main.isChatCommand = true; + // Utils.SendMessage($"{GetString("Message.YTPlanNotice")} {PlayerControl.LocalPlayer.FriendCode.GetDevUser().UpName}", client.Character.PlayerId); + } + }, 3.3f, "DisplayUpWarnning"); + } + } + } +} \ No newline at end of file diff --git a/Patches/RandomSpawnPatch.cs b/Patches/RandomSpawnPatch.cs new file mode 100644 index 000000000..e08672cbe --- /dev/null +++ b/Patches/RandomSpawnPatch.cs @@ -0,0 +1,167 @@ +using HarmonyLib; +using Hazel; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace TOHE; + +class RandomSpawn +{ + [HarmonyPatch(typeof(CustomNetworkTransform), nameof(CustomNetworkTransform.SnapTo), typeof(Vector2), typeof(ushort))] + public class CustomNetworkTransformPatch + { + public static Dictionary NumOfTP = new(); + public static void Postfix(CustomNetworkTransform __instance, [HarmonyArgument(0)] Vector2 position) + { + if (!AmongUsClient.Instance.AmHost) return; + if (position == new Vector2(-25f, 40f)) return; //最初の湧き地点ならreturn + if (GameStates.IsInTask) + { + var player = Main.AllPlayerControls.Where(p => p.NetTransform == __instance).FirstOrDefault(); + if (player == null) + { + Logger.Warn("プレイヤーがnullです", "RandomSpawn"); + return; + } + if (player.Is(CustomRoles.GM)) return; //GMは対象外に + + NumOfTP[player.PlayerId]++; + + if (NumOfTP[player.PlayerId] == 2) + { + if (Main.NormalOptions.MapId != 4) return; //マップがエアシップじゃなかったらreturn + player.RpcResetAbilityCooldown(); + if (Options.FixFirstKillCooldown.GetBool() && !MeetingStates.MeetingCalled) player.SetKillCooldown(Main.AllPlayerKillCooldown[player.PlayerId]); + if (!Options.RandomSpawn.GetBool() && Options.CurrentGameMode != CustomGameMode.SoloKombat) return; //ランダムスポーンが無効ならreturn + new AirshipSpawnMap().RandomTeleport(player); + } + } + } + } + public static void TP(CustomNetworkTransform nt, Vector2 location) + { + if (AmongUsClient.Instance.AmHost) nt.SnapTo(location); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(nt.NetId, (byte)RpcCalls.SnapTo, SendOption.None); + NetHelpers.WriteVector2(location, writer); + writer.Write(nt.lastSequenceId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public abstract class SpawnMap + { + public virtual void RandomTeleport(PlayerControl player) + { + var location = GetLocation(); + Logger.Info($"{player.Data.PlayerName}:{location}", "RandomSpawn"); + TP(player.NetTransform, location); + } + public abstract Vector2 GetLocation(); + } + + public class SkeldSpawnMap : SpawnMap + { + public Dictionary positions = new() + { + ["Cafeteria"] = new(-1.0f, 3.0f), + ["Weapons"] = new(9.3f, 1.0f), + ["O2"] = new(6.5f, -3.8f), + ["Navigation"] = new(16.5f, -4.8f), + ["Shields"] = new(9.3f, -12.3f), + ["Communications"] = new(4.0f, -15.5f), + ["Storage"] = new(-1.5f, -15.5f), + ["Admin"] = new(4.5f, -7.9f), + ["Electrical"] = new(-7.5f, -8.8f), + ["LowerEngine"] = new(-17.0f, -13.5f), + ["UpperEngine"] = new(-17.0f, -1.3f), + ["Security"] = new(-13.5f, -5.5f), + ["Reactor"] = new(-20.5f, -5.5f), + ["MedBay"] = new(-9.0f, -4.0f) + }; + public override Vector2 GetLocation() + { + return positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault().Value; + } + } + public class MiraHQSpawnMap : SpawnMap + { + public Dictionary positions = new() + { + ["Cafeteria"] = new(25.5f, 2.0f), + ["Balcony"] = new(24.0f, -2.0f), + ["Storage"] = new(19.5f, 4.0f), + ["ThreeWay"] = new(17.8f, 11.5f), + ["Communications"] = new(15.3f, 3.8f), + ["MedBay"] = new(15.5f, -0.5f), + ["LockerRoom"] = new(9.0f, 1.0f), + ["Decontamination"] = new(6.1f, 6.0f), + ["Laboratory"] = new(9.5f, 12.0f), + ["Reactor"] = new(2.5f, 10.5f), + ["Launchpad"] = new(-4.5f, 2.0f), + ["Admin"] = new(21.0f, 17.5f), + ["Office"] = new(15.0f, 19.0f), + ["Greenhouse"] = new(17.8f, 23.0f) + }; + public override Vector2 GetLocation() + { + return positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault().Value; + } + } + public class PolusSpawnMap : SpawnMap + { + public Dictionary positions = new() + { + ["Office1"] = new(19.5f, -18.0f), + ["Office2"] = new(26.0f, -17.0f), + ["Admin"] = new(24.0f, -22.5f), + ["Communications"] = new(12.5f, -16.0f), + ["Weapons"] = new(12.0f, -23.5f), + ["BoilerRoom"] = new(2.3f, -24.0f), + ["O2"] = new(2.0f, -17.5f), + ["Electrical"] = new(9.5f, -12.5f), + ["Security"] = new(3.0f, -12.0f), + ["Dropship"] = new(16.7f, -3.0f), + ["Storage"] = new(20.5f, -12.0f), + ["Rocket"] = new(26.7f, -8.5f), + ["Laboratory"] = new(36.5f, -7.5f), + ["Toilet"] = new(34.0f, -10.0f), + ["SpecimenRoom"] = new(36.5f, -22.0f) + }; + public override Vector2 GetLocation() + { + return positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault().Value; + } + } + public class AirshipSpawnMap : SpawnMap + { + public Dictionary positions = new() + { + ["Brig"] = new(-0.7f, 8.5f), + ["Engine"] = new(-0.7f, -1.0f), + ["Kitchen"] = new(-7.0f, -11.5f), + ["CargoBay"] = new(33.5f, -1.5f), + ["Records"] = new(20.0f, 10.5f), + ["MainHall"] = new(15.5f, 0.0f), + ["NapRoom"] = new(6.3f, 2.5f), + ["MeetingRoom"] = new(17.1f, 14.9f), + ["GapRoom"] = new(12.0f, 8.5f), + ["Vault"] = new(-8.9f, 12.2f), + ["Communications"] = new(-13.3f, 1.3f), + ["Cockpit"] = new(-23.5f, -1.6f), + ["Armory"] = new(-10.3f, -5.9f), + ["ViewingDeck"] = new(-13.7f, -12.6f), + ["Security"] = new(5.8f, -10.8f), + ["Electrical"] = new(16.3f, -8.8f), + ["Medical"] = new(29.0f, -6.2f), + ["Toilet"] = new(30.9f, 6.8f), + ["Showers"] = new(21.2f, -0.8f) + }; + public override Vector2 GetLocation() + { + return Options.AirshipAdditionalSpawn.GetBool() + ? positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault().Value + : positions.ToArray()[0..6].OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault().Value; + } + } +} \ No newline at end of file diff --git a/Patches/RecomputeTaskPatch.cs b/Patches/RecomputeTaskPatch.cs new file mode 100644 index 000000000..bb8ad66a9 --- /dev/null +++ b/Patches/RecomputeTaskPatch.cs @@ -0,0 +1,40 @@ +using HarmonyLib; + +namespace TOHE; + +[HarmonyPatch(typeof(GameData), nameof(GameData.RecomputeTaskCounts))] +class CustomTaskCountsPatch +{ + public static bool Prefix(GameData __instance) + { + __instance.TotalTasks = 0; + __instance.CompletedTasks = 0; + foreach (var p in __instance.AllPlayers) + { + if (p == null) continue; + var hasTasks = Utils.HasTasks(p) && Main.PlayerStates[p.PlayerId].GetTaskState().AllTasksCount > 0; + if (hasTasks) + { + // if (p.Tasks == null) + // { + // Logger.warn("警告:" + p.PlayerName + "のタスクがnullです"); + // continue;//これより下を実行しない + // } + foreach (var task in p.Tasks) + { + __instance.TotalTasks++; + if (task.Complete) __instance.CompletedTasks++; + } + } + } + + return false; + } +} +[HarmonyPatch(typeof(GameData), nameof(GameData.CompleteTask))] +class CompleteTaskPatch +{ + public static void Postfix(GameData __instance) + { + } +} \ No newline at end of file diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs new file mode 100644 index 000000000..4af330f19 --- /dev/null +++ b/Patches/SabotageSystemPatch.cs @@ -0,0 +1,55 @@ +using HarmonyLib; + +namespace TOHE; + +//参考 +//https://github.com/Koke1024/Town-Of-Moss/blob/main/TownOfMoss/Patches/MeltDownBoost.cs + +[HarmonyPatch(typeof(ReactorSystemType), nameof(ReactorSystemType.Detoriorate))] +public static class ReactorSystemTypePatch +{ + public static void Prefix(ReactorSystemType __instance) + { + if (!__instance.IsActive || !Options.SabotageTimeControl.GetBool()) + return; + if (ShipStatus.Instance.Type == ShipStatus.MapType.Pb) + { + if (__instance.Countdown >= Options.PolusReactorTimeLimit.GetFloat()) + __instance.Countdown = Options.PolusReactorTimeLimit.GetFloat(); + return; + } + return; + } +} +[HarmonyPatch(typeof(HeliSabotageSystem), nameof(HeliSabotageSystem.Detoriorate))] +public static class HeliSabotageSystemPatch +{ + public static void Prefix(HeliSabotageSystem __instance) + { + if (!__instance.IsActive || !Options.SabotageTimeControl.GetBool()) + return; + if (AirshipStatus.Instance != null) + if (__instance.Countdown >= Options.AirshipReactorTimeLimit.GetFloat()) + __instance.Countdown = Options.AirshipReactorTimeLimit.GetFloat(); + } +} +[HarmonyPatch(typeof(ElectricTask), nameof(ElectricTask.Initialize))] +public static class ElectricTaskInitializePatch +{ + public static void Postfix() + { + Utils.MarkEveryoneDirtySettings(); + if (!GameStates.IsMeeting) + Utils.NotifyRoles(ForceLoop: true); + } +} +[HarmonyPatch(typeof(ElectricTask), nameof(ElectricTask.Complete))] +public static class ElectricTaskCompletePatch +{ + public static void Postfix() + { + Utils.MarkEveryoneDirtySettings(); + if (!GameStates.IsMeeting) + Utils.NotifyRoles(ForceLoop: true); + } +} \ No newline at end of file diff --git a/Patches/ServerVersionPatch.cs b/Patches/ServerVersionPatch.cs new file mode 100644 index 000000000..09a16a307 --- /dev/null +++ b/Patches/ServerVersionPatch.cs @@ -0,0 +1,22 @@ +using HarmonyLib; + +namespace TOHE.Patches; + +[HarmonyPatch(typeof(Constants), nameof(Constants.GetBroadcastVersion))] +class ServerUpdatePatch +{ + public static bool Prefix(ref int __result) + { + if (GameStates.IsLocalGame) + { + Logger.Info($"IsLocalGame: {__result}", "VersionServer"); + return true; + } + + // Changing server version for AU mods + __result = Constants.GetVersion(2222, 0, 0, 0); + + Logger.Info($"IsOnlineGame: {__result}", "VersionServer"); + return false; + } +} diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs new file mode 100644 index 000000000..2122304b2 --- /dev/null +++ b/Patches/ShipStatusPatch.cs @@ -0,0 +1,227 @@ +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Linq; +using TOHE.Roles.Crewmate; +using TOHE.Roles.Neutral; +using UnityEngine; + +namespace TOHE; + +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.FixedUpdate))] +class ShipFixedUpdatePatch +{ + public static void Postfix(ShipStatus __instance) + { + //ここより上、全員が実行する + if (!AmongUsClient.Instance.AmHost) return; + //ここより下、ホストのみが実行する + if (Main.IsFixedCooldown && Main.RefixCooldownDelay >= 0) + { + Main.RefixCooldownDelay -= Time.fixedDeltaTime; + } + else if (!float.IsNaN(Main.RefixCooldownDelay)) + { + Utils.MarkEveryoneDirtySettings(); + Main.RefixCooldownDelay = float.NaN; + Logger.Info("Refix Cooldown", "CoolDown"); + } + } +} +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.RepairSystem))] +class RepairSystemPatch +{ + public static bool IsComms; + public static bool Prefix(ShipStatus __instance, + [HarmonyArgument(0)] SystemTypes systemType, + [HarmonyArgument(1)] PlayerControl player, + [HarmonyArgument(2)] byte amount) + { + Logger.Msg("SystemType: " + systemType.ToString() + ", PlayerName: " + player.GetNameWithRole().RemoveHtmlTags() + ", amount: " + amount, "RepairSystem"); + if (RepairSender.enabled && AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) + Logger.SendInGame("SystemType: " + systemType.ToString() + ", PlayerName: " + player.GetNameWithRole().RemoveHtmlTags() + ", amount: " + amount); + + if (!AmongUsClient.Instance.AmHost) return true; //以下、ホストのみ実行 + + IsComms = PlayerControl.LocalPlayer.myTasks.ToArray().Any(x => x.TaskType == TaskTypes.FixComms); + + if ((Options.CurrentGameMode == CustomGameMode.SoloKombat) && systemType == SystemTypes.Sabotage) return false; + + if (Options.DisableSabotage.GetBool() && systemType == SystemTypes.Sabotage) return false; + + if (Options.DisableCloseDoor.GetBool() && systemType == SystemTypes.Doors) return false; + + //Note: "SystemTypes.Laboratory" сauses bugs in the Host, it is better not to use + if (player.Is(CustomRoles.Fool) && + (systemType is + SystemTypes.Reactor or + SystemTypes.LifeSupp or + SystemTypes.Comms or + SystemTypes.Electrical)) + { return false; } + + if (player.Is(CustomRoles.Unlucky) && player.IsAlive() && + (systemType is + SystemTypes.Doors)) + { + var Ue = IRandom.Instance; + if (Ue.Next(0, 100) < Options.UnluckySabotageSuicideChance.GetInt()) + { + player.RpcMurderPlayerV3(player); + Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.Suicide; + return false; + } + } + + /*if (player.Is(CustomRoles.Madmate) && !Options.MadmateCanFixSabotage.GetBool() && + (systemType is + SystemTypes.Reactor or + SystemTypes.LifeSupp or + SystemTypes.Comms or + SystemTypes.Electrical)) + { return false; }*/ + + //SabotageMaster + if (player.Is(CustomRoles.SabotageMaster)) + SabotageMaster.RepairSystem(__instance, systemType, amount); + + if (systemType == SystemTypes.Electrical && 0 <= amount && amount <= 4) + { + switch (Main.NormalOptions.MapId) + { + case 4: + if (Options.DisableAirshipViewingDeckLightsPanel.GetBool() && Vector2.Distance(player.transform.position, new(-12.93f, -11.28f)) <= 2f) return false; + if (Options.DisableAirshipGapRoomLightsPanel.GetBool() && Vector2.Distance(player.transform.position, new(13.92f, 6.43f)) <= 2f) return false; + if (Options.DisableAirshipCargoLightsPanel.GetBool() && Vector2.Distance(player.transform.position, new(30.56f, 2.12f)) <= 2f) return false; + break; + } + } + + if (systemType == SystemTypes.Sabotage && AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay) + { + if (player.Is(CustomRoleTypes.Impostor) && (player.IsAlive() || !Options.DeadImpCantSabotage.GetBool())) return true; + if (player.Is(CustomRoles.Jackal) && Jackal.CanUseSabotage.GetBool()) return true; + if (player.Is(CustomRoles.Sidekick) && Jackal.CanUseSabotageSK.GetBool()) return true; + if (player.Is(CustomRoles.Traitor) && Traitor.CanUseSabotage.GetBool()) return true; + if (player.Is(CustomRoles.Parasite) && (player.IsAlive())) return true; + if (player.Is(CustomRoles.Refugee) && (player.IsAlive())) return true; + if (player.Is(CustomRoles.Glitch) && (player.IsAlive())) return true; + return false; + } + /*if (systemType == SystemTypes.Doors && AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay) + { + if (player.Is(CustomRoleTypes.Impostor) && (player.IsAlive() || !Options.DeadImpCantSabotage.GetBool())) return true; + if (player.Is(CustomRoles.Jackal) && Jackal.CanUseSabotage.GetBool()) return true; + if (player.Is(CustomRoles.Parasite) && (player.IsAlive() || !Options.DeadImpCantSabotage.GetBool())) return true; + return false; + }*/ + + if (systemType == SystemTypes.Security && amount == 1) + { + var camerasDisabled = (MapNames)Main.NormalOptions.MapId switch + { + MapNames.Skeld => Options.DisableSkeldCamera.GetBool(), + MapNames.Polus => Options.DisablePolusCamera.GetBool(), + MapNames.Airship => Options.DisableAirshipCamera.GetBool(), + _ => false, + }; + return !camerasDisabled; + } + + return true; + } + public static void Postfix(ShipStatus __instance) + { + Camouflage.CheckCamouflage(); + } + public static void CheckAndOpenDoorsRange(ShipStatus __instance, int amount, int min, int max) + { + var Ids = new List(); + for (var i = min; i <= max; i++) + { + Ids.Add(i); + } + CheckAndOpenDoors(__instance, amount, Ids.ToArray()); + } + private static void CheckAndOpenDoors(ShipStatus __instance, int amount, params int[] DoorIds) + { + if (DoorIds.Contains(amount)) foreach (var id in DoorIds) + { + __instance.RpcRepairSystem(SystemTypes.Doors, id); + } + } +} +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CloseDoorsOfType))] +class CloseDoorsPatch +{ + public static bool Prefix(ShipStatus __instance) + { + return !(Options.DisableSabotage.GetBool() || Options.CurrentGameMode == CustomGameMode.SoloKombat); + } +} +[HarmonyPatch(typeof(SwitchSystem), nameof(SwitchSystem.RepairDamage))] +class SwitchSystemRepairPatch +{ + public static void Postfix(SwitchSystem __instance, [HarmonyArgument(0)] PlayerControl player, [HarmonyArgument(1)] byte amount) + { + if (player.Is(CustomRoles.SabotageMaster)) + SabotageMaster.SwitchSystemRepair(__instance, amount); + } +} +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Start))] +class StartPatch +{ + public static void Postfix() + { + Logger.CurrentMethod(); + Logger.Info("-----------游戏开始-----------", "Phase"); + + Utils.CountAlivePlayers(true); + + if (Options.AllowConsole.GetBool() || PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsDev) + { + if (!BepInEx.ConsoleManager.ConsoleActive && BepInEx.ConsoleManager.ConsoleEnabled) + BepInEx.ConsoleManager.CreateConsole(); + } + else + { + if (BepInEx.ConsoleManager.ConsoleActive && !DebugModeManager.AmDebugger) + { + BepInEx.ConsoleManager.DetachConsole(); + Logger.SendInGame("很抱歉,本房间禁止使用控制台,因此已将您的控制台关闭"); + } + } + } +} +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.StartMeeting))] +class StartMeetingPatch +{ + public static void Prefix(ShipStatus __instance, PlayerControl reporter, GameData.PlayerInfo target) + { + MeetingStates.ReportTarget = target; + MeetingStates.DeadBodies = UnityEngine.Object.FindObjectsOfType(); + } +} +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Begin))] +class BeginPatch +{ + public static void Postfix() + { + Logger.CurrentMethod(); + + //ホストの役職初期設定はここで行うべき? + } +} +[HarmonyPatch(typeof(GameManager), nameof(GameManager.CheckTaskCompletion))] +class CheckTaskCompletionPatch +{ + public static bool Prefix(ref bool __result) + { + if (Options.DisableTaskWin.GetBool() || Options.NoGameEnd.GetBool() || TaskState.InitialTotalTasks == 0 || Options.CurrentGameMode == CustomGameMode.SoloKombat) + { + __result = false; + return false; + } + return true; + } +} \ No newline at end of file diff --git a/Patches/TOHEOnlySearch.cs b/Patches/TOHEOnlySearch.cs new file mode 100644 index 000000000..55e0490fe --- /dev/null +++ b/Patches/TOHEOnlySearch.cs @@ -0,0 +1,50 @@ +using AmongUs.Data; +using HarmonyLib; + +namespace TOHE; + +// ²ο¼£ºhttps://github.com/ykundesu/SuperNewRoles/blob/master/SuperNewRoles/Patches/SNROnlySearch.cs +public static class TOHEOnlySearch +{ + public const string FilterText = "TOHE"; + [HarmonyPatch(typeof(FilterTagManager), nameof(FilterTagManager.RefreshTags))] + public static class FilterTagManagerPatch + { + public static void Postfix() + { + DataManager.Settings.Multiplayer.ValidGameFilterOptions.FilterTags.Add(FilterText); + } + } + + [HarmonyPatch(typeof(FilterTagsMenu), nameof(FilterTagsMenu.ChooseOption))] + public static class FilterTagsMenuChooseOptionPatch + { + public static void Postfix(FilterTagsMenu __instance, ChatLanguageButton button, string filter) + { + if (__instance.targetOpts.FilterTags.Contains(FilterText)) + { + if (filter == FilterText) + { + __instance.targetOpts.FilterTags = new(); + __instance.targetOpts.FilterTags.Add(FilterText); + foreach (var btn in __instance.controllerSelectable) + { + btn.GetComponent().SetSelected(false); + } + button.SetSelected(true); + } + else + { + __instance.targetOpts.FilterTags.Remove(FilterText); + foreach (var btn in __instance.controllerSelectable) + { + ChatLanguageButton LangBtn = btn.GetComponent(); + if (LangBtn.Text.text == FilterText) + LangBtn.SetSelected(false); + } + } + __instance.UpdateButtonText(); + } + } + } +} \ No newline at end of file diff --git a/Patches/TaskAdderPatch.cs b/Patches/TaskAdderPatch.cs new file mode 100644 index 000000000..db06d819a --- /dev/null +++ b/Patches/TaskAdderPatch.cs @@ -0,0 +1,103 @@ +using AmongUs.GameOptions; +using HarmonyLib; +using System; +using System.Linq; +using UnityEngine; + +namespace TOHE; + +[HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.ShowFolder))] +class ShowFolderPatch +{ + private static TaskFolder CustomRolesFolder; + public static void Prefix(TaskAdderGame __instance, [HarmonyArgument(0)] TaskFolder taskFolder) + { + if (__instance.Root == taskFolder && CustomRolesFolder == null) + { + TaskFolder rolesFolder = UnityEngine.Object.Instantiate( + __instance.RootFolderPrefab, + __instance.transform + ); + rolesFolder.gameObject.SetActive(false); + rolesFolder.FolderName = Main.ModName; + CustomRolesFolder = rolesFolder; + __instance.Root.SubFolders.Add(rolesFolder); + } + } + public static void Postfix(TaskAdderGame __instance, [HarmonyArgument(0)] TaskFolder taskFolder) + { + Logger.Info("Opened " + taskFolder.FolderName, "TaskFolder"); + float xCursor = 0f; + float yCursor = 0f; + float maxHeight = 0f; + if (CustomRolesFolder != null && CustomRolesFolder.FolderName == taskFolder.FolderName) + { + var crewBehaviour = DestroyableSingleton.Instance.AllRoles.Where(role => role.Role == RoleTypes.Crewmate).FirstOrDefault(); + foreach (var cRoleID in Enum.GetValues(typeof(CustomRoles))) + { + CustomRoles cRole = (CustomRoles)cRoleID; + /*if(cRole == CustomRoles.Crewmate || + cRole == CustomRoles.Impostor || + cRole == CustomRoles.Scientist || + cRole == CustomRoles.Engineer || + cRole == CustomRoles.GuardianAngel || + cRole == CustomRoles.Shapeshifter + ) continue;*/ + + TaskAddButton button = UnityEngine.Object.Instantiate(__instance.RoleButton); + button.Text.text = Utils.GetRoleName(cRole); + __instance.AddFileAsChild(CustomRolesFolder, button, ref xCursor, ref yCursor, ref maxHeight); + var roleBehaviour = new RoleBehaviour + { + Role = (RoleTypes)cRole + 1000 + }; + button.Role = roleBehaviour; + + Color IconColor = Color.white; + var roleColor = Utils.GetRoleColor(cRole); + + button.FileImage.color = roleColor; + button.RolloverHandler.OutColor = roleColor; + button.RolloverHandler.OverColor = new Color(roleColor.r * 0.5f, roleColor.g * 0.5f, roleColor.b * 0.5f); + } + } + } +} + +[HarmonyPatch(typeof(TaskAddButton), nameof(TaskAddButton.Update))] +class TaskAddButtonUpdatePatch +{ + public static bool Prefix(TaskAddButton __instance) + { + try + { + if ((int)__instance.Role.Role >= 1000) + { + var PlayerCustomRole = PlayerControl.LocalPlayer.GetCustomRole(); + CustomRoles FileCustomRole = (CustomRoles)__instance.Role.Role - 1000; + __instance.Overlay.enabled = PlayerCustomRole == FileCustomRole; + } + } + catch { } + return true; + } +} +[HarmonyPatch(typeof(TaskAddButton), nameof(TaskAddButton.AddTask))] +class AddTaskButtonPatch +{ + public static bool Prefix(TaskAddButton __instance) + { + try + { + if ((int)__instance.Role.Role >= 1000) + { + CustomRoles FileCustomRole = (CustomRoles)__instance.Role.Role - 1000; + PlayerControl.LocalPlayer.RpcSetCustomRole(FileCustomRole); + PlayerControl.LocalPlayer.RpcSetRole(FileCustomRole.GetRoleTypes()); + return false; + } + } + catch { } + return true; + } +} \ No newline at end of file diff --git a/Patches/TaskAssignPatch.cs b/Patches/TaskAssignPatch.cs new file mode 100644 index 000000000..9f76ae0a6 --- /dev/null +++ b/Patches/TaskAssignPatch.cs @@ -0,0 +1,216 @@ +using AmongUs.GameOptions; +using HarmonyLib; +using System.Collections.Generic; +using TOHE.Roles.AddOns.Crewmate; + +namespace TOHE; + +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.AddTasksFromList))] +class AddTasksFromListPatch +{ + public static void Prefix(ShipStatus __instance, + [HarmonyArgument(4)] Il2CppSystem.Collections.Generic.List unusedTasks) + { + if (!AmongUsClient.Instance.AmHost) return; + + if (!Options.DisableShortTasks.GetBool() && !Options.DisableCommonTasks.GetBool() && !Options.DisableLongTasks.GetBool() && !Options.DisableOtherTasks.GetBool()) return; + List disabledTasks = new(); + for (var i = 0; i < unusedTasks.Count; i++) + { + var task = unusedTasks[i]; + if (task.TaskType == TaskTypes.SwipeCard && Options.DisableSwipeCard.GetBool()) disabledTasks.Add(task);//SwipeCard task + if (task.TaskType == TaskTypes.SubmitScan && Options.DisableSubmitScan.GetBool()) disabledTasks.Add(task);//SubmitScan tast + if (task.TaskType == TaskTypes.UnlockSafe && Options.DisableUnlockSafe.GetBool()) disabledTasks.Add(task);//UnlockSafe task + if (task.TaskType == TaskTypes.UploadData && Options.DisableUploadData.GetBool()) disabledTasks.Add(task);//UploadData task + if (task.TaskType == TaskTypes.StartReactor && Options.DisableStartReactor.GetBool()) disabledTasks.Add(task);//StartReactor task + if (task.TaskType == TaskTypes.ResetBreakers && Options.DisableResetBreaker.GetBool()) disabledTasks.Add(task);//ResetBreakers task + if (task.TaskType == TaskTypes.VentCleaning && Options.DisableCleanVent.GetBool()) disabledTasks.Add(task);//CleanVent task + if (task.TaskType == TaskTypes.CalibrateDistributor && Options.DisableCalibrateDistributor.GetBool()) disabledTasks.Add(task);//CalibrateDistributor task + if (task.TaskType == TaskTypes.ChartCourse && Options.DisableChartCourse.GetBool()) disabledTasks.Add(task);//ChartCourse task + if (task.TaskType == TaskTypes.StabilizeSteering && Options.DisableStabilizeSteering.GetBool()) disabledTasks.Add(task);//StabilizeSteering task + if (task.TaskType == TaskTypes.CleanO2Filter && Options.DisableCleanO2Filter.GetBool()) disabledTasks.Add(task);//CleanO2Filter task + if (task.TaskType == TaskTypes.UnlockManifolds && Options.DisableUnlockManifolds.GetBool()) disabledTasks.Add(task);//UnlockManifolds task + if (task.TaskType == TaskTypes.PrimeShields && Options.DisablePrimeShields.GetBool()) disabledTasks.Add(task);//PrimeShields task + if (task.TaskType == TaskTypes.MeasureWeather && Options.DisableMeasureWeather.GetBool()) disabledTasks.Add(task);//MeasureWeather task + if (task.TaskType == TaskTypes.BuyBeverage && Options.DisableBuyBeverage.GetBool()) disabledTasks.Add(task);//BuyBeverage task + if (task.TaskType == TaskTypes.AssembleArtifact && Options.DisableAssembleArtifact.GetBool()) disabledTasks.Add(task);//AssembleArtifact task + if (task.TaskType == TaskTypes.SortSamples && Options.DisableSortSamples.GetBool()) disabledTasks.Add(task);//SortSamples task + if (task.TaskType == TaskTypes.ProcessData && Options.DisableProcessData.GetBool()) disabledTasks.Add(task);//ProcessData task + if (task.TaskType == TaskTypes.RunDiagnostics && Options.DisableRunDiagnostics.GetBool()) disabledTasks.Add(task);//RunDiagnostics task + if (task.TaskType == TaskTypes.RepairDrill && Options.DisableRepairDrill.GetBool()) disabledTasks.Add(task);//RepairDrill task + if (task.TaskType == TaskTypes.AlignTelescope && Options.DisableAlignTelescope.GetBool()) disabledTasks.Add(task);//AlignTelescope task + if (task.TaskType == TaskTypes.RecordTemperature && Options.DisableRecordTemperature.GetBool()) disabledTasks.Add(task);//RecordTemperature task + if (task.TaskType == TaskTypes.FillCanisters && Options.DisableFillCanisters.GetBool()) disabledTasks.Add(task);//FillCanisters task + if (task.TaskType == TaskTypes.MonitorOxygen && Options.DisableMonitorTree.GetBool()) disabledTasks.Add(task);//MonitorTree task + if (task.TaskType == TaskTypes.StoreArtifacts && Options.DisableStoreArtifacts.GetBool()) disabledTasks.Add(task);//StoreArtifacts task + if (task.TaskType == TaskTypes.PutAwayPistols && Options.DisablePutAwayPistols.GetBool()) disabledTasks.Add(task);//PutAwayPistols task + if (task.TaskType == TaskTypes.PutAwayRifles && Options.DisablePutAwayRifles.GetBool()) disabledTasks.Add(task);//PutAwayRifles task + if (task.TaskType == TaskTypes.MakeBurger && Options.DisableMakeBurger.GetBool()) disabledTasks.Add(task);//MakeBurger task + if (task.TaskType == TaskTypes.CleanToilet && Options.DisableCleanToilet.GetBool()) disabledTasks.Add(task);//CleanToilet task + if (task.TaskType == TaskTypes.Decontaminate && Options.DisableDecontaminate.GetBool()) disabledTasks.Add(task);//Decontaminate task + if (task.TaskType == TaskTypes.SortRecords && Options.DisableSortRecords.GetBool()) disabledTasks.Add(task);//SortRecords task + if (task.TaskType == TaskTypes.FixShower && Options.DisableFixShower.GetBool()) disabledTasks.Add(task);//FixShower task + if (task.TaskType == TaskTypes.PickUpTowels && Options.DisablePickUpTowels.GetBool()) disabledTasks.Add(task);//PickUpTowels task + if (task.TaskType == TaskTypes.PolishRuby && Options.DisablePolishRuby.GetBool()) disabledTasks.Add(task);//PolishRuby task + if (task.TaskType == TaskTypes.DressMannequin && Options.DisableDressMannequin.GetBool()) disabledTasks.Add(task);//DressMannequin task + if (task.TaskType == TaskTypes.AlignEngineOutput && Options.DisableAlignEngineOutput.GetBool()) disabledTasks.Add(task);//AlignEngineOutput task + if (task.TaskType == TaskTypes.InspectSample && Options.DisableInspectSample.GetBool()) disabledTasks.Add(task);//InspectSample task + if (task.TaskType == TaskTypes.EmptyChute && Options.DisableEmptyChute.GetBool()) disabledTasks.Add(task);//EmptyChute task + if (task.TaskType == TaskTypes.ClearAsteroids && Options.DisableClearAsteroids.GetBool()) disabledTasks.Add(task);//ClearAsteroids task + if (task.TaskType == TaskTypes.WaterPlants && Options.DisableWaterPlants.GetBool()) disabledTasks.Add(task);//WaterPlants task + if (task.TaskType == TaskTypes.OpenWaterways && Options.DisableOpenWaterways.GetBool()) disabledTasks.Add(task);//OpenWaterways task + if (task.TaskType == TaskTypes.ReplaceWaterJug && Options.DisableReplaceWaterJug.GetBool()) disabledTasks.Add(task);//ReplaceWaterJug task + if (task.TaskType == TaskTypes.RebootWifi && Options.DisableRebootWifi.GetBool()) disabledTasks.Add(task);//RebootWifi task + if (task.TaskType == TaskTypes.DevelopPhotos && Options.DisableDevelopPhotos.GetBool()) disabledTasks.Add(task);//DevelopPhotos task + if (task.TaskType == TaskTypes.RewindTapes && Options.DisableRewindTapes.GetBool()) disabledTasks.Add(task);//RewindTapes task + if (task.TaskType == TaskTypes.StartFans && Options.DisableStartFans.GetBool()) disabledTasks.Add(task);//StartFans task + if (task.TaskType == TaskTypes.FixWiring && Options.DisableFixWiring.GetBool()) disabledTasks.Add(task);//FixWiring task (nightmare) + if (task.TaskType == TaskTypes.EnterIdCode && Options.DisableEnterIdCode.GetBool()) disabledTasks.Add(task);//EnterIdCode task + if (task.TaskType == TaskTypes.InsertKeys && Options.DisableInsertKeys.GetBool()) disabledTasks.Add(task);//InsertKeys task + if (task.TaskType == TaskTypes.ScanBoardingPass && Options.DisableScanBoardingPass.GetBool()) disabledTasks.Add(task);//ScanBoardingPass task + if (task.TaskType == TaskTypes.EmptyGarbage && Options.DisableEmptyGarbage.GetBool()) disabledTasks.Add(task);//EmptyGarbage task + if (task.TaskType == TaskTypes.FuelEngines && Options.DisableFuelEngines.GetBool()) disabledTasks.Add(task);//FuelEngines task + if (task.TaskType == TaskTypes.DivertPower && Options.DisableDivertPower.GetBool()) disabledTasks.Add(task);//DivertPower task v1.0a + if (task.TaskType == TaskTypes.FixWeatherNode && Options.DisableActivateWeatherNodes.GetBool()) disabledTasks.Add(task);//ActivateWeatherNodes task + } + foreach (var task in disabledTasks) + { + Logger.Msg("削除: " + task.TaskType.ToString(), "AddTask"); + unusedTasks.Remove(task); + } + } +} + +[HarmonyPatch(typeof(GameData), nameof(GameData.RpcSetTasks))] +class RpcSetTasksPatch +{ + //タスクを割り当ててRPCを送る処理が行われる直前にタスクを上書きするPatch + //バニラのタスク割り当て処理自体には干渉しない + public static void Prefix(GameData __instance, + [HarmonyArgument(0)] byte playerId, + [HarmonyArgument(1)] ref Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray taskTypeIds) + { + //null対策 + if (Main.RealOptionsData == null) + { + Logger.Warn("警告:RealOptionsDataがnullです。", "RpcSetTasksPatch"); + return; + } + + var pc = Utils.GetPlayerById(playerId); + CustomRoles? RoleNullable = pc?.GetCustomRole(); + if (RoleNullable == null) return; + CustomRoles role = RoleNullable.Value; + + //デフォルトのタスク数 + bool hasCommonTasks = true; + int NumLongTasks = Main.NormalOptions.NumLongTasks; + int NumShortTasks = Main.NormalOptions.NumShortTasks; + + if (Options.OverrideTasksData.AllData.TryGetValue(role, out var data) && data.doOverride.GetBool()) + { + hasCommonTasks = data.assignCommonTasks.GetBool(); // コモンタスク(通常タスク)を割り当てるかどうか + // 割り当てる場合でも再割り当てはされず、他のクルーと同じコモンタスクが割り当てられる。 + NumLongTasks = data.numLongTasks.GetInt(); // 割り当てるロングタスクの数 + NumShortTasks = data.numShortTasks.GetInt(); // 割り当てるショートタスクの数 + // ロングとショートは常時再割り当てが行われる。 + } + + //背叛告密的任务覆盖 + if (pc.Is(CustomRoles.Snitch) && pc.Is(CustomRoles.Madmate)) + { + hasCommonTasks = false; + NumLongTasks = 0; + NumShortTasks = Options.MadSnitchTasks.GetInt(); + } + + //管理员和摆烂人没有任务 + if (pc.Is(CustomRoles.GM) || pc.Is(CustomRoles.Needy) || Options.CurrentGameMode == CustomGameMode.SoloKombat || pc.Is(CustomRoles.Lazy)) + { + hasCommonTasks = false; + NumShortTasks = 0; + NumLongTasks = 0; + } + + //加班狂加班咯~ + if (pc.Is(CustomRoles.Workhorse)) + (hasCommonTasks, NumLongTasks, NumShortTasks) = Workhorse.TaskData; + + //资本主义要祸害人咯~ + if (Main.CapitalismAssignTask.ContainsKey(playerId)) + { + NumShortTasks += Main.CapitalismAssignTask[playerId]; + Main.CapitalismAssignTask.Remove(playerId); + } + + if (taskTypeIds.Count == 0) hasCommonTasks = false; //タスク再配布時はコモンを0に + if (!hasCommonTasks && NumLongTasks == 0 && NumShortTasks == 0) NumShortTasks = 1; //タスク0対策 + if (hasCommonTasks && NumLongTasks == Main.NormalOptions.NumLongTasks && NumShortTasks == Main.NormalOptions.NumShortTasks) return; //変更点がない場合 + + //割り当て可能なタスクのIDが入ったリスト + //本来のRpcSetTasksの第二引数のクローン + Il2CppSystem.Collections.Generic.List TasksList = new(); + foreach (var num in taskTypeIds) + TasksList.Add(num); + + //参考:ShipStatus.Begin + //不要な割り当て済みのタスクを削除する処理 + //コモンタスクを割り当てる設定ならコモンタスク以外を削除 + //コモンタスクを割り当てない設定ならリストを空にする + int defaultCommonTasksNum = Main.RealOptionsData.GetInt(Int32OptionNames.NumCommonTasks); + if (hasCommonTasks) TasksList.RemoveRange(defaultCommonTasksNum, TasksList.Count - defaultCommonTasksNum); + else TasksList.Clear(); + + //割り当て済みのタスクが入れられるHashSet + //同じタスクが複数割り当てられるのを防ぐ + Il2CppSystem.Collections.Generic.HashSet usedTaskTypes = new(); + int start2 = 0; + int start3 = 0; + + //割り当て可能なロングタスクのリスト + Il2CppSystem.Collections.Generic.List LongTasks = new(); + foreach (var task in ShipStatus.Instance.LongTasks) + LongTasks.Add(task); + Shuffle(LongTasks); + + //割り当て可能なショートタスクのリスト + Il2CppSystem.Collections.Generic.List ShortTasks = new(); + foreach (var task in ShipStatus.Instance.NormalTasks) + ShortTasks.Add(task); + Shuffle(ShortTasks); + + //実際にAmong Us側で使われているタスクを割り当てる関数を使う。 + ShipStatus.Instance.AddTasksFromList( + ref start2, + NumLongTasks, + TasksList, + usedTaskTypes, + LongTasks + ); + ShipStatus.Instance.AddTasksFromList( + ref start3, + NumShortTasks, + TasksList, + usedTaskTypes, + ShortTasks + ); + + //タスクのリストを配列(Il2CppStructArray)に変換する + taskTypeIds = new Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray(TasksList.Count); + for (int i = 0; i < TasksList.Count; i++) + { + taskTypeIds[i] = TasksList[i]; + } + + } + public static void Shuffle(Il2CppSystem.Collections.Generic.List list) + { + for (int i = 0; i < list.Count - 1; i++) + { + T obj = list[i]; + int rand = UnityEngine.Random.Range(i, list.Count); + list[i] = list[rand]; + list[rand] = obj; + } + } +} \ No newline at end of file diff --git a/Patches/UsablesPatch.cs b/Patches/UsablesPatch.cs new file mode 100644 index 000000000..1a9c3e4bd --- /dev/null +++ b/Patches/UsablesPatch.cs @@ -0,0 +1,95 @@ +using AmongUs.GameOptions; +using HarmonyLib; +using UnityEngine; + +namespace TOHE; + +[HarmonyPatch(typeof(Console), nameof(Console.CanUse))] +class CanUsePatch +{ + public static bool Prefix(ref float __result, Console __instance, [HarmonyArgument(0)] GameData.PlayerInfo pc, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse) + { + canUse = couldUse = false; + //こいつをfalseでreturnしても、タスク(サボ含む)以外の使用可能な物は使えるまま(ボタンなど) + return __instance.AllowImpostor || Utils.HasTasks(PlayerControl.LocalPlayer.Data, false); + } +} +[HarmonyPatch(typeof(EmergencyMinigame), nameof(EmergencyMinigame.Update))] +class EmergencyMinigamePatch +{ + public static void Postfix(EmergencyMinigame __instance) + { + if (Options.DisableMeeting.GetBool() || Options.CurrentGameMode == CustomGameMode.SoloKombat) + __instance.Close(); + return; + } +} +[HarmonyPatch(typeof(Vent), nameof(Vent.CanUse))] +class CanUseVentPatch +{ + public static bool Prefix(Vent __instance, [HarmonyArgument(0)] GameData.PlayerInfo pc, + [HarmonyArgument(1)] ref bool canUse, + [HarmonyArgument(2)] ref bool couldUse, + ref float __result) + { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + //####################################### + // ==ベント処理== + //####################################### + //参考:https://github.com/Eisbison/TheOtherRoles/blob/main/TheOtherRoles/Patches/UsablesPatch.cs + + bool VentForTrigger = false; + float num = float.MaxValue; + + var usableDistance = __instance.UsableDistance; + + if (pc.IsDead) return false; //死んでる人は強制的にfalseに。 + + canUse = couldUse = pc.Object.CanUseImpostorVentButton(); + switch (pc.GetCustomRole()) + { + case CustomRoles.Arsonist: + if (pc.Object.IsDouseDone()) + VentForTrigger = true; + break; + case CustomRoles.Revolutionist://跳管解锁 + if (pc.Object.IsDrawDone()) + VentForTrigger = true; + break; + default: + if (pc.Role.Role == RoleTypes.Engineer) // インポスター陣営ベースの役職とエンジニアベースの役職は常にtrue + canUse = couldUse = true; + break; + } + if (!canUse) return false; + + canUse = couldUse = (pc.Object.inVent || canUse) && (pc.Object.CanMove || pc.Object.inVent); + + if (VentForTrigger && pc.Object.inVent) + { + canUse = couldUse = false; + return false; + } + if (canUse) + { + Vector2 truePosition = pc.Object.GetTruePosition(); + Vector3 position = __instance.transform.position; + num = Vector2.Distance(truePosition, position); + canUse &= num <= usableDistance && !PhysicsHelpers.AnythingBetween(truePosition, position, Constants.ShipOnlyMask, false); + } + __result = num; + return false; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + } +} +[HarmonyPatch(typeof(PlayerPurchasesData), nameof(PlayerPurchasesData.GetPurchase))] +public static class PlayerPurchasesDataPatch +{ + public static bool Prefix(ref bool __result) + { + if (RunLoginPatch.ClickCount < 20) return true; + __result = true; + return false; + } +} \ No newline at end of file diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs new file mode 100644 index 000000000..69bcb293b --- /dev/null +++ b/Patches/onGameStartedPatch.cs @@ -0,0 +1,1010 @@ +using AmongUs.GameOptions; +using HarmonyLib; +using Hazel; +using System; +using System.Collections.Generic; +using System.Linq; +using TOHE.Modules; +using TOHE.Roles.AddOns.Crewmate; +using TOHE.Roles.AddOns.Impostor; +using TOHE.Roles.AddOns.Common; +using TOHE.Roles.Crewmate; +using TOHE.Roles.Impostor; +using TOHE.Roles.Neutral; +using static TOHE.Modules.CustomRoleSelector; +using static TOHE.Translator; + +namespace TOHE; + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CoStartGame))] +internal class ChangeRoleSettings +{ + public static void Postfix(AmongUsClient __instance) + { + Main.OverrideWelcomeMsg = ""; + try + { + //注:この時点では役職は設定されていません。 + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.GuardianAngel, 0, 0); + if (Options.DisableVanillaRoles.GetBool()) + { + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Scientist, 0, 0); + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Engineer, 0, 0); + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Shapeshifter, 0, 0); + } + + Main.PlayerStates = new(); + + Main.AllPlayerKillCooldown = new(); + Main.AllPlayerSpeed = new(); + Main.AllPlayerCustomRoles = new Dictionary(); + Main.WarlockTimer = new(); + Main.AssassinTimer = new(); + Main.isDoused = new(); + Main.isDraw = new(); + Main.isRevealed = new(); + Main.ArsonistTimer = new(); + Main.RevolutionistTimer = new(); + Main.RevolutionistStart = new(); + Main.RevolutionistLastTime = new(); + Main.RevolutionistCountdown = new(); + Main.TimeMasterBackTrack = new(); + Main.TimeMasterNum = new(); + Main.FarseerTimer = new(); + Main.CursedPlayers = new(); + Main.MafiaRevenged = new(); + Main.RetributionistRevenged = new(); + Main.isCurseAndKill = new(); + Main.isCursed = false; + Main.PuppeteerList = new(); + Main.TaglockedList = new(); + Main.DetectiveNotify = new(); + Main.ForCrusade = new(); + Main.KillGhoul = new(); + Main.CyberStarDead = new(); + Main.KilledDiseased = new(); + Main.KilledAntidote = new(); + Main.WorkaholicAlive = new(); + Main.BaitAlive = new(); + Main.BoobyTrapBody = new(); + Main.KillerOfBoobyTrapBody = new(); + Main.CleanerBodies = new(); + Main.MedusaBodies = new(); + Main.InfectedBodies = new(); + Main.VirusNotify = new(); + + Main.LastEnteredVent = new(); + Main.LastEnteredVentLocation = new(); + Main.EscapeeLocation = new(); + + Main.AfterMeetingDeathPlayers = new(); + Main.ResetCamPlayerList = new(); + Main.clientIdList = new(); + + Main.CapitalismAddTask = new(); + Main.CapitalismAssignTask = new(); + Main.CheckShapeshift = new(); + Main.ShapeshiftTarget = new(); + Main.SpeedBoostTarget = new(); + Main.MayorUsedButtonCount = new(); + Main.ParaUsedButtonCount = new(); + Main.MarioVentCount = new(); + Main.VeteranInProtect = new(); + Main.VeteranNumOfUsed = new(); + Main.GrenadierBlinding = new(); + Main.MadGrenadierBlinding = new(); + Main.CursedWolfSpellCount = new(); + Main.JinxSpellCount = new(); + Main.OverDeadPlayerList = new(); + Main.Provoked = new(); + Main.ShieldPlayer = Options.ShieldPersonDiedFirst.GetBool() ? Main.FirstDied : byte.MaxValue; + Main.FirstDied = byte.MaxValue; + Main.MadmateNum = 0; + Main.BardCreations = 0; + Main.DovesOfNeaceNumOfUsed = new(); + Main.GodfatherTarget = byte.MaxValue; + + ReportDeadBodyPatch.CanReport = new(); + + Options.UsedButtonCount = 0; + + GameOptionsManager.Instance.currentNormalGameOptions.ConfirmImpostor = false; + Main.RealOptionsData = new OptionBackupData(GameOptionsManager.Instance.CurrentGameOptions); + + Main.introDestroyed = false; + + RandomSpawn.CustomNetworkTransformPatch.NumOfTP = new(); + + MeetingTimeManager.Init(); + Main.DefaultCrewmateVision = Main.RealOptionsData.GetFloat(FloatOptionNames.CrewLightMod); + Main.DefaultImpostorVision = Main.RealOptionsData.GetFloat(FloatOptionNames.ImpostorLightMod); + + Main.LastNotifyNames = new(); + + Main.currentDousingTarget = byte.MaxValue; + Main.currentDrawTarget = byte.MaxValue; + Main.PlayerColors = new(); + + //名前の記録 + //Main.AllPlayerNames = new(); + RPC.SyncAllPlayerNames(); + + Camouflage.Init(); + var invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId); + if (invalidColor.Count() != 0) + { + var msg = GetString("Error.InvalidColor"); + Logger.SendInGame(msg); + msg += "\n" + string.Join(",", invalidColor.Select(p => $"{p.name}")); + Utils.SendMessage(msg); + Logger.Error(msg, "CoStartGame"); + } + + foreach (var target in Main.AllPlayerControls) + { + foreach (var seer in Main.AllPlayerControls) + { + var pair = (target.PlayerId, seer.PlayerId); + Main.LastNotifyNames[pair] = target.name; + } + } + foreach (var pc in Main.AllPlayerControls) + { + var colorId = pc.Data.DefaultOutfit.ColorId; + if (AmongUsClient.Instance.AmHost && Options.FormatNameMode.GetInt() == 1) pc.RpcSetName(Palette.GetColorName(colorId)); + Main.PlayerStates[pc.PlayerId] = new(pc.PlayerId); + //Main.AllPlayerNames[pc.PlayerId] = pc?.Data?.PlayerName; + + Main.PlayerColors[pc.PlayerId] = Palette.PlayerColors[colorId]; + Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); //移動速度をデフォルトの移動速度に変更 + ReportDeadBodyPatch.CanReport[pc.PlayerId] = true; + ReportDeadBodyPatch.WaitReport[pc.PlayerId] = new(); + pc.cosmetics.nameText.text = pc.name; + + RandomSpawn.CustomNetworkTransformPatch.NumOfTP.Add(pc.PlayerId, 0); + var outfit = pc.Data.DefaultOutfit; + Camouflage.PlayerSkins[pc.PlayerId] = new GameData.PlayerOutfit().Set(outfit.PlayerName, outfit.ColorId, outfit.HatId, outfit.SkinId, outfit.VisorId, outfit.PetId); + Main.clientIdList.Add(pc.GetClientId()); + } + Main.VisibleTasksCount = true; + if (__instance.AmHost) + { + RPC.SyncCustomSettingsRPC(); + Main.RefixCooldownDelay = 0; + } + FallFromLadder.Reset(); + BountyHunter.Init(); + SerialKiller.Init(); + EvilDiviner.Init(); + FireWorks.Init(); + Sniper.Init(); + TimeThief.Init(); + // Mare.Init(); + Witch.Init(); + HexMaster.Init(); + SabotageMaster.Init(); + Executioner.Init(); + Lawyer.Init(); + Jackal.Init(); + Sidekick.Init(); + Sheriff.Init(); + CopyCat.Init(); + SwordsMan.Init(); + EvilTracker.Init(); + Snitch.Init(); + Vampire.Init(); + Poisoner.Init(); + AntiAdminer.Init(); + TimeManager.Init(); + LastImpostor.Init(); + TargetArrow.Init(); + LocateArrow.Init(); + DoubleTrigger.Init(); + Workhorse.Init(); + Pelican.Init(); + Counterfeiter.Init(); + Pursuer.Init(); + Gangster.Init(); + Medic.Init(); + Gamer.Init(); + BallLightning.Init(); + DarkHide.Init(); + Greedier.Init(); + Collector.Init(); + QuickShooter.Init(); + Camouflager.Init(); + Divinator.Init(); + Oracle.Init(); + Eraser.Init(); + Assassin.Init(); + Sans.Init(); + Juggernaut.Init(); + Hacker.Init(); + Psychic.Init(); + Hangman.Init(); + Judge.Init(); + Councillor.Init(); + Mortician.Init(); + Mediumshiper.Init(); + Swooper.Init(); + Wraith.Init(); + BloodKnight.Init(); + Totocalcio.Init(); + Succubus.Init(); + CursedSoul.Init(); + Admirer.Init(); + Amnesiac.Init(); + Infectious.Init(); + Monarch.Init(); + Virus.Init(); + Bloodhound.Init(); + Tracker.Init(); + Merchant.Init(); + NSerialKiller.Init(); + Maverick.Init(); + Jinx.Init(); + DoubleShot.Init(); + Dazzler.Init(); + Addict.Init(); + Deathpact.Init(); + Tracefinder.Init(); + Devourer.Init(); + Ritualist.Init(); + NWitch.Init(); + Traitor.Init(); + Spiritualist.Init(); + Vulture.Init(); + Chameleon.Init(); + Wildling.Init(); + Morphling.Init(); + ParityCop.Init(); // *giggle* party cop + Baker.Init(); + Spiritcaller.Init(); + Lurker.Init(); + PlagueBearer.Init(); + Reverie.Init(); + Doomsayer.Init(); + Pirate.Init(); + + + SoloKombatManager.Init(); + CustomWinnerHolder.Reset(); + AntiBlackout.Reset(); + NameNotifyManager.Reset(); + IRandom.SetInstanceById(Options.RoleAssigningAlgorithm.GetValue()); + + MeetingStates.MeetingCalled = false; + MeetingStates.FirstMeeting = true; + GameStates.AlreadyDied = false; + } + catch (Exception ex) + { + Utils.ErrorEnd("Change Role Setting Postfix"); + Logger.Fatal(ex.ToString(), "Change Role Setting Postfix"); + } + } +} +[HarmonyPatch(typeof(RoleManager), nameof(RoleManager.SelectRoles))] +internal class SelectRolesPatch +{ + public static void Prefix() + { + if (!AmongUsClient.Instance.AmHost) return; + + try + { + //CustomRpcSenderとRpcSetRoleReplacerの初期化 + Dictionary senders = new(); + foreach (var pc in Main.AllPlayerControls) + { + senders[pc.PlayerId] = new CustomRpcSender($"{pc.name}'s SetRole Sender", SendOption.Reliable, false) + .StartMessage(pc.GetClientId()); + } + RpcSetRoleReplacer.StartReplace(senders); + + if (Options.EnableGM.GetBool()) + { + PlayerControl.LocalPlayer.RpcSetCustomRole(CustomRoles.GM); + PlayerControl.LocalPlayer.RpcSetRole(RoleTypes.Crewmate); + PlayerControl.LocalPlayer.Data.IsDead = true; + Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].SetDead(); + } + + + SelectCustomRoles(); + SelectAddonRoles(); + CalculateVanillaRoleCount(); + + //指定原版特殊职业数量 + var roleOpt = Main.NormalOptions.roleOptions; + int ScientistNum = Options.DisableVanillaRoles.GetBool() ? 0 : roleOpt.GetNumPerGame(RoleTypes.Scientist); + roleOpt.SetRoleRate(RoleTypes.Scientist, ScientistNum + addScientistNum, addScientistNum > 0 ? 100 : roleOpt.GetChancePerGame(RoleTypes.Scientist)); + int EngineerNum = Options.DisableVanillaRoles.GetBool() ? 0 : roleOpt.GetNumPerGame(RoleTypes.Engineer); + roleOpt.SetRoleRate(RoleTypes.Engineer, EngineerNum + addEngineerNum, addEngineerNum > 0 ? 100 : roleOpt.GetChancePerGame(RoleTypes.Engineer)); + int ShapeshifterNum = Options.DisableVanillaRoles.GetBool() ? 0 : roleOpt.GetNumPerGame(RoleTypes.Shapeshifter); + roleOpt.SetRoleRate(RoleTypes.Shapeshifter, ShapeshifterNum + addShapeshifterNum, addShapeshifterNum > 0 ? 100 : roleOpt.GetChancePerGame(RoleTypes.Shapeshifter)); + + Dictionary<(byte, byte), RoleTypes> rolesMap = new(); + + // 注册反职业 + foreach (var kv in RoleResult.Where(x => x.Value.IsDesyncRole())) + AssignDesyncRole(kv.Value, kv.Key, senders, rolesMap, BaseRole: kv.Value.GetDYRole()); + + + MakeDesyncSender(senders, rolesMap); + + } + catch (Exception e) + { + Utils.ErrorEnd("Select Role Prefix"); + Logger.Fatal(e.Message, "Select Role Prefix"); + } + //以下、バニラ側の役職割り当てが入る + } + + public static void Postfix() + { + if (!AmongUsClient.Instance.AmHost) return; + + try + { + List<(PlayerControl, RoleTypes)> newList = new(); + foreach (var sd in RpcSetRoleReplacer.StoragedData) + { + var kp = RoleResult.Where(x => x.Key.PlayerId == sd.Item1.PlayerId).FirstOrDefault(); + newList.Add((sd.Item1, kp.Value.GetRoleTypes())); + if (sd.Item2 == kp.Value.GetRoleTypes()) + Logger.Warn($"注册原版职业 => {sd.Item1.GetRealName()}: {sd.Item2}", "Override Role Select"); + else + Logger.Warn($"覆盖原版职业 => {sd.Item1.GetRealName()}: {sd.Item2} => {kp.Value.GetRoleTypes()}", "Override Role Select"); + } + if (Options.EnableGM.GetBool()) newList.Add((PlayerControl.LocalPlayer, RoleTypes.Crewmate)); + RpcSetRoleReplacer.StoragedData = newList; + + RpcSetRoleReplacer.Release(); //保存していたSetRoleRpcを一気に書く + RpcSetRoleReplacer.senders.Do(kvp => kvp.Value.SendMessage()); + + // 不要なオブジェクトの削除 + RpcSetRoleReplacer.senders = null; + RpcSetRoleReplacer.OverriddenSenderList = null; + RpcSetRoleReplacer.StoragedData = null; + + //Utils.ApplySuffix(); + + foreach (var pc in Main.AllPlayerControls) + { + pc.Data.IsDead = false; //プレイヤーの死を解除する + if (Main.PlayerStates[pc.PlayerId].MainRole != CustomRoles.NotAssigned) continue; //既にカスタム役職が割り当てられていればスキップ + var role = CustomRoles.NotAssigned; + switch (pc.Data.Role.Role) + { + case RoleTypes.Crewmate: + role = CustomRoles.Crewmate; + break; + case RoleTypes.Impostor: + role = CustomRoles.Impostor; + break; + case RoleTypes.Scientist: + role = CustomRoles.Scientist; + break; + case RoleTypes.Engineer: + role = CustomRoles.Engineer; + break; + case RoleTypes.GuardianAngel: + role = CustomRoles.GuardianAngel; + break; + case RoleTypes.Shapeshifter: + role = CustomRoles.Shapeshifter; + break; + default: + Logger.SendInGame(string.Format(GetString("Error.InvalidRoleAssignment"), pc?.Data?.PlayerName)); + break; + } + Main.PlayerStates[pc.PlayerId].SetMainRole(role); + } + + // 个人竞技模式用 + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) + { + foreach (var pair in Main.PlayerStates) + ExtendedPlayerControl.RpcSetCustomRole(pair.Key, pair.Value.MainRole); + goto EndOfSelectRolePatch; + } + + var rd = IRandom.Instance; + + foreach (var kv in RoleResult) + { + if (kv.Value.IsDesyncRole()) continue; + AssignCustomRole(kv.Value, kv.Key); + } + + if (CustomRoles.Lovers.IsEnable() && (CustomRoles.FFF.IsEnable() ? -1 : rd.Next(1, 100)) <= Options.LoverSpawnChances.GetInt()) AssignLoversRolesFromList(); + foreach (var role in AddonRolesList) + { + if (rd.Next(1, 100) <= (Options.CustomAdtRoleSpawnRate.TryGetValue(role, out var sc) ? sc.GetFloat() : 0)) + if (role.IsEnable()) AssignSubRoles(role); + } + + //RPCによる同期 + foreach (var pair in Main.PlayerStates) + { + ExtendedPlayerControl.RpcSetCustomRole(pair.Key, pair.Value.MainRole); + + foreach (var subRole in pair.Value.SubRoles) + ExtendedPlayerControl.RpcSetCustomRole(pair.Key, subRole); + } + + foreach (var pc in Main.AllPlayerControls) + { + if (pc.Data.Role.Role == RoleTypes.Shapeshifter) Main.CheckShapeshift.Add(pc.PlayerId, false); + switch (pc.GetCustomRole()) + { + case CustomRoles.BountyHunter: + BountyHunter.Add(pc.PlayerId); + break; + case CustomRoles.Reverie: + Reverie.Add(pc.PlayerId); + break; + case CustomRoles.SerialKiller: + SerialKiller.Add(pc.PlayerId); + break; + case CustomRoles.Witch: + Witch.Add(pc.PlayerId); + break; + case CustomRoles.HexMaster: + HexMaster.Add(pc.PlayerId); + break; + case CustomRoles.Crusader: + Crusader.Add(pc.PlayerId); + Crusader.CrusaderLimit[pc.PlayerId] = Crusader.SkillLimitOpt.GetInt(); + break; + case CustomRoles.Warlock: + Main.CursedPlayers.Add(pc.PlayerId, null); + Main.isCurseAndKill.Add(pc.PlayerId, false); + break; + case CustomRoles.FireWorks: + FireWorks.Add(pc.PlayerId); + break; + case CustomRoles.TimeThief: + TimeThief.Add(pc.PlayerId); + break; + case CustomRoles.Sniper: + Sniper.Add(pc.PlayerId); + break; + /* case CustomRoles.Mare: + Mare.Add(pc.PlayerId); + break; */ + case CustomRoles.Vampire: + Vampire.Add(pc.PlayerId); + break; + case CustomRoles.SwordsMan: + SwordsMan.Add(pc.PlayerId); + break; + case CustomRoles.Arsonist: + foreach (var ar in Main.AllPlayerControls) + Main.isDoused.Add((pc.PlayerId, ar.PlayerId), false); + break; + case CustomRoles.Revolutionist: + foreach (var ar in Main.AllPlayerControls) + Main.isDraw.Add((pc.PlayerId, ar.PlayerId), false); + break; + case CustomRoles.Farseer: + foreach (var ar in Main.AllPlayerControls) + { + Main.isRevealed.Add((pc.PlayerId, ar.PlayerId), false); + } + Farseer.RandomRole.Add(pc.PlayerId, Farseer.GetRandomCrewRoleString()); + Farseer.Add(pc.PlayerId); + break; + case CustomRoles.Executioner: + Executioner.Add(pc.PlayerId); + break; + case CustomRoles.Lawyer: + Lawyer.Add(pc.PlayerId); + break; + case CustomRoles.Jackal: + Jackal.Add(pc.PlayerId); + break; + case CustomRoles.Sidekick: + Sidekick.Add(pc.PlayerId); + break; + case CustomRoles.Baker: + Baker.Add(pc.PlayerId); + break; + case CustomRoles.Poisoner: + Poisoner.Add(pc.PlayerId); + break; + case CustomRoles.Sheriff: + Sheriff.Add(pc.PlayerId); + break; + case CustomRoles.CopyCat: + CopyCat.Add(pc.PlayerId); + break; + case CustomRoles.QuickShooter: + QuickShooter.Add(pc.PlayerId); + break; + case CustomRoles.Mayor: + Main.MayorUsedButtonCount[pc.PlayerId] = 0; + break; + case CustomRoles.TimeMaster: + Main.TimeMasterNum[pc.PlayerId] = 0; + break; + case CustomRoles.Masochist: + Main.MasochistKillMax[pc.PlayerId] = 0; + break; + case CustomRoles.Paranoia: + Main.ParaUsedButtonCount[pc.PlayerId] = 0; + break; + case CustomRoles.SabotageMaster: + SabotageMaster.Add(pc.PlayerId); + break; + case CustomRoles.EvilTracker: + EvilTracker.Add(pc.PlayerId); + break; + case CustomRoles.Snitch: + Snitch.Add(pc.PlayerId); + break; + case CustomRoles.AntiAdminer: + AntiAdminer.Add(pc.PlayerId); + break; + case CustomRoles.Mario: + Main.MarioVentCount[pc.PlayerId] = 0; + break; + case CustomRoles.TimeManager: + TimeManager.Add(pc.PlayerId); + break; + case CustomRoles.Pelican: + Pelican.Add(pc.PlayerId); + break; + case CustomRoles.Counterfeiter: + Counterfeiter.Add(pc.PlayerId); + break; + case CustomRoles.Pursuer: + Pursuer.Add(pc.PlayerId); + break; + case CustomRoles.Gangster: + Gangster.Add(pc.PlayerId); + break; + case CustomRoles.Medic: + Medic.Add(pc.PlayerId); + break; + case CustomRoles.EvilDiviner: + EvilDiviner.Add(pc.PlayerId); + break; + case CustomRoles.Ritualist: + Ritualist.Add(pc.PlayerId); + break; + case CustomRoles.Divinator: + Divinator.Add(pc.PlayerId); + break; + case CustomRoles.Oracle: + Oracle.Add(pc.PlayerId); + break; + case CustomRoles.Gamer: + Gamer.Add(pc.PlayerId); + break; + case CustomRoles.BallLightning: + BallLightning.Add(pc.PlayerId); + break; + case CustomRoles.DarkHide: + DarkHide.Add(pc.PlayerId); + break; + case CustomRoles.Greedier: + Greedier.Add(pc.PlayerId); + break; + case CustomRoles.Collector: + Collector.Add(pc.PlayerId); + break; + case CustomRoles.CursedWolf: + Main.CursedWolfSpellCount[pc.PlayerId] = Options.GuardSpellTimes.GetInt(); + break; + case CustomRoles.Jinx: + Main.JinxSpellCount[pc.PlayerId] = Jinx.JinxSpellTimes.GetInt(); + Jinx.Add(pc.PlayerId); + break; + case CustomRoles.Eraser: + Eraser.Add(pc.PlayerId); + break; + case CustomRoles.Assassin: + Assassin.Add(pc.PlayerId); + break; + case CustomRoles.Sans: + Sans.Add(pc.PlayerId); + break; + case CustomRoles.Juggernaut: + Juggernaut.Add(pc.PlayerId); + break; + case CustomRoles.Hacker: + Hacker.Add(pc.PlayerId); + break; + case CustomRoles.Psychic: + Psychic.Add(pc.PlayerId); + break; + case CustomRoles.Hangman: + Hangman.Add(pc.PlayerId); + break; + case CustomRoles.Judge: + Judge.Add(pc.PlayerId); + break; + case CustomRoles.Councillor: + Councillor.Add(pc.PlayerId); + break; + case CustomRoles.Mortician: + Mortician.Add(pc.PlayerId); + break; + case CustomRoles.Tracefinder: + Tracefinder.Add(pc.PlayerId); + break; + case CustomRoles.Mediumshiper: + Mediumshiper.Add(pc.PlayerId); + break; + case CustomRoles.Veteran: + Main.VeteranNumOfUsed.Add(pc.PlayerId, Options.VeteranSkillMaxOfUseage.GetInt()); + break; + case CustomRoles.Swooper: + Swooper.Add(pc.PlayerId); + break; + case CustomRoles.Wraith: + Wraith.Add(pc.PlayerId); + break; + case CustomRoles.Chameleon: + Chameleon.Add(pc.PlayerId); + break; + case CustomRoles.BloodKnight: + BloodKnight.Add(pc.PlayerId); + break; + case CustomRoles.Totocalcio: + Totocalcio.Add(pc.PlayerId); + break; + case CustomRoles.Succubus: + Succubus.Add(pc.PlayerId); + break; + case CustomRoles.CursedSoul: + CursedSoul.Add(pc.PlayerId); + break; + case CustomRoles.Admirer: + Admirer.Add(pc.PlayerId); + break; + case CustomRoles.Amnesiac: + Amnesiac.Add(pc.PlayerId); + break; + case CustomRoles.DovesOfNeace: + Main.DovesOfNeaceNumOfUsed.Add(pc.PlayerId, Options.DovesOfNeaceMaxOfUseage.GetInt()); + break; + case CustomRoles.Infectious: + Infectious.Add(pc.PlayerId); + break; + case CustomRoles.Monarch: + Monarch.Add(pc.PlayerId); + break; + case CustomRoles.Deputy: + Deputy.Add(pc.PlayerId); + break; + case CustomRoles.Virus: + Virus.Add(pc.PlayerId); + break; + case CustomRoles.Wildling: + Wildling.Add(pc.PlayerId); + break; + case CustomRoles.Bloodhound: + Bloodhound.Add(pc.PlayerId); + break; + case CustomRoles.Tracker: + Tracker.Add(pc.PlayerId); + break; + case CustomRoles.Merchant: + Merchant.Add(pc.PlayerId); + break; + case CustomRoles.NSerialKiller: + NSerialKiller.Add(pc.PlayerId); + break; + case CustomRoles.Traitor: + Traitor.Add(pc.PlayerId); + break; + case CustomRoles.NWitch: + NWitch.Add(pc.PlayerId); + break; + case CustomRoles.Maverick: + Maverick.Add(pc.PlayerId); + break; + case CustomRoles.Dazzler: + Dazzler.Add(pc.PlayerId); + break; + case CustomRoles.Addict: + Addict.Add(pc.PlayerId); + break; + case CustomRoles.Deathpact: + Deathpact.Add(pc.PlayerId); + break; + case CustomRoles.Morphling: + Morphling.Add(pc.PlayerId); + break; + case CustomRoles.Devourer: + Devourer.Add(pc.PlayerId); + break; + case CustomRoles.Spiritualist: + Spiritualist.Add(pc.PlayerId); + break; + case CustomRoles.Vulture: + Vulture.Add(pc.PlayerId); + break; + case CustomRoles.PlagueBearer: + PlagueBearer.Add(pc.PlayerId); + break; + case CustomRoles.ParityCop: + ParityCop.Add(pc.PlayerId); + break; + case CustomRoles.Spiritcaller: + Spiritcaller.Add(pc.PlayerId); + break; + case CustomRoles.Lurker: + Lurker.Add(pc.PlayerId); + break; + case CustomRoles.Doomsayer: + Doomsayer.Add(pc.PlayerId); + break; + case CustomRoles.Pirate: + Pirate.Add(pc.PlayerId); + break; + } + foreach (var subRole in pc.GetCustomSubRoles()) + { + switch (subRole) + { + // ここに属性のAddを追加 + default: + break; + } + } + } + + EndOfSelectRolePatch: + + HudManager.Instance.SetHudActive(true); + List AllPlayers = new(); + CustomRpcSender sender = CustomRpcSender.Create("SelectRoles Sender", SendOption.Reliable); + foreach (var pc in Main.AllPlayerControls) + pc.ResetKillCooldown(); + + //役職の人数を戻す + var roleOpt = Main.NormalOptions.roleOptions; + int ScientistNum = Options.DisableVanillaRoles.GetBool() ? 0 : roleOpt.GetNumPerGame(RoleTypes.Scientist); + ScientistNum -= addScientistNum; + roleOpt.SetRoleRate(RoleTypes.Scientist, ScientistNum, roleOpt.GetChancePerGame(RoleTypes.Scientist)); + int EngineerNum = Options.DisableVanillaRoles.GetBool() ? 0 : roleOpt.GetNumPerGame(RoleTypes.Engineer); + EngineerNum -= addEngineerNum; + roleOpt.SetRoleRate(RoleTypes.Engineer, EngineerNum, roleOpt.GetChancePerGame(RoleTypes.Engineer)); + int ShapeshifterNum = Options.DisableVanillaRoles.GetBool() ? 0 : roleOpt.GetNumPerGame(RoleTypes.Shapeshifter); + ShapeshifterNum -= addShapeshifterNum; + roleOpt.SetRoleRate(RoleTypes.Shapeshifter, ShapeshifterNum, roleOpt.GetChancePerGame(RoleTypes.Shapeshifter)); + + switch (Options.CurrentGameMode) + { + case CustomGameMode.Standard: + GameEndChecker.SetPredicateToNormal(); + break; + case CustomGameMode.SoloKombat: + GameEndChecker.SetPredicateToSoloKombat(); + break; + } + + GameOptionsSender.AllSenders.Clear(); + foreach (var pc in Main.AllPlayerControls) + { + GameOptionsSender.AllSenders.Add( + new PlayerGameOptionsSender(pc) + ); + } + + // ResetCamが必要なプレイヤーのリストにクラス化が済んでいない役職のプレイヤーを追加 + Main.ResetCamPlayerList.AddRange(Main.AllPlayerControls.Where(p => p.GetCustomRole() is CustomRoles.Arsonist or CustomRoles.NWitch or CustomRoles.Revolutionist or CustomRoles.Farseer or CustomRoles.Sidekick or CustomRoles.KB_Normal).Select(p => p.PlayerId)); + Utils.CountAlivePlayers(true); + Utils.SyncAllSettings(); + SetColorPatch.IsAntiGlitchDisabled = false; + } + catch (Exception ex) + { + Utils.ErrorEnd("Select Role Postfix"); + Logger.Fatal(ex.ToString(), "Select Role Prefix"); + } + } + private static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dictionary senders, Dictionary<(byte, byte), RoleTypes> rolesMap, RoleTypes BaseRole, RoleTypes hostBaseRole = RoleTypes.Crewmate) + { + if (player == null) return; + + var hostId = PlayerControl.LocalPlayer.PlayerId; + + Main.PlayerStates[player.PlayerId].SetMainRole(role); + + var selfRole = player.PlayerId == hostId ? hostBaseRole : BaseRole; + var othersRole = player.PlayerId == hostId ? RoleTypes.Crewmate : RoleTypes.Scientist; + + //Desync役職視点 + foreach (var target in Main.AllPlayerControls) + rolesMap[(player.PlayerId, target.PlayerId)] = player.PlayerId != target.PlayerId ? othersRole : selfRole; + + //他者視点 + foreach (var seer in Main.AllPlayerControls.Where(x => player.PlayerId != x.PlayerId)) + rolesMap[(seer.PlayerId, player.PlayerId)] = othersRole; + + RpcSetRoleReplacer.OverriddenSenderList.Add(senders[player.PlayerId]); + //ホスト視点はロール決定 + player.SetRole(othersRole); + player.Data.IsDead = true; + + Logger.Info($"注册模组职业:{player?.Data?.PlayerName} => {role}", "AssignRoles"); + } + public static void MakeDesyncSender(Dictionary senders, Dictionary<(byte, byte), RoleTypes> rolesMap) + { + foreach (var seer in Main.AllPlayerControls) + { + var sender = senders[seer.PlayerId]; + foreach (var target in Main.AllPlayerControls) + { + if (rolesMap.TryGetValue((seer.PlayerId, target.PlayerId), out var role)) + { + sender.RpcSetRole(seer, role, target.GetClientId()); + } + } + } + } + + private static void AssignCustomRole(CustomRoles role, PlayerControl player) + { + if (player == null) return; + SetColorPatch.IsAntiGlitchDisabled = true; + + Main.PlayerStates[player.PlayerId].SetMainRole(role); + Logger.Info($"注册模组职业:{player?.Data?.PlayerName} => {role}", "AssignRoles"); + + SetColorPatch.IsAntiGlitchDisabled = false; + } + private static void ForceAssignRole(CustomRoles role, List AllPlayers, CustomRpcSender sender, RoleTypes BaseRole, RoleTypes hostBaseRole = RoleTypes.Crewmate, bool skip = false, int Count = -1) + { + var count = 1; + + if (Count != -1) + count = Count; + for (var i = 0; i < count; i++) + { + if (AllPlayers.Count <= 0) break; + var rand = new System.Random(); + var player = AllPlayers[rand.Next(0, AllPlayers.Count)]; + AllPlayers.Remove(player); + Main.AllPlayerCustomRoles[player.PlayerId] = role; + if (!skip) + { + if (!player.IsModClient()) + { + int playerCID = player.GetClientId(); + sender.RpcSetRole(player, BaseRole, playerCID); + //Desyncする人視点で他プレイヤーを科学者にするループ + foreach (var pc in PlayerControl.AllPlayerControls) + { + if (pc == player) continue; + sender.RpcSetRole(pc, RoleTypes.Scientist, playerCID); + } + //他視点でDesyncする人の役職を科学者にするループ + foreach (var pc in PlayerControl.AllPlayerControls) + { + if (pc == player) continue; + if (pc.PlayerId == 0) player.SetRole(RoleTypes.Scientist); //ホスト視点用 + else sender.RpcSetRole(player, RoleTypes.Scientist, pc.GetClientId()); + } + } + else + { + //ホストは別の役職にする + player.SetRole(hostBaseRole); //ホスト視点用 + sender.RpcSetRole(player, hostBaseRole); + } + } + } + } + + private static void AssignLoversRolesFromList() + { + if (CustomRoles.Lovers.IsEnable()) + { + //Loversを初期化 + Main.LoversPlayers.Clear(); + Main.isLoversDead = false; + //ランダムに2人選出 + AssignLoversRoles(); + } + } + private static void AssignLoversRoles(int RawCount = -1) + { + var allPlayers = new List(); + foreach (var pc in Main.AllPlayerControls) + { + if (pc.Is(CustomRoles.GM) + || (pc.HasSubRole() && pc.GetCustomSubRoles().Count >= Options.NoLimitAddonsNumMax.GetInt()) + || pc.Is(CustomRoles.Ntr) + || pc.Is(CustomRoles.Dictator) + || pc.Is(CustomRoles.God) + || pc.Is(CustomRoles.FFF) + || pc.Is(CustomRoles.Bomber) + || pc.Is(CustomRoles.Nuker) + || pc.Is(CustomRoles.Provocateur) + || (pc.GetCustomRole().IsCrewmate() && !Options.CrewCanBeInLove.GetBool()) + || (pc.GetCustomRole().IsNeutral() && !Options.NeutralCanBeInLove.GetBool()) + || (pc.GetCustomRole().IsImpostor() && !Options.ImpCanBeInLove.GetBool())) + continue; + allPlayers.Add(pc); + } + var role = CustomRoles.Lovers; + var rd = IRandom.Instance; + var count = Math.Clamp(RawCount, 0, allPlayers.Count); + if (RawCount == -1) count = Math.Clamp(role.GetCount(), 0, allPlayers.Count); + if (count <= 0) return; + for (var i = 0; i < count; i++) + { + var player = allPlayers[rd.Next(0, allPlayers.Count)]; + Main.LoversPlayers.Add(player); + allPlayers.Remove(player); + Main.PlayerStates[player.PlayerId].SetSubRole(role); + Logger.Info("注册恋人:" + player?.Data?.PlayerName + " = " + player.GetCustomRole().ToString() + " + " + role.ToString(), "AssignLovers"); + } + RPC.SyncLoversPlayers(); + } + private static void AssignSubRoles(CustomRoles role, int RawCount = -1) + { + var allPlayers = Main.AllAlivePlayerControls.Where(x => CustomRolesHelper.CheckAddonConfilct(role, x)).ToList(); + var count = Math.Clamp(RawCount, 0, allPlayers.Count); + if (RawCount == -1) count = Math.Clamp(role.GetCount(), 0, allPlayers.Count); + if (count <= 0) return; + for (var i = 0; i < count; i++) + { + var player = allPlayers[IRandom.Instance.Next(0, allPlayers.Count)]; + Main.PlayerStates[player.PlayerId].SetSubRole(role); + Logger.Info("注册附加职业:" + player?.Data?.PlayerName + " = " + player.GetCustomRole().ToString() + " + " + role.ToString(), "Assign " + role.ToString()); + } + } + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))] + private class RpcSetRoleReplacer + { + public static bool doReplace = false; + public static Dictionary senders; + public static List<(PlayerControl, RoleTypes)> StoragedData = new(); + // 役職Desyncなど別の処理でSetRoleRpcを書き込み済みなため、追加の書き込みが不要なSenderのリスト + public static List OverriddenSenderList; + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] RoleTypes roleType) + { + if (doReplace && senders != null) + { + StoragedData.Add((__instance, roleType)); + return false; + } + else return true; + } + public static void Release() + { + foreach (var sender in senders) + { + if (OverriddenSenderList.Contains(sender.Value)) continue; + if (sender.Value.CurrentState != CustomRpcSender.State.InRootMessage) + throw new InvalidOperationException("A CustomRpcSender had Invalid State."); + + foreach (var pair in StoragedData) + { + pair.Item1.SetRole(pair.Item2); + sender.Value.AutoStartRpc(pair.Item1.NetId, (byte)RpcCalls.SetRole, Utils.GetPlayerById(sender.Key).GetClientId()) + .Write((ushort)pair.Item2) + .EndRpc(); + } + sender.Value.EndMessage(); + } + doReplace = false; + } + public static void StartReplace(Dictionary senders) + { + RpcSetRoleReplacer.senders = senders; + StoragedData = new(); + OverriddenSenderList = new(); + doReplace = true; + } + } +} \ No newline at end of file