Beispiel #1
0
        public override void Create(CommandParser Parser)
        {
            Parser.AddCommand(
                Sequence(
                    RequiredRank(500),
                    KeyWord("!RELOAD"),
                    MustMatch("It helps if you give me a path.",
                        Path("TARGET"))))
                .Manual("Given a path, it attempts to recompile that object. The object will be replaced in-place if possible.")
                .ProceduralRule((match, actor) =>
                {
                    var target = match["TARGET"].ToString();
                    var newObject = Core.Database.ReloadObject(target);
                    if (newObject == null) MudObject.SendMessage(actor, "Failed to reload " + target);
                    else MudObject.SendMessage(actor, "Reloaded " + target);
                    return PerformResult.Continue;
                });

            Parser.AddCommand(
                 Sequence(
                     RequiredRank(500),
                     KeyWord("!RESET"),
                     MustMatch("It helps if you give me a path.",
                         Path("TARGET"))))
                 .Manual("Given a path, it attempts to reset that object without reloading or recompiling. The object will be replaced in-place if possible.")
                 .ProceduralRule((match, actor) =>
                 {
                     var target = match["TARGET"].ToString();
                     if (Core.Database.ResetObject(target) == null)
                         MudObject.SendMessage(actor, "Failed to reset " + target);
                     else MudObject.SendMessage(actor, "Reset " + target);
                     return PerformResult.Continue;
                 });
        }
        public List<PossibleMatch> Match(PossibleMatch State, CommandParser.MatchContext Context)
        {
            var R = new List<PossibleMatch>();
			if (Context.ExecutingActor.Rank >= RequiredRank)
				R.Add(State);
			return R;
        }
        public List<PossibleMatch> Match(PossibleMatch State, CommandParser.MatchContext Context)
        {
            var Matches = new List<PossibleMatch>();
            foreach (var Matcher in Matchers)
				Matches.AddRange(Matcher.Match(State, Context));
            return Matches;
        }
        public List<PossibleMatch> Match(PossibleMatch State, CommandParser.MatchContext Context)
        {
            var R = new List<PossibleMatch>();
			if (Filter(State, Context))
				R.Add(State);
			return R;
        }
		public List<PossibleMatch> Match(PossibleMatch State, CommandParser.MatchContext Context)
		{
			var R = new List<PossibleMatch>();
			if (State.Next == null) return R;

			if ((Settings & ObjectMatcherSettings.UnderstandMe) == ObjectMatcherSettings.UnderstandMe)
			{
				if (State.Next.Value.ToUpper() == "ME")
				{
					var possibleMatch = new PossibleMatch(State.Arguments, State.Next.Next);
					possibleMatch.Arguments.Upsert(CaptureName, Context.ExecutingActor);
					R.Add(possibleMatch);
				}
			}

			foreach (var thing in ObjectSource.GetObjects(State, Context))
			{
				var possibleMatch = new PossibleMatch(State.Arguments, State.Next);
				bool matched = false;
				while (possibleMatch.Next != null && thing.Nouns.Contains(possibleMatch.Next.Value.ToUpper()))
				{
					matched = true;
					possibleMatch.Next = possibleMatch.Next.Next;
				}

				if (matched)
				{
					possibleMatch.Arguments.Upsert(CaptureName, thing);
					R.Add(possibleMatch);
				}
			}
			return R;
		}
Beispiel #6
0
 public override void Create(CommandParser Parser)
 {
     Parser.AddCommand(
         Or(
             Sequence(
                 KeyWord("USE"),
                 BestScore("SUBJECT",
                     MustMatch("@not here",
                     Object("SUBJECT", InScope, (actor, thing) =>
                     {
                         // Prefer objects we can actually use.
                         var canUse = Core.GlobalRules.ConsiderCheckRuleSilently("can use?", actor, thing);
                         if (canUse == SharpRuleEngine.CheckResult.Allow)
                             return MatchPreference.Likely;
                         return MatchPreference.Unlikely;
                     })))),
             // We would also like to match when the player types the name of a thing that is
             // useable. Here we just check the 'useable?' property, so that things where
             // 'can use?' fails will still be matched.
             Generic((m, c) =>
             {
                 return new List<RMUD.PossibleMatch>(
                     Object("SUBJECT", InScope)
                         .Match(m, c)
                         .Where(
                         _ => (_["SUBJECT"] as MudObject).GetBooleanProperty("useable?")));
             }, "[A USEABLE ITEM]")))
         .ID("USE")
         .Manual("Use a useable thing. You can also just enter the name of the thing.")
         .Check("can use?", "ACTOR", "SUBJECT")
         .BeforeActing()
         .Perform("used", "ACTOR", "SUBJECT")
         .AfterActing();
 }
Beispiel #7
0
 public override void Create(CommandParser Parser)
 {
     Parser.AddCommand(
         Sequence(
             Or(
                 KeyWord("WHISPER"),
                 KeyWord("TELL")),
             OptionalKeyWord("TO"),
             MustMatch("Whom?",
                 Object("PLAYER", new ConnectedPlayersObjectSource(), ObjectMatcherSettings.None)),
             MustMatch("Tell them what?", Rest("SPEECH"))))
         .Manual("Sends a private message to the player of your choice.")
         .ProceduralRule((match, actor) =>
         {
             if (System.Object.ReferenceEquals(actor, match["PLAYER"]))
             {
                 MudObject.SendMessage(actor, "Talking to yourself?");
                 return PerformResult.Stop;
             }
             return PerformResult.Continue;
         })
         .ProceduralRule((match, actor) =>
         {
             var player = match["PLAYER"] as Actor;
             MudObject.SendMessage(player, "[privately " + DateTime.Now + "] ^<the0> : \"" + match["SPEECH"].ToString() + "\"", actor);
             MudObject.SendMessage(actor, "[privately to <the0>] ^<the1> : \"" + match["SPEECH"].ToString() + "\"", player, actor);
             if (player.ConnectedClient is NetworkClient && (player.ConnectedClient as NetworkClient).IsAfk)
                 MudObject.SendMessage(actor, "^<the0> is afk : " + player.ConnectedClient.Player.GetProperty<Account>("account").AFKMessage, player);
             return PerformResult.Continue;
         });
 }
Beispiel #8
0
        public override void Create(CommandParser Parser)
        {
            Parser.AddCommand(
                Sequence(
                    RequiredRank(500),
                    KeyWord("KICK"),
                    Or(
                        Object("PLAYER", new ConnectedPlayersObjectSource(), ObjectMatcherSettings.None),
                        SingleWord("MASK"))))
                .Manual("Makes bad people go away.")
                .ProceduralRule((match, actor) =>
                {
                    if (match.ContainsKey("PLAYER"))
                        KickPlayer(match["PLAYER"] as Actor, actor);
                    else
                    {
                        var mask = match["MASK"].ToString();
                        var maskRegex = new System.Text.RegularExpressions.Regex(ProscriptionList.ConvertGlobToRegex(mask), System.Text.RegularExpressions.RegexOptions.IgnoreCase);

                        //Iterate over local copy because kicking modifies ConnectedClients.
                        foreach (var client in new List<Client>(Clients.ConnectedClients))
                        {
                            var netClient = client as NetworkClient;
                            if (netClient != null && netClient.IsLoggedOn && maskRegex.Matches(netClient.IPString).Count > 0)
                            {
                                Core.MarkLocaleForUpdate(client.Player);
                                KickPlayer(client.Player, actor);
                            }
                        }
                    }

                    return PerformResult.Continue;
                });
        }
Beispiel #9
0
        public override void Create(CommandParser Parser)
        {
            Parser.AddCommand(
                Sequence(
                    RequiredRank(500),
                    KeyWord("!MOVE"),
                    MustMatch("I don't see that here.",
                        Object("OBJECT", InScope)),
                    OptionalKeyWord("TO"),
                    MustMatch("You have to specify the path of the destination.",
                        Path("DESTINATION"))))
                .Manual("An administrative command to move objects from one place to another. This command entirely ignores all rules that might prevent moving an object.")
                .ProceduralRule((match, actor) =>
                {
                    var destination = MudObject.GetObject(match["DESTINATION"].ToString());
                    if (destination != null)
                    {
                        var target = match["OBJECT"] as MudObject;
                        Core.MarkLocaleForUpdate(target);
                        MudObject.Move(target, destination);
                        Core.MarkLocaleForUpdate(destination);

                        MudObject.SendMessage(actor, "Success.");
                    }
                    else
                        MudObject.SendMessage(actor, "I could not find the destination.");
                    return PerformResult.Continue;
                });
        }
