cm3/
cm3/clans/
cm3/mudprogs/
cm3/player/a/
/****************************************************************************
 * [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;
}