daleken/
daleken/data/notes/
daleken/data/player/
daleken/data/system/poses/
daleken/doc/Homepage/images/
daleken/log/
/*___________________________________________________________________________*
   )()(			  DalekenMUD 1.12 (C) 2000			)()(
   `]['		       by Martin Thomson, Lee Brooks,			`]['
    ||		       Ken Herbert and David Jacques			 ||
    || ----------------------------------------------------------------- ||
    || Envy Diku Mud improvements copyright (C) 1994 by Michael Quan,	 ||
    || David Love, Guilherme 'Willie' Arnold, and Mitchell Tse.		 ||
    || Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael	 ||
    || Chastain, Michael Quan, and Mitchell Tse.			 ||
    || Original Diku Mud copyright (C) 1990, 1991			 ||
    || by Sebastian Hammer, Michael Seifert, Hans Henrik St{rfeldt,	 ||
    || Tom Madsen, and Katja Nyboe.					 ||
    || ----------------------------------------------------------------- ||
    || Any use of this software must follow the licenses of the		 ||
    || creators.  Much time and thought has gone into this software and	 ||
    || you are benefitting. We hope that you share your changes too.	 ||
    || What goes around, comes around.					 ||
    || ----------------------------------------------------------------- ||
    ||				callback.c				 ||
    || Callback functions for events.					 ||
 *_/<>\_________________________________________________________________/<>\_*/


#include "mud.h"
#include "event.h"


/* Externals */
void show_char_to_char	args( ( CHAR_DATA *list, CHAR_DATA *ch ) );
void show_list_to_char	args( ( OBJ_DATA *list, CHAR_DATA *ch, bool fShort,
				bool fShowNothing ) );
DECLARE_DO_FUN( mp_cast );

int evn_picklock;
int evn_picktrap;
int evn_mercenary;
int evn_gate_demon;
int evn_raised_undead;
int evn_swan_song;
int evn_char_fall;
int evn_char_sink;
int evn_char_drown;
int evn_char_suffocate;
int evn_web_struggle;
int evn_stand_up;
int evn_regeneration;
int evn_plant_grow;
int evn_plant_die;
int evn_explode;
int evn_obj_decay;
int evn_imp_grab;
int evn_obj_fall;
int evn_spec_fun;
int evn_class_action;
int evn_hunt_fleer;
int evn_scavenge;
int evn_wander;
int evn_cone_remove;
int evn_room_violence;
int evn_prog_trigger;
int evn_prog_wait;
int evn_area_reset;

struct event_table_type event_table [] = {
    /* Player actions */
    { "pick lock",	&evn_picklock,	ecb_finish_pick,
      EVENT_TEMPORARY	},
    { "pick trap",	&evn_picktrap,	ecb_pick_trap,
      EVENT_TEMPORARY	},
    { "mercenary",	&evn_mercenary,	ecb_mercenary,
      0		},
    { "gate demon",	&evn_gate_demon, ecb_gate_demon,
      0		},
    { "raised undead",	&evn_raised_undead, ecb_raised_undead,
      0		},
    { "swan song",	&evn_swan_song,	ecb_swan_song,
      EVENT_DEATH_REMOVE },
    { "char fall",	&evn_char_fall,	ecb_char_fall,
      EVENT_DEATH_REMOVE },
    { "char sink",	&evn_char_sink,	ecb_char_sink,
      EVENT_DEATH_REMOVE },
    { "char drown",	&evn_char_drown, ecb_char_drown,
      EVENT_DEATH_REMOVE },
    { "char suffocate",	&evn_char_suffocate, ecb_char_suffocate,
      EVENT_DEATH_REMOVE },
    { "web struggle",	&evn_web_struggle, ecb_web_struggle,
      EVENT_DEATH_REMOVE },
    { "stand up",	&evn_stand_up,	ecb_stand_up,
      EVENT_DEATH_REMOVE | EVENT_TEMPORARY },
    { "regeneration",	&evn_regeneration, ecb_regeneration,
      EVENT_DEATH_REMOVE | EVENT_STACKABLE },

    /* Object actions */
    { "plant die",	&evn_plant_grow, ecb_plant_grow,
      EVENT_NO_QUIT	},
    { "plant die",	&evn_plant_die,	ecb_plant_die,
      EVENT_NO_QUIT	},
    { "explode",	&evn_explode,	ecb_explode,
      EVENT_TEMPORARY	},

    /* Update replacements */
    { "object decay",	&evn_obj_decay,	ecb_obj_decay,
      0		},
    { "imp grab",	&evn_imp_grab,	ecb_imp_grab,
      EVENT_TEMPORARY	},
    { "object fall",	&evn_obj_fall,	ecb_obj_fall,
      EVENT_TEMPORARY	},

