/**************************************************************************** * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame | \\._.// * * -----------------------------------------------------------| (0...0) * * SMAUG 1.4 (C) 1994, 1995, 1996, 1998 by Derek Snider | ).:.( * * -----------------------------------------------------------| {o o} * * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus, | / ' ' \ * * Scryn, Rennard, Swordbearer, Gorog, Grishnakh, Nivek, |~'~.VxvxV.~'~* * Tricops and Fireblade | * * ------------------------------------------------------------------------ * * Merc 2.1 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. * * ------------------------------------------------------------------------ * * Battle & death module * ****************************************************************************/ #include <sys/types.h> #include <stdio.h> #include <string.h> #include <time.h> #include "mud.h" extern char lastplayercmd[MAX_INPUT_LENGTH]; extern CHAR_DATA * gch_prev; extern OBJ_DATA *find_projectile( CHAR_DATA *ch, int type ); /* from talent.c */ extern void magic_damage(CHAR_DATA *victim, CHAR_DATA *ch, int dam, int type, int talent, bool dont_wait); extern void travel_teleport(CHAR_DATA *ch); /* from handler.c */ extern void learn_weapon(CHAR_DATA *ch, int i); extern bool is_bane(OBJ_DATA *weapon, CHAR_DATA *victim); /* * Local functions. */ int obj_hitroll args( ( OBJ_DATA *obj ) ); void show_condition args( ( CHAR_DATA *ch, CHAR_DATA *victim ) ); /* * Check to see if player's attacks are (still?) suppressed * #ifdef TRI */ bool is_attack_supressed( CHAR_DATA *ch ) { TIMER *timer; if (IS_NPC(ch)) return FALSE; timer = get_timerptr( ch, TIMER_ASUPRESSED ); if ( !timer ) return FALSE; /* perma-supression -- bard? (can be reset at end of fight, or spell, etc) */ if ( timer->value == -1 ) return TRUE; /* this is for timed supressions */ if ( timer->count >= 1 ) return TRUE; return FALSE; } /* * Check to see if weapon is poisoned. */ bool is_wielding_poisoned( CHAR_DATA *ch ) { /* OBJ_DATA *obj; if ( (obj=get_eq_char(ch, WEAR_HAND)) != NULL && IS_OBJ_STAT(obj, ITEM_POISONED) ) return TRUE; */ return FALSE; } /* * hunting, hating and fearing code -Thoric */ bool is_hunting( CHAR_DATA *ch, CHAR_DATA *victim ) { if ( !ch->hunting || ch->hunting->who != victim ) return FALSE; return TRUE; } bool is_hating( CHAR_DATA *ch, CHAR_DATA *victim ) { if ( !ch->hating || ch->hating->who != victim ) return FALSE; return TRUE; } bool is_fearing( CHAR_DATA *ch, CHAR_DATA *victim ) { if ( !ch->fearing || ch->fearing->who != victim ) return FALSE; return TRUE; } void stop_hunting( CHAR_DATA *ch ) { if ( ch->hunting ) { STRFREE( ch->hunting->name ); DISPOSE( ch->hunting ); ch->hunting = NULL; } return; } void stop_hating( CHAR_DATA *ch ) { if ( ch->hating ) { STRFREE( ch->hating->name ); DISPOSE( ch->hating ); ch->hating = NULL; } return; } void stop_fearing( CHAR_DATA *ch ) { if ( ch->fearing ) { STRFREE( ch->fearing->name ); DISPOSE( ch->fearing ); ch->fearing = NULL; } return; } void start_hunting( CHAR_DATA *ch, CHAR_DATA *victim ) { if (!victim->name) { stop_hunting(ch); return; } if ( ch->hunting ) stop_hunting( ch ); CREATE( ch->hunting, HHF_DATA, 1 ); ch->hunting->name = QUICKLINK( victim->name ); ch->hunting->who = victim; return; } void start_hating( CHAR_DATA *ch, CHAR_DATA *victim ) { if (!victim->name) { stop_hunting(ch); return; } if ( ch->hating ) stop_hating( ch ); CREATE( ch->hating, HHF_DATA, 1 ); ch->hating->name = QUICKLINK( victim->name ); ch->hating->who = victim; return; } void start_fearing( CHAR_DATA *ch, CHAR_DATA *victim ) { if ( ch->fearing ) stop_fearing( ch ); CREATE( ch->fearing, HHF_DATA, 1 ); ch->fearing->name = QUICKLINK( victim->name ); ch->fearing->who = victim; return; } int max_fight( CHAR_DATA *ch ) { return 2 + (ch->weight / 500); } /* * Control the fights going on. * Called periodically by update_handler. * Many hours spent fixing bugs in here by Thoric, as noted by residual * debugging checks. If you never get any of these error messages again * in your logs... then you can comment out some of the checks without * worry. * * Note: This function also handles some non-violence updates. */ void violence_update( void ) { char buf[MAX_STRING_LENGTH]; CHAR_DATA *ch; CHAR_DATA *lst_ch; AFFECT_DATA *paf, *paf_next; TIMER *timer, *timer_next; ch_ret retcode; int i; SKILLTYPE *skill; static int pulse = 0; lst_ch = NULL; pulse = (pulse+1) % 100; for ( ch = last_char; ch; lst_ch = ch, ch = gch_prev ) { set_cur_char( ch ); if ( ch == first_char && ch->prev ) { bug( "ERROR: first_char->prev != NULL, fixing...", 0 ); ch->prev = NULL; } gch_prev = ch->prev; if ( gch_prev && gch_prev->next != ch ) { sprintf( buf, "FATAL: violence_update: %s->prev->next doesn't point to ch.", ch->name ); bug( buf, 0 ); bug( "Short-cutting here", 0 ); ch->prev = NULL; gch_prev = NULL; } /* * See if we got a pointer to someone who recently died... * if so, either the pointer is bad... or it's a player who * "died", and is back at the healer... * Since he/she's in the char_list, it's likely to be the later... * and should not already be in another fight already */ if ( char_died(ch) ) continue; /* * See if we got a pointer to some bad looking data... */ if ( !ch->in_room || !ch->name ) { log_string( "violence_update: bad ch record! (Shortcutting.)" ); sprintf( buf, "ch: %d ch->in_room: %d ch->prev: %d ch->next: %d", (int) ch, (int) ch->in_room, (int) ch->prev, (int) ch->next ); log_string( buf ); log_string( lastplayercmd ); if ( lst_ch ) sprintf( buf, "lst_ch: %d lst_ch->prev: %d lst_ch->next: %d", (int) lst_ch, (int) lst_ch->prev, (int) lst_ch->next ); else strcpy( buf, "lst_ch: NULL" ); log_string( buf ); gch_prev = NULL; continue; } for ( timer = ch->first_timer; timer; timer = timer_next ) { timer_next = timer->next; if ( --timer->count <= 0 ) { if (timer->type == TIMER_DO_FUN) { int tempsub; tempsub = ch->substate; ch->substate = timer->value; (timer->do_fun)( ch, "" ); if ( char_died(ch) ) break; ch->substate = tempsub; } else if (timer->type == TIMER_PKILLED) { if (xIS_SET(ch->pcdata->perm_aff, AFF_ETHEREAL)) if (check_rebirth(ch, 3000)) continue; } extract_timer( ch, timer ); } } if ( char_died(ch) ) continue; /* * We need spells that have shorter durations than an hour. * So a melee round sounds good to me... -Thoric */ for ( paf = ch->first_affect; paf; paf = paf_next ) { paf_next = paf->next; if ( paf->duration > 0 ) paf->duration--; else if ( paf->duration < 0 ) ; else { if ( !paf_next || paf_next->type != paf->type || paf_next->duration > 0 ) { skill = get_skilltype(paf->type); if ( paf->type > 0 && skill && skill->msg_off ) { set_char_color( AT_WEAROFF, ch ); send_to_char( skill->msg_off, ch ); send_to_char( "\n\r", ch ); update_pos(ch); } } affect_remove( ch, paf ); } } if ( char_died(ch) ) continue; if ( (IS_AFFECTED(ch, AFF_FLAMING)) || IS_SET(ch->in_room->room_flags, ROOM_BURNING) || ch->in_room->sector_type == SECT_LAVA || ( (IS_VAMPIRE(ch)) && (time_info.hour>6 && time_info.hour<=18) && !IS_SET(sysdata.quest, QUEST_ETERNAL_NIGHT) && (IS_OUTSIDE(ch)) && (get_light_room(ch->in_room) > -1 )) ) { if (ch->in_room->curr_water > 30 && ch->in_room->sector_type != SECT_LAVA) { REMOVE_BIT(ch->in_room->room_flags, ROOM_BURNING); act( AT_BLUE, "The water puts out the flames covering you.", ch, NULL, NULL, TO_CHAR); act( AT_BLUE, "The water puts out the flames covering $n.", ch, NULL, NULL, TO_ROOM); xREMOVE_BIT(ch->affected_by, AFF_FLAMING); } else if (IS_PACIFIST(ch)) { REMOVE_BIT(ch->in_room->room_flags, ROOM_BURNING); act( AT_BLUE, "$n irritably puts out the fire.", ch, NULL, NULL, TO_ROOM); xREMOVE_BIT(ch->affected_by, AFF_FLAMING); } else if (!IS_SET(ch->immune, RIS_FIRE) && (TALENT(ch, TAL_FIRE) < 100 || (IS_SET(ch->resistant, RIS_FIRE) && IS_AFFECTED(ch, AFF_CONSTRUCT)))) { act( AT_FIRE, "The searing flames covering you sear your flesh!", ch, NULL, NULL, TO_CHAR); act( AT_FIRE, "The flames covering $n sear $s flesh!", ch, NULL, NULL, TO_ROOM); lose_hp(ch, 25); climate_affect(ch, MAG_FIRE); } } if ( char_died(ch) ) continue; if (IS_AFFECTED(ch, AFF_PLAGUE) && !IS_SET(ch->immune, RIS_POISON) && !IS_UNDEAD(ch)) { act( AT_DGREEN, "You convulse in agony as the plague ravages your body.", ch, NULL, NULL, TO_CHAR); lose_hp(ch, 100); } if ( char_died(ch) ) continue; if (ch->speed < 0) ch->speed = 1; else if (ch->speed > 100) ch->speed--; else if (ch->speed < 100) ch->speed++; if (ch->in_room->area->weather->temp > 100) { if (climate_affect(ch, MAG_FIRE)) if (IS_NPC(ch) && ch->spec_fun) travel_teleport(ch); } else if (ch->in_room->area->weather->temp < -100) { if (climate_affect(ch, MAG_COLD)) if (IS_NPC(ch) && ch->spec_fun) travel_teleport(ch); } if ( char_died(ch) ) continue; if (IS_FIGHTING(ch) && ch->singing) { if (ch->mana < 0) { send_to_char("Your voice grows hoarse and you can't sing anymore.\n\r", ch); do_sing(ch, "none"); } else { switch (ch->singing) { case SONG_SPEED: ch->speed += 5; break; case SONG_FIRE: learn_talent(ch, TAL_FIRE); magic_damage(ch->last_hit, ch, ch->curr_talent[TAL_SPEECH] + ch->curr_talent[TAL_FIRE], MAG_FIRE, TAL_SPEECH, TRUE); break; case SONG_THUNDER: learn_talent(ch, TAL_LIGHTNING); magic_damage(ch->last_hit, ch, ch->curr_talent[TAL_SPEECH] + ch->curr_talent[TAL_LIGHTNING], MAG_ELECTRICITY, TAL_SPEECH, TRUE); break; case SONG_WIND: learn_talent(ch, TAL_WIND); magic_damage(ch->last_hit, ch, ch->curr_talent[TAL_SPEECH] + ch->curr_talent[TAL_WIND], MAG_WIND, TAL_SPEECH, TRUE); break; case SONG_RIVERS: learn_talent(ch, TAL_WATER); magic_damage(ch->last_hit, ch, ch->curr_talent[TAL_SPEECH] + ch->curr_talent[TAL_WATER], MAG_WATER, TAL_SPEECH, TRUE); break; case SONG_WINTER: learn_talent(ch, TAL_FROST); magic_damage(ch->last_hit, ch, ch->curr_talent[TAL_SPEECH] + ch->curr_talent[TAL_FROST], MAG_COLD, TAL_SPEECH, TRUE); break; case SONG_LIFE: learn_talent(ch, TAL_HEALING); i = number_range(ch->curr_talent[TAL_SPEECH], ch->curr_talent[TAL_SPEECH]*5); ch->hit += i; use_magic(ch, TAL_SPEECH, i); act(AT_PINK, "$n's song revitalizes $m.", ch, NULL, NULL, TO_ROOM); act(AT_PINK, "Your song revitalizes you.", ch, NULL, NULL, TO_CHAR); break; } } } /* check for exits moving players around */ if ( (retcode=pullcheck(ch, pulse)) == rCHAR_DIED || char_died(ch) ) continue; /* Examine call for special procedure */ if ( IS_NPC(ch) && !xIS_SET( ch->act, ACT_RUNNING ) && ch->spec_fun && IS_AWAKE(ch) ) { if (ch->last_hit) { if (!xIS_SET(ch->act, ACT_PHYS_FAIL) && (xIS_SET(ch->act, ACT_MAG_FAIL) || number_range(1,2) == 1)) { mob_attack(ch, ch->last_hit); continue; } if (xIS_SET(ch->act, ACT_PHYS_FAIL) && xIS_SET(ch->act, ACT_MAG_FAIL)) { do_flee(ch, ""); continue; } } if ( (*ch->spec_fun) ( ch ) ) continue; if ( char_died(ch) ) continue; } } return; } /* * Do one group of attacks. */ ch_ret multi_hit( CHAR_DATA *ch, CHAR_DATA *victim, int dt ) { int chance; int dual_bonus; ch_ret retcode; OBJ_DATA *obj; /* add timer to pkillers */ if ( !IS_NPC(ch) && !IS_NPC(victim) ) { add_timer( ch, TIMER_RECENTFIGHT, 11, NULL, 0 ); add_timer( victim, TIMER_RECENTFIGHT, 11, NULL, 0 ); } if ( IS_NPC(ch) && xIS_SET(ch->act, ACT_NOATTACK) ) return rNONE; /* Group protecting -- Scion */ if (victim->protected_by) { if (victim->protected_by->in_room == victim->in_room) { if (number_percent() < (get_curr_dex(victim->protected_by) + get_curr_int(victim->protected_by))/2) { act(AT_HITME, "You dive in front of $N to protect $M!", victim->protected_by, NULL, victim, TO_CHAR); act(AT_HIT, "$n dives in front of $N to protect $M!", victim->protected_by, NULL, victim, TO_NOTVICT); act(AT_HIT, "$n dives in front of you to protect you!", victim->protected_by, NULL, victim, TO_VICT); victim = victim->protected_by; } } else if (char_died(victim->protected_by)) victim->protected_by = NULL; } obj = get_eq_char(ch, WEAR_HAND); if ( get_eq_char( ch, WEAR_HAND2 ) || (obj && xIS_SET(obj->extra_flags, ITEM_TWO_HANDED)) ) { dual_bonus = (ch->pcdata ? ch->pcdata->weapon[0] / 10 : 10); chance = ch->pcdata ? ch->pcdata->weapon[0] : 100; } /* * Wimp out? */ if (IS_NPC(victim)) { if ( ( xIS_SET(victim->act, ACT_WIMPY) && number_bits( 1 ) == 0 && victim->hit < victim->max_hit / 2 ) || ( IS_AFFECTED(victim, AFF_CHARM) && victim->master && victim->master->in_room != victim->in_room ) || ( get_curr_wil(victim) < 33 ) || ( get_curr_wil(victim) > 66 && victim->hit < victim->max_hit / 4 ) ) { start_fearing( victim, ch ); stop_hunting( victim ); do_flee( victim, "" ); } } return retcode; } /* * Calculate the tohit bonus on the object and return RIS values. * -- Altrag */ int obj_hitroll( OBJ_DATA *obj ) { int tohit = 0; AFFECT_DATA *paf; for ( paf = obj->pIndexData->first_affect; paf; paf = paf->next ) if ( paf->location == APPLY_HITROLL ) tohit += paf->modifier; for ( paf = obj->first_affect; paf; paf = paf->next ) if ( paf->location == APPLY_HITROLL ) tohit += paf->modifier; return tohit; } /* * Set position of a victim. */ void update_pos( CHAR_DATA *victim ) { if ( !victim ) { bug( "update_pos: null victim", 0 ); return; } if (victim->move < 0) { victim->position = POS_STUNNED; } else { if (victim->position == POS_STUNNED) { victim->position = POS_STANDING; act(AT_ACTION, "You come to and climb to your feet.", victim, NULL, NULL, TO_CHAR); act(AT_ACTION, "$n climbs to $s feet.", victim, NULL, NULL, TO_ROOM); } } if ( victim->hit <= -11 ) { if ( victim->mount ) { act( AT_ACTION, "$n falls from $N.", victim, NULL, victim->mount, TO_ROOM ); if (IS_NPC(victim->mount)) xREMOVE_BIT( victim->mount->affected_by, AFF_MOUNTED ); else xREMOVE_BIT( victim->mount->pcdata->perm_aff, AFF_MOUNTED); victim->mount = NULL; } victim->position = POS_DEAD; return; } if ( victim->position > POS_STUNNED && IS_AFFECTED( victim, AFF_PARALYSIS ) ) victim->position = POS_STUNNED; if ( victim->mount ) { act( AT_ACTION, "$n falls unconscious from $N.", victim, NULL, victim->mount, TO_ROOM ); if (IS_NPC(victim->mount)) xREMOVE_BIT( victim->mount->affected_by, AFF_MOUNTED ); else xREMOVE_BIT( victim->mount->pcdata->perm_aff, AFF_MOUNTED); victim->mount = NULL; } return; } /* This is now mob only, since players can just walk away from combat. It has been modified accordingly. -- Scion */ void do_flee( CHAR_DATA *ch, char *argument ) { ROOM_INDEX_DATA *was_in; ROOM_INDEX_DATA *now_in; int attempt; sh_int door; EXIT_DATA *pexit; if (!IS_NPC(ch)) { send_to_char("Just run away with the direction commands! Quickly now!\r\n", ch); return; } if (ch->position <= POS_SLEEPING) return; if (number_range(1,5) != 1) return; if (ch->spec_fun) { travel_teleport(ch); return; } if ( IS_AFFECTED( ch, AFF_BERSERK ) ) { act(AT_FLEE, "$n looks wide eyed and panicked!", ch, NULL, NULL, TO_ROOM); return; } if ( ch->move <= 0 ) { act(AT_FLEE, "$n starts to look very tired.", ch, NULL, NULL, TO_ROOM); return; } /* Make flee fail for mobs (give players a fighting chance, no pun intended) -- Scion */ if (IS_FIGHTING(ch)) { if (number_range(1, (get_curr_dex(ch) + get_curr_lck(ch)) / 2 ) < number_range(1, (get_curr_str(ch->last_hit) + get_curr_dex(ch->last_hit)))) { act(AT_FLEE, "$n tries to escape, but you manage to stop $m!", ch, NULL, ch->last_hit, TO_VICT); act(AT_FLEE, "$n tries to escape, but $N manages to stop $m!", ch, NULL, ch->last_hit, TO_NOTVICT); return; } } was_in = ch->in_room; for ( attempt = 0; attempt < 8; attempt++ ) { door = number_door( ); if (( pexit = get_exit( was_in, door ) ) == NULL || !pexit->to_room || IS_SET( pexit->exit_info, EX_NOFLEE ) || ( IS_SET( pexit->exit_info, EX_CLOSED ) && !IS_AFFECTED( ch, AFF_PASS_DOOR ) ) || IS_SET( pexit->to_room->room_flags, ROOM_NO_MOB ) || ( IS_SET( pexit->to_room->room_flags, ROOM_NOFLOOR) && !IS_FLOATING(ch) ) ) continue; affect_strip ( ch, gsn_sneak ); xREMOVE_BIT ( ch->affected_by, AFF_SNEAK ); move_char( ch, pexit, 0 ); if ( ( now_in = ch->in_room ) == was_in ) continue; ch->in_room = was_in; act( AT_FLEE, "$n flees head over heels!", ch, NULL, NULL, TO_ROOM ); ch->in_room = now_in; act( AT_FLEE, "$n glances around for signs of pursuit.", ch, NULL, NULL, TO_ROOM ); stop_hunting( ch ); if (ch->last_hit) start_fearing(ch, ch->last_hit); return; } return; }