This is Jobo's SocketMUD event handler, adapted for ROM. I've been using it on my mud for a couple years now. Hopefully I haven't forgotten anything in this file ;) For more complete documentation, visit Jobo's site at http://www.socketmud.dk and in particular, http://www.socketmud.dk/download/doc/event.txt. In Jojo's implementation, any numeric parameters/data required by the event function are stored in a string with sprintf() and extracted with sscanf(). I've added three general purpose integers for extra parameters to the struct instead, while retaining the string. I also added a link to another event for events involving more than a single entity. The link can be used to ensure that if either of the entities is extracted then the event(s) will be removed. Stuff that goes in merc.h #define DECLARE_EVENT_FUN( fun ) EVENT_FUN fun typedef bool EVENT_FUN args( ( EVENT_DATA *event ) ); typedef struct event_data EVENT_DATA; #define MAX_EVENT_HASH 128 /* * Events */ struct event_data { EVENT_DATA * next_global; EVENT_DATA * prev_global; EVENT_DATA * next_local; EVENT_DATA * prev_local; EVENT_DATA * link; EVENT_FUN * fun; char * argument; int value0; int value1; int value2; int passes; int type; int ownertype; int bucket; union { AREA_DATA * area; CHAR_DATA * ch; DESCRIPTOR_DATA *d; EXIT_DATA * exit; OBJ_DATA * obj; ROOM_INDEX_DATA *room; } owner; }; /* * Event definitions */ #define EVENT_NONE 0 #define EVENT_OWNER_UNOWNED 0 #define EVENT_OWNER_NONE 1 #define EVENT_OWNER_AREA 2 #define EVENT_OWNER_CHAR 3 #define EVENT_OWNER_DESC 4 #define EVENT_OWNER_EXIT 5 #define EVENT_OWNER_GAME 6 #define EVENT_OWNER_OBJ 7 #define EVENT_OWNER_ROOM 8 /* * The values below may be duplicates for different event types. */ /* Definitions for EVENT_OWNER_CHAR events */ /* Definitions for EVENT_OWNER_DESC events */ /* Definitions for EVENT_OWNER_EXIT events */ /* Definitions for EVENT_OWNER_GAME events */ /* Definitions for EVENT_OWNER_OBJ events */ /* Definitions for EVENT_OWNER_ROOM events */ Someplace in struct area_data: EVENT_DATA * event_first; EVENT_DATA * event_last; Someplace in struct char_data: EVENT_DATA * event_first; EVENT_DATA * event_last; Someplace in descriptor_data: EVENT_DATA * event_first; EVENT_DATA * event_last; Someplace in struct exit_data: EVENT_DATA * event_first; EVENT_DATA * event_last; Someplace in struct obj_data (NOT obj_index_data!): EVENT_DATA * event_first; EVENT_DATA * event_last; Someplace in struct room_index_data: EVENT_DATA * event_first; EVENT_DATA * event_last; Near the bottom where all the functions are declared, add this: #define EVD EVENT_DATA Then add the function prototypes themselves: /* event.c */ void add_event_area args( ( EVENT_DATA *event, AREA_DATA *ch, int delay ) ); void add_event_char args( ( EVENT_DATA *event, CHAR_DATA *ch, int delay ) ); void add_event_desc args( ( EVENT_DATA *event, DESCRIPTOR_DATA *d, int delay ) ); void add_event_exit args( ( EVENT_DATA *event, EXIT_DATA *exit, int delay ) ); void add_event_game args( ( EVENT_DATA *event, int delay ) ); void add_event_obj args( ( EVENT_DATA *event, OBJ_DATA *obj, int delay ) ); void add_event_room args( ( EVENT_DATA *event, ROOM_INDEX_DATA *room, int delay ) ); void dequeue_event args( ( EVENT_DATA *event ) ); int event_time_left args( ( EVENT_DATA *event ) ); EVD * get_event_area args( ( AREA_DATA *area, int type ) ); EVD * get_event_char args( ( CHAR_DATA *ch, int type ) ); EVD * get_event_desc args( ( DESCRIPTOR_DATA *d, int type ) ); EVD * get_event_exit args( ( EXIT_DATA *exit, int type ) ); EVD * get_event_game args( ( int type ) ); EVD * get_event_obj args( ( OBJ_DATA *obj, int type ) ); EVD * get_event_room args( ( ROOM_INDEX_DATA *room, int type ) ); void strip_event_area args(( AREA_DATA *area, int type ) ); void strip_event_char args(( CHAR_DATA *ch, int type ) ); void strip_event_desc args(( DESCRIPTOR_DATA *d, int type ) ); void strip_event_exit args(( EXIT_DATA *exit, int type ) ); void strip_event_game args(( int type ) ); void strip_event_obj args( ( OBJ_DATA *obj, int type ) ); void strip_event_room args(( ROOM_INDEX_DATA *room, int type ) ); void update_events args( ( void ) ); then near the end of merc.h, add this: #undef EVD Stuff to add to recycle.h: /* event recycling */ EVENT_DATA * new_event args( ( void ) ); void free_event args( ( EVENT_DATA *event ) ); Stuff that goes in recycle.c. The top_event and top_event_used variables should probably go in your global variables file if you have one, or in whatever file has your do_memory() function (db.c in stock ROM) and declarations for them added. If you don't want to track usage, take them out. /* Stuff for events */ EVENT_DATA *event_free; EVENT_DATA * new_event( void ) { EVENT_DATA *event; if ( event_free == NULL ) { event = (EVENT_DATA *)alloc_perm( sizeof( *event ) ); top_event++; } else { event = event_free; event_free = event_free->next_global; } memset( event, 0, sizeof( *event ) ); event->argument = &str_empty[0]; top_event_used++; return event; } void free_event( EVENT_DATA *event ) { free_string( event->argument ); event->next_global = event_free; event_free = event; top_event_used--; } A few more things have to be added in recycle.c, or wherever you may have the functions. If you don't have the function because you never free objects of that type (ie rooms), ignore it. In free_char(): while ( ch->event_first != NULL ) dequeue_event( ch->event_first ); In free_descriptor(): while ( d->event_first != NULL ) dequeue_event( d->event_first ); In free_exit(): while ( pExit->event_first != NULL ) dequeue_event( pExit->event_first ); In free_obj(): while ( obj->event_first != NULL ) dequeue_event( obj->event_first ); In free_room_index(): while ( pRoom->event_first != NULL ) dequeue_event( pRoom->event_first ); In update.c function update_handler(), right before the tail_chain() call: update_events( ); The actual event code from event.c: #include #include #include #include "merc.h" #include "recycle.h" static EVENT_DATA * event_q_first[MAX_EVENT_HASH]; static EVENT_DATA * event_q_last[MAX_EVENT_HASH]; static EVENT_DATA * pEventNext; static EVENT_DATA * global_event_first; static EVENT_DATA * global_event_last; static int current_bucket; static bool enqueue_event( EVENT_DATA *event, int game_pulses ); #define LINK_LOCAL(item, list, last) \ do { \ if ( last == NULL ) \ { \ last = item; \ list = item; \ item->next_local = NULL; \ item->prev_local = NULL; \ } \ else \ { \ item->next_local = list; \ item->prev_local = NULL; \ list->prev_local = item; \ list = item; \ } \ } while ( 0 ) #define UNLINK_GLOBAL(item, list, last, bugmess, bugarg) \ do { \ if ( last == list ) \ { \ last = NULL; \ list = NULL; \ } \ else if ( item == list ) \ { \ list = item->next_global; \ item->next_global->prev_global = NULL; \ } \ else if ( item == last ) \ { \ last = item->prev_global; \ item->prev_global->next_global = NULL; \ } \ else if ( !item->prev_global || !item->next_global ) \ { \ bug( bugmess, bugarg ); \ } \ else \ { \ item->prev_global->next_global = item->next_global; \ item->next_global->prev_global = item->prev_global; \ } \ } while ( 0 ) #define UNLINK_LOCAL(item, list, last, bugmess, bugarg) \ do { \ if ( last == list ) \ { \ last = NULL; \ list = NULL; \ } \ else if ( item == list ) \ { \ list = item->next_local; \ item->next_local->prev_local = NULL; \ } \ else if (item == last) \ { \ last = item->prev_local; \ item->prev_local->next_local = NULL; \ } \ else if ( !item->prev_local || !item->next_local ) \ { \ bug( bugmess, bugarg ); \ } \ else \ { \ item->prev_local->next_local = item->next_local; \ item->next_local->prev_local = item->prev_local; \ } \ } while ( 0 ) void add_event_area( EVENT_DATA *event, AREA_DATA *area, int delay ) { if ( event->type == EVENT_NONE ) { bug( "Add_event_area: no type.", 0 ); return; } if ( event->fun == NULL ) { bug( "Add_event_area: NULL EVENT_FUN.", 0 ); return; } event->ownertype = EVENT_OWNER_AREA; event->owner.area = area; LINK_LOCAL( event, area->event_first, area->event_last ); if ( !enqueue_event( event, delay ) ) bug( "Add_event_area: event type %d failed to enqueue.", event->type ); } void add_event_char( EVENT_DATA *event, CHAR_DATA *ch, int delay ) { if ( event->type == EVENT_NONE ) { bug( "Add_event_char: no type.", 0 ); return; } if ( event->fun == NULL ) { bug( "Add_event_char: NULL EVENT_FUN.", 0 ); return; } event->ownertype = EVENT_OWNER_CHAR; event->owner.ch = ch; LINK_LOCAL( event, ch->event_first, ch->event_last ); if ( !enqueue_event( event, delay ) ) bug( "Add_event_char: event type %d failed to enqueue.", event->type ); } void add_event_desc( EVENT_DATA *event, DESCRIPTOR_DATA *d, int delay ) { if ( event->type == EVENT_NONE ) { bug( "Add_event_desc: no type.", 0 ); return; } if ( event->fun == NULL ) { bug( "Add_event_desc: NULL EVENT_FUN.", 0 ); return; } event->ownertype = EVENT_OWNER_DESC; event->owner.d = d; LINK_LOCAL( event, d->event_first, d->event_last ); if ( !enqueue_event( event, delay ) ) bug( "Add_event_desc: event type %d failed to enqueue.", event->type ); } void add_event_exit( EVENT_DATA *event, EXIT_DATA *exit, int delay ) { if ( event->type == EVENT_NONE ) { bug( "Add_event_exit: no type.", 0 ); return; } if ( event->fun == NULL ) { bug( "Add_event_exit: NULL EVENT_FUN.", 0 ); return; } event->ownertype = EVENT_OWNER_EXIT; event->owner.exit = exit; LINK_LOCAL( event, exit->event_first, exit->event_last ); if ( !enqueue_event( event, delay ) ) bug( "Add_event_exit: event type %d failed to enqueue.", event->type ); } void add_event_game( EVENT_DATA *event, int delay ) { if ( event->type == EVENT_NONE ) { bug( "Add_event_game: no type.", 0 ); return; } if ( event->fun == NULL ) { bug( "Add_event_game: NULL EVENT_FUN.", 0 ); return; } event->ownertype = EVENT_OWNER_GAME; LINK_LOCAL( event, global_event_first, global_event_last ); if ( !enqueue_event( event, delay ) ) bug( "Add_event_game: event type %d failed to enqueue.", event->type ); } void add_event_obj( EVENT_DATA *event, OBJ_DATA *obj, int delay ) { if ( event->type == EVENT_NONE ) { bug( "Add_event_obj: no type.", 0 ); return; } if ( event->fun == NULL ) { bug( "Add_event_obj: NULL EVENT_FUN.", 0 ); return; } event->ownertype = EVENT_OWNER_OBJ; event->owner.obj = obj; LINK_LOCAL( event, obj->event_first, obj->event_last ); if ( !enqueue_event( event, delay ) ) bug( "Add_event_obj: event type %d failed to enqueue.", event->type ); } void add_event_room( EVENT_DATA *event, ROOM_INDEX_DATA *room, int delay ) { if ( event->type == EVENT_NONE ) { bug( "Add_event_room: no type.", 0 ); return; } if ( event->fun == NULL ) { bug( "Add_event_room: NULL EVENT_FUN.", 0 ); return; } event->ownertype = EVENT_OWNER_ROOM; event->owner.room = room; LINK_LOCAL( event, room->event_first, room->event_last ); if ( !enqueue_event( event, delay ) ) bug( "Add_event_room: event type %d failed to enqueue.", event->type ); } void dequeue_event( EVENT_DATA *event ) { EVENT_DATA *link; EVENT_DATA *chain; link = event->link; event->link = NULL; if ( link != NULL ) { for ( chain = link; chain != NULL; chain = chain->link ) if ( chain->link == event ) chain->link = NULL; dequeue_event( link ); } UNLINK_GLOBAL( event, event_q_first[event->bucket], event_q_last[event->bucket], "Dequeue_event: event type %d not found in global queue.", event->type ); if ( event == pEventNext ) pEventNext = pEventNext->next_global; switch( event->ownertype ) { default: bug( "Dequeue_event: event type %d has no owner.", event->type ); break; case EVENT_OWNER_AREA: UNLINK_LOCAL( event, event->owner.area->event_first, event->owner.area->event_last, "Dequeue_event(area): event type %d not found in local list.", event->type ); break; case EVENT_OWNER_CHAR: UNLINK_LOCAL( event, event->owner.ch->event_first, event->owner.ch->event_last, "Dequeue_event(char): event type %d not found in local list.", event->type ); break; case EVENT_OWNER_DESC: UNLINK_LOCAL( event, event->owner.d->event_first, event->owner.d->event_last, "Dequeue_event(descriptor): event type %d not found in local list.", event->type ); break; case EVENT_OWNER_EXIT: UNLINK_LOCAL( event, event->owner.exit->event_first, event->owner.exit->event_last, "Dequeue_event(exit): event type %d not found in local list.", event->type ); break; case EVENT_OWNER_GAME: UNLINK_LOCAL( event, global_event_first, global_event_last, "Dequeue_event(game): event type %d not found in local list.", event->type ); break; case EVENT_OWNER_OBJ: UNLINK_LOCAL( event, event->owner.obj->event_first, event->owner.obj->event_last, "Dequeue_event(obj): event type %d not found in local list.", event->type ); break; case EVENT_OWNER_ROOM: UNLINK_LOCAL( event, event->owner.room->event_first, event->owner.room->event_last, "Dequeue_event(room): event type %d not found in local list.", event->type ); break; } free_event( event ); } static bool enqueue_event( EVENT_DATA *event, int game_pulses ) { int bucket; int passes; if ( event->ownertype == EVENT_OWNER_UNOWNED ) { bug( "enqueue_event: event type %d with no owner.", event->type ); return FALSE; } if ( game_pulses < 1 ) game_pulses = 1; bucket = ( game_pulses + current_bucket ) % MAX_EVENT_HASH; passes = game_pulses / MAX_EVENT_HASH; event->bucket = bucket; event->passes = passes; if ( event_q_last[bucket] == NULL ) { event_q_last[bucket] = event; event_q_first[bucket] = event; event->next_global = NULL; event->prev_global = NULL; } else { event->next_global = NULL; event->prev_global = event_q_last[bucket]; event_q_last[bucket]->next_global = event; event_q_last[bucket] = event; } return TRUE; } int event_time_left( EVENT_DATA *pEvent ) { int pulses; pulses = pEvent->bucket - current_bucket + pEvent->passes * MAX_EVENT_HASH; if ( pulses < 0 ) pulses += MAX_EVENT_HASH; return pulses; } EVENT_DATA * get_event_area( AREA_DATA *area, int type ) { EVENT_DATA *event; for ( event = area->event_first; event != NULL; event = event->next_local ) if ( event->type == type ) return event; return NULL; } EVENT_DATA * get_event_char( CHAR_DATA *ch, int type ) { EVENT_DATA *event; for ( event = ch->event_first; event != NULL; event = event->next_local ) if ( event->type == type ) return event; return NULL; } EVENT_DATA * get_event_desc( DESCRIPTOR_DATA *d, int type ) { EVENT_DATA *event; for ( event = d->event_first; event != NULL; event = event->next_local ) if ( event->type == type ) return event; return NULL; } EVENT_DATA * get_event_exit( EXIT_DATA *d, int type ) { EVENT_DATA *event; for ( event = d->event_first; event != NULL; event = event->next_local ) if ( event->type == type ) return event; return NULL; } EVENT_DATA * get_event_game( int type ) { EVENT_DATA *event; for ( event = global_event_first; event != NULL; event = event->next_local ) if ( event->type == type ) return event; return NULL; } EVENT_DATA * get_event_obj( OBJ_DATA *obj, int type ) { EVENT_DATA *event; for ( event = obj->event_first; event != NULL; event = event->next_local ) if ( event->type == type ) return event; return NULL; } EVENT_DATA * get_event_room( ROOM_INDEX_DATA *room, int type ) { EVENT_DATA *event; for ( event = room->event_first; event != NULL; event = event->next_local ) if ( event->type == type ) return event; return NULL; } void strip_event_area( AREA_DATA *area, int type ) { EVENT_DATA *event; EVENT_DATA *event_next; for ( event = area->event_first; event != NULL; event = event_next ) { event_next = event->next_local; if ( event->type == type ) dequeue_event( event ); } } void strip_event_char( CHAR_DATA *ch, int type ) { EVENT_DATA *event; EVENT_DATA *event_next; for ( event = ch->event_first; event != NULL; event = event_next ) { event_next = event->next_local; if ( event->type == type ) dequeue_event( event ); } } void strip_event_desc( DESCRIPTOR_DATA *d, int type ) { EVENT_DATA *event; EVENT_DATA *event_next; for ( event = d->event_first; event != NULL; event = event_next ) { event_next = event->next_local; if ( event->type == type ) dequeue_event( event ); } } void strip_event_exit( EXIT_DATA *exit, int type ) { EVENT_DATA *event; EVENT_DATA *event_next; for ( event = exit->event_first; event != NULL; event = event_next ) { event_next = event->next_local; if ( event->type == type ) dequeue_event( event ); } } void strip_event_game( int type ) { EVENT_DATA *event; EVENT_DATA *event_next; for ( event = global_event_first; event != NULL; event = event_next ) { event_next = event->next_local; if ( event->type == type ) dequeue_event( event ); } } void strip_event_obj( OBJ_DATA *obj, int type ) { EVENT_DATA *event; EVENT_DATA *event_next; for ( event = obj->event_first; event != NULL; event = event_next ) { event_next = event->next_local; if ( event->type == type ) dequeue_event( event ); } } void strip_event_room( ROOM_INDEX_DATA *room, int type ) { EVENT_DATA *event; EVENT_DATA *event_next; for ( event = room->event_first; event != NULL; event = event_next ) { event_next = event->next_local; if ( event->type == type ) dequeue_event( event ); } } void update_events( void ) { EVENT_DATA *event; current_bucket = ( current_bucket + 1 ) % MAX_EVENT_HASH; for ( event = event_q_first[current_bucket]; event != NULL; event = pEventNext ) { pEventNext = event->next_global; if ( event->passes-- > 0 ) continue; if ( ! ( ( event->fun )( event ) ) ) dequeue_event( event ); } } Adding events is pretty straightforward. Use the value0, value1, value2 and argument fields in the event struct for anything you'd like. Add your event to the event queue by calling the appropriate add_event_xxx() function. The third argument (or second in the case of add_event_game()) is when the event should be scheduled to occur, in pulses; so if you want it to happen ten seconds from now you'd use something like 10 * PULSE_PER_SECOND. If your event function removes the event from the queue, it should return TRUE. If it returns FALSE, the event handling code will remove the event. Note that events must be removed from the queue after they occur. If you want a recurring event, the event function needs to schedule a new event. Here are some examples of events with this system. On my mud, if a player is killed, he may not attack or be attacked by another player for a random period of one to ten minutes. Here's how it's done. In merc.h, a definition and declaration: #define EVENT_CHAR_NOPK 2 DECLARE_EVENT_FUN( event_char_nopk ); When a player is killed, in fight.c: event = new_event( ); event->type = EVENT_CHAR_NOPK; event->fun = event_char_nopk; add_event_char( event, victim, number_range( 1 * 60 * PULSE_PER_SECOND, 10 * 60 * PULSE_PER_SECOND ) ); In is_safe(): if ( get_event_char( ch, EVENT_CHAR_NOPK ) != NULL ) { send_to_char( "Not yet.\n\r", ch ); return TRUE; } if ( get_event_char( victim, EVENT_CHAR_NOPK ) != NULL ) { send_to_char( "Not yet.\n\r", ch ); return TRUE; } Similarly in is_safe_spell(); if ( get_event_char( ch, EVENT_CHAR_NOPK ) != NULL || get_event_char( victim, EVENT_CHAR_NOPK ) != NULL ) return TRUE; And the actual function which gets called when the time is up? /* * No action required. Simply let the event driver strip the event. */ bool event_char_nopk( EVENT_DATA *event ) { return FALSE; } Another example is, in my mud, when an object is dropped in a room that is either SECT_AIR or has no floor, and there is an exit down and the exit is not closed, the object falls through into the room below. If the room it falls into is also either air or nofloor, it falls again, etc. But it doesn't happen all at once, it happens after a short delay. This will possibly give players a chance to snag it on the way by, allow mobprogs to react, etc. In merc.h, a #define and declarations: #define EVENT_OBJ_FALL 3 DECLARE_EVENT_FUN( event_obj_fall ); bool add_obj_fall_event args( ( OBJ_DATA *obj ) ); Scattered through out the code, such as in do_drop, do_disarm, and basically any place where an object is placed in a room, I added a call like so (this being from do_drop): obj_from_char( obj ); obj_to_room( obj, ch->in_room ); + add_obj_fall_event( obj ); act( "$n drops $p.", ch, obj, NULL, TO_ROOM ); act( "You drop $p.", ch, obj, NULL, TO_CHAR ); oprog_drop_trigger( obj, ch ); Here's the function itself, in act_obj.c: bool add_obj_fall_event( OBJ_DATA *obj ) { ROOM_INDEX_DATA * in_room; ROOM_INDEX_DATA * to_room; EXIT_DATA * pExit; EVENT_DATA * evNew; if ( ( in_room = obj->in_room ) == NULL ) return FALSE; if ( in_room->sector_type != SECT_AIR && !IS_SET( in_room->room_flags, ROOM_NO_FLOOR ) ) return FALSE; if ( ( pExit = in_room->exit[DIR_DOWN] ) == NULL || IS_SET( pExit->exit_info, EX_CLOSED ) ) return FALSE; if ( ( to_room = pExit->to_room ) == NULL ) return FALSE; evNew = new_event( ); evNew->fun = event_obj_fall; evNew->type = EVENT_OBJ_FALL; evNew->value0 = 0; add_event_obj( evNew, obj, 1 ); return TRUE; } The actual event function. It guards against circular paths by way of MAX_FALL, which is #defined as 10 on my mud, using value0 to track how many rooms it's fallen through. A more sophisticated version might keep track of which rooms its already fallen through by storing the list in event->argument via sprintf(). bool event_obj_fall( EVENT_DATA *event ) { OBJ_DATA * obj; ROOM_INDEX_DATA * in_room; ROOM_INDEX_DATA * to_room; EXIT_DATA * pExit; CHAR_DATA * ch; EVENT_DATA * evNew; obj = event->owner.obj; if ( ( in_room = obj->in_room ) == NULL ) return FALSE; if ( event->value0 > MAX_FALL ) { extract_obj( obj ); return TRUE; } if ( in_room->sector_type != SECT_AIR && !IS_SET( in_room->room_flags, ROOM_NO_FLOOR ) ) return FALSE; if ( ( pExit = in_room->exit[DIR_DOWN] ) == NULL || IS_SET( pExit->exit_info, EX_CLOSED ) ) return FALSE; if ( ( to_room = pExit->to_room ) == NULL ) return FALSE; if ( ( ch = in_room->people ) != NULL ) act( "$p falls away below.", ch, obj, NULL, TO_ALL ); obj_from_room( obj ); obj_to_room( obj, to_room ); if ( ( ch = to_room->people ) != NULL ) act( "$p falls down from above.", ch, obj, NULL, TO_ALL ); evNew = new_event( ); evNew->fun = event_obj_fall; evNew->type = EVENT_OBJ_FALL; evNew->value0 = event->value0 + 1; add_event_obj( evNew, obj, 1 ); return FALSE; }