    { "special function", &evn_spec_fun, ecb_spec_fun,
      EVENT_DEATH_REMOVE | EVENT_TEMPORARY	},
    { "class action",	&evn_class_action, ecb_class_action,
      EVENT_DEATH_REMOVE | EVENT_TEMPORARY	},
    { "hunt fleer",	&evn_hunt_fleer, ecb_hunt_fleer,
      EVENT_TEMPORARY | EVENT_DEATH_REMOVE	},
    { "scavenge",	&evn_scavenge,	ecb_scavenge,
      EVENT_TEMPORARY | EVENT_DEATH_REMOVE	},
    { "wander",		&evn_wander,	ecb_wander,
      EVENT_TEMPORARY | EVENT_DEATH_REMOVE	},

    { "remove cone of silence",	&evn_cone_remove,	ecb_cone_remove,
      0		},
    { "room violence",	&evn_room_violence, ecb_room_violence,
      0		},

    /* Mud prog callback */
    { "mud program",	&evn_prog_trigger, ecb_prog_trigger,
      EVENT_TEMPORARY|EVENT_STACKABLE|EVENT_DEATH_REMOVE	},
    { "mud prog wait",	&evn_prog_wait,	ecb_prog_wait,
      EVENT_TEMPORARY|EVENT_STACKABLE|EVENT_DEATH_REMOVE	},

    /* Area reset */
    { "area reset",	&evn_area_reset, ecb_area_reset,
      EVENT_TEMPORARY	},

    { "",	NULL,	NULL,	0	}
};

/*
 * Finds an event by name.
 */
int event_lookup( const char *name )
{
    int i;

    for( i = 0; event_table[i].type >= 0; ++i )
    {
	if( !str_cmp( event_table[i].name, name ) )
	    return *event_table[i].type;
    }
    return -1;
}


struct event_table_type *event_table_lookup( const char *name )
{
    int i;

    for( i = 0; event_table[i].type >= 0; ++i )
    {
	if( !str_cmp( event_table[i].name, name ) )
	    return (event_table + i);
    }
    return NULL;
}


void ecb_finish_pick( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;
    EXIT_DATA *pexit;
    EXIT_DATA *pexit_rev;
    OBJ_DATA *obj;
    ROOM_INDEX_DATA *to_room;

    /* Check skill roll for player-char, make sure mob isn't charmed */
    if( ( !IS_NPC( ch ) && !get_success( ch, gsn_pick_lock, 100 ) )
	|| ( IS_NPC( ch ) && IS_AFFECTED( ch, AFF_CHARM ) ) )
    {
	send_to_char( "You failed.\n\r", ch );
	return;
    }

    if( e->data[0] == 0 )	/* pick a door */
    {
	if( e->data[1] < 0 || e->data[1] >= MAX_DIR
	    || !( pexit = ch->in_room->exit[e->data[1]] ) )
	{
	    bug( "%s picking invalid door %d.", ch->name, e->data[1] );
	    return;
	}

	if( pexit->key < 0 )
	{
	    act( "$d can't be picked.", ch, NULL, pexit->keyword,
		 TO_CHAR );
	    return;
	}
	if( IS_SET( pexit->exit_info, EX_PICKPROOF )
	    || ( IS_SET( pexit->exit_info, EX_HARDPICK )
		 && number_bits( 2 ) ) )
	{
	    act( "You failed to pick the $d.", ch, NULL, pexit->keyword,
		 TO_CHAR );
	    return;
	}

	REMOVE_BIT( pexit->exit_info, EX_LOCKED );
	act( "&y*Click*  The $d's lock springs open.", ch, NULL,
	     pexit->keyword, TO_CHAR );
	act( "&y$n picks the $d.&n", ch, NULL, pexit->keyword, TO_ROOM );

	/* pick the other side */
	if( ( to_room = pexit->to_room )
	    && ( pexit_rev = to_room->exit[rev_dir[e->data[1]]] )
	    && pexit_rev->to_room == ch->in_room )
	{
	    REMOVE_BIT( pexit_rev->exit_info, EX_LOCKED );
	}
    }
    else	/* picking a container */
    {
	if( !( obj = e->extra.target.obj ) || obj->deleted )
	{
	    bug( "%s picking invalid object.", ch->name );
	    return;
	}

	if( obj->value[2] < 0 )
	{
	    act( "$p can't be unlocked.", ch, obj, NULL, TO_CHAR );
	    return;
	}
	if( IS_SET( obj->value[1], CONT_PICKPROOF )
	    || ( IS_SET( obj->value[1], CONT_HARDPICK )
		 && number_bits( 2 ) ) )
	{
	    act( "You failed to pick $p.", ch, obj, NULL, TO_CHAR );
	    return;
	}

	REMOVE_BIT( obj->value[1], CONT_LOCKED );
	act( "&y*Click*  $p's lock springs open.", ch, obj,
	     NULL, TO_CHAR );
	act( "$n picks $p.", ch, obj, NULL, TO_ROOM );
    }
    return;
}


/*
 * Called in between starting a pick and the finish event.
 */
