06 Aug, 2014, jakesmokes wrote in the 1st comment:
Votes: 0
Hello,

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
06 Aug, 2014, quixadhal wrote in the 2nd comment:
Votes: 0
At the risk of sounding like RTFM… you might want to look at how an LPMUD already does this.

Typically, objects in an LPMUD are built using multiple inheritance, and many of the behaviors are abstracted into several modules that get inherited into the objects. Finally, it uses an event system, rather than directly trying to DO everything.

For example, when a player tries to leave a room via an exit, they use a command (typically go). That command parses what they typed and verifies some basic things, like is the target an exit, is it open, is the player awake, etc.

If the basic stuff is passed, the command calls the room's pre_exit callback. This function is part of the room, and it can reject the move based on whatever criteria is desired, returning an error message to be shown to the user. If it succeeds, the code continues to the actual do_exit() call… which removes the player from the room and issues appropriate messages showing them leaving to everyone else, updating light levels, etc…

THEN, the new room is loaded (if necessary), and the pre_enter callback of the new room is called. It can also deny the player entry for whatever reasons, in which case the player is typically returned to their original room. If it succeeds, then the actual do_enter() code is called and one the player enters the room, eventEnter() is called to allow the room, or anything in the room, to act on the newly arrived player.

So, in your example of taking a chest from a room when a monster is guarding it…. you could have an eventTake() that gets called on the object, and that function could broadcast what's about to happen to the environment…. giving things a chance to act to prevent it.
06 Aug, 2014, jakesmokes wrote in the 3rd comment:
Votes: 0
Fair enough, I'll take another look at LPMud.

Thanks,

David
06 Aug, 2014, plamzi wrote in the 4th comment:
Votes: 0
jakesmokes said:
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.


This is a pretty common issue, with a variety of common solutions. They all involve your event handler being able to do more than just iterate. For instance, it can iterate until event.cancelled is false, or, if the event is emitted with event.fulfillAll = true, it can call an error callback if even one of the found handlers returns an error.

You can get some ideas about event handling here:

http://www.kirupa.com/html5/event_captur...
http://www.html5rocks.com/en/tutorials/e...
07 Aug, 2014, alteraeon wrote in the 5th comment:
Votes: 0
My recommendation is to simply accept that there will be cases where your model doesn't work elegantly. The fact of the matter is that mud commands and behaviour by their very nature aren't an elegant, clean system - if they were, it wouldn't be as interesting to the players. There's simply a lot of default complexity, and there are places where sacrificing it will make things look wrong.

-dentin

Alter Aeon MUD
http://www.alteraeon.com
08 Aug, 2014, Kaz wrote in the 6th comment:
Votes: 0
This reminds me of the Javabeans model, where observers can register themselves with beans as "Vetoable" listeners. This event is fired before the action would take place and allows those observers to say, "No, don't do that."

http://docs.oracle.com/javase/7/docs/api...
08 Aug, 2014, plamzi wrote in the 7th comment:
Votes: 0
alteraeon said:
My recommendation is to simply accept that there will be cases where your model doesn't work elegantly. The fact of the matter is that mud commands and behaviour by their very nature aren't an elegant, clean system - if they were, it wouldn't be as interesting to the players. There's simply a lot of default complexity, and there are places where sacrificing it will make things look wrong.


I agree with the general sentiment, but I think this is one of those cases where you can very easily handle a much wider range of behaviors with very little additional code. It's all happening in the master event manager, and you can easily abstract things that will allow you to support multiple handlers, with any set of conditions you may need to support.
12 Aug, 2014, jakesmokes wrote in the 8th comment:
Votes: 0
Thanks for the feedback. I appreciate it.

I took a look at a couple of LPMud codebases and, frankly, they are pretty hard to follow. Tightly packed C code with precious few, if any, comments.

The solution I wound up with was to iterate over the behaviors and have them return blocks. If any disagree I run the disagreed block. Otherwise I run the accepting blocks. Still not a perfect solution but it prevents an agreement from completing before a disagreement can occur. And it (can) remains single threaded.

I think, to PlaMzi's point about event managers: I plan to implement such a construct (quite soon) but I am not sure that it would prevent the collision of behaviors disagreeing. Unless I a missing something, something wouldn't wind up in the event queue unless it passed checks ensuring its success. The issue, for me, was to be able to look at a set of behavior and make a decision about whether an action could take place.

Anyway, this is fun and a nice learning process. So, again, thanks for all the feedback.

David
12 Aug, 2014, plamzi wrote in the 9th comment:
Votes: 0
jakesmokes said:
I plan to implement such a construct (quite soon) but I am not sure that it would prevent the collision of behaviors disagreeing. Unless I a missing something, something wouldn't wind up in the event queue unless it passed checks ensuring its success. The issue, for me, was to be able to look at a set of behavior and make a decision about whether an action could take place.


jakesmokes said:
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.


Here's a minimal implementation that involves multiple handlers of varying sorts. In the pseudo-code below, the lamp is a light source that can be taken from a guardian, or moved when its owner is moving.

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. */
}


This can output:

> take lamp

The lamp feels hot to the touch.
You can't carry that much weight.

> take lamp

The lamp feels hot to the touch.
The guardian of the lamp prevents you from picking it up.

> take lamp

The lamp feels hot to the touch.
The guardian of the lamp nods in silent approval.
You take the lamp and light it.

> north

Your lamp refuses to let you go further north. Maybe it's smarter than the average lamp.

> south

Your lamp gladly goes south with you and illuminates a passage even further south.
12 Aug, 2014, quixadhal wrote in the 10th comment:
Votes: 0
jakesmokes said:
Thanks for the feedback. I appreciate it.

I took a look at a couple of LPMud codebases and, frankly, they are pretty hard to follow. Tightly packed C code with precious few, if any, comments.

The solution I wound up with was to iterate over the behaviors and have them return blocks. If any disagree I run the disagreed block. Otherwise I run the accepting blocks. Still not a perfect solution but it prevents an agreement from completing before a disagreement can occur. And it (can) remains single threaded.

I think, to PlaMzi's point about event managers: I plan to implement such a construct (quite soon) but I am not sure that it would prevent the collision of behaviors disagreeing. Unless I a missing something, something wouldn't wind up in the event queue unless it passed checks ensuring its success. The issue, for me, was to be able to look at a set of behavior and make a decision about whether an action could take place.

Anyway, this is fun and a nice learning process. So, again, thanks for all the feedback.

David


Actually, don't worry about the C code unless you want to know how the game driver (interpreter) works. Look at an LPC mudlib.

An LPMUD is actually two pieces of software. The game driver (in C) is a language interpreter, network stack, and file I/O manager. The actual game is coded entirely in LPC, a langauge that's like C but without the annoyances of pointers and strings that aren't really strings. :)

The language features multiple inheritance, and most LPMUD mudlibs make use of this to break things into various behavior modules and events.
0.0/10