Beispiel #10
0
        public LoginCommandHandler()
        {
            Parser = new CommandParser();

            CommandFactory.CreateCommandFactory(typeof(Login)).Create(Parser);
            CommandFactory.CreateCommandFactory(typeof(Register)).Create(Parser);
            CommandFactory.CreateCommandFactory(typeof(Quit)).Create(Parser);
        }
        public List<PossibleMatch> Match(PossibleMatch State, CommandParser.MatchContext Context)
        {
            var R = new List<PossibleMatch>();
			if (State.Next != null && State.Next.Value.ToUpper() == Word)
				R.Add(new PossibleMatch(State.Arguments, State.Next.Next));
            if (Optional)
                R.Add(new PossibleMatch(State.Arguments, State.Next));
            return R;
        }
Beispiel #12
0
        public override void Create(CommandParser Parser)
        {
            Parser.AddCommand(
                Sequence(
                    RequiredRank(500),
                    KeyWord("!FORCE"),
                    MustMatch("Whom do you wish to command?",
                        FirstOf(
                            Object("OBJECT", InScope),
                            Path("PATH"))),
                    Rest("RAW-COMMAND")))
                .Manual("An administrative command that allows you to execute a command as if you were another actor or player. The other entity will see all output from the command, and rules restricting their access to the command are considered.")
                .ProceduralRule((match, actor) =>
                    {
                        if (match.ContainsKey("PATH"))
                        {
                            var target = MudObject.GetObject(match["PATH"].ToString());
                            if (target == null)
                            {
                                MudObject.SendMessage(actor, "I can't find whomever it is you want to submit to your foolish whims.");
                                return PerformResult.Stop;
                            }
                            match.Upsert("OBJECT", target);
                        }
                        return PerformResult.Continue;
                    }, "Convert path to object rule.")
                .ProceduralRule((match, actor) =>
                {
                    MudObject target = match["OBJECT"] as MudObject;

                    var targetActor = target as Actor;
                    if (targetActor == null)
                    {
                        MudObject.SendMessage(actor, "You can order inanimate objects about as much as you like, they aren't going to listen.");
                        return PerformResult.Stop;
                    }

                    var command = match["RAW-COMMAND"].ToString();
                    var matchedCommand = Core.DefaultParser.ParseCommand(new PendingCommand { RawCommand = command, Actor = targetActor });

                    if (matchedCommand != null)
                    {
                        if (matchedCommand.Matches.Count > 1)
                            MudObject.SendMessage(actor, "The command was ambigious.");
                        else
                        {
                            MudObject.SendMessage(actor, "Enacting your will.");
                            Core.ProcessPlayerCommand(matchedCommand.Command, matchedCommand.Matches[0], targetActor);
                        }
                    }
                    else
                        MudObject.SendMessage(actor, "The command did not match.");

                    return PerformResult.Continue;
                });
        }
Beispiel #13
0
 public override void Create(CommandParser Parser)
 {
     Parser.AddCommand(
         Or(
             KeyWord("INVENTORY"),
             KeyWord("INV"),
             KeyWord("I")))
         .Manual("Displays what you are wearing and carrying.")
         .Perform("inventory", "ACTOR");
 }
        public List<PossibleMatch> Match(PossibleMatch State, CommandParser.MatchContext Context)
        {
            var R = new List<PossibleMatch>();
			if (State.Next != null)
			{
				var possibleMatch = new PossibleMatch(State.Arguments, null);
				possibleMatch.Arguments.Upsert(ArgumentName, State.Next);
				R.Add(possibleMatch);
			}
			return R;
        }
        public List<PossibleMatch> Match(PossibleMatch State, CommandParser.MatchContext Context)
        {
            var r = new List<PossibleMatch>();
			if (State.Next == null) return r;

			if (Link.IsCardinal(State.Next.Value.ToUpper()))
			{
				var match = new PossibleMatch(State.Arguments, State.Next.Next);
				match.Arguments.Upsert(ArgumentName, Link.ToCardinal(State.Next.Value.ToUpper()));
				r.Add(match);
			}
			return r;
        }