void ecb_pick_trap( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;
    int trap, dam;

    act( "&rYou set off a trap!", ch, NULL, NULL, TO_CHAR );

    trap = number_range( gsn_first_trap, gsn_last_trap );
    dam = percent_fuzzy( 4 + ch->level / 2 + ch->hit / 10, 10 );
    if( number_percent( ) < power( 8, 7, get_curr_dex( ch ) - 16 ) )
    {
	act( "&rA $t shoots out and barely misses $n!&n", ch,
	     skill_table[trap].noun_damage, NULL, TO_ROOM );
	act( "&rA $t shoots out and barely misses you!&n", ch,
	     skill_table[trap].noun_damage, NULL, TO_CHAR );
    }
    else
    {
	act( "&rA $t shoots out and strikes $n!&n", ch,
	     skill_table[trap].noun_damage, NULL, TO_ROOM );
	act( "&rA $t shoots out and strikes you!&n", ch,
	     skill_table[trap].noun_damage, NULL, TO_CHAR );
	damage( ch, ch, dam, trap, WEAR_NONE );
    }
    return;
}


/*
 * A mercenary finishing his hire.
 */
void ecb_mercenary( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;
    ROOM_INDEX_DATA *pRoom;

    do_say( ch, "I have completed my work, now I must leave." );
    xREMOVE_BIT( ch->affected_by, AFF_CHARM );
    do_follow( ch, "self" );
    ch->master = NULL;
    if( !( pRoom = get_room_index( e->data[0] ) ) )
    {
	raw_kill( ch, ch );
	return;
    }
    act( "$n disappears!", ch, NULL, NULL, TO_ROOM );
    char_from_room( ch );
    char_to_room( ch, pRoom );
}


/*
 * A gate demon returning to the underworld.
 */
void ecb_gate_demon( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;

    act( "$n screams a heart rending cry and is sucked back into the void.",
	 ch, NULL, NULL, TO_ROOM );
    extract_char( ch, TRUE );
}


/*
 * An undead creature that was raised up.
 */
void ecb_raised_undead( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;

    act( "$n suddenly slows, then slumps to the ground lifeless.",
	 ch, NULL, NULL, TO_ROOM );
    raw_kill( ch, ch );
}


/*
 * A player that refused to die (swan song skill).
 */
void ecb_swan_song( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;

    xREMOVE_BIT( ch->affected_by, AFF_DEAD );
    if( number_bits( 6 ) == 0 )
    {
	do_shout( ch, "Hey, I'm not dead yet!" );
	ch->hit = UMAX( number_range( 1, 20 ), ch->hit );
    }
    else
    {
	act( "&R$n suffer$% a MASSIVE haemorrage "
	     "and $e crumple$%, stone cold dead.",
	     ch, NULL, NULL, TO_ALL );
	raw_kill( ch, ch );
    }
}


/*
 * Player/mob is falling, this continues the downward spiral...
 * The character should already be ready to fall, but we have to check.
 */
void ecb_char_fall( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;
    EXIT_DATA *pexit;
    ROOM_INDEX_DATA *to_room;

    /* check if falling is still possible */
    if( xIS_SET( ch->affected_by, AFF_FLYING )
	|| IS_SET( ch->body_parts, BODY_PART_WINGS )
	|| !( pexit = ch->in_room->exit[DIR_DOWN] )
	|| ( IS_SET( pexit->exit_info, EX_CLOSED )
	     && ( ( !IS_SET( race_table[ch->race].race_abilities, RACE_PASSDOOR )
		    && !IS_AFFECTED( ch, AFF_PASS_DOOR ) )
		  || IS_SET( pexit->exit_info, EX_PASSPROOF ) ) )
	|| !( to_room = pexit->to_room ) )
	return;

    send_to_char( "&rYou fall!&n ARRRRGGGGHHHH.........\n\r", ch );
    act( "$n falls.", ch, NULL, NULL, TO_ROOM );
    stop_fighting( ch, TRUE );
    char_from_room( ch );
    char_to_room( ch, to_room );
    act( "$n has just dropped in.", ch, NULL, NULL, TO_ROOM );
    if( ch->desc )
    {
	charprintf( ch, "&y%s  %s&n\n\r", ch->in_room->name,
		    sector_string[ch->in_room->sector_type] );
	show_list_to_char( ch->in_room->contents, ch, FALSE, FALSE );
	show_char_to_char( ch->in_room->people, ch );
    }
    char_fall_check( ch, e->data[0] );
}


/*
 * Player/mob is sinking, this continues the downward spiral...
 * The character should already be ready to fall.
 */
