diff --git a/Modules/GameState.cs b/Modules/GameState.cs index bed377062..dcdd2ef89 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -306,6 +306,7 @@ public enum DeathReason Revenge, Execution, Fall, + Exorcised, // TOHE Gambled, diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 701eab517..15d8c0493 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -654,7 +654,7 @@ public static float GetRoleChance(CustomRoles role) private static System.Collections.IEnumerator CoLoadOptions() { //####################################### - // 31000 last id for roles/add-ons (Next use 31100) + // 31100 last id for roles/add-ons (Next use 31200) // Limit id for roles/add-ons --- "59999" //####################################### diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 2207d4072..50712ae0d 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -15,8 +15,9 @@ namespace TOHE; + [Obfuscation(Exclude = true)] -public enum CustomRPC : byte // 185/255 USED +public enum CustomRPC : byte // 189/255 USED { // RpcCalls can increase with each AU version // On version 2024.6.18 the last id in RpcCalls: 65 @@ -82,6 +83,7 @@ public enum CustomRPC : byte // 185/255 USED // BetterAmongUs (BAU) RPC, This is sent to allow other BAU users know who's using BAU! BetterCheck = 150, + ExorcistExorcise, SetEvilTrackerTarget, SetDrawPlayer, SetCrewpostorTasksDone, @@ -156,6 +158,7 @@ public static bool TrustedRpc(byte id) or CustomRPC.RequestRetryVersionCheck or CustomRPC.AntiBlackout or CustomRPC.Judge + or CustomRPC.ExorcistExorcise or CustomRPC.CouncillorJudge or CustomRPC.NemesisRevenge or CustomRPC.RetributionistRevenge @@ -573,6 +576,9 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.Judge: Judge.ReceiveRPC_Custom(reader, __instance); break; + case CustomRPC.ExorcistExorcise: + Exorcist.ReceiveRPC_Custom(reader, __instance); + break; case CustomRPC.PresidentEnd: President.ReceiveRPC(reader, __instance); break; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index a2f19bb93..e77ebf237 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -2401,6 +2401,7 @@ var Breason when BannedReason(Breason) => false, PlayerState.DeathReason.BloodLet => CustomRoles.Bloodmoon.IsEnable(), PlayerState.DeathReason.Starved => CustomRoles.Baker.IsEnable(), PlayerState.DeathReason.Sacrificed => CustomRoles.Altruist.IsEnable(), + PlayerState.DeathReason.Exorcised => CustomRoles.Exorcist.IsEnable(), PlayerState.DeathReason.Electrocuted => CustomRoles.Shocker.IsEnable(), PlayerState.DeathReason.Scavenged => CustomRoles.Scavenger.IsEnable(), PlayerState.DeathReason.BlastedOff => CustomRoles.MoonDancer.IsEnable(), diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 89d73fc1e..431b668fd 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -1,3595 +1,3559 @@ -using Assets.CoreScripts; -using Hazel; -using System; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using TOHE.Modules; -using TOHE.Modules.ChatManager; -using TOHE.Roles.Core; -using TOHE.Roles.Core.AssignManager; -using TOHE.Roles.Coven; -using TOHE.Roles.Crewmate; -using TOHE.Roles.Impostor; -using TOHE.Roles.Neutral; -using UnityEngine; -using static TOHE.Translator; - - -namespace TOHE; - -[HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))] -internal class ChatCommands -{ - private static readonly string modLogFiles = @"./TOHE-DATA/ModLogs.txt"; - private static readonly string modTagsFiles = @"./TOHE-DATA/Tags/MOD_TAGS"; - private static readonly string sponsorTagsFiles = @"./TOHE-DATA/Tags/SPONSOR_TAGS"; - private static readonly string vipTagsFiles = @"./TOHE-DATA/Tags/VIP_TAGS"; - - private static readonly Dictionary Pollvotes = []; - private static readonly Dictionary PollQuestions = []; - private static readonly List PollVoted = []; - private static float Polltimer = 120f; - private static string PollMSG = ""; - - public const string Csize = "85%"; // CustomRole Settings Font-Size - public const string Asize = "75%"; // All Appended Addons Font-Size - - public static List ChatHistory = []; - - public static bool Prefix(ChatController __instance) - { - if (__instance.quickChatField.visible == false && __instance.freeChatField.textArea.text == "") return false; - if (!GameStates.IsModHost && !AmongUsClient.Instance.AmHost) return true; - __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.Trim().Split(' '); - string subArgs = ""; - string subArgs2 = ""; - var canceled = false; - var cancelVal = ""; - Main.isChatCommand = true; - Logger.Info(text, "SendChat"); - if ((Options.NewHideMsg.GetBool() || Blackmailer.HasEnabled) && AmongUsClient.Instance.AmHost) // Blackmailer.ForBlackmailer.Contains(PlayerControl.LocalPlayer.PlayerId)) && PlayerControl.LocalPlayer.IsAlive()) - { - ChatManager.SendMessage(PlayerControl.LocalPlayer, text); - } - //if (text.Length >= 3) if (text[..2] == "/r" && text[..3] != "/rn" && text[..3] != "/rs") args[0] = "/r"; - if (text.Length >= 4) if (text[..3] == "/up") args[0] = "/up"; - - if (GuessManager.GuesserMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (PlayerControl.LocalPlayer.GetRoleClass() is Judge jd && jd.TrialMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (President.EndMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Inspector.InspectCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Pirate.DuelCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (PlayerControl.LocalPlayer.GetRoleClass() is Councillor cl && cl.MurderMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Nemesis.NemesisMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Retributionist.RetributionistMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Ritualist.RitualistMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Medium.MsMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (PlayerControl.LocalPlayer.GetRoleClass() is Swapper sw && sw.SwapMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (PlayerControl.LocalPlayer.GetRoleClass() is Dictator dt && dt.ExilePlayer(PlayerControl.LocalPlayer, text)) goto Canceled; - Directory.CreateDirectory(modTagsFiles); - Directory.CreateDirectory(vipTagsFiles); - Directory.CreateDirectory(sponsorTagsFiles); - - if (Blackmailer.CheckBlackmaile(PlayerControl.LocalPlayer) && PlayerControl.LocalPlayer.IsAlive()) - { - goto Canceled; - } - switch (args[0]) - { - case "/dump": - case "/导出日志": - case "/日志": - case "/导出": - Utils.DumpLog(); - break; - case "/v": - case "/version": - case "/versão": - case "/版本": - canceled = true; - string version_text = ""; - var player = PlayerControl.LocalPlayer; - var title = "" + GetString("DefaultSystemMessageTitle") + ""; - var name = player?.Data?.PlayerName; - try - { - foreach (var kvp in Main.playerVersion.OrderBy(pair => pair.Key).ToArray()) - { - var pc = Utils.GetClientById(kvp.Key)?.Character; - version_text += $"{kvp.Key}/{(pc?.PlayerId != null ? pc.PlayerId.ToString() : "null")}:{pc?.GetRealName(clientData: true) ?? "null"}:{kvp.Value.forkId}/{kvp.Value.version}({kvp.Value.tag})\n"; - } - if (version_text != "") - { - player.SetName(title); - DestroyableSingleton.Instance.Chat.AddChat(player, version_text); - player.SetName(name); - } - } - catch (Exception e) - { - Logger.Error(e.Message, "/version"); - version_text = "Error while getting version : " + e.Message; - if (version_text != "") - { - player.SetName(title); - DestroyableSingleton.Instance.Chat.AddChat(player, version_text); - player.SetName(name); - } - } - break; - - default: - Main.isChatCommand = false; - break; - } - if (AmongUsClient.Instance.AmHost) - { - Main.isChatCommand = true; - switch (args[0]) - { - case "/ans": - case "/asw": - case "/answer": - case "/回答": - Quizmaster.AnswerByChat(PlayerControl.LocalPlayer, args); - break; - - case "/qmquiz": - case "/提问": - Quizmaster.ShowQuestion(PlayerControl.LocalPlayer); - break; - - case "/win": - case "/winner": - case "/vencedor": - case "/胜利": - case "/获胜": - case "/赢": - case "/胜利者": - case "/获胜的人": - case "/赢家": - canceled = true; - if (Main.winnerNameList.Count == 0) Utils.SendMessage(GetString("NoInfoExists")); - else Utils.SendMessage("Winner: " + string.Join(", ", Main.winnerNameList)); - break; - - case "/l": - case "/lastresult": - case "/fimdejogo": - case "/上局信息": - case "/信息": - case "/情况": - canceled = true; - Utils.ShowKillLog(); - Utils.ShowLastRoles(); - Utils.ShowLastResult(); - break; - - case "/gr": - case "/gameresults": - case "/resultados": - case "/对局结果": - case "/上局结果": - case "/结果": - canceled = true; - Utils.ShowLastResult(); - break; - - case "/kh": - case "/killlog": - case "/击杀日志": - case "/击杀情况": - canceled = true; - Utils.ShowKillLog(); - break; - - case "/rs": - case "/sum": - case "/rolesummary": - case "/sumario": - case "/sumário": - case "/summary": - case "/результат": - case "/上局职业": - case "/职业信息": - case "/对局职业": - canceled = true; - Utils.ShowLastRoles(); - break; - - case "/ghostinfo": - case "/幽灵职业介绍": - case "/鬼魂职业介绍": - case "/幽灵职业": - case "/鬼魂职业": - canceled = true; - Utils.SendMessage(GetString("Message.GhostRoleInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - - case "/apocinfo": - case "/apocalypseinfo": - case "/末日中立职业介绍": - case "/末日中立介绍": - case "/末日类中立职业介绍": - case "/末日类中立介绍": - canceled = true; - Utils.SendMessage(GetString("Message.ApocalypseInfo"), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); - break; - - case "/coveninfo": +using Assets.CoreScripts; +using Hazel; +using System; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using TOHE.Modules; +using TOHE.Modules.ChatManager; +using TOHE.Roles.Core; +using TOHE.Roles.Core.AssignManager; +using TOHE.Roles.Coven; +using TOHE.Roles.Crewmate; +using TOHE.Roles.Impostor; +using TOHE.Roles.Neutral; +using UnityEngine; +using static TOHE.Translator; + + +namespace TOHE; + +[HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))] +internal class ChatCommands +{ + private static readonly string modLogFiles = @"./TOHE-DATA/ModLogs.txt"; + private static readonly string modTagsFiles = @"./TOHE-DATA/Tags/MOD_TAGS"; + private static readonly string sponsorTagsFiles = @"./TOHE-DATA/Tags/SPONSOR_TAGS"; + private static readonly string vipTagsFiles = @"./TOHE-DATA/Tags/VIP_TAGS"; + + private static readonly Dictionary Pollvotes = []; + private static readonly Dictionary PollQuestions = []; + private static readonly List PollVoted = []; + private static float Polltimer = 120f; + private static string PollMSG = ""; + + public const string Csize = "85%"; // CustomRole Settings Font-Size + public const string Asize = "75%"; // All Appended Addons Font-Size + + public static List ChatHistory = []; + + public static bool Prefix(ChatController __instance) + { + if (__instance.quickChatField.visible == false && __instance.freeChatField.textArea.text == "") return false; + if (!GameStates.IsModHost && !AmongUsClient.Instance.AmHost) return true; + __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.Trim().Split(' '); + string subArgs = ""; + string subArgs2 = ""; + var canceled = false; + var cancelVal = ""; + Main.isChatCommand = true; + Logger.Info(text, "SendChat"); + if ((Options.NewHideMsg.GetBool() || Blackmailer.HasEnabled) && AmongUsClient.Instance.AmHost) // Blackmailer.ForBlackmailer.Contains(PlayerControl.LocalPlayer.PlayerId)) && PlayerControl.LocalPlayer.IsAlive()) + { + ChatManager.SendMessage(PlayerControl.LocalPlayer, text); + } + //if (text.Length >= 3) if (text[..2] == "/r" && text[..3] != "/rn" && text[..3] != "/rs") args[0] = "/r"; + if (text.Length >= 4) if (text[..3] == "/up") args[0] = "/up"; + + if (GuessManager.GuesserMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (PlayerControl.LocalPlayer.GetRoleClass() is Judge jd && jd.TrialMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (President.EndMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Inspector.InspectCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Pirate.DuelCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (PlayerControl.LocalPlayer.GetRoleClass() is Councillor cl && cl.MurderMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Nemesis.NemesisMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Retributionist.RetributionistMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; + if (PlayerControl.LocalPlayer.GetRoleClass() is Exorcist ex && ex.CheckCommand(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Ritualist.RitualistMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Medium.MsMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (PlayerControl.LocalPlayer.GetRoleClass() is Swapper sw && sw.SwapMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (PlayerControl.LocalPlayer.GetRoleClass() is Dictator dt && dt.ExilePlayer(PlayerControl.LocalPlayer, text)) goto Canceled; + Directory.CreateDirectory(modTagsFiles); + Directory.CreateDirectory(vipTagsFiles); + Directory.CreateDirectory(sponsorTagsFiles); + + if (Blackmailer.CheckBlackmaile(PlayerControl.LocalPlayer) && PlayerControl.LocalPlayer.IsAlive()) + { + goto Canceled; + } + if (Exorcist.IsExorcismCurrentlyActive() && PlayerControl.LocalPlayer.IsAlive()) + { + Exorcist.ExorcisePlayer(PlayerControl.LocalPlayer); + goto Canceled; + } + switch (args[0]) + { + case "/dump": + case "/导出日志": + case "/日志": + case "/导出": + Utils.DumpLog(); + break; + case "/v": + case "/version": + case "/versão": + case "/版本": + canceled = true; + string version_text = ""; + var player = PlayerControl.LocalPlayer; + var title = "" + GetString("DefaultSystemMessageTitle") + ""; + var name = player?.Data?.PlayerName; + try + { + foreach (var kvp in Main.playerVersion.OrderBy(pair => pair.Key).ToArray()) + { + var pc = Utils.GetClientById(kvp.Key)?.Character; + version_text += $"{kvp.Key}/{(pc?.PlayerId != null ? pc.PlayerId.ToString() : "null")}:{pc?.GetRealName(clientData: true) ?? "null"}:{kvp.Value.forkId}/{kvp.Value.version}({kvp.Value.tag})\n"; + } + if (version_text != "") + { + player.SetName(title); + DestroyableSingleton.Instance.Chat.AddChat(player, version_text); + player.SetName(name); + } + } + catch (Exception e) + { + Logger.Error(e.Message, "/version"); + version_text = "Error while getting version : " + e.Message; + if (version_text != "") + { + player.SetName(title); + DestroyableSingleton.Instance.Chat.AddChat(player, version_text); + player.SetName(name); + } + } + break; + + default: + Main.isChatCommand = false; + break; + } + if (AmongUsClient.Instance.AmHost) + { + Main.isChatCommand = true; + switch (args[0]) + { + case "/ans": + case "/asw": + case "/answer": + case "/回答": + Quizmaster.AnswerByChat(PlayerControl.LocalPlayer, args); + break; + + case "/qmquiz": + case "/提问": + Quizmaster.ShowQuestion(PlayerControl.LocalPlayer); + break; + + case "/win": + case "/winner": + case "/vencedor": + case "/胜利": + case "/获胜": + case "/赢": + case "/胜利者": + case "/获胜的人": + case "/赢家": + canceled = true; + if (Main.winnerNameList.Count == 0) Utils.SendMessage(GetString("NoInfoExists")); + else Utils.SendMessage("Winner: " + string.Join(", ", Main.winnerNameList)); + break; + + case "/l": + case "/lastresult": + case "/fimdejogo": + case "/上局信息": + case "/信息": + case "/情况": + canceled = true; + Utils.ShowKillLog(); + Utils.ShowLastRoles(); + Utils.ShowLastResult(); + break; + + case "/gr": + case "/gameresults": + case "/resultados": + case "/对局结果": + case "/上局结果": + case "/结果": + canceled = true; + Utils.ShowLastResult(); + break; + + case "/kh": + case "/killlog": + case "/击杀日志": + case "/击杀情况": + canceled = true; + Utils.ShowKillLog(); + break; + + case "/rs": + case "/sum": + case "/rolesummary": + case "/sumario": + case "/sumário": + case "/summary": + case "/результат": + case "/上局职业": + case "/职业信息": + case "/对局职业": + canceled = true; + Utils.ShowLastRoles(); + break; + + case "/ghostinfo": + case "/幽灵职业介绍": + case "/鬼魂职业介绍": + case "/幽灵职业": + case "/鬼魂职业": + canceled = true; + Utils.SendMessage(GetString("Message.GhostRoleInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/apocinfo": + case "/apocalypseinfo": + case "/末日中立职业介绍": + case "/末日中立介绍": + case "/末日类中立职业介绍": + case "/末日类中立介绍": + canceled = true; + Utils.SendMessage(GetString("Message.ApocalypseInfo"), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); + break; + + case "/coveninfo": case "/covinfo": - case "/巫师阵营职业介绍": - case "/巫师阵营介绍": - case "/巫师介绍": - canceled = true; - Utils.SendMessage(GetString("Message.CovenInfo"), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Coven), GetString("CovenInfoTitle"))); - break; - - case "/rn": - case "/rename": - case "/renomear": - case "/переименовать": - case "/重命名": - case "/命名为": - canceled = true; - if (args.Length < 1) break; - if (args.Skip(1).Join(delimiter: " ").Length is > 10 or < 1) - { - Utils.SendMessage(GetString("Message.AllowNameLength"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - var temp = args.Skip(1).Join(delimiter: " "); - Main.HostRealName = temp; - Main.AllPlayerNames[PlayerControl.LocalPlayer.PlayerId] = temp; - Utils.SendMessage(string.Format(GetString("Message.SetName"), temp), PlayerControl.LocalPlayer.PlayerId); - } - break; - - case "/hn": - case "/hidename": - case "/semnome": - case "/隐藏名字": - case "/藏名": - 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": - case "/nível": - case "/nivel": - case "/等级": - case "/等级设置为": - 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": - case "/atual": - case "/设置": - case "/系统设置": - case "/模组设置": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - switch (subArgs) - { - case "r": - case "roles": - case "funções": - Utils.ShowActiveRoles(); - break; - case "a": - case "all": - case "tudo": - Utils.ShowAllActiveSettings(); - break; - default: - Utils.ShowActiveSettings(); - break; - } - break; - - case "/dis": - case "/disconnect": - case "/desconectar": - case "/断连": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - switch (subArgs) - { - case "crew": - case "tripulante": - case "船员": - GameManager.Instance.enabled = false; - Utils.NotifyGameEnding(); - GameManager.Instance.RpcEndGame(GameOverReason.HumansDisconnect, false); - break; - - case "imp": - case "impostor": - case "内鬼": - case "伪装者": - GameManager.Instance.enabled = false; - Utils.NotifyGameEnding(); - GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); - break; - - default: - __instance.AddChat(PlayerControl.LocalPlayer, "crew | imp"); - if (TranslationController.Instance.currentLanguage.languageID == SupportedLangs.Brazilian) - { - __instance.AddChat(PlayerControl.LocalPlayer, "tripulante | impostor"); - } - cancelVal = "/dis"; - break; - } - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Admin, 0); - break; - - case "/r": - case "/role": - case "/р": - case "/роль": - canceled = true; - if (text.Contains("/role") || text.Contains("/роль")) - subArgs = text.Remove(0, 5); - else - subArgs = text.Remove(0, 2); - SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId); - break; - - case "/up": - case "/指定": - case "/成为": - canceled = true; - subArgs = text.Remove(0, 3); - if (!PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp) - { - Utils.SendMessage($"{GetString("InvalidPermissionCMD")}", PlayerControl.LocalPlayer.PlayerId); - break; - } - if (!Options.EnableUpMode.GetBool()) - { - Utils.SendMessage(string.Format(GetString("Message.YTPlanDisabled"), GetString("EnableYTPlan")), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId, isUp: true); - break; - - //case "/setbasic": - // canceled = true; - // if (GameStates.IsLobby) - // { - // break; - // } - // PlayerControl.LocalPlayer.RpcChangeRoleBasis(CustomRoles.PhantomTOHE); - // break; - - case "/setplayers": - case "/maxjogadores": - case "/设置最大玩家数": - case "/设置最大玩家数量": - case "/设置玩家数": - case "/设置玩家数量": - case "/玩家数": - case "/玩家数量": - case "/玩家": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - var numbereer = Convert.ToByte(subArgs); - if (numbereer > 15 && GameStates.IsVanillaServer) - { - Utils.SendMessage(GetString("Message.MaxPlayersFailByRegion")); - break; - } - Utils.SendMessage(GetString("Message.MaxPlayers") + numbereer); - if (GameStates.IsNormalGame) - GameOptionsManager.Instance.currentNormalGameOptions.MaxPlayers = numbereer; - - else if (GameStates.IsHideNSeek) - GameOptionsManager.Instance.currentHideNSeekGameOptions.MaxPlayers = numbereer; - break; - - case "/h": - case "/help": - case "/ajuda": - case "/хелп": - case "/хэлп": - case "/помощь": - case "/帮助": - case "/教程": - canceled = true; - Utils.ShowHelp(PlayerControl.LocalPlayer.PlayerId); - break; - - case "/icon": - case "/icons": - case "/符号": - case "/标志": - { - Utils.SendMessage(GetString("Command.icons"), PlayerControl.LocalPlayer.PlayerId, GetString("IconsTitle")); - break; - } - - case "/iconhelp": - case "/符号帮助": - case "/标志帮助": - { - Utils.SendMessage(GetString("Command.icons"), title: GetString("IconsTitle")); - break; - } - - case "/kc": - case "/kcount": - case "/количество": - case "/убийцы": - case "/存活阵营": - case "/阵营": - case "/存货阵营信息": - case "/阵营信息": - if (GameStates.IsLobby) break; - - if (!Options.EnableKillerLeftCommand.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - var allAlivePlayers = Main.AllAlivePlayerControls; - int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); - int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); - int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); - int apocnum = allAlivePlayers.Count(pc => pc.IsNeutralApocalypse() || pc.IsTransformedNeutralApocalypse()); - int covnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Coven)); - - var sub = new StringBuilder(); - sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); - - if (Options.ShowMadmatesInLeftCommand.GetBool()) - sub.Append(string.Format("\n\r" + GetString("Remaining.MadmateCount"), madnum)); - - if (Options.ShowApocalypseInLeftCommand.GetBool()) - sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); - - if (Options.ShowCovenInLeftCommand.GetBool()) - sub.Append(string.Format("\n\r" + GetString("Remaining.CovenCount"), covnum)); - - sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); - - Utils.SendMessage(sub.ToString(), PlayerControl.LocalPlayer.PlayerId); - break; - case "/vote": - case "/投票": - case "/票": - subArgs = args.Length != 2 ? "" : args[1]; - if (subArgs == "" || !int.TryParse(subArgs, out int arg)) - break; - var plr = Utils.GetPlayerById(arg); - - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (!Options.EnableVoteCommand.GetBool()) - { - Utils.SendMessage(GetString("VoteDisabled"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (Options.ShouldVoteCmdsSpamChat.GetBool()) - { - canceled = true; - } - - if (arg != 253) // skip - { - if (plr == null || !plr.IsAlive()) - { - Utils.SendMessage(GetString("VoteDead"), PlayerControl.LocalPlayer.PlayerId); - break; - } - } - if (!PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("CannotVoteWhenDead"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (GameStates.IsMeeting) - { - PlayerControl.LocalPlayer.RpcCastVote((byte)arg); - } - break; - - case "/d": - case "/death": - case "/morto": - case "/умер": - case "/причина": - case "/死亡原因": - case "/死亡": - canceled = true; - Logger.Info($"PlayerControl.LocalPlayer.PlayerId: {PlayerControl.LocalPlayer.PlayerId}", "/death command"); - if (GameStates.IsLobby) - { - Logger.Info("IsLobby", "/death command"); - Utils.SendMessage(text: GetString("Message.CanNotUseInLobby"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (PlayerControl.LocalPlayer.IsAlive()) - { - Logger.Info("IsAlive", "/death command"); - Utils.SendMessage(text: GetString("DeathCmd.HeyPlayer") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + GetString("DeathCmd.YouAreRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "\n\n" + GetString("DeathCmd.NotDead"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.Vote) - { - Logger.Info("DeathReason.Vote", "/death command"); - Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Ejected"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.Shrouded) - { - Logger.Info("DeathReason.Shrouded", "/death command"); - Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Shrouded"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.FollowingSuicide) - { - Logger.Info("DeathReason.FollowingSuicide", "/death command"); - Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Lovers"), sendTo: PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - Logger.Info("GetRealKiller()", "/death command"); - var killer = PlayerControl.LocalPlayer.GetRealKiller(out var MurderRole); - string killerName = killer == null ? "N/A" : killer.GetRealName(clientData: true); - string killerRole = killer == null ? "N/A" : Utils.GetRoleName(MurderRole); - Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.DeathReason") + "" + Utils.GetVitalText(PlayerControl.LocalPlayer.PlayerId) + "" + "\n\r" + "" + "\n\r" + GetString("DeathCmd.KillerName") + "" + killerName + "" + "\n\r" + GetString("DeathCmd.KillerRole") + "" + $"{killerRole}" + "", sendTo: PlayerControl.LocalPlayer.PlayerId); - - break; - } - - - case "/m": - case "/myrole": - case "/minhafunção": - case "/м": - case "/мояроль": - case "/身份": - case "/我": - case "/我的身份": - case "/我的职业": - canceled = true; - var role = PlayerControl.LocalPlayer.GetCustomRole(); - if (GameStates.IsInGame) - { - var lp = PlayerControl.LocalPlayer; - var Des = lp.GetRoleInfo(true); - var title = $"" + role.GetRoleTitle() + "\n"; - var Conf = new StringBuilder(); - var Sub = new StringBuilder(); - var rlHex = Utils.GetRoleColorCode(role); - var SubTitle = $"" + GetString("YourAddon") + "\n"; - - if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) - Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref Conf); - var cleared = Conf.ToString(); - var Setting = $"{GetString(role.ToString())} {GetString("Settings:")}\n"; - Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); - - - foreach (var subRole in Main.PlayerStates[lp.PlayerId].SubRoles.ToArray()) - Sub.Append($"\n\n" + $"" + Utils.GetRoleTitle(subRole) + Utils.GetInfoLong(subRole) + ""); - - if (Sub.ToString() != string.Empty) - { - var ACleared = Sub.ToString().Remove(0, 2); - ACleared = ACleared.Length > 1200 ? $"" + ACleared.RemoveHtmlTags() + "" : ACleared; - Sub.Clear().Append(ACleared); - } - - Utils.SendMessage(Des, lp.PlayerId, title, noReplay: true); - Utils.SendMessage("", lp.PlayerId, Conf.ToString(), noReplay: true); - if (Sub.ToString() != string.Empty) Utils.SendMessage(Sub.ToString(), lp.PlayerId, SubTitle, noReplay: true); - } - else - Utils.SendMessage((PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - - case "/me": - case "/我的权限": - case "/权限": - canceled = true; - subArgs = text.Length == 3 ? string.Empty : text.Remove(0, 3); - string Devbox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug ? "<#10e341>" : "<#e31010>"; - string UpBox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp ? "<#10e341>" : "<#e31010>"; - string ColorBox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().ColorCmd ? "<#10e341>" : "<#e31010>"; - - if (string.IsNullOrEmpty(subArgs)) - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}"); - } - else - { - if (byte.TryParse(subArgs, out byte meid)) - { - if (meid != PlayerControl.LocalPlayer.PlayerId) - { - var targetplayer = Utils.GetPlayerById(meid); - if (targetplayer != null && targetplayer.GetClient() != null) - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandTargetInfo"), targetplayer.PlayerId, targetplayer.GetRealName(clientData: true), targetplayer.GetClient().FriendCode, targetplayer.GetClient().GetHashedPuid(), targetplayer.FriendCode.GetDevUser().GetUserType())}"); - } - else - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{(GetString("Message.MeCommandInvalidID"))}"); - } - } - else - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}"); - } - } - else - { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{(GetString("Message.MeCommandInvalidID"))}"); - } - } - break; - - case "/t": - case "/template": - case "/шаблон": - case "/пример": - case "/模板": - case "/模板信息": - canceled = true; - if (args.Length > 1) TemplateManager.SendTemplate(args[1]); - else Utils.SendMessage($"{GetString("ForExample")}:\n{args[0]} test", PlayerControl.LocalPlayer.PlayerId); - break; - - case "/mw": - case "/messagewait": - case "/消息等待时间": - case "/消息冷却": - 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 "/tpout": - case "/传送出": - case "/传出": - canceled = true; - if (!GameStates.IsLobby) break; - if (!Options.PlayerCanUseTP.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - PlayerControl.LocalPlayer.RpcTeleport(new Vector2(0.1f, 3.8f)); - break; - case "/tpin": - case "/传进": - case "/传送进": - canceled = true; - if (!GameStates.IsLobby) break; - if (!Options.PlayerCanUseTP.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - PlayerControl.LocalPlayer.RpcTeleport(new Vector2(-0.2f, 1.3f)); - break; - - case "/say": - case "/s": - case "/с": - case "/сказать": - case "/说": - canceled = true; - if (args.Length > 1) - Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromTheHost")} ~ {PlayerControl.LocalPlayer.GetRealName(clientData: true)}"); - break; - - case "/mid": - case "/玩家列表": - case "/玩家信息": - case "/玩家编号列表": - canceled = true; - string msgText1 = GetString("PlayerIdList"); - foreach (var pc in Main.AllPlayerControls) - { - if (pc == null) continue; - msgText1 += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); - } - Utils.SendMessage(msgText1, PlayerControl.LocalPlayer.PlayerId); - break; - - case "/ban": - case "/banir": - case "/бан": - case "/забанить": - case "/封禁": - canceled = true; - - string banReason = ""; - if (args.Length < 3) - { - Utils.SendMessage(GetString("BanCommandNoReason"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - subArgs = args[1]; - banReason = string.Join(" ", args.Skip(2)); - } - //subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte banPlayerId)) - { - Utils.SendMessage(GetString("BanCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (banPlayerId == 0) - { - Utils.SendMessage(GetString("BanCommandBanHost"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - var bannedPlayer = Utils.GetPlayerById(banPlayerId); - if (bannedPlayer == null) - { - Utils.SendMessage(GetString("BanCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - // Ban the specified player - AmongUsClient.Instance.KickPlayer(bannedPlayer.GetClientId(), true); - string bannedPlayerName = bannedPlayer.GetRealName(); - string textToSend1 = $"{bannedPlayerName} {GetString("BanCommandBanned")}{PlayerControl.LocalPlayer.name} \nReason: {banReason}\n"; - if (GameStates.IsInGame) - { - textToSend1 += $" {GetString("BanCommandBannedRole")} {GetString(bannedPlayer.GetCustomRole().ToString())}"; - } - Utils.SendMessage(textToSend1); - //string moderatorName = PlayerControl.LocalPlayer.GetRealName().ToString(); - //int startIndex = moderatorName.IndexOf("♥") + "♥".Length; - //moderatorName = moderatorName.Substring(startIndex); - //string extractedString = - string moderatorFriendCode = PlayerControl.LocalPlayer.FriendCode.ToString(); - string bannedPlayerFriendCode = bannedPlayer.FriendCode.ToString(); - string modLogname = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n1) ? n1 : ""; - string banlogname = Main.AllPlayerNames.TryGetValue(bannedPlayer.PlayerId, out var n11) ? n11 : ""; - string logMessage = $"[{DateTime.Now}] {moderatorFriendCode},{modLogname} Banned: {bannedPlayerFriendCode},{banlogname} Reason: {banReason}"; - File.AppendAllText(modLogFiles, logMessage + Environment.NewLine); - break; - - case "/warn": - case "/aviso": - case "/варн": - case "/пред": - case "/предупредить": - case "/警告": - case "/提醒": - canceled = true; - subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte warnPlayerId)) - { - Utils.SendMessage(GetString("WarnCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (warnPlayerId == 0) - { - Utils.SendMessage(GetString("WarnCommandWarnHost"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - var warnedPlayer = Utils.GetPlayerById(warnPlayerId); - if (warnedPlayer == null) - { - Utils.SendMessage(GetString("WarnCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - // warn the specified player - string textToSend2 = ""; - string warnReason = "Reason : Not specified\n"; - string warnedPlayerName = warnedPlayer.GetRealName(); - //textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} ~{player.name}"; - if (args.Length > 2) - { - warnReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; - } - else - { - Utils.SendMessage(GetString("WarnExample"), PlayerControl.LocalPlayer.PlayerId); - } - textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} {warnReason} ~{PlayerControl.LocalPlayer.name}"; - Utils.SendMessage(textToSend2); - //string moderatorName1 = PlayerControl.LocalPlayer.GetRealName().ToString(); - //int startIndex1 = moderatorName1.IndexOf("♥") + "♥".Length; - //moderatorName1 = moderatorName1.Substring(startIndex1); - string modLogname1 = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n2) ? n2 : ""; - string warnlogname = Main.AllPlayerNames.TryGetValue(warnedPlayer.PlayerId, out var n12) ? n12 : ""; - - string moderatorFriendCode1 = PlayerControl.LocalPlayer.FriendCode.ToString(); - string warnedPlayerFriendCode = warnedPlayer.FriendCode.ToString(); - string warnedPlayerHashPuid = warnedPlayer.GetClient().GetHashedPuid(); - string logMessage1 = $"[{DateTime.Now}] {moderatorFriendCode1},{modLogname1} Warned: {warnedPlayerFriendCode},{warnedPlayerHashPuid},{warnlogname} Reason: {warnReason}"; - File.AppendAllText(modLogFiles, logMessage1 + Environment.NewLine); - - break; - - case "/kick": - case "/expulsar": - case "/кик": - case "/кикнуть": - case "/выгнать": - case "/踢出": - case "/踢": - canceled = true; - 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; - } - - // Kick the specified player - AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), false); - string kickedPlayerName = kickedPlayer.GetRealName(); - string kickReason = "Reason : Not specified\n"; - if (args.Length > 2) - kickReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; - else - { - Utils.SendMessage("Use /kick [id] [reason] in future. \nExample :-\n /kick 5 not following rules", PlayerControl.LocalPlayer.PlayerId); - } - string textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")} {PlayerControl.LocalPlayer.name} \n {kickReason}"; - - if (GameStates.IsInGame) - { - textToSend += $" {GetString("KickCommandKickedRole")} {GetString(kickedPlayer.GetCustomRole().ToString())}"; - } - Utils.SendMessage(textToSend); - //string moderatorName2 = PlayerControl.LocalPlayer.GetRealName().ToString(); - //int startIndex2 = moderatorName2.IndexOf("♥") + "♥".Length; - //moderatorName2 = moderatorName2.Substring(startIndex2); - - string modLogname2 = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n3) ? n3 : ""; - string kicklogname = Main.AllPlayerNames.TryGetValue(kickedPlayer.PlayerId, out var n13) ? n13 : ""; - - string moderatorFriendCode2 = PlayerControl.LocalPlayer.FriendCode.ToString(); - string kickedPlayerFriendCode = kickedPlayer.FriendCode.ToString(); - string kickedPlayerHashPuid = kickedPlayer.GetClient().GetHashedPuid(); - string logMessage2 = $"[{DateTime.Now}] {moderatorFriendCode2},{modLogname2} Kicked: {kickedPlayerFriendCode},{kickedPlayerHashPuid},{kicklogname} Reason: {kickReason}"; - File.AppendAllText(modLogFiles, logMessage2 + Environment.NewLine); - - break; - - case "/tagcolor": - case "/tagcolour": - case "/标签颜色": - case "/附加名称颜色": - canceled = true; - string name = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n) ? n : ""; - if (name == "") break; - if (!name.Contains('\r') && PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag()) - { - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("ColorCommandNoLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) - { - Logger.Msg($"{subArgs}", "tagcolor"); - Utils.SendMessage(GetString("TagColorInvalidHexCode"), PlayerControl.LocalPlayer.PlayerId); - break; - } - string tagColorFilePath = $"{sponsorTagsFiles}/{PlayerControl.LocalPlayer.FriendCode}.txt"; - if (!File.Exists(tagColorFilePath)) - { - Logger.Msg($"File Not exist, creating file at {tagColorFilePath}", "tagcolor"); - File.Create(tagColorFilePath).Close(); - } - File.WriteAllText(tagColorFilePath, $"{subArgs}"); - } - break; - - case "/exe": - case "/уничтожить": - case "/повесить": - case "/казнить": - case "/казнь": - case "/мут": - case "/驱逐": - case "/驱赶": - 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; - player.SetDeathReason(PlayerState.DeathReason.etc); - player.SetRealKiller(PlayerControl.LocalPlayer); - Main.PlayerStates[player.PlayerId].SetDead(); - player.RpcExileV2(); - MurderPlayerPatch.AfterPlayerDeathTasks(PlayerControl.LocalPlayer, player, GameStates.IsMeeting); - - if (player.IsHost()) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); - else Utils.SendMessage(string.Format(GetString("Message.Executed"), player.Data.PlayerName)); - } - break; - - case "/kill": - case "/matar": - case "/убить": - case "/击杀": - case "/杀死": - 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.RpcMurderPlayer(target); - if (target.IsHost()) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); - else Utils.SendMessage(string.Format(GetString("Message.Executed"), target.Data.PlayerName)); - - _ = new LateTask(() => - { - Utils.NotifyRoles(NoCache: true); - - }, 0.2f, "Update NotifyRoles players after /kill"); - } - break; - - case "/colour": - case "/color": - case "/cor": - case "/цвет": - case "/颜色": - case "/更改颜色": - case "/修改颜色": - case "/换颜色": - 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": - case "/sair": - case "/退出": - case "/退": - canceled = true; - Utils.SendMessage(GetString("Message.CanNotUseByHost"), PlayerControl.LocalPlayer.PlayerId); - break; - - case "/xf": - case "/修复": - case "/修": - canceled = true; - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - foreach (var pc in Main.AllPlayerControls) - { - if (pc.IsAlive()) continue; - - 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": - case "/айди": - case "/编号": - case "/玩家编号": - canceled = true; - string msgText = GetString("PlayerIdList"); - foreach (var pc in Main.AllPlayerControls) - { - if (pc == null) continue; - msgText += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); - } - 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 "/setrole": - case "/设置的职业": - case "/指定的职业": - canceled = true; - subArgs = text.Remove(0, 8); - SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug); - break; - - case "/changerole": - case "/mudarfunção": - case "/改变职业": - case "/修改职业": - canceled = true; - if (GameStates.IsHideNSeek) break; - if (!(DebugModeManager.AmDebugger && GameStates.IsInGame)) break; - if (GameStates.IsOnlineGame && !PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug) break; - subArgs = text.Remove(0, 11); - var setRole = FixRoleNameInput(subArgs).ToLower().Trim().Replace(" ", string.Empty); - Logger.Info(setRole, "changerole Input"); - foreach (var rl in CustomRolesHelper.AllRoles) - { - if (rl.IsVanilla()) continue; - var roleName = GetString(rl.ToString()).ToLower().Trim().TrimStart('*').Replace(" ", string.Empty); - //Logger.Info(roleName, "2"); - if (setRole == roleName) - { - PlayerControl.LocalPlayer.GetRoleClass()?.OnRemove(PlayerControl.LocalPlayer.PlayerId); - PlayerControl.LocalPlayer.RpcChangeRoleBasis(rl); - PlayerControl.LocalPlayer.RpcSetCustomRole(rl); - PlayerControl.LocalPlayer.GetRoleClass().OnAdd(PlayerControl.LocalPlayer.PlayerId); - Utils.SendMessage(string.Format("Debug Set your role to {0}", rl.ToString()), PlayerControl.LocalPlayer.PlayerId); - Utils.NotifyRoles(NoCache: true); - Utils.MarkEveryoneDirtySettings(); - break; - } - } - break; - - case "/end": - case "/encerrar": - case "/завершить": - case "/结束": - case "/结束游戏": - canceled = true; - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); - GameManager.Instance.LogicFlow.CheckEndCriteria(); - break; - case "/cosid": - case "/装扮编号": - case "/衣服编号": - 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": - case "/强制过会议": - case "/强制跳过会议": - case "/过会议": - case "/结束会议": - case "/强制结束会议": - case "/跳过会议": - canceled = true; - if (GameStates.IsMeeting) - { - MeetingHud.Instance.RpcClose(); - } - else - { - PlayerControl.LocalPlayer.NoCheckStartMeeting(null, force: true); - } - break; - - case "/cs": - case "/播放声音": - case "/播放音效": - canceled = true; - subArgs = text.Remove(0, 3); - PlayerControl.LocalPlayer.RPCPlayCustomSound(subArgs.Trim()); - break; - - case "/sd": - case "/播放音效给": - case "/播放声音给": - 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; - - case "/poll": - case "/发起投票": - case "/执行投票": - canceled = true; - - - if (args.Length == 2 && args[1] == GetString("Replay") && Pollvotes.Any() && PollMSG != string.Empty) - { - Utils.SendMessage(PollMSG); - break; - } - - PollMSG = string.Empty; - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - Polltimer = 120f; - - static System.Collections.IEnumerator StartPollCountdown() - { - if (!Pollvotes.Any() || !GameStates.IsLobby) - { - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - - yield break; - } - bool playervoted = (Main.AllPlayerControls.Length - 1) > Pollvotes.Values.Sum(); - - - while (playervoted && Polltimer > 0f) - { - if (!Pollvotes.Any() || !GameStates.IsLobby) - { - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - - yield break; - } - playervoted = (Main.AllPlayerControls.Length - 1) > Pollvotes.Values.Sum(); - Polltimer -= Time.deltaTime; - yield return null; - } - - if (!Pollvotes.Any() || !GameStates.IsLobby) - { - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - - yield break; - } - - Logger.Info($"FINNISHED!! playervote?: {!playervoted} polltime?: {Polltimer <= 0}", "/poll - StartPollCountdown"); - - DetermineResults(); - } - - static void DetermineResults() - { - int basenum = Pollvotes.Values.Max(); - var winners = Pollvotes.Where(x => x.Value == basenum); - - string msg = ""; - - Color32 clr = new(47, 234, 45, 255); //Main.PlayerColors.First(x => x.Key == PlayerControl.LocalPlayer.PlayerId).Value; - var tytul = Utils.ColorString(clr, GetString("PollResultTitle")); - - if (winners.Count() == 1) - { - var losers = Pollvotes.Where(x => x.Key != winners.First().Key); - msg = string.Format(GetString("Poll.Result"), $"{winners.First().Key}{PollQuestions[winners.First().Key]}", winners.First().Value); - - for (int i = 0; i < losers.Count(); i++) - { - msg += $"\n{losers.ElementAt(i).Key} / {losers.ElementAt(i).Value} {PollQuestions[losers.ElementAt(i).Key]}"; - - } - msg += ""; - - - Utils.SendMessage(msg, title: tytul); - } - else - { - var tienum = Pollvotes.Values.Max(); - var tied = Pollvotes.Where(x => x.Value == tienum); - - for (int i = 0; i < (tied.Count() - 1); i++) - { - msg += "\n" + tied.ElementAt(i).Key + PollQuestions[tied.ElementAt(i).Key] + " & "; - } - msg += "\n" + tied.Last().Key + PollQuestions[tied.Last().Key]; - - Utils.SendMessage(string.Format(GetString("Poll.Tied"), msg, tienum), title: tytul); - } - - Pollvotes.Clear(); - PollQuestions.Clear(); - PollVoted.Clear(); - } - - - if (Main.AllPlayerControls.Length < 3) - { - Utils.SendMessage(GetString("Poll.MissingPlayers"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("Poll.OnlyInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (args.SkipWhile(x => !x.Contains('?')).ToArray().Length < 3 || !args.Any(x => x.Contains('?'))) - { - Utils.SendMessage(GetString("PollUsage"), PlayerControl.LocalPlayer.PlayerId); - break; - } - var resultat = args.TakeWhile(x => !x.Contains('?')).Concat(args.SkipWhile(x => !x.Contains('?')).Take(1)); - - string tytul = string.Join(" ", resultat.Skip(1)); - bool Longtitle = tytul.Length > 30; - tytul = Utils.ColorString(Palette.PlayerColors[PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId], tytul); - var altTitle = Utils.ColorString(new Color32(151, 198, 230, 255), GetString("PollTitle")); - - var ClearTIT = args.ToList(); - ClearTIT.RemoveRange(0, resultat.ToArray().Length); - - var Questions = ClearTIT.ToArray(); - string msg = ""; - - - if (Longtitle) msg += "" + tytul + "\n\n"; - for (int i = 0; i < Math.Clamp(Questions.Length, 2, 5); i++) - { - msg += Utils.ColorString(RndCLR(), $"{char.ToUpper((char)(i + 65))}) {Questions[i]}\n"); - Pollvotes[char.ToUpper((char)(i + 65))] = 0; - PollQuestions[char.ToUpper((char)(i + 65))] = $"〖 {Questions[i]} 〗"; - } - msg += $"\n{GetString("Poll.Begin")}"; - msg += $"\n{GetString("Poll.TimeInfo")}"; - PollMSG = !Longtitle ? "" + tytul + "\n\n" + msg : msg; - - Logger.Info($"Poll message: {msg}", "MEssapoll"); - - Utils.SendMessage(msg, title: !Longtitle ? tytul : altTitle); - - Main.Instance.StartCoroutine(StartPollCountdown()); - - - static Color32 RndCLR() - { - byte r, g, b; - - r = (byte)IRandom.Instance.Next(45, 185); - g = (byte)IRandom.Instance.Next(45, 185); - b = (byte)IRandom.Instance.Next(45, 185); - - return new Color32(r, g, b, 255); - } - - break; - - case "/rps": - case "/剪刀石头布": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - subArgs = args.Length != 2 ? "" : args[1]; - - if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - - if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice)) - { - Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (playerChoice < 0 || playerChoice > 2) - { - Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botChoice = rand.Next(0, 3); - var rpsList = new List { GetString("Rock"), GetString("Paper"), GetString("Scissors") }; - if (botChoice == playerChoice) - { - Utils.SendMessage(string.Format(GetString("RpsDraw"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); - } - else if ((botChoice == 0 && playerChoice == 2) || - (botChoice == 1 && playerChoice == 0) || - (botChoice == 2 && playerChoice == 1)) - { - Utils.SendMessage(string.Format(GetString("RpsLose"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); - } - else - { - Utils.SendMessage(string.Format(GetString("RpsWin"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); - } - break; - } - case "/coinflip": - case "/抛硬币": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - - if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("CoinFlipCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botChoice = rand.Next(1, 101); - var coinSide = (botChoice < 51) ? GetString("Heads") : GetString("Tails"); - Utils.SendMessage(string.Format(GetString("CoinFlipResult"), coinSide), PlayerControl.LocalPlayer.PlayerId); - break; - } - case "/gno": - case "/猜数字": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo)) - { - Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (guessedNo < 0 || guessedNo > 99) - { - Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - int targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; - if (Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] == -1) - { - var rand = IRandom.Instance; - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = rand.Next(0, 100); - targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; - } - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]--; - if (Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] == 0 && guessedNo != targetNumber) - { - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = -1; - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] = 7; - //targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; - Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (guessedNo < targetNumber) - { - Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); - break; - } - else if (guessedNo > targetNumber) - { - Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - Utils.SendMessage(string.Format(GetString("GNoWon"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = -1; - Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] = 7; - break; - } - - } - case "/rand": - case "/XY数字": - case "/范围游戏": - case "/猜范围": - case "/范围": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - subArgs = args.Length != 3 ? "" : args[1]; - subArgs2 = args.Length != 3 ? "" : args[2]; - - if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) - { - Utils.SendMessage(GetString("RandCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice1) || subArgs2 == "" || !int.TryParse(subArgs2, out int playerChoice2)) - { - Utils.SendMessage(GetString("RandCommandInfo"), PlayerControl.LocalPlayer.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botResult = rand.Next(playerChoice1, playerChoice2 + 1); - Utils.SendMessage(string.Format(GetString("RandResult"), botResult), PlayerControl.LocalPlayer.PlayerId); - break; - } - - case "/8ball": - case "/8号球": - case "/幸运球": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); - break; - } - canceled = true; - var rando = IRandom.Instance; - int result = rando.Next(0, 16); - string str = ""; - switch (result) - { - case 0: - str = GetString("8BallYes"); - break; - case 1: - str = GetString("8BallNo"); - break; - case 2: - str = GetString("8BallMaybe"); - break; - case 3: - str = GetString("8BallTryAgainLater"); - break; - case 4: - str = GetString("8BallCertain"); - break; - case 5: - str = GetString("8BallNotLikely"); - break; - case 6: - str = GetString("8BallLikely"); - break; - case 7: - str = GetString("8BallDontCount"); - break; - case 8: - str = GetString("8BallStop"); - break; - case 9: - str = GetString("8BallPossibly"); - break; - case 10: - str = GetString("8BallProbably"); - break; - case 11: - str = GetString("8BallProbablyNot"); - break; - case 12: - str = GetString("8BallBetterNotTell"); - break; - case 13: - str = GetString("8BallCantPredict"); - break; - case 14: - str = GetString("8BallWithoutDoubt"); - break; - case 15: - str = GetString("8BallWithDoubt"); - break; - } - Utils.SendMessage("" + str + "", PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medium), GetString("8BallTitle"))); - break; - case "/start": - case "/开始": - case "/старт": - canceled = true; - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), PlayerControl.LocalPlayer.PlayerId); - break; - } - if (GameStates.IsCountDown) - { - Utils.SendMessage(GetString("StartCommandCountdown"), PlayerControl.LocalPlayer.PlayerId); - break; - } - subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !int.TryParse(subArgs, out int countdown)) - { - countdown = 5; - } - else - { - countdown = int.Parse(subArgs); - } - if (countdown < 0 || countdown > 99) - { - Utils.SendMessage(string.Format(GetString("StartCommandInvalidCountdown"), 0, 99), PlayerControl.LocalPlayer.PlayerId); - break; - } - GameStartManager.Instance.BeginGame(); - GameStartManager.Instance.countDownTimer = countdown; - Utils.SendMessage(string.Format(GetString("StartCommandStarted"), PlayerControl.LocalPlayer.name)); - Logger.Info("Game Starting", "ChatCommand"); - 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); - - __instance.quickChatMenu.Clear(); - __instance.quickChatField.Clear(); - } - return !canceled; - } - - public static string FixRoleNameInput(string text) - { - text = text.Replace("着", "者").Trim().ToLower(); - return text switch - { - // Because of partial translation conflicts (zh-cn and zh-tw) - // Need to wait for follow-up finishing - - /* - // GM - "GM(遊戲大師)" or "管理员" or "管理" or "gm" or "GM" => GetString("GM"), - - // 原版职业 - "船員" or "船员" or "白板" or "天选之子" => GetString("CrewmateTOHE"), - "工程師" or "工程师" => GetString("EngineerTOHE"), - "科學家" or "科学家" => GetString("ScientistTOHE"), - "守護天使" or "守护天使" => GetString("GuardianAngelTOHE"), - "偽裝者" or "内鬼" => GetString("ImpostorTOHE"), - "變形者" or "变形者" => GetString("ShapeshifterTOHE"), - - // 隱藏職業 and 隐藏职业 - "陽光開朗大男孩" or "阳光开朗大男孩" => GetString("Sunnyboy"), - "吟遊詩人" or "吟游诗人" => GetString("Bard"), - "核爆者" or "核武器" => GetString("Nuker"), - - // 偽裝者陣營職業 and 内鬼阵营职业 - "賞金獵人" or "赏金猎人" or "赏金" => GetString("BountyHunter"), - "煙火工匠" or "烟花商人" or "烟花爆破者" or "烟花" => GetString("Fireworker"), - "嗜血殺手" or "嗜血杀手" or "嗜血" => GetString("Mercenary"), - "百变怪" or "千面鬼" or "千面" => GetString("ShapeMaster"), - "吸血鬼" or "吸血" => GetString("Vampire"), - "吸血鬼之王" or "吸血鬼女王" => GetString("Vampiress"), - "術士" or "术士" => GetString("Warlock"), - "刺客" or "忍者" => GetString("Ninja"), - "僵屍" or "僵尸" or"殭屍" or "丧尸" => GetString("Zombie"), - "駭客" or "骇客" or "黑客" => GetString("Anonymous"), - "礦工" or "矿工" => GetString("Miner"), - "殺人機器" or "杀戮机器" or "杀戮" or "机器" or "杀戮兵器" => GetString("KillingMachine"), - "通緝犯" or "逃逸者" or "逃逸" => GetString("Escapist"), - "女巫" => GetString("Witch"), - "傀儡師" or "傀儡师" or "傀儡" => GetString("Puppeteer"), - "主謀" or "策划者" => GetString("Mastermind"), - "時間竊賊" or "蚀时者" or "蚀时" or "偷时" => GetString("TimeThief"), - "狙擊手" or "狙击手" or "狙击" => GetString("Sniper"), - "送葬者" or "暗杀者" => GetString("Undertaker"), - "裂縫製造者" or "裂缝制造者" => GetString("RiftMaker"), - "邪惡的追踪者" or "邪恶追踪者" or "邪恶的追踪者" => GetString("EvilTracker"), - "邪惡賭怪" or "邪恶赌怪" or "坏赌" or "恶赌" or "邪恶赌怪" => GetString("EvilGuesser"), - "監管者" or "监管者" or "监管" => GetString("AntiAdminer"), - "狂妄殺手" or "狂妄杀手" => GetString("Arrogance"), - "自爆兵" or "自爆" => GetString("Bomber"), - "清道夫" or "清道" => GetString("Scavenger"), - "陷阱師" or "诡雷" => GetString("Trapster"), - "歹徒" => GetString("Gangster"), - "清潔工" or "清理工" or "清洁工" => GetString("Cleaner"), - "球狀閃電" or "球状闪电" => GetString("Lightning"), - "貪婪者" or "贪婪者" or "贪婪" => GetString("Greedy"), - "被詛咒的狼" or "呪狼" => GetString("CursedWolf"), - "換魂師" or "夺魂者" or "夺魂" => GetString("SoulCatcher"), - "快槍手" or "快枪手" or "快枪" => GetString("QuickShooter"), - "隱蔽者" or "隐蔽者" or "小黑人" => GetString("Camouflager"), - "抹除者" or "抹除" => GetString("Eraser"), - "肢解者" or "肢解" => GetString("Butcher"), - "劊子手" or "刽子手" => GetString("Hangman"), - "隱身人" or "隐匿者" or "隐匿" or "隐身" => GetString("Swooper"), - "船鬼" => GetString("Crewpostor"), - "野人" => GetString("Wildling"), - "騙術師" or "骗术师" => GetString("Trickster"), - "衛道士" or "卫道士" or "内鬼市长" => GetString("Vindicator"), - "寄生蟲" or "寄生虫" => GetString("Parasite"), - "分散者" or "分散" => GetString("Disperser"), - "抑鬱者" or "抑郁者" or "抑郁" => GetString("Inhibitor"), - "破壞者" or "破坏者" or "破坏" => GetString("Saboteur"), - "議員" or "邪恶法官" or "议员" or "邪恶审判" => GetString("Councillor"), - "眩暈者" or "眩晕者" or "眩晕" => GetString("Dazzler"), - "簽約人" or "死亡契约" or "死亡" or "锲约" => GetString("Deathpact"), - "吞噬者" or "吞噬" => GetString("Devourer"), - "軍師" or "军师" => GetString("Consigliere"), - "化型者" or "化形者" => GetString("Morphling"), - "躁動者" or "龙卷风" => GetString("Twister"), - "策畫者" or "潜伏者" or "潜伏" => GetString("Lurker"), - "罪犯" => GetString("Convict"), - "幻想家" or "幻想" => GetString("Visionary"), - "逃亡者" or "逃亡" => GetString("Refugee"), - "潛伏者" or "失败者" or "失败的man" or "失败" => GetString("Underdog"), - "賭博者" or "速度者" or "速度" => GetString("Ludopath"), - "懸賞者" or "教父" => GetString("Godfather"), - "天文學家" or "天文学家" or "天文家" or "天文学" => GetString("Chronomancer"), - "設陷者" or "设陷者" or "设陷" => GetString("Pitfall"), - "狂戰士" or "狂战士" or "升级者" or "狂战士" => GetString("Berserker"), - "壞迷你船員" or "坏迷你船员" or "坏小孩" or "坏迷你" => GetString("EvilMini"), - "勒索者" or "勒索" => GetString("Blackmailer"), - "教唆者" or "教唆" => GetString("Instigator"), - - // 船員陣營職業 and 船员阵营职业 - "擺爛人" or "摆烂人" or "摆烂" => GetString("Needy"), - "大明星" or "明星" => GetString("SuperStar"), - "網紅" or "网红" => GetString("Celebrity"), - "清洗者" or "清洗" => GetString("Cleanser"), - "守衛者" or "守卫者" => GetString("Keeper"), - "俠客" or "侠客" or "正义使者" => GetString("Knight"), - "市長" or "市长" => GetString("Mayor"), - "被害妄想症" or "被害妄想" or "被迫害妄想症" or "被害" or "妄想" or "妄想症" => GetString("Paranoia"), - "愚者" => GetString("Psychic"), - "修理工" or "修理" or "修理大师" => GetString("Mechanic"), - "警長" or "警长" => GetString("Sheriff"), - "義警" or "义务警员" or "警员" => GetString("Vigilante"), - "監禁者" or "狱警" or "狱卒" => GetString("Jailer"), - "模仿者" or "模仿猫" or "模仿" => GetString("CopyCat"), - "告密者" => GetString("Snitch"), - "展現者" or "展现者" or "展现" => GetString("Marshall"), - "增速師" or "增速者" or "增速" => GetString("SpeedBooster"), - "法醫" or "法医" => GetString("Doctor"), - "獨裁主義者" or "独裁者" or "独裁" => GetString("Dictator"), - "偵探" or "侦探" => GetString("Detective"), - "正義賭怪" or "正义赌怪" or "好赌" or "正义的赌怪" => GetString("NiceGuesser"), - "賭場管理員" or "竞猜大师" or "竞猜" => GetString("GuessMaster"), - "傳送師" or "传送师" => GetString("Transporter"), - "時間大師" or "时间操控者" or "时间操控" => GetString("TimeManager"), - "老兵" => GetString("Veteran"), - "埋雷兵" => GetString("Bastion"), - "保鑣" or "保镖" => GetString("Bodyguard"), - "贗品商" or "赝品商" => GetString("Deceiver"), - "擲彈兵" or "掷雷兵" => GetString("Grenadier"), - "軍醫" or "医生" => GetString("Medic"), - "占卜師" or "调查员" or "占卜师" => GetString("FortuneTeller"), - "法官" or "正义法官" or "正义审判" => GetString("Judge"), - "殯葬師" or "入殓师" => GetString("Mortician"), - "通靈師" or "通灵师" => GetString("Mediumshiper"), - "和平之鴿" or "和平之鸽" => GetString("Pacifist"), - "窺視者" or "观察者" or "观察" => GetString("Observer"), - "君主" => GetString("Monarch"), - "預言家" or "预言家" or "预言" => GetString("Overseer"), - "驗屍官" or "验尸官" or "验尸" => GetString("Coroner"), - "正義的追蹤者" or "正义追踪者" or "正义的追踪者" => GetString("Tracker"), - "商人" => GetString("Merchant"), - "總統" or "总统" => GetString("President"), - "獵鷹" or "猎鹰" => GetString("Hawk"), - "捕快" or "下属" => GetString("Deputy"), - "算命師" or "研究者" => GetString("Investigator"), - "守護者" or "守护者" or "守护" => GetString("Guardian"), - "賢者" or "瘾君子" or "醉酒" => GetString("Addict"), - "鼹鼠" => GetString("Mole"), - "藥劑師" or "炼金术士" or "药剂" => GetString("Alchemist"), - "尋跡者" or "寻迹者" or "寻迹" or "寻找鸡腿" => GetString("Tracefinder"), - "先知" or "神谕" or "神谕者" => GetString("Oracle"), - "靈魂論者" or "灵魂论者" => GetString("Spiritualist"), - "變色龍" or "变色龙" or "变色" => GetString("Chameleon"), - "檢查員" or "检查员" or "检查" => GetString("Inspector"), - "仰慕者" or "仰慕" => GetString("Admirer"), - "時間之主" or "时间之主" or "回溯时间" => GetString("TimeMaster"), - "十字軍" or "十字军" => GetString("Crusader"), - "遐想者" or "遐想" => GetString("Reverie"), - "瞭望者" or "瞭望员" => GetString("Lookout"), - "通訊員" or "通信员" => GetString("Telecommunication"), - "執燈人" or "执灯人" or "执灯" or "灯人" or "小灯人" => GetString("Lighter"), - "任務管理員" or "任务管理者" => GetString("TaskManager"), - "目擊者" or "目击者" or "目击" => GetString("Witness"), - "換票師" or "换票师" => GetString("Swapper"), - "警察局長" or "警察局长" => GetString("ChiefOfPolice"), - "好迷你船員" or "好迷你船员" or "好迷你" or "好小孩" => GetString("NiceMini"), - "間諜" or "间谍" => GetString("Spy"), - "隨機者" or "萧暮" or "暮" or "萧暮不姓萧" => GetString("Randomizer"), - "猜想者" or "猜想" or "谜团" => GetString("Enigma"), - "船長" or "舰长" or "船长" => GetString("Captain"), - "慈善家" or "恩人" => GetString("Benefactor"), - - // 中立陣營職業 and 中立阵营职业 - "小丑" or "丑皇" => GetString("Jester"), - "縱火犯" or "纵火犯" or "纵火者" or "纵火" => GetString("Arsonist"), - "焚燒狂" or "焚烧狂" or "焚烧" => GetString("Pyromaniac"), - "神風特攻隊" or "神风特攻队" => GetString("Kamikaze"), - "獵人" or "猎人" => GetString("Huntsman"), - "恐怖分子" => GetString("Terrorist"), - "暴民" or "处刑人" or "处刑" or "处刑者" => GetString("Executioner"), - "律師" or "律师" => GetString("Lawyer"), - "投機主義者" or "投机者" or "投机" => GetString("Opportunist"), - "瑪利歐" or "马里奥" => GetString("Vector"), - "豺狼" or "蓝狼" => GetString("Jackal"), - "神" or "上帝" => GetString("God"), - "冤罪師" or "冤罪师" or "冤罪" => GetString("Innocent"), - "暗殺者" or "隐形者" =>GetString("Stealth"), - "企鵝" or "企鹅" =>GetString("Penguin"), - "鵜鶘" or "鹈鹕" => GetString("Pelican"), - "疫醫" or "瘟疫学家" => GetString("PlagueDoctor"), - "革命家" or "革命者" => GetString("Revolutionist"), - "單身狗" => GetString("Hater"), - "柯南" => GetString("Konan"), - "玩家" => GetString("Demon"), - "潛藏者" or "潜藏" => GetString("Stalker"), - "工作狂" => GetString("Workaholic"), - "至日者" or "至日" => GetString("Solsticer"), - "集票者" or "集票" => GetString("Collector"), - "挑釁者" or "自爆卡车" => GetString("Provocateur"), - "嗜血騎士" or "嗜血骑士" => GetString("BloodKnight"), - "瘟疫之源" or "瘟疫使者" => GetString("PlagueBearer"), - "萬疫之神" or "瘟疫" => GetString("Pestilence"), - "故障者" or "缺点者" or "缺点" => GetString("Glitch"), - "跟班" or "跟班小弟" => GetString("Sidekick"), - "追隨者" or "赌徒" or "下注" => GetString("Follower"), - "魅魔" => GetString("Cultist"), - "連環殺手" or "连环杀手" => GetString("SerialKiller"), - "劍聖" or "天启" => GetString("Juggernaut"), - "感染者" or "感染" => GetString("Infectious"), - "病原體" or "病毒" => GetString("Virus"), - "起訴人" or "起诉人" => GetString("Pursuer"), - "怨靈" or "幽灵" => GetString("Phantom"), - "挑戰者" or "决斗者" or "挑战者" => GetString("Pirate"), - "炸彈王" or "炸弹狂" or "煽动者" => GetString("Agitater"), - "獨行者" or "独行者" => GetString("Maverick"), - "被詛咒的靈魂" or "诅咒之人" => GetString("CursedSoul"), - "竊賊" or "小偷" => GetString("Pickpocket"), - "背叛者" or "背叛" => GetString("Traitor"), - "禿鷲" or "秃鹫" => GetString("Vulture"), - "搗蛋鬼" or "任务执行者" => GetString("Taskinator"), - "麵包師" or "面包师" => GetString("Baker"), - "飢荒" or "饥荒" => GetString("Famine"), - "靈魂召喚者" or "灵魂召唤者" => GetString("Spiritcaller"), - "失憶者" or "失忆者" or "失忆" => GetString("Amnesiac"), - "模仿家" or "效仿者" => GetString("Imitator"), - "強盜" => GetString("Bandit"), - "分身者" => GetString("Doppelganger"), - "受虐狂" => GetString("PunchingBag"), - "賭神" or "末日赌怪" => GetString("Doomsayer"), - "裹屍布" or "裹尸布" => GetString("Shroud"), - "月下狼人" or "狼人" => GetString("Werewolf"), - "薩滿" or "萨满" => GetString("Shaman"), - "冒險家" or "探索者" => GetString("Seeker"), - "精靈" or "小精灵" or "精灵" => GetString("Pixie"), - "咒魔" or "神秘者" => GetString("Occultist"), - "靈魂收割者" or "灵魂收集者" or "灵魂收集" or "收集灵魂" => GetString("SoulCollector"), - "薛丁格的貓" or "薛定谔的猫" => GetString("SchrodingersCat"), - "暗戀者" or "浪漫者" => GetString("Romantic"), - "報復者" or "复仇浪漫者" => GetString("VengefulRomantic"), - "絕情者" or "无情浪漫者" => GetString("RuthlessRomantic"), - "毒醫" or "投毒者" => GetString("Poisoner"), - "代碼工程師" or "巫师" => GetString("HexMaster"), - "幻影" or "魅影" => GetString("Wraith"), - "掃把星" or "扫把星" => GetString("Jinx"), - "魔藥師" or "药剂师" => GetString("PotionMaster"), - "死靈法師" or "亡灵巫师" => GetString("Necromancer"), - "測驗者" or "测验长" => GetString("Quizmaster"), - - // 附加職業 and 附加职业 - "絕境者" or "绝境者" => GetString("LastImpostor"), - "超頻" or "超频波" or "超频" => GetString("Overclocked"), - "戀人" or "恋人" => GetString("Lovers"), - "叛徒" => GetString("Madmate"), - "觀察者" or "窥视者" or "觀察" or "窥视" => GetString("Watcher"), - "閃電俠" or "闪电侠" or "閃電" or "闪电" => GetString("Flash"), - "持燈人" or "火炬" or "持燈" => GetString("Torch"), - "靈媒" or "灵媒" or "靈媒" => GetString("Seer"), - "破平者" or "破平" => GetString("Tiebreaker"), - "膽小鬼" or "胆小鬼" or "膽小" or "胆小" => GetString("Oblivious"), - "視障" or "迷幻者" or "視障" or "迷幻" => GetString("Bewilder"), - "墨鏡" or "患者" => GetString("Sunglasses"), - "加班狂" => GetString("Workhorse"), - "蠢蛋" => GetString("Fool"), - "復仇者" or "复仇者" or "復仇" or "复仇" => GetString("Avanger"), - "Youtuber" or "UP主" or "YT" => GetString("Youtuber"), - "利己主義者" or "利己主义者" or "利己主義" or "利己主义" => GetString("Egoist"), - "竊票者" or "窃票者" or "竊票" or "窃票" => GetString("TicketsStealer"), - //"雙重人格" or "双重人格" => GetString("Schizophrenic"), - "保險箱" or "宝箱怪" => GetString("Mimic"), - "賭怪" or "赌怪" => GetString("Guesser"), - "死神" => GetString("Necroview"), - "長槍" or "持枪" => GetString("Reach"), - "魅魔小弟" => GetString("Charmed"), - "乾淨" or "干净" => GetString("Cleansed"), - "誘餌" or "诱饵" => GetString("Bait"), - "陷阱師" or "陷阱师" => GetString("Trapper"), - "被感染" or "感染" => GetString("Infected"), - "防賭" or "不可被赌" => GetString("Onbound"), - "反擊者" or "回弹者" or "回弹" => GetString("Rebound"), - "平凡者" or "平凡" => GetString("Mundane"), - "騎士" or "骑士" => GetString("Knighted"), - "漠視" or "不受重视" or "被漠視的" => GetString("Unreportable"), - "被傳染" or "传染性" => GetString("Contagious"), - "幸運" or "幸运加持" => GetString("Lucky"), - "倒霉" or "倒霉蛋" => GetString("Unlucky"), - "虛無" or "无效投票" => GetString("VoidBallot"), - "敏感" or "意识者" or "意识" => GetString("Aware"), - "嬌嫩" or "脆弱" or "脆弱者" => GetString("Fragile"), - "專業" or "双重猜测" => GetString("DoubleShot"), - "流氓" => GetString("Rascal"), - "無魂" or "没有灵魂" => GetString("Soulless"), - "墓碑" => GetString("Gravestone"), - "懶人" or "懒人" => GetString("Lazy"), - "驗屍" or "尸检" => GetString("Autopsy"), - "忠誠" or "忠诚" => GetString("Loyal"), - "惡靈" or "恶灵" => GetString("EvilSpirit"), - "狼化" or "招募" or "狼化的" or "被招募的" => GetString("Recruit"), - "被仰慕" or "仰慕" => GetString("Admired"), - "發光" or "光辉" => GetString("Glow"), - "病態" or "患病者" or "患病的" or "患病" => GetString("Diseased"), - "健康" or "健康的" or "健康者" => GetString("Antidote"), - "固執者" or "固执者" or "固執" or "固执" => GetString("Stubborn"), - "無影" or "迅捷" => GetString("Swift"), - "反噬" or "食尸鬼" => GetString("Ghoul"), - "嗜血者" => GetString("Bloodthirst"), - "獵夢者" or "梦魇" or "獵夢"=> GetString("Mare"), - "地雷" or "爆破者" or "爆破" => GetString("Burst"), - "偵察員" or "侦察员" or "偵察" or "侦察" => GetString("Sleuth"), - "笨拙" or "笨蛋" => GetString("Clumsy"), - "敏捷" => GetString("Nimble"), - "規避者" or "规避者" or "规避" => GetString("Circumvent"), - "名人" or "网络员" or "网络" => GetString("Cyber"), - "焦急者" or "焦急的" or "焦急" => GetString("Hurried"), - "OIIAI" => GetString("Oiiai"), - "順從者" or "影响者" or "順從" or "影响" => GetString("Influenced"), - "沉默者" or "沉默" => GetString("Silent"), - "易感者" or "易感" => GetString("Susceptible"), - "狡猾" or "棘手者" or "棘手" => GetString("Tricky"), - "彩虹" => GetString("Rainbow"), - "疲勞者" or "疲劳者" or "疲勞" or "疲劳" => GetString("Tired"), - "雕像" => GetString("Statue"), - "没有搜集的繁体中文" or "雷达" => GetString("Radar"), - - // 幽靈職業 and 幽灵职业 - // 偽裝者 and 内鬼 - "爪牙" => GetString("Minion"), - "黑手黨" or "黑手党" or "黑手" => GetString("Nemesis"), - "嗜血之魂" or "血液伯爵" => GetString("Bloodmoon"), - // 船員 and 船员 - "没有搜集的繁体中文" or "鬼怪" => GetString("Ghastly"), - "冤魂" or "典狱长" => GetString("Warden"), - "報應者" or "惩罚者" or "惩罚" or "报仇者" => GetString("Retributionist"), - - // 随机阵营职业 - "迷你船員" or "迷你船员" or "迷你" or "小孩" or "Mini" => GetString("Mini"),*/ - _ => 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 (var rl in CustomRolesHelper.AllRoles) - { - if (rl.IsVanilla()) continue; - var roleName = GetString(rl.ToString()).ToLower().Trim().Replace(" ", ""); - string nameWithoutId = Regex.Replace(name.Replace(" ", ""), @"^\d+", ""); - if (nameWithoutId == 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.FFA) - { - Utils.SendMessage(GetString("ModeDescribe.FFA"), 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.StartsWith("/bt")) _ = role.Replace("/bt", string.Empty); - if (role.StartsWith("/rt")) _ = role.Replace("/rt", string.Empty); - - if (role == "" || role == string.Empty) - { - Utils.ShowActiveRoles(playerId); - return; - } - - role = FixRoleNameInput(role).ToLower().Trim().Replace(" ", string.Empty); - - foreach (var rl in CustomRolesHelper.AllRoles) - { - 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 or CustomRoles.Mini || rl.IsGhostRole()) 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; - GhostRoleAssign.forceRole.Remove(pid); - RoleAssign.SetRoles.Remove(pid); - RoleAssign.SetRoles.Add(pid, rl); - } - if (rl.IsGhostRole() && !rl.IsAdditionRole() && isDev && (rl.GetCount() >= 1 && rl.GetMode() > 0)) - { - byte pid = playerId == 255 ? (byte)0 : playerId; - CustomRoles setrole = rl.GetCustomRoleTeam() switch - { - Custom_Team.Impostor => CustomRoles.ImpostorTOHE, - _ => CustomRoles.CrewmateTOHE - - }; - RoleAssign.SetRoles.Remove(pid); - RoleAssign.SetRoles.Add(pid, setrole); - GhostRoleAssign.forceRole[pid] = rl; - - devMark = "▲"; - } - - if (isUp) return; - } - var Des = rl.GetInfoLong(); - var title = devMark + $"" + rl.GetRoleTitle() + "\n"; - var Conf = new StringBuilder(); - string rlHex = Utils.GetRoleColorCode(rl); - if (Options.CustomRoleSpawnChances.ContainsKey(rl)) - { - Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[rl], ref Conf); - var cleared = Conf.ToString(); - var Setting = $"{GetString(rl.ToString())} {GetString("Settings:")}\n"; - Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); - - } - // Show role info - Utils.SendMessage(Des, playerId, title, noReplay: true); - - // Show role settings - Utils.SendMessage("", playerId, Conf.ToString(), noReplay: true); - 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 (!Blackmailer.CheckBlackmaile(player)) ChatManager.SendMessage(player, text); - - if (text.StartsWith("\n")) text = text[1..]; - //if (!text.StartsWith("/")) return; - string[] args = text.Split(' '); - string subArgs = ""; - string subArgs2 = ""; - - //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; Logger.Info($"Is Guesser command", "OnReceiveChat"); return; } - if (player.GetRoleClass() is Judge jd && jd.TrialMsg(player, text)) { canceled = true; Logger.Info($"Is Judge command", "OnReceiveChat"); return; } - if (President.EndMsg(player, text)) { canceled = true; Logger.Info($"Is President command", "OnReceiveChat"); return; } - if (Inspector.InspectCheckMsg(player, text)) { canceled = true; Logger.Info($"Is Inspector command", "OnReceiveChat"); return; } - if (Pirate.DuelCheckMsg(player, text)) { canceled = true; Logger.Info($"Is Pirate command", "OnReceiveChat"); return; } - if (player.GetRoleClass() is Councillor cl && cl.MurderMsg(player, text)) { canceled = true; Logger.Info($"Is Councillor command", "OnReceiveChat"); return; } - if (player.GetRoleClass() is Swapper sw && sw.SwapMsg(player, text)) { canceled = true; Logger.Info($"Is Swapper command", "OnReceiveChat"); return; } - if (Medium.MsMsg(player, text)) { Logger.Info($"Is Medium command", "OnReceiveChat"); return; } - if (Nemesis.NemesisMsgCheck(player, text)) { Logger.Info($"Is Nemesis Revenge command", "OnReceiveChat"); return; } - if (Retributionist.RetributionistMsgCheck(player, text)) { Logger.Info($"Is Retributionist Revenge command", "OnReceiveChat"); return; } - if (player.GetRoleClass() is Dictator dt && dt.ExilePlayer(player, text)) { canceled = true; Logger.Info($"Is Dictator command", "OnReceiveChat"); return; } - if (Ritualist.RitualistMsgCheck(player, text)) { canceled = true; Logger.Info($"Is Ritualist command", "OnReceiveChat"); return; } - - Directory.CreateDirectory(modTagsFiles); - Directory.CreateDirectory(vipTagsFiles); - Directory.CreateDirectory(sponsorTagsFiles); - - if (Blackmailer.CheckBlackmaile(player) && player.IsAlive() && !player.IsHost()) - { - Logger.Info($"This player (id {player.PlayerId}) was Blackmailed", "OnReceiveChat"); - ChatManager.SendPreviousMessagesToAll(); - ChatManager.cancel = false; - canceled = true; - return; - } - - switch (args[0]) - { - case "/r": - case "/role": - case "/р": - case "/роль": - Logger.Info($"Command '/r' was activated", "OnReceiveChat"); - if (text.Contains("/role") || text.Contains("/роль")) - subArgs = text.Remove(0, 5); - else - subArgs = text.Remove(0, 2); - SendRolesInfo(subArgs, player.PlayerId, isDev: player.FriendCode.GetDevUser().DeBug); - break; - - case "/m": - case "/myrole": - case "/minhafunção": - case "/м": - case "/мояроль": - case "/身份": - case "/我": - case "/我的身份": - case "/我的职业": - Logger.Info($"Command '/m' was activated", "OnReceiveChat"); - var role = player.GetCustomRole(); - if (GameStates.IsInGame) - { - var Des = player.GetRoleInfo(true); - var title = $"" + role.GetRoleTitle() + "\n"; - var Conf = new StringBuilder(); - var Sub = new StringBuilder(); - var rlHex = Utils.GetRoleColorCode(role); - var SubTitle = $"" + GetString("YourAddon") + "\n"; - - if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) - Utils.ShowChildrenSettings(opt, ref Conf); - var cleared = Conf.ToString(); - var Setting = $"{GetString(role.ToString())} {GetString("Settings:")}\n"; - Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); - - foreach (var subRole in Main.PlayerStates[player.PlayerId].SubRoles.ToArray()) - { - Sub.Append($"\n\n" + $"" + Utils.GetRoleTitle(subRole) + Utils.GetInfoLong(subRole) + ""); - - } - if (Sub.ToString() != string.Empty) - { - var ACleared = Sub.ToString().Remove(0, 2); - ACleared = ACleared.Length > 1200 ? $"" + ACleared.RemoveHtmlTags() + "" : ACleared; - Sub.Clear().Append(ACleared); - } - - Utils.SendMessage(Des, player.PlayerId, title, noReplay: true); - Utils.SendMessage("", player.PlayerId, Conf.ToString(), noReplay: true); - if (Sub.ToString() != string.Empty) Utils.SendMessage(Sub.ToString(), player.PlayerId, SubTitle, noReplay: true); - - Logger.Info($"Command '/m' should be send message", "OnReceiveChat"); - } - else - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - break; - - case "/h": - case "/help": - case "/ajuda": - case "/хелп": - case "/хэлп": - case "/помощь": - case "/帮助": - case "/教程": - Utils.ShowHelpToClient(player.PlayerId); - break; - - case "/ans": - case "/asw": - case "/answer": - case "/回答": - Quizmaster.AnswerByChat(player, args); - break; - - case "/qmquiz": - case "/提问": - Quizmaster.ShowQuestion(player); - break; - - case "/l": - case "/lastresult": - case "/fimdejogo": - case "/上局信息": - case "/信息": - case "/情况": - Utils.ShowKillLog(player.PlayerId); - Utils.ShowLastRoles(player.PlayerId); - Utils.ShowLastResult(player.PlayerId); - break; - - case "/gr": - case "/gameresults": - case "/resultados": - case "/对局结果": - case "/上局结果": - case "/结果": - Utils.ShowLastResult(player.PlayerId); - break; - - case "/kh": - case "/killlog": - case "/击杀日志": - case "/击杀情况": - Utils.ShowKillLog(player.PlayerId); - break; - - case "/rs": - case "/sum": - case "/rolesummary": - case "/sumario": - case "/sumário": - case "/summary": - case "/результат": - case "/上局职业": - case "/职业信息": - case "/对局职业": - Utils.ShowLastRoles(player.PlayerId); - break; - - case "/ghostinfo": - case "/幽灵职业介绍": - case "/鬼魂职业介绍": - case "/幽灵职业": - case "/鬼魂职业": - if (GameStates.IsInGame) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); - break; - } - Utils.SendMessage(GetString("Message.GhostRoleInfo"), player.PlayerId); - break; - - case "/apocinfo": - case "/apocalypseinfo": - case "/末日中立职业介绍": - case "/末日中立介绍": - case "/末日类中立职业介绍": - case "/末日类中立介绍": - Utils.SendMessage(GetString("Message.ApocalypseInfo"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); - break; - - case "/coveninfo": - case "/covinfo": - Utils.SendMessage(GetString("Message.CovenInfo"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Coven), GetString("CovenInfoTitle"))); - break; - - case "/rn": - case "/rename": - case "/renomear": - case "/переименовать": - case "/重命名": - case "/命名为": - if (Options.PlayerCanSetName.GetBool() || player.FriendCode.GetDevUser().IsDev || player.FriendCode.GetDevUser().NameCmd || TagManager.ReadPermission(player.FriendCode) >= 1) - { - if (GameStates.IsInGame) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); - break; - } - if (args.Length < 1) break; - if (args.Skip(1).Join(delimiter: " ").Length is > 10 or < 1) - { - Utils.SendMessage(GetString("Message.AllowNameLength"), player.PlayerId); - break; - } - Main.AllPlayerNames[player.PlayerId] = args.Skip(1).Join(delimiter: " "); - Utils.SendMessage(string.Format(GetString("Message.SetName"), args.Skip(1).Join(delimiter: " ")), player.PlayerId); - break; - } - else - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - } - break; - - case "/n": - case "/now": - case "/atual": - case "/设置": - case "/系统设置": - case "/模组设置": - subArgs = args.Length < 2 ? "" : args[1]; - switch (subArgs) - { - case "r": - case "roles": - case "funções": - Utils.ShowActiveRoles(player.PlayerId); - break; - case "a": - case "all": - case "tudo": - Utils.ShowAllActiveSettings(player.PlayerId); - break; - default: - Utils.ShowActiveSettings(player.PlayerId); - break; - } - break; - - case "/up": - case "/指定": - case "/成为": - _ = text.Remove(0, 3); - if (!Options.EnableUpMode.GetBool()) - { - Utils.SendMessage(string.Format(GetString("Message.YTPlanDisabled"), GetString("EnableYTPlan")), player.PlayerId); - break; - } - else - { - Utils.SendMessage(GetString("Message.OnlyCanBeUsedByHost"), player.PlayerId); - break; - } - - case "/win": - case "/winner": - case "/vencedor": - case "/胜利": - case "/获胜": - case "/赢": - case "/胜利者": - case "/获胜的人": - case "/赢家": - if (Main.winnerNameList.Count == 0) Utils.SendMessage(GetString("NoInfoExists"), player.PlayerId); - else Utils.SendMessage("Winner: " + string.Join(", ", Main.winnerNameList), player.PlayerId); - break; - - - case "/pv": - canceled = true; - if (!Pollvotes.Any()) - { - Utils.SendMessage(GetString("Poll.Inactive"), player.PlayerId); - break; - } - if (PollVoted.Contains(player.PlayerId)) - { - Utils.SendMessage(GetString("Poll.AlreadyVoted"), player.PlayerId); - break; - } - - subArgs = args.Length != 2 ? "" : args[1]; - char vote = ' '; - - if (int.TryParse(subArgs, out int integer) && (Pollvotes.Count - 1) >= integer) - { - vote = char.ToUpper((char)(integer + 65)); - } - else if (!(char.TryParse(subArgs, out vote) && Pollvotes.ContainsKey(char.ToUpper(vote)))) - { - Utils.SendMessage(GetString("Poll.VotingInfo"), player.PlayerId); - break; - } - vote = char.ToUpper(vote); - - PollVoted.Add(player.PlayerId); - Pollvotes[vote]++; - Utils.SendMessage(string.Format(GetString("Poll.YouVoted"), vote, Pollvotes[vote]), player.PlayerId); - Logger.Info($"The new value of {vote} is {Pollvotes[vote]}", "TestPV_CHAR"); - - break; - - case "/icon": - case "/icons": - case "/符号": - case "/标志": - { - Utils.SendMessage(GetString("Command.icons"), player.PlayerId, GetString("IconsTitle")); - break; - } - - case "/kc": - case "/kcount": - case "/количество": - case "/убийцы": - case "/存活阵营": - case "/阵营": - case "/存货阵营信息": - case "/阵营信息": - if (GameStates.IsLobby) break; - - if (!Options.EnableKillerLeftCommand.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - - var allAlivePlayers = Main.AllAlivePlayerControls; - int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); - int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); - int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); - int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); - int covnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Coven)); - - var sub = new StringBuilder(); - sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); - - if (Options.ShowMadmatesInLeftCommand.GetBool()) - sub.Append(string.Format("\n\r" + GetString("Remaining.MadmateCount"), madnum)); - - if (Options.ShowApocalypseInLeftCommand.GetBool()) - sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); - - if (Options.ShowCovenInLeftCommand.GetBool()) - sub.Append(string.Format("\n\r" + GetString("Remaining.CovenCount"), covnum)); - - sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); - - Utils.SendMessage(sub.ToString(), player.PlayerId); - break; - - case "/d": - case "/death": - case "/morto": - case "/умер": - case "/причина": - case "/死亡原因": - case "/死亡": - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - break; - } - else if (player.IsAlive()) - { - Utils.SendMessage(GetString("DeathCmd.HeyPlayer") + "" + player.GetRealName() + "" + GetString("DeathCmd.YouAreRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "\n\n" + GetString("DeathCmd.NotDead"), player.PlayerId); - break; - } - else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.Vote) - { - Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Ejected"), player.PlayerId); - break; - } - else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.Shrouded) - { - Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Shrouded"), player.PlayerId); - break; - } - else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.FollowingSuicide) - { - Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Lovers"), player.PlayerId); - break; - } - else - { - var killer = player.GetRealKiller(out var MurderRole); - string killerName = killer == null ? "N/A" : killer.GetRealName(clientData: true); - string killerRole = killer == null ? "N/A" : Utils.GetRoleName(MurderRole); - Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.DeathReason") + "" + Utils.GetVitalText(player.PlayerId) + "" + "\n\r" + "" + "\n\r" + GetString("DeathCmd.KillerName") + "" + killerName + "" + "\n\r" + GetString("DeathCmd.KillerRole") + "" + $"{killerRole}" + "", player.PlayerId); - break; - } - - case "/t": - case "/template": - case "/шаблон": - case "/пример": - case "/模板": - case "/模板信息": - 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": - case "/cor": - case "/цвет": - case "/颜色": - case "/更改颜色": - case "/修改颜色": - case "/换颜色": - if (Options.PlayerCanSetColor.GetBool() || player.FriendCode.GetDevUser().IsDev || player.FriendCode.GetDevUser().ColorCmd || Utils.IsPlayerVIP(player.FriendCode)) - { - 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": - case "/sair": - case "/退出": - case "/退": - if (Options.PlayerCanUseQuitCommand.GetBool()) - { - 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); - } - } - else - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - } - break; - - case "/id": - case "/айди": - case "/编号": - case "/玩家编号": - if ((Options.ApplyModeratorList.GetValue() == 0 || (!Utils.IsPlayerModerator(player.FriendCode) && TagManager.ReadPermission(player.FriendCode) < 2)) - && !Options.EnableVoteCommand.GetBool()) break; - - string msgText = GetString("PlayerIdList"); - foreach (var pc in Main.AllPlayerControls) - { - if (pc == null) continue; - msgText += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); - } - Utils.SendMessage(msgText, player.PlayerId); - break; - - case "/mid": - case "/玩家列表": - case "/玩家信息": - case "/玩家编号列表": - //canceled = true; - //checking if modlist on or not - if (Options.ApplyModeratorList.GetValue() == 0) - { - Utils.SendMessage(GetString("midCommandDisabled"), player.PlayerId); - break; - } - //checking if player is has necessary privellege or not - if (!Utils.IsPlayerModerator(player.FriendCode) && TagManager.ReadPermission(player.FriendCode) < 2) - { - Utils.SendMessage(GetString("midCommandNoAccess"), player.PlayerId); - break; - } - string msgText1 = GetString("PlayerIdList"); - foreach (var pc in Main.AllPlayerControls) - { - if (pc == null) continue; - msgText1 += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); - } - Utils.SendMessage(msgText1, player.PlayerId); - break; - - case "/ban": - case "/banir": - case "/бан": - case "/забанить": - case "/封禁": - //canceled = true; - // Check if the ban command is enabled in the settings - if (Options.ApplyModeratorList.GetValue() == 0) - { - Utils.SendMessage(GetString("BanCommandDisabled"), player.PlayerId); - break; - } - - // Check if the player has the necessary privileges to use the command - if (!Utils.IsPlayerModerator(player.FriendCode) && TagManager.ReadPermission(player.FriendCode) < 5) - { - Utils.SendMessage(GetString("BanCommandNoAccess"), player.PlayerId); - break; - } - string banReason; - if (args.Length < 3) - { - Utils.SendMessage(GetString("BanCommandNoReason"), player.PlayerId); - break; - } - else - { - subArgs = args[1]; - banReason = string.Join(" ", args.Skip(2)); - } - //subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte banPlayerId)) - { - Utils.SendMessage(GetString("BanCommandInvalidID"), player.PlayerId); - break; - } - - if (banPlayerId == 0) - { - Utils.SendMessage(GetString("BanCommandBanHost"), player.PlayerId); - break; - } - - var bannedPlayer = Utils.GetPlayerById(banPlayerId); - if (bannedPlayer == null) - { - Utils.SendMessage(GetString("BanCommandInvalidID"), player.PlayerId); - break; - } - - // Prevent moderators from baning other moderators - if (Utils.IsPlayerModerator(bannedPlayer.FriendCode) || TagManager.ReadPermission(bannedPlayer.FriendCode) >= 2) - { - Utils.SendMessage(GetString("BanCommandBanMod"), player.PlayerId); - break; - } - - // Ban the specified player - AmongUsClient.Instance.KickPlayer(bannedPlayer.GetClientId(), true); - string bannedPlayerName = bannedPlayer.GetRealName(); - string textToSend1 = $"{bannedPlayerName} {GetString("BanCommandBanned")}{player.name} \nReason: {banReason}\n"; - if (GameStates.IsInGame) - { - textToSend1 += $" {GetString("BanCommandBannedRole")} {GetString(bannedPlayer.GetCustomRole().ToString())}"; - } - Utils.SendMessage(textToSend1); - //string moderatorName = player.GetRealName().ToString(); - //int startIndex = moderatorName.IndexOf("♥") + "♥".Length; - //moderatorName = moderatorName.Substring(startIndex); - //string extractedString = - string modLogname = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n1) ? n1 : ""; - string banlogname = Main.AllPlayerNames.TryGetValue(bannedPlayer.PlayerId, out var n11) ? n11 : ""; - string moderatorFriendCode = player.FriendCode.ToString(); - string bannedPlayerFriendCode = bannedPlayer.FriendCode.ToString(); - string bannedPlayerHashPuid = bannedPlayer.GetClient().GetHashedPuid(); - string logMessage = $"[{DateTime.Now}] {moderatorFriendCode},{modLogname} Banned: {bannedPlayerFriendCode},{bannedPlayerHashPuid},{banlogname} Reason: {banReason}"; - File.AppendAllText(modLogFiles, logMessage + Environment.NewLine); - break; - - case "/warn": - case "/aviso": - case "/варн": - case "/пред": - case "/предупредить": - case "/警告": - case "/提醒": - if (Options.ApplyModeratorList.GetValue() == 0) - { - Utils.SendMessage(GetString("WarnCommandDisabled"), player.PlayerId); - break; - } - if (!Utils.IsPlayerModerator(player.FriendCode) && TagManager.ReadPermission(player.FriendCode) < 2) - { - Utils.SendMessage(GetString("WarnCommandNoAccess"), player.PlayerId); - break; - } - subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte warnPlayerId)) - { - Utils.SendMessage(GetString("WarnCommandInvalidID"), player.PlayerId); - break; - } - if (warnPlayerId == 0) - { - Utils.SendMessage(GetString("WarnCommandWarnHost"), player.PlayerId); - break; - } - - var warnedPlayer = Utils.GetPlayerById(warnPlayerId); - if (warnedPlayer == null) - { - Utils.SendMessage(GetString("WarnCommandInvalidID"), player.PlayerId); - break; - } - - // Prevent moderators from warning other moderators - if (Utils.IsPlayerModerator(warnedPlayer.FriendCode) || TagManager.ReadPermission(warnedPlayer.FriendCode) >= 2) - { - Utils.SendMessage(GetString("WarnCommandWarnMod"), player.PlayerId); - break; - } - // warn the specified player - string warnReason = "Reason : Not specified\n"; - string warnedPlayerName = warnedPlayer.GetRealName(); - //textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} ~{player.name}"; - if (args.Length > 2) - { - warnReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; - } - else - { - Utils.SendMessage("Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", player.PlayerId); - } - Utils.SendMessage($" {warnedPlayerName} {GetString("WarnCommandWarned")} {warnReason} ~{player.name}"); - //string moderatorName1 = player.GetRealName().ToString(); - //int startIndex1 = moderatorName1.IndexOf("♥") + "♥".Length; - //moderatorName1 = moderatorName1.Substring(startIndex1); - string modLogname1 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n2) ? n2 : ""; - string warnlogname = Main.AllPlayerNames.TryGetValue(warnedPlayer.PlayerId, out var n12) ? n12 : ""; - string moderatorFriendCode1 = player.FriendCode.ToString(); - string warnedPlayerFriendCode = warnedPlayer.FriendCode.ToString(); - string warnedPlayerHashPuid = warnedPlayer.GetClient().GetHashedPuid(); - string logMessage1 = $"[{DateTime.Now}] {moderatorFriendCode1},{modLogname1} Warned: {warnedPlayerFriendCode},{warnedPlayerHashPuid},{warnlogname} Reason: {warnReason}"; - File.AppendAllText(modLogFiles, logMessage1 + Environment.NewLine); - - break; - case "/kick": - case "/expulsar": - case "/кик": - case "/кикнуть": - case "/выгнать": - case "/踢出": - case "/踢": - // 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 (!Utils.IsPlayerModerator(player.FriendCode) && TagManager.ReadPermission(player.FriendCode) < 4) - { - 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 (Utils.IsPlayerModerator(kickedPlayer.FriendCode) || TagManager.ReadPermission(kickedPlayer.FriendCode) >= 4) - { - Utils.SendMessage(GetString("KickCommandKickMod"), player.PlayerId); - break; - } - - // Kick the specified player - AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), false); - string kickedPlayerName = kickedPlayer.GetRealName(); - string kickReason = "Reason : Not specified\n"; - if (args.Length > 2) - kickReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; - else - { - Utils.SendMessage("Use /kick [id] [reason] in future. \nExample :-\n /kick 5 not following rules", player.PlayerId); - } - string textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")} {player.name} \n {kickReason}"; - - if (GameStates.IsInGame) - { - textToSend += $" {GetString("KickCommandKickedRole")} {GetString(kickedPlayer.GetCustomRole().ToString())}"; - } - Utils.SendMessage(textToSend); - //string moderatorName2 = player.GetRealName().ToString(); - //int startIndex2 = moderatorName2.IndexOf("♥") + "♥".Length; - //moderatorName2 = moderatorName2.Substring(startIndex2); - string modLogname2 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n3) ? n3 : ""; - string kicklogname = Main.AllPlayerNames.TryGetValue(kickedPlayer.PlayerId, out var n13) ? n13 : ""; - - string moderatorFriendCode2 = player.FriendCode.ToString(); - string kickedPlayerFriendCode = kickedPlayer.FriendCode.ToString(); - string kickedPlayerHashPuid = kickedPlayer.GetClient().GetHashedPuid(); - string logMessage2 = $"[{DateTime.Now}] {moderatorFriendCode2},{modLogname2} Kicked: {kickedPlayerFriendCode},{kickedPlayerHashPuid},{kicklogname} Reason: {kickReason}"; - File.AppendAllText(modLogFiles, logMessage2 + Environment.NewLine); - - break; - case "/modcolor": - case "/modcolour": - case "/模组端颜色": - case "/模组颜色": - if (Options.ApplyModeratorList.GetValue() == 0) - { - Utils.SendMessage(GetString("ColorCommandDisabled"), player.PlayerId); - break; - } - if (!Utils.IsPlayerModerator(player.FriendCode)) - { - Utils.SendMessage(GetString("ColorCommandNoAccess"), player.PlayerId); - break; - } - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("ColorCommandNoLobby"), player.PlayerId); - break; - } - if (!Options.GradientTagsOpt.GetBool()) - { - subArgs = args.Length != 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) - { - Logger.Msg($"{subArgs}", "modcolor"); - Utils.SendMessage(GetString("ColorInvalidHexCode"), player.PlayerId); - break; - } - string colorFilePath = $"{modTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(colorFilePath)) - { - Logger.Warn($"File Not exist, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); - File.Create(colorFilePath).Close(); - } - - File.WriteAllText(colorFilePath, $"{subArgs}"); - break; - } - else - { - subArgs = args.Length < 3 ? "" : args[1] + " " + args[2]; - Regex regex = new(@"^[0-9A-Fa-f]{6}\s[0-9A-Fa-f]{6}$"); - if (string.IsNullOrEmpty(subArgs) || !regex.IsMatch(subArgs)) - { - Logger.Msg($"{subArgs}", "modcolor"); - Utils.SendMessage(GetString("ColorInvalidGradientCode"), player.PlayerId); - break; - } - string colorFilePath = $"{modTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(colorFilePath)) - { - Logger.Msg($"File Not exist, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); - File.Create(colorFilePath).Close(); - } - //Logger.Msg($"File exists, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); - //Logger.Msg($"{subArgs}","modcolor"); - File.WriteAllText(colorFilePath, $"{subArgs}"); - break; - } - case "/vipcolor": - case "/vipcolour": - case "/VIP玩家颜色": - case "/VIP颜色": - if (Options.ApplyVipList.GetValue() == 0) - { - Utils.SendMessage(GetString("VipColorCommandDisabled"), player.PlayerId); - break; - } - if (!Utils.IsPlayerVIP(player.FriendCode)) - { - Utils.SendMessage(GetString("VipColorCommandNoAccess"), player.PlayerId); - break; - } - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("VipColorCommandNoLobby"), player.PlayerId); - break; - } - if (!Options.GradientTagsOpt.GetBool()) - { - subArgs = args.Length != 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) - { - Logger.Msg($"{subArgs}", "vipcolor"); - Utils.SendMessage(GetString("VipColorInvalidHexCode"), player.PlayerId); - break; - } - string colorFilePathh = $"{vipTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(colorFilePathh)) - { - Logger.Warn($"File Not exist, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); - File.Create(colorFilePathh).Close(); - } - - File.WriteAllText(colorFilePathh, $"{subArgs}"); - break; - } - else - { - subArgs = args.Length < 3 ? "" : args[1] + " " + args[2]; - Regex regexx = new(@"^[0-9A-Fa-f]{6}\s[0-9A-Fa-f]{6}$"); - if (string.IsNullOrEmpty(subArgs) || !regexx.IsMatch(subArgs)) - { - Logger.Msg($"{subArgs}", "vipcolor"); - Utils.SendMessage(GetString("VipColorInvalidGradientCode"), player.PlayerId); - break; - } - string colorFilePathh = $"{vipTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(colorFilePathh)) - { - Logger.Msg($"File Not exist, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); - File.Create(colorFilePathh).Close(); - } - //Logger.Msg($"File exists, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); - //Logger.Msg($"{subArgs}","modcolor"); - File.WriteAllText(colorFilePathh, $"{subArgs}"); - break; - } - case "/tagcolor": - case "/tagcolour": - case "/标签颜色": - case "/附加名称颜色": - string name1 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n) ? n : ""; - if (name1 == "") break; - if (!name1.Contains('\r') && player.FriendCode.GetDevUser().HasTag()) - { - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("ColorCommandNoLobby"), player.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) - { - Logger.Msg($"{subArgs}", "tagcolor"); - Utils.SendMessage(GetString("TagColorInvalidHexCode"), player.PlayerId); - break; - } - string tagColorFilePath = $"{sponsorTagsFiles}/{player.FriendCode}.txt"; - if (!File.Exists(tagColorFilePath)) - { - Logger.Msg($"File Not exist, creating file at {tagColorFilePath}", "tagcolor"); - File.Create(tagColorFilePath).Close(); - } - - File.WriteAllText(tagColorFilePath, $"{subArgs}"); - } - break; - - case "/xf": - case "/修复": - case "/修": - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - break; - } - foreach (var pc in Main.AllPlayerControls) - { - if (pc.IsAlive()) continue; - - pc.RpcSetNameEx(pc.GetRealName(isMeeting: true)); - } - ChatUpdatePatch.DoBlockChat = false; - //Utils.NotifyRoles(isForMeeting: GameStates.IsMeeting, NoCache: true); - Utils.SendMessage(GetString("Message.TryFixName"), player.PlayerId); - break; - - case "/tpout": - case "/传送出": - case "/传出": - if (!GameStates.IsLobby) break; - if (!Options.PlayerCanUseTP.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - player.RpcTeleport(new Vector2(0.1f, 3.8f)); - break; - case "/tpin": - case "/传进": - case "/传送进": - if (!GameStates.IsLobby) break; - if (!Options.PlayerCanUseTP.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - - player.RpcTeleport(new Vector2(-0.2f, 1.3f)); - break; - - case "/vote": - case "/投票": - case "/票": - subArgs = args.Length != 2 ? "" : args[1]; - if (subArgs == "" || !int.TryParse(subArgs, out int arg)) - break; - var plr = Utils.GetPlayerById(arg); - - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - break; - } - - - if (!Options.EnableVoteCommand.GetBool()) - { - Utils.SendMessage(GetString("VoteDisabled"), player.PlayerId); - break; - } - if (Options.ShouldVoteCmdsSpamChat.GetBool()) - { - canceled = true; - ChatManager.SendPreviousMessagesToAll(); - } - - if (arg != 253) // skip - { - if (plr == null || !plr.IsAlive()) - { - Utils.SendMessage(GetString("VoteDead"), player.PlayerId); - break; - } - } - if (!player.IsAlive()) - { - Utils.SendMessage(GetString("CannotVoteWhenDead"), player.PlayerId); - break; - } - if (GameStates.IsMeeting) - { - player.RpcCastVote((byte)arg); - } - break; - - case "/say": - case "/s": - case "/с": - case "/сказать": - case "/说": - if (player.FriendCode.GetDevUser().IsDev) - { - if (args.Length > 1) - Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromDev")} ~ {player.GetRealName(clientData: true)}"); - } - else if (player.FriendCode.IsDevUser() && !dbConnect.IsBooster(player.FriendCode)) - { - if (args.Length > 1) - Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromSponsor")} ~ {player.GetRealName(clientData: true)}"); - } - else if (Utils.IsPlayerModerator(player.FriendCode) || TagManager.CanUseSayCommand(player.FriendCode)) - { - if (Options.ApplyModeratorList.GetValue() == 0 || Options.AllowSayCommand.GetBool() == false) - { - Utils.SendMessage(GetString("SayCommandDisabled"), player.PlayerId); - break; - } - else - { - var modTitle = (Utils.IsPlayerModerator(player.FriendCode) || TagManager.ReadPermission(player.FriendCode) >= 2) ? $"{GetString("MessageFromModerator")}" : $"{GetString("MessageFromVIP")}"; - if (args.Length > 1) - Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{modTitle} ~ {player.GetRealName(clientData: true)}"); - //string moderatorName3 = player.GetRealName().ToString(); - //int startIndex3 = moderatorName3.IndexOf("♥") + "♥".Length; - //moderatorName3 = moderatorName3.Substring(startIndex3); - string modLogname3 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n4) ? n4 : ""; - - string moderatorFriendCode3 = player.FriendCode.ToString(); - string logMessage3 = $"[{DateTime.Now}] {moderatorFriendCode3},{modLogname3} used /s: {args.Skip(1).Join(delimiter: " ")}"; - File.AppendAllText(modLogFiles, logMessage3 + Environment.NewLine); - - } - } - break; - case "/rps": - case "/剪刀石头布": - //canceled = true; - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - - if (!GameStates.IsLobby && player.IsAlive()) - { - Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); - break; - } - - if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice)) - { - Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); - break; - } - else if (playerChoice < 0 || playerChoice > 2) - { - Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botChoice = rand.Next(0, 3); - var rpsList = new List { GetString("Rock"), GetString("Paper"), GetString("Scissors") }; - if (botChoice == playerChoice) - { - Utils.SendMessage(string.Format(GetString("RpsDraw"), rpsList[botChoice]), player.PlayerId); - } - else if ((botChoice == 0 && playerChoice == 2) || - (botChoice == 1 && playerChoice == 0) || - (botChoice == 2 && playerChoice == 1)) - { - Utils.SendMessage(string.Format(GetString("RpsLose"), rpsList[botChoice]), player.PlayerId); - } - else - { - Utils.SendMessage(string.Format(GetString("RpsWin"), rpsList[botChoice]), player.PlayerId); - } - break; - } - case "/coinflip": - case "/抛硬币": - //canceled = true; - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - - if (!GameStates.IsLobby && player.IsAlive()) - { - Utils.SendMessage(GetString("CoinflipCommandInfo"), player.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botChoice = rand.Next(1, 101); - var coinSide = (botChoice < 51) ? GetString("Heads") : GetString("Tails"); - Utils.SendMessage(string.Format(GetString("CoinFlipResult"), coinSide), player.PlayerId); - break; - } - case "/gno": - case "/猜数字": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - //canceled = true; - if (!GameStates.IsLobby && player.IsAlive()) - { - Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); - break; - } - subArgs = args.Length != 2 ? "" : args[1]; - if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo)) - { - Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); - break; - } - else if (guessedNo < 0 || guessedNo > 99) - { - Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); - break; - } - else - { - int targetNumber = Main.GuessNumber[player.PlayerId][0]; - if (Main.GuessNumber[player.PlayerId][0] == -1) - { - var rand = IRandom.Instance; - Main.GuessNumber[player.PlayerId][0] = rand.Next(0, 100); - targetNumber = Main.GuessNumber[player.PlayerId][0]; - } - Main.GuessNumber[player.PlayerId][1]--; - if (Main.GuessNumber[player.PlayerId][1] == 0 && guessedNo != targetNumber) - { - Main.GuessNumber[player.PlayerId][0] = -1; - Main.GuessNumber[player.PlayerId][1] = 7; - //targetNumber = Main.GuessNumber[player.PlayerId][0]; - Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), player.PlayerId); - break; - } - else if (guessedNo < targetNumber) - { - Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); - break; - } - else if (guessedNo > targetNumber) - { - Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); - break; - } - else - { - Utils.SendMessage(string.Format(GetString("GNoWon"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); - Main.GuessNumber[player.PlayerId][0] = -1; - Main.GuessNumber[player.PlayerId][1] = 7; - break; - } - } - case "/rand": - case "/XY数字": - case "/范围游戏": - case "/猜范围": - case "/范围": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - subArgs = args.Length != 3 ? "" : args[1]; - subArgs2 = args.Length != 3 ? "" : args[2]; - - if (!GameStates.IsLobby && player.IsAlive()) - { - Utils.SendMessage(GetString("RandCommandInfo"), player.PlayerId); - break; - } - if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice1) || subArgs2 == "" || !int.TryParse(subArgs2, out int playerChoice2)) - { - Utils.SendMessage(GetString("RandCommandInfo"), player.PlayerId); - break; - } - else - { - var rand = IRandom.Instance; - int botResult = rand.Next(playerChoice1, playerChoice2 + 1); - Utils.SendMessage(string.Format(GetString("RandResult"), botResult), player.PlayerId); - break; - } - case "/8ball": - case "/8号球": - case "/幸运球": - if (!Options.CanPlayMiniGames.GetBool()) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - break; - } - canceled = true; - var rando = IRandom.Instance; - int result = rando.Next(0, 16); - string str = ""; - switch (result) - { - case 0: - str = GetString("8BallYes"); - break; - case 1: - str = GetString("8BallNo"); - break; - case 2: - str = GetString("8BallMaybe"); - break; - case 3: - str = GetString("8BallTryAgainLater"); - break; - case 4: - str = GetString("8BallCertain"); - break; - case 5: - str = GetString("8BallNotLikely"); - break; - case 6: - str = GetString("8BallLikely"); - break; - case 7: - str = GetString("8BallDontCount"); - break; - case 8: - str = GetString("8BallStop"); - break; - case 9: - str = GetString("8BallPossibly"); - break; - case 10: - str = GetString("8BallProbably"); - break; - case 11: - str = GetString("8BallProbablyNot"); - break; - case 12: - str = GetString("8BallBetterNotTell"); - break; - case 13: - str = GetString("8BallCantPredict"); - break; - case 14: - str = GetString("8BallWithoutDoubt"); - break; - case 15: - str = GetString("8BallWithDoubt"); - break; - } - Utils.SendMessage("" + str + "", player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medium), GetString("8BallTitle"))); - break; - case "/me": - case "/我的权限": - case "/权限": - - string Devbox = player.FriendCode.GetDevUser().DeBug ? "<#10e341>" : "<#e31010>"; - string UpBox = player.FriendCode.GetDevUser().IsUp ? "<#10e341>" : "<#e31010>"; - string ColorBox = player.FriendCode.GetDevUser().ColorCmd ? "<#10e341>" : "<#e31010>"; - - subArgs = text.Length == 3 ? string.Empty : text.Remove(0, 3); - if (string.IsNullOrEmpty(subArgs)) - { - Utils.SendMessage((player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), player.PlayerId, player.GetRealName(clientData: true), player.GetClient().FriendCode, player.GetClient().GetHashedPuid(), player.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}", player.PlayerId); - } - else - { - if (Options.ApplyModeratorList.GetValue() == 0 || (!Utils.IsPlayerModerator(player.FriendCode) && TagManager.ReadPermission(player.FriendCode) < 2)) - { - Utils.SendMessage(GetString("Message.MeCommandNoPermission"), player.PlayerId); - break; - } - - - - if (byte.TryParse(subArgs, out byte meid)) - { - if (meid != player.PlayerId) - { - var targetplayer = Utils.GetPlayerById(meid); - if (targetplayer != null && targetplayer.GetClient() != null) - { - Utils.SendMessage($"{string.Format(GetString("Message.MeCommandTargetInfo"), targetplayer.PlayerId, targetplayer.GetRealName(clientData: true), targetplayer.GetClient().FriendCode, targetplayer.GetClient().GetHashedPuid(), targetplayer.FriendCode.GetDevUser().GetUserType())}", player.PlayerId); - } - else - { - Utils.SendMessage($"{(GetString("Message.MeCommandInvalidID"))}", player.PlayerId); - } - } - else - { - Utils.SendMessage($"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}", player.PlayerId); - } - } - else - { - Utils.SendMessage($"{(GetString("Message.MeCommandInvalidID"))}", player.PlayerId); - } - } - break; - - case "/start": - case "/开始": - case "/старт": - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); - break; - } - - if (!Utils.IsPlayerModerator(player.FriendCode) && TagManager.ReadPermission(player.FriendCode) < 3) - { - Utils.SendMessage(GetString("StartCommandNoAccess"), player.PlayerId); - - break; - - } - if (Options.ApplyModeratorList.GetValue() == 0 || Options.AllowStartCommand.GetBool() == false) - { - Utils.SendMessage(GetString("StartCommandDisabled"), player.PlayerId); - break; - } - if (GameStates.IsCountDown) - { - Utils.SendMessage(GetString("StartCommandCountdown"), player.PlayerId); - break; - } - subArgs = args.Length < 2 ? "" : args[1]; - if (string.IsNullOrEmpty(subArgs) || !int.TryParse(subArgs, out int countdown)) - { - countdown = 5; - } - else - { - countdown = int.Parse(subArgs); - } - if (countdown < Options.StartCommandMinCountdown.CurrentValue || countdown > Options.StartCommandMaxCountdown.CurrentValue) - { - Utils.SendMessage(string.Format(GetString("StartCommandInvalidCountdown"), Options.StartCommandMinCountdown.CurrentValue, Options.StartCommandMaxCountdown.CurrentValue), player.PlayerId); - break; - } - GameStartManager.Instance.BeginGame(); - GameStartManager.Instance.countDownTimer = countdown; - Utils.SendMessage(string.Format(GetString("StartCommandStarted"), player.name)); - break; - case "/end": - case "/encerrar": - case "/завершить": - case "/结束": - case "/结束游戏": - if (!TagManager.CanUseEndCommand(player.FriendCode)) - { - Utils.SendMessage(GetString("EndCommandNoAccess"), player.PlayerId); - break; - - } - Utils.SendMessage(string.Format(GetString("EndCommandEnded"), player.name)); - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); - GameManager.Instance.LogicFlow.CheckEndCriteria(); - break; - case "/exe": - case "/уничтожить": - case "/повесить": - case "/казнить": - case "/казнь": - case "/мут": - case "/驱逐": - case "/驱赶": - if (!TagManager.CanUseExecuteCommand(player.FriendCode)) - { - Utils.SendMessage(GetString("ExecuteCommandNoAccess"), player.PlayerId); - break; - } - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - break; - } - if (args.Length < 2 || !int.TryParse(args[1], out int id)) break; - var target = Utils.GetPlayerById(id); - if (target != null) - { - target.Data.IsDead = true; - target.SetDeathReason(PlayerState.DeathReason.etc); - target.SetRealKiller(player); - Main.PlayerStates[target.PlayerId].SetDead(); - target.RpcExileV2(); - MurderPlayerPatch.AfterPlayerDeathTasks(target, target, GameStates.IsMeeting); - Utils.SendMessage(string.Format(GetString("Message.ExecutedNonHost"), target.Data.PlayerName, player.Data.PlayerName)); - } - break; - - - default: - if (SpamManager.CheckSpam(player, text)) return; - break; - } - } -} -[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] -class ChatUpdatePatch -{ - public static bool DoBlockChat = false; - public static ChatController Instance; - public static void Postfix(ChatController __instance) - { - if (!AmongUsClient.Instance.AmHost || Main.MessagesToSend.Count == 0 || (Main.MessagesToSend[0].Item2 == byte.MaxValue && Main.MessageWait.Value > __instance.timeSinceLastMessage)) return; - if (DoBlockChat) return; - - Instance ??= __instance; - - if (Main.DarkTheme.Value) - { - var chatBubble = __instance.chatBubblePool.Prefab.Cast(); - chatBubble.TextArea.overrideColorTags = false; - chatBubble.TextArea.color = Color.white; - chatBubble.Background.color = Color.black; - } - - var player = PlayerControl.LocalPlayer; - if (GameStates.IsInGame || player.Data.IsDead) - { - player = Main.AllAlivePlayerControls.ToArray().OrderBy(x => x.PlayerId).FirstOrDefault() - ?? Main.AllPlayerControls.ToArray().OrderBy(x => x.PlayerId).FirstOrDefault() - ?? player; - } - //Logger.Info($"player is null? {player == null}", "ChatUpdatePatch"); - if (player == null) return; - - (string msg, byte sendTo, string title) = Main.MessagesToSend[0]; - //Logger.Info($"MessagesToSend - sendTo: {sendTo} - title: {title}", "ChatUpdatePatch"); - - if (sendTo != byte.MaxValue && GameStates.IsLobby) - { - var networkedPlayerInfo = Utils.GetPlayerInfoById(sendTo); - if (networkedPlayerInfo != null) - { - if (networkedPlayerInfo.DefaultOutfit.ColorId == -1) - { - var delaymessage = Main.MessagesToSend[0]; - Main.MessagesToSend.RemoveAt(0); - Main.MessagesToSend.Add(delaymessage); - return; - } - // green beans color id is -1 - } - // It is impossible to get null player here unless it quits - } - Main.MessagesToSend.RemoveAt(0); - - int clientId = sendTo == byte.MaxValue ? -1 : Utils.GetPlayerById(sendTo).GetClientId(); - var name = player.Data.PlayerName; - - //__instance.freeChatField.textArea.characterLimit = 999; - - if (clientId == -1) - { - player.SetName(title); - DestroyableSingleton.Instance.Chat.AddChat(player, msg, false); - player.SetName(name); - } - - if (clientId == AmongUsClient.Instance.ClientId || sendTo == PlayerControl.LocalPlayer.PlayerId) - { - player.SetName(title); - DestroyableSingleton.Instance.Chat.AddChat(player, msg, false); - player.SetName(name); - return; - } - - var writer = CustomRpcSender.Create("MessagesToSend", SendOption.None); - writer.StartMessage(clientId); - writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) - .Write(player.Data.NetId) - .Write(title) - .EndRpc(); - writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) - .Write(msg) - .EndRpc(); - writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) - .Write(player.Data.NetId) - .Write(player.Data.PlayerName) - .EndRpc(); - writer.EndMessage(); - writer.SendMessage(); - - __instance.timeSinceLastMessage = 0f; - } -} - -[HarmonyPatch(typeof(FreeChatInputField), nameof(FreeChatInputField.UpdateCharCount))] -internal class UpdateCharCountPatch -{ - public static void Postfix(FreeChatInputField __instance) - { - int length = __instance.textArea.text.Length; - __instance.charCountText.SetText(length <= 0 ? GetString("ThankYouForUsingTOHE") : $"{length}/{__instance.textArea.characterLimit}"); - __instance.charCountText.enableWordWrapping = false; - if (length < (AmongUsClient.Instance.AmHost ? 888 : 444)) - __instance.charCountText.color = Color.black; - else if (length < (AmongUsClient.Instance.AmHost ? 1111 : 777)) - __instance.charCountText.color = new Color(1f, 1f, 0f, 1f); - else - __instance.charCountText.color = Color.red; - } -} -[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSendChat))] -class RpcSendChatPatch -{ - public static bool Prefix(PlayerControl __instance, string chatText, ref bool __result) - { - if (string.IsNullOrWhiteSpace(chatText)) - { - __result = false; - return false; - } - if (!GameStates.IsModHost) - { - __result = false; - return true; - } - 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.Contains("who", StringComparison.OrdinalIgnoreCase)) - DestroyableSingleton.Instance.SendWho(); - MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(__instance.NetId, (byte)RpcCalls.SendChat, SendOption.None); - messageWriter.Write(chatText); - messageWriter.EndMessage(); - __result = true; - return false; - } -} + canceled = true; + Utils.SendMessage(GetString("Message.CovenInfo"), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Coven), GetString("CovenInfoTitle"))); + break; + + case "/rn": + case "/rename": + case "/renomear": + case "/переименовать": + case "/重命名": + case "/命名为": + canceled = true; + if (args.Length < 1) break; + if (args.Skip(1).Join(delimiter: " ").Length is > 10 or < 1) + { + Utils.SendMessage(GetString("Message.AllowNameLength"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + var temp = args.Skip(1).Join(delimiter: " "); + Main.HostRealName = temp; + Main.AllPlayerNames[PlayerControl.LocalPlayer.PlayerId] = temp; + Utils.SendMessage(string.Format(GetString("Message.SetName"), temp), PlayerControl.LocalPlayer.PlayerId); + } + break; + + case "/hn": + case "/hidename": + case "/semnome": + case "/隐藏名字": + case "/藏名": + 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": + case "/nível": + case "/nivel": + case "/等级": + case "/等级设置为": + 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": + case "/atual": + case "/设置": + case "/系统设置": + case "/模组设置": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + switch (subArgs) + { + case "r": + case "roles": + case "funções": + Utils.ShowActiveRoles(); + break; + case "a": + case "all": + case "tudo": + Utils.ShowAllActiveSettings(); + break; + default: + Utils.ShowActiveSettings(); + break; + } + break; + + case "/dis": + case "/disconnect": + case "/desconectar": + case "/断连": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + switch (subArgs) + { + case "crew": + case "tripulante": + case "船员": + GameManager.Instance.enabled = false; + Utils.NotifyGameEnding(); + GameManager.Instance.RpcEndGame(GameOverReason.HumansDisconnect, false); + break; + + case "imp": + case "impostor": + case "内鬼": + case "伪装者": + GameManager.Instance.enabled = false; + Utils.NotifyGameEnding(); + GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); + break; + + default: + __instance.AddChat(PlayerControl.LocalPlayer, "crew | imp"); + if (TranslationController.Instance.currentLanguage.languageID == SupportedLangs.Brazilian) + { + __instance.AddChat(PlayerControl.LocalPlayer, "tripulante | impostor"); + } + cancelVal = "/dis"; + break; + } + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Admin, 0); + break; + + case "/r": + case "/role": + case "/р": + case "/роль": + canceled = true; + if (text.Contains("/role") || text.Contains("/роль")) + subArgs = text.Remove(0, 5); + else + subArgs = text.Remove(0, 2); + SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId); + break; + + case "/up": + case "/指定": + case "/成为": + canceled = true; + subArgs = text.Remove(0, 3); + if (!PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp) + { + Utils.SendMessage($"{GetString("InvalidPermissionCMD")}", PlayerControl.LocalPlayer.PlayerId); + break; + } + if (!Options.EnableUpMode.GetBool()) + { + Utils.SendMessage(string.Format(GetString("Message.YTPlanDisabled"), GetString("EnableYTPlan")), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId, isUp: true); + break; + + //case "/setbasic": + // canceled = true; + // if (GameStates.IsLobby) + // { + // break; + // } + // PlayerControl.LocalPlayer.RpcChangeRoleBasis(CustomRoles.PhantomTOHE); + // break; + + case "/setplayers": + case "/maxjogadores": + case "/设置最大玩家数": + case "/设置最大玩家数量": + case "/设置玩家数": + case "/设置玩家数量": + case "/玩家数": + case "/玩家数量": + case "/玩家": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + var numbereer = Convert.ToByte(subArgs); + if (numbereer > 15 && GameStates.IsVanillaServer) + { + Utils.SendMessage(GetString("Message.MaxPlayersFailByRegion")); + break; + } + Utils.SendMessage(GetString("Message.MaxPlayers") + numbereer); + if (GameStates.IsNormalGame) + GameOptionsManager.Instance.currentNormalGameOptions.MaxPlayers = numbereer; + + else if (GameStates.IsHideNSeek) + GameOptionsManager.Instance.currentHideNSeekGameOptions.MaxPlayers = numbereer; + break; + + case "/h": + case "/help": + case "/ajuda": + case "/хелп": + case "/хэлп": + case "/помощь": + case "/帮助": + case "/教程": + canceled = true; + Utils.ShowHelp(PlayerControl.LocalPlayer.PlayerId); + break; + + case "/icon": + case "/icons": + case "/符号": + case "/标志": + { + Utils.SendMessage(GetString("Command.icons"), PlayerControl.LocalPlayer.PlayerId, GetString("IconsTitle")); + break; + } + + case "/iconhelp": + case "/符号帮助": + case "/标志帮助": + { + Utils.SendMessage(GetString("Command.icons"), title: GetString("IconsTitle")); + break; + } + + case "/kc": + case "/kcount": + case "/количество": + case "/убийцы": + case "/存活阵营": + case "/阵营": + case "/存货阵营信息": + case "/阵营信息": + if (GameStates.IsLobby) break; + + if (!Options.EnableKillerLeftCommand.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + var allAlivePlayers = Main.AllAlivePlayerControls; + int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); + int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); + int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); + int apocnum = allAlivePlayers.Count(pc => pc.IsNeutralApocalypse() || pc.IsTransformedNeutralApocalypse()); + int covnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Coven)); + + var sub = new StringBuilder(); + sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); + + if (Options.ShowMadmatesInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.MadmateCount"), madnum)); + + if (Options.ShowApocalypseInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); + + if (Options.ShowCovenInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.CovenCount"), covnum)); + + sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); + + Utils.SendMessage(sub.ToString(), PlayerControl.LocalPlayer.PlayerId); + break; + case "/vote": + case "/投票": + case "/票": + subArgs = args.Length != 2 ? "" : args[1]; + if (subArgs == "" || !int.TryParse(subArgs, out int arg)) + break; + var plr = Utils.GetPlayerById(arg); + + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (!Options.EnableVoteCommand.GetBool()) + { + Utils.SendMessage(GetString("VoteDisabled"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (Options.ShouldVoteCmdsSpamChat.GetBool()) + { + canceled = true; + } + + if (arg != 253) // skip + { + if (plr == null || !plr.IsAlive()) + { + Utils.SendMessage(GetString("VoteDead"), PlayerControl.LocalPlayer.PlayerId); + break; + } + } + if (!PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("CannotVoteWhenDead"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (GameStates.IsMeeting) + { + PlayerControl.LocalPlayer.RpcCastVote((byte)arg); + } + break; + + case "/d": + case "/death": + case "/morto": + case "/умер": + case "/причина": + case "/死亡原因": + case "/死亡": + canceled = true; + Logger.Info($"PlayerControl.LocalPlayer.PlayerId: {PlayerControl.LocalPlayer.PlayerId}", "/death command"); + if (GameStates.IsLobby) + { + Logger.Info("IsLobby", "/death command"); + Utils.SendMessage(text: GetString("Message.CanNotUseInLobby"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (PlayerControl.LocalPlayer.IsAlive()) + { + Logger.Info("IsAlive", "/death command"); + Utils.SendMessage(text: GetString("DeathCmd.HeyPlayer") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + GetString("DeathCmd.YouAreRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "\n\n" + GetString("DeathCmd.NotDead"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.Vote) + { + Logger.Info("DeathReason.Vote", "/death command"); + Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Ejected"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.Shrouded) + { + Logger.Info("DeathReason.Shrouded", "/death command"); + Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Shrouded"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason == PlayerState.DeathReason.FollowingSuicide) + { + Logger.Info("DeathReason.FollowingSuicide", "/death command"); + Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Lovers"), sendTo: PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + Logger.Info("GetRealKiller()", "/death command"); + var killer = PlayerControl.LocalPlayer.GetRealKiller(out var MurderRole); + string killerName = killer == null ? "N/A" : killer.GetRealName(clientData: true); + string killerRole = killer == null ? "N/A" : Utils.GetRoleName(MurderRole); + Utils.SendMessage(text: GetString("DeathCmd.YourName") + "" + PlayerControl.LocalPlayer.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(PlayerControl.LocalPlayer.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.DeathReason") + "" + Utils.GetVitalText(PlayerControl.LocalPlayer.PlayerId) + "" + "\n\r" + "" + "\n\r" + GetString("DeathCmd.KillerName") + "" + killerName + "" + "\n\r" + GetString("DeathCmd.KillerRole") + "" + $"{killerRole}" + "", sendTo: PlayerControl.LocalPlayer.PlayerId); + + break; + } + + + case "/m": + case "/myrole": + case "/minhafunção": + case "/м": + case "/мояроль": + case "/身份": + case "/我": + case "/我的身份": + case "/我的职业": + canceled = true; + var role = PlayerControl.LocalPlayer.GetCustomRole(); + if (GameStates.IsInGame) + { + var lp = PlayerControl.LocalPlayer; + var Des = lp.GetRoleInfo(true); + var title = $"" + role.GetRoleTitle() + "\n"; + var Conf = new StringBuilder(); + var Sub = new StringBuilder(); + var rlHex = Utils.GetRoleColorCode(role); + var SubTitle = $"" + GetString("YourAddon") + "\n"; + + if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) + Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[role], ref Conf); + var cleared = Conf.ToString(); + var Setting = $"{GetString(role.ToString())} {GetString("Settings:")}\n"; + Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); + + + foreach (var subRole in Main.PlayerStates[lp.PlayerId].SubRoles.ToArray()) + Sub.Append($"\n\n" + $"" + Utils.GetRoleTitle(subRole) + Utils.GetInfoLong(subRole) + ""); + + if (Sub.ToString() != string.Empty) + { + var ACleared = Sub.ToString().Remove(0, 2); + ACleared = ACleared.Length > 1200 ? $"" + ACleared.RemoveHtmlTags() + "" : ACleared; + Sub.Clear().Append(ACleared); + } + + Utils.SendMessage(Des, lp.PlayerId, title, noReplay: true); + Utils.SendMessage("", lp.PlayerId, Conf.ToString(), noReplay: true); + if (Sub.ToString() != string.Empty) Utils.SendMessage(Sub.ToString(), lp.PlayerId, SubTitle, noReplay: true); + } + else + Utils.SendMessage((PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/me": + case "/我的权限": + case "/权限": + canceled = true; + subArgs = text.Length == 3 ? string.Empty : text.Remove(0, 3); + string Devbox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug ? "<#10e341>" : "<#e31010>"; + string UpBox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp ? "<#10e341>" : "<#e31010>"; + string ColorBox = PlayerControl.LocalPlayer.FriendCode.GetDevUser().ColorCmd ? "<#10e341>" : "<#e31010>"; + + if (string.IsNullOrEmpty(subArgs)) + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}"); + } + else + { + if (byte.TryParse(subArgs, out byte meid)) + { + if (meid != PlayerControl.LocalPlayer.PlayerId) + { + var targetplayer = Utils.GetPlayerById(meid); + if (targetplayer != null && targetplayer.GetClient() != null) + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandTargetInfo"), targetplayer.PlayerId, targetplayer.GetRealName(clientData: true), targetplayer.GetClient().FriendCode, targetplayer.GetClient().GetHashedPuid(), targetplayer.FriendCode.GetDevUser().GetUserType())}"); + } + else + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{(GetString("Message.MeCommandInvalidID"))}"); + } + } + else + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}"); + } + } + else + { + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, (PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{(GetString("Message.MeCommandInvalidID"))}"); + } + } + break; + + case "/t": + case "/template": + case "/шаблон": + case "/пример": + case "/模板": + case "/模板信息": + canceled = true; + if (args.Length > 1) TemplateManager.SendTemplate(args[1]); + else Utils.SendMessage($"{GetString("ForExample")}:\n{args[0]} test", PlayerControl.LocalPlayer.PlayerId); + break; + + case "/mw": + case "/messagewait": + case "/消息等待时间": + case "/消息冷却": + 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 "/tpout": + case "/传送出": + case "/传出": + canceled = true; + if (!GameStates.IsLobby) break; + if (!Options.PlayerCanUseTP.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + PlayerControl.LocalPlayer.RpcTeleport(new Vector2(0.1f, 3.8f)); + break; + case "/tpin": + case "/传进": + case "/传送进": + canceled = true; + if (!GameStates.IsLobby) break; + if (!Options.PlayerCanUseTP.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + PlayerControl.LocalPlayer.RpcTeleport(new Vector2(-0.2f, 1.3f)); + break; + + case "/say": + case "/s": + case "/с": + case "/сказать": + case "/说": + canceled = true; + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromTheHost")} ~ {PlayerControl.LocalPlayer.GetRealName(clientData: true)}"); + break; + + case "/mid": + case "/玩家列表": + case "/玩家信息": + case "/玩家编号列表": + canceled = true; + string msgText1 = GetString("PlayerIdList"); + foreach (var pc in Main.AllPlayerControls) + { + if (pc == null) continue; + msgText1 += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); + } + Utils.SendMessage(msgText1, PlayerControl.LocalPlayer.PlayerId); + break; + + case "/ban": + case "/banir": + case "/бан": + case "/забанить": + case "/封禁": + canceled = true; + + string banReason = ""; + if (args.Length < 3) + { + Utils.SendMessage(GetString("BanCommandNoReason"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + subArgs = args[1]; + banReason = string.Join(" ", args.Skip(2)); + } + //subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte banPlayerId)) + { + Utils.SendMessage(GetString("BanCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (banPlayerId == 0) + { + Utils.SendMessage(GetString("BanCommandBanHost"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + var bannedPlayer = Utils.GetPlayerById(banPlayerId); + if (bannedPlayer == null) + { + Utils.SendMessage(GetString("BanCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + // Ban the specified player + AmongUsClient.Instance.KickPlayer(bannedPlayer.GetClientId(), true); + string bannedPlayerName = bannedPlayer.GetRealName(); + string textToSend1 = $"{bannedPlayerName} {GetString("BanCommandBanned")}{PlayerControl.LocalPlayer.name} \nReason: {banReason}\n"; + if (GameStates.IsInGame) + { + textToSend1 += $" {GetString("BanCommandBannedRole")} {GetString(bannedPlayer.GetCustomRole().ToString())}"; + } + Utils.SendMessage(textToSend1); + //string moderatorName = PlayerControl.LocalPlayer.GetRealName().ToString(); + //int startIndex = moderatorName.IndexOf("♥") + "♥".Length; + //moderatorName = moderatorName.Substring(startIndex); + //string extractedString = + string moderatorFriendCode = PlayerControl.LocalPlayer.FriendCode.ToString(); + string bannedPlayerFriendCode = bannedPlayer.FriendCode.ToString(); + string modLogname = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n1) ? n1 : ""; + string banlogname = Main.AllPlayerNames.TryGetValue(bannedPlayer.PlayerId, out var n11) ? n11 : ""; + string logMessage = $"[{DateTime.Now}] {moderatorFriendCode},{modLogname} Banned: {bannedPlayerFriendCode},{banlogname} Reason: {banReason}"; + File.AppendAllText(modLogFiles, logMessage + Environment.NewLine); + break; + + case "/warn": + case "/aviso": + case "/варн": + case "/пред": + case "/предупредить": + case "/警告": + case "/提醒": + canceled = true; + subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte warnPlayerId)) + { + Utils.SendMessage(GetString("WarnCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (warnPlayerId == 0) + { + Utils.SendMessage(GetString("WarnCommandWarnHost"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + var warnedPlayer = Utils.GetPlayerById(warnPlayerId); + if (warnedPlayer == null) + { + Utils.SendMessage(GetString("WarnCommandInvalidID"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + // warn the specified player + string textToSend2 = ""; + string warnReason = "Reason : Not specified\n"; + string warnedPlayerName = warnedPlayer.GetRealName(); + //textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} ~{player.name}"; + if (args.Length > 2) + { + warnReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; + } + else + { + Utils.SendMessage(GetString("WarnExample"), PlayerControl.LocalPlayer.PlayerId); + } + textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} {warnReason} ~{PlayerControl.LocalPlayer.name}"; + Utils.SendMessage(textToSend2); + //string moderatorName1 = PlayerControl.LocalPlayer.GetRealName().ToString(); + //int startIndex1 = moderatorName1.IndexOf("♥") + "♥".Length; + //moderatorName1 = moderatorName1.Substring(startIndex1); + string modLogname1 = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n2) ? n2 : ""; + string warnlogname = Main.AllPlayerNames.TryGetValue(warnedPlayer.PlayerId, out var n12) ? n12 : ""; + + string moderatorFriendCode1 = PlayerControl.LocalPlayer.FriendCode.ToString(); + string warnedPlayerFriendCode = warnedPlayer.FriendCode.ToString(); + string warnedPlayerHashPuid = warnedPlayer.GetClient().GetHashedPuid(); + string logMessage1 = $"[{DateTime.Now}] {moderatorFriendCode1},{modLogname1} Warned: {warnedPlayerFriendCode},{warnedPlayerHashPuid},{warnlogname} Reason: {warnReason}"; + File.AppendAllText(modLogFiles, logMessage1 + Environment.NewLine); + + break; + + case "/kick": + case "/expulsar": + case "/кик": + case "/кикнуть": + case "/выгнать": + case "/踢出": + case "/踢": + canceled = true; + 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; + } + + // Kick the specified player + AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), false); + string kickedPlayerName = kickedPlayer.GetRealName(); + string kickReason = "Reason : Not specified\n"; + if (args.Length > 2) + kickReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; + else + { + Utils.SendMessage("Use /kick [id] [reason] in future. \nExample :-\n /kick 5 not following rules", PlayerControl.LocalPlayer.PlayerId); + } + string textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")} {PlayerControl.LocalPlayer.name} \n {kickReason}"; + + if (GameStates.IsInGame) + { + textToSend += $" {GetString("KickCommandKickedRole")} {GetString(kickedPlayer.GetCustomRole().ToString())}"; + } + Utils.SendMessage(textToSend); + //string moderatorName2 = PlayerControl.LocalPlayer.GetRealName().ToString(); + //int startIndex2 = moderatorName2.IndexOf("♥") + "♥".Length; + //moderatorName2 = moderatorName2.Substring(startIndex2); + + string modLogname2 = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n3) ? n3 : ""; + string kicklogname = Main.AllPlayerNames.TryGetValue(kickedPlayer.PlayerId, out var n13) ? n13 : ""; + + string moderatorFriendCode2 = PlayerControl.LocalPlayer.FriendCode.ToString(); + string kickedPlayerFriendCode = kickedPlayer.FriendCode.ToString(); + string kickedPlayerHashPuid = kickedPlayer.GetClient().GetHashedPuid(); + string logMessage2 = $"[{DateTime.Now}] {moderatorFriendCode2},{modLogname2} Kicked: {kickedPlayerFriendCode},{kickedPlayerHashPuid},{kicklogname} Reason: {kickReason}"; + File.AppendAllText(modLogFiles, logMessage2 + Environment.NewLine); + + break; + + case "/tagcolor": + case "/tagcolour": + case "/标签颜色": + case "/附加名称颜色": + canceled = true; + string name = Main.AllPlayerNames.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var n) ? n : ""; + if (name == "") break; + if (!name.Contains('\r') && PlayerControl.LocalPlayer.FriendCode.GetDevUser().HasTag()) + { + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("ColorCommandNoLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) + { + Logger.Msg($"{subArgs}", "tagcolor"); + Utils.SendMessage(GetString("TagColorInvalidHexCode"), PlayerControl.LocalPlayer.PlayerId); + break; + } + string tagColorFilePath = $"{sponsorTagsFiles}/{PlayerControl.LocalPlayer.FriendCode}.txt"; + if (!File.Exists(tagColorFilePath)) + { + Logger.Msg($"File Not exist, creating file at {tagColorFilePath}", "tagcolor"); + File.Create(tagColorFilePath).Close(); + } + File.WriteAllText(tagColorFilePath, $"{subArgs}"); + } + break; + + case "/exe": + case "/уничтожить": + case "/повесить": + case "/казнить": + case "/казнь": + case "/мут": + case "/驱逐": + case "/驱赶": + 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; + player.SetDeathReason(PlayerState.DeathReason.etc); + player.SetRealKiller(PlayerControl.LocalPlayer); + Main.PlayerStates[player.PlayerId].SetDead(); + player.RpcExileV2(); + MurderPlayerPatch.AfterPlayerDeathTasks(PlayerControl.LocalPlayer, player, GameStates.IsMeeting); + + if (player.IsHost()) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); + else Utils.SendMessage(string.Format(GetString("Message.Executed"), player.Data.PlayerName)); + } + break; + + case "/kill": + case "/matar": + case "/убить": + case "/击杀": + case "/杀死": + 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.RpcMurderPlayer(target); + if (target.IsHost()) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); + else Utils.SendMessage(string.Format(GetString("Message.Executed"), target.Data.PlayerName)); + + _ = new LateTask(() => + { + Utils.NotifyRoles(NoCache: true); + + }, 0.2f, "Update NotifyRoles players after /kill"); + } + break; + + case "/colour": + case "/color": + case "/cor": + case "/цвет": + case "/颜色": + case "/更改颜色": + case "/修改颜色": + case "/换颜色": + 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": + case "/sair": + case "/退出": + case "/退": + canceled = true; + Utils.SendMessage(GetString("Message.CanNotUseByHost"), PlayerControl.LocalPlayer.PlayerId); + break; + + case "/xf": + case "/修复": + case "/修": + canceled = true; + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + foreach (var pc in Main.AllPlayerControls) + { + if (pc.IsAlive()) continue; + + 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": + case "/айди": + case "/编号": + case "/玩家编号": + canceled = true; + string msgText = GetString("PlayerIdList"); + foreach (var pc in Main.AllPlayerControls) + { + if (pc == null) continue; + msgText += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); + } + 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 "/setrole": + case "/设置的职业": + case "/指定的职业": + canceled = true; + subArgs = text.Remove(0, 8); + SendRolesInfo(subArgs, PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug); + break; + + case "/changerole": + case "/mudarfunção": + case "/改变职业": + case "/修改职业": + canceled = true; + if (GameStates.IsHideNSeek) break; + if (!(DebugModeManager.AmDebugger && GameStates.IsInGame)) break; + if (GameStates.IsOnlineGame && !PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug) break; + subArgs = text.Remove(0, 11); + var setRole = FixRoleNameInput(subArgs).ToLower().Trim().Replace(" ", string.Empty); + Logger.Info(setRole, "changerole Input"); + foreach (var rl in CustomRolesHelper.AllRoles) + { + if (rl.IsVanilla()) continue; + var roleName = GetString(rl.ToString()).ToLower().Trim().TrimStart('*').Replace(" ", string.Empty); + //Logger.Info(roleName, "2"); + if (setRole == roleName) + { + PlayerControl.LocalPlayer.GetRoleClass()?.OnRemove(PlayerControl.LocalPlayer.PlayerId); + PlayerControl.LocalPlayer.RpcChangeRoleBasis(rl); + PlayerControl.LocalPlayer.RpcSetCustomRole(rl); + PlayerControl.LocalPlayer.GetRoleClass().OnAdd(PlayerControl.LocalPlayer.PlayerId); + Utils.SendMessage(string.Format("Debug Set your role to {0}", rl.ToString()), PlayerControl.LocalPlayer.PlayerId); + Utils.NotifyRoles(NoCache: true); + Utils.MarkEveryoneDirtySettings(); + break; + } + } + break; + + case "/end": + case "/encerrar": + case "/завершить": + case "/结束": + case "/结束游戏": + canceled = true; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); + GameManager.Instance.LogicFlow.CheckEndCriteria(); + break; + case "/cosid": + case "/装扮编号": + case "/衣服编号": + 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": + case "/强制过会议": + case "/强制跳过会议": + case "/过会议": + case "/结束会议": + case "/强制结束会议": + case "/跳过会议": + canceled = true; + if (GameStates.IsMeeting) + { + MeetingHud.Instance.RpcClose(); + } + else + { + PlayerControl.LocalPlayer.NoCheckStartMeeting(null, force: true); + } + break; + + case "/cs": + case "/播放声音": + case "/播放音效": + canceled = true; + subArgs = text.Remove(0, 3); + PlayerControl.LocalPlayer.RPCPlayCustomSound(subArgs.Trim()); + break; + + case "/sd": + case "/播放音效给": + case "/播放声音给": + 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; + + case "/poll": + case "/发起投票": + case "/执行投票": + canceled = true; + + + if (args.Length == 2 && args[1] == GetString("Replay") && Pollvotes.Any() && PollMSG != string.Empty) + { + Utils.SendMessage(PollMSG); + break; + } + + PollMSG = string.Empty; + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + Polltimer = 120f; + + static System.Collections.IEnumerator StartPollCountdown() + { + if (!Pollvotes.Any() || !GameStates.IsLobby) + { + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + + yield break; + } + bool playervoted = (Main.AllPlayerControls.Length - 1) > Pollvotes.Values.Sum(); + + + while (playervoted && Polltimer > 0f) + { + if (!Pollvotes.Any() || !GameStates.IsLobby) + { + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + + yield break; + } + playervoted = (Main.AllPlayerControls.Length - 1) > Pollvotes.Values.Sum(); + Polltimer -= Time.deltaTime; + yield return null; + } + + if (!Pollvotes.Any() || !GameStates.IsLobby) + { + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + + yield break; + } + + Logger.Info($"FINNISHED!! playervote?: {!playervoted} polltime?: {Polltimer <= 0}", "/poll - StartPollCountdown"); + + DetermineResults(); + } + + static void DetermineResults() + { + int basenum = Pollvotes.Values.Max(); + var winners = Pollvotes.Where(x => x.Value == basenum); + + string msg = ""; + + Color32 clr = new(47, 234, 45, 255); //Main.PlayerColors.First(x => x.Key == PlayerControl.LocalPlayer.PlayerId).Value; + var tytul = Utils.ColorString(clr, GetString("PollResultTitle")); + + if (winners.Count() == 1) + { + var losers = Pollvotes.Where(x => x.Key != winners.First().Key); + msg = string.Format(GetString("Poll.Result"), $"{winners.First().Key}{PollQuestions[winners.First().Key]}", winners.First().Value); + + for (int i = 0; i < losers.Count(); i++) + { + msg += $"\n{losers.ElementAt(i).Key} / {losers.ElementAt(i).Value} {PollQuestions[losers.ElementAt(i).Key]}"; + + } + msg += ""; + + + Utils.SendMessage(msg, title: tytul); + } + else + { + var tienum = Pollvotes.Values.Max(); + var tied = Pollvotes.Where(x => x.Value == tienum); + + for (int i = 0; i < (tied.Count() - 1); i++) + { + msg += "\n" + tied.ElementAt(i).Key + PollQuestions[tied.ElementAt(i).Key] + " & "; + } + msg += "\n" + tied.Last().Key + PollQuestions[tied.Last().Key]; + + Utils.SendMessage(string.Format(GetString("Poll.Tied"), msg, tienum), title: tytul); + } + + Pollvotes.Clear(); + PollQuestions.Clear(); + PollVoted.Clear(); + } + + + if (Main.AllPlayerControls.Length < 3) + { + Utils.SendMessage(GetString("Poll.MissingPlayers"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("Poll.OnlyInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (args.SkipWhile(x => !x.Contains('?')).ToArray().Length < 3 || !args.Any(x => x.Contains('?'))) + { + Utils.SendMessage(GetString("PollUsage"), PlayerControl.LocalPlayer.PlayerId); + break; + } + var resultat = args.TakeWhile(x => !x.Contains('?')).Concat(args.SkipWhile(x => !x.Contains('?')).Take(1)); + + string tytul = string.Join(" ", resultat.Skip(1)); + bool Longtitle = tytul.Length > 30; + tytul = Utils.ColorString(Palette.PlayerColors[PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId], tytul); + var altTitle = Utils.ColorString(new Color32(151, 198, 230, 255), GetString("PollTitle")); + + var ClearTIT = args.ToList(); + ClearTIT.RemoveRange(0, resultat.ToArray().Length); + + var Questions = ClearTIT.ToArray(); + string msg = ""; + + + if (Longtitle) msg += "" + tytul + "\n\n"; + for (int i = 0; i < Math.Clamp(Questions.Length, 2, 5); i++) + { + msg += Utils.ColorString(RndCLR(), $"{char.ToUpper((char)(i + 65))}) {Questions[i]}\n"); + Pollvotes[char.ToUpper((char)(i + 65))] = 0; + PollQuestions[char.ToUpper((char)(i + 65))] = $"〖 {Questions[i]} 〗"; + } + msg += $"\n{GetString("Poll.Begin")}"; + msg += $"\n{GetString("Poll.TimeInfo")}"; + PollMSG = !Longtitle ? "" + tytul + "\n\n" + msg : msg; + + Logger.Info($"Poll message: {msg}", "MEssapoll"); + + Utils.SendMessage(msg, title: !Longtitle ? tytul : altTitle); + + Main.Instance.StartCoroutine(StartPollCountdown()); + + + static Color32 RndCLR() + { + byte r, g, b; + + r = (byte)IRandom.Instance.Next(45, 185); + g = (byte)IRandom.Instance.Next(45, 185); + b = (byte)IRandom.Instance.Next(45, 185); + + return new Color32(r, g, b, 255); + } + + break; + + case "/rps": + case "/剪刀石头布": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + subArgs = args.Length != 2 ? "" : args[1]; + + if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + + if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice)) + { + Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (playerChoice < 0 || playerChoice > 2) + { + Utils.SendMessage(GetString("RpsCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botChoice = rand.Next(0, 3); + var rpsList = new List { GetString("Rock"), GetString("Paper"), GetString("Scissors") }; + if (botChoice == playerChoice) + { + Utils.SendMessage(string.Format(GetString("RpsDraw"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); + } + else if ((botChoice == 0 && playerChoice == 2) || + (botChoice == 1 && playerChoice == 0) || + (botChoice == 2 && playerChoice == 1)) + { + Utils.SendMessage(string.Format(GetString("RpsLose"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); + } + else + { + Utils.SendMessage(string.Format(GetString("RpsWin"), rpsList[botChoice]), PlayerControl.LocalPlayer.PlayerId); + } + break; + } + case "/coinflip": + case "/抛硬币": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + + if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("CoinFlipCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botChoice = rand.Next(1, 101); + var coinSide = (botChoice < 51) ? GetString("Heads") : GetString("Tails"); + Utils.SendMessage(string.Format(GetString("CoinFlipResult"), coinSide), PlayerControl.LocalPlayer.PlayerId); + break; + } + case "/gno": + case "/猜数字": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo)) + { + Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (guessedNo < 0 || guessedNo > 99) + { + Utils.SendMessage(GetString("GNoCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + int targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; + if (Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] == -1) + { + var rand = IRandom.Instance; + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = rand.Next(0, 100); + targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; + } + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]--; + if (Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] == 0 && guessedNo != targetNumber) + { + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = -1; + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] = 7; + //targetNumber = Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0]; + Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (guessedNo < targetNumber) + { + Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); + break; + } + else if (guessedNo > targetNumber) + { + Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + Utils.SendMessage(string.Format(GetString("GNoWon"), Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1]), PlayerControl.LocalPlayer.PlayerId); + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][0] = -1; + Main.GuessNumber[PlayerControl.LocalPlayer.PlayerId][1] = 7; + break; + } + + } + case "/rand": + case "/XY数字": + case "/范围游戏": + case "/猜范围": + case "/范围": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + subArgs = args.Length != 3 ? "" : args[1]; + subArgs2 = args.Length != 3 ? "" : args[2]; + + if (!GameStates.IsLobby && PlayerControl.LocalPlayer.IsAlive()) + { + Utils.SendMessage(GetString("RandCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice1) || subArgs2 == "" || !int.TryParse(subArgs2, out int playerChoice2)) + { + Utils.SendMessage(GetString("RandCommandInfo"), PlayerControl.LocalPlayer.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botResult = rand.Next(playerChoice1, playerChoice2 + 1); + Utils.SendMessage(string.Format(GetString("RandResult"), botResult), PlayerControl.LocalPlayer.PlayerId); + break; + } + + case "/8ball": + case "/8号球": + case "/幸运球": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), PlayerControl.LocalPlayer.PlayerId); + break; + } + canceled = true; + var rando = IRandom.Instance; + int result = rando.Next(0, 16); + string str = ""; + switch (result) + { + case 0: + str = GetString("8BallYes"); + break; + case 1: + str = GetString("8BallNo"); + break; + case 2: + str = GetString("8BallMaybe"); + break; + case 3: + str = GetString("8BallTryAgainLater"); + break; + case 4: + str = GetString("8BallCertain"); + break; + case 5: + str = GetString("8BallNotLikely"); + break; + case 6: + str = GetString("8BallLikely"); + break; + case 7: + str = GetString("8BallDontCount"); + break; + case 8: + str = GetString("8BallStop"); + break; + case 9: + str = GetString("8BallPossibly"); + break; + case 10: + str = GetString("8BallProbably"); + break; + case 11: + str = GetString("8BallProbablyNot"); + break; + case 12: + str = GetString("8BallBetterNotTell"); + break; + case 13: + str = GetString("8BallCantPredict"); + break; + case 14: + str = GetString("8BallWithoutDoubt"); + break; + case 15: + str = GetString("8BallWithDoubt"); + break; + } + Utils.SendMessage("" + str + "", PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medium), GetString("8BallTitle"))); + break; + case "/start": + case "/开始": + case "/старт": + canceled = true; + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), PlayerControl.LocalPlayer.PlayerId); + break; + } + if (GameStates.IsCountDown) + { + Utils.SendMessage(GetString("StartCommandCountdown"), PlayerControl.LocalPlayer.PlayerId); + break; + } + subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !int.TryParse(subArgs, out int countdown)) + { + countdown = 5; + } + else + { + countdown = int.Parse(subArgs); + } + if (countdown < 0 || countdown > 99) + { + Utils.SendMessage(string.Format(GetString("StartCommandInvalidCountdown"), 0, 99), PlayerControl.LocalPlayer.PlayerId); + break; + } + GameStartManager.Instance.BeginGame(); + GameStartManager.Instance.countDownTimer = countdown; + Utils.SendMessage(string.Format(GetString("StartCommandStarted"), PlayerControl.LocalPlayer.name)); + Logger.Info("Game Starting", "ChatCommand"); + 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); + + __instance.quickChatMenu.Clear(); + __instance.quickChatField.Clear(); + } + return !canceled; + } + + public static string FixRoleNameInput(string text) + { + text = text.Replace("着", "者").Trim().ToLower(); + return text switch + { + // Because of partial translation conflicts (zh-cn and zh-tw) + // Need to wait for follow-up finishing + + /* + // GM + "GM(遊戲大師)" or "管理员" or "管理" or "gm" or "GM" => GetString("GM"), + + // 原版职业 + "船員" or "船员" or "白板" or "天选之子" => GetString("CrewmateTOHE"), + "工程師" or "工程师" => GetString("EngineerTOHE"), + "科學家" or "科学家" => GetString("ScientistTOHE"), + "守護天使" or "守护天使" => GetString("GuardianAngelTOHE"), + "偽裝者" or "内鬼" => GetString("ImpostorTOHE"), + "變形者" or "变形者" => GetString("ShapeshifterTOHE"), + + // 隱藏職業 and 隐藏职业 + "陽光開朗大男孩" or "阳光开朗大男孩" => GetString("Sunnyboy"), + "吟遊詩人" or "吟游诗人" => GetString("Bard"), + "核爆者" or "核武器" => GetString("Nuker"), + + // 偽裝者陣營職業 and 内鬼阵营职业 + "賞金獵人" or "赏金猎人" or "赏金" => GetString("BountyHunter"), + "煙火工匠" or "烟花商人" or "烟花爆破者" or "烟花" => GetString("Fireworker"), + "嗜血殺手" or "嗜血杀手" or "嗜血" => GetString("Mercenary"), + "百变怪" or "千面鬼" or "千面" => GetString("ShapeMaster"), + "吸血鬼" or "吸血" => GetString("Vampire"), + "吸血鬼之王" or "吸血鬼女王" => GetString("Vampiress"), + "術士" or "术士" => GetString("Warlock"), + "刺客" or "忍者" => GetString("Ninja"), + "僵屍" or "僵尸" or"殭屍" or "丧尸" => GetString("Zombie"), + "駭客" or "骇客" or "黑客" => GetString("Anonymous"), + "礦工" or "矿工" => GetString("Miner"), + "殺人機器" or "杀戮机器" or "杀戮" or "机器" or "杀戮兵器" => GetString("KillingMachine"), + "通緝犯" or "逃逸者" or "逃逸" => GetString("Escapist"), + "女巫" => GetString("Witch"), + "傀儡師" or "傀儡师" or "傀儡" => GetString("Puppeteer"), + "主謀" or "策划者" => GetString("Mastermind"), + "時間竊賊" or "蚀时者" or "蚀时" or "偷时" => GetString("TimeThief"), + "狙擊手" or "狙击手" or "狙击" => GetString("Sniper"), + "送葬者" or "暗杀者" => GetString("Undertaker"), + "裂縫製造者" or "裂缝制造者" => GetString("RiftMaker"), + "邪惡的追踪者" or "邪恶追踪者" or "邪恶的追踪者" => GetString("EvilTracker"), + "邪惡賭怪" or "邪恶赌怪" or "坏赌" or "恶赌" or "邪恶赌怪" => GetString("EvilGuesser"), + "監管者" or "监管者" or "监管" => GetString("AntiAdminer"), + "狂妄殺手" or "狂妄杀手" => GetString("Arrogance"), + "自爆兵" or "自爆" => GetString("Bomber"), + "清道夫" or "清道" => GetString("Scavenger"), + "陷阱師" or "诡雷" => GetString("Trapster"), + "歹徒" => GetString("Gangster"), + "清潔工" or "清理工" or "清洁工" => GetString("Cleaner"), + "球狀閃電" or "球状闪电" => GetString("Lightning"), + "貪婪者" or "贪婪者" or "贪婪" => GetString("Greedy"), + "被詛咒的狼" or "呪狼" => GetString("CursedWolf"), + "換魂師" or "夺魂者" or "夺魂" => GetString("SoulCatcher"), + "快槍手" or "快枪手" or "快枪" => GetString("QuickShooter"), + "隱蔽者" or "隐蔽者" or "小黑人" => GetString("Camouflager"), + "抹除者" or "抹除" => GetString("Eraser"), + "肢解者" or "肢解" => GetString("Butcher"), + "劊子手" or "刽子手" => GetString("Hangman"), + "隱身人" or "隐匿者" or "隐匿" or "隐身" => GetString("Swooper"), + "船鬼" => GetString("Crewpostor"), + "野人" => GetString("Wildling"), + "騙術師" or "骗术师" => GetString("Trickster"), + "衛道士" or "卫道士" or "内鬼市长" => GetString("Vindicator"), + "寄生蟲" or "寄生虫" => GetString("Parasite"), + "分散者" or "分散" => GetString("Disperser"), + "抑鬱者" or "抑郁者" or "抑郁" => GetString("Inhibitor"), + "破壞者" or "破坏者" or "破坏" => GetString("Saboteur"), + "議員" or "邪恶法官" or "议员" or "邪恶审判" => GetString("Councillor"), + "眩暈者" or "眩晕者" or "眩晕" => GetString("Dazzler"), + "簽約人" or "死亡契约" or "死亡" or "锲约" => GetString("Deathpact"), + "吞噬者" or "吞噬" => GetString("Devourer"), + "軍師" or "军师" => GetString("Consigliere"), + "化型者" or "化形者" => GetString("Morphling"), + "躁動者" or "龙卷风" => GetString("Twister"), + "策畫者" or "潜伏者" or "潜伏" => GetString("Lurker"), + "罪犯" => GetString("Convict"), + "幻想家" or "幻想" => GetString("Visionary"), + "逃亡者" or "逃亡" => GetString("Refugee"), + "潛伏者" or "失败者" or "失败的man" or "失败" => GetString("Underdog"), + "賭博者" or "速度者" or "速度" => GetString("Ludopath"), + "懸賞者" or "教父" => GetString("Godfather"), + "天文學家" or "天文学家" or "天文家" or "天文学" => GetString("Chronomancer"), + "設陷者" or "设陷者" or "设陷" => GetString("Pitfall"), + "狂戰士" or "狂战士" or "升级者" or "狂战士" => GetString("Berserker"), + "壞迷你船員" or "坏迷你船员" or "坏小孩" or "坏迷你" => GetString("EvilMini"), + "勒索者" or "勒索" => GetString("Blackmailer"), + "教唆者" or "教唆" => GetString("Instigator"), + + // 船員陣營職業 and 船员阵营职业 + "擺爛人" or "摆烂人" or "摆烂" => GetString("Needy"), + "大明星" or "明星" => GetString("SuperStar"), + "網紅" or "网红" => GetString("Celebrity"), + "清洗者" or "清洗" => GetString("Cleanser"), + "守衛者" or "守卫者" => GetString("Keeper"), + "俠客" or "侠客" or "正义使者" => GetString("Knight"), + "市長" or "市长" => GetString("Mayor"), + "被害妄想症" or "被害妄想" or "被迫害妄想症" or "被害" or "妄想" or "妄想症" => GetString("Paranoia"), + "愚者" => GetString("Psychic"), + "修理工" or "修理" or "修理大师" => GetString("Mechanic"), + "警長" or "警长" => GetString("Sheriff"), + "義警" or "义务警员" or "警员" => GetString("Vigilante"), + "監禁者" or "狱警" or "狱卒" => GetString("Jailer"), + "模仿者" or "模仿猫" or "模仿" => GetString("CopyCat"), + "告密者" => GetString("Snitch"), + "展現者" or "展现者" or "展现" => GetString("Marshall"), + "增速師" or "增速者" or "增速" => GetString("SpeedBooster"), + "法醫" or "法医" => GetString("Doctor"), + "獨裁主義者" or "独裁者" or "独裁" => GetString("Dictator"), + "偵探" or "侦探" => GetString("Detective"), + "正義賭怪" or "正义赌怪" or "好赌" or "正义的赌怪" => GetString("NiceGuesser"), + "賭場管理員" or "竞猜大师" or "竞猜" => GetString("GuessMaster"), + "傳送師" or "传送师" => GetString("Transporter"), + "時間大師" or "时间操控者" or "时间操控" => GetString("TimeManager"), + "老兵" => GetString("Veteran"), + "埋雷兵" => GetString("Bastion"), + "保鑣" or "保镖" => GetString("Bodyguard"), + "贗品商" or "赝品商" => GetString("Deceiver"), + "擲彈兵" or "掷雷兵" => GetString("Grenadier"), + "軍醫" or "医生" => GetString("Medic"), + "占卜師" or "调查员" or "占卜师" => GetString("FortuneTeller"), + "法官" or "正义法官" or "正义审判" => GetString("Judge"), + "殯葬師" or "入殓师" => GetString("Mortician"), + "通靈師" or "通灵师" => GetString("Mediumshiper"), + "和平之鴿" or "和平之鸽" => GetString("Pacifist"), + "窺視者" or "观察者" or "观察" => GetString("Observer"), + "君主" => GetString("Monarch"), + "預言家" or "预言家" or "预言" => GetString("Overseer"), + "驗屍官" or "验尸官" or "验尸" => GetString("Coroner"), + "正義的追蹤者" or "正义追踪者" or "正义的追踪者" => GetString("Tracker"), + "商人" => GetString("Merchant"), + "總統" or "总统" => GetString("President"), + "獵鷹" or "猎鹰" => GetString("Hawk"), + "捕快" or "下属" => GetString("Deputy"), + "算命師" or "研究者" => GetString("Investigator"), + "守護者" or "守护者" or "守护" => GetString("Guardian"), + "賢者" or "瘾君子" or "醉酒" => GetString("Addict"), + "鼹鼠" => GetString("Mole"), + "藥劑師" or "炼金术士" or "药剂" => GetString("Alchemist"), + "尋跡者" or "寻迹者" or "寻迹" or "寻找鸡腿" => GetString("Tracefinder"), + "先知" or "神谕" or "神谕者" => GetString("Oracle"), + "靈魂論者" or "灵魂论者" => GetString("Spiritualist"), + "變色龍" or "变色龙" or "变色" => GetString("Chameleon"), + "檢查員" or "检查员" or "检查" => GetString("Inspector"), + "仰慕者" or "仰慕" => GetString("Admirer"), + "時間之主" or "时间之主" or "回溯时间" => GetString("TimeMaster"), + "十字軍" or "十字军" => GetString("Crusader"), + "遐想者" or "遐想" => GetString("Reverie"), + "瞭望者" or "瞭望员" => GetString("Lookout"), + "通訊員" or "通信员" => GetString("Telecommunication"), + "執燈人" or "执灯人" or "执灯" or "灯人" or "小灯人" => GetString("Lighter"), + "任務管理員" or "任务管理者" => GetString("TaskManager"), + "目擊者" or "目击者" or "目击" => GetString("Witness"), + "換票師" or "换票师" => GetString("Swapper"), + "警察局長" or "警察局长" => GetString("ChiefOfPolice"), + "好迷你船員" or "好迷你船员" or "好迷你" or "好小孩" => GetString("NiceMini"), + "間諜" or "间谍" => GetString("Spy"), + "隨機者" or "萧暮" or "暮" or "萧暮不姓萧" => GetString("Randomizer"), + "猜想者" or "猜想" or "谜团" => GetString("Enigma"), + "船長" or "舰长" or "船长" => GetString("Captain"), + "慈善家" or "恩人" => GetString("Benefactor"), + + // 中立陣營職業 and 中立阵营职业 + "小丑" or "丑皇" => GetString("Jester"), + "縱火犯" or "纵火犯" or "纵火者" or "纵火" => GetString("Arsonist"), + "焚燒狂" or "焚烧狂" or "焚烧" => GetString("Pyromaniac"), + "神風特攻隊" or "神风特攻队" => GetString("Kamikaze"), + "獵人" or "猎人" => GetString("Huntsman"), + "恐怖分子" => GetString("Terrorist"), + "暴民" or "处刑人" or "处刑" or "处刑者" => GetString("Executioner"), + "律師" or "律师" => GetString("Lawyer"), + "投機主義者" or "投机者" or "投机" => GetString("Opportunist"), + "瑪利歐" or "马里奥" => GetString("Vector"), + "豺狼" or "蓝狼" => GetString("Jackal"), + "神" or "上帝" => GetString("God"), + "冤罪師" or "冤罪师" or "冤罪" => GetString("Innocent"), + "暗殺者" or "隐形者" =>GetString("Stealth"), + "企鵝" or "企鹅" =>GetString("Penguin"), + "鵜鶘" or "鹈鹕" => GetString("Pelican"), + "疫醫" or "瘟疫学家" => GetString("PlagueDoctor"), + "革命家" or "革命者" => GetString("Revolutionist"), + "單身狗" => GetString("Hater"), + "柯南" => GetString("Konan"), + "玩家" => GetString("Demon"), + "潛藏者" or "潜藏" => GetString("Stalker"), + "工作狂" => GetString("Workaholic"), + "至日者" or "至日" => GetString("Solsticer"), + "集票者" or "集票" => GetString("Collector"), + "挑釁者" or "自爆卡车" => GetString("Provocateur"), + "嗜血騎士" or "嗜血骑士" => GetString("BloodKnight"), + "瘟疫之源" or "瘟疫使者" => GetString("PlagueBearer"), + "萬疫之神" or "瘟疫" => GetString("Pestilence"), + "故障者" or "缺点者" or "缺点" => GetString("Glitch"), + "跟班" or "跟班小弟" => GetString("Sidekick"), + "追隨者" or "赌徒" or "下注" => GetString("Follower"), + "魅魔" => GetString("Cultist"), + "連環殺手" or "连环杀手" => GetString("SerialKiller"), + "劍聖" or "天启" => GetString("Juggernaut"), + "感染者" or "感染" => GetString("Infectious"), + "病原體" or "病毒" => GetString("Virus"), + "起訴人" or "起诉人" => GetString("Pursuer"), + "怨靈" or "幽灵" => GetString("Phantom"), + "挑戰者" or "决斗者" or "挑战者" => GetString("Pirate"), + "炸彈王" or "炸弹狂" or "煽动者" => GetString("Agitater"), + "獨行者" or "独行者" => GetString("Maverick"), + "被詛咒的靈魂" or "诅咒之人" => GetString("CursedSoul"), + "竊賊" or "小偷" => GetString("Pickpocket"), + "背叛者" or "背叛" => GetString("Traitor"), + "禿鷲" or "秃鹫" => GetString("Vulture"), + "搗蛋鬼" or "任务执行者" => GetString("Taskinator"), + "麵包師" or "面包师" => GetString("Baker"), + "飢荒" or "饥荒" => GetString("Famine"), + "靈魂召喚者" or "灵魂召唤者" => GetString("Spiritcaller"), + "失憶者" or "失忆者" or "失忆" => GetString("Amnesiac"), + "模仿家" or "效仿者" => GetString("Imitator"), + "強盜" => GetString("Bandit"), + "分身者" => GetString("Doppelganger"), + "受虐狂" => GetString("PunchingBag"), + "賭神" or "末日赌怪" => GetString("Doomsayer"), + "裹屍布" or "裹尸布" => GetString("Shroud"), + "月下狼人" or "狼人" => GetString("Werewolf"), + "薩滿" or "萨满" => GetString("Shaman"), + "冒險家" or "探索者" => GetString("Seeker"), + "精靈" or "小精灵" or "精灵" => GetString("Pixie"), + "咒魔" or "神秘者" => GetString("Occultist"), + "靈魂收割者" or "灵魂收集者" or "灵魂收集" or "收集灵魂" => GetString("SoulCollector"), + "薛丁格的貓" or "薛定谔的猫" => GetString("SchrodingersCat"), + "暗戀者" or "浪漫者" => GetString("Romantic"), + "報復者" or "复仇浪漫者" => GetString("VengefulRomantic"), + "絕情者" or "无情浪漫者" => GetString("RuthlessRomantic"), + "毒醫" or "投毒者" => GetString("Poisoner"), + "代碼工程師" or "巫师" => GetString("HexMaster"), + "幻影" or "魅影" => GetString("Wraith"), + "掃把星" or "扫把星" => GetString("Jinx"), + "魔藥師" or "药剂师" => GetString("PotionMaster"), + "死靈法師" or "亡灵巫师" => GetString("Necromancer"), + "測驗者" or "测验长" => GetString("Quizmaster"), + + // 附加職業 and 附加职业 + "絕境者" or "绝境者" => GetString("LastImpostor"), + "超頻" or "超频波" or "超频" => GetString("Overclocked"), + "戀人" or "恋人" => GetString("Lovers"), + "叛徒" => GetString("Madmate"), + "觀察者" or "窥视者" or "觀察" or "窥视" => GetString("Watcher"), + "閃電俠" or "闪电侠" or "閃電" or "闪电" => GetString("Flash"), + "持燈人" or "火炬" or "持燈" => GetString("Torch"), + "靈媒" or "灵媒" or "靈媒" => GetString("Seer"), + "破平者" or "破平" => GetString("Tiebreaker"), + "膽小鬼" or "胆小鬼" or "膽小" or "胆小" => GetString("Oblivious"), + "視障" or "迷幻者" or "視障" or "迷幻" => GetString("Bewilder"), + "墨鏡" or "患者" => GetString("Sunglasses"), + "加班狂" => GetString("Workhorse"), + "蠢蛋" => GetString("Fool"), + "復仇者" or "复仇者" or "復仇" or "复仇" => GetString("Avanger"), + "Youtuber" or "UP主" or "YT" => GetString("Youtuber"), + "利己主義者" or "利己主义者" or "利己主義" or "利己主义" => GetString("Egoist"), + "竊票者" or "窃票者" or "竊票" or "窃票" => GetString("TicketsStealer"), + //"雙重人格" or "双重人格" => GetString("Schizophrenic"), + "保險箱" or "宝箱怪" => GetString("Mimic"), + "賭怪" or "赌怪" => GetString("Guesser"), + "死神" => GetString("Necroview"), + "長槍" or "持枪" => GetString("Reach"), + "魅魔小弟" => GetString("Charmed"), + "乾淨" or "干净" => GetString("Cleansed"), + "誘餌" or "诱饵" => GetString("Bait"), + "陷阱師" or "陷阱师" => GetString("Trapper"), + "被感染" or "感染" => GetString("Infected"), + "防賭" or "不可被赌" => GetString("Onbound"), + "反擊者" or "回弹者" or "回弹" => GetString("Rebound"), + "平凡者" or "平凡" => GetString("Mundane"), + "騎士" or "骑士" => GetString("Knighted"), + "漠視" or "不受重视" or "被漠視的" => GetString("Unreportable"), + "被傳染" or "传染性" => GetString("Contagious"), + "幸運" or "幸运加持" => GetString("Lucky"), + "倒霉" or "倒霉蛋" => GetString("Unlucky"), + "虛無" or "无效投票" => GetString("VoidBallot"), + "敏感" or "意识者" or "意识" => GetString("Aware"), + "嬌嫩" or "脆弱" or "脆弱者" => GetString("Fragile"), + "專業" or "双重猜测" => GetString("DoubleShot"), + "流氓" => GetString("Rascal"), + "無魂" or "没有灵魂" => GetString("Soulless"), + "墓碑" => GetString("Gravestone"), + "懶人" or "懒人" => GetString("Lazy"), + "驗屍" or "尸检" => GetString("Autopsy"), + "忠誠" or "忠诚" => GetString("Loyal"), + "惡靈" or "恶灵" => GetString("EvilSpirit"), + "狼化" or "招募" or "狼化的" or "被招募的" => GetString("Recruit"), + "被仰慕" or "仰慕" => GetString("Admired"), + "發光" or "光辉" => GetString("Glow"), + "病態" or "患病者" or "患病的" or "患病" => GetString("Diseased"), + "健康" or "健康的" or "健康者" => GetString("Antidote"), + "固執者" or "固执者" or "固執" or "固执" => GetString("Stubborn"), + "無影" or "迅捷" => GetString("Swift"), + "反噬" or "食尸鬼" => GetString("Ghoul"), + "嗜血者" => GetString("Bloodthirst"), + "獵夢者" or "梦魇" or "獵夢"=> GetString("Mare"), + "地雷" or "爆破者" or "爆破" => GetString("Burst"), + "偵察員" or "侦察员" or "偵察" or "侦察" => GetString("Sleuth"), + "笨拙" or "笨蛋" => GetString("Clumsy"), + "敏捷" => GetString("Nimble"), + "規避者" or "规避者" or "规避" => GetString("Circumvent"), + "名人" or "网络员" or "网络" => GetString("Cyber"), + "焦急者" or "焦急的" or "焦急" => GetString("Hurried"), + "OIIAI" => GetString("Oiiai"), + "順從者" or "影响者" or "順從" or "影响" => GetString("Influenced"), + "沉默者" or "沉默" => GetString("Silent"), + "易感者" or "易感" => GetString("Susceptible"), + "狡猾" or "棘手者" or "棘手" => GetString("Tricky"), + "彩虹" => GetString("Rainbow"), + "疲勞者" or "疲劳者" or "疲勞" or "疲劳" => GetString("Tired"), + "雕像" => GetString("Statue"), + "没有搜集的繁体中文" or "雷达" => GetString("Radar"), + + // 幽靈職業 and 幽灵职业 + // 偽裝者 and 内鬼 + "爪牙" => GetString("Minion"), + "黑手黨" or "黑手党" or "黑手" => GetString("Nemesis"), + "嗜血之魂" or "血液伯爵" => GetString("Bloodmoon"), + // 船員 and 船员 + "没有搜集的繁体中文" or "鬼怪" => GetString("Ghastly"), + "冤魂" or "典狱长" => GetString("Warden"), + "報應者" or "惩罚者" or "惩罚" or "报仇者" => GetString("Retributionist"), + + // 随机阵营职业 + "迷你船員" or "迷你船员" or "迷你" or "小孩" or "Mini" => GetString("Mini"),*/ + _ => 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 (var rl in CustomRolesHelper.AllRoles) + { + if (rl.IsVanilla()) continue; + var roleName = GetString(rl.ToString()).ToLower().Trim().Replace(" ", ""); + string nameWithoutId = Regex.Replace(name.Replace(" ", ""), @"^\d+", ""); + if (nameWithoutId == 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.FFA) + { + Utils.SendMessage(GetString("ModeDescribe.FFA"), 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.StartsWith("/bt")) _ = role.Replace("/bt", string.Empty); + if (role.StartsWith("/rt")) _ = role.Replace("/rt", string.Empty); + + if (role == "" || role == string.Empty) + { + Utils.ShowActiveRoles(playerId); + return; + } + + role = FixRoleNameInput(role).ToLower().Trim().Replace(" ", string.Empty); + + foreach (var rl in CustomRolesHelper.AllRoles) + { + 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 or CustomRoles.Mini || rl.IsGhostRole()) 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; + GhostRoleAssign.forceRole.Remove(pid); + RoleAssign.SetRoles.Remove(pid); + RoleAssign.SetRoles.Add(pid, rl); + } + if (rl.IsGhostRole() && !rl.IsAdditionRole() && isDev && (rl.GetCount() >= 1 && rl.GetMode() > 0)) + { + byte pid = playerId == 255 ? (byte)0 : playerId; + CustomRoles setrole = rl.GetCustomRoleTeam() switch + { + Custom_Team.Impostor => CustomRoles.ImpostorTOHE, + _ => CustomRoles.CrewmateTOHE + + }; + RoleAssign.SetRoles.Remove(pid); + RoleAssign.SetRoles.Add(pid, setrole); + GhostRoleAssign.forceRole[pid] = rl; + + devMark = "▲"; + } + + if (isUp) return; + } + var Des = rl.GetInfoLong(); + var title = devMark + $"" + rl.GetRoleTitle() + "\n"; + var Conf = new StringBuilder(); + string rlHex = Utils.GetRoleColorCode(rl); + if (Options.CustomRoleSpawnChances.ContainsKey(rl)) + { + Utils.ShowChildrenSettings(Options.CustomRoleSpawnChances[rl], ref Conf); + var cleared = Conf.ToString(); + var Setting = $"{GetString(rl.ToString())} {GetString("Settings:")}\n"; + Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); + + } + // Show role info + Utils.SendMessage(Des, playerId, title, noReplay: true); + + // Show role settings + Utils.SendMessage("", playerId, Conf.ToString(), noReplay: true); + 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 (!Blackmailer.CheckBlackmaile(player)) ChatManager.SendMessage(player, text); + + if (text.StartsWith("\n")) text = text[1..]; + //if (!text.StartsWith("/")) return; + string[] args = text.Split(' '); + string subArgs = ""; + string subArgs2 = ""; + + //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; Logger.Info($"Is Guesser command", "OnReceiveChat"); return; } + if (player.GetRoleClass() is Judge jd && jd.TrialMsg(player, text)) { canceled = true; Logger.Info($"Is Judge command", "OnReceiveChat"); return; } + if (President.EndMsg(player, text)) { canceled = true; Logger.Info($"Is President command", "OnReceiveChat"); return; } + if (Inspector.InspectCheckMsg(player, text)) { canceled = true; Logger.Info($"Is Inspector command", "OnReceiveChat"); return; } + if (Pirate.DuelCheckMsg(player, text)) { canceled = true; Logger.Info($"Is Pirate command", "OnReceiveChat"); return; } + if (player.GetRoleClass() is Councillor cl && cl.MurderMsg(player, text)) { canceled = true; Logger.Info($"Is Councillor command", "OnReceiveChat"); return; } + if (player.GetRoleClass() is Swapper sw && sw.SwapMsg(player, text)) { canceled = true; Logger.Info($"Is Swapper command", "OnReceiveChat"); return; } + if (Medium.MsMsg(player, text)) { Logger.Info($"Is Medium command", "OnReceiveChat"); return; } + if (Nemesis.NemesisMsgCheck(player, text)) { Logger.Info($"Is Nemesis Revenge command", "OnReceiveChat"); return; } + if (Retributionist.RetributionistMsgCheck(player, text)) { Logger.Info($"Is Retributionist Revenge command", "OnReceiveChat"); return; } + if (player.GetRoleClass() is Exorcist ex && ex.CheckCommand(player, text)) { canceled = true; Logger.Info($"Is Exorcist command", "OnReceiveChat"); return; } + if (player.GetRoleClass() is Dictator dt && dt.ExilePlayer(player, text)) { canceled = true; Logger.Info($"Is Dictator command", "OnReceiveChat"); return; } + if (Ritualist.RitualistMsgCheck(player, text)) { canceled = true; Logger.Info($"Is Ritualist command", "OnReceiveChat"); return; } + + Directory.CreateDirectory(modTagsFiles); + Directory.CreateDirectory(vipTagsFiles); + Directory.CreateDirectory(sponsorTagsFiles); + + if (Blackmailer.CheckBlackmaile(player) && player.IsAlive() && !player.IsHost()) + { + Logger.Info($"This player (id {player.PlayerId}) was Blackmailed", "OnReceiveChat"); + ChatManager.SendPreviousMessagesToAll(); + ChatManager.cancel = false; + canceled = true; + return; + } + if (Exorcist.IsExorcismCurrentlyActive() && player.IsAlive() && !player.IsHost()) + { + Logger.Info($"This player (id {player.PlayerId}) was Exorcised", "OnReceiveChat"); + Exorcist.ExorcisePlayer(player); + canceled=true; + return; + } + + switch (args[0]) + { + case "/r": + case "/role": + case "/р": + case "/роль": + Logger.Info($"Command '/r' was activated", "OnReceiveChat"); + if (text.Contains("/role") || text.Contains("/роль")) + subArgs = text.Remove(0, 5); + else + subArgs = text.Remove(0, 2); + SendRolesInfo(subArgs, player.PlayerId, isDev: player.FriendCode.GetDevUser().DeBug); + break; + + case "/m": + case "/myrole": + case "/minhafunção": + case "/м": + case "/мояроль": + case "/身份": + case "/我": + case "/我的身份": + case "/我的职业": + Logger.Info($"Command '/m' was activated", "OnReceiveChat"); + var role = player.GetCustomRole(); + if (GameStates.IsInGame) + { + var Des = player.GetRoleInfo(true); + var title = $"" + role.GetRoleTitle() + "\n"; + var Conf = new StringBuilder(); + var Sub = new StringBuilder(); + var rlHex = Utils.GetRoleColorCode(role); + var SubTitle = $"" + GetString("YourAddon") + "\n"; + + if (Options.CustomRoleSpawnChances.TryGetValue(role, out var opt)) + Utils.ShowChildrenSettings(opt, ref Conf); + var cleared = Conf.ToString(); + var Setting = $"{GetString(role.ToString())} {GetString("Settings:")}\n"; + Conf.Clear().Append($"" + $"" + Setting + cleared + "" + ""); + + foreach (var subRole in Main.PlayerStates[player.PlayerId].SubRoles.ToArray()) + { + Sub.Append($"\n\n" + $"" + Utils.GetRoleTitle(subRole) + Utils.GetInfoLong(subRole) + ""); + + } + if (Sub.ToString() != string.Empty) + { + var ACleared = Sub.ToString().Remove(0, 2); + ACleared = ACleared.Length > 1200 ? $"" + ACleared.RemoveHtmlTags() + "" : ACleared; + Sub.Clear().Append(ACleared); + } + + Utils.SendMessage(Des, player.PlayerId, title, noReplay: true); + Utils.SendMessage("", player.PlayerId, Conf.ToString(), noReplay: true); + if (Sub.ToString() != string.Empty) Utils.SendMessage(Sub.ToString(), player.PlayerId, SubTitle, noReplay: true); + + Logger.Info($"Command '/m' should be send message", "OnReceiveChat"); + } + else + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + + case "/h": + case "/help": + case "/ajuda": + case "/хелп": + case "/хэлп": + case "/помощь": + case "/帮助": + case "/教程": + Utils.ShowHelpToClient(player.PlayerId); + break; + + case "/ans": + case "/asw": + case "/answer": + case "/回答": + Quizmaster.AnswerByChat(player, args); + break; + + case "/qmquiz": + case "/提问": + Quizmaster.ShowQuestion(player); + break; + + case "/l": + case "/lastresult": + case "/fimdejogo": + case "/上局信息": + case "/信息": + case "/情况": + Utils.ShowKillLog(player.PlayerId); + Utils.ShowLastRoles(player.PlayerId); + Utils.ShowLastResult(player.PlayerId); + break; + + case "/gr": + case "/gameresults": + case "/resultados": + case "/对局结果": + case "/上局结果": + case "/结果": + Utils.ShowLastResult(player.PlayerId); + break; + + case "/kh": + case "/killlog": + case "/击杀日志": + case "/击杀情况": + Utils.ShowKillLog(player.PlayerId); + break; + + case "/rs": + case "/sum": + case "/rolesummary": + case "/sumario": + case "/sumário": + case "/summary": + case "/результат": + case "/上局职业": + case "/职业信息": + case "/对局职业": + Utils.ShowLastRoles(player.PlayerId); + break; + + case "/ghostinfo": + case "/幽灵职业介绍": + case "/鬼魂职业介绍": + case "/幽灵职业": + case "/鬼魂职业": + if (GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); + break; + } + Utils.SendMessage(GetString("Message.GhostRoleInfo"), player.PlayerId); + break; + + case "/apocinfo": + case "/apocalypseinfo": + case "/末日中立职业介绍": + case "/末日中立介绍": + case "/末日类中立职业介绍": + case "/末日类中立介绍": + Utils.SendMessage(GetString("Message.ApocalypseInfo"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Apocalypse), GetString("ApocalypseInfoTitle"))); + break; + + case "/coveninfo": + case "/covinfo": + Utils.SendMessage(GetString("Message.CovenInfo"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Coven), GetString("CovenInfoTitle"))); + break; + + case "/rn": + case "/rename": + case "/renomear": + case "/переименовать": + case "/重命名": + case "/命名为": + if (Options.PlayerCanSetName.GetBool() || player.FriendCode.GetDevUser().IsDev || player.FriendCode.GetDevUser().NameCmd || Utils.IsPlayerVIP(player.FriendCode)) + { + if (GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); + break; + } + if (args.Length < 1) break; + if (args.Skip(1).Join(delimiter: " ").Length is > 10 or < 1) + { + Utils.SendMessage(GetString("Message.AllowNameLength"), player.PlayerId); + break; + } + Main.AllPlayerNames[player.PlayerId] = args.Skip(1).Join(delimiter: " "); + Utils.SendMessage(string.Format(GetString("Message.SetName"), args.Skip(1).Join(delimiter: " ")), player.PlayerId); + break; + } + else + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + } + break; + + case "/n": + case "/now": + case "/atual": + case "/设置": + case "/系统设置": + case "/模组设置": + subArgs = args.Length < 2 ? "" : args[1]; + switch (subArgs) + { + case "r": + case "roles": + case "funções": + Utils.ShowActiveRoles(player.PlayerId); + break; + case "a": + case "all": + case "tudo": + Utils.ShowAllActiveSettings(player.PlayerId); + break; + default: + Utils.ShowActiveSettings(player.PlayerId); + break; + } + break; + + case "/up": + case "/指定": + case "/成为": + _ = text.Remove(0, 3); + if (!Options.EnableUpMode.GetBool()) + { + Utils.SendMessage(string.Format(GetString("Message.YTPlanDisabled"), GetString("EnableYTPlan")), player.PlayerId); + break; + } + else + { + Utils.SendMessage(GetString("Message.OnlyCanBeUsedByHost"), player.PlayerId); + break; + } + + case "/win": + case "/winner": + case "/vencedor": + case "/胜利": + case "/获胜": + case "/赢": + case "/胜利者": + case "/获胜的人": + case "/赢家": + if (Main.winnerNameList.Count == 0) Utils.SendMessage(GetString("NoInfoExists"), player.PlayerId); + else Utils.SendMessage("Winner: " + string.Join(", ", Main.winnerNameList), player.PlayerId); + break; + + + case "/pv": + canceled = true; + if (!Pollvotes.Any()) + { + Utils.SendMessage(GetString("Poll.Inactive"), player.PlayerId); + break; + } + if (PollVoted.Contains(player.PlayerId)) + { + Utils.SendMessage(GetString("Poll.AlreadyVoted"), player.PlayerId); + break; + } + + subArgs = args.Length != 2 ? "" : args[1]; + char vote = ' '; + + if (int.TryParse(subArgs, out int integer) && (Pollvotes.Count - 1) >= integer) + { + vote = char.ToUpper((char)(integer + 65)); + } + else if (!(char.TryParse(subArgs, out vote) && Pollvotes.ContainsKey(char.ToUpper(vote)))) + { + Utils.SendMessage(GetString("Poll.VotingInfo"), player.PlayerId); + break; + } + vote = char.ToUpper(vote); + + PollVoted.Add(player.PlayerId); + Pollvotes[vote]++; + Utils.SendMessage(string.Format(GetString("Poll.YouVoted"), vote, Pollvotes[vote]), player.PlayerId); + Logger.Info($"The new value of {vote} is {Pollvotes[vote]}", "TestPV_CHAR"); + + break; + + case "/icon": + case "/icons": + case "/符号": + case "/标志": + { + Utils.SendMessage(GetString("Command.icons"), player.PlayerId, GetString("IconsTitle")); + break; + } + + case "/kc": + case "/kcount": + case "/количество": + case "/убийцы": + case "/存活阵营": + case "/阵营": + case "/存货阵营信息": + case "/阵营信息": + if (GameStates.IsLobby) break; + + if (!Options.EnableKillerLeftCommand.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + + var allAlivePlayers = Main.AllAlivePlayerControls; + int impnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Impostor)); + int madnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsMadmate() || pc.Is(CustomRoles.Madmate)); + int apocnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNA()); + int neutralnum = allAlivePlayers.Count(pc => pc.GetCustomRole().IsNK()); + int covnum = allAlivePlayers.Count(pc => pc.Is(Custom_Team.Coven)); + + var sub = new StringBuilder(); + sub.Append(string.Format(GetString("Remaining.ImpostorCount"), impnum)); + + if (Options.ShowMadmatesInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.MadmateCount"), madnum)); + + if (Options.ShowApocalypseInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.ApocalypseCount"), apocnum)); + + if (Options.ShowCovenInLeftCommand.GetBool()) + sub.Append(string.Format("\n\r" + GetString("Remaining.CovenCount"), covnum)); + + sub.Append(string.Format("\n\r" + GetString("Remaining.NeutralCount"), neutralnum)); + + Utils.SendMessage(sub.ToString(), player.PlayerId); + break; + + case "/d": + case "/death": + case "/morto": + case "/умер": + case "/причина": + case "/死亡原因": + case "/死亡": + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + } + else if (player.IsAlive()) + { + Utils.SendMessage(GetString("DeathCmd.HeyPlayer") + "" + player.GetRealName() + "" + GetString("DeathCmd.YouAreRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "\n\n" + GetString("DeathCmd.NotDead"), player.PlayerId); + break; + } + else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.Vote) + { + Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Ejected"), player.PlayerId); + break; + } + else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.Shrouded) + { + Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Shrouded"), player.PlayerId); + break; + } + else if (Main.PlayerStates[player.PlayerId].deathReason == PlayerState.DeathReason.FollowingSuicide) + { + Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.Lovers"), player.PlayerId); + break; + } + else + { + var killer = player.GetRealKiller(out var MurderRole); + string killerName = killer == null ? "N/A" : killer.GetRealName(clientData: true); + string killerRole = killer == null ? "N/A" : Utils.GetRoleName(MurderRole); + Utils.SendMessage(GetString("DeathCmd.YourName") + "" + player.GetRealName() + "" + "\n\r" + GetString("DeathCmd.YourRole") + "" + $"{Utils.GetRoleName(player.GetCustomRole())}" + "" + "\n\r" + GetString("DeathCmd.DeathReason") + "" + Utils.GetVitalText(player.PlayerId) + "" + "\n\r" + "" + "\n\r" + GetString("DeathCmd.KillerName") + "" + killerName + "" + "\n\r" + GetString("DeathCmd.KillerRole") + "" + $"{killerRole}" + "", player.PlayerId); + break; + } + + case "/t": + case "/template": + case "/шаблон": + case "/пример": + case "/模板": + case "/模板信息": + 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": + case "/cor": + case "/цвет": + case "/颜色": + case "/更改颜色": + case "/修改颜色": + case "/换颜色": + if (Options.PlayerCanSetColor.GetBool() || player.FriendCode.GetDevUser().IsDev || player.FriendCode.GetDevUser().ColorCmd || Utils.IsPlayerVIP(player.FriendCode)) + { + 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": + case "/sair": + case "/退出": + case "/退": + if (Options.PlayerCanUseQuitCommand.GetBool()) + { + 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); + } + } + else + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + } + break; + + case "/id": + case "/айди": + case "/编号": + case "/玩家编号": + if ((Options.ApplyModeratorList.GetValue() == 0 || !Utils.IsPlayerModerator(player.FriendCode)) + && !Options.EnableVoteCommand.GetBool()) break; + + string msgText = GetString("PlayerIdList"); + foreach (var pc in Main.AllPlayerControls) + { + if (pc == null) continue; + msgText += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); + } + Utils.SendMessage(msgText, player.PlayerId); + break; + + case "/mid": + case "/玩家列表": + case "/玩家信息": + case "/玩家编号列表": + //canceled = true; + //checking if modlist on or not + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("midCommandDisabled"), player.PlayerId); + break; + } + //checking if player is has necessary privellege or not + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("midCommandNoAccess"), player.PlayerId); + break; + } + string msgText1 = GetString("PlayerIdList"); + foreach (var pc in Main.AllPlayerControls) + { + if (pc == null) continue; + msgText1 += "\n" + pc.PlayerId.ToString() + " → " + pc.GetRealName(); + } + Utils.SendMessage(msgText1, player.PlayerId); + break; + + case "/ban": + case "/banir": + case "/бан": + case "/забанить": + case "/封禁": + //canceled = true; + // Check if the ban command is enabled in the settings + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("BanCommandDisabled"), player.PlayerId); + break; + } + + // Check if the player has the necessary privileges to use the command + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("BanCommandNoAccess"), player.PlayerId); + break; + } + string banReason; + if (args.Length < 3) + { + Utils.SendMessage(GetString("BanCommandNoReason"), player.PlayerId); + break; + } + else + { + subArgs = args[1]; + banReason = string.Join(" ", args.Skip(2)); + } + //subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte banPlayerId)) + { + Utils.SendMessage(GetString("BanCommandInvalidID"), player.PlayerId); + break; + } + + if (banPlayerId == 0) + { + Utils.SendMessage(GetString("BanCommandBanHost"), player.PlayerId); + break; + } + + var bannedPlayer = Utils.GetPlayerById(banPlayerId); + if (bannedPlayer == null) + { + Utils.SendMessage(GetString("BanCommandInvalidID"), player.PlayerId); + break; + } + + // Prevent moderators from baning other moderators + if (Utils.IsPlayerModerator(bannedPlayer.FriendCode)) + { + Utils.SendMessage(GetString("BanCommandBanMod"), player.PlayerId); + break; + } + + // Ban the specified player + AmongUsClient.Instance.KickPlayer(bannedPlayer.GetClientId(), true); + string bannedPlayerName = bannedPlayer.GetRealName(); + string textToSend1 = $"{bannedPlayerName} {GetString("BanCommandBanned")}{player.name} \nReason: {banReason}\n"; + if (GameStates.IsInGame) + { + textToSend1 += $" {GetString("BanCommandBannedRole")} {GetString(bannedPlayer.GetCustomRole().ToString())}"; + } + Utils.SendMessage(textToSend1); + //string moderatorName = player.GetRealName().ToString(); + //int startIndex = moderatorName.IndexOf("♥") + "♥".Length; + //moderatorName = moderatorName.Substring(startIndex); + //string extractedString = + string modLogname = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n1) ? n1 : ""; + string banlogname = Main.AllPlayerNames.TryGetValue(bannedPlayer.PlayerId, out var n11) ? n11 : ""; + string moderatorFriendCode = player.FriendCode.ToString(); + string bannedPlayerFriendCode = bannedPlayer.FriendCode.ToString(); + string bannedPlayerHashPuid = bannedPlayer.GetClient().GetHashedPuid(); + string logMessage = $"[{DateTime.Now}] {moderatorFriendCode},{modLogname} Banned: {bannedPlayerFriendCode},{bannedPlayerHashPuid},{banlogname} Reason: {banReason}"; + File.AppendAllText(modLogFiles, logMessage + Environment.NewLine); + break; + + case "/warn": + case "/aviso": + case "/варн": + case "/пред": + case "/предупредить": + case "/警告": + case "/提醒": + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("WarnCommandDisabled"), player.PlayerId); + break; + } + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("WarnCommandNoAccess"), player.PlayerId); + break; + } + subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte warnPlayerId)) + { + Utils.SendMessage(GetString("WarnCommandInvalidID"), player.PlayerId); + break; + } + if (warnPlayerId == 0) + { + Utils.SendMessage(GetString("WarnCommandWarnHost"), player.PlayerId); + break; + } + + var warnedPlayer = Utils.GetPlayerById(warnPlayerId); + if (warnedPlayer == null) + { + Utils.SendMessage(GetString("WarnCommandInvalidID"), player.PlayerId); + break; + } + + // Prevent moderators from warning other moderators + if (Utils.IsPlayerModerator(warnedPlayer.FriendCode)) + { + Utils.SendMessage(GetString("WarnCommandWarnMod"), player.PlayerId); + break; + } + // warn the specified player + string warnReason = "Reason : Not specified\n"; + string warnedPlayerName = warnedPlayer.GetRealName(); + //textToSend2 = $" {warnedPlayerName} {GetString("WarnCommandWarned")} ~{player.name}"; + if (args.Length > 2) + { + warnReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; + } + else + { + Utils.SendMessage("Use /warn [id] [reason] in future. \nExample :-\n /warn 5 lava chatting", player.PlayerId); + } + Utils.SendMessage($" {warnedPlayerName} {GetString("WarnCommandWarned")} {warnReason} ~{player.name}"); + //string moderatorName1 = player.GetRealName().ToString(); + //int startIndex1 = moderatorName1.IndexOf("♥") + "♥".Length; + //moderatorName1 = moderatorName1.Substring(startIndex1); + string modLogname1 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n2) ? n2 : ""; + string warnlogname = Main.AllPlayerNames.TryGetValue(warnedPlayer.PlayerId, out var n12) ? n12 : ""; + string moderatorFriendCode1 = player.FriendCode.ToString(); + string warnedPlayerFriendCode = warnedPlayer.FriendCode.ToString(); + string warnedPlayerHashPuid = warnedPlayer.GetClient().GetHashedPuid(); + string logMessage1 = $"[{DateTime.Now}] {moderatorFriendCode1},{modLogname1} Warned: {warnedPlayerFriendCode},{warnedPlayerHashPuid},{warnlogname} Reason: {warnReason}"; + File.AppendAllText(modLogFiles, logMessage1 + Environment.NewLine); + + break; + case "/kick": + case "/expulsar": + case "/кик": + case "/кикнуть": + case "/выгнать": + case "/踢出": + case "/踢": + // 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 (!Utils.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 (Utils.IsPlayerModerator(kickedPlayer.FriendCode)) + { + Utils.SendMessage(GetString("KickCommandKickMod"), player.PlayerId); + break; + } + + // Kick the specified player + AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), false); + string kickedPlayerName = kickedPlayer.GetRealName(); + string kickReason = "Reason : Not specified\n"; + if (args.Length > 2) + kickReason = "Reason : " + string.Join(" ", args.Skip(2)) + "\n"; + else + { + Utils.SendMessage("Use /kick [id] [reason] in future. \nExample :-\n /kick 5 not following rules", player.PlayerId); + } + string textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")} {player.name} \n {kickReason}"; + + if (GameStates.IsInGame) + { + textToSend += $" {GetString("KickCommandKickedRole")} {GetString(kickedPlayer.GetCustomRole().ToString())}"; + } + Utils.SendMessage(textToSend); + //string moderatorName2 = player.GetRealName().ToString(); + //int startIndex2 = moderatorName2.IndexOf("♥") + "♥".Length; + //moderatorName2 = moderatorName2.Substring(startIndex2); + string modLogname2 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n3) ? n3 : ""; + string kicklogname = Main.AllPlayerNames.TryGetValue(kickedPlayer.PlayerId, out var n13) ? n13 : ""; + + string moderatorFriendCode2 = player.FriendCode.ToString(); + string kickedPlayerFriendCode = kickedPlayer.FriendCode.ToString(); + string kickedPlayerHashPuid = kickedPlayer.GetClient().GetHashedPuid(); + string logMessage2 = $"[{DateTime.Now}] {moderatorFriendCode2},{modLogname2} Kicked: {kickedPlayerFriendCode},{kickedPlayerHashPuid},{kicklogname} Reason: {kickReason}"; + File.AppendAllText(modLogFiles, logMessage2 + Environment.NewLine); + + break; + case "/modcolor": + case "/modcolour": + case "/模组端颜色": + case "/模组颜色": + if (Options.ApplyModeratorList.GetValue() == 0) + { + Utils.SendMessage(GetString("ColorCommandDisabled"), player.PlayerId); + break; + } + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("ColorCommandNoAccess"), player.PlayerId); + break; + } + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("ColorCommandNoLobby"), player.PlayerId); + break; + } + if (!Options.GradientTagsOpt.GetBool()) + { + subArgs = args.Length != 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) + { + Logger.Msg($"{subArgs}", "modcolor"); + Utils.SendMessage(GetString("ColorInvalidHexCode"), player.PlayerId); + break; + } + string colorFilePath = $"{modTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(colorFilePath)) + { + Logger.Warn($"File Not exist, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); + File.Create(colorFilePath).Close(); + } + + File.WriteAllText(colorFilePath, $"{subArgs}"); + break; + } + else + { + subArgs = args.Length < 3 ? "" : args[1] + " " + args[2]; + Regex regex = new(@"^[0-9A-Fa-f]{6}\s[0-9A-Fa-f]{6}$"); + if (string.IsNullOrEmpty(subArgs) || !regex.IsMatch(subArgs)) + { + Logger.Msg($"{subArgs}", "modcolor"); + Utils.SendMessage(GetString("ColorInvalidGradientCode"), player.PlayerId); + break; + } + string colorFilePath = $"{modTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(colorFilePath)) + { + Logger.Msg($"File Not exist, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); + File.Create(colorFilePath).Close(); + } + //Logger.Msg($"File exists, creating file at {modTagsFiles}/{player.FriendCode}.txt", "modcolor"); + //Logger.Msg($"{subArgs}","modcolor"); + File.WriteAllText(colorFilePath, $"{subArgs}"); + break; + } + case "/vipcolor": + case "/vipcolour": + case "/VIP玩家颜色": + case "/VIP颜色": + if (Options.ApplyVipList.GetValue() == 0) + { + Utils.SendMessage(GetString("VipColorCommandDisabled"), player.PlayerId); + break; + } + if (!Utils.IsPlayerVIP(player.FriendCode)) + { + Utils.SendMessage(GetString("VipColorCommandNoAccess"), player.PlayerId); + break; + } + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("VipColorCommandNoLobby"), player.PlayerId); + break; + } + if (!Options.GradientTagsOpt.GetBool()) + { + subArgs = args.Length != 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) + { + Logger.Msg($"{subArgs}", "vipcolor"); + Utils.SendMessage(GetString("VipColorInvalidHexCode"), player.PlayerId); + break; + } + string colorFilePathh = $"{vipTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(colorFilePathh)) + { + Logger.Warn($"File Not exist, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); + File.Create(colorFilePathh).Close(); + } + + File.WriteAllText(colorFilePathh, $"{subArgs}"); + break; + } + else + { + subArgs = args.Length < 3 ? "" : args[1] + " " + args[2]; + Regex regexx = new(@"^[0-9A-Fa-f]{6}\s[0-9A-Fa-f]{6}$"); + if (string.IsNullOrEmpty(subArgs) || !regexx.IsMatch(subArgs)) + { + Logger.Msg($"{subArgs}", "vipcolor"); + Utils.SendMessage(GetString("VipColorInvalidGradientCode"), player.PlayerId); + break; + } + string colorFilePathh = $"{vipTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(colorFilePathh)) + { + Logger.Msg($"File Not exist, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); + File.Create(colorFilePathh).Close(); + } + //Logger.Msg($"File exists, creating file at {vipTagsFiles}/{player.FriendCode}.txt", "vipcolor"); + //Logger.Msg($"{subArgs}","modcolor"); + File.WriteAllText(colorFilePathh, $"{subArgs}"); + break; + } + case "/tagcolor": + case "/tagcolour": + case "/标签颜色": + case "/附加名称颜色": + string name1 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n) ? n : ""; + if (name1 == "") break; + if (!name1.Contains('\r') && player.FriendCode.GetDevUser().HasTag()) + { + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("ColorCommandNoLobby"), player.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !Utils.CheckColorHex(subArgs)) + { + Logger.Msg($"{subArgs}", "tagcolor"); + Utils.SendMessage(GetString("TagColorInvalidHexCode"), player.PlayerId); + break; + } + string tagColorFilePath = $"{sponsorTagsFiles}/{player.FriendCode}.txt"; + if (!File.Exists(tagColorFilePath)) + { + Logger.Msg($"File Not exist, creating file at {tagColorFilePath}", "tagcolor"); + File.Create(tagColorFilePath).Close(); + } + + File.WriteAllText(tagColorFilePath, $"{subArgs}"); + } + break; + + case "/xf": + case "/修复": + case "/修": + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + } + foreach (var pc in Main.AllPlayerControls) + { + if (pc.IsAlive()) continue; + + pc.RpcSetNameEx(pc.GetRealName(isMeeting: true)); + } + ChatUpdatePatch.DoBlockChat = false; + //Utils.NotifyRoles(isForMeeting: GameStates.IsMeeting, NoCache: true); + Utils.SendMessage(GetString("Message.TryFixName"), player.PlayerId); + break; + + case "/tpout": + case "/传送出": + case "/传出": + if (!GameStates.IsLobby) break; + if (!Options.PlayerCanUseTP.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + player.RpcTeleport(new Vector2(0.1f, 3.8f)); + break; + case "/tpin": + case "/传进": + case "/传送进": + if (!GameStates.IsLobby) break; + if (!Options.PlayerCanUseTP.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + + player.RpcTeleport(new Vector2(-0.2f, 1.3f)); + break; + + case "/vote": + case "/投票": + case "/票": + subArgs = args.Length != 2 ? "" : args[1]; + if (subArgs == "" || !int.TryParse(subArgs, out int arg)) + break; + var plr = Utils.GetPlayerById(arg); + + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + break; + } + + + if (!Options.EnableVoteCommand.GetBool()) + { + Utils.SendMessage(GetString("VoteDisabled"), player.PlayerId); + break; + } + if (Options.ShouldVoteCmdsSpamChat.GetBool()) + { + canceled = true; + ChatManager.SendPreviousMessagesToAll(); + } + + if (arg != 253) // skip + { + if (plr == null || !plr.IsAlive()) + { + Utils.SendMessage(GetString("VoteDead"), player.PlayerId); + break; + } + } + if (!player.IsAlive()) + { + Utils.SendMessage(GetString("CannotVoteWhenDead"), player.PlayerId); + break; + } + if (GameStates.IsMeeting) + { + player.RpcCastVote((byte)arg); + } + break; + + case "/say": + case "/s": + case "/с": + case "/сказать": + case "/说": + if (player.FriendCode.GetDevUser().IsDev) + { + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromDev")} ~ {player.GetRealName(clientData: true)}"); + } + else if (player.FriendCode.IsDevUser() && !dbConnect.IsBooster(player.FriendCode)) + { + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromSponsor")} ~ {player.GetRealName(clientData: true)}"); + } + else if (Utils.IsPlayerModerator(player.FriendCode)) + { + if (Options.ApplyModeratorList.GetValue() == 0 || Options.AllowSayCommand.GetBool() == false) + { + Utils.SendMessage(GetString("SayCommandDisabled"), player.PlayerId); + break; + } + else + { + if (args.Length > 1) + Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString("MessageFromModerator")} ~ {player.GetRealName(clientData: true)}"); + //string moderatorName3 = player.GetRealName().ToString(); + //int startIndex3 = moderatorName3.IndexOf("♥") + "♥".Length; + //moderatorName3 = moderatorName3.Substring(startIndex3); + string modLogname3 = Main.AllPlayerNames.TryGetValue(player.PlayerId, out var n4) ? n4 : ""; + + string moderatorFriendCode3 = player.FriendCode.ToString(); + string logMessage3 = $"[{DateTime.Now}] {moderatorFriendCode3},{modLogname3} used /s: {args.Skip(1).Join(delimiter: " ")}"; + File.AppendAllText(modLogFiles, logMessage3 + Environment.NewLine); + + } + } + break; + case "/rps": + case "/剪刀石头布": + //canceled = true; + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + + if (!GameStates.IsLobby && player.IsAlive()) + { + Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); + break; + } + + if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice)) + { + Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); + break; + } + else if (playerChoice < 0 || playerChoice > 2) + { + Utils.SendMessage(GetString("RpsCommandInfo"), player.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botChoice = rand.Next(0, 3); + var rpsList = new List { GetString("Rock"), GetString("Paper"), GetString("Scissors") }; + if (botChoice == playerChoice) + { + Utils.SendMessage(string.Format(GetString("RpsDraw"), rpsList[botChoice]), player.PlayerId); + } + else if ((botChoice == 0 && playerChoice == 2) || + (botChoice == 1 && playerChoice == 0) || + (botChoice == 2 && playerChoice == 1)) + { + Utils.SendMessage(string.Format(GetString("RpsLose"), rpsList[botChoice]), player.PlayerId); + } + else + { + Utils.SendMessage(string.Format(GetString("RpsWin"), rpsList[botChoice]), player.PlayerId); + } + break; + } + case "/coinflip": + case "/抛硬币": + //canceled = true; + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + + if (!GameStates.IsLobby && player.IsAlive()) + { + Utils.SendMessage(GetString("CoinflipCommandInfo"), player.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botChoice = rand.Next(1, 101); + var coinSide = (botChoice < 51) ? GetString("Heads") : GetString("Tails"); + Utils.SendMessage(string.Format(GetString("CoinFlipResult"), coinSide), player.PlayerId); + break; + } + case "/gno": + case "/猜数字": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + //canceled = true; + if (!GameStates.IsLobby && player.IsAlive()) + { + Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); + break; + } + subArgs = args.Length != 2 ? "" : args[1]; + if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo)) + { + Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); + break; + } + else if (guessedNo < 0 || guessedNo > 99) + { + Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); + break; + } + else + { + int targetNumber = Main.GuessNumber[player.PlayerId][0]; + if (Main.GuessNumber[player.PlayerId][0] == -1) + { + var rand = IRandom.Instance; + Main.GuessNumber[player.PlayerId][0] = rand.Next(0, 100); + targetNumber = Main.GuessNumber[player.PlayerId][0]; + } + Main.GuessNumber[player.PlayerId][1]--; + if (Main.GuessNumber[player.PlayerId][1] == 0 && guessedNo != targetNumber) + { + Main.GuessNumber[player.PlayerId][0] = -1; + Main.GuessNumber[player.PlayerId][1] = 7; + //targetNumber = Main.GuessNumber[player.PlayerId][0]; + Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), player.PlayerId); + break; + } + else if (guessedNo < targetNumber) + { + Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); + break; + } + else if (guessedNo > targetNumber) + { + Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); + break; + } + else + { + Utils.SendMessage(string.Format(GetString("GNoWon"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); + Main.GuessNumber[player.PlayerId][0] = -1; + Main.GuessNumber[player.PlayerId][1] = 7; + break; + } + } + case "/rand": + case "/XY数字": + case "/范围游戏": + case "/猜范围": + case "/范围": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + subArgs = args.Length != 3 ? "" : args[1]; + subArgs2 = args.Length != 3 ? "" : args[2]; + + if (!GameStates.IsLobby && player.IsAlive()) + { + Utils.SendMessage(GetString("RandCommandInfo"), player.PlayerId); + break; + } + if (subArgs == "" || !int.TryParse(subArgs, out int playerChoice1) || subArgs2 == "" || !int.TryParse(subArgs2, out int playerChoice2)) + { + Utils.SendMessage(GetString("RandCommandInfo"), player.PlayerId); + break; + } + else + { + var rand = IRandom.Instance; + int botResult = rand.Next(playerChoice1, playerChoice2 + 1); + Utils.SendMessage(string.Format(GetString("RandResult"), botResult), player.PlayerId); + break; + } + case "/8ball": + case "/8号球": + case "/幸运球": + if (!Options.CanPlayMiniGames.GetBool()) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + break; + } + canceled = true; + var rando = IRandom.Instance; + int result = rando.Next(0, 16); + string str = ""; + switch (result) + { + case 0: + str = GetString("8BallYes"); + break; + case 1: + str = GetString("8BallNo"); + break; + case 2: + str = GetString("8BallMaybe"); + break; + case 3: + str = GetString("8BallTryAgainLater"); + break; + case 4: + str = GetString("8BallCertain"); + break; + case 5: + str = GetString("8BallNotLikely"); + break; + case 6: + str = GetString("8BallLikely"); + break; + case 7: + str = GetString("8BallDontCount"); + break; + case 8: + str = GetString("8BallStop"); + break; + case 9: + str = GetString("8BallPossibly"); + break; + case 10: + str = GetString("8BallProbably"); + break; + case 11: + str = GetString("8BallProbablyNot"); + break; + case 12: + str = GetString("8BallBetterNotTell"); + break; + case 13: + str = GetString("8BallCantPredict"); + break; + case 14: + str = GetString("8BallWithoutDoubt"); + break; + case 15: + str = GetString("8BallWithDoubt"); + break; + } + Utils.SendMessage("" + str + "", player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medium), GetString("8BallTitle"))); + break; + case "/me": + case "/我的权限": + case "/权限": + + string Devbox = player.FriendCode.GetDevUser().DeBug ? "<#10e341>" : "<#e31010>"; + string UpBox = player.FriendCode.GetDevUser().IsUp ? "<#10e341>" : "<#e31010>"; + string ColorBox = player.FriendCode.GetDevUser().ColorCmd ? "<#10e341>" : "<#e31010>"; + + subArgs = text.Length == 3 ? string.Empty : text.Remove(0, 3); + if (string.IsNullOrEmpty(subArgs)) + { + Utils.SendMessage((player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{string.Format(GetString("Message.MeCommandInfo"), player.PlayerId, player.GetRealName(clientData: true), player.GetClient().FriendCode, player.GetClient().GetHashedPuid(), player.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}", player.PlayerId); + } + else + { + if (Options.ApplyModeratorList.GetValue() == 0 || !Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("Message.MeCommandNoPermission"), player.PlayerId); + break; + } + + + + if (byte.TryParse(subArgs, out byte meid)) + { + if (meid != player.PlayerId) + { + var targetplayer = Utils.GetPlayerById(meid); + if (targetplayer != null && targetplayer.GetClient() != null) + { + Utils.SendMessage($"{string.Format(GetString("Message.MeCommandTargetInfo"), targetplayer.PlayerId, targetplayer.GetRealName(clientData: true), targetplayer.GetClient().FriendCode, targetplayer.GetClient().GetHashedPuid(), targetplayer.FriendCode.GetDevUser().GetUserType())}", player.PlayerId); + } + else + { + Utils.SendMessage($"{(GetString("Message.MeCommandInvalidID"))}", player.PlayerId); + } + } + else + { + Utils.SendMessage($"{string.Format(GetString("Message.MeCommandInfo"), PlayerControl.LocalPlayer.PlayerId, PlayerControl.LocalPlayer.GetRealName(clientData: true), PlayerControl.LocalPlayer.GetClient().FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid(), PlayerControl.LocalPlayer.FriendCode.GetDevUser().GetUserType(), Devbox, UpBox, ColorBox)}", player.PlayerId); + } + } + else + { + Utils.SendMessage($"{(GetString("Message.MeCommandInvalidID"))}", player.PlayerId); + } + } + break; + + case "/start": + case "/开始": + case "/старт": + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); + break; + } + + if (!Utils.IsPlayerModerator(player.FriendCode)) + { + Utils.SendMessage(GetString("StartCommandNoAccess"), player.PlayerId); + + break; + + } + if (Options.ApplyModeratorList.GetValue() == 0 || Options.AllowStartCommand.GetBool() == false) + { + Utils.SendMessage(GetString("StartCommandDisabled"), player.PlayerId); + break; + } + if (GameStates.IsCountDown) + { + Utils.SendMessage(GetString("StartCommandCountdown"), player.PlayerId); + break; + } + subArgs = args.Length < 2 ? "" : args[1]; + if (string.IsNullOrEmpty(subArgs) || !int.TryParse(subArgs, out int countdown)) + { + countdown = 5; + } + else + { + countdown = int.Parse(subArgs); + } + if (countdown < Options.StartCommandMinCountdown.CurrentValue || countdown > Options.StartCommandMaxCountdown.CurrentValue) + { + Utils.SendMessage(string.Format(GetString("StartCommandInvalidCountdown"), Options.StartCommandMinCountdown.CurrentValue, Options.StartCommandMaxCountdown.CurrentValue), player.PlayerId); + break; + } + GameStartManager.Instance.BeginGame(); + GameStartManager.Instance.countDownTimer = countdown; + Utils.SendMessage(string.Format(GetString("StartCommandStarted"), player.name)); + break; + + + default: + if (SpamManager.CheckSpam(player, text)) return; + break; + } + } +} +[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] +class ChatUpdatePatch +{ + public static bool DoBlockChat = false; + public static ChatController Instance; + public static void Postfix(ChatController __instance) + { + if (!AmongUsClient.Instance.AmHost || Main.MessagesToSend.Count == 0 || (Main.MessagesToSend[0].Item2 == byte.MaxValue && Main.MessageWait.Value > __instance.timeSinceLastMessage)) return; + if (DoBlockChat) return; + + Instance ??= __instance; + + if (Main.DarkTheme.Value) + { + var chatBubble = __instance.chatBubblePool.Prefab.Cast(); + chatBubble.TextArea.overrideColorTags = false; + chatBubble.TextArea.color = Color.white; + chatBubble.Background.color = Color.black; + } + + var player = PlayerControl.LocalPlayer; + if (GameStates.IsInGame || player.Data.IsDead) + { + player = Main.AllAlivePlayerControls.ToArray().OrderBy(x => x.PlayerId).FirstOrDefault() + ?? Main.AllPlayerControls.ToArray().OrderBy(x => x.PlayerId).FirstOrDefault() + ?? player; + } + //Logger.Info($"player is null? {player == null}", "ChatUpdatePatch"); + if (player == null) return; + + (string msg, byte sendTo, string title) = Main.MessagesToSend[0]; + //Logger.Info($"MessagesToSend - sendTo: {sendTo} - title: {title}", "ChatUpdatePatch"); + + if (sendTo != byte.MaxValue && GameStates.IsLobby) + { + var networkedPlayerInfo = Utils.GetPlayerInfoById(sendTo); + if (networkedPlayerInfo != null) + { + if (networkedPlayerInfo.DefaultOutfit.ColorId == -1) + { + var delaymessage = Main.MessagesToSend[0]; + Main.MessagesToSend.RemoveAt(0); + Main.MessagesToSend.Add(delaymessage); + return; + } + // green beans color id is -1 + } + // It is impossible to get null player here unless it quits + } + Main.MessagesToSend.RemoveAt(0); + + int clientId = sendTo == byte.MaxValue ? -1 : Utils.GetPlayerById(sendTo).GetClientId(); + var name = player.Data.PlayerName; + + //__instance.freeChatField.textArea.characterLimit = 999; + + if (clientId == -1) + { + player.SetName(title); + DestroyableSingleton.Instance.Chat.AddChat(player, msg, false); + player.SetName(name); + } + + if (clientId == AmongUsClient.Instance.ClientId || sendTo == PlayerControl.LocalPlayer.PlayerId) + { + player.SetName(title); + DestroyableSingleton.Instance.Chat.AddChat(player, msg, false); + player.SetName(name); + return; + } + + var writer = CustomRpcSender.Create("MessagesToSend", SendOption.None); + writer.StartMessage(clientId); + writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(player.Data.NetId) + .Write(title) + .EndRpc(); + writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) + .Write(msg) + .EndRpc(); + writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(player.Data.NetId) + .Write(player.Data.PlayerName) + .EndRpc(); + writer.EndMessage(); + writer.SendMessage(); + + __instance.timeSinceLastMessage = 0f; + } +} + +[HarmonyPatch(typeof(FreeChatInputField), nameof(FreeChatInputField.UpdateCharCount))] +internal class UpdateCharCountPatch +{ + public static void Postfix(FreeChatInputField __instance) + { + int length = __instance.textArea.text.Length; + __instance.charCountText.SetText(length <= 0 ? GetString("ThankYouForUsingTOHE") : $"{length}/{__instance.textArea.characterLimit}"); + __instance.charCountText.enableWordWrapping = false; + if (length < (AmongUsClient.Instance.AmHost ? 888 : 444)) + __instance.charCountText.color = Color.black; + else if (length < (AmongUsClient.Instance.AmHost ? 1111 : 777)) + __instance.charCountText.color = new Color(1f, 1f, 0f, 1f); + else + __instance.charCountText.color = Color.red; + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSendChat))] +class RpcSendChatPatch +{ + public static bool Prefix(PlayerControl __instance, string chatText, ref bool __result) + { + if (string.IsNullOrWhiteSpace(chatText)) + { + __result = false; + return false; + } + if (!GameStates.IsModHost) + { + __result = false; + return true; + } + 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.Contains("who", StringComparison.OrdinalIgnoreCase)) + 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/Resources/Images/Skills/shush.png b/Resources/Images/Skills/shush.png new file mode 100644 index 000000000..8fcc3e94e Binary files /dev/null and b/Resources/Images/Skills/shush.png differ diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 0393b9844..ea16cf5a1 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -427,6 +427,7 @@ "Sloth": "Sloth", "Prohibited": "Prohibited", "Eavesdropper": "Eavesdropper", + "Exorcist": "Exorcist", "Shocker": "Shocker", "Revenant": "Revenant", "BracketAddons": "Add Brackets To Add-ons", @@ -751,6 +752,7 @@ "SlothInfo": "You're slower", "ProhibitedInfo": "Certain Vents are blocked", "EavesdropperInfo": "Listen in on other roles", + "ExorcistInfo": "Kill people who won't shut up", "ShockerInfo": "Shock unsuspecting players", "RevenantInfo": "Take your killer's role", "EngineerTOHEInfoLong": "(Crewmates):\nAs the Engineer, you may access the Vents while Comms Sabotaged is inactive.", @@ -830,6 +832,7 @@ "LudopathInfoLong": "(Impostors):\nAs the Ludopath, your Kill Cooldown is randomized.\n\nMinimum it can be is 1 second, while the maximum is your default Kill Cooldown.", "GodfatherInfoLong": "(Impostors):\nAs the Godfather, you vote someone to make them your target.\nIn the next round, if someone kills the target, the killer will turn into a Refugee or Madmate.", "ChronomancerInfoLong": "(Impostors):\nAs the Chronomancer, you have a charge bar which indicates when the slaughter is ready. When it is at 100% the next time you kill someone, you go into slaughter mode, meaning you can kill indefinitely until your bar runs out of charge. Otherwise, you have a normal KCD.", + "ExorcistInfoLong": "(Impostors):\nAs the Exorcist, you can use /ex in meeting to kill a player who speaks in the next set amount of seconds after using the command. Depending on the settings, the Exorcist can kill multiple people in one meeting. You'll lose your ability if you excersise too many people.", "PitfallInfoLong": "(Impostors):\nAs the Pitfall, you use your Shapeshift to mark the area around the Shapeshift as a trap. Players who enter this area will be immobilized quickly, and their vision will be affected.", "EvilMiniInfoLong": "(Impostors):\nAs the Evil Mini, you are unkillable until you grow up and have a very long initial Kill Cooldown, which gets drastically shortened as you grow up.", "BlackmailerInfoLong": "(Impostors):\nAs the Blackmailer, when you Shift into a target, you will blackmail that player. This means that during the meetings, they won't be able to speak.\n\nNote: If someone is already blackmailed, blackmailing another person un-blackmails the current person.", @@ -2264,6 +2267,7 @@ "DeathReason.Starved": "Starved", "DeathReason.Equilibrium": "Equilibrium", "DeathReason.Sacrificed": "Sacrificed", + "DeathReason.Exorcised": "Exorcised", "DeathReason.Electrocuted": "Electrocuted", "DeathReason.Scavenged": "Scavenged", "DeathReason.BlastedOff": "Blasted Off", @@ -4133,6 +4137,21 @@ "EavesdropperMsgTitle": "You found a secret", "EavesdropPercentChance": "Chance to eavesdrop", + "ExorcistKill": "{0} was Exorcised", + "ExorcistNotify": "Exorcism is being activated in {0} seconds", + "ExorcistEnd": "Exorcism has ended", + "ExorcistStart": "Exorcism has started for {0} seconds", + "ExorcistActive": "Exorcism is active", + "ExorcistOutOfUsages": "You're out of usages!", + "ExorcistDispelled": "You're out of spells!", + "ExorcismActiveFor": "Exorcism Duration", + "ExorcismPerGame": "Max exorcises per game", + "ExorcismDelay": "Exorcism Delay", + "ExorcismSacrificesToDispel": "How many sacrifices are needed to dispel", + "ExorcismLimitMeeting": "Max exorcises per meeting", + "ExorcismEndOnKill": "End exorcism on kill", + "ExorcistTryHideMsg": "Try to hide exorcist's commands", + "PreventSeeRolesBeforeSkillUsedUp": "Prevent seeing others roles before skill used up", "ChiefOfPoliceSkillCooldown": "Cooldown for recruiting Sheriffs", diff --git a/Resources/Lang/it_IT.json b/Resources/Lang/it_IT.json index 1e6fa01bd..0cd54b6a8 100644 --- a/Resources/Lang/it_IT.json +++ b/Resources/Lang/it_IT.json @@ -424,7 +424,7 @@ "BountyHunterInfo": "Elimina il tuo bersaglio", "FireworkerInfo": "Esci di scena col BOTTO", "MercenaryInfo": "Continua a uccidere, altrimenti ti suiciderai", - "ShapeMasterInfo": "Uccidi velocemente senza ricarica mutazione", + "ShapeMasterInfo": "Swiftly kill with no Shapeshift Cooldown", "VampireInfo": "Le tue uccisioni sono ritardate", "WarlockInfo": "Maledici gli Astronauti poi Mutati per farli uccidere", "NinjaInfo": "Segna un bersaglio, poi Mutati per ucciderlo", @@ -445,7 +445,7 @@ "SniperInfo": "Cecchina i giocatori a distanza Mutandoti", "UndertakerInfo": "Teletrasporta un cadavere alla posizione segnata", "RiftMakerInfo": "Traccio due squarci, toccali per deformare lo spazio", - "EvilTrackerInfo": "Mutati per tenere traccia dei giocatori", + "EvilTrackerInfo": "Track players by Shifting", "EvilHackerInfo": "Hackera il sistema", "AntiAdminerInfo": "Sai quando i giocatori sono vicini ai dispositivi", "ArroganceInfo": "With each kill you make, your Cooldown decreases", diff --git a/Resources/Lang/ja_JP.json b/Resources/Lang/ja_JP.json index ea35be3ee..0b0cd337f 100644 --- a/Resources/Lang/ja_JP.json +++ b/Resources/Lang/ja_JP.json @@ -746,12 +746,12 @@ "CrewmateTOHEInfoLong": "(クルーメイト):\nあなたの目標は非常にシンプルです。インポスターを見つけ出し、追放することです。クルーメイトは、すべての殺人者を排除するか、すべてのタスクを完了することで勝利します。", "BountyHunterInfoLong": "(インポスター):\n賞金稼ぎとして、割り当てられたターゲット (矢印で示されている場合) をキルすると、次のキルクールダウンが短縮されます。\nターゲット以外の誰かをキルした場合、次のキルクールダウンが延長されます。\nターゲットは一定時間後に変更されます。", "FireworkerInfoLong": "(インポスター):\nファイワーカーとして、花火を置くためにシェイプシフトできます。ホストが設定した最大数までです。最後のインポスターであり、すべての花火が設置されている場合、もう一度シェイプシフトして爆発させ、範囲内の全員(あなたも含む)を殺害します。花火で全プレイヤーを殺害すれば、インポスターの勝利と見なされます。", - "MercenaryInfoLong": "(インポスター):\n傭兵として、あなたは変身のクールダウン (使用不可) によって示された期限内に殺害を行う必要があります。殺害に失敗した場合、あなた自身が死亡します。", + "MercenaryInfoLong": "(Impostors):\nAs the Mercenary, you must kill within your deadline, as shown by your Shapeshift Cooldown (which you cannot use). If you fail to kill, you die.", "ShapeMasterInfoLong": "(インポスター):\n形状の達人として、シェイプシフトのクールダウンがありません。", "VampireInfoLong": "(インポスター):\n吸血鬼として、あなたのキルは遅延します。つまり、先に会議が呼ばれても、ターゲットは依然として死亡します。しかし、おとりを噛んだ場合は通常通り殺し、死体を報告します。設定によっては、ダブルトリガー (プレイヤーを噛む - シングルクリック、通常のキル - ダブルクリック) を使用することができます。", "WarlockInfoLong": "(インポスター):\nウォーロックとして、一度に1人のプレイヤーに呪いをかけることができます。シェイプシフトした場合、プレイヤーに呪いをかけていれば、彼らが最も近い人を殺害します。これにはあなたや他のインポスターも含まれることがあります(設定による)。シェイプシフトしている間も通常通り殺害できます。", "ZombieInfoLong": "(インポスター):\nゾンビはキルクールダウンが短いですが、非常に遅く移動し、視界が非常に狭いです。ゾンビはディクテーター以外の誰からも投票で追い出されません。また、ゾンビの移動速度はキルを行うか、時間が経過するにつれて徐々に遅くなります。", - "NinjaInfoLong": "(インポスター):\n忍者として、キルボタンを使用して対象をマークする (シングルクリック) か通常通りに殺害する (ダブルクリック) ことができます。その後、変身してマークされた対象にテレポートし、彼らを殺害することができます。", + "NinjaInfoLong": "(Impostors):\nAs the Ninja, you can use your Kill button to mark a target (single click) or kill normally (double click). You may then Shapeshift to teleport to the marked target and kill them.", "AnonymousInfoLong": "(インポスター): \nアノニマスとして、シェイプシフトを使用してターゲットにそのラウンドで殺害したプレイヤーを報告させることができます。そのラウンドで誰も殺害していない場合、ターゲットはまるで自分自身が死んだかのように自分の遺体を報告します。注:これは怠け者や怠け者の男には効果がありません。また、遺体が通常報告できるかどうかにかかわらず、この能力は機能します。", "MinerInfoLong": "(インポスター): \n鉱山労働者として、シェイプシフトして最後にいたベントにテレポートすることができます。", "KillingMachineInfoLong": "(インポスター):\n殺人マシンとして、非常に短いキルクールダウンがありますが、通気口を使用できず、クルーメイトの視界しか持っておらず、破壊行為もできず、報告もできず、緊急会議を招集することもできません。\n\n注意:シールド、誘き寄せトラップ、くくり罠などは一切影響しません。", @@ -760,36 +760,36 @@ "NemesisInfoLong": "(インポスター):\nネメシスとして、最後のインポスターである場合のみ殺害できます。死亡している場合は、コマンド /rv [ID] を使用してIDが入力されたプレイヤーを殺害できます。全プレイヤーのIDを表示するには /id を使用するか、彼らの名前の横を見てください。", "BloodmoonInfoLong": "(インポスター [幽霊]):\n血の月として、敵を攻撃して血を流させます。これにより、ホストが設定した時間に敵は死亡し、その事実を認識することになります。", "PossessorInfoLong": "(インポスター [幽霊]):\n乗っ取り者として、他のプレイヤーが警戒範囲にいない時にプレイヤーを乗っ取ることができます。乗っ取ったプレイヤーを、他のプレイヤーが注視範囲にいない場所までできるだけ遠くに導いてください。乗っ取りの時間が切れると、もし他のプレイヤーが注視範囲内にいなければ、乗っ取られたプレイヤーは殺されます。もし乗っ取り中に警戒範囲内に他のプレイヤーが入ってきた場合、乗っ取り者は即座に乗っ取りを解除します。", - "PuppeteerInfoLong": "(インポスター):\nパペッティアーとして、キルボタンを使用して操る (シングルクリック) か通常通り殺害する (ダブルクリック) ことができます。操られたプレイヤーは、触れた次の非インポスターを殺害します。オプションによっては、操られたターゲットも殺害した後に死亡します。", - "MastermindInfoLong": "(インポスター):\nマスターマインドとして、プレイヤーにキルボタンを1回使用して操作できます。 ターゲットにキルボタンがない場合、これは何もしません。 しかし、ターゲットにどのようなキルボタンがあっても、一定の遅延後に操作されたことを知らされ、生き残るために制限時間内に誰かを殺さなければなりません。 時間制限が切れるか、誰かを殺す前にミーティングが呼ばれた場合、彼らは死にます。 通常、誰かをダブルクリックして殺すこともできます。", - "YinYangerInfoLong": "(インポスター):\nキルボタンを1回使用して「陰」を選び、もう1回使用して「陽」を選びます。この2人のプレイヤーが出会うと、お互いを殺し合います。「陰」と「陽」を選んだ後は、通常通りキルできるようになります。", + "PuppeteerInfoLong": "(Impostors):\nAs the Puppeteer, you can use your Kill button to Puppeteer (single click) or kill normally (double click).\nThose you Puppeteer will kill the next non-Impostor they touch. Depending on options, Puppeteered targets will also die once they kill.", + "MastermindInfoLong": "(Impostors):\nAs the Mastermind, you can use your Kill button on a player once to manipulate them. The manipulation does nothing if the target doesn't have a Kill button. But if the target does have a Kill button, whoever you manipulate will be told after a delay that they got manipulated and must kill someone in a limited time to survive. If the time limit expires or a meeting gets called before killing someone, they die.\nDouble click on someone to kill them normally.", + "YinYangerInfoLong": "(Impostors):\nAs the YinYanger, you can use your Kill button one time to pick your Yin and then a second time to choose a Yang. When those two players meet, they'll kill each other. When Yin & Yang have been chosen, you can kill normally.", "TimeThiefInfoLong": "(インポスター): \n時間泥棒がプレイヤーを殺すたびに、会議時間が一定時間短縮されます。 時間泥棒が死亡すると、会議時間は通常に戻ります。", - "SniperInfoLong": "(インポスター):\n 遠くのプレイヤーを狙撃できます。\n成功するにはシェイプシフトを2回行う必要があります。\n最初のシェイプシフトの位置から最後のシフト位置に向かって矢印を想像してください。\nこれが狙撃が行われる方向です。\n狙撃はその経路上の最初の人を殺します。\n弾薬を使い切るまで通常のキルはできません。", + "SniperInfoLong": "(Impostors):\nYou can shoot players from far away.\nYou have to Shapeshift twice to make a successful snipe.\nImagine an arrow pointing from your first Shapeshift location towards your Unshift location.\nThat will be the direction in which the snipe will be made.\nThe snipe kills the first person in its path.\nYou cannot kill people normally until you use up all of your ammo.", "UndertakerInfoLong": "(インポスター): \nプレイヤーにシェイプシフトするたびに、マークが付けられた場所が設定されます。あなたのキルはそれらの場所にテレポートします。\n各キルと会議の後、設定された場所はリセットされます。", "RiftMakerInfoLong": "(インポスター):\n裂け目作成者として、シェイプシフトして裂け目を作り出せます。裂け目を作った場所に触れることで、一つの裂け目から別の裂け目へとテレポートできます。ベントを使おうとすると強制的に外に出され、すべての裂け目が消えてしまいます。\n\n注意: 同時に設置できる裂け目は2つまでです。3つ目を設置しようとすると、最初に作った裂け目が削除されます。", "EvilTrackerInfoLong": "(インポスター):\nイーヴィル・トラッカーは他の人を追跡でき、イーヴィル・トラッカーは誰かに変身して追跡対象を変更できます (変身後すぐに元に戻ります) 。イーヴィルトラッカーの名前の下にある矢印は、対象の方向を示しています。イーヴィルトラッカーの仲間がキルしたとき、イーヴィルトラッカーはキルの光を見るでしょう", "EvilHackerInfoLong": "(インポスター):\nイーヴィル・ハッカーは会議の始まりに直前の管理情報を入手できます。\n使用されていない部屋は表示されません。\nインポスターがいる部屋には「★」が付けられます。\n死体がある部屋には死体の数が表示されます。\n例:★カフェテリア:3 (死体×1)。", "EvilGuesserInfoLong": "(インポスター):\nイーヴィル・ゲッサーは会議中に特定のプレイヤーの役割を推測できます。正確な場合、対象が死亡し、間違っている場合、イーヴィル・ゲッサーが死亡します。\n推測コマンドは:/bt [プレイヤーID] [role]\nプレイヤーの名前の前にプレイヤーのIDを表示することができ、またはすべてのプレイヤーのIDを表示するために/idコマンドを使用できます。", - "AntiAdminerInfoLong": "(インポスター):\nアンチ・アドミナーは常にカメラ、アドミンテーブル、バイタル、ドアログ、および/または他のデバイスの近くに仲間またはニュートラルがいるかどうかを知ることができます。注意:アンチ・アドミナーはプレイヤーがデバイスを使用しているかどうかを確実には知りません。彼らは単にデバイスの近くに誰かがいることを知っています。", - "ArroganceInfoLong": "(インポスター):\n傲慢は、彼ら自身の成功したキルごとにキルのクールダウンを減少させます。", - "BomberInfoLong": "(インポスター):\n爆弾魔は変身ボタンを使って自爆し、一定範囲内のプレイヤーを殺すことができます。しかし、その代償として爆弾魔も死にます。注意: 爆弾魔が爆発すると、すべてのプレイヤーがキルフラッシュを目撃します。", - "ScavengerInfoLong": "(インポスター):\nスカベンジャーのキルは死体を残しません。さらに、犠牲者がベイトの場合、セルフリポートは行われません。", + "AntiAdminerInfoLong": "(Impostors):\nThe Anti Adminer can at any time find out if there are Crewmates or Neutrals near Cameras, Admin Table, Vitals, DoorLog, and/or other devices. Note: Anti Adminer does not know if the player uses the device while near it. They only know that someone is near the device.", + "ArroganceInfoLong": "(Impostors):\nThe Arrogance reduces their Kill Cooldown with each successful kill of theirs.", + "BomberInfoLong": "(Impostors):\nThe Bomber can use the Shapeshift button to self-explode, killing players within a certain range. But as a price, the Bomber will also die. Note: All players will see a kill flash when the Bomber explodes.", + "ScavengerInfoLong": "(Impostors):\nScavenger kills do not leave dead bodies behind. In addition, if the victim is a Bait, no self-report will be made.", "TrapsterInfoLong": "(インポスター): \nトラップスターには独自のキル方法があります。死体を報告しようとするプレイヤーを、トラップスターが殺した死体の報告を開始することで排除することができます。\n注意: トラップスターが おとり を殺した場合、トラップスターは即座に死にます。", - "GangsterInfoLong": "(インポスター):\nギャングスターはキルボタンを押すことでプレイヤーをマッドメイトに勧誘しようとすることができます。勧誘が成功した場合、ギャングスターと対象はお互いに盾のアニメーションを見ることができます (お互いにしか見えません) 。残りの勧誘可能な人数はギャングスターの名前の横に表示されます (最大はホストによって設定されます) 。ギャングスターが勧誘できないプレイヤー、例えばニュートラルまたは特別なクルーのいくつかに勧誘しようとした場合、彼らは通常通り対象をキルします。ギャングスターが残りの勧誘を持っていない場合、その時点から通常のキルしかできません。", - "CleanerInfoLong": "(インポスター):\nクリーナーは報告ボタンを押して、出会った死体を含むすべての死体を片付けることができます (自分がキルしたものも含む) 。片付けが成功した場合、クリーナーは自分の体に盾のアニメーションを見ることができます (自分だけが見ることができます) 。片付けた死体は報告できません (ベイトを含む) 。", - "LightningInfoLong": "(インポスター):\n稲妻として、あなたは通常の方法で殺すことはできません。代わりに、あなたのキルボタンはターゲットを量子化し、遅延の後にアクティブ化します。これにより、次に接触する人が彼らを殺します。積極的に量子化されている人は名前の隣に「■」が表示されます。さらに、会議の終わりまで生き残った量子化された人は死亡します。キラーを量子化する設定もあります。", - "GreedyInfoLong": "(インポスター):\n欲張りは奇数と偶数のキルで異なるキルクールダウンを持ちます。欲張りのキルクールダウンは会議ごとにリセットされ、最初のキルは常に奇数キルです。", - "CursedWolfInfoLong": "(インポスター):\n呪われた狼が殺されそうになると、呪われた狼は殺人者に死の呪いをかけます。\n(ホストが反撃できる最大回数を設定します)", - "SoulCatcherInfoLong": "(インポスター):\n魂を捉える者として、対象が死亡していない、ベントにいない、ペリカンに飲み込まれていない、またはその他の奇妙な状態にない限り、対象と場所を入れ替えることができます。", - "QuickShooterInfoLong": "(インポスター):\nキルクールダウンが終了したとき、速射手はシェイプシフトを使用してキルクールダウンをリセットし、弾を保存できます (保存が成功すると、彼自身だけが見ることができる盾のアニメーションが体に表示されます)。速射手がキルを完了すると、彼は弾を使い切るまでキルクールダウンを相殺できます。毎回の会議の初めに、速射手は一定数の弾を保持できます (数はホストによって設定されます)。", - "CamouflagerInfoLong": "(インポスター):\nカモフラージャーがシェイプシフトを使用すると、すべてのプレイヤーはまったく同じように見えるようになります。この状態は、カモフラージャーがシェイプシフトを解除するまで続きます。注意:コミュニケーション妨害カモフラージュのスキルとカモフラージャーのスキルは重ねることができます。カモフラージャーのスキルがアクティブな間に会議が開催された場合、スキルは無効になります。", - "EraserInfoLong": "(インポスター):\nイレイサーは会議で任意のクルーメイトを選んでその役職を消すために投票できます。そして、その消去は会議が終了した後に効果を発揮します。注意:スキルが消去されたプレイヤーは、ゲーム結果ページを含めて常にバニラの役職とみなされます。\nプレイヤーは一度しか消去できません (を含むおいあい猫)", + "GangsterInfoLong": "(Impostors):\nThe Gangster, a powerful character, can try to recruit a player to a Madmate by pressing the Kill button. If the recruitment is successful, both the Gangster and the target will see the Shield Animation on each other as a reminder (only visible to each other). The remaining number of available recruits is displayed next to the Gangster's name (the Host sets the maximum). If the Gangster tries to recruit players who cannot be recruited, such as Neutrals or some special Crewmates, they will kill the target normally instead. When the Gangster has no remaining recruitments, they can only make normal kills from that point on.", + "CleanerInfoLong": "(Impostors):\nCleaner can press the Report button to clean up any dead body they come across (including those they kill). If the cleanup is successful, the Cleaner will see a Shield Animation on their body as a reminder (only visible to himself). The cleaned-up body cannot be reported (including Bait).", + "LightningInfoLong": "(Impostors):\nAs the Lightning, you cannot kill normally. Instead, your Kill button quantizes targets, which activates after a delay, causing the next person they encounter to kill them. Those who are actively quantized show a「■」next to their name. Additionally, those who have been quantized die if they survive until the end of a meeting. There is a setting to quantize your killer.", + "GreedyInfoLong": "(Impostors):\nGreedy kills with odd and even kills will have different Kill Cooldowns. Greedy's Kill Cooldown is reset every meeting, and Greedy's first kill is always odd.", + "CursedWolfInfoLong": "(Impostors):\nWhen the Cursed Wolf is about to be killed, the Cursed Wolf will curse the killer to death. (The Host sets the maximum of times you can counterattack)", + "SoulCatcherInfoLong": "(Impostors):\nAs the Soul Catcher, you can Shapeshift to swap places with your target as long as they are not dead, in a Vent, swallowed by Pelican, or in a similar odd state.", + "QuickShooterInfoLong": "(Impostors):\nWhen the Kill Cooldown is over, Quick Shooter can reset the Kill Cooldown by Shapeshift to store a bullet (when the storage is successful, a Shield Animation visible only to himself will appear on their body as a reminder). If Quick Shooter has bullets, he can use one to bypass the Kill Cooldown; he will kill even if it's still on Cooldown and use a bullet. At the beginning of each meeting, the quick shooter can only keep a certain number of bullets (The Host sets the number).", + "CamouflagerInfoLong": "(Impostors):\nWhen the Camouflager uses Shapeshift, all players start to look the same. This state ends when the Camouflager reverts its Shapeshifting. It's important to note that the skills of Communication Sabotage Camouflage, and the skills of the Camouflager can be superimposed.\nThis skill will be invalid if a meeting is held during the skill activation of the Camouflager.", + "EraserInfoLong": "(Impostors):\nEraser can vote for any target at the meeting to erase the target's roles, and the erasure will take effect after the meeting ends. Note: Players with erased skills will always be considered a vanilla role, including the game result page.\nA target can only be erased once (including OIIAI)", "ButcherInfoLong": "(インポスター):\n肉屋のキル (パッシブキルを含む) はターゲットに複数の死体を残し、報告時に他の死体を正確に識別することが不可能になります。注意:実装の原則に従い、殺されたターゲットは殺されたアニメーションを繰り返し表示する必要があります。このアニメーションはスキップできず、この期間中は会議に通常通り参加することはできません。さらに、肉屋がアヴェンジャーをキルした場合、アヴェンジャーは怒りで全員に復讐します。", - "HangmanInfoLong": "(インポスター):\nハングマンの変身中の殺害方法は絞殺です。絞殺は対象の状態を無視します。例えば、メディックのシールド、ボディガードの保護、スーパースターのスキルなどです。絞殺されたプレイヤーは死体を残さず、また、そのスキルを発動させません。例えば、ベテランの反撃殺 (追加の役割を含む) 、さらに、予知者には提示されません。", - "SwooperInfoLong": "(インポスター):\nスウーパーとして、ベントを使用して一時的に消えることができます。画面上では依然として見える状態であります。再びベントすると可視状態に戻ります。", + "HangmanInfoLong": "(Impostors):\nAs the Hangman, during the Shapeshifting, you use a unique killing method-strangling. This method ignores any status of the target, such as the shield of the Medic, the Bodyguard's protection, the Super Star's skills, etc. The strangled player will not leave a dead body, nor will it trigger any of its skills. For example, Veteran kill back (including additional roles), and Seer will not be prompted.", + "SwooperInfoLong": "(Impostors):\nAs the Swooper, you can Vent to Vanish temporarily. You will still appear visible on your screen. Vent again to become visible.", "CrewpostorInfoLong": "(チームインポスター):\nタスクを完了するたびに最も近くのプレイヤーをキルします。", - "WildlingInfoLong": "(インポスター):\nワイルドリングとして、シェイプシフトはできますが、ベントの能力はありません。キルすると、一時的に攻撃に対して免疫が得られます。", - "TricksterInfoLong": "(インポスター):\nいたずら者として、通常のインポスターとして機能しますが、1つの重要な違いがあります。他のクルーメイトの役割に対してはクルーメイトとして表示されます。シェリフはあなたをキルできません。サイキックはあなたを邪悪と見ません。密告者はあなたを見つけることができません。", + "WildlingInfoLong": "(Impostors):\nAs the Wildling, you can Shapeshift but cannot Vent.\nWhen you kill, you temporarily become immune to attacks.", + "TricksterInfoLong": "(Impostors):\nAs the Trickster, you function as a regular Impostor but with one key difference.\nYou appear as a Crewmate to Crewmate roles.\n\nThe Sheriff cannot kill you.\nPsychic does not see you as evil.\nSnitch cannot find you.", "VindicatorInfoLong": "(インポスター):\nヴィンディケーターとして、市長のように追加の投票権を持っています。", "StealthInfoLong": "(インポスター):\nステルスがキルしたとき、同じ部屋にいるプレイヤーは短時間視界を奪われる。", "PenguinInfoLong": "(インポスター):\nペンギンはキルボタンを押して対象を制御し、引きずり回すことができます。\n引きずっている間に、対象は再びキルボタンを押すか、一定の時間経過後に死亡します。直接キルするにはキルボタンを2回押します。", @@ -806,56 +806,56 @@ "LurkerInfoLong": "(インポスター):\n潜伏者として、クールダウンを一定の秒数短縮するためにベントに入ることができます。キルした後、クールダウンは元の値にリセットされます。", "VisionaryInfoLong": "(インポスター):\nビジョナリーとして、会議中に生存プレイヤーの陣営を見ることができます。以下の情報がプレイヤーに表示されます:\n\n- 赤い名前はインポスターを示します。\n- シアンの名前はクルーメイトを示します。\n- グレーの名前はニュートラルを示します。", "PlagueDoctorInfoLong": "(中立):\n(TOHのペスト医師)\nペストドクターの目標は、生きているすべてのプレイヤーを感染させることです。\n彼らは最初に一人のプレイヤーを感染させることから始め、その後、感染したプレイヤーの範囲内で設定された時間を過ごした人は誰でも自身が感染します。\n感染の進行は累積的であり、距離が離れたり会議後でもリセットされません。", - "RefugeeInfoLong": "(マッドメイツ):\n難民として、あなたは次のいずれかでした:\n -インポスターを思い出した記憶喪失者\n -ゴッドファーザーのターゲットを殺した殺人者\n -パートナーがインポスターだったロマンティック\n -インポスターを模倣した模倣者\n\n今、あなたの役割はインポスターを助けてクルーメイトを排除することです。", + "RefugeeInfoLong": "(Madmates):\nAs the Refugee, you were either:\n -An Amnesiac who remembered an Impostor\n -A killer who killed the Godfather's target.\n -A Romantic whose partner was an Impostor\n -Or an Imitator that imitated an Impostor.\n\nNow your job is to help the Impostors kill the Crewmates.", "UnderdogInfoLong": "(インポスター):\nアンダードッグとして、一定数のプレイヤーが生存するまでキルできません。", "ConsigliereInfoLong": "(インポスター):\nコンシリエーレとして、キルボタンを使用して他のプレイヤーの役割を明らかにすることができます。\n\n1回クリック:役割を明らかにする\n2回クリック:キル\n\n明らかにする回数が尽きた場合、キルボタンは通常通り機能します。", "LudopathInfoLong": "(Impostors):\nAs the Ludopath, your Kill Cooldown is randomized.\n\nMinimum it can be is 1 second, while the maximum is your default Kill Cooldown.", "GodfatherInfoLong": "(インポスター):\nゴッドファーザーとして、誰かをターゲットにするために投票します。\n次のラウンドで、もしそのターゲットが誰かに殺された場合、殺した人物は難民またはマッドメイツに変わります。", "ChronomancerInfoLong": "(インポスター):\n時間魔術師として、虐殺の準備が整うときに示すチャージバーがあります。それが100%になると、次に誰かをキルしたときに虐殺モードに入ります。これにより、チャージがなくなるまで無限にキルすることができます。そうでない場合、通常のキルクールダウンがあります。", - "PitfallInfoLong": "(インポスター):\nピットフォールとして、シェイプシフトを使用してシェイプシフトの周りのエリアをトラップとしてマークします。このエリアに入るプレイヤーは一時的に動けなくなり、視界も影響を受けます。", - "EvilMiniInfoLong": "(インポスター):\nイービルミニとして(邪悪な子供)、成長するまで不死身で、非常に長い初期キルのクールダウンがあります。成長するにつれてクールダウンが大幅に短縮されます。", - "BlackmailerInfoLong": "(インポスター):\n恐喝者として、ターゲットに変身するとそのプレイヤーを脅迫します。これは、会議中にそのプレイヤーが話せなくなることを意味します。\n\n注意: すでに誰かが脅迫されている場合、別の人を脅迫すると現在の脅迫が解除されます。", - "InstigatorInfoLong": "(インポスター):\n煽動者として、あなたの役割はクルーメイト同士を対立させることです。会議でクルーメイトが投票によって追放されるたびに、あなたが生きている限り、無実のプレイヤーに投票した追加のクルーメイトが会議後に死亡します。追加で死亡するプレイヤーの数はホストが決定します。", - "LazyGuyInfoLong": "(クルーメイト):\n怠け者は1つのタスクしか持っていません。さらに、インポスターの能力は怠け者に影響を与えません。例えば、アノニマスのスケープゴートになること、ウォーロックやパペティアーによってマークされることなどはできません。怠け者にはアドオンはありません。", + "PitfallInfoLong": "(Impostors):\nAs the Pitfall, you use your Shapeshift to mark the area around the Shapeshift as a trap. Players who enter this area will be immobilized quickly, and their vision will be affected.", + "EvilMiniInfoLong": "(Impostors):\nAs the Evil Mini, you are unkillable until you grow up and have a very long initial Kill Cooldown, which gets drastically shortened as you grow up.", + "BlackmailerInfoLong": "(Impostors):\nAs the Blackmailer, when you Shift into a target, you will blackmail that player. This means that during the meetings, they won't be able to speak.\n\nNote: If someone is already blackmailed, blackmailing another person un-blackmails the current person.", + "InstigatorInfoLong": "(Impostors):\nAs the Instigator, it's your job to turn the Crewmates against each other. Each time a Crewmate gets voted out in a meeting, if you are alive, an additional Crewmate who voted for the innocent player will die after the meeting. The Host determines the number of additional players dying.", + "LazyGuyInfoLong": "(Crewmates):\nLazy Guy has only one task. In addition, the Impostor's abilities can't affect the Lazy Guy, such as being a scapegoat for Anonymous, being marked by a Warlock or Puppeteer, and more. Lazy Guy will not have any Add-ons.", "SuperStarInfoLong": "(クルーメイト):\nスーパースターの名前の隣には星のロゴが表示されるので、誰もがスーパースターが誰であるかを知っています。スーパースターは、マーダラーがスーパースターと一緒にいるときにのみキルできます (通常のキルのみ) 。さらに、ゲッサーによってスーパースターが当てられることはありません。 ", "CelebrityInfoLong": "(クルーメイト):\n有名人が死亡したとき、すべてのクルーメイトはキルフラッシュを見る (シアーがキルフラッシュを見るのと同じ)。次の会議で通知が表示されます。インポスターはこれについて何も知りません。", - "CleanserInfoLong": "(クルーメイト):\nクレンザーとして、会議で任意のターゲットのアドオンを消去するために投票することができます。この消去は会議終了後に効果を発揮します。設定によっては、消去されたプレイヤーは再びアドオンを受け取ることができない場合があります。", + "CleanserInfoLong": "(Crewmates):\nAs The Cleanser, you can vote to erase the Add-ons of any target at the meeting. This erasure takes effect after the meeting ends. Depending on the settings, the cleansed player may never receive Add-ons again.", "KeeperInfoLong": "(クルーメイト):\nキーパーとして、あなたは誰かを守るために投票することができます。これにより、その人が追放されるのを防ぐことができます。この能力は設定によって変更可能な回数だけ使用できます。", - "MayorInfoLong": "(クルーメイト):\n市長として、あなたは追加の投票権を持っています。設定として、これらの投票は隠されることがあり、いつでも会議を呼び出すためにベントを使用することができます。また、タスク完了時に市長であることが明らかにされます。", + "MayorInfoLong": "(Crewmates):\nAs the Mayor, you have extra votes. Depending on the settings, players can't see your extra votes, you can Vent to call a meeting at any time, or you can have yourself revealed as Mayor upon task completion.", "PsychicInfoLong": "(クルーメイト):\nサイキックは会議中に赤くハイライトされた複数のプレイヤーの名前を見ることができ、少なくとも1人は悪人です。サイキックはマッドメイトになると、すべてのニュートラルとキリングクルーメイトが赤い名前で正しく表示されます。", "MechanicInfoLong": "(クルーメイト):\n整備士はいつでもベントを使用できます。また、リアクター、O2、通信を片側だけ使用して修理することができます。ライトはスイッチを1つだけ操作することで修正できます。ドアを開けると、マップ上のすべてのドアが開きます。", "SheriffInfoLong": "(クルーメイト):\nシェリフにはタスクがありません。シェリフはインポスターを殺すことができます(ホストの設定によれば、シェリフはニュートラルも殺すことができます) 。シェリフがクルーメイトを殺そうとすると、シェリフ自身が死にます。シェリフはマッドメイトになると誰でも殺すことができます (ホストの設定によれば) 。", "VigilanteInfoLong": "(クルーメイト):\nビジランテはクルーに対する潜在的な脅威を排除する役割を果たしますが、誤って無実のクルーメイトを殺すと、後悔と罪悪感に駆られたマッドメイトになります。\n\n注意: ギャングスターはビジランテをマッドメイトに変えることはできません。", "JailerInfoLong": "(クルーメイト):\n看守として、プレイヤーを牢屋に閉じ込めるためにキルボタンを使用します。次の会議中、牢獄に入れられたプレイヤーは投票または投票を受けることができません (投票数は 0 になります)。看守は投票によって囚人を処刑することを選択できます。看守が無実のプレイヤーを処刑した場合、看守はゲームの残りの部分での処刑能力を失います。看守が悪人の場合、誰でも処刑できます。看守には制限された処刑回数があります。\n\n注意: 牢屋に入れられたプレイヤーは推測または判断されず、牢屋に入れられたプレイヤーは看守を推測できる唯一のプレイヤーです。", "SnitchInfoLong": "(クルーメイト):\n密告者がすべてのタスクを完了すると、会議中にインポスターの名前が赤で表示されます。密告者が最後のタスクを残すと、インポスターは自分たちと密告者の名前の隣に「★」マークを見ることになります。密告者がマッドメイトになると、「★」マークが赤くなります。", - "MarshallInfoLong": "(クルーメイト):\n指揮官として、自分の任務を完了して、他のクルーに自分を明かしてください。 他のチームはあなたを見ることができません。 しかし、マッドメイトはあなたを見ることができます。", - "DoctorInfoLong": "(クルーメイト):\n医者はすべてのプレイヤーの死因を確認できます。さらに、医者はバッテリーが残っている限りどこからでもバイタルサインを確認できます。", + "MarshallInfoLong": "(Crewmates):\nAs the Marshall, complete your tasks to reveal yourself to the rest of the Crewmates.\nOther teams will not be able to see you.\nHowever, Madmates CAN see you.", + "DoctorInfoLong": "(Crewmates):\nDoctor can see the cause of death for all players. In addition, the Doctor can access Vitals wherever you are while he still has battery left.", "DictatorInfoLong": "(クルーメイト):\nディクテーターが誰かに投票すると、会議はその場で終了し、ディクテーターが投票したプレイヤーは排除されます。ディクテーターが誰かを投票する瞬間、ディクテーターも死亡します。", "DetectiveInfoLong": "(クルーメイト):\n探偵が遺体を報告した後、彼らは手がかりのメッセージを受け取ります。これにより探偵は犠牲者の役割が何であったかを知ることができます。ホストの設定によっては、探偵は殺害者の役割を知ることができるかもしれません。注記:探偵は気づかない状態にはなりません。。", "UndercoverInfoLong": "(クルーメイト):\nインポスターはアンダーカバーが誰であるかを知っており、彼を仲間と見なしますが、アンダーカバー自身はインポスターが誰かを知りません。", - "NiceGuesserInfoLong": "(インポスター):\nナイスゲッサー (優しい推測者) は会議中に特定のプレイヤーの役割を推測できます。正しい場合、ターゲットが死亡し、間違っている場合、ナイスゲッサーは自殺します。\n推測のコマンドは:/bt [プレイヤーID] [役割]\nプレイヤーの名前の前にプレイヤーのIDが表示されます。または、/idコマンドを使用してすべてのプレイヤーのIDを表示できます。\nナイスゲッサーはマッドメイトになったときにクルーメイトの役割を推測できます。", - "GuessMasterInfoLong": "(クルーメイト):\n推測マスターとして、会議中に行われたすべての推測試みについての情報を受け取ります。役割推測者が試みた推測の内容と、誤った推測の場合にはその旨も通知されます。", + "NiceGuesserInfoLong": "(Crewmates):\nThe Nice Guesser can guess the role of a certain player during the meeting. If it is correct, it will kill the target, and if it is wrong, Nice Guesser will suicide.\nThe guessing command is: /bt [player id] [role]\nYou can see the player's id before the player's name or use the /id command to view the id of all players.\nNice Guesser can guess Crewmate when become Madmate.", + "GuessMasterInfoLong": "(Crewmates):\nAs the Guess Master, you will receive information about every attempted guess made during a meeting. You will be informed about the role the Guesser tried to guess, and you will also be notified in case of a misguess.", "KnightInfoLong": "(クルーメイト):\n騎士にはタスクがありません。彼らは誰でも殺すことができますが、ゲーム全体で一度だけです。", - "TransporterInfoLong": "(クルーメイト):\nトランスポーターがタスクを完了するたびに、2つのランダムなプレイヤーが位置を交換しますが、プレイヤーが足りない場合、何も起こりません。注:ベントにいるプレイヤーは選択されません。", + "TransporterInfoLong": "(Crewmates):\nWhenever the Transporter completes the task, two random players will switch positions, but if there are not enough players left, nothing will happen. Note: Players in a Vent will not be selected.", "TimeManagerInfoLong": "(クルーメイト):\nタイムマネージャーが行うタスクが多いほど、会議時間が長くなります。タイムマネージャーが死亡すると、会議時間は通常に戻ります。タイムマネージャーがマッドメイトになると、スキルは増加ではなく会議時間の短縮に変わります。", - "VeteranInfoLong": "(クルーメイト):\nベテランはベントを使用してアラート状態に入ることができます。アラート状態のベテランを殺そうとするプレイヤーがいる場合、ベテランは代わりにその殺人者を殺します。ベテランはアラート状態に入るときと出るときに、シールドアニメーションが表示され、頭の上にテキストが表示されます。", - "BastionInfoLong": "(クルーメイト):\nバスティオンとして、ベントに爆弾を仕掛けてインポスターやニュートラルを排除します。\nただし、注意してください。爆弾でクルーメイトも殺すことができます。", - "CopyCatInfoLong": "(クルーメイト): \nコピーキャットとして、あなたはキルボタンを使用してターゲットの役割をコピーできます。\nいくつかのクルーメイトの役割のみをコピーできます。\nマッドメイトやラスカルをコピーしようとすると、ターゲットの役割のマッドメイトバリエーションになります。\nクルーメイトのバリアントを持つ悪役をターゲットにすると、あなたはクルーメイトのバリアントになります。\nさらに、ミーティングの後にあなたの役割はコピーキャットにリセットされます。\n「注意: 会議中に人を推測することはできません。」", + "VeteranInfoLong": "(Crewmates):\nAs the Veteran, you can enter the alert state by Venting. If a player tries to kill the Veteran in the alert state, the Veteran will kill the murderer instead. Veteran will see a Shield Animation on their body and a text above their head as a reminder when they enter and exit the alert state.", + "BastionInfoLong": "(Crewmates):\nAs the Bastion, bomb Vents to kill off Impostors and Neutrals.\nBe careful though; Crewmates can also be killed with the bombs.", + "CopyCatInfoLong": "(Crewmate):\nAs the Copycat, you can use your Kill button to copy the target's role.\n\nYou can only copy some Crewmate roles.\nIf you try to copy a Madmate or Rascal, you become the Madmate variation of the target role.\nIf you target an evil with a Crewmate variant, you'll become the Crewmate variant.\n\nAdditionally, Your role will be set back to Copycat after every meeting.\nNote You can't guess people in meetings.", "BodyguardInfoLong": "(クルーメイト):\nボディガードが近くで殺される可能性があるプレイヤーがいる場合、ボディガードはキルを防ぎ、殺人者と共に死にます。 ボディガードのスキルはどのチームのプレイヤーにも影響します。 ボディガードがマッドメイトになり、殺人者がインポスターの場合、ボディガードのスキルは発動しません。", - "DeceiverInfoLong": "(クルーメイト):\n欺瞞者は、キルボタンを通じて他のプレーヤーに偽物を売ることができます。偽物が成功裏に売れた場合、欺瞞者は自身の体に盾のアニメーションを見て、それをリマインダーとします。偽物は次の会議が終了した後に効果が現れます。キル能力のないプレーヤーが偽物を持っている場合、そのプレーヤーは即座に自殺します。キル能力を持つプレーヤーが偽物を持っている場合、次に誰かを殺そうとした時に自殺します。", - "GrenadierInfoLong": "(クルーメイト):\n擲弾兵(投擲者)として、近くのプレイヤーにフラッシュバングを使って視界を奪うことができます。これにより、インポスターは視界を失い、設定によってはニュートラルも同様です。", + "DeceiverInfoLong": "(Crewmates):\nThe Deceiver can sell the counterfeit to other players through the Kill button. If the counterfeit is sold successfully, the Deceiver will see a Shield Animation on their body as a reminder. The counterfeit will take effect after the end of the next meeting. If the player with no kill ability holds the counterfeit, he will kill himself immediately. If the player with the killing ability has the counterfeit, he will commit suicide when he tries to kill someone next time.", + "GrenadierInfoLong": "(Crewmates):\nAs the Grenadier, you can Vent to Flashbang players nearby, causing them to lose vision if they are an Impostor or, depending on settings, a Neutral.", "MedicInfoLong": "(クルーメイト):\nメディックはキルボタンを押して対象にシールドを配置できます。メディックはゲーム全体で1つのシールドしか提供できず、メディックが死ぬと対象のシールドが削除されます。メディックはまた、誰かが対象のシールドを破ろうとしているかどうかを見ることもできます。\nホストの設定に応じて、メディックまたは対象がプレイヤーがシールドを持っているかどうかを見ることができます (名前の横に緑の円「●」として表示されます) 。", "FortuneTellerInfoLong": "(クルーメイト):\n占い師として、ミーティングでプレイヤーに投票して、彼らの役割に関する手がかりを得ることができます。手がかりは実際の役割に関連します。\n\n占い師のタスクが完了した場合、手がかりではなく正確な役割が分かります!\n\n注意:ランダムなアクティブプレイヤーをヒントとして与える設定がオンになっている場合、同じプレイヤーを複数回チェックすることはできません。", "JudgeInfoLong": "(Crewmates):\nThe Judge can judge a certain player during the meeting. If the target is evil, the target will be killed (whether it is evil or not is set by the Host). If it is wrong, the Judge commits suicide.\nCommand for judgment: /tl [player id]\nYou can see the player's id before the player's name, or use the /id command to view the id of all players.\nJudges can judge all players when they become Madmate.\nIn meeting the ability count shows how many trails you have in this meeting. Out of meeting the ability counts shows how many trails you have for the whole game.", "MorticianInfoLong": "(クルーメイト):\nモーティシャンはすべての死体に矢印が指し示すのを見ることができ、モーティシャンが死体を報告すると、被害者が最後に接触したプレイヤーを知ることができます。注意:モーティシャンは気づかないまたは預言者ではありません。", "MediumInfoLong": "(クルーメイト):\nミディアムは死体が報告された後、死んだプレイヤーとコンタクトを取ることができます。報告するのはミディアムでなくてもかまいません。死んだプレイヤーはミディアムの質問にYESまたはNOで1回だけ答えることができます (死んだプレイヤーは/ms yesまたは/ms noを使用できます) 。注意:ミディアムはObliviousではありません。", - "ObserverInfoLong": "(クルーメイト):\nオブザーバーとして、最初の会議後、他のプレイヤーによって引き起こされるすべてのシールドアニメーションを確認できます。これは通常、何らかの役割能力の使用を示しているので、注意が必要です。", + "ObserverInfoLong": "(Crewmates):\nAs the Observer, you can see all Shield Animations caused by other players after the first meeting. The Shield Animations typically indicate a role ability, so look out for this.", "MonarchInfoLong": "(クルーメイト):\n君主として、他のプレイヤーに追加の投票権を与えるために彼らを騎士にすることができます。\n\n既に追加の投票権を持っているプレイヤーには騎士にすることはできません。\n\n騎士になったプレイヤーは金色の名前で表示されます。\n騎士になったプレイヤーが生存している場合、君主は推測されたりキルされたりすることはありません。", - "PacifistInfoLong": "(クルーメイト):\nパシフィストがベントを使用すると、キルボタンを持つすべてのプレイヤーのキルクールダウンがリセットされます。パシフィストがマッドメイトになると、この能力はクルーメイトにのみ効果があります。", - "OverseerInfoLong": "(クルーメイト):\n監督者として、あなたの視野は非常に限られていますが、近くのプレイヤーの役割を明らかにするために殺害ボタンを使用できます。役割を明らかにするために殺害ボタンを使用し、明らかにする対象の隣に「○」が表示されます。対象の近くに定められた時間いることでその役割を明らかにできますが、対象から遠く離れすぎると明らかにすることは中止されます。", + "PacifistInfoLong": "(Crewmates):\nWhen the Pacifist Vents, they will reset the Kill Cooldown for every player with a Kill button. When they become a Madmate, this ability will only work on Crewmates.", + "OverseerInfoLong": "(Crewmates):\nAs The Overseer, you have minimal vision, but you can use your Kill button to reveal the role of a nearby player. A 「○」 will be displayed next to the revealed target after you use the Kill button on them, and you will also be scanning them (only you can see this). Stay near the target for a defined time to reveal his role; if you move too far away, the reveal will cancel.", "CoronerInfoLong": "(クルーメイト):\n 遺体検案官、遺体検案官として、死体を報告することはできません。代わりに、死体を報告しようとすると、殺害者へと導く矢印が表示されます。ミーティングが呼ばれた場合、矢印は消えます。設定によっては、見つけた死体を報告することはできません。", "PresidentInfoLong": "(クルーメイト): \n大統領、 大統領には2つの能力があります:ミーティング終了と身元公開。\n能力1: ミーティング終了 - 大統領としてミーティング中に /finish と入力すると、即座にミーティングが終了します。\n能力2: 身元公開 - ミーティングで /reveal と入力して自己公開をすると、全員があなたが大統領であることが分かり、コマンドを入力した後は予測不可能になります。しかし、大統領が自己公開した後、大統領を殺した人は次の殺害でキルCDが大幅に減少します。", - "MerchantInfoLong": "(クルーメイト):\n 商人 、タスクを完了するごとに、ランダムなプレイヤーにランダムなアドオンを販売します。売ったアドオンごとにお金が入ります。一定額のお金を持っている場合、殺害を図った人に賄賂を与えることで次の殺害を回避できます。賄賂を受け取ったプレイヤーはあなたを殺すことができませんが、誰かは分かりません。使用した賄賂のお金は失われ、追加の賄賂には利用できません。", + "MerchantInfoLong": "(Crewmates):\nAs a merchant, you sell a random Add-on to a random player for each task you complete. Each Add-on sold earns you money. If you have a certain amount of money, you can prevent the next killing attempt against you by bribing the murderer. The bribed player won't be able to kill you, but you don't know who it is. The money used is lost and not available for additional bribes.", "RetributionistInfoLong": "(クルーメイト):\n ふくしゅうしゃ、 死後、限られた数のプレイヤーを殺害できます。\n使用方法: /ret [playerID] で殺害。", "HawkInfoLong": "(クルーメイト [幽霊]):\nホークとして、ホストが決めた限られた数のプレイヤーを殺すことができますが、外す可能性があります。何度も同じ人を斬ると命中率が上がります。", "DeputyInfoLong": "(Crewmates):\nAs the Deputy, use your Kill button to handcuff a player.\nThe player who is handcuffed will have their next kill attempt treated as a handcuff break, and the Kill Cooldown will be reset.\n\nIf the target does not have a Kill button, then the handcuff was a waste.", @@ -864,8 +864,8 @@ "AddictInfoLong": "(クルーメイト):\n中毒者として、自殺タイマーがあります。期限が切れると自殺します。\nタイマーは通気口のクールダウンによって示されます。通気口のクールダウンが 0 秒になると、まだ通気する時間があります。\nそれに間に合わない場合、死亡し、間に合った場合、自殺タイマーがリセットされます。\nまた、通気された後、一定の期間誰もあなたと対話できません。\nこの期間が終了すると、さらに一定の期間行動不能になり、死体を報告することはできません。", "MoleInfoLong": "(クルーメイト):\nモグラ\"として、ベントを使用すると、ベント内に 1 秒間留まります。ベントから出ると、マップ内のランダムなベントの近くにスポーンします (ただし、直前に使用したベントを除く)。", "AlchemistInfoLong": "(クルーメイト):\n錬金術師として、タスクを完了することでポーションを作成します。作成したポーションは、対応する説明と指示を含む役職名の下に表示されます。つの異なるポーションと、何もしない水のボトルを取得できます。", - "KamikazeInfoLong": "(インポスター):\nロケットミサイルのように、人々をマークするにはシングルクリックします。通常、ダブルクリックで殺害します。死亡すると、\"ターゲット指定\"という死因でマークされたすべての人も死亡します。", - "TracefinderInfoLong": "(クルーメイト):\nトレースファインダーとして、いつでもバイタルを確認できます。さらに、死体への矢印がホストによって設定された遅延を持って表示されます。", + "KamikazeInfoLong": "(Impostors):\nAs the Kamikaze you can single click to mark players. Double-click to kill normally. When you die, all marked also die, with death reason Targeted.", + "TracefinderInfoLong": "(Crewmates):\nAs the Tracefinder, you can access Vitals at any time.\nIn addition, you get arrows pointing to dead bodies, with a delay set by the Host.", "OracleInfoLong": "(クルーメイト):\nオラクルとして、会議中にプレイヤーに投票できます。彼らがクルーメイト、ニュートラル、またはインポスターであるかどうかを確認できます。設定によっては、結果が間違っている可能性があります。", "SpiritualistInfoLong": "(クルーメイト):\nスピリチュアリストとして、前回の会議の被害者の霊に指し示す矢印が得られます。矢印が間隔をおいて消えたり現れたりするオプションがあります。可能であれば霊にあなたの能力を知らせてください。彼らがあなたの味方であれば、悪役を見つけて追放するのに役立つかもしれません。ただし、悪役がクルーメイトに対して同じことをする可能性もあるので注意してください。", "ChameleonInfoLong": "(クルーメイト):\nカメレオンとして、ベントを使用して一時的に消えることができます。画面上では依然として見える状態であります。再びベントすると可視状態に戻ります。", @@ -884,12 +884,12 @@ "SwapperInfoLong": "(クルーメイト):\nスワッパーとして、会議での投票を交換できます。\n\n投票を交換するには、'/sw [playerID]' を2回使用します。\n\nプレイヤーのIDは会議でプレイヤー名の横に表示されますが、/idを使用してすべてのプレイヤーIDのリストを取得することもできます。\n\n注意:自分自身を交換することはできません", "ChiefOfPoliceInfoLong": "(Crewmates):\nAs the Chief of Police, you can recruit a player to be a Sheriff(only once per game).\nDepending on the settings, you may recruit non-Crewmates or players without a Kill button.\nYou may suicide when you recruit a wrong player.", "NiceMiniInfoLong": "(クルーメイト):\nナイスミニとして、あなたの生存は非常に重要です。成長するまでは殺されることはなく、成長する前に死んだり会議で追放されたりすると、全員が負けます。このユニークな役割は、あなたの生存が自分自身だけでなく、クルー全体の成功に繋がるという新たなダイナミクスをゲームにもたらします。", - "SpyInfoLong": "(クルーメイト):\nスパイとして、誰かがキルボタンを使用して (キルボタンを介して使用されるすべてのアビリティ) 、あなたは数秒間その名前がオレンジ色で表示されます。\n注意:クルーメイトがあなたにアビリティを使用した場合、彼らもオレンジ色の名前で表示されます!\n注意:アビリティの使用回数が残っていない場合、オレンジ色の名前は一切表示されません!\n注意:キルボタンの相互作用がブロックされた場合、プレイヤーのクールダウンは10秒にリセットされます。", - "RandomizerInfoLong": "(クルーメイト):\nこのランダマイザーとして、死亡時にあなたの殺害者は以下のいずれかの行動を行います:\n 1. あなたの遺体を自己報告します。\n 2. あなたの遺体の隣に立ちます。\n 3. 彼らのキルクールダウンが600秒に設定されます。\n 4. ランダムにプレイヤーを復讐します。", - "ArsonistInfoLong": "(中立):\n放火魔は、プレイヤーを選択してキルボタンをクリックし、数秒間追跡することで放火することができます。放火が始まり成功すると、シールドのアニメーションがリマインダーとして表示されます (自分にのみ見えます) 。放火魔が生き残っている全プレイヤーに放火した場合、ベントを使って火を起こし、単独で勝利することができます。\nプレイヤーの名前の横に「△」が表示されている場合、それは放火されていることを意味し、「▲」が表示されている場合、完全に放火されていることを意味します。設定によっては、放火魔はいつでも火を起こすことができます。しかし、全員を殺害することに失敗した場合、彼は敗北します。", + "SpyInfoLong": "(Crewmates):\nAs the Spy, when someone uses their Kill button on you (any ability used through the Kill button), you'll see their name in orange for a few seconds.\nNote: If a Crewmate used their ability on you, you'll also see them with an orange name!\nNote: If you cannot use left, you won't see orange names!\nNote: If the Kill button interaction is blocked, the player's Cooldown will reset to 10s'", + "RandomizerInfoLong": "(Crewmates):\nAs this Randomizer, when you die, your killer will do one of the following:\n 1. self-report your body\n 2. stand next to your body\n 3. have their Kill Cooldown set to 600s\n 4. Randomly avenge a player.", + "ArsonistInfoLong": "(Neutrals):\nThe Arsonist can douse a player by clicking the Kill button on the player and following them for a few seconds. When the dousing starts and it's successful, a Shield Animation will happen as a reminder (only visible to themselves). When the Arsonist has doused all surviving players, the Arsonist can Vent to start the fire and win alone.\n\nIf the player name shows 「△」, that means they are being doused;\nif the player name shows 「▲」, it means they have been completely doused.\nDepending on the setting, Arsonist may start the fire anytime. But if he fails to kill everyone, he loses.", "EnigmaInfoLong": "(クルーメイト): \nエニグマとして、会議ごとにキラーについてのランダムな手がかりを得ます。設定によっては、手がかりを得るためには遺体を報告する必要があるかもしれません。タスクを多く完了するほど、手がかりはより正確になります。", - "PyromaniacInfoLong": "(中立):\n放火狂として、プレイヤーに火をつける (シングルクリック) か、通常通りに殺す (ダブルクリック) ことができます。プレイヤーに火をつけてもすぐには何も起こりませんが、火をつけたプレイヤーを殺すと、あなたのキルクールダウンが大幅に短縮されます。勝つためには、最後の生存者でいる必要があります。", - "HuntsmanInfoLong": "(中立):\nハントスマンとして、毎回リセットされる特定の数のターゲットがあります。ターゲットのうちの1人を倒すと、キルクールダウンが永久に設定された量だけ減少します。ターゲット以外の誰かを倒した場合、キルクールダウンは永久に設定された量だけ増加します。ターゲットは色付きの名前で表示されます。", + "PyromaniacInfoLong": "(Neutrals):\nAs the Pyromaniac, you can douse players (single click) or kill normally (double click). Dousing players do nothing immediately, but killing a doused player will significantly shorten your Kill Cooldown. To win, be the last player alive.", + "HuntsmanInfoLong": "(Neutrals):\nAs the Huntsman, you are given a certain number of targets that reset every meeting. If you successfully eliminate one of your targets, your Kill Cooldown goes down permanently by the set amount. However, if you kill someone not one of your targets, your Kill Cooldown permanently increases by the set amount. A colored name indicates your targets.", "MiniInfoLong": "(クルーメイトまたはインポスター):\nミニは2つの役割です。ナイスミニまたはイービルミニのいずれかが選択されます。\n\n詳細については、それぞれ '/r nicemini' および '/r evilmini' を使用してください。", "JesterInfoLong": "(中立):\n道化師が投票で追放されると、道化師は単独でゲームに勝利します。道化師がゲームの終わりまで生き残ると、道化師はゲームに敗北します。注意:道化師、執行者、およびイノセントは一緒に勝利できます。", "TerroristInfoLong": "(中立):\nテロリストがすべてのタスクを完了した後に死亡した場合、テロリストは一人でゲームに勝利します (投票で排除されるか、殺されるかにかかわらず勝利できます) 。", @@ -899,33 +899,33 @@ "VectorInfoLong": "(中立):\nマリオはベントから指定された回数飛び出すことで、単独で勝利します。(回数はホストの設定によります。)", "JackalInfoLong": "(中立):\nジャッカルとして、最後の生存者になれば勝利します。さらに、殺害ボタンを使ってリクルートすることが可能です。\nただし、ターゲットがリクルート不可能な場合、使用回数を使い果たしている場合、またはリクルートオプションがない場合は、通常通りに殺害します(リクルートできると思って他人の前で殺害ボタンを押さないでください) 。\nターゲットが殺害ボタンを持ち、サイドキックに変わるオプションがオンの場合、ターゲットはサイドキックになります。それ以外の場合、リクルートアドオンを与えるオプションがオンなら、ターゲットはリクルートアドオンを獲得します。\n設定によっては、ジャッカルが殺された場合、ランダムにサイドキックが新たなジャッカルとして選ばれます。サイドキックがいない場合、リクルートが選ばれる場合があります。", "GodInfoLong": "(中立):\n神として、最初から全員の役割を知っています。ゲームの最後まで生き残れば、勝利を手に入れます。つまり、他の全員が負けてあなたが勝ちます。", - "InnocentInfoLong": "(中立):\nイノセントはキルボタンを使用して任意のプレイヤーを植え付けることができ、植え付けられた対象は即座にイノセントを殺害します。対象が会議で投票により追放されると、イノセントが勝利します。注:道化師、執行者、およびイノセントは一緒に勝利することができます。", - "PelicanInfoLong": "(中立):\nペリカンとして、キルボタンを使用してプレイヤーを生きたまま飲み込み、マップ外にテレポートしますが、すぐには殺害しません。飲み込まれたプレイヤーは、ラウンドの終わりにあなたがまだ生きている場合のみ死亡します。ラウンド中に死亡したり離れたりすると、生存している飲み込まれたプレイヤーはあなたがいた場所にマップ内で再出現します。", - "RevolutionistInfoLong": "(中立):\nレボリューショニストとして、プレイヤーをリクルートするためにキルボタンを使用し、シールドアニメーションが再生されるまでプレイヤーを追いかけます。リクルートには、ホストによって設定された確率でプレイヤーを殺害する可能性があります (ただしリクルートは継続します)。必要なプレイヤー数をリクルートすると (あなたの名前の横に表示されます)、指定された時間内にベントを使用してすぐにゲームに勝利する必要があります。時間内にベントしなければ、あなたは負けて死にます。", - "HaterInfoLong": "(中立):\nヘイターとして、キルクールダウンはありません。ただし、恋人、他のリクルート役割、アドオンのみを殺害できます。設定によっては、それ以外の誰かを殺害すると自殺します。ゲームの最後に勝利チームと一緒に勝利し、殺害可能な役割が生存していない場合に勝ちます。恋人にはなりません。", + "InnocentInfoLong": "(Neutrals):\nThe Innocent can use the Kill button to plant any player, and the planted target will immediately kill the Innocent. If the target gets voted out in the meeting, the Innocent wins. Note: Jester, Executioner, and Innocent can win together.", + "PelicanInfoLong": "(Neutrals):\nAs the Pelican, you can use the Kill button to swallow a player alive, teleporting them off-bounds but not killing them yet. Those swallowed will only die if you're still alive at the end of the round. If you die or leave during the round, all alive swallowed players will spawn into the map where you were.", + "RevolutionistInfoLong": "(Neutrals):\nAs the Revolutionist, you can recruit players by clicking the Kill button on the player and following them until the Shield Animation plays for you. Recruiting has a chance, set by the Host, to kill players (though they are still recruited). When the required number of players are recruited (displayed next to your name), you must Vent within the specified time to win the game immediately with all your recruits. If you do not Vent in time, you lose and die.", + "HaterInfoLong": "(Neutrals):\nAs the Hater, you have no Kill Cooldown. However, depending on the settings, you can only kill Lovers and other Recruiting Roles and Add-ons. Killing anyone else will make you suicide. You win at the end of the game with the winning team if none of the killable roles are alive. You will not be Lovers.", "DemonInfoLong": "(中立):\n悪魔として、健康を奪いながら殺害します。全員の名前の近くに表示されるパーセンテージで健康を確認でき、攻撃するたびに被害者に気づかれずにその健康のパーセンテージを減らします。被害者の健康を0に減らすと、彼らは死亡します。最後の生存者であれば勝利します。", - "StalkerInfoLong": "(中立):\nストーカーは誰でも殺すことができ、すべてのキルはすぐに電力の妨害を引き起こします(電力が既に妨害されている場合、何も起こりません) 。ストーカーはベントできません。ストーカーが生きている間にインポスターが勝利するか、クルーメイトがインポスターを殺して勝利すると (ホストの設定に応じて、クルーメイトが中立を殺して勝利した場合、ストーカーも勝利する場合があります) 、ストーカーは単独で勝利します。", + "StalkerInfoLong": "(Neutrals):\nThe Stalker can kill anyone, and every kill will immediately cause a Lights Sabotage (if Lights Sabotage is already active, nothing will happen). Stalker cannot Vent. If the Impostor wins while the Stalker is alive or the Crewmate wins by killing the Impostors (according to the Host's setting, the Stalker may also win when the Crewmate wins by killing the Neutrals), then the Stalker wins alone.", "WorkaholicInfoLong": "(中立):\nワーカホリックとして、全てのタスクを完了した時に単独で勝利します。ホストの設定によっては、生存している場合のみ勝利できたり、ゲームの始めに全員に明らかになることがあります (これらの設定が同時にオンになることはほとんどありません) 。", - "SolsticerInfoLong": "(中立):\nソルスティスとして、あなたは死ぬことはありません。一回のラウンドで全てのタスクを完了させることで勝利します。会議が終わるたびに、タスクはリセットされ、最初からやり直さなければなりません。\nソルスティスに対する投票は直接キャンセルされます。\nソルスティスに対する殺害試みは、会議が終了するまでペリカンのようにマップ外へテレポートさせます。\nキラーのキルクールダウンは10秒にリセットされます。", + "SolsticerInfoLong": "(Neutrals):\nAs the Solsticer, you won't die, and you win by finishing all your tasks in a single round. After every meeting finishes, your tasks reset, and you need to start all over again.\nVotes on the Solsticer will be directly canceled.\nKill attempts on the Solsticer will teleport it out of the map like Pelican until the meeting is finished.\nThe killer's Kill Cooldown will be reset to 10 seconds.\nSolsticer is counted as nothing in-game.", "CollectorInfoLong": "(中立):\nコレクターとして、プレイヤーに投票すると、そのプレイヤーに投票した他のプレイヤー1人につき1ポイントを獲得します。必要な投票数を集めると、ジェスターやエグゼキューショナーのターゲットを追放しても、ゲームが終了し、あなたは単独で勝利します。", - "GlitchInfoLong": "(中立):\nグリッチとして、プレイヤーをハックする (シングルクリック) か通常通り殺害する (ダブルクリック) ことができます。ハックされたプレイヤーは、ハックの期間中、殺害、ベント、報告をすることができません。さらに、ドア以外の妨害を呼び出すと効果がなく、ランダムなプレイヤーに変装します。妨害中または後に変装することはできません。勝利するためには、最後の生存プレイヤーである必要があります。", + "GlitchInfoLong": "(Neutrals):\nAs the Glitch, you can hack players (single click) or kill normally (double click).\nThose who have been hacked cannot kill, Vent, or report for the hack duration.\nAdditionally, calling a Sabotage other than doors will have no effect and will instead disguise you as a random player. You cannot disguise during or after Sabotages.\nTo win, be the last player alive.", "SidekickInfoLong": "(中立):\nサイドキックとして、あなたの役割はジャッカルを助けて全員を排除することです。\nあなたとジャッカルは一緒に勝利します。\n設定によっては、元のジャッカルが殺された場合に新しいジャッカルになることがあります。\n元のジャッカルが死ぬまで、殺害ができない場合もあります。", - "ProvocateurInfoLong": "(中立):\nプロヴォケーターはキルボタンで任意のターゲットを殺すことができます。ゲームの最後にターゲットが負けると、プロヴォケーターは勝利チームと一緒に勝利します。", - "BloodKnightInfoLong": "(中立):\n血の騎士は、最後のキル役が生き残り、クルーメイトの数がブラッドナイトの数以下または同じ場合に勝利します。ブラッドナイトは、各キルの後に一時的なシールドを獲得し、数秒間不死身になります", - "PlagueBearerInfoLong": "(黙示録):\nプレイグベアラーとして、キルボタンを使用して誰もがペスティレンスに変身するために皆を感染させます。\nペスティレンスに変身したら、不死でキルの能力を獲得します。\nさらに、ペスティレンスに変身した後、あなたを殺そうとする誰もがあなたを殺します。\nまた、感染したプレイヤーが未感染のプレイヤーと接触すると、そのプレイヤーも感染します。", + "ProvocateurInfoLong": "(Neutrals):\nAs the Provocateur, you can kill any target with the Kill button. If the target loses at the end of the game, the Provocateur wins with the winning team.", + "BloodKnightInfoLong": "(Neutrals):\nThe Blood Knight wins when they're the last killing role alive, and the amount of Crewmates is lower or equal to the amount of Blood Knights. The Blood Knight gains a temporary shield after every kill, making them immortal for a few seconds.", + "PlagueBearerInfoLong": "(Apocalypse):\nAs the Plaguebearer, plague everyone using your Kill button to turn into Pestilence.\nOnce you turn into Pestilence, you will become immortal and gain the ability to kill, and you will kill anyone who tries to kill you.\n\nAlso, when infected players interact with uninfected players, they will also be infected.", "PestilenceInfoLong": "(黙示録):\nペスティレンスとして、あなたは止められない機械です。\nあなたへの攻撃はすべて反射されます。\n間接的な殺害すらあなたを倒しません。\nペスティレンスを倒す唯一の方法は、投票または誤った予想です。\n変身すると、会議で全員にあなたの存在が知らされます。", - "SoulCollectorInfoLong": "(黙示録):\n魂の収集者として、キルボタンを使ってプレイヤーの死亡を予測できます。ターゲットが選択したラウンド中か、その後の会議で死亡した場合、魂を獲得します。\n\nターゲットは各会議後、または死亡した時点でリセットされます。 \n\n設定可能な数の魂を集めると、“死”になります。また、パッシブ魂獲得設定が有効の場合、会議ごとに魂を1つ獲得します。", + "SoulCollectorInfoLong": "(Apocalypse):\nAs Soul Collector, you can use your Kill button on a player to predict their death. You will gain a Soul if your target dies in the round you select them or the meeting after.\nYour target resets after each meeting or after they die, whichever comes first. \n\nOnce you collect the configurable amount of Souls, you become Death. If the gain passive Souls setting is enabled, you will gain a Soul each meeting.", "DeathInfoLong": "(黙示録):\n魂の収集者が必要な魂を集めると、“死”になります。“死”は、次の会議の終了までに追放されなければ、全員をキルして勝利します。\n\n“死”に変身する会議では、設定可能な追加の会議時間が与えられ、“死”を見つけるための議論ができます。\n\nあなたは無敵であり、変身後の会議でその存在が全員に知らされます。", "BakerInfoLong": "(Apocalypse):\nAs the Baker, you can use your Kill Button on a player per round to give them bread. \nOnce a set amount of players are alive with bread, you become Famine.\n\nIf the Bread gives additional effects and the setting is on, then you can Vent to change the bread that you give out. \nBread Effects:\nReveal: Reveals the target's role to the Baker (stays the whole game)\nRoleblock: Resets the target's Kill Cooldown when they try to use their Kill Button\nBarrier: Gives the target a Barrier that is only known to the Baker (Barrier is removed after the meeting)", "FamineInfoLong": "(黙示録):\nパン職人が設定された数のパンを持つプレイヤーを生存させると、“飢饉”になります。会議後に飢饉が追放されなかった場合、“飢饉”となり、パンを持っていないプレイヤー (他の黙示録メンバーを除く) は餓死します。\n\nパンを持っていないプレイヤーの餓死後、飢饉はキルボタンを使用して残りのプレイヤーを飢えさせることができ、次の会議直前にそのプレイヤーをキルします。\n\nあなたは無敵であり、変身後の会議でその存在が全員に知らされます。", "BerserkerInfoLong": "(黙示録):\n狂戦士として、キルごとにレベルが上がります。ホストが設定したレベルに達すると、新しい能力を解放します。\n\nスカベンジャーキルは、自分のキルを消失させます。\n爆弾キルは、キルした対象を爆発させます。キルする際には注意が必要で、他の黙示録メンバーが近くにいると巻き込まれることがあります。 \nあるレベルに達すると、“戦争”になります。", - "WarInfoLong": "(黙示録):\n戦争として、あなたは無敵で、キルのクールダウンが短く、以前の能力で誰でもキルすることができます。\n変身すると、会議で全員にあなたの存在が知らされます。", - "FollowerInfoLong": "(中立):\nフォロワーは、キルボタンを使って誰かをフォローを開始し、もう一度キルボタンを使ってフォロー対象を切り替えることができます。フォロワーの対象が勝利すると、フォロワーも勝利します。注意: フォロワーは死亡後も勝利することがあります。", - "CultistInfoLong": "(中立):\nカルティストとして、あなたのキルボタンは他のプレイヤーを魅了し、彼らにあなたと一緒に勝つようにさせます。勝利するためには、脅威となる全員を魅了し、多数を獲得する必要があります。設定によっては、ニュートラルを魅了できるかもしれませんし、魅了されたプレイヤーは元のチーム、何もなし、またはカルティストとして数えられ、多数派によって勝利を決定する場合があります。", + "WarInfoLong": "(Apocalypse):\nAs War, you are invincible, have a lower Kill Cooldown, and can kill anyone with your previous powers.\nYour presence is announced to everyone at the meeting after you transform.", + "FollowerInfoLong": "(Neutrals):\nThe Follower can use their Kill Button on someone to start following them and can use the Kill Button again to switch the following target. If the Follower's target wins, the Follower will win along with them. Note: The Follower can also win after they die.", + "CultistInfoLong": "(Neutrals):\nAs the Cultist, your Kill Button is used to Charm others, making them win with you. To win, charm all who pose a threat and gain the majority.\nDepending on settings, you may be able to charm Neutrals, and those you Charm may count as their original team, nothing, or a Cultist to determine when you win due to majority.", "SerialKillerInfoLong": "(中立):\nシリアルキラーとして、最後の生存者になれば勝利です。", - "JuggernautInfoLong": "(中立):\nジャガーノートとして、各キルごとにキルクールダウンが減少します。\n\n勝つためには、全員を倒してください。", + "JuggernautInfoLong": "(Neutrals):\nAs the Juggernaut, your Kill Cooldown decreases with each kill you make.\n\nKill everyone to win.", "InfectiousInfoLong": "(中立):\n感染者としてのあなたの役割は、できるだけ多くのプレイヤーを感染させることである。\nすべての殺人鬼を感染させれば、単純にクルーよりも数が多くなり、ゲームに勝利する。\nあなたが死亡した場合、次回のミーティング以降、感染したプレイヤーは全員死亡します。\nそれまでに勝利条件を満たしていれば勝利となります。", - "VirusInfoLong": "(中立):\nウイルスの役割は、他のすべてのプレイヤーを殺すか感染させることです。ウイルスがクルーを殺すと、その死体はウイルスに感染します。この死体を報告したクルーも感染し、設定に依存して、ウイルスが投票で排除されない場合、ミーティングの最後に死亡するかウイルスチームに加わります。ウイルスチームのプレイヤーがクルーチームのプレイヤーよりも多い場合、ウイルスチームの勝利です。", + "VirusInfoLong": "(Neutrals):\nThe task of the Virus is to kill or infect all other players. When the Virus murders a Crewmate, their corpse is infected with a virus. The Crewmate who reports this corpse is infected joins the virus team or dies at the end of the meeting if the Virus doesn't get voted out, depending on the settings. If more players are on the Virus team than the Crewmate team, the Virus team wins.", "PursuerInfoLong": "(中立):\n追跡者 、として、他のプレイヤーに能力を使用して、彼らが殺しを試みるときに誤射させることができます。\n勝つためには、単にゲームの最後まで生き残ります。", "SpecterInfoLong": "(中立):\nスペクターとして、あなたの役割は殺されてタスクを完了することです。\n生きている間にタスクを行うことができます。\n生きている状態では勝てません。\n殺された場合、タスクが完了していれば勝利チームと一緒に勝つことができます。", "PirateInfoLong": "(中立):\n海賊、として、毎ラウンドターゲットを選択するためにキルボタンを使用します。\n次の会議でターゲットと決闘します。\n海賊とターゲットが同じ数字を選んだ場合、海賊の勝利です。\nさらに、海賊が決闘に勝利するか、ターゲットが決闘に参加しない場合、海賊はターゲットを殺します。\n\n決闘コマンド:/duel X (ここでXは 0、1、または 2 にすることができます)\n\nホストが設定した決闘の勝利回数を達成した後に勝利します。\n\n注記:対象者が決闘に参加していない場合、その殺害は海賊の勝利にはカウントされません。", @@ -934,16 +934,16 @@ "CursedSoulInfoLong": "(中立):\n呪われた魂として、ゲームの最後まで生き残って勝利してください。\n道化師や処刑人から勝利を奪うこともできます。\nさらに、他のプレイヤーの魂を盗むこともできます。\n魂を持たないプレイヤーは勝ち、あなたと一緒に死にます。", "PickpocketInfoLong": "(中立):\nスリとして、あなたは殺害から票を盗みます。\nこれらの投票は非表示になります。\n全員を倒して勝ちます。", "TraitorInfoLong": "(中立):\n裏切り者として、私は詐欺師を裏切った詐欺師でした。\nあなたは詐欺師のことを知っていますが、彼らはあなたのことを知りません。\nでもトリック? 彼らはあなたを殺すことができますが、あなたは彼らを殺すことはできません。\n他の手段で詐欺師を排除し、他の全員を倒して勝利してください!", - "TrollerInfoLong": "(中立):\nトローラーとして、タスクを完了させることで、プレイヤーにランダムなイベントを発生させることができます。例えば、全プレイヤーのスピードを変えたり、テレポートさせたり、サボタージュに影響を与えたりすることができます。また、勝利チームと共に勝利することができます。", - "VultureInfoLong": "(中立):\nハゲタカとして、死体を通報して勝ちましょう!\n死体を報告すると、食べるクールダウンがリセットされていれば、その死体を食べることができるようになります (その後は報告できなくなります)。\n食べる能力がクールダウン中の場合は、通常どおり死体を報告します。\nまた、ラウンドあたりの食事の最大数に達した場合、死体は通常通り報告されます。", + "TrollerInfoLong": "(Neutrals):\nAs a Troller, you can complete tasks so that random events can happen to players.\nFor example, changing the speed of all players, teleportation, influencing Sabotage, etc.\nAlso you can win with the winning team.", + "VultureInfoLong": "(Neutrals):\nAs the Vulture, report bodies to win!\n\nWhen you report a body, if your Eat Cooldown is up, you'll eat the body (makes it unreportable).\nIf your eat ability is still on Cooldown, then you'll report the body normally.\n\nAdditionally, you'll report bodies normally if the maximum bodies eaten per round is reached.", "AbyssbringerInfoLong": "(インポスター):\n深淵をもたらす者として、ブラックホールを設置することができます。\nブラックホールはプレイヤーを吸い込み、接触すると殺害します。", - "TaskinatorInfoLong": "(中立):\nタスキネーターとして、タスクを完了するたびにそのタスクは爆弾を設置されます。別のプレイヤーが爆弾付きのタスクを完了した時、爆弾が爆発してそのプレイヤーは死亡します。\n\nクルーが勝利しない状況で最後まで生き残れば勝ちです。\n\n 注意:タスキネーターの爆弾はあらゆる保護を無視します。", + "TaskinatorInfoLong": "(Neutrals):\nAs the Taskinator, whenever you finish a task, the task will be bombed. When another player completes the bombed task, the bomb will detonate, and the player will die.\n\nYou win if you survive till the end and Crewmates don't win.\n\n Note: Taskinator bombs ignore all protection.", "BenefactorInfoLong": "(クルーメイト):\n恩人として、タスクを完了すると、そのタスクはマークされます。別のプレイヤーがマークされたタスクを完了すると、一時的な盾が得られます。\n\n注:盾は直接の攻撃からのみ保護します。", "SpiritcallerInfoLong": "(中立):\n精霊召喚師として、あなたの犠牲者は死後、悪霊になります。これらの悪霊は、他のプレイヤーを一時的に凍らせたり、視界を遮ったりして攻撃することができます。また、殺人未遂からあなたを守る一時的な盾を与えることもできます。", - "AmnesiacInfoLong": "(中立):\n記憶喪失者として、リポートボタンを使用してターゲットを記憶し、その役割を引き継ぐことができます。\nゲームバランスを保つため、記憶した役割がベントを使用できない場合、記憶喪失者としてもベントを使用することはできません。", - "ImitatorInfoLong": "(中立): \n模倣者として、あなたのキルボタンを使用してプレイヤーを模倣してください。\n\nあなたはシェリフ、難民、またはいくつかのニュートラルになるでしょう。", - "BanditInfoLong": "(中立):\n山賊として、キルボタンを1回クリックするとプレイヤーのアドオンを盗み、2回クリックするとキルが可能です。設定に応じて、アドオンは即座に盗むか、会議開始後に盗むかが決まります。最大の盗み回数に達した後は、通常通りキルが行われます。また、ターゲットに盗めるアドオンがない場合やターゲットが頑固な場合、ターゲットをキルします。\n\n全員を倒して勝ちます。\n\n注: 浄化されたプレイヤー、ラストインポスター、およびラヴァーズのアドオンは盗むことができません。\n注:「バンディットがベントを使える」が有効な場合、器用なプレイヤーから盗むのがより困難になります。", - "DoppelgangerInfoLong": "(中立):\nドッペルゲンガーとして、キルボタンを使用してプレイヤーのアイデンティティ (名前とスキン) を奪い、ターゲットを殺します。\n\n全員を倒して勝ちます。\n\n注: 迷彩が有効な場合、ターゲットのアイデンティティを奪うことはできません。", + "AmnesiacInfoLong": "(Neutrals):\nAs the Amnesiac, use your report button to remember a target and get its role.\nTo balance the game, you will not be able to Vent after remembering your role if you can't Vent as Amnesiac.'", + "ImitatorInfoLong": "(Neutrals):\nAs the Imitator, use your Kill button to imitate a player.\n\nYou'll either become a Sheriff, a Refugee, or some Neutral.", + "BanditInfoLong": "(Neutrals):\nAs the Bandit, you can click your Kill button one time to steal a player's Add-on and twice to kill. Depending on the settings, you may instantly steal the Add-on or after the meeting starts. After the maximum number of steals is reached, you will kill normally. Additionally, if there are no stealable Add-ons on the target or the target is Stubborn, you will kill the target.\n\nKill everyone to win.\n\nNote: Cleansed, Last Impostor, and Lovers cannot be stolen.\nNote: If Bandit can Vent is on, Nimble will become unstealable.", + "DoppelgangerInfoLong": "(Neutrals):\nAs the Doppelganger, use your Kill button to steal a player's identity (their name and skin) and then kill your target.\n\nKill everyone to win.\n\nNote: You cannot steal the target's identity when Camouflage is active.", "PunchingBagInfoLong": "(中立):\nパンチングバッグとして、あなたの目標は数回攻撃されて勝利することです。\n\n攻撃回数に追加されるため、推測されることはありません。", "DoomsayerInfoLong": "(中立):\nドゥームセイヤー(終末予告者)は会議中に特定のプレイヤーの役割を推測できます。\nドゥームセイヤーが特定の役割を一定数推測します (数はホストの設定に依存します)。\n推測のコマンドは:/bt [player id] [role] です。\nプレイヤーの名前の前にプレイヤーのIDを表示することができ、すべてのプレイヤーのIDを表示するために/idコマンドを使用できます。", "ShroudInfoLong": "(中立):\n覆いとして、あなたは通常殺さない。\n代わりに、プレイヤーを包むためにあなたのキルボタンを使用してください。\n包まれたプレイヤーは他の人を殺します。\n包まれたプレイヤーが殺害を行わなければ、会議の後に自分自身を殺すでしょう。\n\n覆いは、名前の隣に「◈」マークがある包まれたプレイヤーを見ます。\n殺害を行わなかった包まれたプレイヤーも、会議で「◈」マークを持っており、会議の終わりまでに覆いが生きていれば死にます。", @@ -975,16 +975,16 @@ "LastImpostorInfoLong": "(アドオン):\nこの効果は最後に生き残った詐欺師に与えられます。キルのクールダウンが減少します。", "OverclockedInfoLong": "(アドオン):\nオーバークロックすると、キルのクールダウンが一定の割合で減少します。キル ボタンのあるロールにのみ適用されます。", "LoversInfoLong": "(アドオン):\nラバーズは2人のプレイヤーの組み合わせです。ラバーズは、ラバーズだけが残れば勝ちです。ラバーズのどちらかが勝つと、もう一方も一緒に勝ちます。ラバーズはお互いの名前の横に「♥」マークが表示されます。 恋人が死亡すると、恋人が勝ち、もう一方は恋に死ぬ (ホストの設定によっては恋に落ちない) ことになり、恋人のどちらかが会議で追放されると、もう一方は死んで報告できない死体となります。", - "MadmateInfoLong": "(アドオン):\n乗組員だけがマッドメイトになれる。マッドメイトの使命は、インポスターがゲームに勝つのを助けることだ。すべてのインポスターが殺されるか追放されると、マッドメイトは負ける。」 彼らは マッドメイト が誰であるかを知っている可能性があり、インポスター も マッドメイト が誰であるかを知っている可能性があります (ホストの設定によって異なります)。\n怠け者と有名人は狂った仲間になることはできません。 シェリフ 、 密告者 、ナイス・ゲッサー、市長、裁判官はマッドメイトになることができます (ホストの設定に応じて)。 以下の役割がマッドメイトに変換されるとスキルが変化します。\n\nタイムマネージャー => タスクにより会議時間が短縮されます。\nボディガード => インポスターがキラーの場合、スキルは発動しません。\n擲弾兵 (投擲者) => 閃光弾は、詐欺師ではなく乗組員と中立者に影響を与えます。\nシェリフ => 詐欺師を含む誰でも殺害できます (ホストの設定に応じて)。\nナイスゲッサー => 乗組員と中立者を推測できます。\nサイキック => すべての邪悪なニュートラルと乗組員の名前が赤色で表示されます。\n裁判官 => 誰に対しても判決を下すことができます。\nパシフィスト => 彼らの能力はクルーメイトにのみ効果があります。", + "MadmateInfoLong": "(Add-ons):\nOnly Crewmates can become Madmate. Madmate's task is to help the Impostors win the game. Madmate will lose if all Impostors are killed/ejected. Madmates may know who are Impostors, and Impostors may know who are Madmates (Host settings).\n\nLazy Guy, Celebrity can't become Madmate. Sheriff, Snitch, Nice Guesser, Mayor, and Judge may become Madmate (Host settings). Skill changes when the following roles are converted into Madmates:\n\nTime Manager => Doing tasks will reduce meeting time.\nBodyguard => Skill won't activate if the killer is an Impostor.\nGrenadier => Flash bomb will work on Crewmates and Neutrals instead of the Impostors.\nSheriff => Can kill anyone, including Impostors (Host settings).\nNice Guesser => Can guess Crewmates and Neutrals\nPsychic => All evil Neutrals and Crewmates' names with the ability to kill will be displayed in Red.\nJudge => Can judge anyone\nPacifist => Their ability only works on Crewmates.", "WatcherInfoLong": "(アドオン):\n会議中、ウォッチャーは全員の投票を見ることができます。", "FlashInfoLong": "(アドオン):\n閃光のデフォルト移動速度は他のキャラクターよりも速いです(速度はホストの設定に依存します)。", - "TorchInfoLong": "(アドオン):\nトーチは最大の視力を持っており、ライトの妨害行為の影響を受けません。", - "SeerInfoLong": "(アドオン):\nプレイヤーが死ぬたびに、予知者はキルフラッシュ (赤いフラッシュ、時には妨害行為のような警報音を伴う) を見ることができます。", + "TorchInfoLong": "(Add-ons):\nTorch has maximum vision and is not affected by Lights Sabotage.", + "SeerInfoLong": "(Add-ons):\nWhenever a player dies, the Seer will see a kill-flash (a red flash, possibly accompanied by an alarm sound like Sabotage).", "TiebreakerInfoLong": "(アドオン):\n同点の場合、タイブレーカーが投票したターゲットが優先されます。 注: 複数のタイブレーカーが同時に異なる同点ターゲットを選択した場合、タイブレーカーのスキルは効果がありません。", "ObliviousInfoLong": "(アドオン):\n探偵と清掃員はオブリビアスになることはできません。オブリビアスは死体を報告できません。注: オブリビアスによって殺された餌は自動的に報告され、オブリビアスは匿名になります。」 の代用としても使えます。", "BewilderInfoLong": "(アドオン):\n当惑させることは、より小さい/大きい視野を持つかもしれません。当惑させることが殺された場合、設定によっては、殺害者の視野が当惑させる者の視野と同じになる可能性があります。", "WorkhorseInfoLong": "(アドオン):\n最初にすべてのタスクを完了したプレイヤーは働き馬(ワークホース)になり、そのプレイヤーに追加のタスクが与えられます。追加のタスクの数はホストによって設定されます。", - "FoolInfoLong": "(アドオン):\n探偵と整備士は愚者にはならない。愚者はサボタージュを修理することができない。", + "FoolInfoLong": "(Add-ons):\nSleuth and Mechanic won't be Fool. Fools can't repair any Sabotage.", "AvangerInfoLong": "(アドオン):\nホストは、詐欺師がアベンジャーになれるかどうかを設定できます。アベンジャーが殺されると (投票による死亡や通常のキルはカウントされません) 、アベンジャーはランダムなプレイヤーに復讐します。", "YoutuberInfoLong": "(アドオン):\nYouTuberになれるのはクルーメイトだけです。ゲームで最初にYouTuberが殺された場合、YouTuberは単独で勝利します。YouTuberが勝利条件を満たさない場合、YouTuberはクルーメイトに従って勝利します。注意:ゲッサーや他の間接的な殺し方による失格、推理など、YouTuberのスキルはトリガーされません。", "EgoistInfoLong": "(アドオン):\nマッドメイト派と中立派はエゴイストにはなりません。エゴイストのチームが勝った場合、そのチームではなくエゴイスト自身が勝ちます。", @@ -996,24 +996,24 @@ "ReachInfoLong": "(アドオン):\nキルボタンを持つ役割のみがこのアドオンを取得できます。他のすべてのプレイヤーとは異なり、あなたはゲーム内で最長のキル範囲を持っています。", "BaitInfoLong": "(アドオン):\nおとりが死ぬと、おとりを殺した犯人が自動的におとりの死体を報告します。ただし、スカベンジャー、クリーナー、スウーパー、レイス、メデューサ、または殺人マシンが おとり を殺した場合、この報告は行われません。報告にはホストの設定に応じて遅延が生じる場合があります。", "TrapperInfoLong": "(アドオン):\n「ベアトラップ」が殺されると、ベアトラップは殺人者を設定可能な時間だけ動けなくします。", - "CharmedInfoLong": "(裏切りのアドオン):\n「魅了」アドオンはカルティストによって魅了されたプレイヤーによって取得されます。一度魅了されると、あなたはもはや元のチームではなくカルティストのチームに所属します。", + "CharmedInfoLong": "(Betrayal Add-ons):\nThe Charmed Add-on is obtained by being charmed by the Cultist.\nOnce charmed, you are now on the Cultist's team and no longer on your original team.", "CleansedInfoLong": "(アドオン):\nクレンズドアドオンは、クレンザーによってすべてのアドオンが削除された場合にのみ取得できます。クレンザーの設定に応じて、将来的にさらにアドオンを取得できない場合があります。", - "InfectedInfoLong": "(裏切りのアドオン):\n「インフェクティッド」アドオンは、インフェクシャスに感染したことによって取得されます。一度感染すると、あなたはインフェクシャスのために働き、元のチームで勝つことはありません。", - "OnboundInfoLong": "(アドオン):\n「オンバウンド」アドオンを使用すると、会議中にあなたを推測することはできません。", - "ReboundInfoLong": "(アドオン):\n「リバウンド」アドオンを持っていると、ゲッサーがあなたを正確に推測した場合、またはジャッジがあなたを正確に判断した場合、彼らは代わりに死亡します。ダブルショットのプレイヤーがあなたを正確に推測した場合、即座に死亡します。", + "InfectedInfoLong": "(Betrayal Add-ons):\nThe Infected Add-on is obtained by being infected by the Infectious.\nOnce infected, you work for the Infectious and do not win with your original team.", + "OnboundInfoLong": "(Add-ons):\nWith the Onbound Add-on, you cannot be guessed in meetings.", + "ReboundInfoLong": "(Add-ons):\nWith the Rebound Add-on, if a Guesser successfully guessed you or a Judge successfully judged you, they will die instead.\nIf a player with Double Shot guesses you correctly, they will die instantly.", "MundaneInfoLong": "(アドオン):\n平凡として、すべてのタスクを完了した後でしか推測することができません。", "KnightedInfoLong": "(アドオン):\n君主が誰かを騎士にすると、その人は追加の投票権を得ます。", - "UnreportableInfoLong": "(アドオン):\n無視されたアドオンを使用すると、あなたの遺体は報告できません。", + "UnreportableInfoLong": "(Add-ons):\nWith the Disregarded Add-on, your corpse will be unreportable.", "ContagiousInfoLong": "(裏切りのアドオン):\nウイルスが感染すると、あなたは伝染病になります。\n伝染病のプレイヤーはウイルスのチームに所属します。\nミーティング後に死亡するかどうかは、ウイルスの設定に依存します。", - "LuckyInfoLong": "(アドオン):\nラッキーアドオンがあると、キルを回避する確率が発生します。この確率はホストによって設定されます。回避が発動すると、キラーにはシールドのアニメーションが表示されますが、あなたには何もわかりません。", + "LuckyInfoLong": "(Add-ons):\nWith the Lucky Add-on, there is a probability for you to evade the kill; the Host sets the specific probability. The killer will see a Shield Animation when the evasion takes effect, but you will not know anything.", "DoubleShotInfoLong": "(アドオン):\nダブルショットを持つプレイヤーが役割を誤って推測した場合、もう一度推測する機会が与えられますが、次の誤った推測は自殺につながります。", - "RascalInfoLong": "(アドオン): \nラスカルとして、あなたは保安官によって死ぬことができ、スニッチが味方をスニッチがマッドメイツプレイヤーを見つけることができれば、彼はあなたを見つけることができます。 ホストの構成に応じて異なります。マーチャントによって割り当てられることはできません。", - "SoullessInfoLong": "(アドオン): \n呪われた魂があなたの魂を奪うと、このアドオンを取得します。\nあなたは生存プレイヤーとしてカウントされません。", + "RascalInfoLong": "(Add-ons):\nAs the Rascal, you can die to the Sheriff, and Snitch can find you if Snitch can find Madmates.\n\nOnly assigned to Crewmates, cannot be assigned by the Merchant.", + "SoullessInfoLong": "(Add-ons):\nWhen a Cursed Soul steals your soul, you get this Add-on.\n\nYou are not counted as alive.", "GravestoneInfoLong": "(アドオン): \nグレイヴストーンとして、あなたの役割は死んだときに誰にでも公開されます。", "LazyInfoLong": "(アドオン): \nレイジーとして、あなたには1つの短いタスクが割り当てられ、ウォーロック、パペティア、ギャングスターからの影響を受けません。", "AutopsyInfoLong": "(アドオン): \n検死として、あなたは人々がどのように死んだかを見ることができます。\nドクター、トレースファインダー、サイエンティスト、サニーボーイに割り当てられません。", - "RebirthInfoLong": "(アドオン):\n再生として、あなたが追放される際、あなたに投票したランダムなクルーメイトとスキンを交換します。\n注意: ホストの投票はカウントされません。\n再生をすべて使い果たした場合、再生の能力は失われます。", - "LoyalInfoLong": "(アドオン): \n忠実な役割として、あなたはジャッカルやカルトなどの役割に勧誘されません。中立役には割り当てられません。", + "RebirthInfoLong": "(Add-ons):\nAs the Rebirth, if you're the player about to be ejected, you will swap skins with a random Crewmate who voted for you.\nNotice: The Host vote never counts\nRebirth will be removed from you if you exhausted all your rebirths.", + "LoyalInfoLong": "(Add-ons):\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\n\nCannot be assigned to Neutrals.", "EvilSpiritInfoLong": "(アドオン):\n邪悪なスピリットとして、あなたの仕事はスピリットコーラーを勝利に導くことです。ハントボタンを使用してプレイヤーを凍結させ、視界を制限することができます。また、ハントボタンを使用してスピリットコーラーがキルの試みに対するシールドを一時的に得ることもできます。", "RecruitInfoLong": "(裏切りアドオン):\nリクルートとして、あなたはジャッカルのチームに所属し、ジャッカルとそのサイドキックを支援します。\n元のチームと一緒に勝利することはできません。\n設定によっては、元のジャッカルが殺されてサイドキックがいない場合、新たなジャッカルになることがあります。", "AdmiredInfoLong": "(Betrayal Add-ons):\nAs an Admired player, you win with the Crewmates and not your original team.\n\nYou can see the Admirer.", @@ -1024,41 +1024,41 @@ "AntidoteInfoLong": "(アドオン): \n 他のプレイヤーがあなたにキルボタンを使用しようとすると、そのクールダウン時間が設定可能な時間だけ減少します。", "StubbornInfoLong": "(アドオン):\n頑固なアドオンを持っている場合、消しゴムはロールを消去できません。 浄化者はあなたを浄化することはできませんし、バンディットはあなたから盗むことはできません。 また、君主はあなたをナイトにすることはできません。 さらに、販売者から新しいアドオンを入手することはできません。", "SwiftInfoLong": "(アドオン):\n速い者として、殺害時に動きを見せません。\n注意: スウィフトはおとりも無視します。", - "UnluckyInfoLong": "(アドオン):\n不運なとして、タスクを完了したり、キルをしたり、ベントを使用したり、ドアを開けたりすると、死ぬ可能性があります。", + "UnluckyInfoLong": "(Add-ons):\nAs Unlucky, when you Complete Tasks, Kill, Venting, or open a Door, you have a chance to die.", "SpurtInfoLong": "(アドオン):\n歩き始めると、最初は大幅なスピードブーストを得ますが、それが急速に低下し、スピードを回復させるためにしばらく静止して休む必要があります。", "VoidBallotInfoLong": "(アドオン):\nこのアドオンを持つ者は投票数が 0 になります。", "AwareInfoLong": "(アドオン): \n気づきとして、あなたは明かす役割が自分と交流したかどうかを次の会議で通知されます。", - "FragileInfoLong": "(アドオン):\n壊れやすい役割として、誰かがあなたに対してキルボタンを使用しようとすると (その役割が直接的に殺すことができなくても)、あなたは即座に死亡します。", - "GhoulInfoLong": "(アドオン):\nグールとして、タスク完了時に2つのうちの1つの結果が発生します。\n生存している場合:自殺\n死んでいる場合:生存していれば殺害者を殺害します。\nクルーメイトにのみ割り当てられ、タスクがないクルーメイトやタスクベースのクルーメイトには割り当てられません。", - "BloodthirstInfoLong": "(アドオン): \n血の渇きを持つ者として、タスクを行うことで殺害が可能になります。\nタスクを完了すると、次に接触するプレイヤーが死亡します。\n\n会議後もあなたの血の渇きは残ります。\n殺害を行うと、次にタスクを完了するまで血の渇きは消えます。\n血の渇きは重複しません。\n\nタスクを持つクルーメイトにのみ割り当てられます。", - "MareInfoLong": "(アドオン):\n牝馬(メア)」として、キルのクールダウンが短く、速度が高いが、ライトが点灯している間しかキルできません。\n\nさらに、ライトが点灯している間、あなたの名前は赤く表示されます。\n\nこの役職はインポスターにのみ割り当てられ、推測できません。", - "BurstInfoLong": "(アドオン): \nバーストとして、あなたのキラーはベントの中にいない場合、設定された時間経過後に爆発します。", + "FragileInfoLong": "(Add-ons):\nAs Fragile, you will instantly die if someone tries to use the Kill button on you (even if the role cannot directly kill).", + "GhoulInfoLong": "(Add-ons):\nAs the Ghoul, one of two outcomes can occur on task completion.\n\nIf alive: Suicide\nIf dead: You kill your killer if they're alive.\n\nThis is only assigned to Crewmates, and not Crewmates with no tasks or are task-based.", + "BloodthirstInfoLong": "(Add-ons):\nAs the Bloodthirst, doing tasks allows you to become bloodthirsty and kill players.\nWhen you finish a task, the next player you come in contact with dies.\n\nYour Bloodthirst remains after a meeting.\nUpon making a kill, your Bloodthirst clears till the next task you complete.\nBloodthirsts do not stack.\n\nOnly assigned to Crewmates with tasks.", + "MareInfoLong": "(Add-ons):\nAs the Mare, you have a low Kill Cooldown and have higher speed but can only kill during lights.\n\nAdditionally, your name will appear in red during lights.\n\nOnly assigned to Impostors and cannot be guessed.", + "BurstInfoLong": "(Add-ons):\nAs the Burst, your killer explodes if they aren't inside a Vent after a set amount of time.", "SleuthInfoLong": "(アドオン):\n探偵として、死体から情報を得ることができます。\nオプションで、キラーの役割も知ることができます。\nディテクティブまたはモーティシャンには割り当てられません。", - "ClumsyInfoLong": "(アドオン):\n不器用なため、キルをミスする確率があります。\nミスした場合、クールダウンがリセットされ、対象は触れられないままです。\nキラーにのみ割り当てられます。", - "CircumventInfoLong": "(アドオン):\n回避者として、あなたはベントを使用できません。\n\nインポスターにのみ割り当てられます。", - "NimbleInfoLong": "(アドオン):\n器用な役割として、あなたはベントボタンにアクセスできます。\nこの能力は一部のクルーメイトにのみ与えられます。", - "InfluencedInfoLong": "(アドオン):\n影響を受けた者として、あなたの投票は最も票を得たプレイヤーに強制されます。追放されるプレイヤーを選ぶ際、影響を受けた投票はカウントされません。最初に投票したプレイヤーに対するあなたの投票スキルは依然として機能することに注意してください。生存している全てのプレイヤーが影響を受けている場合、投票結果は変わりません。コレクターは影響を受けることはありません。", + "ClumsyInfoLong": "(Add-ons):\nAs the Clumsy, you have a chance to miss your kill.\n\nWhen you miss, your Cooldown is reset, and the target remains untouched.\n\nOnly assigned to killers.", + "CircumventInfoLong": "(Add-ons):\nAs the Circumvent, you can't Vent.\n\nOnly assigned to Impostors.", + "NimbleInfoLong": "(Add-ons):\nAs the Nimble, you gain access to the Vent button.\n\nOnly assigned to certain Crewmates.", + "InfluencedInfoLong": "(Add-ons):\nAs the Influenced, your vote will be forced to the player with the most votes.\nInfluenced vote won't be counted while choosing the exiled player'\nNote that your vote skill still functions on the player you voted first\nIf all the alive players are Influenced, then the vote result won't shift\nCollector cannot become Influenced.", "SilentInfoLong": "(アドオン):\nサイレントとして、あなたの投票アイコンは結果画面に表示されません。\nそのため、誰が誰に投票したかは分かりません。", "SusceptibleInfoLong": "(アドオン):\n影響を受けやすい者として、あなたの死因はランダムになります。", "TrickyInfoLong": "(アドオン):\nトリッキーとして、あなたの殺害はランダムな死因を持つことになります。", "TiredInfoLong": "(アドオン):\n疲れたが誰かを倒す (またはキル能力を使用する) たび、またはタスクを完了するたびに、一時的に視界が狭く、速度が低下します。", "StatueInfoLong": "(アドオン):\n多くの人が像の近くにいると、設定によって像は完全に凍結するか、または速度が遅くなります。", "EvaderInfoLong": "(アドオン):\n回避者が投票で追放されるとき、追放を回避する可能性があります (確率はホストが設定します)。", - "CyberInfoLong": "(アドオン):\nサイバーとして、グループ内にいる間は死ぬことができません。\n設定によっては、インポスター、中立者、またはクルーメイトがあなたの死亡を知ることができます。", - "HurriedInfoLong": "(アドオン):\n急いでいる場合、チームと一緒に勝つためにすべてのタスクを完了する必要があります! タスクに失敗した場合、負けます。\n急いでいると、マッドメイト、チャームされたりしないように目標に急ぎます。", - "OiiaiInfoLong": "(アドオン):\nオイアイ猫として、死亡した場合、殺害者が自分の役割を忘れるようになります。さらに、設定によっては、オイアイ猫を殺害者に渡すことができます。", - "RainbowInfoLong": "(アドオン)\n虹のように、あなたはクレイジーなくらい色を変えます.", + "CyberInfoLong": "(Add-ons):\nAs the Cyber, you cannot die while in a group.\nDepending on the settings, Impostors, Neutrals, and or Crewmates will know if you die.", + "HurriedInfoLong": "(Add-ons):\nAs the hurried, you must finish all your tasks to win with your team! If you fail with your tasks, you lose.\nHurried hurries to his goal, so it won't get Madmate, Charmed or so.", + "OiiaiInfoLong": "(Add-ons):\nAs the OIIAI, when you die, you will make your killer forget their role.\nAdditionally, you may pass OIIAI on to the killer, depending on settings.", + "RainbowInfoLong": "(Add-ons):\nAs the Rainbow, you change your colors like crazy.", "GMInfoLong": "(なし): \nゲームマスターは観察役の役割です。\n彼らの存在はゲームに影響を与えず、すべてのプレイヤーがゲームマスターであることを知っています。 ゲームマスターの役割はホストに割り当てられ、ゲーム開始時に自動的にゴーストになります。", - "SunnyboyInfoLong": "(中立):\nサニーボーイとして、ゲームの最後までに死んでいれば勝利します。生存している間は、キラーが多数派を獲得してもゲームは終了しません。さらに、持ち運び可能なバイタルにアクセスできます。", - "BardInfoLong": "(インポスター):\n吟遊詩人が生きている場合、追放の確認画面には吟遊詩人によって構成された文が表示されます。吟遊詩人がクリエーションを完了するたびに、吟遊詩人のキルクールダウンが永久に半分になります。", + "SunnyboyInfoLong": "(Neutrals):\nAs the Sunnyboy, you win if you are dead by the end of the game. The game will not end when you are alive due to killers gaining the majority.\nAdditionally, you have access to portable Vitals.", + "BardInfoLong": "(Impostors):\nWhen a Bard is alive, the exile confirmation will display a sentence composed by the Bard. Whenever the Bard completes a creation, the Bard's Kill Cooldown will be permanently halved.", "WardenInfoLong": "(クルーメイト [幽霊]):\n守護者として、近くの危険を誰かに警告し、さらに一時的なスピードブーストを与えます。", "GhastlyInfoLong": "(クルーメイト [幽霊]):\nゴースとして、何も知らない人を憑依し、その後、彼らに対象を選ばせます。そうすると、あなたが他の誰かを憑依するか、憑依時間が終わるまで、彼らはその対象にのみ自分の殺害能力 (または殺害スキル) を使用できるようになります。", - "MinionInfoLong": "(インポスター [幽霊]):\n手下として、非インポスターを一時的に盲目にすることができます。", - "DollMasterInfoLong": "(インポスター):\nドールマスターとして、シェイプシフトボタンを使って任意のプレイヤーを一時的に操作し、あなたの行為を行わせることができます!", - "DoubleAgentInfoLong": "(インポスター):\n二重スパイとして、キルボタンにはアクセスできません。しかし、会議で誰かに投票することで、そのプレイヤーに爆弾を渡すことができ、一度に1人にしか渡せません。会議が終了すると、爆弾は一定時間後に作動し、爆発します。\n注: 会議中に誰かに爆弾を渡した後、さらに投票することができます。\n\nまた、設定に応じて、二重スパイはベント中にバスティオンやアジテーターの爆弾を解除できることがあります。\n\n二重スパイは、最後のインポスターとなったときに役割を変更することができ、設定に応じて、役割が尊敬されるインポスター、いたずら者、裏切り者、または二重スパイのままになることがあります。", + "MinionInfoLong": "(Impostor [Ghost]):\nAs the Minion, you can temporarily blind non-Impostors.", + "DollMasterInfoLong": "(Impostor):\nAs the Dollmaster, you can temporarily take control of any player by using the Shapeshift button and to make them do your deeds!", + "DoubleAgentInfoLong": "(Impostor):\nAs the Double Agent, you cannot access the Kill button. However, you can vote for someone in a meeting to pass a bomb onto them, which can only be done one player at a time. Once the meeting has finished, the bomb will activate and explode in a set amount of time.\nNote: when you pass the bomb onto someone in a meeting, you can vote afterward.\n\nAdditionally depending on settings the Double Agent can diffuse Bastion and Agitator bombs when Venting.\n\nThe Double Agent can change roles when they are the Last Impostor, depending on the settings the role can be a Admired Impostor, Trickster, Traitor, or stay as the Double Agent.", "SlothInfoLong": "(アドオン):\n怠け者のデフォルト移動速度は他のプレイヤーよりも遅いです (速度はホストの設定に依存します)。", - "ProhibitedInfoLong": "(アドオン):\n禁止された者として、使用できない特定のベントがあります。\n無効化されるベントの数はホストの設定によって決まります。", - "EavesdropperInfoLong": "(アドオン):\n立ち聞きとして、葬儀屋や探偵のように、他の役職やアドオンに基づく情報メッセージを読むチャンスがあります。", + "ProhibitedInfoLong": "(Add-ons):\nAs the Prohibited, you have specific Vents that you can't use.\nHow many Vents are disabled depends on the Host's settings.", + "EavesdropperInfoLong": "(Add-ons):\nAs the Eavesdropper, you have a chance to read other Role/Add-on information-based messages like Mortician or Sleuth.", "ApocalypseInfoLong": "(黙示録):\n黙示録のメンバーは、独自のチームに所属し、一緒に行動して勝利を目指します。\nゲーム内に複数の黙示録役職がある場合、互いの役職を確認することができます。\nホストの設定によっては、黙示録役職が推測を行ったり、推測されることが可能です。", "RevenantInfoLong": "(中立):\nレヴナント(亡霊)として、あなたの目標は殺されることです。\nもし殺されると、あなたは殺した相手の役職を奪い、その相手を逆に殺害します。\n殺される前に勝利することはできません。\nなお、レヴナント(亡霊)の能力は直接殺される場合のみ有効です。", "ShowTextOverlay": "テキストオーバーレイ", diff --git a/Resources/Lang/zh_CN.json b/Resources/Lang/zh_CN.json index db3b0aa20..68f7666a3 100644 --- a/Resources/Lang/zh_CN.json +++ b/Resources/Lang/zh_CN.json @@ -3955,4 +3955,4 @@ "PolicPreventRecruitNonKiller": "防止招募没有击杀按钮的玩家", "PolicSuidiceWhenTargetNotKiller": "招募非带刀玩家或非船员时自杀", "PolicPassConverted": "可以将已转换的附加职业转移给警长" -} +} \ No newline at end of file diff --git a/Resources/Lang/zh_TW.json b/Resources/Lang/zh_TW.json index 189dfabdb..43126d8bf 100644 --- a/Resources/Lang/zh_TW.json +++ b/Resources/Lang/zh_TW.json @@ -3955,4 +3955,4 @@ "PolicPreventRecruitNonKiller": "防止招募沒有擊殺按鈕的玩家", "PolicSuidiceWhenTargetNotKiller": "Suicides when recruit a non-Killer or non-Crewmate", "PolicPassConverted": "Can pass Converted Add-on to Sheriff" -} +} \ No newline at end of file diff --git a/Roles/Impostor/Exorcist.cs b/Roles/Impostor/Exorcist.cs new file mode 100644 index 000000000..d13626412 --- /dev/null +++ b/Roles/Impostor/Exorcist.cs @@ -0,0 +1,258 @@ +using Hazel; +using TMPro; +using TOHE.Roles.Core; +using TOHE.Roles.Crewmate; +using UnityEngine; +using UnityEngine.Events; +using static TOHE.Translator; + +namespace TOHE.Roles.Impostor; + +internal class Exorcist : RoleBase +{ + //===========================SETUP================================\\ + public override CustomRoles Role => CustomRoles.Exorcist; + private const int Id = 31100; + public static readonly HashSet PlayerIds = []; + public static bool HasEnabled => PlayerIds.Any(); + public override CustomRoles ThisRoleBase => CustomRoles.Impostor; + public override Custom_RoleType ThisRoleType => Custom_RoleType.ImpostorKilling; + //==================================================================\\ + private static OptionItem ExorcismActiveFor; + private static OptionItem ExorcismPerGame; + private static OptionItem ExorcismDelay; + private static OptionItem ExorcismSacrificesToDispel; + private static OptionItem ExorcismLimitMeeting; + private static OptionItem ExorcismEndOnKill; + private static OptionItem TryHideMsg; + + private int ExorcismLimitPerMeeting; + private static bool IsExorcismActive; + private static bool IsDelayActive; + private static PlayerControl ExorcistPlayer; + private int Sacrifices = 0; + private bool Dispelled = false; + + public override void SetupCustomOption() + { + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Exorcist); + ExorcismActiveFor = FloatOptionItem.Create(Id + 11, "ExorcismActiveFor", new(1f, 10f, 1f), 3f, TabGroup.ImpostorRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Exorcist]) + .SetValueFormat(OptionFormat.Seconds); + ExorcismPerGame = IntegerOptionItem.Create(Id + 12, "ExorcismPerGame", new(1, 10, 1), 3, TabGroup.ImpostorRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Exorcist]); + ExorcismDelay = FloatOptionItem.Create(Id + 13, "ExorcismDelay", new(0f, 10f, 1f), 3f, TabGroup.ImpostorRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Exorcist]) + .SetValueFormat(OptionFormat.Seconds); + ExorcismSacrificesToDispel = IntegerOptionItem.Create(Id + 14, "ExorcismSacrificesToDispel", new(1, 10, 1), 2, TabGroup.ImpostorRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Exorcist]); + ExorcismLimitMeeting = IntegerOptionItem.Create(Id + 15, "ExorcismLimitMeeting", new(1, 5, 1), 1, TabGroup.ImpostorRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Exorcist]); + ExorcismEndOnKill = BooleanOptionItem.Create(Id + 16, "ExorcismEndOnKill", true, TabGroup.ImpostorRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Exorcist]); + TryHideMsg = BooleanOptionItem.Create(Id + 17, "ExorcistTryHideMsg", true, TabGroup.ImpostorRoles, false) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Exorcist]) + .SetColor(Color.green); + } + public override void Init() + { + PlayerIds.Clear(); + } + public override void Add(byte playerId) + { + PlayerIds.Add(playerId); + ExorcismLimitPerMeeting = ExorcismLimitMeeting.GetInt(); + AbilityLimit = ExorcismPerGame.GetInt(); + } + public override void Remove(byte playerId) + { + PlayerIds.Remove(playerId); + } + + public override void AfterMeetingTasks() + { + ExorcismLimitPerMeeting = ExorcismLimitMeeting.GetInt(); + } + + public bool CheckCommand(PlayerControl player, string msg, bool isUI = false) + { + if (!AmongUsClient.Instance.AmHost) return false; + if (!GameStates.IsMeeting || player == null || GameStates.IsExilling) return false; + if (!player.Is(CustomRoles.Exorcist) || !player.IsAlive()) return false; + + + msg = msg.ToLower().Trim(); + + var commands = new[] { "exorcise", "exorcism", "ex" }; + foreach (var cmd in commands) + { + if (msg.StartsWith("/" + cmd)) + { + if (AbilityLimit <= 0 || ExorcismLimitPerMeeting <= 0) + { + if (TryHideMsg.GetBool() && !player.Data.IsHost()) + GuessManager.TryHideMsg(); + player.ShowInfoMessage(isUI, GetString("ExorcistOutOfUsages")); + return true; + } + if (Dispelled) + { + if (TryHideMsg.GetBool() && !player.Data.IsHost()) + GuessManager.TryHideMsg(); + player.ShowInfoMessage(isUI, GetString("ExorcistDispelled")); + return true; + } + if (IsExorcismActive || IsDelayActive) + { + if (TryHideMsg.GetBool() && !player.Data.IsHost()) + GuessManager.TryHideMsg(); + player.ShowInfoMessage(isUI, GetString("ExorcistActive")); + return true; + } + ActivateExorcism(player); + GetProgressText(player.PlayerId, false); + return true; + } + } + return false; + } + + public static bool IsExorcismCurrentlyActive() + { + return IsExorcismActive; + } + + public static void ExorcisePlayer(PlayerControl player) + { + if (ExorcismEndOnKill.GetBool() && IsExorcismActive) + { + IsExorcismActive = false; + Utils.SendMessage(Translator.GetString("ExorcistEnd")); + } + player.SetDeathReason(PlayerState.DeathReason.Exorcised); + player.SetRealKiller(ExorcistPlayer); + GuessManager.RpcGuesserMurderPlayer(player); + Main.PlayersDiedInMeeting.Add(player.PlayerId); + MurderPlayerPatch.AfterPlayerDeathTasks(player, PlayerControl.LocalPlayer, true); + Utils.SendMessage(string.Format(Translator.GetString("ExorcistKill"), player.name.RemoveHtmlTags())); + Exorcist exorcist = (Exorcist)ExorcistPlayer.GetRoleClass(); + exorcist.Sacrifice(); + } + + public static void ActivateExorcism(PlayerControl player) + { + var exorcist = (Exorcist)player.GetRoleClass(); + exorcist.ExorcismLimitPerMeeting--; + exorcist.AbilityLimit--; + exorcist.SendSkillRPC(); + + if (TryHideMsg.GetBool()) + GuessManager.TryHideMsg(); + ExorcistPlayer = player; + IsDelayActive = true; + if (ExorcismDelay.GetFloat() > 0) + Utils.SendMessage(string.Format(GetString("ExorcistNotify"), ExorcismDelay.GetFloat())); + + _ = new LateTask(() => + { + IsExorcismActive = true; + IsDelayActive = false; + Utils.SendMessage(string.Format(Translator.GetString("ExorcistStart"), ExorcismActiveFor.GetFloat())); + _ = new LateTask(() => + { + if (IsExorcismActive) + { + IsExorcismActive = false; + Utils.SendMessage(GetString("ExorcistEnd")); + } + }, ExorcismActiveFor.GetFloat(), "ExorcistNotify"); + + }, ExorcismDelay.GetFloat(), "ExorcistNotify"); + } + + public void Sacrifice() + { + Sacrifices++; + if (Sacrifices >= ExorcismSacrificesToDispel.GetInt()) + Dispelled = true; + } + public override string GetProgressText(byte playerId, bool coooms) + => Utils.ColorString(AbilityLimit <= 0 ? Color.gray : Utils.GetRoleColor(CustomRoles.Exorcist), $"({AbilityLimit})") ?? "Invalid"; + + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] + class StartMeetingPatch + { + public static void Postfix(MeetingHud __instance) + { + if (PlayerControl.LocalPlayer.GetRoleClass() is Exorcist exorcist) + exorcist.CreateExorcistButton(__instance); + } + } + + public void CreateExorcistButton(MeetingHud __instance) + { + + if (GameObject.Find("ExorcistButton") != null) + GameObject.Destroy(GameObject.Find("ExorcistButton")); + + PlayerControl pc = PlayerControl.LocalPlayer; + if (!pc.IsAlive()) return; + + GameObject parent = GameObject.Find("Main Camera").transform.Find("Hud").Find("ChatUi").Find("ChatScreenRoot").Find("ChatScreenContainer").gameObject; + GameObject template = __instance.transform.Find("MeetingContents").Find("ButtonStuff").Find("button_skipVoting").gameObject; + GameObject exorcistButton = UnityEngine.Object.Instantiate(template, parent.transform); + exorcistButton.name = "ExorcistButton"; + exorcistButton.transform.localPosition = new Vector3(3.46f, 0f, 45f); + exorcistButton.SetActive(true); + SpriteRenderer renderer = exorcistButton.GetComponent(); + renderer.sprite = CustomButton.Get("shush"); + renderer.color = Color.white; + GameObject Text_TMP = exorcistButton.GetComponentInChildren().gameObject; + Text_TMP.SetActive(false); + PassiveButton button = exorcistButton.GetComponent(); + button.OnClick.RemoveAllListeners(); + button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => ExorcistOnClick(exorcistButton))); + GameObject ControllerHighlight = exorcistButton.transform.Find("ControllerHighlight").gameObject; + ControllerHighlight.transform.localScale = new Vector3(0.5f, 2f, 0.5f); + } + + + private static void ExorcistOnClick(GameObject exorcistButton) + { + if (!PlayerControl.LocalPlayer.IsAlive()) return; + Logger.Msg($"Exorcist Click: ID {PlayerControl.LocalPlayer.PlayerId}", "Exorcist UI"); + if (AmongUsClient.Instance.AmHost && PlayerControl.LocalPlayer.GetRoleClass() is Exorcist exorcist) + { + if (exorcist.AbilityLimit <= 0) + { + PlayerControl.LocalPlayer.ShowInfoMessage(true, GetString("ExorcistOutOfUsages")); + return; + } + exorcist.CheckCommand(PlayerControl.LocalPlayer, "/ex", true); + } + else if (PlayerControl.LocalPlayer.GetRoleClass() is Exorcist exorcist1) + { + SendExorcismRPC(PlayerControl.LocalPlayer.PlayerId); + } + exorcistButton.SetActive(false); + _ = new LateTask(() => exorcistButton.SetActive(true), 1f, "ExorcistButton"); + } + + private static void SendExorcismRPC(byte exorcistId) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ExorcistExorcise, SendOption.Reliable); + writer.Write(exorcistId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC_Custom(MessageReader reader, PlayerControl pc) + { + if (pc.GetRoleClass() is Exorcist exorcist) + { + byte exorcistId = reader.ReadByte(); + PlayerControl exorcistPlayer = Utils.GetPlayerById(exorcistId); + if (exorcistPlayer == null) return; + exorcist.CheckCommand(exorcistPlayer, "/ex", false); + } + } +} diff --git a/main.cs b/main.cs index 2c9abcea5..0e36c2f87 100644 --- a/main.cs +++ b/main.cs @@ -703,6 +703,7 @@ public enum CustomRoles EvilHacker, EvilMini, EvilTracker, + Exorcist, Fireworker, Gangster, Godfather,