Beispiel #16
0
 public override void Create(CommandParser Parser)
 {
     Parser.AddCommand(
         Sequence(
             Or(
                 KeyWord("LOOK"),
                 KeyWord("L")),
             RelativeLocation("RELLOC"),
             Object("OBJECT", InScope)))
         .Manual("Lists object that are in, on, under, or behind the object specified.")
         .Check("can look relloc?", "ACTOR", "OBJECT", "RELLOC")
         .Perform("look relloc", "ACTOR", "OBJECT", "RELLOC");
 }
		public ParserCommandHandler()
		{
			Parser = new CommandParser();

			//Iterate over all types, find ICommandFactories, Create commands
			foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
			{
				if (type.IsSubclassOf(typeof(CommandFactory)))
				{
					var instance = Activator.CreateInstance(type) as CommandFactory;
					instance.Create(Parser);
				}
			}
		}
Beispiel #18
0
 public override void Create(CommandParser Parser)
 {
     Parser.AddCommand(
         Sequence(
             KeyWord("REMOVE"),
             BestScore("OBJECT",
                 MustMatch("@clothing remove what",
                     Object("OBJECT", InScope, PreferWorn)))))
         .Manual("Expose your amazingly supple flesh.")
         .Check("can remove?", "ACTOR", "OBJECT")
         .BeforeActing()
         .Perform("removed", "ACTOR", "OBJECT")
         .AfterActing();
 }
        public List<PossibleMatch> Match(PossibleMatch State, CommandParser.MatchContext Context)
        {
            var Matches = new List<PossibleMatch>();
            Matches.Add(State);
            foreach (var Matcher in Matchers)
            {
                var NextMatches = new List<PossibleMatch>();
                foreach (var Match in Matches)
                    NextMatches.AddRange(Matcher.Match(Match, Context));
                Matches = NextMatches;
				if (Matches.Count == 0) return Matches; //Shortcircuit for when no matches are found.
            }
            return Matches;
        }
Beispiel #20
0
 public override void Create(CommandParser Parser)
 {
     Parser.AddCommand(
         Sequence(
             KeyWord("DROP"),
             BestScore("SUBJECT",
                 MustMatch("@dont have that",
                     Object("SUBJECT", InScope, PreferHeld)))))
         .Manual("Drop a held item. This can also be used to remove and drop a worn item.")
         .Check("can drop?", "ACTOR", "SUBJECT")
         .BeforeActing()
         .Perform("drop", "ACTOR", "SUBJECT")
         .AfterActing();
 }
Beispiel #21
0
 public override void Create(CommandParser Parser)
 {
     Parser.AddCommand(
         Sequence(
             RequiredRank(500),
             KeyWord("!SCOPE")))
         .Manual("List all of the objects in scope")
         .ProceduralRule((match, actor) =>
         {
             foreach (var thing in MudObject.EnumerateVisibleTree(MudObject.FindLocale(actor)))
                 MudObject.SendMessage(actor, thing.Short + " - " + thing.GetType().Name);
             return PerformResult.Continue;
         }, "List all the damn things in scope rule.");
 }
Beispiel #22
0
 public override void Create(CommandParser Parser)
 {
     Parser.AddCommand(
         Sequence(
             KeyWord("WEAR"),
             BestScore("OBJECT",
                 MustMatch("@clothing wear what",
                     Object("OBJECT", InScope, PreferHeld)))))
         .Manual("Cover your disgusting flesh.")
         .Check("can wear?", "ACTOR", "OBJECT")
         .BeforeActing()
         .Perform("worn", "ACTOR", "OBJECT")
         .AfterActing();
 }