void ecb_char_sink( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;
    OBJ_DATA *boat;
    EXIT_DATA *pexit;
    ROOM_INDEX_DATA *toroom;

    if( ch->in_room->sector_type != SECT_WATER_SWIM
	&& ch->in_room->sector_type != SECT_WATER_NOSWIM
	&& ch->in_room->sector_type != SECT_UNDERWATER
	&& !IS_SET( ch->in_room->room_flags, ROOM_FLOODED ) )
	return;

    for( boat = ch->carrying; boat; boat = boat->next_content )
	if( !boat->deleted && boat->item_type == ITEM_BOAT )
	    break;

    if( !( pexit = ch->in_room->exit[boat ? DIR_UP : DIR_DOWN] )
	|| ( IS_SET( pexit->exit_info, EX_CLOSED )
	     && ( ( !IS_SET( race_table[ch->race].race_abilities, RACE_PASSDOOR )
		    && !IS_AFFECTED( ch, AFF_PASS_DOOR ) )
		  || IS_SET( pexit->exit_info, EX_PASSPROOF ) ) )
	|| !( toroom = ch->in_room->exit[boat ? DIR_UP : DIR_DOWN]->to_room ) )
	return;

    if( toroom->sector_type != SECT_WATER_SWIM
	&& toroom->sector_type != SECT_WATER_NOSWIM
	&& toroom->sector_type != SECT_UNDERWATER
	&& !IS_SET( toroom->room_flags, ROOM_FLOODED ) )
	return;

    if( boat )
    {
	act( "&bYour boat forces you to float higher in the water.",
	     ch, NULL, NULL, TO_CHAR );
	act( "&b$n floats up, bouyed by $s boat.", ch, NULL, NULL, TO_ROOM );
    }
    else
	act( "&b$n slowly sink$% in the water.", ch, NULL, NULL, TO_ALL );
    char_from_room( ch );
    char_to_room( ch, toroom );
    if( boat )
	act( "&b$n has bobbed up from below.", ch, NULL, NULL, TO_ROOM );
    else
	act( "&b$n has drifted in from above.", ch, NULL, NULL, TO_ROOM );
    do_look( ch, AUTOLOOK );
    return;
}


/*
 * Player/mob is underwater and they can't breathe...
 * The character should already be ready to fall.
 */
void ecb_char_drown( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;

    if( IS_AFFECTED( ch, AFF_BREATHING )
	|| !ch->in_room
	|| ch->in_room->sector_type == SECT_WATER_SWIM
	|| ( ( ( ch->in_room->sector_type != SECT_UNDERWATER
		 && !IS_SET( ch->in_room->room_flags, ROOM_FLOODED ) )
	       || IS_SET( ch->body_parts, BODY_PART_GILLS ) )
	     && ( ch->in_room->sector_type == SECT_UNDERWATER
		  || IS_SET( ch->body_parts, BODY_PART_LUNGS ) ) ) )
	return;

    send_to_char( "&RYou are drowning!&n\n\r", ch );
    act( "$n sputters and chokes!", ch, NULL, NULL, TO_ROOM );
    if( e->data[0] == 5 )
	ch->position = POS_STUNNED;
    else if( e->data[0] == 10 )
    {
	act( "$n is DEAD!", ch, NULL, NULL, TO_ROOM );
	if( !IS_NPC( ch ) )
	{
	    SysInfo->deaths++;
	    sprintf( log_buf, "%s drowned at %s",
		     ch->name, ch->in_room->name );
	    talk_channel( NULL, log_buf, CHANNEL_INFO, "INFO" );
	    log_string( "%s (%d)", log_buf, ch->in_room->vnum );
	    wiznetf( ch, WIZ_DEATHS, get_trust( ch ), "%s (%d)",
		     log_buf, ch->in_room->vnum );
	    ch->hit = 1;
	}
	raw_kill( ch, ch );
	return;
    }

    damage( ch, ch, ch->level + e->data[0], gsn_breathing, WEAR_NONE );

    e->data[0]++;
    duplicate_event( e, number_fuzzy( 4 * PULSE_PER_SECOND ) );
    return;
}


/*
 * A player who is stranded in space without the means to breathe.
 */
void ecb_char_suffocate( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;

    if( IS_AFFECTED( ch, AFF_BREATHING )
	|| ch->in_room->sector_type != SECT_SPACE )
	return;

    send_to_char( "&RYou can't breathe!&n\n\r", ch );
    act( "$n can't breathe, $e is turning red!", ch, NULL, NULL, TO_ROOM );

    if( e->data[0] == 2 )
	ch->position = POS_STUNNED;
    else if( e->data[0] == 5 )
    {
	act( "$n is DEAD!", ch, NULL, NULL, TO_ROOM );
	if( !IS_NPC( ch ) )
	{
	    SysInfo->deaths++;
	    sprintf( log_buf, "%s died due to lack of air at %s",
		     ch->name, ch->in_room->name );
	    talk_channel( NULL, log_buf, CHANNEL_INFO, "INFO" );
	    log_string( "%s (%d)", log_buf, ch->in_room->vnum );
	    wiznetf( ch, WIZ_DEATHS, get_trust( ch ), "%s (%d)",
		     log_buf, ch->in_room->vnum );
	    ch->hit = 1;
	}

	raw_kill( ch, ch );
	return;
    }

    damage( ch, ch, ch->level * ( e->data[0] / 3 + 1 ),
	    gsn_breathing, WEAR_NONE );

    e->data[0]++;
    duplicate_event( e, number_fuzzy( 3 * PULSE_PER_SECOND ) );
    return;
}


/*
 * An entangled character struggling in a web.
 */
void ecb_web_struggle( EVENT *e )
{
    web_update( e->actor.target.ch );
}


/*
 * For all those mobs you throw, trip and smash.
 */
