Thursday, April 14, 2005

Triggers and events

While thinking about the scripting language to use (a topic for another day), the idea of event-handling and triggers came to mind. Whatever language I choose to use or write should be able to support them, because I feel that events are a big part of a virtual world.

The best example is speech. A character walks up to the banker and says, "I'd like to access my bank box please." Or he walks onto a magic circle and says a certain word, finding himself teleported to another part of the world. Both of these examples are from Ultima Online, but I'm sure every virtual world has some case of this. Maybe not speech, exactly, but some way for an NPC or object to react to something a character does.

The issue is how to implement it. There are two ways that I can see: each object (which includes PCs and NPCs) has a youJustHeard() function, which is passed a reference to the speaker, and the text spoken; or each speaker has a youJustSaid() function, which takes the text spoken.

In the first case, every object in the world has a listener function. There are really two ways to deal with this: each object, upon creation, must register itself as a listener, to let the world know that it might be interested in things that are said in the world; or whenever something is spoken, the world looks around the speaker to get a list of all the objects within range, and "tells" each of them what was said.

If the world has to check for nearby listeners every time anything is spoken, it should be obvious that there is going to be a lot of this searching. When characters start gabbing in-game, every table, door, dog and character within a given distance is going to be told, whether or not they want to know. In this case, we better hope that we have one hell of an optimized way to get a list of objects within a certain area.

With the registration method, the world would be able to reduce the number of objects that it has to look through, because only objects that can listen are registered. Again, we would still want this to be very optimized, since every character will be in this list, as will many NPCs and probably some inanimate objects.

Both of these methods of listening seem to have a lot of overhead for every word said.

Taking the other approach, it's the responsibility of the speaker to let everyone know what was said. Instead of relying on the server to do all the figuring for finding listeners, the logic for that could be part of the speaker's youJustSaid() script, which would ask the system for a list of things within range (something that seems to be inescapable), and then the speaker's script would call each of the listener's youJustHeard() functions.

At first glance, this seems to be less effective than letting the server do it directly, because the same work is being done (finding a list of listeners, calling each of their functions), but now it's (most likely) being done in an interpreter, which isn't as optimal as a compiled server core. And while that's probably true, having the logic take place in a dynamic spot -- that is, something that the game designer can change -- allows for more flexibility whenever something is spoken.

For instance, perhaps our world allows spells, and instead of clicking an icon or scroll to cast, you simply say the words out loud. As well as having every listener's youJustHeard() being called, we would also want the character to start waving his or her hands, and spell effects to happen, and an apple to appear. Or a certain class of character might be able to open doors with but a word; the doors by themselves have no logic to listen for such things (though it could of course be added), but the speaker's script could not only inform all listeners of the words, but also call the door's use() function. Having the speech handler "local" to the speaker also allows them to control who hears them - they might have a slider that defines how loud they speak (whisper, murmur, talk, yell), or they might be able to speak to one person specifically (telepathy).

All of these things can, of course, be done at the listener's side instead of the speaker's side, and is just as accessible to the world designer, for he or she controls the scripts for everything in the world. And there's no reason that you can't mix the two; the generic "character speaker" script may know how to open doors or cast spells when the character speaks, but there's no need to put the logic for word-activated teleporters in every character when the teleporter itself can have that functionality coded into its listener script. But with this approach, we're saying that it's still the speaker's responsibility to let the teleporter know that something is said -- and what it does with that is up to it.

A third possibility, which I'm not going to entertain, is a "speech parser", which looks at everything everyone says and decides how to handle it. If a sentence contains the word "bank", then search for any bankers nearby and tell them. If one of the four magic words in the game are spoken, check the character is in one of the few places that it matters.

This is very limiting. It basically hard-codes these trigger words, requiring a list of words and their effectual area or audience. The parser might also be responsible for making the effect happen, instead of calling a function on some other object -- again, very awkward.

So, speaker or listener. A decision needs to be made... but not so fast. Triggering and events based on speech is only one possibility. What about the shopkeeper that sees you steal something? What about the citizen that sees you attack another? What about the guard that sees you discard items on the ground - littering!

Speech is only one of the generic actions that we might want world objects to react to. NPCs and monsters should likely defend themselves when you attack them; guards might arrest you if you move somewhere you shouldn't; and the shopkeeper certainly shouldn't sit idly while you get his inventory without paying.

But again, who should handle this? If my character attacks a monster, then it's pretty easy to let that monster (and its beingAttackedBy() function) know this. But what about onlookers? Maybe another monster will step in if you attack its buddy, or a nearby NPC will come to aid you.

If I drop an item on the ground, do I tell every object about this put action I just did, or do I broadcast a "I just put X at Y" message and let the "listeners" do what they will with that knowledge (fined for littering, beggars run over to pick it up). Note that depending on the game interface, this might also "push" a look action to some clients so they now know the item's there, or the world has a constant look going. But it's different to see an object suddenly appear on the ground, and to know that character X put it there. Or at least it should be.

I don't think I've convinced myself either way on who should take care of these messages about the events in the world. I'm hoping as I continue to mull over it that I can find an example that definitively says that one method is better than the other (and I equally hope I don't find two examples that contradict).

No comments: