|
Hooks are Wyvern's most original, powerful extension mechanism. You can use hooks to do really remarkable things. We'll cover them in a fair amount of detail in this tutorial, and then you can look at the tons of sample code to see it used in real game objects. Contents
OverviewEvery command in the game can be "hooked". A "hook" is an object that implements wyvern.lib.HookCallback. Your hook can do three things:
Hopefully that whets your appetite enough to read the rest of this tutorial!
Command HooksExample 1 - NotificationsLet's say you want to make an object that says "Ouch!" when you drop it. There are 2 main kinds of hooks: pre-hooks and post-hooks. In this case, you want to say "ouch" after the item is dropped, so you'll use a post-hook.The way you figure out the name of the hook you want to register on is simple:
Here's the code:
To run the example, we did this:
> clone wiz/rhialto/python/hooktest1.py Let's take a look at the code for Mr. Sensitive in detail:
pickedUp.
In the dropped method, we just pass, meaning
do nothing. We'll talk about this later.
Finally, we get to the meat of the example - the
Generally speaking, you have to look at the documentation for a class to figure out whether its event is a TargetedEvent or not. DropCommand says in its comments for createEvent() and execute() that it's using a TargetedEvent. You could also do a quick test in Jython:
targeted = isinstance(event, TargetedEvent) "targeted" will be set to 1 if the event you're passed is a TargetedEvent.
An Easier WayYou may have been thinking, "If we already know we're dropped, because we're a PickupInterest, why do we need to use the hook?" Good question! You can do it either way. A simpler version of the example looks like this:
Why did we bother with the hook, then? Well, Mr. Sensitive seemed like a pretty good example of how to get on a post-hook, so we did it that way. However, wanting to know when you're picked up or dropped is soooo common that we created the PickupInterest interface so you don't have to bother with the hook. This is a common theme you'll find in the Wyvern Platform APIs:
Example 2 - VetoIn this example we'll take Mr. Sensitive the Skull and have him prevent you from throwing him. You can drop him or pick him up, or give him away, but he hates to be thrown.In this case, we want to prevent it before you throw him, so we need to use a pre-hook. You use the same rule: take the command name ("throw") and add "preHook", to get throwPreHook. Here's the code, very similar to the first example:
To run this example, we did this:
> clone wiz/rhialto/python/hooktest2.py In this case, there's no ThrowInterest, so we have to use a hook to get the behavior we want. The only real differences in this example are:
veto() function -
this is the message the agent gets when the event fails. As you can see, the event system is pretty powerful. Now we'll move on to a really fancy example, where we change the parameters of an event before it's executed.
Example 3 - Changing an EventIn this example, we'll make a mini-speech-filter that puts "Ummm..." in front of anything a player says out loud. To do this, we need to modify what's said before they say it, so we use a pre-hook. Using our usual rules, the name of the hook is sayPreHook. How will we do this one? It's simple:
How do we know how to get and set the message in the say-event?
Well, we have to read the
documentation
for the event. In this case,
SayCommand says that its event is a SayCommand.SayEvent (an
inner class of SayCommand), and it provides
For many commands, they don't use special CommandEvent classes that provide methods like getMessage() or getTarget(). Events are property lists - you can get and set properties on them exactly the way you would for a GameObject, so you don't need to provide getter and setter methods. You just need to document which properties are on the event, which is what most built-in commands do. In our example, though, SayCommand just happens to provide the getMessage() and setMessage() functions, so we'll use them. Here's the code:
To run this example, we did this:
> clone wiz/rhialto/python/hooktest3.py Worked like a charm!
How It WorksHow do these hooks work, you might ask? To understand them, you just need to look at the wyvern.lib.Command interface, which we cover in detail in the Making New Commands tutorial.The Command interface has 3 methods:
createEvent methods to populate the events
with all the information that could possibly be useful for hook
callbacks. Then, in their execute
methods, they check whether the event was vetoed, and if so,
they issue event.getFailureMessage() and return.
The failure message is the string you passed into the
veto() method, in Example 2 above. Then, if the event hasn't been vetoed, they pull all the properties back out of the event and execute it with those properties. The properties may have been changed by a HookCallback, changing the way the event works. If you're trying to write a command that can be hooked, you should do the same thing.
Event FailuresIt's not terribly common, but sometimes you want to know if an event failed - in other words, the player wasn't successful in performing the command. For example, you might want to be notified if they bungled a spell.The game provides a mechanism for this. It's a special post-hook called the FailedPostHook. You construct the name using the same rules as you do for pre-hooks and (successful) post-hooks, so for the "move" command, you'd use moveFailedPostHook. We're not going to give an example of this, since:
Failed-post-hooks are only used in a few places in the game engine. One place is in Vehicle, which includes ships - if you try to move the ship, but it bumps into a land mass, it tells you "The ship can't go that way." I've always wanted to make an Annoying Parrot that sits on your shoulder and laughs at you ("Haw, haw!") whenever one of your commands fails. You'd do this using failed post-hooks, although you'd have to register them for every command.
Map HooksThe examples above showed ways to hook a command for a single player. It's also possible to hook the commands for an entire map, so that anyone (including monsters) executing the command will pass through your hook function first.This is the way Sokoban vetoes all diagonal moves in the map, and the way the side-scroller converts moves into jumps (although see the note about moves below.) Here's an example, just like the previous one, but it makes ALL players say "Ummm..." when they're in the map.
To execute it: > clone wiz/rhialto/python/hooktest4.pyA new Test Map Hook has been placed in your inventory. > drop test You drop your Test Map Hook. > say hi. You say: Ummm... hi. So a map-hook is just like a regular command hook, except it's for everyone who types that command in the map you register for.
A Note About MovesBecause of some technicalities around the way "move" commands are structured, it's not possible to change the direction of a move using a hook. However, you can do something else that works just as well:
Room HooksProximity HooksMethod Hooks
|