void ecb_stand_up( EVENT *e )
{
    do_stand( e->actor.target.ch, "" );
}


/*
 * Healing effect for the regeneration spell.
 */
void ecb_regeneration( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;
    int heal;

    heal = e->data[1] * 3 / 100;
    if( ch->hit < ch->max_hit / 5 )
	heal *= 2;
    heal = UMIN( heal, e->data[0] );
    if( ch->hit >= ch->max_hit )
	heal = 0;
    else
    {
	heal = UMIN( heal, ch->max_hit - ch->hit );
	ch->hit += heal;
	update_pos( ch );
    }

    e->data[0] -= heal + e->data[1] / 250 + 1;
    if( e->data[0] > 0 )
	duplicate_event( e, PULSE_PER_SECOND );
    else
	send_to_char( "You stop regenerating.\n\r", ch );
}


/*
 * For all those mobs who would like to cast spells and use their skills in
 * combat.
 */
void ecb_class_action( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;
    CHAR_DATA *victim;
    int sk, i = 0;
    char buf[MAX_INPUT_LENGTH];
    char buf2[MAX_INPUT_LENGTH];

    if( ch->class < 0 || !( victim = ch->fighting ) )
	return;

    /* Pick a skill */
    do
    {
	i++;
	sk = number_range( 1, MAX_SKILL - 1 );
    }
    while( i < MAX_SKILL / 4
	   && ( ch->level < skill_table[sk].skill_level[ch->class]
			|| !IS_SET( skill_table[sk].skill_type,
				    SKILL_TYPE_ACTION ) ) );

    if( i < MAX_SKILL / 4 )
    {
	if( skill_table[sk].spell_fun == spell_null )
	{
	    one_argument( skill_table[sk].name, buf );
	    if( !str_suffix( " combo", skill_table[sk].name ) )
	    {
		sprintf( buf2, "combo %s", buf );
		interpret( ch, buf2 );
	    }
	    else
		interpret( ch, buf );
	}
	else
	{
	    sprintf( buf, "'%s'", skill_table[sk].name );
	    if( !str_cmp( "dispel magic", skill_table[sk].name ) )
	    {
		sprintf( buf, "'%s' '%s'", skill_table[sk].name, victim->name );
	    }
	    mp_cast( ch, buf );
	}
    }
    if( !ch->deleted && ch->fighting )
	create_char_event( ch, evn_class_action,
			   ( i < MAX_SKILL / 4 )
			   ? skill_table[sk].beats : PULSE_VIOLENCE );
    return;
}


/*
 * Plant specific code, when they finally mature.
 */
void ecb_plant_grow( EVENT *e )
{
    OBJ_DATA *obj = e->actor.target.obj;
    CHAR_DATA *rch;

    obj->item_type = ITEM_PLANT;
    if( obj->carried_by )
    {
	act( "$p is now fully grown.", obj->carried_by, obj, NULL, TO_CHAR );
    }
    else if( obj->in_room && ( rch = obj->in_room->people ) )
    {
	act( "$p is now fully grown.", rch, obj, NULL, TO_ALL );
    }
}


/*
 * Not all plants make it to maturity.
 */
void ecb_plant_die( EVENT *e )
{
    OBJ_DATA *obj = e->actor.target.obj;
    CHAR_DATA *rch;

    if( obj->carried_by )
    {
	act( "$p slowly withers and dies.", obj->carried_by, obj, NULL, TO_CHAR );
    }
    else if( obj->in_room && ( rch = obj->in_room->people ) )
    {
	act( "$p slowly withers and dies.", rch, obj, NULL, TO_ALL );
    }
}


/*
 * Explosion event.
 */
void ecb_explode( EVENT *e )
{
    OBJ_DATA *obj = e->actor.target.obj;

    if( obj->carried_by )
	act( "&r$p violently explodes!", obj->carried_by, obj, NULL, TO_CHAR );
    else if( obj->in_room && obj->in_room->people )
	act( "&r$p violently explodes!", obj->in_room->people,
	     obj, NULL, TO_ALL );
    explode( obj );
}


/*
 * Object decay.
 * Added a container effect where items fall out and decay as well.
 */
