private static void ProcessCommandsWorkerThread() { while (!ShuttingDown) { CommandReadyHandle.WaitOne(); try { //if (NextCommand.Actor.ConnectedClient != null) // NextCommand.Actor.ConnectedClient.TimeOfLastCommand = DateTime.Now; NextCommand.Actor.CommandHandler.HandleCommand(NextCommand); } catch (System.Threading.ThreadAbortException) { LogError("Command worker thread was aborted. Timeout hit?"); Core.ClearPendingMessages(); } catch (Exception e) { LogCommandError(e); Core.ClearPendingMessages(); } NextCommand = null; CommandFinishedHandle.Set(); } }
/// <summary> /// Process commands in a single thread, until there are no more queued commands. /// Heartbeat is called between every command. /// </summary> public static void ProcessCommands() { if ((Core.Flags & StartupFlags.SingleThreaded) != StartupFlags.SingleThreaded) { throw new InvalidOperationException("ProcessCommands should only be called in single threaded mode."); } while (PendingCommands.Count > 0 && !ShuttingDown) { GlobalRules.ConsiderPerformRule("heartbeat"); Core.SendPendingMessages(); PendingCommand PendingCommand = null; try { PendingCommand = PendingCommands.FirstOrDefault(); if (PendingCommand != null) { PendingCommands.Remove(PendingCommand); } } catch (Exception e) { LogCommandError(e); PendingCommand = null; } if (PendingCommand != null) { NextCommand = PendingCommand; //Reset flags that the last command may have changed CommandTimeoutEnabled = true; SilentFlag = false; GlobalRules.LogRules(null); try { var handler = NextCommand.Actor.GetProperty <ClientCommandHandler>("command handler"); if (handler != null) { handler.HandleCommand(NextCommand); } } catch (Exception e) { LogCommandError(e); Core.DiscardPendingMessages(); } if (PendingCommand.ProcessingCompleteCallback != null) { PendingCommand.ProcessingCompleteCallback(); } } } }
public MatchedCommand ParseCommand(PendingCommand Command) { var tokens = new LinkedList<String>(Command.RawCommand.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries)); var rootMatch = new PossibleMatch(tokens.First); rootMatch.Upsert("ACTOR", Command.Actor); rootMatch.Upsert("LOCATION", Command.Actor == null ? null : Command.Actor.Location); // A pending command can have some properties set when it is queued. For example, the 'go' action // generates a 'look' command after processing. It will give the 'look' command a property named // 'auto' to indicate that the look command was generated. Rules can then use this property to // implement behavior. From the same example, 'look' may emit a brief description if it is generated // by the 'go' action, but emit a more detailed description if the player enters the command 'look'. // See StandardActions.Go if (Command.PreSettings != null) foreach (var setting in Command.PreSettings) rootMatch.Upsert(setting.Key.ToUpper(), setting.Value); var matchContext = new MatchContext { ExecutingActor = Command.Actor }; // Try every single command defined, until one matches. foreach (var command in Commands) { IEnumerable<PossibleMatch> matches; try { matches = command.Matcher.Match(rootMatch, matchContext); } catch (MatchAborted ma) { // The match didn't fail; it generated an error. These means the match progressed to a point // where the author of the command felt that the input could not logically match any other // command, however, the input was still malformed in some way. Abort matching, and dummy up // a command entry to display the error message to the player. return new MatchedCommand( new CommandEntry().ProceduralRule((match, actor) => { MudObject.SendMessage(actor, ma.Message); return PerformResult.Continue; }), // We need a fake match just so it can be passed to the procedural rule. new PossibleMatch[] { new PossibleMatch(null) }); } // Only accept matches that consumed all of the input. matches = matches.Where(m => m.Next == null); // If we did consume all of the input, we will assume this match is successful. Note it is // possible for a command to generate multiple matches, but not possible to match multiple commands. if (matches.Count() > 0) return new MatchedCommand(command, matches); } return null; }
/// <summary> /// Process commands in a single thread, until there are no more queued commands. /// Heartbeat is called between every command. /// </summary> public static void ProcessCommands() { if ((Core.Flags & StartupFlags.SingleThreaded) != StartupFlags.SingleThreaded) throw new InvalidOperationException("ProcessCommands should only be called in single threaded mode."); while (PendingCommands.Count > 0 && !ShuttingDown) { GlobalRules.ConsiderPerformRule("heartbeat"); Core.SendPendingMessages(); PendingCommand PendingCommand = null; try { PendingCommand = PendingCommands.FirstOrDefault(); if (PendingCommand != null) PendingCommands.Remove(PendingCommand); } catch (Exception e) { LogCommandError(e); PendingCommand = null; } if (PendingCommand != null) { NextCommand = PendingCommand; //Reset flags that the last command may have changed CommandTimeoutEnabled = true; SilentFlag = false; GlobalRules.LogRules(null); try { NextCommand.Actor.CommandHandler.HandleCommand(NextCommand); } catch (Exception e) { LogCommandError(e); Core.ClearPendingMessages(); } if (PendingCommand.ProcessingCompleteCallback != null) PendingCommand.ProcessingCompleteCallback(); } } }
public void HandleCommand(PendingCommand Command) { try { var matchedCommand = Parser.ParseCommand(Command); if (matchedCommand != null) { Core.ProcessPlayerCommand(matchedCommand.Command, matchedCommand.Matches[0], Command.Actor); } else MudObject.SendMessage(Command.Actor, "I do not understand."); } catch (Exception e) { Core.ClearPendingMessages(); MudObject.SendMessage(Command.Actor, e.Message); } }
public void HandleCommand(PendingCommand Command) { Command.Actor.SetProperty("command handler", ParentHandler); //Just retry if the attempt to help has failed. if (DisambigObjects == null) { Core.EnqueuActorCommand(Command); return; } int ordinal = 0; if (Int32.TryParse(Command.RawCommand, out ordinal)) { if (ordinal < 0 || ordinal >= DisambigObjects.Count) { Core.SendMessage(Command.Actor, "That wasn't a valid option. I'm aborting disambiguation."); } else { var choosenMatches = MatchedCommand.Matches.Where(m => Object.ReferenceEquals(m[DisambigArgument], DisambigObjects[ordinal])); MatchedCommand.Matches = new List <PossibleMatch>(choosenMatches); if (MatchedCommand.Matches.Count == 1) { Core.ProcessPlayerCommand(MatchedCommand.Command, MatchedCommand.Matches[0], Command.Actor); } else { // WHat? There are still multiple options? Core.SendMessage(Command.Actor, "That helped narrow it down, but I'm still not sure what you mean."); Command.Actor.SetProperty("command handler", new DisambigCommandHandler(Command.Actor, MatchedCommand, ParentHandler)); } } } else { // The input was nor an ordial. Go ahead and requeue the command so the normal command handler // can take care of it. Core.EnqueuActorCommand(Command); } }
public void HandleCommand(PendingCommand Command) { try { var matchedCommand = Parser.ParseCommand(Command); if (matchedCommand != null) { Core.ProcessPlayerCommand(matchedCommand.Command, matchedCommand.Matches[0], Command.Actor); } else { Core.SendMessage(Command.Actor, "I do not understand."); } } catch (Exception e) { Core.DiscardPendingMessages(); Core.SendMessage(Command.Actor, e.Message); } }
public static void EnqueuActorCommand(PendingCommand Command) { PendingCommandLock.WaitOne(); PendingCommands.AddLast(Command); PendingCommandLock.ReleaseMutex(); }
private static void ProcessThreadedCommands() { if ((Core.Flags & StartupFlags.SingleThreaded) == StartupFlags.SingleThreaded) { throw new InvalidOperationException("ProcessThreadedCommands should never be called in single threaded mode."); } IndividualCommandThread = new Thread(ProcessCommandsWorkerThread); IndividualCommandThread.Start(); while (!ShuttingDown) { System.Threading.Thread.Sleep(10); DatabaseLock.WaitOne(); Heartbeat(); DatabaseLock.ReleaseMutex(); while (PendingCommands.Count > 0 && !ShuttingDown) { PendingCommand PendingCommand = null; PendingCommandLock.WaitOne(); try { PendingCommand = PendingCommands.FirstOrDefault(pc => { return(true); //if (pc.Actor.ConnectedClient == null) return true; //else return (DateTime.Now - pc.Actor.ConnectedClient.TimeOfLastCommand).TotalMilliseconds > SettingsObject.AllowedCommandRate; }); if (PendingCommand != null) { PendingCommands.Remove(PendingCommand); } } catch (Exception e) { LogCommandError(e); PendingCommand = null; } PendingCommandLock.ReleaseMutex(); if (PendingCommand != null) { DatabaseLock.WaitOne(); NextCommand = PendingCommand; //Reset flags that the last command may have changed CommandTimeoutEnabled = true; SilentFlag = false; GlobalRules.LogRules(null); CommandReadyHandle.Set(); //Signal worker thread to proceed. if (!CommandFinishedHandle.WaitOne(SettingsObject.CommandTimeOut)) { if (!CommandTimeoutEnabled) //Timeout is disabled, go ahead and wait for infinity. { CommandFinishedHandle.WaitOne(); } else { //Kill the command processor thread. IndividualCommandThread.Abort(); ClearPendingMessages(); if (PendingCommand.Actor.ConnectedClient != null) { PendingCommand.Actor.ConnectedClient.Send("Command timeout.\r\n"); LogError(String.Format("Command timeout. {0} - {1}", PendingCommand.Actor.ConnectedClient.ConnectionDescription, PendingCommand.RawCommand)); } else { LogError(String.Format("Command timeout [No client] - {1}", PendingCommand.RawCommand)); } IndividualCommandThread = new Thread(ProcessCommandsWorkerThread); IndividualCommandThread.Start(); } } if (PendingCommand.ProcessingCompleteCallback != null) { PendingCommand.ProcessingCompleteCallback(); } DatabaseLock.ReleaseMutex(); } } } IndividualCommandThread.Abort(); if (Core.OnShutDown != null) { Core.OnShutDown(); } }
public void HandleCommand(PendingCommand Command) { Command.Actor.CommandHandler = ParentHandler; AuthenticatingCommand(Command.Actor, UserName, Command.RawCommand); }
public void HandleCommand(PendingCommand Command) { if (String.IsNullOrEmpty(Command.RawCommand)) { return; } bool displayMatches = false; bool displayTime = false; // Debug commands always start with '@'. if (Command.RawCommand[0] == '@') { if (Command.RawCommand.ToUpper().StartsWith("@MATCH ")) { // Display all matches of the player's input. Do not actually execute the command. Command.RawCommand = Command.RawCommand.Substring("@MATCH ".Length); displayMatches = true; } else if (Command.RawCommand.ToUpper().StartsWith("@TIME ")) { // Time how long the command takes to match and execute. Command.RawCommand = Command.RawCommand.Substring("@TIME ".Length); displayTime = true; } else if (Command.RawCommand.ToUpper().StartsWith("@DEBUG ")) { // Turn off the automatic command execution timeout while executing the command. // Use this when testing a command in the debugger. Otherwise, the command processing thread might // be aborted while you are debugging it. Command.RawCommand = Command.RawCommand.Substring("@DEBUG ".Length); if (Command.Actor.Rank < 500) { MudObject.SendMessage(Command.Actor, "You do not have sufficient rank to use the debug command."); return; } // This will be reset by the command queue before the next command is parsed. Core.CommandTimeoutEnabled = false; } else if (Command.RawCommand.ToUpper().StartsWith("@RULES ")) { // Display all the rules invoked while executing this command. Command.RawCommand = Command.RawCommand.Substring("@RULES ".Length); Core.GlobalRules.LogRules(Command.Actor); } else { MudObject.SendMessage(Command.Actor, "I don't recognize that debugging command."); return; } } var startTime = DateTime.Now; var matchedCommand = Core.DefaultParser.ParseCommand(Command); if (displayMatches) { var matchEndTime = DateTime.Now; if (matchedCommand == null) { MudObject.SendMessage(Command.Actor, String.Format("Matched nothing in {0:n0} milliseconds.", (matchEndTime - startTime).TotalMilliseconds)); } else { MudObject.SendMessage(Command.Actor, String.Format("Matched {0} in {1:n0} milliseconds. {2} unique matches.", matchedCommand.Command.ManualName, (matchEndTime - startTime).TotalMilliseconds, matchedCommand.Matches.Count)); foreach (var match in matchedCommand.Matches) { var builder = new StringBuilder(); foreach (var arg in match) { builder.Append("["); builder.Append(arg.Key); builder.Append(" : "); builder.Append(arg.Value.ToString()); builder.Append("] "); } MudObject.SendMessage(Command.Actor, builder.ToString()); } } } else { if (matchedCommand != null) { // If there are multiple matches, replace this handler with a disambiguation handler. if (matchedCommand.Matches.Count > 1) { Command.Actor.CommandHandler = new DisambigCommandHandler(Command.Actor, matchedCommand, this); } else { Core.ProcessPlayerCommand(matchedCommand.Command, matchedCommand.Matches[0], Command.Actor); } } else { MudObject.SendMessage(Command.Actor, "huh?"); } } Core.GlobalRules.LogRules(null); if (displayTime) { var endTime = DateTime.Now; MudObject.SendMessage(Command.Actor, String.Format("Command completed in {0} milliseconds.", (endTime - startTime).TotalMilliseconds)); } }
private static void ProcessThreadedCommands() { if ((Core.Flags & StartupFlags.SingleThreaded) == StartupFlags.SingleThreaded) throw new InvalidOperationException("ProcessThreadedCommands should never be called in single threaded mode."); IndividualCommandThread = new Thread(ProcessCommandsWorkerThread); IndividualCommandThread.Start(); while (!ShuttingDown) { System.Threading.Thread.Sleep(10); DatabaseLock.WaitOne(); Heartbeat(); DatabaseLock.ReleaseMutex(); while (PendingCommands.Count > 0 && !ShuttingDown) { PendingCommand PendingCommand = null; PendingCommandLock.WaitOne(); try { PendingCommand = PendingCommands.FirstOrDefault(pc => { return true; //if (pc.Actor.ConnectedClient == null) return true; //else return (DateTime.Now - pc.Actor.ConnectedClient.TimeOfLastCommand).TotalMilliseconds > SettingsObject.AllowedCommandRate; }); if (PendingCommand != null) PendingCommands.Remove(PendingCommand); } catch (Exception e) { LogCommandError(e); PendingCommand = null; } PendingCommandLock.ReleaseMutex(); if (PendingCommand != null) { DatabaseLock.WaitOne(); NextCommand = PendingCommand; //Reset flags that the last command may have changed CommandTimeoutEnabled = true; SilentFlag = false; GlobalRules.LogRules(null); CommandReadyHandle.Set(); //Signal worker thread to proceed. if (!CommandFinishedHandle.WaitOne(SettingsObject.CommandTimeOut)) { if (!CommandTimeoutEnabled) //Timeout is disabled, go ahead and wait for infinity. CommandFinishedHandle.WaitOne(); else { //Kill the command processor thread. IndividualCommandThread.Abort(); ClearPendingMessages(); if (PendingCommand.Actor.ConnectedClient != null) { PendingCommand.Actor.ConnectedClient.Send("Command timeout.\r\n"); LogError(String.Format("Command timeout. {0} - {1}", PendingCommand.Actor.ConnectedClient.ConnectionDescription, PendingCommand.RawCommand)); } else LogError(String.Format("Command timeout [No client] - {1}", PendingCommand.RawCommand)); IndividualCommandThread = new Thread(ProcessCommandsWorkerThread); IndividualCommandThread.Start(); } } if (PendingCommand.ProcessingCompleteCallback != null) PendingCommand.ProcessingCompleteCallback(); DatabaseLock.ReleaseMutex(); } } } IndividualCommandThread.Abort(); if (Core.OnShutDown != null) Core.OnShutDown(); }
public MatchedCommand ParseCommand(PendingCommand Command) { var tokens = new LinkedList <String>(Command.RawCommand.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries)); var rootMatch = new PossibleMatch(tokens.First); rootMatch.Upsert("ACTOR", Command.Actor); rootMatch.Upsert("LOCATION", Command.Actor == null ? null : Command.Actor.Location); // A pending command can have some properties set when it is queued. For example, the 'go' action // generates a 'look' command after processing. It will give the 'look' command a property named // 'auto' to indicate that the look command was generated. Rules can then use this property to // implement behavior. From the same example, 'look' may emit a brief description if it is generated // by the 'go' action, but emit a more detailed description if the player enters the command 'look'. // See StandardActions.Go if (Command.PreSettings != null) { foreach (var setting in Command.PreSettings) { rootMatch.Upsert(setting.Key.ToUpper(), setting.Value); } } var matchContext = new MatchContext { ExecutingActor = Command.Actor }; // Try every single command defined, until one matches. foreach (var command in Commands) { IEnumerable <PossibleMatch> matches; try { matches = command.Matcher.Match(rootMatch, matchContext); } catch (MatchAborted ma) { // The match didn't fail; it generated an error. These means the match progressed to a point // where the author of the command felt that the input could not logically match any other // command, however, the input was still malformed in some way. Abort matching, and dummy up // a command entry to display the error message to the player. return(new MatchedCommand( new CommandEntry().ProceduralRule((match, actor) => { MudObject.SendMessage(actor, ma.Message); return PerformResult.Continue; }), // We need a fake match just so it can be passed to the procedural rule. new PossibleMatch[] { new PossibleMatch(null) })); } // Only accept matches that consumed all of the input. matches = matches.Where(m => m.Next == null); // If we did consume all of the input, we will assume this match is successful. Note it is // possible for a command to generate multiple matches, but not possible to match multiple commands. if (matches.Count() > 0) { return(new MatchedCommand(command, matches)); } } return(null); }
public void HandleCommand(PendingCommand Command) { Command.Actor.SetProperty("command handler", ParentHandler); AuthenticatingCommand(Command.Actor, UserName, Command.RawCommand); }
public void HandleCommand(PendingCommand Command) { if (String.IsNullOrEmpty(Command.RawCommand)) return; bool displayMatches = false; bool displayTime = false; // Debug commands always start with '@'. if (Command.RawCommand[0] == '@') { if (Command.RawCommand.ToUpper().StartsWith("@MATCH ")) { // Display all matches of the player's input. Do not actually execute the command. Command.RawCommand = Command.RawCommand.Substring("@MATCH ".Length); displayMatches = true; } else if (Command.RawCommand.ToUpper().StartsWith("@TIME ")) { // Time how long the command takes to match and execute. Command.RawCommand = Command.RawCommand.Substring("@TIME ".Length); displayTime = true; } else if (Command.RawCommand.ToUpper().StartsWith("@DEBUG ")) { // Turn off the automatic command execution timeout while executing the command. // Use this when testing a command in the debugger. Otherwise, the command processing thread might // be aborted while you are debugging it. Command.RawCommand = Command.RawCommand.Substring("@DEBUG ".Length); if (Command.Actor.Rank < 500) { MudObject.SendMessage(Command.Actor, "You do not have sufficient rank to use the debug command."); return; } // This will be reset by the command queue before the next command is parsed. Core.CommandTimeoutEnabled = false; } else if (Command.RawCommand.ToUpper().StartsWith("@RULES ")) { // Display all the rules invoked while executing this command. Command.RawCommand = Command.RawCommand.Substring("@RULES ".Length); Core.GlobalRules.LogRules(Command.Actor); } else { MudObject.SendMessage(Command.Actor, "I don't recognize that debugging command."); return; } } var startTime = DateTime.Now; var matchedCommand = Core.DefaultParser.ParseCommand(Command); if (displayMatches) { var matchEndTime = DateTime.Now; if (matchedCommand == null) { MudObject.SendMessage(Command.Actor, String.Format("Matched nothing in {0:n0} milliseconds.", (matchEndTime - startTime).TotalMilliseconds)); } else { MudObject.SendMessage(Command.Actor, String.Format("Matched {0} in {1:n0} milliseconds. {2} unique matches.", matchedCommand.Command.ManualName, (matchEndTime - startTime).TotalMilliseconds, matchedCommand.Matches.Count)); foreach (var match in matchedCommand.Matches) { var builder = new StringBuilder(); foreach (var arg in match) { builder.Append("["); builder.Append(arg.Key); builder.Append(" : "); builder.Append(arg.Value.ToString()); builder.Append("] "); } MudObject.SendMessage(Command.Actor, builder.ToString()); } } } else { if (matchedCommand != null) { // If there are multiple matches, replace this handler with a disambiguation handler. if (matchedCommand.Matches.Count > 1) Command.Actor.CommandHandler = new DisambigCommandHandler(Command.Actor, matchedCommand, this); else Core.ProcessPlayerCommand(matchedCommand.Command, matchedCommand.Matches[0], Command.Actor); } else MudObject.SendMessage(Command.Actor, "huh?"); } Core.GlobalRules.LogRules(null); if (displayTime) { var endTime = DateTime.Now; MudObject.SendMessage(Command.Actor, String.Format("Command completed in {0} milliseconds.", (endTime - startTime).TotalMilliseconds)); } }
public MatchedCommand ParseCommand(PendingCommand Command) { var tokens = new LinkedList <String>(Command.RawCommand.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries)); var rootMatch = new PossibleMatch(tokens.First); rootMatch.Upsert("ACTOR", Command.Actor); rootMatch.Upsert("LOCATION", Command.Actor == null ? null : Command.Actor.Location); // A pending command can have some properties set when it is queued. For example, the 'go' action // generates a 'look' command after processing. It will give the 'look' command a property named // 'auto' to indicate that the look command was generated. Rules can then use this property to // implement behavior. From the same example, 'look' may emit a brief description if it is generated // by the 'go' action, but emit a more detailed description if the player enters the command 'look'. // See StandardActions.Go if (Command.PreSettings != null) { foreach (var setting in Command.PreSettings) { rootMatch.Upsert(setting.Key.ToUpper(), setting.Value); } } var matchContext = new MatchContext { ExecutingActor = Command.Actor }; // Try every single command defined, until one matches. foreach (var command in Commands) { IEnumerable <PossibleMatch> matches; matchContext.CurrentParseCommand = command; try { matches = command.Matcher.Match(rootMatch, matchContext); } catch (MatchAborted ma) { // The match didn't fail; it generated an error. This usually represents an oversite in the command's grammar. // Abort matching, and dummy up a command entry to display the error message to the player. return(new MatchedCommand( new CommandEntry().ProceduralRule((match, actor) => { Core.SendMessage(actor, ma.Message); return PerformResult.Continue; }), // We need a fake match just so it can be passed to the procedural rule. new PossibleMatch[] { new PossibleMatch(null) })); } // Only accept matches that consumed all of the input as valid, successful matches. matches = matches.Where(m => m.Next == null); // If we did consume all of the input, we will assume this match is successful. Note it is // possible for a command to generate multiple matches, but not possible to match multiple commands. if (matches.Count() > 0) { return(new MatchedCommand(command, matches)); } } // No command matched; lets return a dummy command that display's the huh? text. return(new MatchedCommand( new CommandEntry() .ProceduralRule((match, actor) => { // Todo: Expand match arguments into error message. if (matchContext.BestFailedCommand != null && matchContext.BestFailedMatch.ParseDepth > 0) { Core.SendMessage(actor, "That's the name of a command, but I couldn't figure out what you meant."); Core.SendMessage(actor, "The best failed match was " + matchContext.BestFailedCommand.ManualName + ", which reached a depth of " + matchContext.BestFailedMatch.ParseDepth); if (!String.IsNullOrEmpty(matchContext.BestFailedParseStageDescription)) { Core.SendMessage(actor, Core.FormatParserMessage(actor, matchContext.BestFailedParseStageDescription, matchContext.BestFailedMatch)); } } else { Core.SendMessage(actor, "I don't think that is a command I know. I could not parse any of it."); } return PerformResult.Continue; }), new PossibleMatch[] { new PossibleMatch(null) } )); }
public void HandleCommand(PendingCommand Command) { Command.Actor.CommandHandler = ParentHandler; //Just retry if the attempt to help has failed. if (DisambigObjects == null) { Core.EnqueuActorCommand(Command); return; } int ordinal = 0; if (Int32.TryParse(Command.RawCommand, out ordinal)) { if (ordinal < 0 || ordinal >= DisambigObjects.Count) MudObject.SendMessage(Command.Actor, "That wasn't a valid option. I'm aborting disambiguation."); else { var choosenMatches = MatchedCommand.Matches.Where(m => Object.ReferenceEquals(m[DisambigArgument], DisambigObjects[ordinal])); MatchedCommand.Matches = new List<PossibleMatch>(choosenMatches); if (MatchedCommand.Matches.Count == 1) Core.ProcessPlayerCommand(MatchedCommand.Command, MatchedCommand.Matches[0], Command.Actor); else { // WHat? There are still multiple options? MudObject.SendMessage(Command.Actor, "That helped narrow it down, but I'm still not sure what you mean."); Command.Actor.CommandHandler = new DisambigCommandHandler(Command.Actor, MatchedCommand, ParentHandler); } } } else { // The input was nor an ordial. Go ahead and requeue the command so the normal command handler // can take care of it. Core.EnqueuActorCommand(Command); } }