Beispiel #23
0
        public override void Create(CommandParser Parser)
        {
            Parser.AddCommand(KeyWord("SONAR"))
                .ProceduralRule((match, actor) =>
                {

                    var builder = new System.Text.StringBuilder();

                    var mapGrid = new int[MapWidth, MapHeight];
                    for (int y = 0; y < MapHeight; ++y)
                        for (int x = 0; x < MapWidth; ++x)
                            mapGrid[x, y] = ' ';

                    for (int y = 1; y < MapHeight - 1; ++y)
                    {
                        mapGrid[0, y] = '|';
                        mapGrid[MapWidth - 1, y] = '|';
                    }

                    for (int x = 1; x < MapWidth - 1; ++x)
                    {
                        mapGrid[x, 0] = '-';
                        mapGrid[x, MapHeight - 1] = '-';
                    }

                    mapGrid[0, 0] = '+';
                    mapGrid[0, MapHeight - 1] = '+';
                    mapGrid[MapWidth - 1, 0] = '+';
                    mapGrid[MapWidth - 1, MapHeight - 1] = '+';

                    var roomLegend = new Dictionary<int, String>();

                    if (actor.Location is RMUD.Room)
                        MapLocation(mapGrid, roomLegend, (MapWidth / 2), (MapHeight / 2), actor.Location as RMUD.Room, '@');

                    for (int y = 0; y < MapHeight; ++y)
                    {
                        for (int x = 0; x < MapWidth; ++x)
                            builder.Append((char)mapGrid[x, y]);
                        builder.Append("\r\n");
                    }

                    foreach (var entry in roomLegend)
                        builder.Append((char)entry.Key + " - " + entry.Value + "\r\n");

                    MudObject.SendMessage(actor, builder.ToString());
                    return RMUD.PerformResult.Continue;
                }, "Implement sonar device rule.");
        }
		public LoginCommandHandler()
		{
			Parser = new CommandParser();

			Parser.AddCommand(
				new Sequence(new KeyWord("LOGIN", false), new SingleWord("NAME")),
				new CommandProcessorWrapper((m, a) =>
				{
					a.Short = m.Arguments["NAME"].ToString();
					a.ConnectedClient.CommandHandler = Mud.ParserCommandHandler;
					a.Rank = 500; //Everyone is a wizard! 
					Thing.Move(a, Mud.GetObject("dummy"));
					Mud.EnqueuClientCommand(a.ConnectedClient, "look");
				}),
				"Login to an existing account.");
		}
Beispiel #25
0
 public override void Create(CommandParser Parser)
 {
     Parser.AddCommand(
         Sequence(
             KeyWord("AFK"),
             MustMatch("You have to supply an afk message.",
                 Rest("MESSAGE"))))
         .Manual("Sets your afk message. This message is displayed after 5 minutes of inactivity on the WHO list, and to any player who attempts to whisper to you.")
         .ProceduralRule((match, actor) =>
         {
             if (actor.ConnectedClient != null)
                 actor.ConnectedClient.Player.GetProperty<Account>("account").AFKMessage = match["MESSAGE"].ToString();
             MudObject.SendMessage(actor, "AFK message set.");
             return PerformResult.Continue;
         });
 }
Beispiel #26
0
        public override void Create(CommandParser Parser)
        {
            Parser.AddCommand(
                Sequence(
                    RequiredRank(500),
                    KeyWord("!INSPECT"),
                    MustMatch("I don't see that here.",
                        Or(
                            Object("OBJECT", InScope),
                            KeyWord("HERE")))))
                .Manual("Take a peek at the internal workings of any mud object.")
                .ProceduralRule((match, actor) =>
                    {
                        if (!match.ContainsKey("OBJECT"))
                            match.Upsert("OBJECT", actor.Location);
                        return PerformResult.Continue;
                    }, "Convert locale option to standard form rule.")
                .ProceduralRule((match, actor) =>
                {
                    var target = match["OBJECT"] as MudObject;
                    MudObject.SendMessage(actor, target.GetType().Name);

                    foreach (var @interface in target.GetType().GetInterfaces())
                        MudObject.SendMessage(actor, "Implements " + @interface.Name);

                    foreach (var field in target.GetType().GetFields())
                        MudObject.SendMessage(actor, "field " + field.FieldType.Name + " " + field.Name + " = " + WriteValue(field.GetValue(target)));

                    foreach (var property in target.GetType().GetProperties())
                    {
                        var s = (property.CanWrite ? "property " : "readonly ") + property.PropertyType.Name + " " + property.Name;
                        if (property.CanRead)
                        {
                            s += " = ";
                            try
                            {
                                s += WriteValue(property.GetValue(target, null));
                            }
                            catch (Exception) { s += "[Error reading value]"; }
                        }
                        MudObject.SendMessage(actor, s);
                    }

                    return PerformResult.Continue;
                }, "List all the damn things rule.");
        }