void ecb_obj_decay( EVENT *e )
{
    OBJ_DATA *obj = e->actor.target.obj;
    const char *message;
    OBJ_DATA *obj_content, *obj_next;
    OBJ_DATA *cont_obj = NULL;
    CHAR_DATA *cont_ch = NULL;
    ROOM_INDEX_DATA *cont_room = NULL;
    int delay = 25;

    switch( obj->item_type )
    {
    default:
	message = "$p vanishes.";
	break;
    case ITEM_PLANT:
	message = "$p slowly withers and dies.";
	break;
    case ITEM_FOUNTAIN:
	message = "$p dries up.";
	break;
    case ITEM_CORPSE_NPC:
	message = "$p decays into dust.";
	delay = 3;
	break;
    case ITEM_CORPSE_PC:
	message = "$p is taken by a beam of light from heaven.";
	break;
    case ITEM_FOOD:
	message = "$p decomposes.";
	break;
    case ITEM_EXPLOSIVE:
	message = "$p fizzles and sparks weakly.";
	break;
    case ITEM_PORTAL:
	message = "$p shimmers and fades back into nothingness.";
	break;
    }

    if( obj->in_room )
    {
	if( ( cont_ch = obj->in_room->people ) )
	    act( message, cont_ch, obj, NULL, TO_ALL );
	cont_room = obj->in_room;
	obj_from_room( obj );
    }
    else if( obj->carried_by )
    {
	act( message, obj->carried_by, obj, NULL, TO_CHAR );
	cont_ch = obj->carried_by;
	obj_from_char( obj );
    }
    else if( obj->in_obj )
    {
	cont_obj = obj->in_obj;
	obj_from_obj( obj );
    }

    for( obj_content = obj->contains; obj_content; obj_content = obj_next )
    {
	obj_next = obj_content->next_content;
	if( obj_content->deleted )
	    continue;

	obj_from_obj( obj_content );
	if( cont_room )
	    obj_to_room( obj_content, cont_room );
	else if( cont_ch )
	    obj_to_char( obj_content, cont_ch );
	else if( cont_obj )
	    obj_to_obj( obj_content, cont_obj );
	else
	{
	    extract_obj( obj_content );
	    continue;
	}
	/* rely on the imp grab to get rid of money */
	if( obj->item_type != ITEM_MONEY )
	    set_timer_tick( obj_content, percent_fuzzy( delay, 20 ) );
    }

    extract_obj( obj );
    return;
}


void ecb_imp_grab( EVENT *e )
{
    OBJ_DATA *obj = e->actor.target.obj;
    CHAR_DATA *rch;

    if( ( rch = obj->in_room->people ) )
	act( "&yA small imp runs in and grabs $p.",
	     rch, obj, NULL, TO_ALL );

    extract_obj( obj );
}


void ecb_obj_fall( EVENT *e )
{
    OBJ_DATA *obj = e->actor.target.obj;
    CHAR_DATA *rch;
    ROOM_INDEX_DATA *new_room;
    EXIT_DATA *pexit = obj->in_room->exit[DIR_DOWN];

    /* check that falling is still possible*/
    if( !pexit || !( new_room = pexit->to_room )
	|| IS_SET( pexit->exit_info, EX_CLOSED ) )
	return;

    if( ( rch = obj->in_room->people ) != NULL )
	act( "$p falls away.", rch, obj, NULL, TO_ALL );

    obj_from_room( obj );
    obj_to_room( obj, new_room );

    if( ( rch = obj->in_room->people ) != NULL )
	act( "$p drops from the sky.", rch, obj, NULL, TO_ALL );

    if( !obj->in_room )		/* really now, not necessary :p */
	return;

    /* check that the fall continues */
    obj_fall_check( obj, e->data[0] );
}


/*
 * Mobile spec procedures.
 */
void ecb_spec_fun( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;

    if( !ch->in_room
	|| IS_AFFECTED( ch, AFF_CHARM ) )
	return;

    if( IS_SET( spec_table[ch->spec_fun].usage, SPEC_FIGHT )
	&& !ch->fighting )
	return;

    (*spec_table[ch->spec_fun].spec_fun)
	( ch, NULL, spec_table[ch->spec_fun].usage, NULL );
    if( !ch->deleted )
	create_char_event( ch, evn_spec_fun,
			   number_range( PULSE_MOBILE / 2,
					 3 * PULSE_MOBILE / 2 ) );
}


/*
 * A mob will follow a fleeing PC.
 */
void ecb_hunt_fleer( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;

    if( IS_AFFECTED( ch, AFF_CHARM )
	|| !ch->tracking || ch->position < POS_STANDING )
	return;
    if( mob_track_update( ch ) )
	create_char_event( ch, evn_hunt_fleer,
			   number_range( PULSE_MOBILE / 2,
					 3 * PULSE_MOBILE / 2 ) );
}


/*
 * Pick up that rubbish, used to be in mobile_update.
 */
void ecb_scavenge( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;
    OBJ_DATA *obj;
    OBJ_DATA *obj_best = NULL;
    int max = 1;

    if( IS_AFFECTED( ch, AFF_CHARM )
	|| ch->position < POS_STANDING )
	return;

    if( ch->in_room->contents )
    {
	for( obj = ch->in_room->contents; obj; obj = obj->next_content )
	{
	    if( CAN_WEAR( obj, ITEM_TAKE )
		&& obj->cost > max && can_see_obj( ch, obj ) )
	    {
		obj_best = obj;
		max = obj->cost;
	    }
	}

	if( obj_best )
	{
	    obj_from_room( obj_best );
	    obj_to_char( obj_best, ch );
	    act( "$n gets $p.", ch, obj_best, NULL, TO_ROOM );
	}
    }
    create_char_event( ch, evn_scavenge,
		       number_range( PULSE_MOBILE / 2,
				     5 * PULSE_MOBILE ) );
}


