lamp.listen({
event: 'take',
check: can_carry,
type: 'veto'
});
lamp.listen({
event: 'take',
check: guardian_allows,
type: 'veto'
});
lamp.listen({
event: 'take',
check: item_touch,
type: 'info'
});
lamp.listen({
event: 'move',
check: object_can_move,
type: 'veto'
});
lamp.listen({
event: 'move',
call: object_move
});
function eventManager(evt, target) {
if (!target.evt) /* target is not listening for this event */
return;
var messages = [];
foreach (target.evt as handler) {
if (handler.check) {
response = handler.check(evt, target);
if (handler.veto && response.error) /* a crucial check has failed */
return [ response.error ]; /* for example: "You can't carry that much weight.", or "The guardian of the lamp prevents you from picking it up."
else if (response)
messages.push(response); /* for example, a veto check has succeeded, or an informative check may add: "The lamp feels hot to the touch." */
}
if (handler.call)
handler.call(target); /* for example, handle movement of the lamp without having to worry about checks. */
}
evt.doer.send(messages);
evt.success(); /* event has succeeded, so transfer target to evt.doer, for example. */
}
At the risk of sounding stupid, I have run into a snag. Thanks in advance for reading / replying if you do :-).
I have set up objects in my code. Objects are a very shallow hierarchy. They have tags for being describe, location, inventory, etc. They also have a list of behaviors: Door, container, takable, etc. Behaviors adhere to a protocol: handles(action), attempt(action), done(action). When a command function wants to do something (take an object) it figures out which object it is and then figures out if the object can handle the action and then attempts it.
When an action is attempted, the behaviors are iterated and are given a chance to handle. Behaviors return handled if they do, or didn't if they didn't. Behaviors report to the socket if there is a problem (monster is guarding the object, or it can't be taken). Behaviors maintain their own state (open, locked, trapped, whatever).
I like the idea in general. It prevents having a deep (static) object hierarchy. The (stupid) problem I feel like I am having is this:
What if an object has multiple behaviors that can / want to handle a particular action? One says ok, the other says no. In the case of take (Take the chest) then "takable" might say, sure. But the monster (behavior) guarding might say "no". As much as I have been thinking about this, I can't think of a way to solve this issue in a way that feels pleasing. Solutions I have considered:
- Disallow ambiguity. Only one behavior can handle an action. It maintains the state and handles the state, deals with the action and that's that. But I think about using the mechanism for tracking change when a player moves. He might be carrying a light source and an item with radius magical effects, so it would be "nice" to be able to send that notification and have two behaviors respond.
- Make two passes. If the item being taken is takable in theory, but cannot be taken for a reason specific to that object (guarded, magically held) then one pass to get all of the results and then reconcile and report.
- Use the mechanism less. So don't keep track of the fact that an object is being guarded by a monster in this way. Use another mechanism for such things. This is a variation of option #1.
I think the source of my boggle is that I am thinking about it too much. But I am trying to come up with a solution that is reasonably elegant and easy to use. And, truthfully, it would be nice to code the semantics of the command function as behaviors rather than add a lot of code to the current (and future) set of command functions. In this case, the command function would just validate the input parameters (objects) and then pass the intent along to the receivers. So, put the shiny ring inside the leather pack might call "Take" on the pack with "ring" as the object.
Another problem of sorts (less of an issue) is that the protocol for calling behaviors doesn't allow parameters really. I just pass the player / NPC to the function and the parse tree resulting from the player's input. I do have a provision for getting state from these, so I can ask an object if its locked and the appropriate behavior will respond with a boolean.
All in all it feels at once awkward and cool. Am I being stupid? Am I trying to be too flexible?
I could, I suppose, create a protocol for objects that is static and known and all objects would have behaviors or NULL. So, _container would contain the container behavior or be NULL and the command parser would just deal with that. So every object would have a slot for a known command / behavior type. Having a list of them (as I do now) might just be an attempt to not constrain objects to a particular set of possible behaviors.
Anyway.. I'm not looking to have the problem solved. Maybe just some feedback, if possible, about where my thinking has gone off. I feel like I am close to a reasonable solution. But it doesn't quite feel "there".
Thoughts?
As always, your time is appreciated.
David