public void TestTotalDuration(string input, int expectedDur, int defaultDur, string[] validInputs) { Parser parser = new Parser(); string inputRegex = parser.BuildInputRegex(validInputs); ParsedInputSequence inputSequence = parser.ParseInputs(input, inputRegex, new ParserOptions(0, defaultDur, false)); Assert.AreEqual(inputSequence.TotalDuration, expectedDur); }
public void TestOneInput(string input, bool checkDur, int maxDur) { Parser parser = new Parser(); string inputRegex = parser.BuildInputRegex(new string[] { input }); ParsedInputSequence inputSequence = parser.ParseInputs(input, inputRegex, new ParserOptions(0, 200, checkDur, maxDur)); Assert.AreEqual(inputSequence.ParsedInputResult, inputSequence.TotalDuration <= maxDur ? ParsedInputResults.Valid : ParsedInputResults.Invalid); Assert.AreEqual(inputSequence.Inputs.Count, 1); }
public void TestInputCount(string input, int expectedInputCount, string[] validInputs) { Parser parser = new Parser(); string inputRegex = parser.BuildInputRegex(validInputs); ParsedInputSequence inputSequence = parser.ParseInputs(input, inputRegex, new ParserOptions(0, 200, false)); int inputCount = 0; for (int i = 0; i < inputSequence.Inputs.Count; i++) { inputCount += inputSequence.Inputs[i].Count; } Assert.AreEqual(inputCount, expectedInputCount); }
public void TestSubInputDuration(string input, int[] expectedDuration, string[] validInputs) { Parser parser = new Parser(); string inputRegex = parser.BuildInputRegex(validInputs); ParsedInputSequence inputSequence = parser.ParseInputs(input, inputRegex, new ParserOptions(0, 200, false, 0)); int expectedDurIndex = 0; for (int i = 0; i < inputSequence.Inputs.Count; i++) { for (int j = 0; j < inputSequence.Inputs[i].Count; j++) { ParsedInput inp = inputSequence.Inputs[i][j]; Assert.AreEqual(inp.duration, expectedDuration[expectedDurIndex]); expectedDurIndex++; } } }
public override void ExecuteCommand(EvtChatCommandArgs args) { string input = args.Command.ArgumentsAsString; if (string.IsNullOrEmpty(input) == true) { QueueMessage(UsageMessage); return; } GameConsole usedConsole = null; int lastConsoleID = 1; lastConsoleID = (int)DataHelper.GetSettingInt(SettingsConstants.LAST_CONSOLE, 1L); using (BotDBContext context = DatabaseManager.OpenContext()) { GameConsole lastConsole = context.Consoles.FirstOrDefault(c => c.ID == lastConsoleID); if (lastConsole != null) { //Create a new console using data from the database usedConsole = new GameConsole(lastConsole.Name, lastConsole.InputList, lastConsole.InvalidCombos); } } //If there are no valid inputs, don't attempt to parse if (usedConsole == null) { QueueMessage($"The current console does not point to valid data. Please set a different console to use, or if none are available, add one."); return; } if (usedConsole.ConsoleInputs.Count == 0) { QueueMessage($"The current console, \"{usedConsole.Name}\", does not have any available inputs. Cannot determine length."); return; } ParsedInputSequence inputSequence = default; try { string userName = args.Command.ChatMessage.Username; //Get default and max input durations //Use user overrides if they exist, otherwise use the global values int defaultDur = (int)DataHelper.GetUserOrGlobalDefaultInputDur(userName); int maxDur = (int)DataHelper.GetUserOrGlobalMaxInputDur(userName); string regexStr = usedConsole.InputRegex; string readyMessage = string.Empty; Parser parser = new Parser(); using (BotDBContext context = DatabaseManager.OpenContext()) { //Get input synonyms for this console IQueryable <InputSynonym> synonyms = context.InputSynonyms.Where(syn => syn.ConsoleID == lastConsoleID); //Prepare the message for parsing readyMessage = parser.PrepParse(input, context.Macros, synonyms); } //Parse inputs to get our parsed input sequence inputSequence = parser.ParseInputs(readyMessage, regexStr, new ParserOptions(0, defaultDur, true, maxDur)); } catch (Exception exception) { string excMsg = exception.Message; //Handle parsing exceptions inputSequence.ParsedInputResult = ParsedInputResults.Invalid; QueueMessage($"Invalid input: {excMsg}", Serilog.Events.LogEventLevel.Warning); return; } //Check for non-valid messages if (inputSequence.ParsedInputResult != ParsedInputResults.Valid) { const string dyMacroLenErrorMsg = "Note that length cannot be determined for dynamic macros without inputs filled in."; if (inputSequence.ParsedInputResult == ParsedInputResults.NormalMsg || string.IsNullOrEmpty(inputSequence.Error) == true) { QueueMessage($"Invalid input. {dyMacroLenErrorMsg}"); } else { QueueMessage($"Invalid input: {inputSequence.Error}. {dyMacroLenErrorMsg}"); } return; } QueueMessage($"Total length: {inputSequence.TotalDuration}ms"); }
public override void ExecuteCommand(EvtChatCommandArgs args) { string input = args.Command.ArgumentsAsString; if (string.IsNullOrEmpty(input) == true) { QueueMessage(UsageMessage); return; } GameConsole usedConsole = null; int lastConsoleID = (int)DataHelper.GetSettingInt(SettingsConstants.LAST_CONSOLE, 1L); using (BotDBContext context = DatabaseManager.OpenContext()) { GameConsole lastConsole = context.Consoles.FirstOrDefault(c => c.ID == lastConsoleID); if (lastConsole != null) { //Create a new console using data from the database usedConsole = new GameConsole(lastConsole.Name, lastConsole.InputList, lastConsole.InvalidCombos); } } //If there are no valid inputs, don't attempt to parse if (usedConsole == null) { QueueMessage($"The current console does not point to valid data. Please set a different console to use, or if none are available, add one."); return; } if (usedConsole.ConsoleInputs.Count == 0) { QueueMessage($"The current console, \"{usedConsole.Name}\", does not have any available inputs."); return; } ParsedInputSequence inputSequence = default; int defaultPort = 0; try { string userName = args.Command.ChatMessage.Username; //Get default and max input durations //Use user overrides if they exist, otherwise use the global values User user = DataHelper.GetUser(userName); defaultPort = (int)user.ControllerPort; int defaultDur = (int)DataHelper.GetUserOrGlobalDefaultInputDur(userName); int maxDur = (int)DataHelper.GetUserOrGlobalMaxInputDur(userName); string regexStr = usedConsole.InputRegex; string readyMessage = string.Empty; Parser parser = new Parser(); using (BotDBContext context = DatabaseManager.OpenContext()) { //Get input synonyms for this console IQueryable <InputSynonym> synonyms = context.InputSynonyms.Where(syn => syn.ConsoleID == lastConsoleID); //Prepare the message for parsing readyMessage = parser.PrepParse(input, context.Macros, synonyms); } //Parse inputs to get our parsed input sequence inputSequence = parser.ParseInputs(readyMessage, regexStr, new ParserOptions(defaultPort, defaultDur, true, maxDur)); } catch (Exception exception) { string excMsg = exception.Message; //Handle parsing exceptions inputSequence.ParsedInputResult = ParsedInputResults.Invalid; QueueMessage($"Invalid input: {excMsg}"); return; } //Check for invalid inputs if (inputSequence.ParsedInputResult != ParsedInputResults.Valid) { if (inputSequence.ParsedInputResult == ParsedInputResults.NormalMsg || string.IsNullOrEmpty(inputSequence.Error) == true) { QueueMessage($"Invalid input."); } else { QueueMessage($"Invalid input: {inputSequence.Error}."); } return; } //Reverse parse the string string reverseParsed = ReverseParser.ReverseParseNatural(inputSequence, usedConsole, new ReverseParser.ReverseParserOptions(ReverseParser.ShowPortTypes.ShowNonDefaultPorts, defaultPort)); int botCharLimit = (int)DataHelper.GetSettingInt(SettingsConstants.BOT_MSG_CHAR_LIMIT, 500L); QueueMessageSplit(reverseParsed, botCharLimit, ", "); }
public override void ExecuteCommand(EvtChatCommandArgs args) { List <string> arguments = args.Command.ArgumentsAsList; if (arguments.Count < 2) { QueueMessage(UsageMessage); return; } string macroName = arguments[0].ToLowerInvariant(); //Make sure the first argument has at least a minimum number of characters if (macroName.Length < MIN_MACRO_NAME_LENGTH) { QueueMessage($"Input macros need to be at least {MIN_MACRO_NAME_LENGTH} characters long."); return; } if (macroName.StartsWith(Parser.DEFAULT_PARSER_REGEX_MACRO_INPUT) == false) { QueueMessage($"Input macros must start with \"{Parser.DEFAULT_PARSER_REGEX_MACRO_INPUT}\"."); return; } //For simplicity with wait inputs, force the first character in the macro name to be alphanumeric if (char.IsLetterOrDigit(arguments[0][1]) == false) { QueueMessage("The first character in input macro names must be alphanumeric."); return; } //Check for max macro name if (macroName.Length > MAX_MACRO_NAME_LENGTH) { QueueMessage($"Input macros may have up to a max of {MAX_MACRO_NAME_LENGTH} characters in their name."); return; } int curConsoleID = (int)DataHelper.GetSettingInt(SettingsConstants.LAST_CONSOLE, 1L); GameConsole consoleInstance = null; using (BotDBContext context = DatabaseManager.OpenContext()) { GameConsole curConsole = context.Consoles.FirstOrDefault(c => c.ID == curConsoleID); if (curConsole == null) { QueueMessage("Cannot validate input macro, as the current console is invalid. Fix this by setting another console."); return; } consoleInstance = new GameConsole(curConsole.Name, curConsole.InputList); } Parser parser = new Parser(); //Trim the macro name from the input sequence string macroVal = args.Command.ArgumentsAsString.Remove(0, macroName.Length + 1).ToLowerInvariant(); //Console.WriteLine(macroVal); bool isDynamic = false; //Check for a dynamic macro int openParenIndex = macroName.IndexOf('(', 0); if (openParenIndex >= 0) { //If we found the open parenthesis, check for the asterisk //This is not comprehensive, but it should smooth out a few issues if (openParenIndex == (macroName.Length - 1) || macroName[openParenIndex + 1] != '*') { QueueMessage("Invalid input macro. Dynamic macro arguments must be specified with \"*\"."); return; } if (macroName[macroName.Length - 1] != ')') { QueueMessage("Invalid input macro. Dynamic macros must end with \")\"."); return; } isDynamic = true; } //Validate input if not dynamic if (isDynamic == false) { ParsedInputSequence inputSequence = default; try { string userName = args.Command.ChatMessage.Username; //Get default and max input durations //Use user overrides if they exist, otherwise use the global values int defaultDur = (int)DataHelper.GetUserOrGlobalDefaultInputDur(userName); int maxDur = (int)DataHelper.GetUserOrGlobalMaxInputDur(userName); string regexStr = consoleInstance.InputRegex; string readyMessage = string.Empty; using (BotDBContext context = DatabaseManager.OpenContext()) { IQueryable <InputSynonym> synonyms = context.InputSynonyms.Where(syn => syn.ConsoleID == curConsoleID); readyMessage = parser.PrepParse(macroVal, context.Macros, synonyms); } inputSequence = parser.ParseInputs(readyMessage, regexStr, new ParserOptions(0, defaultDur, true, maxDur)); //Console.WriteLine(inputSequence.ToString()); if (inputSequence.ParsedInputResult != ParsedInputResults.Valid) { if (string.IsNullOrEmpty(inputSequence.Error) == true) { QueueMessage("Invalid input macro."); } else { QueueMessage($"Invalid input macro: {inputSequence.Error}"); } return; } } catch (Exception e) { QueueMessage($"Invalid input macro: {e.Message}"); return; } } string message = string.Empty; using (BotDBContext context = DatabaseManager.OpenContext()) { InputMacro inputMacro = context.Macros.FirstOrDefault(m => m.MacroName == macroName); //Not an existing macro, so add it if (inputMacro == null) { InputMacro newMacro = new InputMacro(macroName, macroVal); context.Macros.Add(newMacro); if (isDynamic == false) { message = $"Added input macro \"{macroName}\"!"; } else { message = $"Added dynamic input macro \"{macroName}\"! Dynamic input macros can't be validated beforehand, so verify it works manually."; } } //Update the macro value else { inputMacro.MacroValue = macroVal; if (isDynamic == false) { message = $"Updated input macro \"{macroName}\"!"; } else { message = $"Updated dynamic input macro \"{macroName}\"! Dynamic input macros can't be validated beforehand, so verify it works manually."; } } context.SaveChanges(); } QueueMessage(message); }
private void ProcessMsgAsInput(EvtUserMessageArgs e) { //Ignore commands as inputs if (e.UsrMessage.Message.StartsWith(DataConstants.COMMAND_IDENTIFIER) == true) { return; } GameConsole usedConsole = null; int lastConsoleID = 1; using (BotDBContext context = DatabaseManager.OpenContext()) { lastConsoleID = (int)DataHelper.GetSettingIntNoOpen(SettingsConstants.LAST_CONSOLE, context, 1L); GameConsole lastConsole = context.Consoles.FirstOrDefault(c => c.ID == lastConsoleID); if (lastConsole != null) { //Create a new console using data from the database usedConsole = new GameConsole(lastConsole.Name, lastConsole.InputList, lastConsole.InvalidCombos); } } //If there are no valid inputs, don't attempt to parse if (usedConsole == null) { MsgHandler.QueueMessage($"The current console does not point to valid data. Please set a different console to use, or if none are available, add one."); return; } if (usedConsole.ConsoleInputs.Count == 0) { MsgHandler.QueueMessage($"The current console, \"{usedConsole.Name}\", does not have any available inputs."); } ParsedInputSequence inputSequence = default; string userName = e.UsrMessage.Username; int defaultDur = 200; int defaultPort = 0; try { int maxDur = 60000; string regexStr = usedConsole.InputRegex; string readyMessage = string.Empty; //Get default and max input durations //Use user overrides if they exist, otherwise use the global values User user = DataHelper.GetUser(userName); //Get default controller port defaultPort = (int)user.ControllerPort; defaultDur = (int)DataHelper.GetUserOrGlobalDefaultInputDur(userName); maxDur = (int)DataHelper.GetUserOrGlobalMaxInputDur(userName); //TRBotLogger.Logger.Information($"Default dur: {defaultDur} | Max dur: {maxDur}"); using (BotDBContext context = DatabaseManager.OpenContext()) { //Get input synonyms for this console IQueryable <InputSynonym> synonyms = context.InputSynonyms.Where(syn => syn.ConsoleID == lastConsoleID); //Prepare the message for parsing readyMessage = InputParser.PrepParse(e.UsrMessage.Message, context.Macros, synonyms); } //Parse inputs to get our parsed input sequence inputSequence = InputParser.ParseInputs(readyMessage, regexStr, new ParserOptions(defaultPort, defaultDur, true, maxDur)); TRBotLogger.Logger.Debug(inputSequence.ToString()); TRBotLogger.Logger.Debug("Reverse Parsed (on parse): " + ReverseParser.ReverseParse(inputSequence, usedConsole, new ReverseParser.ReverseParserOptions(ReverseParser.ShowPortTypes.ShowNonDefaultPorts, defaultPort, ReverseParser.ShowDurationTypes.ShowNonDefaultDurations, defaultDur))); } catch (Exception exception) { string excMsg = exception.Message; //Handle parsing exceptions MsgHandler.QueueMessage($"ERROR PARSING: {excMsg} | {exception.StackTrace}", Serilog.Events.LogEventLevel.Warning); inputSequence.ParsedInputResult = ParsedInputResults.Invalid; } //Check for non-valid messages if (inputSequence.ParsedInputResult != ParsedInputResults.Valid) { //Display error message for invalid inputs if (inputSequence.ParsedInputResult == ParsedInputResults.Invalid) { MsgHandler.QueueMessage(inputSequence.Error); } return; } #region Parser Post-Process Validation /* All this validation may be able to be performed faster. * Find a way to speed it up. */ long globalInputPermLevel = DataHelper.GetSettingInt(SettingsConstants.GLOBAL_INPUT_LEVEL, 0L); int userControllerPort = 0; long userLevel = 0; using (BotDBContext context = DatabaseManager.OpenContext()) { User user = DataHelper.GetUserNoOpen(e.UsrMessage.Username, context); //Check if the user is silenced and ignore the message if so if (user.HasEnabledAbility(PermissionConstants.SILENCED_ABILITY) == true) { return; } //Ignore based on user level and permissions if (user.Level < globalInputPermLevel) { MsgHandler.QueueMessage($"Inputs are restricted to levels {(PermissionLevels)globalInputPermLevel} and above."); return; } userControllerPort = (int)user.ControllerPort; userLevel = user.Level; } //First, add delays between inputs if we should //We do this first so we can validate the inserted inputs later //The blank inputs can have a different permission level if (DataHelper.GetUserOrGlobalMidInputDelay(e.UsrMessage.Username, out long midInputDelay) == true) { MidInputDelayData midInputDelayData = ParserPostProcess.InsertMidInputDelays(inputSequence, userControllerPort, (int)midInputDelay, usedConsole); //If it's successful, replace the input list and duration if (midInputDelayData.Success == true) { int oldDur = inputSequence.TotalDuration; inputSequence.Inputs = midInputDelayData.NewInputs; inputSequence.TotalDuration = midInputDelayData.NewTotalDuration; //TRBotLogger.Logger.Debug($"Mid input delay success. Message: {midInputDelayData.Message} | OldDur: {oldDur} | NewDur: {inputSequence.TotalDuration}\n{ReverseParser.ReverseParse(inputSequence, usedConsole, new ReverseParser.ReverseParserOptions(ReverseParser.ShowPortTypes.ShowAllPorts, 0))}"); } } InputValidation validation = default; using (BotDBContext context = DatabaseManager.OpenContext()) { User user = DataHelper.GetUserNoOpen(userName, context); //Check for restricted inputs on this user validation = ParserPostProcess.InputSequenceContainsRestrictedInputs(inputSequence, user.GetRestrictedInputs()); if (validation.InputValidationType != InputValidationTypes.Valid) { if (string.IsNullOrEmpty(validation.Message) == false) { MsgHandler.QueueMessage(validation.Message); } return; } //Check for invalid input combinations validation = ParserPostProcess.ValidateInputCombos(inputSequence, usedConsole.InvalidCombos, DataContainer.ControllerMngr, usedConsole); if (validation.InputValidationType != InputValidationTypes.Valid) { if (string.IsNullOrEmpty(validation.Message) == false) { MsgHandler.QueueMessage(validation.Message); } return; } } //Check for level permissions and ports validation = ParserPostProcess.ValidateInputLvlPermsAndPorts(userLevel, inputSequence, DataContainer.ControllerMngr, usedConsole.ConsoleInputs); if (validation.InputValidationType != InputValidationTypes.Valid) { if (string.IsNullOrEmpty(validation.Message) == false) { MsgHandler.QueueMessage(validation.Message, Serilog.Events.LogEventLevel.Warning); } return; } #endregion //Make sure inputs aren't stopped if (InputHandler.InputsHalted == true) { //We can't process inputs because they're currently stopped MsgHandler.QueueMessage("New inputs cannot be processed until all other inputs have stopped.", Serilog.Events.LogEventLevel.Warning); return; } //Fetch these values ahead of time to avoid passing the database context through so many methods long autoPromoteEnabled = DataHelper.GetSettingInt(SettingsConstants.AUTO_PROMOTE_ENABLED, 0L); long autoPromoteInputReq = DataHelper.GetSettingInt(SettingsConstants.AUTO_PROMOTE_INPUT_REQ, long.MaxValue); long autoPromoteLevel = DataHelper.GetSettingInt(SettingsConstants.AUTO_PROMOTE_LEVEL, -1L); string autoPromoteMsg = DataHelper.GetSettingString(SettingsConstants.AUTOPROMOTE_MESSAGE, string.Empty); bool addedInputCount = false; TRBotLogger.Logger.Debug($"Reverse Parsed (post-process): " + ReverseParser.ReverseParse(inputSequence, usedConsole, new ReverseParser.ReverseParserOptions(ReverseParser.ShowPortTypes.ShowNonDefaultPorts, defaultPort, ReverseParser.ShowDurationTypes.ShowNonDefaultDurations, defaultDur))); //Get the max recorded inputs per-user long maxUserRecInps = DataHelper.GetSettingInt(SettingsConstants.MAX_USER_RECENT_INPUTS, 0L); //It's a valid input - save it in the user's stats //Also record the input if we should using (BotDBContext context = DatabaseManager.OpenContext()) { User user = DataHelper.GetUserNoOpen(e.UsrMessage.Username, context); //Ignore if the user is opted out if (user.IsOptedOut == false) { user.Stats.ValidInputCount++; addedInputCount = true; context.SaveChanges(); //If we should store recent user inputs, do so if (maxUserRecInps > 0) { //Get the input sequence - we may have added mid input delays between //As a result, we'll need to reverse parse it string message = ReverseParser.ReverseParse(inputSequence, usedConsole, new ReverseParser.ReverseParserOptions(ReverseParser.ShowPortTypes.ShowNonDefaultPorts, (int)user.ControllerPort, ReverseParser.ShowDurationTypes.ShowNonDefaultDurations, defaultDur)); //Add the recorded input user.RecentInputs.Add(new RecentInput(message)); context.SaveChanges(); int diff = user.RecentInputs.Count - (int)maxUserRecInps; //If we're over the max after adding, remove if (diff > 0) { //Order by ascending ID and take the difference //Lower IDs = older entries IEnumerable <RecentInput> shouldRemove = user.RecentInputs.OrderBy(r => r.UserID).Take(diff); foreach (RecentInput rec in shouldRemove) { user.RecentInputs.Remove(rec); context.SaveChanges(); } } } } } bool autoPromoted = false; //Check if auto promote is enabled and auto promote the user if applicable if (addedInputCount == true) { using (BotDBContext context = DatabaseManager.OpenContext()) { User user = DataHelper.GetUserNoOpen(e.UsrMessage.Username, context); //Check if the user was already autopromoted, autopromote is enabled, //and if the user reached the autopromote input count requirement if (user.Stats.AutoPromoted == 0 && autoPromoteEnabled > 0 && user.Stats.ValidInputCount >= autoPromoteInputReq) { //Only autopromote if this is a valid permission level //We may not want to log or send a message for this, as it has potential to be very spammy, //and it's not something the users can control if (PermissionHelpers.IsValidPermissionValue(autoPromoteLevel) == true) { //Mark the user as autopromoted and save user.Stats.AutoPromoted = 1; autoPromoted = true; context.SaveChanges(); } } } } if (autoPromoted == true) { //If the user is already at or above this level, don't set them to it //Only set if the user is below if (userLevel < autoPromoteLevel) { //Adjust abilities and promote to the new level DataHelper.AdjustUserLvlAndAbilitiesOnLevel(userName, autoPromoteLevel); if (string.IsNullOrEmpty(autoPromoteMsg) == false) { PermissionLevels permLvl = (PermissionLevels)autoPromoteLevel; string finalMsg = autoPromoteMsg.Replace("{0}", userName).Replace("{1}", permLvl.ToString()); MsgHandler.QueueMessage(finalMsg); } } } InputModes inputMode = (InputModes)DataHelper.GetSettingInt(SettingsConstants.INPUT_MODE, 0L); //If the mode is Democracy, add it as a vote for this input if (inputMode == InputModes.Democracy) { //Set up the routine if it doesn't exist BaseRoutine foundRoutine = RoutineHandler.FindRoutine(RoutineConstants.DEMOCRACY_ROUTINE_ID, out int indexFound); DemocracyRoutine democracyRoutine = null; if (foundRoutine == null) { long voteTime = DataHelper.GetSettingInt(SettingsConstants.DEMOCRACY_VOTE_TIME, 10000L); democracyRoutine = new DemocracyRoutine(voteTime); RoutineHandler.AddRoutine(democracyRoutine); } else { democracyRoutine = (DemocracyRoutine)foundRoutine; } democracyRoutine.AddInputSequence(userName, inputSequence.Inputs); } //If it's Anarchy, carry out the input else { /************************************ * Finally carry out the inputs now! * ************************************/ InputHandler.CarryOutInput(inputSequence.Inputs, usedConsole, DataContainer.ControllerMngr); } }
public override void ExecuteCommand(EvtChatCommandArgs args) { List <string> arguments = args.Command.ArgumentsAsList; string userName = args.Command.ChatMessage.Username.ToLowerInvariant(); long userControllerPort = 0; long userLevel = 0; using (BotDBContext context = DatabaseManager.OpenContext()) { User user = DataHelper.GetUserNoOpen(userName, context); if (user == null) { QueueMessage("Huh, looks like you're not in the database!"); return; } if (user.HasEnabledAbility(PermissionConstants.INPUT_EXERCISE_ABILITY) == false) { QueueMessage("You do not have the ability to use input exercises!"); return; } userControllerPort = user.ControllerPort; userLevel = user.Level; } //Get the last console used int lastConsoleID = (int)DataHelper.GetSettingInt(SettingsConstants.LAST_CONSOLE, 1L); GameConsole usedConsole = null; using (BotDBContext context = DatabaseManager.OpenContext()) { GameConsole lastConsole = context.Consoles.FirstOrDefault(c => c.ID == lastConsoleID); if (lastConsole != null) { //Create a new console using data from the database usedConsole = new GameConsole(lastConsole.Name, lastConsole.InputList, lastConsole.InvalidCombos); } } //If there are no valid inputs, don't attempt to generate or solve if (usedConsole == null) { QueueMessage($"The current console does not point to valid data, making it impossible to solve or generate a new exercise. Please set a different console to use, or if none are available, add one."); return; } if (usedConsole.ConsoleInputs.Count == 0) { QueueMessage($"The current console, \"{usedConsole.Name}\", does not have any available inputs. Cannot determine solve or generate an exercise."); return; } string creditsName = DataHelper.GetCreditsName(); int botCharLimit = (int)DataHelper.GetSettingInt(SettingsConstants.BOT_MSG_CHAR_LIMIT, 500L); ReverseParser.ReverseParserOptions parseOptions = new ReverseParser.ReverseParserOptions(ReverseParser.ShowPortTypes.None, (int)userControllerPort); //Handle no arguments if (arguments.Count == 0) { if (UserExercises.TryGetValue(userName, out InputExercise inputExercise) == true) { OutputInputExercise(inputExercise, usedConsole, botCharLimit, creditsName, inputExercise.ParseOptions); } else { QueueMessage(NO_EXERCISE_FOUND_MSG); } return; } //If "new" is specified, generate a new input sequence if ((arguments.Count == 1 || arguments.Count == 2) && arguments[0].ToLowerInvariant() == GENERATE_NEW_ARG) { //Check for a difficulty argument if (arguments.Count == 2) { string difString = arguments[1].ToLowerInvariant(); //Check difficulty level if (difString == EASY_DIFFICULTY_ARG) { parseOptions.ShowPortType = ReverseParser.ShowPortTypes.None; } else if (difString == HARD_DIFFICULTY_ARG) { parseOptions.ShowPortType = ReverseParser.ShowPortTypes.ShowNonDefaultPorts; } else { QueueMessage($"Invalid difficulty level specified. Please choose either \"{EASY_DIFFICULTY_ARG}\" or \"{HARD_DIFFICULTY_ARG}\"."); return; } } //Use the global default input duration for consistency int defaultInputDur = (int)DataHelper.GetSettingInt(SettingsConstants.DEFAULT_INPUT_DURATION, 200L); //Generate the exercise ParsedInputSequence newSequence = GenerateExercise(userLevel, defaultInputDur, usedConsole, parseOptions); //Give greater credit rewards for longer input sequences long creditReward = (newSequence.Inputs.Count * BASE_CREDIT_REWARD) * CREDIT_REWARD_MULTIPLIER; //This parser option is set when performing a hard exercise if (parseOptions.ShowPortType == ReverseParser.ShowPortTypes.ShowNonDefaultPorts) { creditReward = (long)Math.Ceiling(creditReward * HARD_EXERCISE_MULTIPLIER); } InputExercise inputExercise = new InputExercise(newSequence, parseOptions, creditReward); UserExercises[userName] = inputExercise; OutputInputExercise(inputExercise, usedConsole, botCharLimit, creditsName, parseOptions); return; } //Make sure the user has an exercise - if not, output the same message if (UserExercises.TryGetValue(userName, out InputExercise exercise) == false) { QueueMessage(NO_EXERCISE_FOUND_MSG); return; } //There's more than one argument and the user has an exercise; so it has to be the input //Let's validate it! if (ExecuteValidateInput(userName, args.Command.ArgumentsAsString, usedConsole, parseOptions) == true) { using (BotDBContext context = DatabaseManager.OpenContext()) { User user = DataHelper.GetUserNoOpen(userName, context); //Grant credits if the user isn't opted out if (user.IsOptedOut == false) { long creditReward = exercise.CreditReward; user.Stats.Credits += creditReward; context.SaveChanges(); QueueMessage($"Correct input! Awesome job! You've earned your {creditReward} {creditsName.Pluralize(false, creditReward)}!"); } else { QueueMessage("Correct input! Awesome job!"); } } //Remove the entry UserExercises.TryRemove(userName, out InputExercise value); } }
public override void ExecuteCommand(EvtChatCommandArgs args) { string userName = args.Command.ChatMessage.Username; string argInputSequence = args.Command.ArgumentsAsString; if (string.IsNullOrEmpty(argInputSequence) == true) { string periodicInputSequence = DataHelper.GetSettingString(SettingsConstants.PERIODIC_INPUT_VALUE, string.Empty); if (string.IsNullOrEmpty(periodicInputSequence) == true) { QueueMessage("The current periodic input sequence is not defined! You can define one by providing an input sequence as an argument."); } int botCharLimit = (int)DataHelper.GetSettingInt(SettingsConstants.BOT_MSG_CHAR_LIMIT, 500L); QueueMessageSplit($"The current periodic input sequence is: {periodicInputSequence}", botCharLimit, string.Empty); return; } //Replace all non-space whitespace in the argument with space for readability //Some platforms will do this by default (Ex. Twitch), but we can't rely on them all being consistent here argInputSequence = Helpers.ReplaceAllWhitespaceWithSpace(argInputSequence); long globalInputPermLevel = DataHelper.GetSettingInt(SettingsConstants.GLOBAL_INPUT_LEVEL, 0L); //Consider the user's level so they cannot use this to perform inputs normally unavailable to them long userLevel = 0L; using (BotDBContext context = DatabaseManager.OpenContext()) { User user = DataHelper.GetUserNoOpen(userName, context); //Check for permissions if (user == null || user.HasEnabledAbility(PermissionConstants.SET_PERIODIC_INPUT_SEQUENCE_ABILITY) == false) { QueueMessage("You do not have the ability to set the periodic input sequence!"); return; } //Check if the user is silenced if (user.HasEnabledAbility(PermissionConstants.SILENCED_ABILITY) == true) { QueueMessage("Nice try, but you can't set the periodic input sequence while silenced!"); return; } //Ignore based on user level and permissions if (user.Level < globalInputPermLevel) { QueueMessage($"Inputs are restricted to levels {(PermissionLevels)globalInputPermLevel} and above, so you can't set the periodic input sequence."); return; } userLevel = user.Level; } //Parse the input sequence //Set up the console GameConsole usedConsole = null; int lastConsoleID = (int)DataHelper.GetSettingInt(SettingsConstants.LAST_CONSOLE, 1L); using (BotDBContext context = DatabaseManager.OpenContext()) { GameConsole lastConsole = context.Consoles.FirstOrDefault(c => c.ID == lastConsoleID); if (lastConsole != null) { //Create a new console using data from the database usedConsole = new GameConsole(lastConsole.Name, lastConsole.InputList, lastConsole.InvalidCombos); } } //If there are no valid inputs, don't attempt to parse if (usedConsole == null) { DataContainer.MessageHandler.QueueMessage("The current console does not point to valid data. Please set a different console to use, or if none are available, add one."); return; } if (usedConsole.ConsoleInputs.Count == 0) { DataContainer.MessageHandler.QueueMessage($"The current console, \"{usedConsole.Name}\", does not have any available inputs."); return; } int defaultDur = (int)DataHelper.GetUserOrGlobalDefaultInputDur(string.Empty); int maxDur = (int)DataHelper.GetUserOrGlobalMaxInputDur(string.Empty); int defaultPeriodicInpPort = (int)DataHelper.GetSettingInt(SettingsConstants.PERIODIC_INPUT_PORT, 0L); ParsedInputSequence inputSequence = default; try { string regexStr = usedConsole.InputRegex; string readyMessage = string.Empty; Parser parser = new Parser(); using (BotDBContext context = DatabaseManager.OpenContext()) { //Get input synonyms for this console IQueryable <InputSynonym> synonyms = context.InputSynonyms.Where(syn => syn.ConsoleID == lastConsoleID); //Prepare the message for parsing readyMessage = parser.PrepParse(argInputSequence, context.Macros, synonyms); } //Parse inputs to get our parsed input sequence inputSequence = parser.ParseInputs(readyMessage, regexStr, new ParserOptions(defaultPeriodicInpPort, defaultDur, true, maxDur)); } catch (Exception exception) { string excMsg = exception.Message; //Handle parsing exceptions inputSequence.ParsedInputResult = ParsedInputResults.Invalid; DataContainer.MessageHandler.QueueMessage($"Couldn't parse periodic input sequence: {excMsg}"); return; } //Check for non-valid messages if (inputSequence.ParsedInputResult != ParsedInputResults.Valid) { string message = inputSequence.Error; if (string.IsNullOrEmpty(message) == true) { message = "Input is invalid"; } DataContainer.MessageHandler.QueueMessage($"Cannot set periodic input sequence: {message}"); return; } /* Perform some post-processing ahead of time since this input will be carried out */ using (BotDBContext context = DatabaseManager.OpenContext()) { User user = DataHelper.GetUserNoOpen(userName, context); //Check for restricted inputs on this user InputValidation inpValidation = ParserPostProcess.InputSequenceContainsRestrictedInputs(inputSequence, user.GetRestrictedInputs()); if (inpValidation.InputValidationType != InputValidationTypes.Valid) { if (string.IsNullOrEmpty(inpValidation.Message) == false) { QueueMessage(inpValidation.Message); } return; } } //Check for level permissions and ports InputValidation validation = ParserPostProcess.ValidateInputLvlPermsAndPorts(userLevel, inputSequence, DataContainer.ControllerMngr, usedConsole.ConsoleInputs); if (validation.InputValidationType != InputValidationTypes.Valid) { if (string.IsNullOrEmpty(validation.Message) == false) { QueueMessage(validation.Message); } return; } //Defer input combo validation to when it's about to be be executed //This way, the state of the virtual controllers now shouldn't //affect an otherwise valid combo from being accepted string newPeriodicInpSequence = ReverseParser.ReverseParse(inputSequence, usedConsole, new ReverseParser.ReverseParserOptions(ReverseParser.ShowPortTypes.ShowNonDefaultPorts, defaultPeriodicInpPort, ReverseParser.ShowDurationTypes.ShowNonDefaultDurations, defaultDur)); /* * Finally, after everything is good, set the input sequence! */ using (BotDBContext context = DatabaseManager.OpenContext()) { Settings periodicInputSeqSetting = DataHelper.GetSettingNoOpen(SettingsConstants.PERIODIC_INPUT_VALUE, context); periodicInputSeqSetting.ValueStr = newPeriodicInpSequence; context.SaveChanges(); } QueueMessage("Successfully set the periodic input sequence!"); }