/*
 * The aimless wandering of mobiles.
 * This event callback is the most called event, accounting for over half the
 * events generated, very performance sensitive.
 */
void ecb_wander( EVENT *e )
{
    CHAR_DATA *ch = e->actor.target.ch;
    EXIT_DATA *pexit;
    CHAR_DATA *rch;
    char buf[MAX_INPUT_LENGTH];
    int rnum;
    int door;

    if( IS_AFFECTED( ch, AFF_CHARM )
	|| ch->position < POS_STANDING )
	return;

    if( ch->hit < ch->max_hit / 2 )
	rnum = 3;
    else
	rnum = 5;

    if( ( door = number_bits( rnum ) ) <= 5
	&& ( pexit = ch->in_room->exit[door] )
	&& pexit->to_room
	&& !IS_SET( pexit->exit_info, EX_CLOSED )
	&& !IS_SET( pexit->to_room->room_flags, ROOM_NO_MOB )
	&& ( !xIS_SET( ch->act, ACT_STAY_AREA )
	     || pexit->to_room->area == ch->in_room->area ) )
    {
	/* Give message if hurt */
	if( rnum == 3 )
	    act( "$n flees in terror!", ch, NULL, NULL, TO_ROOM );

	stop_fighting_room( ch, TRUE );
	move_char( ch, door );

	if( ch->deleted )
	    return;

	for( rch = ch->in_room->people;
	     rch && ch->position >= POS_STANDING
		 && rnum == 3;
	     rch = rch->next_in_room )
	{
	    int direction;

	    if( rch->deleted || IS_NPC( rch ) )
		continue;

	    if( ch->fearing == rch )
	    {
		switch( number_bits( 2 ) )
		{
		default:
		    sprintf( buf, "May I have some peace %s?", rch->name );
		    break;
		case 0:
		    sprintf( buf, "Get away from me, %s!", rch->name );
		    break;
		case 1:
		    sprintf( buf, "Leave me be, %s!", rch->name );
		    break;
		case 2:
		    sprintf( buf, "%s is trying to kill me! Help!", rch->name );
		    break;
		case 3:
		    sprintf( buf, "Someone save me from %s!", rch->name );
		    break;
		}
		do_yell( ch, buf );
	    }
	    act( "$n flee$% in terror!", ch, NULL, NULL, TO_ALL );

	    /* Find an exit giving each one an equal chance */
	    door = -1;
	    for( direction = 0; direction <= 5; direction++ )
	    {
		if( ch->in_room->exit[direction]
		    && number_range( 0, direction ) == 0 )
		    door = direction;
	    }

	    /* If no exit, attack.  Else flee! */
	    if( door == -1
		|| ( IS_AFFECTED( rch, AFF_TAUNT )
		     && number_bits( 2 ) == 0 ) )
	    {
		act( "$n screams and attacks $N.",
		     ch, NULL, rch, TO_NOTVICT );
		act( "$n screams and attacks you.",
		     ch, NULL, rch, TO_VICT );
		multi_hit( ch, rch, TYPE_UNDEFINED );
	    }
	    else
	    {
		stop_fighting( ch, TRUE );
		move_char( ch, door );
	    }
	    break;
	}	/* for( rch ... ) */
    }
    create_char_event( ch, evn_wander,
		       number_range( PULSE_MOBILE / 2,
				     rnum * PULSE_MOBILE ) );
}


/*
 * Remove the temporary cone of silence on a room.
 */
void ecb_cone_remove( EVENT *e )
{
    ROOM_INDEX_DATA *room = e->actor.target.room;

    REMOVE_BIT( room->room_flags, ROOM_TEMP_CONE_OF_SILENCE );
}


/*
 * Room violence update.
 */
void ecb_room_violence( EVENT *e )
{
    room_violence_update( e->actor.target.room );
}


/*
 * Triggers for rand_progs and time_progs can be entered here.
 * NOTE: Time progs are called one pulse after the hour, this is
 *  enabled by making pulse_point global.
 */
