20080601

Very Controlling

Snooping is a feature that most MUDs have, including mine. I haven't talked much about it yet because it felt fairly fragile to me for a while. This morning I fixed up several problems with it, and now it feels somewhat more solid. It's time to talk about snooping and controlling.

In case you don't know, snooping is a mechanism by which one character can see everything that another character is doing or play as a Nipic as though it were alive. Due to the sensitive nature of spying on characters, it's a command typically reserved for very few immortals on the MUD, and even then is employed judiciously. Snooping and controlling Nipics, however, is a privilege given to lower level immortals, so that they can take control of Nipics strategically in roleplay situations. You can imagine a quest where a Nipic controlled by a real player is interacting with characters. Having been there personally in the past, it can be pretty cool.

Basic support for snooping (and controlling--I'll just refer to it as snooping henceforth) was one of the earlier things I implemented, because I had a gut feeling that it would prompt architectural changes to the guts of the MUD. My gut was right; several layered pieces had to be inserted to make it all work.

Let's consider the case of another character, since it's somewhat simpler. When you snoop a character, you want just one thing: that all the text that is shown to that target character is also shown to you. Each "living" entity in the game is attached to a manager that handles inserting it into the world, removing it, and keeping track of what mode of interaction with the world it's currently in (the InteractionMode).

When a character sees some text, the writeString method of ConnectionManager is called. This in turn sends the text over the network socket, which then shows in the client's telnet session. The simple thing to do is to hook or shim this call so that it also sends the text to the snooper's manager (actually, to another interaction mode, but we'll get to that shortly). This is basically what I did. MobManager keeps track of a list of "output filters." Whenever writeString is called, it invokes a method on each OutputFilter that is subscribed to receive text from this manager. As long as you're connected to the MUD, you have one output filter that sends text back across the network to you. When you get snooped, a second output filter is added, that also sends the text to someone else. As a relatively minor detail, this is done for both input and output, so the snooper actually sees the exact commands that someone else types in.

I hinted that something is different about snooping Nipics. The obvious thing is that you not only get to see what they're seeing, but you also get to control them. Let's dig into this a little more. In a regular situation, when you type a command, it gets translated into an Invocation of some Command object and is ultimately excuted by calling the callCommand method of Command. Here's the code behind one such command:

def callCommand(invocation, intermode, args)
    if args.empty?()
        intermode.mob().seeText('What do you want to say?')
    else
        intermode.mob().doSay(args)
    end
end # function call command

The intermode parameter here is always the interaction mode that produced the command, so even if you're snooping a Nipic, it would be your (the human's) interaction mode, not the Nipic's. But the mob() call... hmm... If we redirected that call to point to the target Nipic instead of ourselves as it normally does, then commands we type will affect the target Nipic instead. Turns out this is exactly what I did, and it works just as it's expected.

So I just mentioned overriding the mob method of the interaction mode. Since when you start snooping someone, you can't do anything yourself until you're done, this is a prime candidate for having an interaction mode. Indeed, when you snoop someone you enter the ControlInteractionMode, which shims the mob call and inserts the input and output filters into the target mob. When you type exit you leave the interaction mode and remove the filters.

My motto is, "there's always gotchas," and this is no exception. With the introduction of eventos, Nipics don't actually print out text at all for any eventos. Why would they need to? They don't care what the display text for an evento is; the relevant data for the evento is encoded in the object, and they fetch and act on that directly. This poses a problem for snooping: if the Nipic doesn't display text, then that text won't get forwarded to the snooper.

I fixed this much in the same way as the filters. Since the manager already receives and processes eventos--in this case, the NipicManager--it can now keep track of a list of evento listeners that should receive notifications of events. For characters, this listener just sends the text to the network. For Nipics, this listener currently does very little, but is the connection point for mobprogs. When a snooper enters the ControlInteractionMode, when it sets up the input filters, it also adds the same evento filter that characters get into the target Nipic's manager. The only thing this does is cause events to display text to the Nipic, like characters see. Then--cleverly, I feel--the output filter correctly forwards the text to the snooper.

What's left to do? Well, if you snoop a Nipic and use the "ooc" command, which sends an out-of-character message to all characters on the MUD, the MUD currently crashes, because the doOOC method is only implemented on Character, not Nipic. This makes sense, because Nipics have no out-of-character presence. Something in the ControlInteractionMode will probably need to ensure that you can't issue commands to Nipics that aren't valid.

0 comments:

Post a Comment