private bool AttemptInvokeCommand <TObj>(StaticCommand command, IRCMessage msg, string cmdStr, Match m, TObj extraObject) { if (command.HasAttribute <DebuggingOnlyAttribute>() && !TwitchPlaySettings.data.EnableDebuggingCommands) { return(false); } if (command.HasAttribute <ElevatorOnlyAttribute>() && !(GameRoom.Instance is ElevatorGameRoom)) { return(false); } if (command.HasAttribute <ElevatorDisallowedAttribute>() && GameRoom.Instance is ElevatorGameRoom) { return(false); } if (!UserAccess.HasAccess(msg.UserNickName, TwitchPlaySettings.data.AnarchyMode ? command.Attr.AccessLevelAnarchy : command.Attr.AccessLevel, orHigher: true)) { IRCConnection.SendMessageFormat("@{0}, you need {1} access to use that command{2}.", msg.UserNickName, UserAccess.LevelToString(TwitchPlaySettings.data.AnarchyMode ? command.Attr.AccessLevelAnarchy : command.Attr.AccessLevel), TwitchPlaySettings.data.AnarchyMode ? " in anarchy mode" : ""); // Return true so that the command counts as processed return(true); } if (extraObject is TwitchModule mdl && mdl.Solved && !command.HasAttribute <SolvedAllowedAttribute>() && !TwitchPlaySettings.data.AnarchyMode) { IRCConnection.SendMessageFormat(TwitchPlaySettings.data.AlreadySolved, mdl.Code, mdl.PlayerName, msg.UserNickName, mdl.BombComponent.GetModuleDisplayName()); // Return true so that the command counts as processed (otherwise you get the above message multiple times) return(true); } Leaderboard.Instance.GetRank(msg.UserNickName, out Leaderboard.LeaderboardEntry entry); if (entry?.Team == null && extraObject is TwitchModule && OtherModes.VSModeOn) { IRCConnection.SendMessage($@"{msg.UserNickName}, you have not joined a team, and cannot solve modules in this mode until you do, please use !join evil or !join good."); // Return true so that the command counts as processed (otherwise you get the above message multiple times) return(true); } if (!TwitchGame.IsAuthorizedDefuser(msg.UserNickName, msg.IsWhisper)) { return(true); } BanData ban = UserAccess.IsBanned(msg.UserNickName); if (ban != null) { if (double.IsPositiveInfinity(ban.BanExpiry)) { IRCConnection.SendMessage($"Sorry @{msg.UserNickName}, You were banned permanently from Twitch Plays by {ban.BannedBy}{(string.IsNullOrEmpty(ban.BannedReason) ? "." : $", for the following reason: {ban.BannedReason}")}", msg.UserNickName, !msg.IsWhisper); } else { int secondsRemaining = (int)(ban.BanExpiry - DateTime.Now.TotalSeconds()); int daysRemaining = secondsRemaining / 86400; secondsRemaining %= 86400; int hoursRemaining = secondsRemaining / 3600; secondsRemaining %= 3600; int minutesRemaining = secondsRemaining / 60; secondsRemaining %= 60; string timeRemaining = $"{secondsRemaining} seconds."; if (daysRemaining > 0) { timeRemaining = $"{daysRemaining} days, {hoursRemaining} hours, {minutesRemaining} minutes, {secondsRemaining} seconds."; } else if (hoursRemaining > 0) { timeRemaining = $"{hoursRemaining} hours, {minutesRemaining} minutes, {secondsRemaining} seconds."; } else if (minutesRemaining > 0) { timeRemaining = $"{minutesRemaining} minutes, {secondsRemaining} seconds."; } IRCConnection.SendMessage($"Sorry @{msg.UserNickName}, You were timed out from Twitch Plays by {ban.BannedBy}{(string.IsNullOrEmpty(ban.BannedReason) ? "." : $", For the following reason: {ban.BannedReason}")} You can participate again in {timeRemaining}", msg.UserNickName, !msg.IsWhisper); } return(true); } var parameters = command.Method.GetParameters(); var groupAttrs = parameters.Select(p => (GroupAttribute)p.GetCustomAttributes(typeof(GroupAttribute), false).FirstOrDefault()).ToArray(); var arguments = new object[parameters.Length]; for (int i = 0; i < parameters.Length; i++) { // Capturing groups from the regular expression if (groupAttrs[i] != null && m != null) { var group = m.Groups[groupAttrs[i].GroupIndex]; NumberParseResult result; // Helper function to parse numbers (ints, floats, doubles) NumberParseResult IsNumber <TNum>(TryParse <TNum> tryParse) { var isNullable = parameters[i].ParameterType == typeof(Nullable <>).MakeGenericType(typeof(TNum)); if (parameters[i].ParameterType != typeof(TNum) && !isNullable) { return(NumberParseResult.NotOfDesiredType); } if (group.Success && tryParse(group.Value, out TNum rslt)) { arguments[i] = rslt; return(NumberParseResult.Success); } if (isNullable) { return(NumberParseResult.Success); } IRCConnection.SendMessage(group.Success ? "@{0}, “{1}” is not a valid number." : "@{0}, the command could not be parsed.", msg.UserNickName, !msg.IsWhisper, msg.UserNickName, group.Success ? group.Value : null); return(NumberParseResult.Error); } // Strings if (parameters[i].ParameterType == typeof(string)) { arguments[i] = m.Success ? group.Value : null; } // Booleans — only specifies whether the group matched or not else if (parameters[i].ParameterType == typeof(bool)) { arguments[i] = group.Success; } // Numbers (int, float, double); includes nullables else if ( (result = IsNumber <int>(int.TryParse)) != NumberParseResult.NotOfDesiredType || (result = IsNumber <float>(float.TryParse)) != NumberParseResult.NotOfDesiredType || (result = IsNumber <double>(double.TryParse)) != NumberParseResult.NotOfDesiredType) { if (result == NumberParseResult.Error) { return(true); } } } // Built-in parameter names else if (parameters[i].ParameterType == typeof(string) && parameters[i].Name == "user") { arguments[i] = msg.UserNickName; } else if (parameters[i].ParameterType == typeof(string) && parameters[i].Name == "cmd") { arguments[i] = cmdStr; } else if (parameters[i].ParameterType == typeof(bool) && parameters[i].Name == "isWhisper") { arguments[i] = msg.IsWhisper; } else if (parameters[i].ParameterType == typeof(IRCMessage)) { arguments[i] = msg; } else if (parameters[i].ParameterType == typeof(KMGameInfo)) { arguments[i] = GetComponent <KMGameInfo>(); } else if (parameters[i].ParameterType == typeof(KMGameInfo.State)) { arguments[i] = CurrentState; } else if (parameters[i].ParameterType == typeof(FloatingHoldable) && extraObject is TwitchHoldable twitchHoldable) { arguments[i] = twitchHoldable.Holdable; } // Object we passed in (module, bomb, holdable) else if (parameters[i].ParameterType.IsAssignableFrom(typeof(TObj))) { arguments[i] = extraObject; } else if (parameters[i].IsOptional) { arguments[i] = parameters[i].DefaultValue; } else { IRCConnection.SendMessage("@{0}, this is a bug; please notify the devs. Error: the “{1}” command has an unrecognized parameter “{2}”. It expects a type of “{3}”, and the extraObject is of type “{4}”.", msg.UserNickName, !msg.IsWhisper, msg.UserNickName, command.Method.Name, parameters[i].Name, parameters[i].ParameterType.Name, extraObject?.GetType().Name); return(true); } } var invokeResult = command.Method.Invoke(null, arguments); if (invokeResult is bool invRes) { return(invRes); } else if (invokeResult is IEnumerator coroutine) { ProcessCommandCoroutine(coroutine, extraObject); } else if (invokeResult != null) { IRCConnection.SendMessage("@{0}, this is a bug; please notify the devs. Error: the “{1}” command returned something unrecognized.", msg.UserNickName, !msg.IsWhisper, msg.UserNickName, command.Method.Name); } if ((TwitchPlaySettings.data.AnarchyMode ? command.Attr.AccessLevelAnarchy : command.Attr.AccessLevel) > AccessLevel.Defuser) { AuditLog.Log(msg.UserNickName, UserAccess.HighestAccessLevel(msg.UserNickName), msg.Text); } return(true); }