/*___________________________________________________________________________* )()( 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 ); }