Subject: Events

From: [email-masked] (Clay Luther)
Date: Fri, 5 Feb 1993 10:31:15 -0800

>You replied to MOO-Cows:
>> >Has anyone figured out how to let player2 follow player1 when they
>> >move?
>> 
>> Again, another problem *trivially* handled by the events code.  Toot-toot.
>
>Funny that you should mention this, because I was just thinking last
>night how easy it would be to do to make a follow command that would
>rely on the events sent out by room exits.  Alas, no-one's done it yet
>on my stomping grounds (OpalMOO).  Does the code already exist?
>
>This makes me wonder what other capacities events are used for.  Where
>else besides opal have events been core-ified?  What is used to send
>out calls?
>
>I ask this not because I am confused about events, just because I'm
>not creative enough about their implementation.  Opal is also young
>enough to be able to make pretty sweeping changes easily...
>
>Just a little question--

Events are easy to implement.  We first implemented them on the GodNet and
I am rewriting and cleaning them up at the present for Wu Han's House of
Wax.

An event is a verb sent to any object that wants one when a certain "event"
occurs.  The basic events I've reimplemented in WHHOW are event_move,
event_enter, event_exit, event_look, and event_parse.  Again these are the
basic events, and the event architecture allows you to, at any time, create
any new event you need.

EVENT_MOVE: This is a special event sent to the object moved.
        NUM event_move(LIST {OBJ where_from, OBJ where_to})

EVENT_ENTER: This is a room event generated when an object enters a room.
        NUM event_enter(LIST {OBJ who_entered, OBJ room_entered})

EVENT_EXIT: This is a room event generated when an object leaves a room.
        NUM event_exit(LIST {OBJ who_left, OBJ room_left})

EVENT_LOOK: This is a room event generated whenever the room is looked at.
        NUM event_look(LIST {OBJ who_looked, OBJ room_looked_in})

EVENT_PARSE: This is a special room event generated whenever an unparsable
             string is entered by the user.
        NUM event_parse(LIST {OBJ player, OBJ room, LIST unparsed_strings})

SETTING UP EVENTS

First, create the "catch-all" event handler on the root object:

@list #1:event_* without numbers
#1:"event_*"   this none this
return 0;

This allows any child of $root to handle any event without error.

All "handled" events by objects are therefore simply overrides of the basic
event handler.

Now that objects can handle events, we must devise some way of posting
events to them.  Since all player action within a (lambda)moo takes place
within the context of a room, it seems logical to make rooms the source of
events.  Also, it is conceivable that some objects won't care to be
notified of *any* events and that some objects would like to hear about
events in rooms they do not currently occupy.

To handle both of these cases, you must create a new property on $room:

@prop $room.event_list {}

To handle the posting of events, we create a new room method:

@list $room:post_event without numbers
#3:"post_event"   this none this
"** CWL Send an event to all objects in the this.event_list";
"** USAGE: post_event(STR event, LIST args)";
event_name = "event_" + args[1];
arg_list = args[2];
for o in (this.event_list)
  if ($recycler:valid(o))
    o:(event_name)(arg_list);
  else
    this.event_list = setremove(this.event_list, o);
  endif
endfor

You also need two more methods on $room, one to add objects to the
event_list and one to remove objects from the event_list:

@list $room:add_to_event_list without numbers
#3:"add_to_event_list"   this none this
"** CWL Add an object to this room's event list";
"** USAGE: add_to_event_list(OBJ object || LIST objects)";
o = args[1];
if (typeof(o) != LIST)
  o = {o};
endif
for oo in (o)
  if ($recycler:valid(oo) && !(oo in this.event_list))
    this.event_list = setadd(this.event_list, oo);
  endif
endfor
return this.event_list;

@list $room:remove_from_event_list without numbers
#3:"remove_from_event_list"   this none this
"** CWL Remove an object from the event list";
"** USAGE: remove_from_event_list(OBJ o || LIST objects)";
o = args[1];
if (typeof(o) != LIST)
  o = {o};
endif
for oo in (o)
  if ($recycler:valid(oo) && oo in this.event_list)
    this.event_list = setremove(this.event_list, oo);
  endif
endfor
return this.event_list;

INSTALLING THE BASIC EVENTS

EVENT_ENTER
To install event_enter, modify the $room:enterfunc to be:

@list $room:enterfunc without numbers
#3:"enterfunc"   this none this
object = args[1];
this:post_event("enter", {object, this});
if (is_player(object))
  player = object;
  this:look_self(player.brief);
endif
if (object == this.blessed_object)
  this.blessed_object = #-1;
endif

The post_event line was added.

EVENT_EXIT
To install the event_exit, modify the $room:exitfunc to be:

@list $room:exitfunc without numbers
#3:"exitfunc"   this none this
this:post_event("exit", {args[1], this});
return;

EVENT_LOOK
To install the event_look, modify the $room:look_self to be:

@list $room:look_self without number
#3:"look_self"   this none this
player:tell(this:title());
if (!(args && args[1]))
  pass();
endif
this:tell_contents(setremove(this.contents, player), this.ctype);
this:post_event("look", {player, this});

Moving on to the slightly more tricky EVENT_PARSE...

To install EVENT_PARSE, you must add another method to $room and modify the
$room:here_huh method:

@list $room:here_huh without numbers
#3:"here_huh"   this none this
":here_huh(verb,args)  -- room-specific :huh processing.  This should
return 1 if it finds something interesting to do and 0 otherwise; see
$command_utils:do_huh.";
"For the generic room, we check for the case of the caller specifying an
exit for which a corresponding verb was never defined.";
set_task_perms(caller_perms());
if (args[2] || $failed_match == (exit = this:match_exit(verb = args[1])))
  "** Before we give up, let's try sending an event_parse";
  if (this:huh_event_parse(args[1], args[2]))
    return 1;
  endif
...etc unchanged...

Now add the method $room:huh_event_parse:

@list $room:huh_event_parse without numbers
#3:"huh_event_parse"   this none this
"** Try to see if some object in the event_list wants to take a stab at this.";
parse_list = {args[1], @args[2]};
for o in (this.event_list)
  if ($recycler:valid(o))
    if (o:event_parse({player, this, parse_list}))
      return 1;
    endif
  else
    this.event_list = setremove(this.event_list, o);
  endif
endfor
return 0;

The last basic event method you need to add is EVENT_MOVE.  Modify #1:moveto:

@list #1:moveto without numbers
#1:"moveto"   this none this
set_task_perms(this.owner);
this:event_move({this.location, args[1]});
return move(this, args[1]);

HOW DO EVENTS FUNCTION?

Here is a sample of how events work.  In WHHOW I have built an object
called "Tester."  It has a simple override of event_* which prints out to
the player that an event has occured.  I have added white space to the log
make it more readable.

Notes surrounded by ** ** are comments.

@d tester:
Tester (#84) [ ]
  Child of generic thing (#5).
  Location Ballard (#2).
   #84:event_*                  Ballard (#2)         rxd    this none this
-------------------------------- finished ---------------------------------

@list tester:event_* without numbers
#84:"event_*"   this none this
l = $string_utils:english_list(args[1]);
this.location:announce_all(verb, " ", l);
return 1;
** In other words, whenever tester receives an event_* call, it **
** will print the string "event_foo args...." **

drop tester
event_move #2 and #11
** event_move is always sent to the object being moved. **
** #2 is the previous location, #11 is the new location **

You drop Tester.
;#11:add_to_event_list(#84)
=> {#84}

** Now, a Guest (#76) connects to the moo **

event_enter #76 and #11
** All event messages now come from Tester unless otherwise noted **
** The guest #76 entered room #11 **

event_look #76 and #11
** Guest #76 looked at room #11 **
Guest has connected.

event_exit #76 and #11
** Guest #76 left room #11 **
Guest enters the door into the ticket booth.

event_enter #76 and #11
event_look #76 and #11
** Guest #76 entered room #11, again a look was performed **
Guest leaves the ticket booth.

event_look #76 and #11
** Guest #76 looks at the room #11 again **

event_parse #76, #11, and {list}
** Guest #76 entered a command that could not be normally parsed in room #11 **

** At this point, I'll add a event_parse verb to myself **

@verb me:event_parse this none this
Verb added (0).
.program #2:event_parse
**** WARNING: The `.program' command is obsolete and will be removed soon.
Now programming Ballard:event_parse(0).  Use "." to end.
who = args[1][1];
where = args[1][2];
plist = args[1][3];
pstr = $string_utils:english_list(plist);
this:tell(who, " sent \"", pstr, "\" in ", where);
.
0 error(s).
Verb programmed.

** Now, I'll add myself to the room's event list and remove tester **
;#11:add_to_event_list(#2)
=> {#84, #2}
;#11:remove_from_event_list(#84)
=> {#2}

** Now, Guest will enter the command "Play with my nose" **
** The server will fail to parse this command at hand it to my event_parse **

#76 sent "Play, with, my, and nose" in #11
** Guest #76 typed in "Play..." in room #11 **

** Now, clear the event_list by passing the entire list to the remove method **
;#11:remove_from_event_list(#11.event_list)
=> {}

get tester
event_move #11 and #2
** This event is sent to tester directly, so tester's event_* method responds **
** Tester was moved from object #11 to object #2 **
You take Tester.

Ta-da.

---
Clay W. Luther                                 [email-masked]
Software Engineer                             Vortech Data, Inc
Office (214) 994-1377                        Fax (214) 994-1310
                             Also [email-masked]

Well, it had been 987 years in outer space time when I got back.
Couldn't seem to find any of my friends to tell my interesting
stories to.