void ecb_prog_trigger( EVENT *e )
{
    CHAR_DATA *ch;
    OBJ_DATA *obj;
    ROOM_INDEX_DATA *room;
    EVENT *enew = NULL;

    switch( e->actor.type )
    {
    case TARGET_CHAR:
	ch = e->actor.target.ch;

	/* It's alright to break here on these cases, however be careful
	 * that you don't break on temporary effects, the event must be
	 * created for next time. -- Symposium
	 */
	if( !ch || ch->deleted || !IS_NPC( ch ) )
	    break;

	switch( e->data[0] )
	{
	case RAND_PROG:
	    if( ch->in_room->area->nplayer > 0
		&& !IS_AFFECTED( ch, AFF_CHARM ) )
		mprog_percent_check( ch, NULL, NULL, NULL, RAND_PROG );
	    enew = create_char_event( e->actor.target.ch, evn_prog_trigger,
				      percent_fuzzy( PULSE_TICK, 10 ) );
	    break;
	case TIME_PROG:
	    if( ch->in_room->area->nplayer > 0
		&& !IS_AFFECTED( ch, AFF_CHARM ) )
		mprog_time_trigger( ch );
	    enew = create_char_event( e->actor.target.ch, evn_prog_trigger,
				      pulse_point + 1 + number_bits( 2 ) );
	    break;
	case HITPRCNT_PROG:
	    mprog_hitprcnt_trigger( ch );
	    if( !ch->deleted && ch->fighting )
		enew = create_char_event( e->actor.target.ch, evn_prog_trigger,
					  number_fuzzy( PULSE_VIOLENCE ) );
	    break;
	case FIGHT_PROG:
	    if( ch->fighting )
		mprog_percent_check( ch, ch->fighting,
				     NULL, NULL, FIGHT_PROG );
	    if( !ch->deleted && ch->fighting )
		enew = create_char_event( e->actor.target.ch, evn_prog_trigger,
					  number_fuzzy( PULSE_VIOLENCE ) );
	    break;
	case DELAY_PROG:
	    mprog_percent_check( ch, NULL, NULL, NULL, DELAY_PROG );
	    break;
	default:
	    bug( "Bad trigger for mob in event: %d.", e->data[0] );
	    break;
	}

	break;

    case TARGET_OBJ:
	obj = e->actor.target.obj;

	if( !obj || obj->deleted )
	    break;

	switch( e->data[0] )
	{
	case RAND_PROG:
	    if( obj->carried_by
		|| ( obj->in_room && obj->in_room->area->nplayer > 0 ) )
		oprog_percent_check( NULL, obj, NULL, RAND_PROG );
	    enew = create_obj_event( e->actor.target.obj, evn_prog_trigger,
				     percent_fuzzy( PULSE_TICK, 10 ) );
	    break;
	case TIME_PROG:
	    if( obj->carried_by
		|| ( obj->in_room && obj->in_room->area->nplayer > 0 ) )
		oprog_time_trigger( obj );
	    enew = create_obj_event( e->actor.target.obj, evn_prog_trigger,
				     pulse_point + 1 + number_bits( 2 ) );
	    break;
	default:
	    bug( "Bad trigger for object in event: %d.", e->data[0] );
	    break;
	}

	break;

    case TARGET_ROOM:
	room = e->actor.target.room;

	if( !room )
	    break;

	for( ch = room->people; ch; ch = ch->next_in_room )
	    if( !ch->deleted && !IS_NPC( ch ) )
		break;

	switch( e->data[0] )
	{
	case RAND_PROG:
	    if( ch )
		rprog_percent_check( room, ch, NULL, NULL, RAND_PROG );
	    enew = create_room_event( e->actor.target.room, evn_prog_trigger,
				      percent_fuzzy( PULSE_TICK, 10 ) );
	    break;
	case TIME_PROG:
	    if( ch )
		rprog_time_trigger( room, ch );
	    enew = create_room_event( e->actor.target.room, evn_prog_trigger,
				      pulse_point + 1 + number_bits( 2 ) );
	    break;
	default:
	    bug( "Bad trigger for room in event: %d.", e->data[0] );
	    break;
	}

	break;
    }
    if( enew )
	enew->data[0] = e->data[0];
}


void ecb_prog_wait( EVENT *e )
{
    MPROG_CALLBACK *cb = (MPROG_CALLBACK *)e->extra.target.typeless;

    switch( e->actor.type )
    {
    case TARGET_CHAR:
	break;
    case TARGET_OBJ:
	cb->info.mob = oset_supermob( e->actor.target.obj );
	break;
    case TARGET_ROOM:
	cb->info.mob = rset_supermob( e->actor.target.room );
	break;
    default:
	delete_all_locals( &cb->info );
	bug( "ecb_prog_wait: bad target type" );
	return;
    }

    parse( cb->commands, &cb->info, cb->stack, cb->line_no );

    switch( e->actor.type )
    {
    case TARGET_CHAR:
	break;
    case TARGET_OBJ:
    case TARGET_ROOM:
	release_supermob();
	break;
    }
    /* parse takes care of the stack objects, but we have to deal
     * with the info struct */
    delete_all_locals( &cb->info );
}


void ecb_area_reset( EVENT *e )
{
    AREA_DATA *area = e->actor.target.area;
    DESCRIPTOR_DATA *d;
    CHAR_DATA *pch;
    int time;

    if( area->nplayer > 0 )
    {
	for( d = descriptor_list; d; d = d->next )
	{
	    pch = CH( d );
	    if( !xIS_SET( pch->act, PLR_BUSY )
		&& IS_AWAKE( pch ) && pch->in_room
		&& pch->in_room->area == area )
	    {
		if( area->repop && area->repop[0] != '\0' )
		    send_to_char( area->repop, pch );
		else
		    send_to_char( "You hear the patter of little feet.\n\r",
				  pch );
	    }
	}
    }
    reset_area( area );

    time = area->age;
    if( area->nplayer <= 0 )
	time = UMIN( 4, time );

    time = time * PULSE_PER_SECOND * 60;
    time = number_range( time * 2 / 3, time * 4 / 3 );
    create_area_event( area, evn_area_reset, time );
}