Beispiel #27
0
 public override void Create(CommandParser Parser)
 {
     Parser.AddCommand(
         KeyWord("QUIT"))
         .Manual("Disconnect from the game immediately.")
         .ProceduralRule((match, actor) =>
         {
             if (actor != null && actor.ConnectedClient != null)
                 match.Upsert("CLIENT", actor.ConnectedClient);
             if (match.ContainsKey("CLIENT"))
             {
                 (match["CLIENT"] as Client).Send("Goodbye...\r\n");
                 (match["CLIENT"] as Client).Disconnect();
             }
             return PerformResult.Continue;
         });
 }
Beispiel #28
0
 public override void Create(CommandParser Parser)
 {
     Parser.AddCommand(
         BestScore("SUBJECT",
             Sequence(
                 KeyWord("TAPE"),
                 MustMatch("@not here",
                     Object("SUBJECT", InScope, PreferHeld)),
                 OptionalKeyWord("TO"),
                 MustMatch("@not here",
                     Object("OBJECT", InScope)))))
         .Manual("Tape one thing to another")
         .Check("can tape to?", "ACTOR", "SUBJECT", "OBJECT")
         .BeforeActing()
         .Perform("taped to", "ACTOR", "SUBJECT", "OBJECT")
         .AfterActing();
 }
Beispiel #29
0
 public override void Create(CommandParser Parser)
 {
     Parser.AddCommand(
         Sequence(
             KeyWord("UNLOCK"),
             BestScore("ITEM",
                 MustMatch("@not here",
                     Object("ITEM", InScope))),
             OptionalKeyWord("WITH"),
             BestScore("KEY",
                 MustMatch("@not here",
                     Object("KEY", InScope, PreferHeld)))))
         .Manual("Use the KEY to unlock the ITEM.")
         .Check("can lock?", "ACTOR", "ITEM", "KEY")
         .BeforeActing()
         .Perform("unlocked", "ACTOR", "ITEM", "KEY")
         .AfterActing();
 }
Beispiel #30
0
 public override void Create(CommandParser Parser)
 {
     Parser.AddCommand(
         Sequence(
             KeyWord("CLOSE"),
             BestScore("SUBJECT",
                 MustMatch("@not here",
                     Object("SUBJECT", InScope, (actor, thing) =>
                         {
                             if (Core.GlobalRules.ConsiderCheckRuleSilently("can close?", actor, thing) == CheckResult.Allow) return MatchPreference.Likely;
                             return MatchPreference.Unlikely;
                         })))))
         .Manual("Closes a thing.")
         .Check("can close?", "ACTOR", "SUBJECT")
         .BeforeActing()
         .Perform("close", "ACTOR", "SUBJECT")
         .AfterActing();
 }
Beispiel #31
0
 public virtual void Create(CommandParser Parser)
 {
     throw new NotImplementedException();
 }
