using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using AmongUs.GameOptions; using Assets.CoreScripts; using EHR.Gamemodes; using EHR.Modules; using EHR.Patches; using EHR.Roles; using HarmonyLib; using Hazel; using InnerNet; using UnityEngine; using UnityEngine.Networking; using static EHR.Translator; // ReSharper disable InconsistentNaming namespace EHR; internal class Command(string key, string arguments, Command.UsageLevels usageLevel, Command.UsageTimes usageTime, Action action, bool isCanceled, bool alwaysHidden, string[] argsDescriptions = null) { public enum UsageLevels { Everyone, Modded, Host, HostOrModerator, HostOrAdmin } public enum UsageTimes { Always, InLobby, InGame, InMeeting, AfterDeath, AfterDeathOrLobby } public static List AllCommands = []; public string[] CommandForms = GetString($"CommandForms.{key}").Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); public string Key => key; public string Arguments => arguments; public string Description => GetString($"CommandDescription.{key}"); public string[] ArgsDescriptions => argsDescriptions ?? []; public UsageLevels UsageLevel => usageLevel; public UsageTimes UsageTime => usageTime; public Action Action => action; public bool IsCanceled => isCanceled; public bool AlwaysHidden => alwaysHidden; public bool IsThisCommand(string text) { if (!text.StartsWith('/')) return false; text = text.ToLower().Trim().TrimStart('/'); return CommandForms.Any(text.Split(' ')[0].Equals); } public bool CanUseCommand(PlayerControl pc, bool checkTime = true, bool sendErrorMessage = false) { if (UsageLevel == UsageLevels.Everyone && UsageTime == UsageTimes.Always && !Lovers.PrivateChat.GetBool()) return true; if (Lovers.PrivateChat.GetBool() && GameStates.IsInTask && pc.IsAlive()) return false; switch (UsageLevel) { case UsageLevels.Host when !pc.IsHost(): case UsageLevels.Modded when !pc.IsModdedClient(): case UsageLevels.HostOrModerator when !pc.IsHost() && (AmongUsClient.Instance.AmHost && !ChatCommands.IsPlayerModerator(pc.FriendCode)): case UsageLevels.HostOrAdmin when !pc.IsHost() && AmongUsClient.Instance.AmHost && !ChatCommands.IsPlayerAdmin(pc.FriendCode): if (sendErrorMessage) Utils.SendMessage("\n", pc.PlayerId, GetString($"Commands.NoAccess.Level.{UsageLevel}")); return false; } if (!checkTime) return true; switch (UsageTime) { case UsageTimes.InLobby when !GameStates.IsLobby: case UsageTimes.InGame when !GameStates.InGame: case UsageTimes.InMeeting when !GameStates.IsMeeting: case UsageTimes.AfterDeath when pc.IsAlive(): case UsageTimes.AfterDeathOrLobby when pc.IsAlive() && !GameStates.IsLobby: if (sendErrorMessage) Utils.SendMessage("\n", pc.PlayerId, GetString($"Commands.NoAccess.Time.{UsageTime}")); return false; } return true; } } [HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))] internal static class ChatCommands { public static readonly List ChatHistory = []; public static readonly Dictionary LastSentCommand = []; private static readonly Dictionary PollVotes = []; private static readonly Dictionary PollAnswers = []; private static readonly List PollVoted = []; private static float PollTimer = 45f; private static List GMPollGameModes = []; private static List MPollMaps = []; public static readonly Dictionary MutedPlayers = []; public static Dictionary> DraftRoles = []; public static Dictionary DraftResult = []; public static readonly HashSet Spectators = []; public static readonly HashSet LastSpectators = []; public static readonly HashSet ForcedSpectators = []; private static HashSet ReadyPlayers = []; public static HashSet VotedToStart = []; private static string CurrentAnagram = string.Empty; public static bool HasMessageDuringEjectionScreen; private static bool WaitingToSend; private static long LastSetNameInLobby; public static void LoadCommands() { Command.AllCommands = [ new("LT", "", Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, LTCommand, false, false), new("Dump", "", Command.UsageLevels.Modded, Command.UsageTimes.Always, DumpCommand, false, false), new("Version", "", Command.UsageLevels.Modded, Command.UsageTimes.Always, VersionCommand, false, false), new("ChangeSetting", "{name} {?} [?]", Command.UsageLevels.Host, Command.UsageTimes.InLobby, ChangeSettingCommand, true, false, [GetString("CommandArgs.ChangeSetting.Name"), GetString("CommandArgs.ChangeSetting.UnknownValue"), GetString("CommandArgs.ChangeSetting.UnknownValue")]), new("Winner", "", Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, WinnerCommand, true, false), new("LastResult", "", Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, LastResultCommand, true, false), new("Rename", "{name}", Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, RenameCommand, true, false, [GetString("CommandArgs.Rename.Name")]), new("HideName", "", Command.UsageLevels.Host, Command.UsageTimes.InLobby, HideNameCommand, true, false), new("Level", "{level}", Command.UsageLevels.Host, Command.UsageTimes.InLobby, LevelCommand, true, false, [GetString("CommandArgs.Level.Level")]), new("Now", "", Command.UsageLevels.Everyone, Command.UsageTimes.Always, NowCommand, true, false), new("Disconnect", "{team}", Command.UsageLevels.Host, Command.UsageTimes.InGame, DisconnectCommand, true, false, [GetString("CommandArgs.Disconnect.Team")]), new("R", "[role]", Command.UsageLevels.Everyone, Command.UsageTimes.Always, RCommand, true, false, [GetString("CommandArgs.R.Role")]), new("Up", "{role}", Command.UsageLevels.Host, Command.UsageTimes.InLobby, UpCommand, true, false, [GetString("CommandArgs.Up.Role")]), new("SetRole", "{id} {role}", Command.UsageLevels.Host, Command.UsageTimes.InLobby, SetRoleCommand, true, false, [GetString("CommandArgs.SetRole.Id"), GetString("CommandArgs.SetRole.Role")]), new("Help", "", Command.UsageLevels.Everyone, Command.UsageTimes.Always, HelpCommand, true, false), new("KCount", "", Command.UsageLevels.Everyone, Command.UsageTimes.InGame, KCountCommand, true, false), new("AddMod", "{id}", Command.UsageLevels.Host, Command.UsageTimes.Always, AddModCommand, true, false, [GetString("CommandArgs.AddMod.Id")]), new("DeleteMod", "{id}", Command.UsageLevels.Host, Command.UsageTimes.Always, DeleteModCommand, true, false, [GetString("CommandArgs.DeleteMod.Id")]), new("Combo", "{mode} {role} {addon} [all]", Command.UsageLevels.Everyone, Command.UsageTimes.Always, ComboCommand, true, false, [GetString("CommandArgs.Combo.Mode"), GetString("CommandArgs.Combo.Role"), GetString("CommandArgs.Combo.Addon"), GetString("CommandArgs.Combo.All")]), new("Effect", "{effect}", Command.UsageLevels.Host, Command.UsageTimes.InGame, EffectCommand, true, false, [GetString("CommandArgs.Effect.Effect")]), new("AFKExempt", "{id}", Command.UsageLevels.HostOrAdmin, Command.UsageTimes.Always, AFKExemptCommand, true, false, [GetString("CommandArgs.AFKExempt.Id")]), new("MyRole", "", Command.UsageLevels.Everyone, Command.UsageTimes.InGame, MyRoleCommand, true, false), new("TPOut", "", Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, TPOutCommand, true, false), new("TPIn", "", Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, TPInCommand, true, false), new("Template", "{tag}", Command.UsageLevels.Everyone, Command.UsageTimes.Always, TemplateCommand, true, false, [GetString("CommandArgs.Template.Tag")]), new("MessageWait", "{duration}", Command.UsageLevels.Host, Command.UsageTimes.Always, MessageWaitCommand, true, false, [GetString("CommandArgs.MessageWait.Duration")]), new("Death", "[id]", Command.UsageLevels.Everyone, Command.UsageTimes.AfterDeath, DeathCommand, true, false, [GetString("CommandArgs.Death.Id")]), new("Say", "{message}", Command.UsageLevels.HostOrModerator, Command.UsageTimes.Always, SayCommand, true, false, [GetString("CommandArgs.Say.Message")]), new("Vote", "{id}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, VoteCommand, true, true, [GetString("CommandArgs.Vote.Id")]), new("Ask", "{number1} {number2}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, AskCommand, true, true, [GetString("CommandArgs.Ask.Number1"), GetString("CommandArgs.Ask.Number2")]), new("Answer", "{number}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, AnswerCommand, true, false, [GetString("CommandArgs.Answer.Number")]), new("QA", "{letter}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, QACommand, true, false, [GetString("CommandArgs.QA.Letter")]), new("QS", "", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, QSCommand, true, false), new("Target", "{id}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, TargetCommand, true, true, [GetString("CommandArgs.Target.Id")]), new("Chat", "{message}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, ChatCommand, true, true, [GetString("CommandArgs.Chat.Message")]), new("Check", "{id} {role}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, CheckCommand, true, true, [GetString("CommandArgs.Check.Id"), GetString("CommandArgs.Check.Role")]), new("Ban", "{id} [reason]", Command.UsageLevels.HostOrModerator, Command.UsageTimes.Always, BanKickCommand, true, false, [GetString("CommandArgs.Ban.Id"), GetString("CommandArgs.Ban.Reason")]), new("Exe", "{id}", Command.UsageLevels.HostOrAdmin, Command.UsageTimes.Always, ExeCommand, true, false, [GetString("CommandArgs.Exe.Id")]), new("Kill", "{id}", Command.UsageLevels.Host, Command.UsageTimes.Always, KillCommand, true, false, [GetString("CommandArgs.Kill.Id")]), new("Colour", "{color}", Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, ColorCommand, true, false, [GetString("CommandArgs.Colour.Color")]), new("ID", "", Command.UsageLevels.Everyone, Command.UsageTimes.Always, IDCommand, true, false), new("ChangeRole", "{role}", Command.UsageLevels.Host, Command.UsageTimes.InGame, ChangeRoleCommand, true, false, [GetString("CommandArgs.ChangeRole.Role")]), new("End", "", Command.UsageLevels.HostOrAdmin, Command.UsageTimes.InGame, EndCommand, true, false), new("CosID", "", Command.UsageLevels.Modded, Command.UsageTimes.Always, CosIDCommand, true, false), new("MTHY", "", Command.UsageLevels.Host, Command.UsageTimes.InGame, MTHYCommand, true, false), new("CSD", "{sound}", Command.UsageLevels.Modded, Command.UsageTimes.Always, CSDCommand, true, false, [GetString("CommandArgs.CSD.Sound")]), new("SD", "{sound}", Command.UsageLevels.Modded, Command.UsageTimes.Always, SDCommand, true, false, [GetString("CommandArgs.SD.Sound")]), new("GNO", "{number}", Command.UsageLevels.Everyone, Command.UsageTimes.AfterDeathOrLobby, GNOCommand, true, false, [GetString("CommandArgs.GNO.Number")]), new("Poll", "{question} {answerA} {answerB} [answerC] [answerD]", Command.UsageLevels.HostOrModerator, Command.UsageTimes.Always, PollCommand, true, false, [GetString("CommandArgs.Poll.Question"), GetString("CommandArgs.Poll.AnswerA"), GetString("CommandArgs.Poll.AnswerB"), GetString("CommandArgs.Poll.AnswerC"), GetString("CommandArgs.Poll.AnswerD")]), new("PV", "{vote}", Command.UsageLevels.Everyone, Command.UsageTimes.Always, PVCommand, false, false, [GetString("CommandArgs.PV.Vote")]), new("HM", "{id}", Command.UsageLevels.Everyone, Command.UsageTimes.AfterDeath, HMCommand, true, false, [GetString("CommandArgs.HM.Id")]), new("Decree", "{number}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, DecreeCommand, true, true, [GetString("CommandArgs.Decree.Number")]), new("AddVIP", "{id}", Command.UsageLevels.Host, Command.UsageTimes.Always, AddVIPCommand, true, false, [GetString("CommandArgs.AddVIP.Id")]), new("DeleteVIP", "{id}", Command.UsageLevels.Host, Command.UsageTimes.Always, DeleteVIPCommand, true, false, [GetString("CommandArgs.DeleteVIP.Id")]), new("Assume", "{id} {number}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, AssumeCommand, true, true, [GetString("CommandArgs.Assume.Id"), GetString("CommandArgs.Assume.Number")]), new("Note", "{action} [?]", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, NoteCommand, true, true, [GetString("CommandArgs.Note.Action"), GetString("CommandArgs.Note.UnknownValue")]), new("OS", "{chance} {role}", Command.UsageLevels.HostOrAdmin, Command.UsageTimes.InLobby, OSCommand, true, false, [GetString("CommandArgs.OS.Chance"), GetString("CommandArgs.OS.Role")]), new("Negotiation", "{number}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, NegotiationCommand, true, false, [GetString("CommandArgs.Negotiation.Number")]), new("Mute", "{id} [duration]", Command.UsageLevels.HostOrModerator, Command.UsageTimes.AfterDeathOrLobby, MuteCommand, true, false, [GetString("CommandArgs.Mute.Id"), GetString("CommandArgs.Mute.Duration")]), new("Unmute", "{id}", Command.UsageLevels.HostOrAdmin, Command.UsageTimes.Always, UnmuteCommand, true, false, [GetString("CommandArgs.Unmute.Id")]), new("DraftStart", "", Command.UsageLevels.HostOrModerator, Command.UsageTimes.InLobby, DraftStartCommand, true, false), new("DraftDescription", "{index}", Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, DraftDescriptionCommand, false, false, [GetString("CommandArgs.DraftDescription.Index")]), new("Draft", "{number}", Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, DraftCommand, false, false, [GetString("CommandArgs.Draft.Number")]), new("ReadyCheck", "", Command.UsageLevels.HostOrModerator, Command.UsageTimes.InLobby, ReadyCheckCommand, true, false), new("Ready", "", Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, ReadyCommand, true, false), new("EnableAllRoles", "", Command.UsageLevels.Host, Command.UsageTimes.InLobby, EnableAllRolesCommand, true, false), new("Achievements", "", Command.UsageLevels.Modded, Command.UsageTimes.Always, AchievementsCommand, true, false), new("DeathNote", "{name}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, DeathNoteCommand, true, true, [GetString("CommandArgs.DeathNote.Name")]), new("Whisper", "{ids} {message}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, WhisperCommand, true, true, [GetString("CommandArgs.Whisper.Ids"), GetString("CommandArgs.Whisper.Message")]), new("HWhisper", "{id} {message}", Command.UsageLevels.Host, Command.UsageTimes.Always, HWhisperCommand, true, false, [GetString("CommandArgs.HWhisper.Id"), GetString("CommandArgs.HWhisper.Message")]), new("Spectate", "[id]", Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, SpectateCommand, false, false, [GetString("CommandArgs.Spectate.Id")]), new("Anagram", "", Command.UsageLevels.Everyone, Command.UsageTimes.AfterDeathOrLobby, AnagramCommand, true, false), new("RoleList", "", Command.UsageLevels.Everyone, Command.UsageTimes.Always, RoleListCommand, true, false), new("JailTalk", "{message}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, JailTalkCommand, true, true, [GetString("CommandArgs.JailTalk.Message")]), new("GameModeList", "", Command.UsageLevels.Everyone, Command.UsageTimes.Always, GameModeListCommand, true, false), new("GameModePoll", "", Command.UsageLevels.HostOrModerator, Command.UsageTimes.InLobby, GameModePollCommand, true, false), new("MapPoll", "", Command.UsageLevels.HostOrModerator, Command.UsageTimes.InLobby, MapPollCommand, true, false), new("PresetPoll", "", Command.UsageLevels.HostOrModerator, Command.UsageTimes.InLobby, PresetPollCommand, true, false), new("ChangePreset", "{preset}", Command.UsageLevels.HostOrModerator, Command.UsageTimes.InLobby, ChangePresetCommand, true, false, [GetString("CommandArgs.ChangePreset.Preset")]), new("EightBall", "[question]", Command.UsageLevels.Everyone, Command.UsageTimes.Always, EightBallCommand, false, false, [GetString("CommandArgs.EightBall.Question")]), new("AddTag", "{id} {color} {tag}", Command.UsageLevels.Host, Command.UsageTimes.Always, AddTagCommand, true, false, [GetString("CommandArgs.AddTag.Id"), GetString("CommandArgs.AddTag.Color"), GetString("CommandArgs.AddTag.Tag")]), new("DeleteTag", "{id}", Command.UsageLevels.Host, Command.UsageTimes.InLobby, DeleteTagCommand, true, false, [GetString("CommandArgs.DeleteTag.Id")]), new("DayBreak", "", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, DayBreakCommand, true, true), new("Fix", "{id}", Command.UsageLevels.HostOrModerator, Command.UsageTimes.InGame, FixCommand, true, false, [GetString("CommandArgs.Fix.Id")]), new("XOR", "{role} {role}", Command.UsageLevels.Everyone, Command.UsageTimes.Always, XORCommand, true, false, [GetString("CommandArgs.XOR.Role"), GetString("CommandArgs.XOR.Role")]), new("ChemistInfo", "", Command.UsageLevels.Everyone, Command.UsageTimes.Always, ChemistInfoCommand, true, false), new("Forge", "{id} {role}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, ForgeCommand, true, true, [GetString("CommandArgs.Forge.Id"), GetString("CommandArgs.Forge.Role")]), new("Choose", "{role}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, ChooseCommand, true, true, [GetString("CommandArgs.Choose.Role")]), new("CopyPreset", "{sourcepreset} {targetpreset}", Command.UsageLevels.Host, Command.UsageTimes.InLobby, CopyPresetCommand, true, false, [GetString("CommandArgs.CopyPreset.SourcePreset"), GetString("CommandArgs.CopyPreset.TargetPreset")]), new("AddAdmin", "{id}", Command.UsageLevels.Host, Command.UsageTimes.Always, AddAdminCommand, true, false, [GetString("CommandArgs.AddAdmin.Id")]), new("DeleteAdmin", "{id}", Command.UsageLevels.Host, Command.UsageTimes.Always, DeleteAdminCommand, true, false, [GetString("CommandArgs.DeleteAdmin.Id")]), new("VoteStart", "", Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, VoteStartCommand, true, false), new("Imitate", "{id}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, ImitateCommand, true, true, [GetString("CommandArgs.Imitate.Id")]), new("Retribute", "{id}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, RetributeCommand, true, true, [GetString("CommandArgs.Retribute.Id")]), new("Revive", "{id}", Command.UsageLevels.Host, Command.UsageTimes.InGame, ReviveCommand, true, false, [GetString("CommandArgs.Revive.Id")]), new("Select", "{id} {role}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, SelectCommand, true, true, [GetString("CommandArgs.Select.Id"), GetString("CommandArgs.Select.Role")]), new("UIScale", "{scale}", Command.UsageLevels.Modded, Command.UsageTimes.Always, UIScaleCommand, true, false, [GetString("CommandArgs.UIScale.Scale")]), new("Fabricate", "{deathreason}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, FabricateCommand, true, true, [GetString("CommandArgs.Fabricate.DeathReason")]), new("Start", "", Command.UsageLevels.HostOrModerator, Command.UsageTimes.InLobby, StartCommand, false, false), new("Summon", "{id}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, SummonCommand, true, true, [GetString("CommandArgs.Summon.Id")]), new("CovenInfo", "", Command.UsageLevels.Everyone, Command.UsageTimes.Always, CovenInfoCommand, true, false), new("NeutralInfo", "", Command.UsageLevels.Everyone, Command.UsageTimes.Always, NeutralInfoCommand, true, false), new("PlayerInfo", "[id]", Command.UsageLevels.Everyone, Command.UsageTimes.Always, PlayerInfoCommand, true, false, [GetString("CommandArgs.PlayerInfo.Id")]), new("TimeLimit", "", Command.UsageLevels.Everyone, Command.UsageTimes.InGame, TimeLimitCommand, true, false), new("Exo", "", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, ExoCommand, true, true), new("Reroll", "", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, RerollCommand, true, true), new("ConfirmAuth", "{uuid}", Command.UsageLevels.Everyone, Command.UsageTimes.Always, ConfirmAuthCommand, true, false, [GetString("CommandArgs.ConfirmAuth.UUID")]), // Commands with action handled elsewhere new("Guess", "{id} {role}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _) => { }, true, false, [GetString("CommandArgs.Guess.Id"), GetString("CommandArgs.Guess.Role")]), new("Trial", "{id}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _) => { }, true, false, [GetString("CommandArgs.Trial.Id")]), new("Swap", "{id}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _) => { }, true, false, [GetString("CommandArgs.Swap.Id")]), new("Compare", "{id1} {id2}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _) => { }, true, false, [GetString("CommandArgs.Compare.Id1"), GetString("CommandArgs.Compare.Id2")]), new("Medium", "{answer}", Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _) => { }, true, false, [GetString("CommandArgs.Medium.Answer")]), new("Revenge", "{id}", Command.UsageLevels.Everyone, Command.UsageTimes.AfterDeath, (_, _, _) => { }, true, false, [GetString("CommandArgs.Revenge.Id")]) ]; } private static string[] ModsFileCache = []; private static string[] VIPsFileCache = []; private static string[] AdminsFileCache = []; private static long LastModFileUpdate; private static long LastVIPFileUpdate; private static long LastAdminFileUpdate; // Function to check if a Player is Moderator public static bool IsPlayerModerator(string friendCode) { if (IsPlayerAdmin(friendCode)) return true; friendCode = friendCode.Replace(':', '#'); if (friendCode == "" || friendCode == string.Empty || !Options.ApplyModeratorList.GetBool()) return false; if (Main.UserData.TryGetValue(friendCode, out Options.UserData userData) && userData.Moderator) return true; long now = Utils.TimeStamp; string[] friendCodes; if (LastModFileUpdate + 5 > now) friendCodes = ModsFileCache; else { var friendCodesFilePath = $"{Main.DataPath}/EHR_DATA/Moderators.txt"; if (!File.Exists(friendCodesFilePath)) { File.WriteAllText(friendCodesFilePath, string.Empty); return false; } friendCodes = ModsFileCache = File.ReadAllLines(friendCodesFilePath); LastModFileUpdate = now; } return friendCodes.Any(code => code.Contains(friendCode, StringComparison.OrdinalIgnoreCase)); } // Function to check if a player is a VIP public static bool IsPlayerVIP(string friendCode) { if (IsPlayerModerator(friendCode)) return true; friendCode = friendCode.Replace(':', '#'); if (friendCode == "" || friendCode == string.Empty || !Options.ApplyVIPList.GetBool()) return false; if (Main.UserData.TryGetValue(friendCode, out Options.UserData userData) && userData.Vip) return true; long now = Utils.TimeStamp; string[] friendCodes; if (LastVIPFileUpdate + 5 > now) friendCodes = VIPsFileCache; else { var friendCodesFilePath = $"{Main.DataPath}/EHR_DATA/VIPs.txt"; if (!File.Exists(friendCodesFilePath)) { File.WriteAllText(friendCodesFilePath, string.Empty); return false; } friendCodes = VIPsFileCache = File.ReadAllLines(friendCodesFilePath); LastVIPFileUpdate = now; } return friendCodes.Any(code => code.Contains(friendCode, StringComparison.OrdinalIgnoreCase)); } // Function to check if a player is an Admin public static bool IsPlayerAdmin(string friendCode) { friendCode = friendCode.Replace(':', '#'); if (friendCode == "" || friendCode == string.Empty || !Options.ApplyAdminList.GetBool()) return false; if (Main.UserData.TryGetValue(friendCode, out Options.UserData userData) && userData.Admin) return true; long now = Utils.TimeStamp; string[] friendCodes; if (LastAdminFileUpdate + 5 > now) friendCodes = AdminsFileCache; else { var friendCodesFilePath = $"{Main.DataPath}/EHR_DATA/Admins.txt"; if (!File.Exists(friendCodesFilePath)) { File.WriteAllText(friendCodesFilePath, string.Empty); return false; } friendCodes = AdminsFileCache = File.ReadAllLines(friendCodesFilePath); LastAdminFileUpdate = now; } return friendCodes.Any(code => code.Contains(friendCode, StringComparison.OrdinalIgnoreCase)); } public static bool Prefix(ChatController __instance) { if (__instance.quickChatField.visible) return true; __instance.freeChatField.textArea.text = __instance.freeChatField.textArea.text.Replace("\b", string.Empty).Replace("\r", string.Empty).Replace(" Main.PlayerStates.Values.DoIf(x => !x.IsDead && x.Role is Astral { Timer: not null } && x.Player, x => ChatManager.ClearChat(x.Player)), 0.2f, log: false); if (CheckMute(PlayerControl.LocalPlayer.PlayerId)) goto Canceled; if (string.IsNullOrWhiteSpace(text)) goto Canceled; goto Skip; Canceled: Main.IsChatCommand = false; canceled = true; Skip: if (ExileController.Instance) canceled = true; if (canceled) { Logger.Info("Command Canceled", "ChatCommand"); __instance.freeChatField.textArea.Clear(); __instance.freeChatField.textArea.SetText(cancelVal); } else if (GameStates.IsLobby && AmongUsClient.Instance.AmHost) { long now = Utils.TimeStamp; if (LastSetNameInLobby + 3 < now) { Utils.ApplySuffix(PlayerControl.LocalPlayer, out string name); PlayerControl.LocalPlayer.RpcSetName(name); LastSetNameInLobby = now; } } if (text.Contains("666") && PlayerControl.LocalPlayer.Is(CustomRoles.Demon)) Achievements.Type.WhatTheHell.Complete(); if (!canceled && AmongUsClient.Instance.AmHost && Utils.TempReviveHostRunning) { if (!WaitingToSend) Main.Instance.StartCoroutine(Wait()); return false; IEnumerator Wait() { WaitingToSend = true; while (Utils.TempReviveHostRunning && AmongUsClient.Instance.AmHost) yield return null; yield return new WaitForSecondsRealtime(0.5f); WaitingToSend = false; if (GameStates.IsEnded || GameStates.IsLobby) yield break; if (HudManager.InstanceExists) HudManager.Instance.Chat.SendChat(); } } if (!canceled) ChatManager.SendMessage(PlayerControl.LocalPlayer, text); return !canceled; } private static void CheckAnagramGuess(byte id, string text) { if (CurrentAnagram != string.Empty && text.Contains(CurrentAnagram)) { Utils.SendMessage("\n", title: string.Format(GetString("Anagram.CorrectGuess"), id.ColoredPlayerName(), CurrentAnagram), importance: MessageImportance.High); CurrentAnagram = string.Empty; } } public static void RequestCommandProcessingFromHost(string text, string commandKey) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestCommandProcessing, SendOption.Reliable, AmongUsClient.Instance.HostId); writer.Write(commandKey); writer.Write(text); AmongUsClient.Instance.FinishRpcImmediately(writer); } // --------------------------------------------------------------------------------------------------------------------------------------------- public static void ExoCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (!Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) || state.IsDead || state.MainRole != CustomRoles.Exorcist || player.GetAbilityUseLimit() < 1) return; int duration = Exorcist.AbilityDuration.GetInt(); Exorcist.AbilityEndTS = Utils.TimeStamp + duration; player.RpcRemoveAbilityUse(); Utils.SendMessage(string.Format(GetString("Exorcist.AbilityUsedMsg"), duration), title: CustomRoles.Exorcist.ToColoredString()); LateTask.New(() => { if (!GameStates.IsMeeting) return; Utils.SendMessage(GetString("Exorcist.AbilityEnded"), title: CustomRoles.Exorcist.ToColoredString()); }, duration); MeetingManager.SendCommandUsedMessage(args[0]); } private static void RerollCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (!Reroll.TryQueueCommandTrigger(player)) return; MeetingManager.SendCommandUsedMessage(args[0]); } private static void TimeLimitCommand(PlayerControl player, string text, string[] args) { Utils.SendMessage("\n", player.PlayerId, Options.EnableGameTimeLimit.GetBool() ? $"{Options.GameTimeLimit.GetInt() - Main.GameTimer.Elapsed.TotalSeconds:N0}s {GetString("RemainingText.Suffix")}" : "∞"); } private static void PlayerInfoCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2) { Utils.SendMessage("" + string.Join('\n', Main.CachedAllPlayerControls().Select(x => { ClientData client = x.GetClient(); string name = Main.AllPlayerNames.GetValueOrDefault(x.PlayerId, string.Empty); string id = string.IsNullOrEmpty(name) ? $"ID {x.PlayerId}" : $" (ID {x.PlayerId})"; return $"{name}{id} - {x.FriendCode} | {client?.GetHashedPuid()} | {client?.PlatformData.Platform}"; })) + "", player.PlayerId); } else if (byte.TryParse(args[1], out byte playerId)) { PlayerControl pc = playerId.GetPlayer(); if (!pc) return; ClientData client = pc.GetClient(); string name = Main.AllPlayerNames.GetValueOrDefault(pc.PlayerId, string.Empty); string id = string.IsNullOrEmpty(name) ? $"ID {pc.PlayerId}" : $" (ID {pc.PlayerId})"; Utils.SendMessage($"{name}{id}:\n{pc.FriendCode}\n{client?.GetHashedPuid()}\n{client?.PlatformData.Platform}", player.PlayerId); } } private static void NeutralInfoCommand(PlayerControl player, string text, string[] args) { Utils.SendMessage(GetString("NeutralInfoDescription"), player.PlayerId); } private static void CovenInfoCommand(PlayerControl player, string text, string[] args) { Utils.SendMessage(GetString("CovenInfoDescription"), player.PlayerId); } public static void SummonCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (!Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) || state.IsDead || state.Role is not Summoner sum || player.GetAbilityUseLimit() < 1) return; if (args.Length < 2 || !byte.TryParse(args[1], out byte targetId) || !Main.PlayerStates.TryGetValue(targetId, out var targetState) || !targetState.IsDead || targetState.MainRole == CustomRoles.GM) return; Summoner.AlreadySummoned ??= []; bool reSummoned = !Summoner.AlreadySummoned.Add(targetId); if (reSummoned && !Summoner.AllowSummoningTheSamePlayerTwice.GetBool()) { Utils.SendMessage("\n", player.PlayerId, GetString("Summoner.CantSummonSamePlayerTwice")); return; } sum.SummonedPlayerId = targetId; if (!reSummoned || Summoner.ReSummonTakesAbilityUse.GetBool()) player.RpcRemoveAbilityUse(); Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("Summoner.SummonSuccessMessage"), targetId.ColoredPlayerName())); MeetingManager.SendCommandUsedMessage(args[0]); } private static void StartCommand(PlayerControl player, string text, string[] args) { VotedToStart.UnionWith(Main.EnumeratePlayerControls().Select(x => x.PlayerId)); } private static void FabricateCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (!Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) || state.IsDead || state.Role is not Fabricator fab) return; if (args.Length < 2 || !PlayerState.AllDeathReason.FindFirst(x => GetString($"DeathReason.{x}").Replace(" ", string.Empty).Equals(args[1].Replace(" ", string.Empty), StringComparison.OrdinalIgnoreCase), out PlayerState.DeathReason newDeathReason)) { Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("Fabricator.InvalidDeathReason"), args.Length >= 2 ? args[1] : "")); return; } fab.NextDeathReason = newDeathReason; Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("Fabricator.SetDeathReason"), GetString($"DeathReason.{newDeathReason}"))); MeetingManager.SendCommandUsedMessage(args[0]); } private static void UIScaleCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !float.TryParse(args[1], out float scale) || scale == 0f) return; HudManagerStartPatch.TryResizeUI(scale); } public static void SelectCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (!Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) || state.IsDead || state.Role is not Loner loner || loner.Done) return; if (args.Length < 3 || !GuessManager.MsgToPlayerAndRole(text[7..], out byte targetId, out CustomRoles pickedRole, out _) || targetId == player.PlayerId) return; if (!pickedRole.IsImpostor() || pickedRole.IsVanilla() || CustomRoleSelector.RoleResult.ContainsValue(pickedRole) || pickedRole.GetMode() == 0) return; if (!Main.PlayerStates.TryGetValue(targetId, out PlayerState ts) || ts.IsDead) return; loner.PickedPlayer = targetId; loner.PickedRole = pickedRole; Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("Loner.Picked"), targetId.ColoredPlayerName(), pickedRole.ToColoredString())); MeetingManager.SendCommandUsedMessage(args[0]); } private static void ReviveCommand(PlayerControl player, string text, string[] args) { if ((!Options.NoGameEnd.GetBool() && !player.FriendCode.GetDevUser().up) || args.Length < 2 || !byte.TryParse(args[1], out byte targetId)) return; PlayerControl target = Utils.GetPlayerById(targetId); if (target == null) return; target.RpcRevive(); } private static void ConfirmAuthCommand(PlayerControl player, string text, string[] args) { if (GameStates.CurrentServerType != GameStates.ServerType.Vanilla) { Utils.SendMessage("\n", player.PlayerId, GetString("ConfirmAuth.ErrorNotVanilla")); return; } if (!Options.PostLobbyCodeToEHRWebsite.GetBool()) { Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("ConfirmAuth.ErrorLobbyUnlisted"), GetString("PostLobbyCodeToEHRDiscordServer"))); return; } if (args.Length >= 2) { string uuid = args[1].Trim(); if (string.IsNullOrWhiteSpace(uuid)) return; Main.Instance.StartCoroutine(SendVerificationCoroutine(uuid)); } return; IEnumerator SendVerificationCoroutine(string uuid) { string friendCode = player.FriendCode; string puid = player.GetClient().ProductUserId; var gameId = AmongUsClient.Instance.GameId.ToString(); if (string.IsNullOrWhiteSpace(friendCode) || string.IsNullOrWhiteSpace(puid)) { Logger.Error($" Missing friendcode/puid for player {player.PlayerId}", "ConfirmAuth"); yield break; } if (string.IsNullOrWhiteSpace(gameId) || gameId == "32") { Logger.Error(" Invalid GameId", "ConfirmAuth"); yield break; } var json = $"{{\"uuid\":\"{uuid}\",\"friend_code\":\"{friendCode}\",\"puid\":\"{puid}\",\"game_id\":\"{gameId}\"}}"; byte[] bodyRaw = Encoding.UTF8.GetBytes(json); var uwr = new UnityWebRequest("https://app.gurge44.eu/api/verify_ingame", "POST") { uploadHandler = new UploadHandlerRaw(bodyRaw), downloadHandler = new DownloadHandlerBuffer() }; uwr.SetRequestHeader("Content-Type", "application/json"); uwr.SetRequestHeader("User-Agent", $"{Main.ModName} v{Main.PluginVersion}"); uwr.timeout = 10; // seconds yield return uwr.SendWebRequest(); if (uwr.result != UnityWebRequest.Result.Success) { Logger.Error($" HTTP error sending verification: {uwr.error} (code {(int)uwr.responseCode})", "ConfirmAuth"); Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("ConfirmAuth.Error"), uwr.error)); } else { Logger.Msg($" Sent ok. Resp code {(int)uwr.responseCode}. Body: {uwr.downloadHandler?.text}", "ConfirmAuth"); Utils.SendMessage("\n", player.PlayerId, GetString("ConfirmAuth.Success")); } } } public static void RetributeCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (!player.IsAlive() || !Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) || state.Role is not Retributionist { Notified: true } rb || rb.Camping == byte.MaxValue) return; PlayerControl campTarget = Utils.GetPlayerById(rb.Camping); if (campTarget == null || campTarget.IsAlive() || !Main.PlayerStates.TryGetValue(campTarget.PlayerId, out PlayerState campState)) return; if (args.Length < 2 || !byte.TryParse(args[1], out byte targetId)) return; byte realKiller = campState.GetRealKiller(); if (realKiller != targetId) { rb.Notified = false; RPC.PlaySoundRPC(player.PlayerId, Sounds.SabotageSound); Utils.SendMessage("\n", player.PlayerId, GetString("Retributionist.Fail"), importance: MessageImportance.High); } else { PlayerControl killer = Utils.GetPlayerById(realKiller); if (killer == null || !killer.IsAlive()) { rb.Notified = false; Utils.SendMessage("\n", player.PlayerId, GetString("Retributionist.KillerDead")); } else if (!killer.Is(CustomRoles.Pestilence)) { killer.SetRealKiller(player); Main.PlayerStates[killer.PlayerId].deathReason = PlayerState.DeathReason.Retribution; Medic.IsDead(killer); killer.RpcGuesserMurderPlayer(); Utils.AfterPlayerDeathTasks(killer, true); Utils.SendMessage("\n", title: CustomRoles.Retributionist.ColoredTextByRole(string.Format(GetString("Retributionist.SuccessOthers"), targetId.ColoredPlayerName(), CustomRoles.Retributionist.ToColoredString())), importance: MessageImportance.High); Utils.SendMessage("\n", player.PlayerId, GetString("Retributionist.Success")); } } MeetingManager.SendCommandUsedMessage(args[0]); } public static void ImitateCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (Imitator.PlayerIdList == null || !Imitator.PlayerIdList.Contains(player.PlayerId) || !player.IsAlive() || args.Length < 2 || !byte.TryParse(args[1], out byte targetId) || !Main.PlayerStates.TryGetValue(targetId, out PlayerState targetState)) return; if (!targetState.IsDead) { RPC.PlaySoundRPC(player.PlayerId, Sounds.SabotageSound); Utils.SendMessage("\n", player.PlayerId, GetString("Imitator.TargetMustBeDead")); return; } if (!targetState.MainRole.Is(Team.Crewmate) || targetState.MainRole == CustomRoles.GM) { RPC.PlaySoundRPC(player.PlayerId, Sounds.SabotageSound); Utils.SendMessage("\n", player.PlayerId, GetString("Imitator.TargetMustBeCrew"), importance: MessageImportance.High); return; } Imitator.ImitatingRole ??= []; Imitator.ImitatingRole[player.PlayerId] = targetState.MainRole; RPC.PlaySoundRPC(player.PlayerId, Sounds.TaskComplete); Logger.Info($"{player.GetRealName()} will be imitating as {targetState.MainRole}", "Imitator"); Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("Imitator.Success"), targetId.ColoredPlayerName()), importance: MessageImportance.High); MeetingManager.SendCommandUsedMessage(args[0]); } private static void VoteStartCommand(PlayerControl player, string text, string[] args) { if (Options.DisableVoteStartCommand.GetBool()) { Utils.SendMessage("\n", player.PlayerId, GetString("VoteStartDisabled")); return; } if (VotedToStart.Add(player.PlayerId)) { int voteCount = VotedToStart.Count; int playerCount = PlayerControl.AllPlayerControls.Count; var percentage = (int)Math.Round(voteCount / (float)playerCount * 100f); var required = (int)Math.Ceiling(playerCount / 2f); Utils.SendMessage(string.Format(GetString("VotedToStart"), voteCount, playerCount, percentage, required), title: string.Format(GetString("VotedToStart.Title"), player.PlayerId.ColoredPlayerName())); } } private static void DeleteAdminCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !byte.TryParse(args[1], out byte remAdminId)) return; PlayerControl remAdminPc = Utils.GetPlayerById(remAdminId); if (remAdminPc == null) return; string remFc = remAdminPc.FriendCode.Replace(':', '#'); if (!IsPlayerAdmin(remFc)) { Utils.SendMessage(GetString("PlayerNotAdmin"), player.PlayerId); return; } File.WriteAllLines($"{Main.DataPath}/EHR_DATA/Admins.txt", File.ReadAllLines($"{Main.DataPath}/EHR_DATA/Admins.txt").Where(x => !x.Contains(remFc))); Utils.SendMessage(GetString("PlayerRemovedFromAdminList"), player.PlayerId); } private static void AddAdminCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !byte.TryParse(args[1], out byte newAdminId)) return; PlayerControl newAdminPc = Utils.GetPlayerById(newAdminId); if (newAdminPc == null) return; string fc = newAdminPc.FriendCode.Replace(':', '#'); if (IsPlayerModerator(fc)) Utils.SendMessage(GetString("PlayerAlreadyAdmin"), player.PlayerId); File.AppendAllText($"{Main.DataPath}/EHR_DATA/Admins.txt", $"\n{fc}"); Utils.SendMessage(GetString("PlayerAddedToAdminList"), player.PlayerId); } private static void CopyPresetCommand(PlayerControl player, string text, string[] args) { if (args.Length < 3 || !int.TryParse(args[1], out int sourcePresetId) || sourcePresetId is < 1 or > 20 || (!int.TryParse(args[2], out int targetPreset) && targetPreset is < 1 or > 20)) return; Prompt.Show(string.Format(GetString("Promt.CopyPreset"), sourcePresetId, targetPreset), Copy, () => { }); return; void Copy() { sourcePresetId--; targetPreset--; foreach (OptionItem optionItem in OptionItem.AllOptions) { if (optionItem.IsSingleValue) continue; optionItem.AllValues[targetPreset] = optionItem.AllValues[sourcePresetId]; } OptionItem.SyncAllOptions(); OptionSaver.Save(); } } public static void ChooseCommand(PlayerControl player, string text, string[] args) { if (!Main.PlayerStates.TryGetValue(player.PlayerId, out var state) || state.IsDead || state.Role is not Pawn pawn) return; if (args.Length < 2 || !GetRoleByName(string.Join(' ', args[1..]), out var role) || !role.IsEnable()) { Utils.SendMessage("\n", player.PlayerId, GetString("PawnChooseFail")); return; } pawn.ChosenRole = role; Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("PawnChosenRole"), role.ToColoredString())); MeetingManager.SendCommandUsedMessage(args[0]); } public static void ForgeCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (!player.IsAlive() || !player.Is(CustomRoles.Forger) || player.GetAbilityUseLimit() < 1) return; if (args.Length < 3 || !GuessManager.MsgToPlayerAndRole(text[6..], out byte targetId, out CustomRoles forgeRole, out _)) return; player.RpcRemoveAbilityUse(); Forger.Forges[targetId] = forgeRole; Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("ForgeSuccess"), (int)Math.Round(player.GetAbilityUseLimit(), 1), targetId.ColoredPlayerName(), forgeRole.ToColoredString())); MeetingManager.SendCommandUsedMessage(args[0]); } private static void ChemistInfoCommand(PlayerControl player, string text, string[] args) { Utils.SendMessage(Chemist.GetProcessesInfo(), player.PlayerId, CustomRoles.Chemist.ToColoredString()); } private static void XORCommand(PlayerControl player, string text, string[] args) { if ((!player.IsHost() && !IsPlayerAdmin(player.FriendCode)) || args.Length < 3 || !GetRoleByName(args[1], out CustomRoles role1) || !GetRoleByName(args[2], out CustomRoles role2)) { Utils.SendMessage(string.Join('\n', Main.XORRoles.ConvertAll(x => $"{x.Item1.ToColoredString()} ⊕ {x.Item2.ToColoredString()}")), player.PlayerId, GetString("XORListTitle")); return; } if (Main.XORRoles.Remove((role1, role2)) || Main.XORRoles.Remove((role2, role1))) { Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("XORRemoved"), role1.ToColoredString(), role2.ToColoredString())); return; } if (role1 == role2) { Utils.SendMessage("\n", player.PlayerId, GetString("XORSameRole")); return; } if (role1.IsAdditionRole() || role2.IsAdditionRole()) { Utils.SendMessage("\n", player.PlayerId, GetString("XORAdditionRole")); return; } Main.XORRoles.Add((role1, role2)); Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("XORAdded"), role1.ToColoredString(), role2.ToColoredString())); } private static void FixCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !byte.TryParse(args[1], out byte id)) return; var pc = id.GetPlayer(); if (pc == null) return; pc.FixBlackScreen(); if (Main.EnumeratePlayerControls().All(x => x.IsAlive())) Logger.SendInGame(GetString("FixBlackScreenWaitForDead"), Color.yellow); } public static void DayBreakCommand(PlayerControl player, string text, string[] args) { if (!player.IsAlive() || Main.PlayerStates[player.PlayerId].Role is not Starspawn sp || sp.HasUsedDayBreak) return; Starspawn.IsDayBreak = true; sp.HasUsedDayBreak = true; player.RPCPlayCustomSound("Line"); Utils.SendMessage("\n", title: string.Format(GetString("StarspawnUsedDayBreak"), CustomRoles.Starspawn.ToColoredString()), importance: MessageImportance.High); } private static void AddTagCommand(PlayerControl player, string text, string[] args) { if (args.Length < 4 || !byte.TryParse(args[1], out byte id)) return; PlayerControl pc = id.GetPlayer(); if (pc == null) return; Color color = ColorUtility.TryParseHtmlString($"#{args[2].ToLower()}", out Color c) ? c : Color.red; string tag = Utils.ColorString(color, string.Join(' ', args[3..]) + " "); PrivateTagManager.AddTag(pc.FriendCode, tag); Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("AddTagSuccess"), tag, id.ColoredPlayerName(), id)); } private static void DeleteTagCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !byte.TryParse(args[1], out byte id)) return; PlayerControl pc = id.GetPlayer(); if (pc == null) return; PrivateTagManager.DeleteTag(pc.FriendCode); Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("DeleteTagSuccess"), id.ColoredPlayerName())); Utils.DirtyName.Add(pc.PlayerId); } private static void EightBallCommand(PlayerControl player, string text, string[] args) { if (Options.Disable8ballCommand.GetBool()) { Utils.SendMessage("\n", player.PlayerId, GetString("EightBallDisabled"), importance: MessageImportance.Low); return; } Utils.SendMessage(GetString($"8BallResponse.{IRandom.Instance.Next(20)}"), player.IsAlive() ? byte.MaxValue : player.PlayerId, GetString("8BallResponseTitle")); } public static void GameModePollCommand(PlayerControl player, string text, string[] args) { GMPollGameModes = Main.CustomGameModeValues[..^1].Where(x => Options.GMPollGameModesSettings[x].GetBool()).ToList(); string gmNames = string.Join(' ', GMPollGameModes.Select(x => GetString(x.ToString()).Replace(' ', '_'))); var msg = $"/poll {GetString("GameModePoll.Question").TrimEnd('?')}? {gmNames}"; PollCommand(player, msg, msg.Split(' ')); } public static void MapPollCommand(PlayerControl player, string text, string[] args) { MPollMaps = Main.MapNamesValues.Where(x => Options.MPollMapsSettings[x].GetBool()).ToList(); string mNames = string.Join(' ', MPollMaps.Select(x => GetString(x.ToString()).Replace(' ', '_'))); var msg = $"/poll {GetString("MapPoll.Question").TrimEnd('?')}? {mNames}"; PollCommand(player, msg, msg.Split(' ')); } public static void PresetPollCommand(PlayerControl player, string text, string[] args) { var presetConfigs = new[] { Main.Preset1, Main.Preset2, Main.Preset3, Main.Preset4, Main.Preset5, Main.Preset6, Main.Preset7, Main.Preset8, Main.Preset9, Main.Preset10, Main.Preset11, Main.Preset12, Main.Preset13, Main.Preset14, Main.Preset15, Main.Preset16, Main.Preset17, Main.Preset18, Main.Preset19, Main.Preset20 }; string presetNames = string.Join(' ', presetConfigs.Select((cfg, i) => (cfg.Value == (string)cfg.DefaultValue ? GetString($"Preset_{i + 1}") : cfg.Value) .Replace(' ', '_'))); var msg = $"/poll {GetString("PresetPoll.Question").TrimEnd('?')}? {presetNames}"; PollCommand(player, msg, msg.Split(' ')); } private static void ChangePresetCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !int.TryParse(args[1], out int presetNum) || presetNum is < 1 or > 20) return; if (Options.Preset == null) return; Options.Preset.SetValue(presetNum - 1); Utils.SendMessage(string.Format(GetString("ChangePreset.Changed"), presetNum, Options.Preset.GetString())); } private static void GameModeListCommand(PlayerControl player, string text, string[] args) { string info = string.Join("\n\n", Main.CustomGameModeValues[1..^1] .Select(x => (GameMode: x, Color: Utils.GetRoleColorCode(CustomRoleSelector.GameModeRoles.TryGetValue(x, out CustomRoles role) ? role : x == CustomGameMode.HideAndSeek ? CustomRoles.Hider : CustomRoles.Witness, "#000000"))) .Select(x => $"<{x.Color}>{GetString($"{x.GameMode}")}\n{GetString($"ModeDescribe.{x.GameMode}").Split("\n\n")[0]}")); Utils.SendMessage(info, player.PlayerId, GetString("GameModeListTitle")); } private static void JailTalkCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2) return; Jailor jailor = Main.PlayerStates[player.PlayerId].Role as Jailor ?? Main.PlayerStates.Select(x => x.Value.Role as Jailor).FirstOrDefault(x => x != null); if (jailor == null) return; bool amJailor = Jailor.PlayerIdList.Contains(player.PlayerId); bool amJailed = player.PlayerId == jailor.JailorTarget; if (!amJailor && !amJailed) return; string title = CustomRoles.Jailor.ColoredTextByRole(GetString("JailTalkTitle")); string message = string.Join(' ', args[1..]); if (amJailor) Utils.SendMessage(message, jailor.JailorTarget, title, importance: MessageImportance.High); else Jailor.PlayerIdList.ForEach(x => Utils.SendMessage(message, x, title, importance: MessageImportance.Low)); MeetingManager.SendCommandUsedMessage(args[0]); } private static void RoleListCommand(PlayerControl player, string text, string[] args) { StringBuilder sb = new(""); Dictionary rot = Main.RoleOptionTypeValues .Without(RoleOptionType.Coven_Miscellaneous) .GroupBy(x => x.ToString().Split('_')[0]) .ToDictionary(x => Enum.Parse(x.Key), x => x.ToArray()); foreach (Team team in Main.TeamValues[1..]) { sb.Append(""); sb.Append(Utils.ColorString(team.GetColor(), GetString(team.ToString()).ToUpper())); sb.Append(""); int factionMin; int factionMax; if (Options.FactionMinMaxSettings.TryGetValue(team, out (OptionItem MinSetting, OptionItem MaxSetting) factionLimits)) { factionMin = factionLimits.MinSetting.GetInt(); factionMax = factionLimits.MaxSetting.GetInt(); } else { factionMin = Math.Max(0, Main.NormalOptions.MaxPlayers - Options.FactionMinMaxSettings[Team.Neutral].MaxSetting.GetInt() - Options.FactionMinMaxSettings[Team.Impostor].MaxSetting.GetInt() - Options.FactionMinMaxSettings[Team.Coven].MaxSetting.GetInt()); factionMax = Math.Max(0, Main.NormalOptions.MaxPlayers - Options.FactionMinMaxSettings[Team.Neutral].MinSetting.GetInt() - Options.FactionMinMaxSettings[Team.Impostor].MinSetting.GetInt() - Options.FactionMinMaxSettings[Team.Coven].MinSetting.GetInt()); } sb.Append(' '); sb.Append(factionMin); sb.Append(" - "); sb.Append(factionMax); sb.Append("\n\n"); if (team == Team.Neutral) { sb.Append(Options.MinNNKs.GetInt()); sb.Append('-'); sb.Append(Options.MaxNNKs.GetInt()); sb.Append(' '); sb.Append(GetString("NeutralNonKillingRoles")); sb.Append("\n\n"); } if (rot.TryGetValue(team, out RoleOptionType[] subCategories)) { foreach (RoleOptionType subCategory in subCategories) { if (Options.RoleSubCategoryLimits.TryGetValue(subCategory, out OptionItem[] limits) && (team == Team.Neutral || limits[0].GetBool())) { int min = limits[1].GetInt(); int max = limits[2].GetInt(); factionMin -= max; factionMax -= min; sb.Append(min); sb.Append('-'); sb.Append(max); sb.Append(' '); sb.Append(Utils.ColorString(subCategory.GetRoleOptionTypeColor(), GetString($"ROT.{subCategory}")[2..])); sb.Append('\n'); } } if (team != Team.Neutral && factionMax > 0) { sb.Append(Math.Max(0, factionMin)); sb.Append('-'); sb.Append(factionMax); sb.Append(' '); sb.Append(GetString("RoleRateNoColor")); sb.Append(' '); sb.Append(GetString("Roles")); sb.Append('\n'); } sb.Append("\n\n"); } } Utils.SendMessage("\n", player.PlayerId, sb.ToString().Trim() + ""); } private static void AnagramCommand(PlayerControl player, string text, string[] args) { string langParam = GetLangParam(); int lengthIndex = Options.AnagramWordLength.GetValue(); int wordLength = lengthIndex == 0 ? 0 : lengthIndex + 1; int difficulty = Options.AnagramDifficulty.GetValue() + 1; Main.Instance.StartCoroutine(Main.GetRandomWord(CreateAnagram, langParam, wordLength, difficulty)); return; void CreateAnagram(string word) { string scrambled = new(word.ToLower().ToCharArray().Shuffle()); CurrentAnagram = word; byte sendTo = GameStates.InGame && !player.IsAlive() ? player.PlayerId : byte.MaxValue; Utils.SendMessage(string.Format(GetString("Anagram"), scrambled, word[0]), sendTo, GetString("AnagramTitle")); } static string GetLangParam() { int langIndex = Options.AnagramLanguage.GetValue(); if (langIndex == 0) { return GetUserTrueLang() switch { SupportedLangs.Spanish => "es", SupportedLangs.Italian => "it", SupportedLangs.German => "de", SupportedLangs.French => "fr", SupportedLangs.SChinese => "zh", SupportedLangs.TChinese => "zh", SupportedLangs.Brazilian => "pt-br", SupportedLangs.Portuguese => "pt-br", _ => "" }; } return langIndex switch { 2 => "es", 3 => "it", 4 => "de", 5 => "fr", 6 => "zh", 7 => "pt-br", _ => "" }; } } private static void SpectateCommand(PlayerControl player, string text, string[] args) { if (player.IsHost() && args.Length > 1 && byte.TryParse(args[1], out byte targetId)) { PlayerControl pc = targetId.GetPlayer(); if (pc == null) return; if (ForcedSpectators.Remove(targetId)) Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("SpectateCommand.RemovedForcedSpectator"), targetId.ColoredPlayerName())); if (ForcedSpectators.Add(targetId)) Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("SpectateCommand.ForcedSpectator"), targetId.ColoredPlayerName())); return; } if (Options.DisableSpectateCommand.GetBool()) { Utils.SendMessage("\n", player.PlayerId, GetString("SpectateDisabled"), importance: MessageImportance.Low); return; } if (LastSpectators.Contains(player.PlayerId)) { Utils.SendMessage("\n", player.PlayerId, GetString("SpectateCommand.WasSpectatingLastRound")); return; } if (Spectators.Remove(player.PlayerId)) { Utils.SendMessage("\n", player.PlayerId, GetString("SpectateCommand.Removed")); return; } if (Spectators.Add(player.PlayerId)) Utils.SendMessage("\n", player.PlayerId, GetString("SpectateCommand.Success")); } private static void WhisperCommand(PlayerControl player, string text, string[] args) { if (!player.IsAlive() || Silencer.ForSilencer.Contains(player.PlayerId)) return; if (Options.DisableWhisperCommand.GetBool()) { Utils.SendMessage("\n", player.PlayerId, GetString("WhisperDisabled"), importance: MessageImportance.Low); return; } if (Magistrate.CallCourtNextMeeting) { Utils.SendMessage("\n", player.PlayerId, GetString("NoWhisperDuringCourt"), importance: MessageImportance.Low); return; } if (player.Is(CustomRoles.God)) { Utils.SendMessage("\n", player.PlayerId, GetString("NoWhisperAsRole"), importance: MessageImportance.Low); return; } if (args.Length < 3) return; string coloredRole = CustomRoles.Listener.ToColoredString(); PlayerControl[] listeners = CustomRoles.Listener.IsEnable() ? Main.EnumerateAlivePlayerControls().Where(x => x.Is(CustomRoles.Listener)).ToArray() : []; string[] ids = args[1].Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); foreach (string id in ids) { if (!byte.TryParse(id, out byte targetId)) continue; PlayerState state = Main.PlayerStates[targetId]; if (state.IsDead || state.SubRoles.Contains(CustomRoles.Shy)) continue; string fromName = player.PlayerId.ColoredPlayerName(); string toName = targetId.ColoredPlayerName(); string msg = args[2..].Join(delimiter: " "); string title = string.Format(GetString("WhisperTitle"), fromName, player.PlayerId); Utils.SendMessage(msg, targetId, title, importance: MessageImportance.High); ChatUpdatePatch.LastMessages.Add((msg, targetId, title, Utils.TimeStamp)); foreach (PlayerControl listener in listeners) { if (IRandom.Instance.Next(100) >= Listener.WhisperHearChance.GetInt()) continue; string message = IRandom.Instance.Next(100) < Listener.FullMessageHearChance.GetInt() ? string.Format(GetString("Listener.FullMessage"), coloredRole, fromName, toName, msg) : string.Format(GetString("Listener.FromTo"), coloredRole, fromName, toName); Utils.SendMessage("\n", listener.PlayerId, message); if (listener.AmOwner && ++Listener.LocalPlayerHeardMessagesThisMeeting >= 3) Achievements.Type.Eavesdropper.Complete(); } } MeetingManager.SendCommandUsedMessage(args[0]); } private static void HWhisperCommand(PlayerControl player, string text, string[] args) { if (args.Length < 3 || !byte.TryParse(args[1], out byte targetId)) return; string msg = args[2..].Join(delimiter: " "); string title = string.Format(GetString("HWhisperTitle"), player.PlayerId.ColoredPlayerName()); Utils.SendMessage(msg, targetId, title, importance: MessageImportance.High); ChatUpdatePatch.LastMessages.Add((msg, targetId, title, Utils.TimeStamp)); } private static void DeathNoteCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (!player.Is(CustomRoles.NoteKiller) || args.Length < 2) return; if (!NoteKiller.CanGuess) { Utils.SendMessage(GetString("DeathNoteCommand.CanNotGuess"), player.PlayerId, importance: MessageImportance.Low); return; } string guess = args[1].ToLower(); guess = char.ToUpper(guess[0]) + guess[1..]; byte deadPlayer = NoteKiller.RealNames.GetKeyByValue(guess); if (deadPlayer == 0 && (!NoteKiller.RealNames.TryGetValue(0, out string name) || name != guess)) { NoteKiller.CanGuess = false; RPC.PlaySoundRPC(player.PlayerId, Sounds.SabotageSound); Utils.SendMessage(GetString("DeathNoteCommand.WrongName"), player.PlayerId); return; } PlayerControl pc = deadPlayer.GetPlayer(); if (pc == null || !pc.IsAlive()) { NoteKiller.CanGuess = false; Utils.SendMessage(GetString("DeathNoteCommand.PlayerNotFoundOrDead"), player.PlayerId); return; } PlayerState state = Main.PlayerStates[pc.PlayerId]; state.deathReason = PlayerState.DeathReason.Kill; state.RealKiller.ID = player.PlayerId; pc.RpcGuesserMurderPlayer(); Utils.AfterPlayerDeathTasks(pc, true); string coloredName = deadPlayer.ColoredPlayerName(); Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("DeathNoteCommand.Success"), coloredName), importance: MessageImportance.Low); Utils.SendMessage(string.Format(GetString("DeathNoteCommand.SuccessForOthers"), coloredName), importance: MessageImportance.High); NoteKiller.Kills++; if (player.AmOwner && NoteKiller.Kills >= 3) Achievements.Type.IKnowYourNames.CompleteAfterGameEnd(); MeetingManager.SendCommandUsedMessage(args[0]); } private static void AchievementsCommand(PlayerControl player, string text, string[] args) { Func ToAchievementString = x => $"{GetString($"Achievement.{x}")} - {GetString($"Achievement.{x}.Description")}"; Achievements.Type[] allAchievements = Enum.GetValues(); Achievements.Type[] union = Achievements.CompletedAchievements.Union(Achievements.WaitingAchievements).ToArray(); var completedAchievements = $"{union.Join(ToAchievementString, "\n")}"; var incompleteAchievements = $"{allAchievements.Except(union).Join(ToAchievementString, "\n")}"; Utils.SendMessage(incompleteAchievements, player.PlayerId, GetString("IncompleteAchievementsTitle")); Utils.SendMessage(completedAchievements, player.PlayerId, GetString("CompletedAchievementsTitle") + $" <#00a5ff>(<#00ffa5>{union.Length}/{allAchievements.Length})"); } private static void EnableAllRolesCommand(PlayerControl player, string text, string[] args) { Prompt.Show( GetString("Promt.EnableAllRoles"), () => Options.CustomRoleSpawnChances.Values.DoIf(x => x.GetValue() == 0, x => x.SetValue(1)), () => Utils.EnterQuickSetupRoles(false)); } public static void ReadyCheckCommand(PlayerControl player, string text, string[] args) { Utils.SendMessage(GetString("ReadyCheckMessage"), title: GetString("ReadyCheckTitle")); ReadyPlayers = [player.PlayerId]; ReadyPlayers.UnionWith(Spectators); Main.Instance.StopCoroutine(Countdown()); Main.Instance.StartCoroutine(Countdown()); return; IEnumerator Countdown() { var timer = 30f; while (timer > 0f) { if (!GameStates.IsLobby) yield break; if (Main.EnumeratePlayerControls().Select(x => x.PlayerId).All(ReadyPlayers.Contains)) break; timer -= Time.deltaTime; yield return null; } byte[] notReadyPlayers = Main.EnumeratePlayerControls().Select(x => x.PlayerId).Except(ReadyPlayers).ToArray(); if (notReadyPlayers.Length == 0) Utils.SendMessage("\n", player.PlayerId, GetString("EveryoneReadyTitle")); else Utils.SendMessage(string.Join(", ", notReadyPlayers.Select(x => x.ColoredPlayerName())), player.PlayerId, string.Format(GetString("PlayersNotReadyTitle"), notReadyPlayers.Length)); if (Spectators.Count > 0) Utils.SendMessage(string.Join(", ", Spectators.Select(x => x.ColoredPlayerName())), player.PlayerId, string.Format(GetString("SpectatorsList"), Spectators.Count)); } } private static void ReadyCommand(PlayerControl player, string text, string[] args) { ReadyPlayers.Add(player.PlayerId); } public static void DraftStartCommand(PlayerControl player, string text, string[] args) { CustomGameMode gameMode = Options.CurrentGameMode; if (gameMode is not (CustomGameMode.Standard or CustomGameMode.HideAndSeek)) return; DraftResult = []; byte[] allPlayerIds = Main.EnumeratePlayerControls().Select(x => x.PlayerId).ToArray(); int maxRolesPerPlayer = Options.DraftMaxRolesPerPlayer.GetInt(); if (gameMode == CustomGameMode.HideAndSeek) { List<(CustomRoles Role, IHideAndSeekRole Interface)> hnsRoles = CustomHnS.GetAllHnsRoleTypes().Select(x => (Role: Enum.Parse(ignoreCase: true, value: x.Name), Interface: (IHideAndSeekRole)Activator.CreateInstance(x))).Where(x => x.Role is CustomRoles.Seeker or CustomRoles.Hider || x.Role.GetMode() != 0).ToList(); Dictionary memberNum = new() { [Team.Impostor] = Main.NormalOptions.NumImpostors, [Team.Neutral] = CustomHnS.RandomNeutralsNum }; foreach ((Team team, int num) in memberNum) { var suitableRoles = hnsRoles.FindAll(x => x.Interface.Team == team); if (suitableRoles.Count == 0) continue; allPlayerIds.Where(x => !DraftRoles.ContainsKey(x)).TakeRandom(num).Do(x => DraftRoles[x] = [suitableRoles.RandomElement().Role]); } var hiderRoles = hnsRoles.FindAll(x => x.Interface.Team == Team.Crewmate); if (hiderRoles.Count == 0) return; while (true) { if (DraftRoles.Values.FindFirst(x => x.Count < maxRolesPerPlayer, out List roles)) roles.Add(hiderRoles.RandomElement().Role); else if (allPlayerIds.FindFirst(x => !DraftRoles.ContainsKey(x), out byte id)) DraftRoles[id] = [hiderRoles.RandomElement().Role]; else break; } foreach (List rolesList in DraftRoles.Values) { HashSet seen = []; rolesList.RemoveAll(r => !seen.Add(r)); } Main.Instance.StartCoroutine(RepeatedlySendMessage()); return; } bool rollSpawnChance = Options.DraftAffectedByRoleSpawnChances.GetBool(); List allRoles = Main.CustomRoleValues.Where(x => x < CustomRoles.NotAssigned && x.IsEnable() && !x.IsForOtherGameMode() && !x.IsVanilla() && x is not CustomRoles.GM && !ShouldNotSpawn(x) && (!rollSpawnChance || IRandom.Instance.Next(100) < x.GetMode())).Shuffle(); if (allRoles.Count < allPlayerIds.Length) { Utils.SendMessage(GetString("DraftNotEnoughRoles"), player.PlayerId); return; } List impRoles = allRoles.Where(x => x.IsImpostor()).TakeRandom(Options.FactionMinMaxSettings[Team.Impostor].MaxSetting.GetInt()); List nkRoles = allRoles.Where(x => x.IsNK()).TakeRandom(Math.Min(Options.FactionMinMaxSettings[Team.Neutral].MaxSetting.GetInt(), Options.RoleSubCategoryLimits[RoleOptionType.Neutral_Killing][2].GetInt())); List nnkRoles = allRoles.Where(x => x.IsNonNK()).TakeRandom(Math.Min(Options.FactionMinMaxSettings[Team.Neutral].MaxSetting.GetInt() - Options.RoleSubCategoryLimits[RoleOptionType.Neutral_Killing][2].GetInt(), Options.MaxNNKs.GetInt())); List covenRoles = allRoles.Where(x => x.IsCoven()).TakeRandom(Options.FactionMinMaxSettings[Team.Coven].MaxSetting.GetInt()); allRoles.RemoveAll(x => x.IsImpostor() || x.IsNK() || x.IsNonNK() || x.IsCoven()); DraftRoles = allRoles .Take(allPlayerIds.Length * maxRolesPerPlayer - impRoles.Count - nkRoles.Count - nnkRoles.Count - covenRoles.Count) .CombineWith(impRoles, nkRoles, nnkRoles, covenRoles) .Shuffle() .Partition(allPlayerIds.Length) .Zip(allPlayerIds) .ToDictionary(x => x.Second, x => x.First.Take(maxRolesPerPlayer).ToList()); Main.Instance.StartCoroutine(RepeatedlySendMessage()); return; IEnumerator RepeatedlySendMessage() { for (var index = 0; index < 3; index++) { if (Options.CurrentGameMode is not (CustomGameMode.Standard or CustomGameMode.HideAndSeek)) { DraftRoles = []; DraftResult = []; yield break; } List messages = []; foreach ((byte id, List roles) in DraftRoles) { if (DraftResult.ContainsKey(id)) continue; IEnumerable roleList = roles.Select((x, i) => $"{i + 1}. {x.ToColoredString()}"); string msg = string.Format(GetString(index == 0 ? "DraftStart" : "DraftResend"), string.Join('\n', roleList)); messages.Add(new Message(msg, id, GetString("DraftTitle"))); } messages.SendMultipleMessages(index == 0 ? MessageImportance.Medium : MessageImportance.Low); yield return new WaitForSecondsRealtime(20f); if (DraftResult.Count >= DraftRoles.Count || !GameStates.IsLobby || GameStates.InGame) yield break; } } static bool ShouldNotSpawn(CustomRoles role) { return role switch { CustomRoles.Weatherman when Main.LIMap || GameStates.CurrentServerType == GameStates.ServerType.Vanilla => true, CustomRoles.RoomRusher when Main.LIMap => true, CustomRoles.Doctor when Options.EveryoneSeesDeathReasons.GetBool() => true, CustomRoles.Commander when Main.NormalOptions.NumImpostors <= 1 && Commander.CannotSpawnAsSoloImp.GetBool() => true, CustomRoles.Changeling when Changeling.GetAvailableRoles(true).Count == 0 => true, _ => false }; } } private static void DraftDescriptionCommand(PlayerControl player, string text, string[] args) { CustomRoles role; try // Sometimes a System.ArgumentOutOfRangeException occurs here { if (DraftRoles.Count == 0 || !DraftRoles.TryGetValue(player.PlayerId, out List roles) || args.Length < 2 || !int.TryParse(args[1], out int chosenIndex) || chosenIndex <= 0 || roles.Count < chosenIndex) return; role = roles[chosenIndex - 1]; } catch (Exception e) { Utils.ThrowException(e); return; } string coloredString = role.ToColoredString(); string roleName = GetString(role.ToString()); StringBuilder sb = new(); StringBuilder settings = new(); var title = $"{coloredString} {Utils.GetRoleMode(role)}"; sb.Append(GetString($"{role}InfoLong").FixRoleName(role).TrimStart()); if (Options.CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem chance)) AddSettings(chance); if (role is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor && Options.CustomRoleSpawnChances.TryGetValue(CustomRoles.Lovers, out chance)) AddSettings(chance); string txt = sb.ToString().Replace(roleName, coloredString).Replace(roleName.ToLower(), coloredString); sb.Clear().Append(txt); if (settings.Length > 0) Utils.SendMessage("\n", player.PlayerId, settings.ToString()); Utils.SendMessage(sb.ToString(), player.PlayerId, title); return; void AddSettings(StringOptionItem stringOptionItem) { settings.AppendLine($"{GetString("SettingsForRoleText")} <{Utils.GetRoleColorCode(role)}>{roleName}:"); Utils.ShowChildrenSettings(stringOptionItem, settings, disableColor: false); settings.Append(""); } } private static void DraftCommand(PlayerControl player, string text, string[] args) { if (DraftRoles.Count == 0 || !DraftRoles.TryGetValue(player.PlayerId, out List roles) || args.Length < 2 || !int.TryParse(args[1], out int chosenIndex)) return; if (roles.Count < chosenIndex || chosenIndex < 1) { DraftResult.Remove(player.PlayerId); Utils.SendMessage(string.Format(GetString("DraftChosen"), GetString("pet_RANDOM_FOR_EVERYONE")), player.PlayerId, GetString("DraftTitle")); return; } CustomRoles role = roles[chosenIndex - 1]; DraftResult[player.PlayerId] = role; Utils.SendMessage(string.Format(GetString("DraftChosen"), role.ToColoredString()), player.PlayerId, GetString("DraftTitle")); if (DraftResult.Count >= DraftRoles.Count) Utils.SendMessage("\n", PlayerControl.LocalPlayer.PlayerId, GetString("EveryoneDrafted")); } private static void MuteCommand(PlayerControl player, string text, string[] args) { bool host = player.IsHost(); if (!host && (GameStates.InGame || MutedPlayers.ContainsKey(player.PlayerId))) return; if (!byte.TryParse(args[1], out byte id) || id.IsHost() || (!host && IsPlayerModerator(id.GetPlayer()?.FriendCode))) return; long now = Utils.TimeStamp; int duration = args.Length < 3 || !int.TryParse(args[2], out int dur) ? 60 : dur; MutedPlayers[id] = (now, duration, now); List messages = [ new("\n", player.PlayerId, string.Format(GetString("PlayerMuted"), id.ColoredPlayerName(), duration)), new("\n", id, string.Format(GetString("YouMuted"), player.PlayerId.ColoredPlayerName(), duration)) ]; if (!host) messages.Add(new Message("\n", 0, string.Format(GetString("ModeratorMuted"), player.PlayerId.ColoredPlayerName(), id.ColoredPlayerName(), duration))); messages.SendMultipleMessages(); } private static void UnmuteCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !byte.TryParse(args[1], out byte id)) return; MutedPlayers.Remove(id); Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("PlayerUnmuted"), id.ColoredPlayerName())); Utils.SendMessage("\n", id, string.Format(GetString("YouUnmuted"), player.PlayerId.ColoredPlayerName())); if (!player.IsHost()) Utils.SendMessage("\n", 0, string.Format(GetString("AdminUnmuted"), player.PlayerId.ColoredPlayerName(), id.ColoredPlayerName())); } private static void NegotiationCommand(PlayerControl player, string text, string[] args) { if (!Negotiator.On || !player.IsAlive() || args.Length < 2 || !int.TryParse(args[1], out int index)) return; Negotiator.ReceiveCommand(player, index); MeetingManager.SendCommandUsedMessage(args[0]); } private static void OSCommand(PlayerControl player, string text, string[] args) { if (!GameStates.IsLobby || args.Length < 3 || !byte.TryParse(args[1], out byte chance) || chance > 100 || chance % 5 != 0 || !GetRoleByName(string.Join(' ', args[2..]), out CustomRoles role) || !Options.CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem option)) return; if (role.IsAdditionRole()) { option.SetValue(chance == 0 ? 0 : 1); if (!Options.CustomAdtRoleSpawnRate.TryGetValue(role, out IntegerOptionItem adtOption)) return; adtOption.SetValue(chance / 5); } else option.SetValue(chance / 5); } private static void NoteCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (!player.Is(CustomRoles.Journalist) || !player.IsAlive()) return; Journalist.OnReceiveCommand(player, args); MeetingManager.SendCommandUsedMessage(args[0]); } private static void AssumeCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (args.Length < 3 || !byte.TryParse(args[1], out byte id) || !int.TryParse(args[2], out int num) || !player.Is(CustomRoles.Assumer) || !player.IsAlive()) return; Assumer.Assume(player.PlayerId, id, num); MeetingManager.SendCommandUsedMessage(args[0]); } private static void DeleteVIPCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !byte.TryParse(args[1], out byte VIPId)) return; PlayerControl VIPPc = Utils.GetPlayerById(VIPId); if (VIPPc == null) return; string fc = VIPPc.FriendCode.Replace(':', '#'); if (!IsPlayerVIP(fc)) Utils.SendMessage(GetString("PlayerNotVIP"), player.PlayerId); string[] lines = File.ReadAllLines($"{Main.DataPath}/EHR_DATA/VIPs.txt").Where(line => !line.Contains(fc)).ToArray(); File.WriteAllLines($"{Main.DataPath}/EHR_DATA/VIPs.txt", lines); Utils.SendMessage(GetString("PlayerRemovedFromVIPList"), player.PlayerId); } private static void AddVIPCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !byte.TryParse(args[1], out byte newVIPId)) return; PlayerControl newVIPPc = Utils.GetPlayerById(newVIPId); if (newVIPPc == null) return; string fc = newVIPPc.FriendCode.Replace(':', '#'); if (IsPlayerVIP(fc)) Utils.SendMessage(GetString("PlayerAlreadyVIP"), player.PlayerId); File.AppendAllText($"{Main.DataPath}/EHR_DATA/VIPs.txt", $"\n{fc}"); Utils.SendMessage(GetString("PlayerAddedToVIPList"), player.PlayerId); } private static void DecreeCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (!player.Is(CustomRoles.President)) return; LateTask.New(() => { if (args.Length < 2) { Utils.SendMessage(President.GetHelpMessage(), player.PlayerId, importance: MessageImportance.High); return; } President.UseDecree(player, args[1]); }, 0.2f, log: false); } private static void HMCommand(PlayerControl player, string text, string[] args) { if (!player.Is(CustomRoles.Messenger) || (Messenger.Sent != null && Messenger.Sent.Contains(player.PlayerId)) || args.Length < 2 || !int.TryParse(args[1], out int id) || id is > 3 or < 1) return; Main.Instance.StartCoroutine(SendOnMeeting()); return; IEnumerator SendOnMeeting() { bool meeting = GameStates.IsMeeting; while (!GameStates.IsMeeting && GameStates.InGame) yield return null; if (!GameStates.InGame) yield break; if (!meeting) yield return new WaitForSecondsRealtime(7f); PlayerControl killer = player.GetRealKiller(); if (!killer && id != 3) yield break; Team team = player.GetTeam(); string message = id switch { 1 => string.Format(GetString("MessengerMessage.1"), GetString((Main.PlayerStates[killer.PlayerId].LastRoom?.RoomId ?? SystemTypes.Outside).ToString())), 2 => string.Format(GetString("MessengerMessage.2"), killer.GetCustomRole().ToColoredString()), _ => string.Format(GetString("MessengerMessage.3"), Utils.ColorString(team.GetColor(), GetString($"{team}"))) }; Utils.SendMessage(message, title: string.Format(GetString("MessengerTitle"), player.PlayerId.ColoredPlayerName()), importance: MessageImportance.High); Messenger.Sent ??= []; Messenger.Sent.Add(player.PlayerId); } } // Credit: Drakos for the base code private static void PollCommand(PlayerControl player, string text, string[] args) { PollVotes.Clear(); PollAnswers.Clear(); PollVoted.Clear(); if (!args.Any(x => x.Contains('?'))) { Utils.SendMessage(GetString("PollUsage"), player.PlayerId); return; } int splitIndex = Array.IndexOf(args, args.First(x => x.Contains('?'))) + 1; string[] answers = args[splitIndex..]; string msg = string.Join(" ", args[1..splitIndex]) + "\n"; bool gmPoll = msg.Contains(GetString("GameModePoll.Question")); bool mPoll = msg.Contains(GetString("MapPoll.Question")); bool pPoll = msg.Contains(GetString("PresetPoll.Question")); if (gmPoll && GMPollGameModes.Count > 6) msg += ""; PollTimer = gmPoll ? 60f : 45f; Color[] gmPollColors = gmPoll ? Main.GameModeColors.Where(x => GMPollGameModes.Contains(x.Key)).Select(x => x.Value).ToArray() : []; for (var i = 0; i < Math.Max(answers.Length, 2); i++) { var choiceLetter = (char)(i + 65); msg += Utils.ColorString(gmPoll ? gmPollColors[i] : RandomColor(), $"{char.ToUpper(choiceLetter)}) {answers[i]}\n"); PollVotes[choiceLetter] = 0; PollAnswers[choiceLetter] = $"〖 {answers[i]} 〗"; } msg += $"\n{GetString("Poll.Begin")}\n"; string title = GetString("Poll.Title"); Utils.SendMessage(msg + $"{string.Format(GetString("Poll.TimeInfo"), (int)Math.Round(PollTimer))}", title: title); Main.Instance.StartCoroutine(StartPollCountdown()); return; IEnumerator StartPollCountdown() { if (PollVotes.Count == 0) yield break; bool notEveryoneVoted = PlayerControl.AllPlayerControls.Count - 1 > PollVotes.Values.Sum(); var resendTimer = 0f; while ((notEveryoneVoted || gmPoll || mPoll || pPoll) && PollTimer > 0f) { if (!GameStates.IsLobby) yield break; notEveryoneVoted = PlayerControl.AllPlayerControls.Count - 1 > PollVotes.Values.Sum(); PollTimer -= Time.deltaTime; resendTimer += Time.deltaTime; if (resendTimer > 23f) { resendTimer = 0f; Utils.SendMessage(msg + $"{string.Format(GetString("Poll.TimeInfo"), (int)Math.Round(PollTimer))}", title: title, importance: MessageImportance.Low); } yield return null; } DetermineResults(); } void DetermineResults() { int maxVotes = PollVotes.Values.Max(); KeyValuePair[] winners = PollVotes.Where(x => x.Value == maxVotes).ToArray(); string result = winners.Length == 1 ? string.Format(GetString("Poll.Winner"), winners[0].Key, PollAnswers[winners[0].Key], winners[0].Value) + PollVotes.Where(x => x.Value > 0 && x.Key != winners[0].Key).Aggregate("", (s, t) => s + $"{t.Key} - {t.Value} {PollAnswers[t.Key]}\n") : string.Format(GetString("Poll.Tie"), string.Join(" & ", winners.Select(x => $"{x.Key}{PollAnswers[x.Key]}")), maxVotes); Utils.SendMessage(result, title: Utils.ColorString(new(0, 255, 165, 255), GetString("PollResultTitle"))); PollVotes.Clear(); PollAnswers.Clear(); PollVoted.Clear(); if (winners.Length is > 0 and < 4 && GameStates.IsLobby) { int winnerIndex = (winners.Length == 1 ? winners[0].Key : winners.RandomElement().Key) - 65; if (gmPoll) Options.GameMode.SetValue((int)GMPollGameModes[winnerIndex] - 1, doSave: true, doSync: true); if (mPoll) Main.NormalOptions.MapId = (byte)winnerIndex; if (pPoll) Options.Preset?.SetValue(winnerIndex); } } static Color32 RandomColor() { byte[] colors = IRandom.Sequence(3, 0, 160).Select(x => (byte)x).ToArray(); return new(colors[0], colors[1], colors[2], 255); } } private static void PVCommand(PlayerControl player, string text, string[] args) { if (PollVotes.Count == 0) { Utils.SendMessage(GetString("Poll.Inactive"), player.PlayerId, importance: MessageImportance.Low); return; } if (PollVoted.Contains(player.PlayerId)) { Utils.SendMessage(GetString("Poll.AlreadyVoted"), player.PlayerId, importance: MessageImportance.Low); return; } if (args.Length != 2 || !char.TryParse(args[1], out char vote) || !PollVotes.ContainsKey(char.ToUpper(vote))) { Utils.SendMessage(GetString("Poll.VotingInfo"), player.PlayerId); return; } vote = char.ToUpper(vote); PollVoted.Add(player.PlayerId); PollVotes[vote]++; Utils.SendMessage(string.Format(GetString("Poll.YouVoted"), vote, PollVotes[vote]), player.PlayerId); } private static void HelpCommand(PlayerControl player, string text, string[] args) { Utils.ShowHelp(player.PlayerId); } private static void DumpCommand(PlayerControl player, string text, string[] args) { Utils.DumpLog(); } private static void GNOCommand(PlayerControl player, string text, string[] args) { if (!GameStates.IsLobby && player.IsAlive()) { Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId, importance: MessageImportance.Low); return; } string subArgs = args.Length != 2 ? "" : args[1]; if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo) || guessedNo is < 0 or > 99) { Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId, importance: MessageImportance.Low); return; } 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; Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), player.PlayerId); return; } if (guessedNo < targetNumber) { Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); return; } if (guessedNo > targetNumber) { Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); return; } 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; } private static void SDCommand(PlayerControl player, string text, string[] args) { if (args.Length < 1 || !int.TryParse(args[1], out int sound1)) return; RPC.PlaySound(player.PlayerId, (Sounds)sound1); } private static void CSDCommand(PlayerControl player, string text, string[] args) { string subArgs = text.Remove(0, 3); CustomSoundsManager.Play(subArgs.Trim()); } private static void MTHYCommand(PlayerControl player, string text, string[] args) { if (GameStates.IsMeeting) { MeetingHudRpcClosePatch.AllowClose = true; MeetingHud.Instance.RpcClose(); } else player.NoCheckStartMeeting(null, true); } private static void CosIDCommand(PlayerControl player, string text, string[] args) { NetworkedPlayerInfo.PlayerOutfit of = player.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"); } private static void EndCommand(PlayerControl player, string text, string[] args) { if (!player.IsHost() && !IsPlayerAdmin(player.FriendCode)) return; CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); GameManager.Instance.LogicFlow.CheckEndCriteria(); } private static void ChangeRoleCommand(PlayerControl player, string text, string[] args) { if (GameStates.IsLobby || !player.FriendCode.GetDevUser().up) return; string subArgs = text.Remove(0, 8); string setRole = FixRoleNameInput(subArgs.Trim()); foreach (CustomRoles rl in Main.CustomRoleValues) { if (rl.IsVanilla()) continue; string roleName = GetString(rl.ToString()).ToLower().Trim(); if (setRole.Contains(roleName)) { if (!rl.IsAdditionRole()) player.SetRole(rl.GetRoleTypes()); player.RpcSetCustomRole(rl); player.RpcChangeRoleBasis(rl); if (rl.IsGhostRole()) GhostRolesManager.SpecificAssignGhostRole(player.PlayerId, rl, true); Main.PlayerStates[player.PlayerId].RemoveSubRole(CustomRoles.NotAssigned); break; } } } private static void IDCommand(PlayerControl player, string text, string[] args) { string msgText = GetString("PlayerIdList"); msgText = Main.EnumeratePlayerControls().Aggregate(msgText, (current, pc) => $"{current}\n{pc.PlayerId} \u2192 {pc.GetRealName()}"); Utils.SendMessage(msgText, player.PlayerId); } private static void ColorCommand(PlayerControl player, string text, string[] args) { if (GameStates.IsInGame) { Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId, importance: MessageImportance.Low); return; } if (!player.IsHost() && !Options.PlayerCanSetColor.GetBool() && !IsPlayerVIP(player.FriendCode) && !player.FriendCode.GetDevUser().up) { Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId, importance: MessageImportance.Low); return; } string subArgs = args.Length < 2 ? string.Empty : args[1]; byte color = Utils.MsgToColor(subArgs, player.IsHost()); if (color == byte.MaxValue) { Utils.SendMessage(GetString("IllegalColor"), player.PlayerId, importance: MessageImportance.Low); return; } player.RpcSetColor(color); Utils.SendMessage(string.Format(GetString("Message.SetColor"), subArgs), player.PlayerId, importance: MessageImportance.Low); } private static void KillCommand(PlayerControl player, string text, string[] args) { if (GameStates.IsLobby) { Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId, importance: MessageImportance.Low); return; } if (args.Length < 2 || !int.TryParse(args[1], out int id2)) return; PlayerControl target = Utils.GetPlayerById(id2); if (target != null) { target.Kill(target); if (target.AmOwner) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); else Utils.SendMessage(string.Format(GetString("Message.Executed"), target.Data.PlayerName)); } } private static void ExeCommand(PlayerControl player, string text, string[] args) { if (GameStates.IsLobby) { Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId, importance: MessageImportance.Low); return; } if (args.Length < 2 || !int.TryParse(args[1], out int id)) return; PlayerControl pc = Utils.GetPlayerById(id); if (pc != null) { Main.PlayerStates[pc.PlayerId].deathReason = PlayerState.DeathReason.etc; pc.RpcExileV2(); pc.Data.IsDead = true; Main.PlayerStates[pc.PlayerId].SetDead(); Utils.AfterPlayerDeathTasks(pc, GameStates.IsMeeting); if (pc.AmOwner) Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); else Utils.SendMessage(string.Format(GetString("Message.Executed"), pc.Data.PlayerName)); } } private static void BanKickCommand(PlayerControl player, string text, string[] args) { // Check if the Kick command is enabled in the settings if (!Options.ApplyModeratorList.GetBool() && !player.IsHost()) { Utils.SendMessage(GetString("KickCommandDisabled"), player.PlayerId, importance: MessageImportance.Low); return; } // Check if the Player has the necessary privileges to use the command if (!IsPlayerModerator(player.FriendCode) && !player.IsHost()) { Utils.SendMessage(GetString("KickCommandNoAccess"), player.PlayerId, importance: MessageImportance.Low); return; } string subArgs = args.Length < 2 ? string.Empty : args[1]; if (string.IsNullOrWhiteSpace(subArgs) || !byte.TryParse(subArgs, out byte kickPlayerId)) { Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId, importance: MessageImportance.Low); return; } if (kickPlayerId.IsHost()) { Utils.SendMessage(GetString("KickCommandKickHost"), player.PlayerId, importance: MessageImportance.Low); return; } PlayerControl kickedPlayer = Utils.GetPlayerById(kickPlayerId); if (kickedPlayer == null) { Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId, importance: MessageImportance.Low); return; } // Prevent Moderators from kicking other Moderators if (IsPlayerModerator(kickedPlayer.FriendCode) && !player.IsHost()) { Utils.SendMessage(GetString("KickCommandKickMod"), player.PlayerId, importance: MessageImportance.Low); return; } try { string kickedPlayerName = kickedPlayer.GetRealName(); var textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")}"; if (GameStates.IsInGame) textToSend += $"\n{string.Format(GetString("KickCommandKickedRole"), kickedPlayer.GetCustomRole().ToColoredString())}"; if (args.Length >= 3) textToSend += $"\n{GetString("KickCommandKickedReason")} {string.Join(' ', args[2..])}"; Utils.SendMessage(textToSend, importance: GameStates.IsInGame ? MessageImportance.Medium : MessageImportance.Low); string modLogFilePath = $"{Main.DataPath}/EHR_DATA/ModLogs/{DateTime.Now:yyyy-MM-dd}.txt"; if (!File.Exists(modLogFilePath)) { string directoryName = Path.GetDirectoryName(modLogFilePath); if (!string.IsNullOrWhiteSpace(directoryName)) Directory.CreateDirectory(directoryName); File.WriteAllText(modLogFilePath, "=== Moderation Log ===\n"); } string logEntry = $"[{DateTime.Now:HH:mm:ss}] {player.GetRealName()} {(args[0] == "/ban" ? "banned" : "kicked")} {kickedPlayerName} [{kickedPlayer.FriendCode}|{kickedPlayer.GetClient().GetHashedPuid()}] for {(args.Length >= 3 ? string.Join(' ', args[2..]) : "[no reason provided]")}\n"; File.AppendAllText(modLogFilePath, logEntry); } catch (Exception e) { Utils.ThrowException(e); } AmongUsClient.Instance.KickPlayer(kickedPlayer.OwnerId, args[0] == "/ban"); } public static void CheckCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (!player.IsAlive() || !player.Is(CustomRoles.Inquirer) || player.GetAbilityUseLimit() < 1) return; if (args.Length < 3 || !GuessManager.MsgToPlayerAndRole(text[6..], out byte checkId, out CustomRoles checkRole, out _)) return; bool hasRole = Utils.GetPlayerById(checkId).Is(checkRole); if (IRandom.Instance.Next(100) < Inquirer.FailChance.GetInt()) hasRole = !hasRole; LateTask.New(() => Utils.SendMessage(GetString(hasRole ? "Inquirer.MessageTrue" : "Inquirer.MessageFalse"), player.PlayerId, importance: MessageImportance.High), 0.2f, log: false); player.RpcRemoveAbilityUse(); MeetingManager.SendCommandUsedMessage(args[0]); } private static void ChatCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (!Ventriloquist.On || !player.IsAlive() || !player.Is(CustomRoles.Ventriloquist) || player.PlayerId.GetAbilityUseLimit() < 1) return; var vl2 = (Ventriloquist)Main.PlayerStates[player.PlayerId].Role; if (vl2.Target == byte.MaxValue) return; PlayerControl tg = Utils.GetPlayerById(vl2.Target); string msg = text[6..]; LateTask.New(() => tg?.RpcSendChat(msg), 0.2f, log: false); ChatManager.AddChatHistory(tg, msg); player.RpcRemoveAbilityUse(); MeetingManager.SendCommandUsedMessage(args[0]); } public static void TargetCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (!Ventriloquist.On || !player.IsAlive() || !player.Is(CustomRoles.Ventriloquist) || player.PlayerId.GetAbilityUseLimit() < 1) return; var vl = (Ventriloquist)Main.PlayerStates[player.PlayerId].Role; vl.Target = args.Length < 2 ? byte.MaxValue : byte.TryParse(args[1], out byte targetId) ? targetId : byte.MaxValue; player.RPCPlayCustomSound("Line"); MeetingManager.SendCommandUsedMessage(args[0]); } private static void QSCommand(PlayerControl player, string text, string[] args) { if (!QuizMaster.On || !player.IsAlive()) return; var qm2 = (QuizMaster)Main.PlayerStates.Values.First(x => x.Role is QuizMaster).Role; if (qm2.Target != player.PlayerId || !QuizMaster.MessagesToSend.TryGetValue(player.PlayerId, out string msg)) return; Utils.SendMessage(msg, player.PlayerId, GetString("QuizMaster.QuestionSample.Title"), importance: MessageImportance.High); MeetingManager.SendCommandUsedMessage(args[0]); } private static void QACommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !QuizMaster.On || !player.IsAlive()) return; var qm = (QuizMaster)Main.PlayerStates.Values.First(x => x.Role is QuizMaster).Role; if (qm.Target != player.PlayerId) return; qm.Answer(args[1].ToUpper()); MeetingManager.SendCommandUsedMessage(args[0]); } private static void AnswerCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2) return; Mathematician.Reply(player, args[1]); } private static void AskCommand(PlayerControl player, string text, string[] args) { if (Starspawn.IsDayBreak) return; if (args.Length < 3 || !player.Is(CustomRoles.Mathematician)) return; Mathematician.Ask(player, args[1], args[2]); } private static void VoteCommand(PlayerControl player, string text, string[] args) { if (text.Length < 6 || !GameStates.IsMeeting) return; string toVote = text[6..].Replace(" ", string.Empty); if (!byte.TryParse(toVote, out byte voteId) || MeetingHud.Instance.playerStates?.FirstOrDefault(x => x.TargetPlayerId == player.PlayerId)?.DidVote is true or null) return; if (voteId > PlayerControl.AllPlayerControls.Count) return; PlayerControl votedPlayer = voteId.GetPlayer(); if (!player.UsesMeetingShapeshift() && Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) && votedPlayer != null && state.Role.OnVote(player, votedPlayer)) return; MeetingHud.Instance.CastVote(player.PlayerId, voteId); } private static void SayCommand(PlayerControl player, string text, string[] args) { if (!player.IsHost() && !IsPlayerModerator(player.FriendCode)) return; if (args.Length > 1) Utils.SendMessage(args[1..].Join(delimiter: " "), title: $"{GetString(player.IsHost() ? "MessageFromTheHost" : "SayTitle")}", importance: player.IsHost() ? MessageImportance.High : MessageImportance.Medium); } private static void DeathCommand(PlayerControl player, string text, string[] args) { if (!GameStates.IsInGame) return; if (Main.DiedThisRound.Contains(player.PlayerId) && Utils.IsRevivingRoleAlive()) return; PlayerControl target = args.Length < 2 || !byte.TryParse(args[1], out byte targetId) ? player : targetId.GetPlayer(); if (target == null) return; PlayerControl killer = target.GetRealKiller(); if (killer == null) { Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("DeathCommandFail"), GetString($"DeathReason.{Main.PlayerStates[target.PlayerId].deathReason}")), importance: MessageImportance.Low); return; } Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("DeathCommand"), killer.PlayerId.ColoredPlayerName(), (killer.Is(CustomRoles.Bloodlust) ? $"{CustomRoles.Bloodlust.ToColoredString()} " : string.Empty) + killer.GetCustomRole().ToColoredString())); } private static void MessageWaitCommand(PlayerControl player, string text, string[] args) { 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); } private static void TemplateCommand(PlayerControl player, string text, string[] args) { if (args.Length > 1) { if (player.AmOwner) TemplateManager.SendTemplate(args[1]); else TemplateManager.SendTemplate(args[1], player.PlayerId); } else { HashSet tags = TemplateManager.GetAllTags(); string message = tags.Count > 0 ? string.Format(GetString("Message.TemplateList"), string.Join("\n", tags)) : GetString("Message.NoTemplatesFound"); if (player.AmOwner) HudManager.Instance.Chat.AddChat(player, (player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + message); else Utils.SendMessage(message, player.PlayerId, importance: MessageImportance.Low); } } private static void TPInCommand(PlayerControl player, string text, string[] args) { if (!GameStates.IsLobby) return; if (!Options.PlayerCanTPInAndOut.GetBool() && !IsPlayerVIP(player.FriendCode) && !player.FriendCode.GetDevUser().up) { Utils.SendMessage(GetString("Message.OnlyVIPCanUse"), player.PlayerId, importance: MessageImportance.Low); return; } player.TP(new Vector2(-0.2f, 1.3f)); } private static void TPOutCommand(PlayerControl player, string text, string[] args) { if (!GameStates.IsLobby) return; if (!Options.PlayerCanTPInAndOut.GetBool() && !IsPlayerVIP(player.FriendCode) && !player.FriendCode.GetDevUser().up) { Utils.SendMessage(GetString("Message.OnlyVIPCanUse"), player.PlayerId, importance: MessageImportance.Low); return; } player.TP(new Vector2(0.1f, 3.8f)); } private static void MyRoleCommand(PlayerControl player, string text, string[] args) { CustomRoles role = player.GetCustomRole(); if (GameStates.IsInGame) { StringBuilder sb = new(); StringBuilder titleSb = new(); StringBuilder settings = new(); settings.Append(""); titleSb.Append($"{role.ToColoredString()} {Utils.GetRoleMode(role)}"); sb.Append(player.GetRoleInfo(true).TrimStart()); if (Options.CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem opt)) Utils.ShowChildrenSettings(opt, settings, disableColor: false); settings.Append(""); if (role.PetActivatedAbility()) sb.Append($"{GetString("SupportsPetMessage")}"); string searchStr = GetString(role.ToString()); sb.Replace(searchStr, role.ToColoredString()); sb.Replace(searchStr.ToLower(), role.ToColoredString()); foreach (CustomRoles subRole in Main.PlayerStates[player.PlayerId].SubRoles) { sb.Append($"\n\n{subRole.ToColoredString()} {Utils.GetRoleMode(subRole)} {GetString($"{subRole}InfoLong").FixRoleName(subRole)}"); string searchSubStr = GetString(subRole.ToString()); sb.Replace(searchSubStr, subRole.ToColoredString()); sb.Replace(searchSubStr.ToLower(), subRole.ToColoredString()); } if (settings.Length > 0) Utils.SendMessage("\n", player.PlayerId, settings.ToString()); Utils.SendMessage(sb.ToString(), player.PlayerId, titleSb.ToString(), importance: MessageImportance.High); if (role.UsesPetInsteadOfKill()) Utils.SendMessage("\n", player.PlayerId, GetString("UsesPetInsteadOfKillNotice")); if (player.UsesMeetingShapeshift()) Utils.SendMessage("\n", player.PlayerId, GetString("UsesMeetingShapeshiftNotice")); } else Utils.SendMessage((player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + GetString("Message.CanNotUseInLobby"), player.PlayerId); } private static void AFKExemptCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !byte.TryParse(args[1], out byte afkId)) return; AFKDetector.ExemptedPlayers.Add(afkId); Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("PlayerExemptedFromAFK"), afkId.ColoredPlayerName())); } private static void EffectCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !GameStates.IsInTask || !Randomizer.Exists) return; if (Enum.TryParse(args[1], true, out Randomizer.Effect effect)) effect.Apply(player); } private static void ComboCommand(PlayerControl player, string text, string[] args) { if ((!player.IsHost() && !IsPlayerAdmin(player.FriendCode)) || args.Length < 4) { if (Main.AlwaysSpawnTogetherCombos.Count == 0 && Main.NeverSpawnTogetherCombos.Count == 0) return; StringBuilder sb = new(); sb.Append(""); if (Main.AlwaysSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> alwaysList) && alwaysList.Count > 0) { sb.AppendLine(GetString("AlwaysComboListTitle")); sb.AppendLine(alwaysList.Join(x => $"{x.Key.ToColoredString()} \u00a7 {x.Value.Join(r => r.ToColoredString())}", "\n")); sb.AppendLine(); } if (Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> neverList) && neverList.Count > 0) { sb.AppendLine(GetString("NeverComboListTitle")); sb.AppendLine(neverList.Join(x => $"{x.Key.ToColoredString()} \u2194 {x.Value.Join(r => r.ToColoredString())}", "\n")); sb.AppendLine(); } sb.Append(GetString("ComboUsage")); Utils.SendMessage("\n", player.PlayerId, sb.ToString()); return; } switch (args[1]) { case "add": case "ban": if (GetRoleByName(args[2], out CustomRoles mainRole) && GetRoleByName(args[3], out CustomRoles addOn)) { if (mainRole.IsAdditionRole() || !addOn.IsAdditionRole() || (addOn == CustomRoles.Lovers && args[1] == "add")) break; if (args[1] == "add") { if (!Main.AlwaysSpawnTogetherCombos.ContainsKey(OptionItem.CurrentPreset)) Main.AlwaysSpawnTogetherCombos[OptionItem.CurrentPreset] = []; if (!Main.AlwaysSpawnTogetherCombos[OptionItem.CurrentPreset].TryGetValue(mainRole, out List list1)) Main.AlwaysSpawnTogetherCombos[OptionItem.CurrentPreset][mainRole] = [addOn]; else if (!list1.Contains(addOn)) list1.Add(addOn); if (text.EndsWith(" all")) { for (var preset = 0; preset < OptionItem.NumPresets; preset++) { if (preset == OptionItem.CurrentPreset) continue; if (!Main.AlwaysSpawnTogetherCombos.ContainsKey(preset)) Main.AlwaysSpawnTogetherCombos[preset] = []; if (!Main.AlwaysSpawnTogetherCombos[preset].TryGetValue(mainRole, out List list2)) Main.AlwaysSpawnTogetherCombos[preset][mainRole] = [addOn]; else if (!list2.Contains(addOn)) list2.Add(addOn); } } } else { if (!Main.NeverSpawnTogetherCombos.ContainsKey(OptionItem.CurrentPreset)) Main.NeverSpawnTogetherCombos[OptionItem.CurrentPreset] = []; if (!Main.NeverSpawnTogetherCombos[OptionItem.CurrentPreset].TryGetValue(mainRole, out List list2)) Main.NeverSpawnTogetherCombos[OptionItem.CurrentPreset][mainRole] = [addOn]; else if (!list2.Contains(addOn)) list2.Add(addOn); if (text.EndsWith(" all")) { for (var preset = 0; preset < OptionItem.NumPresets; preset++) { if (preset == OptionItem.CurrentPreset) continue; if (!Main.NeverSpawnTogetherCombos.ContainsKey(preset)) Main.NeverSpawnTogetherCombos[preset] = []; if (!Main.NeverSpawnTogetherCombos[preset].TryGetValue(mainRole, out List list3)) Main.NeverSpawnTogetherCombos[preset][mainRole] = [addOn]; else if (!list3.Contains(addOn)) list3.Add(addOn); } } } Utils.SendMessage(string.Format(args[1] == "add" ? GetString("ComboAdd") : GetString("ComboBan"), GetString(mainRole.ToString()), GetString(addOn.ToString())), player.PlayerId); Utils.SaveComboInfo(); } break; case "remove": case "allow": if (GetRoleByName(args[2], out CustomRoles mainRole2) && GetRoleByName(args[3], out CustomRoles addOn2)) { if (mainRole2.IsAdditionRole() || !addOn2.IsAdditionRole()) break; if (text.EndsWith(" all")) { for (var preset = 0; preset < OptionItem.NumPresets; preset++) { if (Main.AlwaysSpawnTogetherCombos.TryGetValue(preset, out Dictionary> list1)) { if (list1.TryGetValue(mainRole2, out List list2)) { list2.Remove(addOn2); if (list2.Count == 0) list1.Remove(mainRole2); if (list1.Count == 0) Main.AlwaysSpawnTogetherCombos.Remove(preset); } } if (Main.NeverSpawnTogetherCombos.TryGetValue(preset, out Dictionary> list3)) { if (list3.TryGetValue(mainRole2, out List list4)) { list4.Remove(addOn2); if (list4.Count == 0) list3.Remove(mainRole2); if (list3.Count == 0) Main.NeverSpawnTogetherCombos.Remove(preset); } } } Utils.SendMessage(string.Format(GetString("ComboRemove"), GetString(mainRole2.ToString()), GetString(addOn2.ToString())), player.PlayerId); Utils.SaveComboInfo(); } else { if (args[1] == "remove" && Main.AlwaysSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> alwaysList) && alwaysList.TryGetValue(mainRole2, out List list3)) { list3.Remove(addOn2); if (list3.Count == 0) alwaysList.Remove(mainRole2); Utils.SendMessage(string.Format(GetString("ComboRemove"), GetString(mainRole2.ToString()), GetString(addOn2.ToString())), player.PlayerId); Utils.SaveComboInfo(); } else if (Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> neverList) && neverList.TryGetValue(mainRole2, out List list4)) { list4.Remove(addOn2); if (list4.Count == 0) neverList.Remove(mainRole2); Utils.SendMessage(string.Format(GetString("ComboAllow"), GetString(mainRole2.ToString()), GetString(addOn2.ToString())), player.PlayerId); Utils.SaveComboInfo(); } } } break; } } private static void DeleteModCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !byte.TryParse(args[1], out byte remModId)) return; PlayerControl remModPc = Utils.GetPlayerById(remModId); if (remModPc == null) return; string remFc = remModPc.FriendCode.Replace(':', '#'); if (!IsPlayerModerator(remFc)) { Utils.SendMessage(GetString("PlayerNotMod"), player.PlayerId); return; } File.WriteAllLines($"{Main.DataPath}/EHR_DATA/Moderators.txt", File.ReadAllLines($"{Main.DataPath}/EHR_DATA/Moderators.txt").Where(x => !x.Contains(remFc))); Utils.SendMessage(GetString("PlayerRemovedFromModList"), player.PlayerId); } private static void AddModCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !byte.TryParse(args[1], out byte newModId)) return; PlayerControl newModPc = Utils.GetPlayerById(newModId); if (newModPc == null) return; string fc = newModPc.FriendCode.Replace(':', '#'); if (IsPlayerModerator(fc)) { Utils.SendMessage(GetString("PlayerAlreadyMod"), player.PlayerId); return; } File.AppendAllText($"{Main.DataPath}/EHR_DATA/Moderators.txt", $"\n{fc}"); Utils.SendMessage(GetString("PlayerAddedToModList"), player.PlayerId); } private static void KCountCommand(PlayerControl player, string text, string[] args) { if (GameStates.IsLobby || !Options.EnableGameStateCommand.GetBool() || Main.AllAlivePlayerControlsCount < Options.MinPlayersForGameStateCommand.GetInt()) { Utils.SendMessage(GetString("Message.CommandUnavailable"), player.PlayerId, importance: MessageImportance.Low); return; } Utils.SendMessage("\n", player.PlayerId, Utils.GetGameStateData(), importance: MessageImportance.High); } private static void SetRoleCommand(PlayerControl player, string text, string[] args) { string subArgs = string.Join(' ', args[1..]); if (!GuessManager.MsgToPlayerAndRole(subArgs, out byte resultId, out CustomRoles roleToSet, out _)) { Utils.SendMessage(GetString("InvalidArguments"), player.PlayerId); return; } if (resultId != 0 && !player.FriendCode.GetDevUser().up && !GameStates.IsLocalGame) { Utils.SendMessage(GetString("Message.NoPermissionSetRoleOthers"), player.PlayerId); return; } PlayerControl targetPc = Utils.GetPlayerById(resultId); if (targetPc == null) return; if (roleToSet.IsAdditionRole()) { if (!Main.SetAddOns.ContainsKey(resultId)) Main.SetAddOns[resultId] = []; if (Main.SetAddOns[resultId].Contains(roleToSet)) Main.SetAddOns[resultId].Remove(roleToSet); else Main.SetAddOns[resultId].Add(roleToSet); } else Main.SetRoles[targetPc.PlayerId] = roleToSet; Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("RoleSelected"), resultId.ColoredPlayerName(), roleToSet.ToColoredString())); if (roleToSet.OnlySpawnsWithPets() && !Options.UsePets.GetBool()) Prompt.Show(GetString("Promt.SetRoleRequiresPets"), () => Options.UsePets.SetValue(1), () => { }); } private static void UpCommand(PlayerControl player, string text, string[] args) { Utils.SendMessage($"{GetString("UpReplacedMessage")}", player.PlayerId); } private static void RCommand(PlayerControl player, string text, string[] args) { string subArgs = text.Remove(0, 2); byte to = player.AmOwner && (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) ? byte.MaxValue : player.PlayerId; SendRolesInfo(subArgs, to); } private static void DisconnectCommand(PlayerControl player, string text, string[] args) { string subArgs = args.Length < 2 ? string.Empty : args[1]; switch (subArgs) { case "crew": GameManager.Instance.enabled = false; GameManager.Instance.ShouldCheckForGameEnd = false; MessageWriter msg = AmongUsClient.Instance.StartEndGame(); msg.Write((byte)6); msg.Write(false); AmongUsClient.Instance.FinishEndGame(msg); break; case "imp": GameManager.Instance.enabled = false; GameManager.Instance.ShouldCheckForGameEnd = false; MessageWriter msg2 = AmongUsClient.Instance.StartEndGame(); msg2.Write((byte)5); msg2.Write(false); AmongUsClient.Instance.FinishEndGame(msg2); break; default: if (!HudManager.InstanceExists) break; HudManager.Instance.Chat.AddChat(player, "crew | imp"); break; } ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Admin, 0); } private static void NowCommand(PlayerControl player, string text, string[] args) { string subArgs = args.Length < 2 ? string.Empty : args[1]; switch (subArgs) { case "r": case "roles": Utils.ShowActiveRoles(player.PlayerId); break; case "a": case "all": Utils.ShowAllActiveSettings(player.PlayerId); break; default: Utils.ShowActiveSettings(player.PlayerId); break; } } private static void LevelCommand(PlayerControl player, string text, string[] args) { string subArgs = args.Length < 2 ? string.Empty : args[1]; Utils.SendMessage(string.Format(GetString("Message.SetLevel"), subArgs), player.PlayerId); _ = int.TryParse(subArgs, out int input); if (input is < 1 or > 999) { Utils.SendMessage(GetString("Message.AllowLevelRange"), player.PlayerId); return; } var number = Convert.ToUInt32(input); player.RpcSetLevel(number - 1); } private static void HideNameCommand(PlayerControl player, string text, string[] args) { Main.HideName.Value = args.Length > 1 ? string.Join(' ', args[1..]) : Main.HideName.DefaultValue.ToString(); GameStartManagerPatch.GameStartManagerStartPatch.HideName.text = ColorUtility.TryParseHtmlString(Main.HideColor.Value, out _) ? $"{Main.HideName.Value}" : $"{Main.HideName.Value}"; } private static void RenameCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2) return; string name = Regex.Replace(string.Join(' ', args[1..]), "]*>", string.Empty).Trim(); if (BanManager.CheckDenyNamePlayer(player, name)) return; if (name.RemoveHtmlTags().Length is > 15 or < 1) Utils.SendMessage(GetString("Message.AllowNameLength"), player.PlayerId, importance: MessageImportance.Low); else { if (player.AmOwner) Main.NickName = name; else { if (!Options.PlayerCanSetName.GetBool() && !IsPlayerVIP(player.FriendCode) && !player.FriendCode.GetDevUser().up) { Utils.SendMessage(GetString("Message.OnlyVIPCanUse"), player.PlayerId, importance: MessageImportance.Low); return; } if (GameStates.IsInGame) { Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId, importance: MessageImportance.Low); return; } Main.AllPlayerNames[player.PlayerId] = name; player.RpcSetName(name); } } } private static void LastResultCommand(PlayerControl player, string text, string[] args) { Utils.ShowKillLog(player.PlayerId); Utils.ShowLastAddOns(player.PlayerId); Utils.ShowLastRoles(player.PlayerId); Utils.ShowLastResult(player.PlayerId); } private static void WinnerCommand(PlayerControl player, string text, string[] args) { if (Main.WinnerNameList.Count == 0) Utils.SendMessage(GetString("NoInfoExists"), importance: MessageImportance.Low); else Utils.SendMessage("Winners:\n" + string.Join(", ", Main.WinnerNameList)); } private static void ChangeSettingCommand(PlayerControl player, string text, string[] args) { string subArgs = args.Length < 2 ? "" : args[1]; switch (subArgs) { case "map": subArgs = args.Length < 3 ? "" : args[2]; switch (subArgs) { case "skeld": case "theskeld": GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 0); break; case "mira": case "mirahq": GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 1); break; case "polus": GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 2); break; case "dleks": case "dlekseht": GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 3); break; case "airship": GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 4); break; case "fungle": case "thefungle": GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 5); break; case "submerged" when SubmergedCompatibility.Loaded: GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 6); break; case "custom": subArgs = args.Length < 4 ? "" : args[3]; GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, byte.Parse(subArgs)); break; } break; case "impostors": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.NumImpostors, int.Parse(subArgs)); AmongUsClient.Instance.StartGame(); break; case "players": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetInt(Int32OptionNames.MaxPlayers, int.Parse(subArgs)); AmongUsClient.Instance.StartGame(); break; case "recommended": subArgs = args.Length < 3 ? "" : args[2]; switch (subArgs) { case "on": GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.IsDefaults, true); break; case "off": GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.IsDefaults, false); break; } break; case "confirmejects": subArgs = args.Length < 3 ? "" : args[2]; switch (subArgs) { case "on": GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.ConfirmImpostor, true); break; case "off": GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.ConfirmImpostor, false); break; } break; case "emergencymeetings": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.NumEmergencyMeetings, int.Parse(subArgs)); break; case "anonymousvotes": subArgs = args.Length < 3 ? "" : args[2]; switch (subArgs) { case "on": GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.AnonymousVotes, true); break; case "off": GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.AnonymousVotes, false); break; } break; case "emergencycooldown": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.EmergencyCooldown, int.Parse(subArgs)); break; case "discussiontime": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.DiscussionTime, int.Parse(subArgs)); break; case "votingtime": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.VotingTime, int.Parse(subArgs)); break; case "playerspeed": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.currentNormalGameOptions.SetFloat(FloatOptionNames.PlayerSpeedMod, float.Parse(subArgs)); break; case "crewmatevision": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.currentNormalGameOptions.SetFloat(FloatOptionNames.CrewLightMod, float.Parse(subArgs)); break; case "impostorvision": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.currentNormalGameOptions.SetFloat(FloatOptionNames.ImpostorLightMod, float.Parse(subArgs)); break; case "killcooldown": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.currentNormalGameOptions.SetFloat(FloatOptionNames.KillCooldown, float.Parse(subArgs)); break; case "killdistance": subArgs = args.Length < 3 ? "" : args[2]; switch (subArgs) { case "short": GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.KillDistance, 0); break; case "medium": GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.KillDistance, 1); break; case "long": GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.KillDistance, 2); break; case "custom": subArgs = args.Length < 4 ? "" : args[3]; GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.KillDistance, int.Parse(subArgs)); break; } break; case "taskbarupdates": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.currentNormalGameOptions.TaskBarMode = subArgs switch { "always" => AmongUs.GameOptions.TaskBarMode.Normal, "meetings" => AmongUs.GameOptions.TaskBarMode.MeetingOnly, "never" => AmongUs.GameOptions.TaskBarMode.Invisible, _ => GameOptionsManager.Instance.currentNormalGameOptions.TaskBarMode }; break; case "visualtasks": subArgs = args.Length < 3 ? "" : args[2]; switch (subArgs) { case "on": GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.VisualTasks, true); break; case "off": GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.VisualTasks, false); break; } break; case "commontasks": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetInt(Int32OptionNames.NumCommonTasks, int.Parse(subArgs)); break; case "longtasks": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetInt(Int32OptionNames.NumLongTasks, int.Parse(subArgs)); break; case "shorttasks": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetInt(Int32OptionNames.NumShortTasks, int.Parse(subArgs)); break; case "scientistcount": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Scientist, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Scientist)); break; case "scientistchance": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Scientist, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Scientist), int.Parse(subArgs)); break; case "vitalsdisplaycooldown": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ScientistCooldown, float.Parse(subArgs)); break; case "batteryduration": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ScientistBatteryCharge, float.Parse(subArgs)); break; case "engineercount": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.currentNormalGameOptions.RoleOptions.SetRoleRate(RoleTypes.Engineer, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Engineer)); break; case "engineerchance": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Engineer, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Engineer), int.Parse(subArgs)); break; case "ventusecooldown": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.EngineerCooldown, float.Parse(subArgs)); break; case "maxtimeinvents": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.EngineerInVentMaxTime, float.Parse(subArgs)); break; case "guardianangelcount": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.GuardianAngel, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.GuardianAngel)); break; case "guardianangelchance": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.GuardianAngel, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.GuardianAngel), int.Parse(subArgs)); break; case "protectcooldown": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.GuardianAngelCooldown, float.Parse(subArgs)); break; case "protectduration": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ProtectionDurationSeconds, float.Parse(subArgs)); break; case "protectvisibletoimpostors": subArgs = args.Length < 3 ? "" : args[2]; switch (subArgs) { case "on": GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.ImpostorsCanSeeProtect, true); break; case "off": GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.ImpostorsCanSeeProtect, false); break; } break; case "shapeshiftercount": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Shapeshifter, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Shapeshifter)); break; case "shapeshifterchance": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Shapeshifter, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Shapeshifter), int.Parse(subArgs)); break; case "shapeshiftduration": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ShapeshifterDuration, float.Parse(subArgs)); break; case "shapeshiftcooldown": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ShapeshifterCooldown, float.Parse(subArgs)); break; case "leaveshapeshiftevidence": subArgs = args.Length < 3 ? "" : args[2]; switch (subArgs) { case "on": GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.ShapeshifterLeaveSkin, true); break; case "off": GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.ShapeshifterLeaveSkin, false); break; } break; case "phantomcount": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Phantom, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Phantom)); break; case "phantomchance": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Phantom, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Phantom), int.Parse(subArgs)); break; case "invisduration": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.PhantomDuration, float.Parse(subArgs)); break; case "inviscooldown": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.PhantomCooldown, float.Parse(subArgs)); break; case "noisemakercount": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Noisemaker, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Noisemaker)); break; case "noisemakerchance": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Noisemaker, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Noisemaker), int.Parse(subArgs)); break; case "noisemakerimpostoralert": subArgs = args.Length < 3 ? "" : args[2]; switch (subArgs) { case "on": GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.NoisemakerImpostorAlert, true); break; case "off": GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.NoisemakerImpostorAlert, false); break; } break; case "noisemakeralertduration": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.NoisemakerAlertDuration, int.Parse(subArgs)); break; case "trackercount": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Tracker, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Tracker)); break; case "trackerchance": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Tracker, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Tracker), int.Parse(subArgs)); break; case "trackduration": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.TrackerDuration, float.Parse(subArgs)); break; case "trackcooldown": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.TrackerCooldown, float.Parse(subArgs)); break; case "trackdelay": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.TrackerDelay, float.Parse(subArgs)); break; case "vipercount": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Viper, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Viper)); break; case "viperchance": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Viper, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Viper), int.Parse(subArgs)); break; case "viperdissolvetime": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ViperDissolveTime, float.Parse(subArgs)); break; case "detectivecount": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Detective, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Detective)); break; case "detectivechance": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Detective, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Detective), int.Parse(subArgs)); break; case "detectivesuspectlimit": subArgs = args.Length < 3 ? "" : args[2]; GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.DetectiveSuspectLimit, float.Parse(subArgs)); break; default: Utils.SendMessage(GetString("Commands.ChangeSettingHelp"), player.PlayerId); break; } GameOptionsManager.Instance.GameHostOptions = GameOptionsManager.Instance.CurrentGameOptions; GameManager.Instance.LogicOptions.SyncOptions(); } private static void VersionCommand(PlayerControl player, string text, string[] args) { string versionText = Main.PlayerVersion.OrderBy(pair => pair.Key).Aggregate(string.Empty, (current, kvp) => current + $"{kvp.Key}: ({Main.AllPlayerNames[kvp.Key]}) {kvp.Value.forkId}/{kvp.Value.version}({kvp.Value.tag})\n"); if (versionText != string.Empty && HudManager.InstanceExists) HudManager.Instance.Chat.AddChat(player, (player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + versionText); } private static void LTCommand(PlayerControl player, string text, string[] args) { if (!GameStates.IsLobby) return; float timer = GameStartManagerPatch.Timer; int minutes = (int)timer / 60; int seconds = (int)timer % 60; string lt = string.Format(GetString("LobbyCloseTimer"), $"{minutes:00}:{seconds:00}"); if (timer <= 60) lt = Utils.ColorString(Color.red, lt); Utils.SendMessage(lt, player.PlayerId); } // ------------------------------------------------------------------------------------------------------------------------- private static bool CheckMute(byte id) { if (!MutedPlayers.TryGetValue(id, out (long MuteTimeStamp, int Duration, long LastMessageTimeStamp) mute)) return false; long now = Utils.TimeStamp; long timeLeft = mute.Duration - (now - mute.MuteTimeStamp); if (timeLeft <= 0) { MutedPlayers.Remove(id); return false; } if (now - mute.LastMessageTimeStamp < 5) return true; mute.LastMessageTimeStamp = now; MutedPlayers[id] = mute; Utils.SendMessage("\n", id, string.Format(GetString("MuteMessage"), timeLeft)); return true; } private static string FixRoleNameInput(string text) { text = text.Replace("着", "者").Trim().ToLower(); return text switch { "schrodingers cat" or "schrodingerscat" or "cat" => "Schrödinger's Cat", _ => text }; } public static bool GetRoleByName(string name, out CustomRoles role) { role = new(); if (name == "") return false; if ((TranslationController.InstanceExists ? TranslationController.Instance.currentLanguage.languageID : SupportedLangs.SChinese) == SupportedLangs.SChinese) { Regex r = new("[\u4e00-\u9fa5]+$"); MatchCollection mc = r.Matches(name); var result = string.Empty; for (var 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(); string nameWithoutId = Regex.Replace(name.Replace(" ", string.Empty), @"^\d+", string.Empty); foreach (CustomRoles rl in Main.CustomRoleValues) { if (rl.IsVanilla()) continue; string roleName = Regex.Replace(GetString(rl.ToString()).RemoveHtmlTags().ToLower(), @"[^\p{L}-]+", string.Empty); if (nameWithoutId == roleName) { role = rl; return true; } } return false; } private static void SendRolesInfo(string role, byte playerId, bool isDev = false, bool isUp = false) { if (Options.CurrentGameMode != CustomGameMode.Standard) { string text = $"{GetString($"ModeDescribe.{Options.CurrentGameMode}")}\n\n{GetString("NToCheckGameModeSettings")}"; Utils.SendMessage(text, playerId, importance: MessageImportance.Low); if (Options.CurrentGameMode != CustomGameMode.HideAndSeek) 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 == "") { Utils.ShowActiveRoles(playerId); return; } role = FixRoleNameInput(role).ToLower().Trim().Replace(" ", string.Empty); foreach (CustomRoles rl in Main.CustomRoleValues) { if (rl.IsVanilla()) continue; string roleName = Regex.Replace(GetString(rl.ToString()).RemoveHtmlTags().ToLower().Trim().TrimStart('*'), @"[^\p{L}-]+", string.Empty); if (role == roleName) { if ((isDev || isUp) && GameStates.IsLobby) { var devMark = "▲"; if (rl.IsAdditionRole() || rl is CustomRoles.GM) devMark = string.Empty; if (rl.GetCount() < 1 || rl.GetMode() == 0) devMark = string.Empty; if (isUp) Utils.SendMessage(devMark == "▲" ? string.Format(GetString("Message.YTPlanSelected"), roleName) : string.Format(GetString("Message.YTPlanSelectFailed"), roleName), playerId, importance: MessageImportance.Low); if (isUp) return; } string coloredString = rl.ToColoredString(); StringBuilder sb = new(); StringBuilder settings = new(); var title = $"{coloredString} {Utils.GetRoleMode(rl)}"; sb.Append(GetString($"{rl}InfoLong").FixRoleName(rl).TrimStart()); if (Options.CustomRoleSpawnChances.TryGetValue(rl, out StringOptionItem chance)) AddSettings(chance); if (rl is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor && Options.CustomRoleSpawnChances.TryGetValue(CustomRoles.Lovers, out chance)) AddSettings(chance); string txt = sb.ToString().Replace(roleName, coloredString, StringComparison.OrdinalIgnoreCase); sb.Clear().Append(txt); if (rl.PetActivatedAbility()) sb.Append($"{GetString("SupportsPetMessage")}"); if (settings.Length > 0) Utils.SendMessage("\n", playerId, settings.ToString()); if (rl.UsesPetInsteadOfKill()) Utils.SendMessage("\n", playerId, GetString("UsesPetInsteadOfKillNotice")); if (rl.UsesMeetingShapeshift()) Utils.SendMessage("\n", playerId, GetString("UsesMeetingShapeshiftNotice")); Utils.SendMessage(sb.ToString(), playerId, title, importance: MessageImportance.High); return; void AddSettings(StringOptionItem stringOptionItem) { settings.AppendLine($"{GetString("SettingsForRoleText")} {rl.ToColoredString()}:"); Utils.ShowChildrenSettings(stringOptionItem, settings, disableColor: false); settings.Append(""); } } } foreach (CustomGameMode gameMode in Main.CustomGameModeValues) { string gmString = GetString(gameMode.ToString()); string match = gmString.ToLower().Trim().TrimStart('*').Replace(" ", string.Empty); if (role.Equals(match, StringComparison.OrdinalIgnoreCase)) { string text = $"{GetString($"ModeDescribe.{gameMode}")}\n\n{GetString("NToCheckGameModeSettings")}"; Utils.SendMessage(text, playerId, gmString, importance: MessageImportance.Low); return; } } Utils.SendMessage(isUp ? GetString("Message.YTPlanCanNotFindRoleThePlayerEnter") : GetString("Message.CanNotFindRoleThePlayerEnter"), playerId, importance: MessageImportance.Low); } // ------------------------------------------------------------------------------------------------------------------------- public static void OnReceiveChat(PlayerControl player, string text, out bool canceled) { canceled = false; if (!AmongUsClient.Instance.AmHost || player.AmOwner) return; long now = Utils.TimeStamp; if (LastSentCommand.TryGetValue(player.PlayerId, out long ts) && ts + 2 >= now && !player.IsModdedClient()) { Logger.Warn("Chat message ignored, it was sent too soon after their last message", "ReceiveChat"); return; } if (GameStates.InGame && (Silencer.ForSilencer.Contains(player.PlayerId) || (Main.PlayerStates[player.PlayerId].Role is Dad { IsEnable: true } dad && dad.UsingAbilities.Contains(Dad.Ability.GoForMilk))) && player.IsAlive()) { ChatManager.SendPreviousMessagesToAll(); canceled = true; LastSentCommand[player.PlayerId] = now; return; } if (GameStates.IsMeeting && Exorcist.AbilityEndTS > now && player.IsAlive()) { player.RpcGuesserMurderPlayer(); player.SetRealKiller(Main.EnumeratePlayerControls().FirstOrDefault(x => x.Is(CustomRoles.Exorcist))); } if (text.StartsWith("\n")) text = text[1..]; switch (Options.CurrentGameMode) { case CustomGameMode.TheMindGame when !player.IsModdedClient(): TheMindGame.OnChat(player, text.ToLower()); break; case CustomGameMode.BedWars: BedWars.OnChat(player, text); break; } CheckAnagramGuess(player.PlayerId, text.ToLower()); foreach (PlayerState state in Main.PlayerStates.Values) { if (state.Role is Astral { Timer: not null } && state.Player && state.Player.PlayerId != player.PlayerId) { if (state.Player.AmOwner) canceled = true; else ChatManager.ClearChat(state.Player); } } if (!Starspawn.IsDayBreak) { if (GuessManager.GuesserMsg(player, text) || Judge.TrialMsg(player, text) || Swapper.SwapMsg(player, text) || Inspector.InspectorCheckMsg(player, text) || Councillor.MurderMsg(player, text)) { canceled = true; LastSentCommand[player.PlayerId] = now; return; } if (Medium.MsMsg(player, text) || Nemesis.NemesisMsgCheck(player, text)) { LastSentCommand[player.PlayerId] = now; return; } } var commandEntered = false; if (text.StartsWith('/') && !player.IsModdedClient() && (!GameStates.IsMeeting || MeetingHud.Instance.state is not MeetingHud.VoteStates.Results and not MeetingHud.VoteStates.Proceeding)) { Utils.CheckServerCommand(ref text, out bool spamRequired); if (!spamRequired) canceled = true; string[] args = text.Split(' '); foreach (Command command in Command.AllCommands) { if (!command.IsThisCommand(text)) continue; Logger.Info($" Recognized command: {text}", "ReceiveChat"); commandEntered = true; if (!command.CanUseCommand(player, sendErrorMessage: true)) { canceled = true; break; } if (command.AlwaysHidden && spamRequired) Utils.SendMessage("\n", player.PlayerId, GetString("NoSpamAnymoreUseCmd")); command.Action(player, text, args); if (command.IsCanceled) canceled |= command.AlwaysHidden || !Options.HostSeesCommandsEnteredByOthers.GetBool(); break; } } if (!commandEntered && Astral.On && !player.Is(CustomRoles.Astral)) Main.PlayerStates.Values.DoIf(x => !x.IsDead && x.Role is Astral { Timer: not null } && x.Player, x => ChatManager.ClearChat(x.Player)); if (CheckMute(player.PlayerId)) { canceled = true; ChatManager.SendPreviousMessagesToAll(); return; } if (ExileController.Instance) { canceled = true; HasMessageDuringEjectionScreen = true; } if (!canceled) ChatManager.SendMessage(player, text); switch (commandEntered) { case true: LastSentCommand[player.PlayerId] = now; break; case false: SpamManager.CheckSpam(player, text); break; } } } [HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] internal static class ChatUpdatePatch { public static readonly List<(string Text, byte SendTo, string Title, long SendTimeStamp)> LastMessages = []; public static void Postfix(ChatController __instance) { var chatBubble = __instance.chatBubblePool.Prefab.CastFast(); chatBubble.TextArea.overrideColorTags = false; if (Main.DarkTheme.Value) { chatBubble.TextArea.color = Color.white; chatBubble.Background.color = new(0.1f, 0.1f, 0.1f, 1f); } long now = Utils.TimeStamp; LastMessages.RemoveAll(x => now - x.SendTimeStamp > 10); } internal static bool SendLastMessages(ref CustomRpcSender sender) { PlayerControl player = GameStates.IsLobby ? Main.EnumeratePlayerControls().Without(PlayerControl.LocalPlayer).RandomElement() : Main.EnumerateAlivePlayerControls().MinBy(x => x.PlayerId) ?? Main.EnumeratePlayerControls().MinBy(x => x.PlayerId) ?? PlayerControl.LocalPlayer; if (player == null) return false; bool wasCleared = false; foreach ((string msg, byte sendTo, string title, _) in LastMessages) wasCleared = SendMessage(player, msg, sendTo, title, ref sender); return LastMessages.Count > 0 && !wasCleared; } private static bool SendMessage(PlayerControl player, string msg, byte sendTo, string title, ref CustomRpcSender sender) { int clientId = sendTo == byte.MaxValue ? -1 : Utils.GetPlayerById(sendTo).OwnerId; string name = player.Data.PlayerName; if (clientId == -1 && HudManager.InstanceExists) { player.SetName(title); HudManager.Instance.Chat.AddChat(player, msg); player.SetName(name); } sender.AutoStartRpc(player.NetId, RpcCalls.SetName, clientId) .Write(player.Data.NetId) .Write(title) .EndRpc(); sender.AutoStartRpc(player.NetId, RpcCalls.SendChat, clientId) .Write(msg) .EndRpc(); sender.AutoStartRpc(player.NetId, RpcCalls.SetName, clientId) .Write(player.Data.NetId) .Write(player.Data.PlayerName) .EndRpc(); if (sender.stream.Length > 500) { sender.SendMessage(); sender = CustomRpcSender.Create(sender.name, sender.sendOption); return true; } return false; } } [HarmonyPatch(typeof(FreeChatInputField), nameof(FreeChatInputField.Awake))] internal static class FreeChatFieldAwakePatch { public static void Postfix(FreeChatInputField __instance) { UpdateCharCountPatch.Postfix(__instance); } } [HarmonyPatch(typeof(FreeChatInputField), nameof(FreeChatInputField.UpdateCharCount))] internal static class UpdateCharCountPatch { public static void Postfix(FreeChatInputField __instance) { int length = __instance.textArea.text.Length; __instance.charCountText.SetText(length <= 0 ? GetString("ThankYouForUsingEHR") : $"{length}/{__instance.textArea.characterLimit}"); __instance.charCountText.enableWordWrapping = false; __instance.charCountText.color = length switch { < 800 => Color.black, < 1000 => Color.yellow, _ => Color.red }; } } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSendChat))] internal static class RpcSendChatPatch { public static bool Prefix(PlayerControl __instance, string chatText, ref bool __result) { if (!AmongUsClient.Instance.AmHost && !__instance.IsAlive() && Options.CurrentGameMode != CustomGameMode.Standard) return true; if (string.IsNullOrWhiteSpace(chatText)) { __result = false; return false; } int return_count = PlayerControl.LocalPlayer.name.Count(x => x == '\n'); chatText = new StringBuilder(chatText).Insert(0, "\n", return_count).ToString(); if (AmongUsClient.Instance.AmClient && HudManager.InstanceExists) HudManager.Instance.Chat.AddChat(__instance, chatText); if (chatText.Contains("who", StringComparison.OrdinalIgnoreCase)) UnityTelemetry.Instance.SendWho(); MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.SendChat, SendOption.Reliable); messageWriter.Write(chatText); AmongUsClient.Instance.FinishRpcImmediately(messageWriter); __result = true; return false; } }