Beispiel #32
0
        public override void Create(CommandParser Parser)
        {
            // The engine has passed us the default parser. We want to add the command to it.
            // The command should follow the form 'push [object] [direction]'. CommandFactory defines
            // a bunch of functions to generate matchers. Calling 'KeyWord(...)' is equivilent to
            // 'new RMUD.Matchers.KeyWord(...)'. It's minor, but makes things far more readable.
            // The actual parser operates on a list of words, with each piece matching or failing.
            // Each matcher returns a set of possible matches.
            Parser.AddCommand(
                Sequence(                                                     // Attemps to match a set of matchers in sequence.
                    KeyWord("PUSH"),                                          // Is the word 'push'? Yes -> match. No -> Fail.
                    BestScore("SUBJECT",                                      // Match the child, and choose the subset of possible matches that are best.
                              MustMatch("@not here",                          // If the child doesn't match, go ahead and report an error.
                                        Object("SUBJECT", InScope))),         // Match a mud object. "SUBJECT" is the argument we store it in, InScope is the source of the objects.
                    MustMatch("@unmatched cardinal", Cardinal("DIRECTION")))) // Finally, match a cardinal direction.
            // With the matcher itself built, the call to AddCommand is over. AddCommand return a
            // command building object with a fluent interface.

            // Commands need an ID string so that they can be invoked by rules. For an example, take a look at
            // the go command, which attempts to open closed doors before going.
            .ID("StandardActions:Push")

            // Lets attach some help documentation to the command.
            .Manual("Push objects from room to room.")

            // Every command has a rulebook called its 'procedural rules'. When the command is matched,
            // these rules are invoked. The rules can access things matched above (such as the object
            // matched by that object matcher, which will be stored as "SUBJECT". There are several
            // helpful functions to make common procedural rules easy to add. We start with the raw form,
            // since we need to translate the direction we matched to the actual link object. When the
            // command is matched, these rules will be called in the order declared.
            .ProceduralRule((match, actor) =>
            {
                // Guard against a null location.
                if (actor.Location.HasValue(out var loc))
                {
                    // The direction matched was stored in the match as "DIRECTION".
                    var direction = match["DIRECTION"] as Direction?;
                    // Rooms have a collection of objects that are in them. Links happen to have two specific
                    // properties set that we can use to find them: First, 'portal?' will be true, and
                    // 'link direction' will hold the direction the link goes in. So we search for the link.
                    var link = loc.EnumerateObjects().FirstOrDefault(thing => thing.GetProperty <bool>("portal?") && thing.GetProperty <Direction>("link direction") == direction.Value);
                    // Store the link in the match, and later procedural rules will be able to find it.
                    match.Upsert("LINK", link);
                    // Procedural rules return PerformResults. If they return stop, the command stops right there.
                    return(PerformResult.Continue);
                }
                else
                {
                    Core.SendMessage(actor, "You do not appear to be anywhere.");
                    return(PerformResult.Stop); // Stop will stop execution immediately.
                }
            }, "lookup link rule")              // We can also name procedural rules. This is always a good idea, as the
            // name will help with debugging the rules later.

            // Pushing between rooms is also going, so we want to make sure the player can go that direction.
            // The command builder type provides the 'Check' function which adds a procedural rule to invoke
            // a check rulebook (and stop the command if it returns CheckResult.Disallow). So we can go ahead
            // and check 'can go?', giving the go rules a chance to stop this action.
            // Check takes the name of the rulebook, and then any arguments to pass to it. The arguments are
            // drawn from the match: So "ACTOR" would be the actor invoking the command, and "LINK" is that
            // link we found in the proceeding rule.
            // The 'can go?' rulebook also handles the case where "LINK" is null, so we don't have to worry
            // about it here.
            .Check("can go?", "ACTOR", "LINK")

            // We still want some rules to govern the pushing action too, and in this case the rulebook will
            // take one more argument than the 'can go?' rulebook. Lets go ahead and invoke them too.
            .Check("can push direction?", "ACTOR", "SUBJECT", "LINK")

            // There's a rulebook every command that counts as the player 'acting' (meaning they are affecting
            // the world in some way) should check before actually doing anything. Since every command should
            // check it, it has its own function to add it.
            .BeforeActing()

            // We're done checking if the command is allowed at this point, so we can finally get around to
            // actually performing the action. The arguments to Perform behave the same as those to Check.
            // It just invokes a different kind of rulebook.
            .Perform("push direction", "ACTOR", "SUBJECT", "LINK")

            // The opposite of BeforeActing is AfterActing. Can you guess when it should be invoked?
            .AfterActing()

            // The last procedural rule lets the engine know it needs to update both sides of the link.
            // Most commands can just call 'MarkLocale()' here to add a rule to mark the actor's locale
            // for update. Since the actor moved, that won't work in this case. We need to make sure we
            // update where the player WAS as well as where he IS!
            .ProceduralRule((match, actor) =>
            {
                Core.MarkLocaleForUpdate(actor);
                Core.MarkLocaleForUpdate(match["LINK"] as MudObject);
                return(PerformResult.Continue);
            }, "Mark both sides of link for update rule");     // Hey the command is finally done.
        }