/*___________________________________________________________________________*
)()( 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. ||
|| ----------------------------------------------------------------- ||
|| fight.c ||
|| Combat, combat skills and death code. ||
*_/<>\_________________________________________________________________/<>\_*/
#include <math.h>
#include "mud.h"
#include "event.h"
/*
* Local functions.
*/
bool check_shield_block args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );
bool check_blink args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );
bool check_dodge args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );
bool check_parry args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );
bool check_stumble args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );
bool check_familiar args( ( CHAR_DATA *ch, CHAR_DATA *victim, int dam ) );
void dam_message args( ( CHAR_DATA *ch, CHAR_DATA *victim, int dam,
int dt, int wpn ) );
void group_gain args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );
bool is_wielding_poisoned args( ( CHAR_DATA *ch, int wpn ) );
void make_corpse args( ( CHAR_DATA *ch ) );
void one_hit args( ( CHAR_DATA *ch, CHAR_DATA *victim, int dt,
int wpn ) );
void raw_kill args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );
void set_fighting args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );
bool disarm args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );
void trip args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );
void use_magical_item args( ( CHAR_DATA *ch ) );
void strangle_hit args( ( CHAR_DATA *ch ) );
void strangle_struggle args( ( CHAR_DATA *ch ) );
void check_critical args( ( CHAR_DATA *ch, CHAR_DATA *victim, int dam,
int wpn, int dt ) );
void remove_part args( ( CHAR_DATA *ch, CHAR_DATA *victim, int part ) );
void damage_eq args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );
void start_fights args( ( CHAR_DATA *ch ) );
void attack args( ( CHAR_DATA *ch, int dt, int number ) );
void barehand_hit args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );
void flashing_blades args( ( CHAR_DATA *ch, CHAR_DATA *victim,
int dt, int wpn ) );
bool get_juggle_attack args( ( CHAR_DATA *ch, int num ) );
void dirty_fighting args( ( CHAR_DATA *ch ) );
void end_battle_check args( ( CHAR_DATA *victim ) );
#define ATTACK_COUNT 10
const int attack_lag[ATTACK_COUNT] =
{ 0, 2, 12, 18, 25, 32, 43, 55, 75, 100 };
const struct limb_loss part_loss_table[] =
{
{ 1, BODY_PART_HEAD|BODY_PART_EYES|BODY_PART_EAR_L|BODY_PART_EAR_R|
BODY_PART_NOSE|BODY_PART_HORNS, 50,
"&yYour head is chopped off!",
"&y$n's head goes flying with an arc of blood!" },
{ 3, BODY_PART_EYES, 51,
"&yYour eyes are gouged out.",
"&y$n's eyes drop out on the ground!" },
{ 13, BODY_PART_ARM_L | BODY_PART_HAND_L | BODY_PART_FINGERS_L, 52,
"&yYour left arm is chopped off!",
"&y$n's left arm goes flying!" },
{ 23, BODY_PART_ARM_R | BODY_PART_HAND_R | BODY_PART_FINGERS_R, 52,
"&yYour right arm is chopped off!",
"&y$n's right arm goes flying!" },
{ 29, BODY_PART_HAND_L | BODY_PART_FINGERS_L, 53,
"&yYour left hand is chopped off!",
"&y$n's left hand goes flying!" },
{ 35, BODY_PART_HAND_R | BODY_PART_FINGERS_R, 53,
"&yYour right hand is chopped off!",
"&y$n's right hand goes flying!" },
{ 38, BODY_PART_FINGERS_L, 54,
"&yYour left fingers are chopped off!",
"&y$n's left fingers drop to the ground!" },
{ 41, BODY_PART_FINGERS_R, 54,
"&yYour right fingers are chopped off!",
"&y$n's right fingers drop to the ground!" },
{ 51, BODY_PART_LEG_L | BODY_PART_FOOT_L, 55,
"&yYour left leg is chopped off!",
"&y$n's left leg goes flying!" },
{ 61, BODY_PART_LEG_R | BODY_PART_FOOT_R, 55,
"&yYour right leg is chopped off!",
"&y$n's right leg goes flying!" },
{ 67, BODY_PART_FOOT_L, 56,
"&yYour left foot is chopped off!",
"&y$n's left foot goes flying!" },
{ 73, BODY_PART_FOOT_R, 56,
"&yYour right foot is chopped off!",
"&y$n's right foot goes flying!" },
{ 75, BODY_PART_TAIL, 57,
"&yYour tail is severed!",
"&y$n's tail is severed!" },
{ 77, BODY_PART_HORNS, 58,
"&yYour horns are shorn from your head!",
"&y$n's horns are shorn from $s head!" },
{ 79, BODY_PART_WINGS, 59,
"&yYour wings are chopped off!",
"&y$n's wings flutter to the ground!" },
{ 81, BODY_PART_LUNGS, 60,
"&yYour lungs are torn out!",
"&y$n's lungs are torn out!" },
{ 83, BODY_PART_GILLS, 61,
"&yYour gills are ripped off!",
"&y$n's gills are ripped off!" },
{ 86, BODY_PART_EAR_L, 62,
"&yYour left ear drops to the ground.",
"&y$n's left ear drops to the ground." },
{ 89, BODY_PART_EAR_R, 62,
"&yYour right ear drops to the ground.",
"&y$n's right ear drops to the ground." },
{ 91, BODY_PART_NOSE, 63,
"&yYour nose drops to the ground.",
"&y$n's nose drops to the ground." },
{ 0, 0, 1, NULL, NULL }
};
/*
* Called from violence_update
* starts everyone fighting each other
* this is the most expensive part of violence update.
*/
void start_fights( CHAR_DATA *ch )
{
CHAR_DATA *victim;
CHAR_DATA *rch;
bool mobfighting = FALSE;
if( IS_AFFECTED( ch, AFF_BLIND )
|| ( IS_NPC( ch ) && ch->pIndexData->pShop )
|| !IS_AWAKE( ch ) )
return;
if( ( victim = ch->fighting ) )
{
if( victim->deleted || ch->in_room != victim->in_room )
stop_fighting( ch, FALSE );
/* switch to a charmie's master if there is one */
else if( IS_AFFECTED( victim, AFF_CHARM )
&& IS_NPC( victim )
&& !xIS_SET( victim->act, ACT_MERCENARY )
&& victim->master && !victim->master->deleted
&& ch->in_room == victim->master->in_room
&& number_bits( 3 ) > 3 )
{
stop_fighting( ch, FALSE );
set_fighting( ch, victim->master );
}
return;
}
/*
* Ok. So ch is not fighting anyone.
* Is there a fight going on?
* will we join in?
*/
for( rch = ch->in_room->people; rch; rch = rch->next_in_room )
{
if( rch->deleted
|| !IS_AWAKE( rch )
|| !( victim = rch->fighting ) )
continue;
/* a player or mercenary (ch) will assist
* players or charmies (rch) in the same group
* that are fighting NPCs
*/
if( ( !IS_NPC( ch )
|| ( IS_AFFECTED( ch, AFF_CHARM )
&& xIS_SET( ch->act, ACT_MERCENARY ) ) )
&& ( !IS_NPC( rch ) || IS_AFFECTED( rch, AFF_CHARM ) )
&& is_same_group( ch, rch )
&& IS_NPC( victim ) )
break;
if( IS_NPC( ch ) && IS_NPC( rch )
&& !IS_NPC( victim ) )
{
mobfighting = TRUE;
break;
}
}
if( !victim || !rch )
return;
/*
* Now that someone is fighting, consider fighting another pc
* or not at all.
*/
if( mobfighting )
{
CHAR_DATA *vch;
int number;
number = 0;
for( vch = ch->in_room->people; vch; vch = vch->next_in_room )
{
if( can_see( ch, vch )
&& is_same_group( vch, victim )
&& ( number_range( 0, number ) == 0
|| IS_AFFECTED( vch, AFF_TAUNT ) ) )
{
victim = vch;
number++;
}
}
if( !xIS_SET( ch->act, ACT_ASSIST )
&& ( ( rch->pIndexData != ch->pIndexData )
|| ( IS_GOOD( ch ) && IS_GOOD( victim ) )
|| ( !IS_EVIL( ch ) && !IS_EVIL( victim )
&& number_bits( 2 ) != 0 )
|| abs( victim->level - ch->level ) > 4 ) )
return;
}
set_fighting( ch, victim );
return;
}
/*
* Attack.
* Called from update_violence and multi_hit
* Does one attack depending on number.
*/
void attack( CHAR_DATA *ch, int dt, int number )
{
CHAR_DATA *victim;
int wield = WEAR_WIELD_R;
if( !( victim = ch->fighting ) || victim->deleted )
return;
if( get_eq_char( ch, WEAR_WIELD_DOUBLE ) )
wield = WEAR_WIELD_DOUBLE;
else if( !get_eq_char( ch, WEAR_WIELD_R )
&& get_eq_char( ch, WEAR_WIELD_L ) )
wield = WEAR_WIELD_L;
if( number > 0 && ( dt == gsn_backstab || dt == gsn_circle
|| dt == gsn_slit_throat || dt == gsn_impale
|| dt == gsn_atemi || dt == gsn_charge ) )
return;
if( wield == WEAR_WIELD_DOUBLE && number > 0
&& number != 2 && number != 9 )
return;
if( number != -1 && ( !IS_NPC( ch )
&& ch->pcdata->pc_bits >= PC_BIT_STRANGLE ) )
return;
/*
* The action starts here, special considerations
* are taken into account like dual and so forth
*/
switch( number )
{
case -1:
if( !IS_NPC( ch ) )
{
if( ch->pcdata->pc_bits >= PC_BIT_STRANGLE )
strangle_hit( ch );
else if( get_success( ch, gsn_dirty_fighting, 50 ) )
dirty_fighting( ch );
}
return;
case 0:
one_hit( ch, victim, dt, wield );
return;
case 1:
if( IS_AFFECTED( ch, AFF_HASTE ) && number_bits( 5 ) < 25 )
one_hit( ch, victim, dt, wield );
return;
case 2:
if( IS_NPC_CLASS( ch ) ? number_percent( ) < ch->level
: get_success( ch, gsn_second_attack, 70 ) )
one_hit( ch, victim, dt, wield );
return;
case 3:
if( dt == TYPE_UNDEFINED
&& IS_SET( ch->body_parts, BODY_PART_TAIL )
&& number_bits( 2 ) == 0 )
one_hit( ch, victim, gsn_race_tail, WEAR_WIELD_R );
return;
case 4:
if( wield == WEAR_WIELD_DOUBLE )
{
if( IS_NPC( ch ) )
{
if( number_percent( ) < UMIN( 50, ch->level / 2 ) )
one_hit( ch, victim, dt, wield );
return;
}
if( ch->pcdata->learned[gsn_second_attack] * 3 / 4 > number_bits( 7 )
&& ch->pcdata->learned[gsn_third_attack] / 2 > number_bits( 7 ) )
one_hit( ch, victim, dt, wield );
}
else if( IS_NPC_CLASS( ch ) ? number_bits( 5 ) < ch->level
: get_success( ch, gsn_third_attack, 50 ) )
one_hit( ch, victim, dt, wield );
return;
case 5:
if( get_juggle_attack( ch, 1 )
|| IS_NPC_CLASS( ch ) ? number_bits( 6 ) < ch->level
: get_success( ch, gsn_fourth_attack, 30 ) )
one_hit( ch, victim, dt, wield );
return;
case 6:
if( get_juggle_attack( ch, 2 )
|| IS_NPC_CLASS( ch ) ? number_bits( 7 ) < ( ch->level - 25 )
: get_success( ch, gsn_fifth_attack, 25 ) )
one_hit( ch, victim, dt, wield );
return;
case 7:
if( get_juggle_attack( ch, 3 )
|| ( IS_NPC_CLASS( ch )
&& number_bits( 8 ) < ( ch->level - 100 ) / 2 ) )
one_hit( ch, victim, dt, wield );
return;
case 8:
if( IS_NPC( ch ) && number_bits( 10 ) < ch->level )
one_hit( ch, victim, dt, wield );
return;
case 9:
if( IS_NPC( ch ) )
one_hit( ch, victim, dt, wield );
return;
default:
bug( "attack: Unknown attack action %d from %s.", number, ch->name );
return;
}
return;
}
/*
* Update all the fighting in a single room.
* This has the advantage of a very short list when working out initiatives
* so inserts are extremely simple.
*/
void room_violence_update( ROOM_INDEX_DATA *room )
{
CHAR_DATA *ch;
CHAR_DATA *list = NULL;
CHAR_DATA *threads[ATTACK_COUNT];
CHAR_DATA *extra_threads[ATTACK_COUNT];
int i, count;
int min = 100000, max = -100000;
for( ch = room->people; ch; ch = ch->next_in_room )
{
if( ch->deleted )
continue;
start_fights( ch );
if( !ch->fighting )
continue;
ch->temp = get_speed( ch ) + number_range( 1, 10 );
/* get minimum/maximum values so we can fix it */
if( ch->temp > max )
max = ch->temp;
if( ch->temp < min )
min = ch->temp;
if( !list || ch->temp > list->temp )
{
ch->next_initiative = list;
list = ch;
}
else
{
CHAR_DATA *tmp;
for( tmp = list; tmp && tmp->next_initiative;
tmp = tmp->next_initiative )
if( ch->temp > tmp->next_initiative->temp )
break;
if( !tmp )
{
bug( "room_violence_update: can't insert %s",
ch->name );
continue;
}
ch->next_initiative = tmp->next_initiative;
tmp->next_initiative = ch;
}
}
if( !list )
return;
/* special attacks as well as a little twiddling
* it would be nice to have all the values in
* a restricted range...
*/
max = UMAX( max, min + 50 );
for( ch = list; ch; ch = ch->next_initiative )
{
ch->temp = ( ch->temp - min ) * 100 / ( max - min );
if( !ch->deleted && ch->fighting )
attack( ch, TYPE_UNDEFINED, -1 );
}
/* there is a thread for each value in attack() */
for( i = 0; i < ATTACK_COUNT; ++i )
{
threads[i] = list;
extra_threads[i] = list;
}
for( count = 0; count < 125; ++count )
{
for( i = 0; i < ATTACK_COUNT; ++i )
{
if( threads[i] )
{
if( threads[i]->deleted || !threads[i]->fighting )
threads[i] = threads[i]->next_initiative;
else if( threads[i]->temp - attack_lag[i] == list->temp - count )
{
attack( threads[i], TYPE_UNDEFINED, i );
threads[i] = threads[i]->next_initiative;
}
}
/* A bonus to any with high initiative */
if( extra_threads[i] )
{
if( extra_threads[i]->deleted
|| !extra_threads[i]->fighting )
extra_threads[i] = extra_threads[i]->next_initiative;
else if( extra_threads[i]->temp - 50 - 2 * attack_lag[i]
== list->temp - count )
{
if( number_bits( 1 ) )
attack( extra_threads[i], TYPE_UNDEFINED, i );
extra_threads[i] = extra_threads[i]->next_initiative;
}
}
}
}
/* clean up */
for( ch = list; ch; ch = ch->next_initiative )
if( !ch->deleted )
ch->extra_bits &= ~CH_EX_RESET;
create_room_event( room, evn_room_violence, PULSE_VIOLENCE );
}
/*
* Do one group of attacks.
* Now passed on to attack( ), only used to set fighting
*/
void multi_hit( CHAR_DATA *ch, CHAR_DATA *victim, int dt )
{
int i;
/*
* Set the fighting fields now.
*/
if( victim->position > POS_STUNNED )
{
if( !victim->fighting )
set_fighting( victim, ch );
if( victim->position != POS_SMASHED
&& victim->position != POS_SLEEPING
&& victim->position != POS_GETTING_UP )
victim->position = POS_FIGHTING;
if( !ch->fighting )
set_fighting( ch, victim );
}
for( i = 0; i < ATTACK_COUNT; i++ )
attack( ch, dt, i );
if( !IS_NPC( victim ) && dt != gsn_retaliate
&& get_success( victim, gsn_retaliate, 40 ) )
multi_hit( victim, ch, gsn_retaliate );
return;
}
/*
* Hit one guy once.
* one_hit() modified for new damage/combat system.
* --Symposium (6/1998)
*/
void one_hit( CHAR_DATA *ch, CHAR_DATA *victim, int dt, int wpn )
{
OBJ_DATA *wield = NULL;
int combat_no;
int dam, basic_dam;
int save_hit;
bool offwield = FALSE;
/*
* Can't beat a dead char!
* Guard against weird room-leavings.
*/
if( victim->position == POS_DEAD || ch->in_room != victim->in_room )
{
bug( "one_hit: ch %s not with victim %s, or victim POS_DEAD",
ch->name, victim->name );
return;
}
if( !IS_NPC( victim ) && victim->pcdata->pc_bits >= PC_BIT_STRANGLE
&& victim->fighting == ch )
{
strangle_struggle( ch );
return;
}
/* Juggle those weapons!
* Make sure this is only for regular hits, not specials.
* Otherwise people could backstab with the wrong weapon :)
*/
if( dt == TYPE_UNDEFINED )
{
juggle_shuffle( ch );
if( ch->deleted || !ch->fighting || ch->position <= POS_STUNNED )
return;
}
/*
* Figure out the type of damage message.
*/
if( wpn != WEAR_NONE )
wield = get_eq_char( ch, wpn );
if( dt == TYPE_UNDEFINED )
{
if( wpn == WEAR_WIELD_R )
offwield = TRUE;
dt = TYPE_HIT;
if( wield && wield->item_type == ITEM_WEAPON )
dt += wield->value[3];
}
dam = GET_AC( victim ) - 100;
dam += ( get_size( ch ) - get_size( victim ) ) / 2;
if( !can_see( victim, ch ) )
dam += 50;
if( ch->class == CLASS_NONE )
combat_no = 400 + ch->level / 5;
else
combat_no = 375 + class_table[ch->class].combat_no;
dam = dam * 100 + ch->level * combat_no;
dam = UMIN( -1, dam / 100 );
dam += number_range( 0, get_hitroll( ch ) * 6 + 5 );
dam += number_range( 0, ( ch->level - 3 ) * 4 + 10 );
if( dam > ch->level )
dam = UMIN( dam, ( ch->level + get_hitroll( ch ) ) * 12 );
/* MISSED 'IM
* (1/32 chance of always hit and always miss)
*/
if( ( dam <= 0 || number_bits( 5 ) == 0 )
&& !IS_AFFECTED( ch, AFF_NEVERMISS )
&& number_bits( 5 ) )
{
damage( ch, victim, 0, dt, wpn );
return;
}
else if( dam < 0 )
dam = 1;
/* if you hit you get the weapon's base damage added */
if( IS_NPC( ch ) )
{
basic_dam = number_range( ch->level / 3, ch->level ) + 1;
if( wield )
{
if( wield->wear_loc == WEAR_WIELD_DOUBLE )
basic_dam *= 3;
else
basic_dam += basic_dam * 2 / 3;
}
}
else if( wield && dt != gsn_race_tail ) /* weaponed PC */
{
basic_dam = number_range( wield->value[1], wield->value[2] );
if( basic_dam > 100000 )
{
bug( "One_hit basic_dam range > 100000 from %d to %d",
wield->value[1], wield->value[2] );
}
if( IS_SET( wield->extra_flags, ITEM_HORNED )
&& number_bits( 5 ) == 0 )
basic_dam *= 2;
if( wield->wear_loc == WEAR_WIELD_DOUBLE )
{
if( dt == gsn_backstab || dt == gsn_atemi )
basic_dam += basic_dam
* get_success( ch, gsn_two_handed, 200 ) / 50;
else
basic_dam += basic_dam
* get_success( ch, gsn_two_handed, 200 ) / 30;
}
if( wield->required_skill > 0 )
{
if( get_success( ch, gsn_weapon_skill, 100 ) )
basic_dam = basic_dam / 2 + basic_dam
* get_success( ch, wield->required_skill, 200 ) / 180;
else
basic_dam = basic_dam
* get_success( ch, wield->required_skill, 200 ) / 90;
}
if( ch->pcdata->learned[gsn_weapon_skill] > 0 )
basic_dam += basic_dam
* get_success( ch, gsn_weapon_skill, 250 ) / 150;
}
else /* no weapon basic damage */
{
basic_dam = get_size( ch ) / 10 + 1;
if( dt != gsn_race_tail ) /* barehand */
{
int i;
basic_dam /= 2;
if( ( i = get_success( ch, gsn_barehand, 100 ) ) )
{
basic_dam += i * ch->level / ( 400 - get_curr_dex( ch ) * 3 );
}
basic_dam = number_range( basic_dam, basic_dam * 2 );
if( dt == gsn_smash )
{
basic_dam += get_curr_str( ch ) * ch->level / 30;
}
else if( dt == TYPE_HIT && number_bits( 5 ) == 0
&& get_success( ch, gsn_golden_touch, 60 ) )
{
dt = gsn_golden_touch;
dam = dam * 5 / 2;
}
}
else
basic_dam = number_range( basic_dam / 2, basic_dam );
}
/*
* An idea I had, maybe this will work if I put some thought into it.
dam = dam * ( 75 + get_damroll( ch ) );
* --Symposium
*/
dam = dam / 10 + basic_dam + get_damroll( ch );
/*
* Now the other bonuses.
*/
if( wield && IS_SET( wield->extra_flags, ITEM_CHARGED ) )
{
if( ch->level < L_SEN )
REMOVE_BIT( wield->extra_flags, ITEM_CHARGED );
dam += dam * get_curr_int( ch ) / 12;
}
if( wield && wpn == WEAR_WIELD_L && get_eq_char( ch, WEAR_WIELD_R ) )
dam /= 2;
if( !IS_NPC( ch ) && ch->pcdata->learned[gsn_enhanced_damage] > 0 )
dam += dam * get_success( ch, gsn_enhanced_damage, 250 ) / 150;
if( !wield && !str_cmp( race_table[ch->race].name, "Vampire" ) )
dam += dam / 2;
if( IS_AFFECTED( ch, AFF_BLEEDING ) )
dam -= dam / 3;
if( dt == gsn_whirlwind )
dam += dam / 2;
else if( dt == gsn_headbutt )
{
OBJ_DATA *helmet;
helmet = get_eq_char( ch, WEAR_HEAD );
if( ch != victim )
dam *= 2;
if( ( helmet && IS_SET( helmet->extra_flags, ITEM_HORNED ) )
|| ( !helmet && IS_SET( ch->body_parts, BODY_PART_HORNS ) ) )
dam += dam / 2;
}
else if( dt == gsn_stomp )
{
dam += get_damroll( ch );
dam += get_size( ch ) / 2;
dam -= get_size( victim ) / 3;
dam = dam * ( get_curr_str( ch ) * 5 + get_curr_dex( ch ) * 2 ) / 120;
if( !str_cmp( class_table[ch->class].who_name, "cru" ) )
{
dam *= 3;
dam /= 2;
}
}
else if( dt == gsn_impale )
{
dam *= get_curr_str( ch );
dam /= 6;
if( !number_range( 0, UMAX( 70, 200 - ch->pcdata->learned[gsn_impale] ) ) )
{
send_to_char( "You have struck a vital organ!\n\r", ch );
act( "$n has struck a vital organ!", ch, NULL, victim, TO_ROOM );
dam *= 4;
}
}
else if( dt == gsn_backstab || dt == gsn_atemi )
{
dam = power( dam * ( 2 + UMIN( ( ch->level / 10 ), 4 ) ),
3, get_curr_dex( ch ) - 18 );
if( number_range( 0, power( 1000, 1, 20 - get_curr_dex( ch ) ) ) == 1 )
dam *= 4;
}
else if( dt == gsn_slit_throat )
{
dam = power( dam * ( 2 + UMIN( ( ch->level / 8 ), 8 ) ),
6, get_curr_dex( ch ) - 20 );
if( number_range( 0, power( 250, 2, 20 - get_curr_dex( ch ) ) ) == 0 )
{
OBJ_DATA *head;
char buf[MAX_INPUT_LENGTH];
act( "$n tear$% $N's head clean off!", ch, NULL, victim, TO_ALL );
head = create_object( get_obj_index( 50 ), victim->level );
head->value[0] = BODY_PART_HEAD;
sprintf( buf, head->short_descr, IS_NPC( victim )
? victim->short_descr : victim->name );
free_string( head->short_descr );
head->short_descr = str_dup( buf );
if( IS_AFFECTED( victim, AFF_POISON ) )
head->value[3] = 1;
obj_to_room( head, ch->in_room );
victim->hit /= 2;
damage( ch, victim, victim->max_hit * 3, dt, wpn );
return;
}
}
else if( dt == gsn_circle ) /* 150% to 200% at lev. 50 */
dam += dam / 2 + ( dam * ch->level ) / 100;
else if( dt == gsn_retaliate )
dam *= 2;
else if( dt == gsn_roundhouse )
dam = dam + dam * get_success( ch, dt, 100 ) / 50;
else if( dt == gsn_uppercut )
dam = dam + dam * get_success( ch, dt, 100 ) / 100;
else if( dt == gsn_elbow )
dam = dam + 3 * dam * get_success( ch, dt, 100 ) / 400;
else if( dt == gsn_knee )
dam = dam + dam * get_success( ch, dt, 100 ) / 200;
else if( dt == gsn_backhand )
dam = dam + dam * get_success( ch, dt, 100 ) / 400;
else if( dt == gsn_smash )
dam *= 2;
else if( dt == gsn_golden_touch )
dam = dam * 5 / 2;
/* they have hit so they must do SOME damage */
if( dam <= 0 )
dam = 1;
save_hit = victim->hit;
damage( ch, victim, dam, dt, wpn );
if( !victim->deleted && ch->fighting && victim->hit < save_hit )
damage_eq( ch, victim );
if( wield && number_bits( 7 )
< pow( 8, ( get_curr_str( ch ) - wield->weight - 20 ) / 12. )
&& !get_success( ch, gsn_weapon_skill, 30 ) )
{
act( "&rYou struck out too powerfully that stroke ...",
ch, NULL, NULL, TO_CHAR );
mod_item_condition( ch, wield,
UMAX( 1, ( get_curr_str( ch ) + number_bits( 3 )
- wield->weight ) / 27 ) );
}
if( ch->fighting && offwield
&& dt >= TYPE_HIT && get_eq_char( ch, WEAR_WIELD_L )
&& ( ( IS_SET( race_table[ch->race].race_abilities, RACE_DUAL_WIELD )
&& number_percent( ) < 40 )
|| IS_NPC_CLASS( ch ) ? number_percent( ) < UMIN( ch->level, 40 )
: get_success( ch, gsn_dual, 50 ) ) )
one_hit( ch, victim, TYPE_UNDEFINED, WEAR_WIELD_L );
flashing_blades( ch, victim, dt, wpn );
return;
}
/*
* casts a spell from a weapon
*/
void cast_weapon_spell( CHAR_DATA *ch, CHAR_DATA *victim, OBJ_DATA *wield )
{
int cost[MAGIC_MAX], i;
for( i = 0; i < MAGIC_MAX; ++i )
{
cost[i] = mana_cost( ch, wield->value[0], i ) / 3;
if( ch->mana[i] < cost[i] )
return;
}
for( i = 0; i < MAGIC_MAX; ++i )
ch->mana[i] -= cost[i];
( *skill_table[wield->value[0]].spell_fun )
( gsn_weapon_spell, ( wield->level / 2 ), ch, victim );
}
/*
* Apply any damage modifiers for spell sphere and other magical considerations
*/
int sphere_damage_mod( int dam, int sn, CHAR_DATA *ch, CHAR_DATA *victim )
{
int i, total, sphere[MAGIC_MAX];
float diff;
for( i = 0; i < MAGIC_MAX; ++i )
{
sphere[i] = 0;
if( skill_table[sn].min_mana[i] != 0 )
break;
}
if( i >= MAGIC_MAX ) /* this isn't a spell */
return dam;
dam *= 1000;
total = 0;
for( ; i < MAGIC_MAX; ++i )
{
if( skill_table[sn].min_mana[i] > 0 )
total += sphere[i] = skill_table[sn].min_mana[i];
else
sphere[i] = 0;
}
for( i = 0; i < MAGIC_MAX; ++i )
{
if( ( sphere[i] = sphere[i] * 1000 / total ) <= 0 )
continue;
diff = ch->level - victim->level;
diff = pow( 1.4, diff ) / 3;
diff += get_magic( ch, i ) - get_magic( victim, i );
diff -= get_magic_resist( victim ) / 40;
/*
* 1.5 times the damage if diff is 10,
* 0.5 times damage if diff is -10
* formula is inverse tan so that maximum damage is 2 times
* and minimum is zero.
*/
dam *= 1 + sphere[i] * atan( diff / 10 ) / ( M_PI * 500 );
}
/* intelligence mod, base damage at 20, 1.5 times at 30, 0.5 times at 10 */
diff = get_curr_int( ch ) * 2 - get_curr_wis( victim );
diff = diff / 20 - 1;
dam *= 1 + 2 * atan( diff ) / M_PI;
/* body temperature mods */
if( IS_SET( skill_table[sn].skill_type, SKILL_TYPE_FIRE ) )
dam -= ( dam * get_curr_temp( ch ) ) / 250;
if( IS_SET( skill_table[sn].skill_type, SKILL_TYPE_ICE ) )
dam += ( dam * get_curr_temp( ch ) ) / 250;
return UMAX( 1, dam / 1000 );
}
/*
* Inflict damage from a hit.
*/
void damage( CHAR_DATA *ch, CHAR_DATA *victim, int dam, int dt, int wpn )
{
int sntemp;
if( is_safe( ch, victim ) )
{
if( ch->fighting )
stop_fighting( ch, TRUE );
return;
}
if( victim->position == POS_DEAD )
return;
if( IS_AFFECTED( ch, AFF_FAST_TALK ) )
affect_strip( ch, skill_lookup( "fast talk" ) );
/*
* Stop up any residual loopholes.
*/
if( dam > 100000 && ch->level <= LEVEL_HERO )
{
if( IS_NPC( ch ) && ch->desc )
bug( "Damage: %d from %s by %s: > 100000 points with %d dt!",
dam, ch->name, ch->desc->original->name, dt );
else
bug( "Damage: %d from %s: > 100000 points with %d dt!",
dam, ch->name, dt );
}
if( victim != ch )
{
/*
* Certain attacks are forbidden.
* Most other attacks are returned.
*/
check_killer( ch, victim );
if( victim->position > POS_STUNNED && dt != TYPE_MPDAMAGE )
{
if( !victim->fighting )
set_fighting( victim, ch );
if( victim->position != POS_SMASHED
&& victim->position != POS_SLEEPING
&& victim->position != POS_GETTING_UP )
victim->position = POS_FIGHTING;
if( !ch->fighting )
set_fighting( ch, victim );
/*
* If NPC victim is following, ch might attack victim's master.
* No charm check here because charm would be dispelled from
* tanking mobile when combat ensues thus ensuring PC charmer is
* not harmed.
* Check for is_same_group wont work as following mobile is not
* always grouped with PC charmer - Kahn
*/
if( IS_NPC( ch )
&& IS_NPC( victim )
&& victim->master
&& victim->master->in_room == ch->in_room
&& number_bits( 2 ) == 0 )
{
stop_fighting( ch, FALSE );
set_fighting( ch, victim->master );
return;
}
}
/*
* More charm stuff.
*/
if( victim->master == ch )
stop_follower( victim );
/*
* Inviso attacks ... not.
*/
if( IS_NPC( ch ) && xIS_SET( ch->act, ACT_BURIED )
&& dt != TYPE_MPDAMAGE )
{
act( "$n tears $mself up from the earth.",
ch, NULL, NULL, TO_ROOM );
xREMOVE_BIT( ch->act, ACT_BURIED );
}
if( IS_AFFECTED( ch, AFF_INVISIBLE ) && dt != TYPE_MPDAMAGE )
{
affect_strip( ch, gsn_invis );
affect_strip( ch, gsn_mass_invis );
if( !is_affected( ch, gsn_vanish ) )
{
xREMOVE_BIT( ch->affected_by, AFF_INVISIBLE );
act( "$n fades into existence.", ch, NULL, NULL, TO_ROOM );
}
else
xSET_BIT( ch->affected_by, AFF_INVISIBLE );
}
/*
* Damage modifiers.
*/
/* Surge bonus, before changing from the weapon spell */
if( dt >= 0 && dt < MAX_SKILL
&& IS_SET( skill_table[dt].skill_type, SKILL_TYPE_MAGIC )
&& !IS_NPC( ch ) && IS_SET( ch->pcdata->pc_bits, PC_BIT_SURGE ) )
dam += dam;
if( dt == gsn_weapon_spell )
{
OBJ_DATA *wield;
wield = get_eq_char( ch, WEAR_WIELD_R );
if( !wield || wield->value[0] <= 0 )
wield = get_eq_char( ch, WEAR_WIELD_DOUBLE );
if( !wield || wield->value[0] <= 0 )
wield = get_eq_char( ch, WEAR_WIELD_L );
if( !wield )
bug( "Weird weapon spell bug [%s].", ch );
else
dt = wield->value[0];
}
if( dt < 0 || dt > MAX_SKILL
|| !IS_SET( skill_table[dt].skill_type, SKILL_TYPE_NO_RESIL ) )
{
dam *= get_curr_resil( victim );
dam /= 1000;
}
/* magic spells */
if( dt >= 0 && dt < MAX_SKILL
&& IS_SET( skill_table[dt].skill_type, SKILL_TYPE_MAGIC ) )
dam = sphere_damage_mod( dam, dt, ch, victim );
/* weapon strikes + warp flesh */
if( IS_AFFECTED( victim, AFF_WARP_FLESH )
&& ( ( dt > 0 && dt < MAX_SKILL &&
IS_SET( skill_table[dt].skill_type, SKILL_TYPE_WEAPONSTRIKE ) )
|| dt >= TYPE_HIT ) )
dam *= 2;
switch( victim->position )
{
case POS_SMASHED:
dam += dam / 2;
break;
case POS_SITTING:
dam += dam / 10;
break;
case POS_RESTING:
dam += dam / 5;
break;
default:
break;
}
if( IS_AFFECTED( victim, AFF_SANCTUARY )
|| ( !IS_NPC( victim )
&& IS_SET( race_table[victim->race].race_abilities, RACE_SANCT ) ) )
dam /= 2;
if( ( IS_AFFECTED( victim, AFF_PROTECT )
|| IS_SET( race_table[victim->race].race_abilities,
RACE_PROTECTION ) )
&& ( ( IS_EVIL( ch ) && IS_GOOD( victim ) )
|| ( IS_GOOD( ch ) && IS_EVIL( victim ) ) ) )
dam -= dam / 4;
if( dam < 0 )
dam = 0;
/*
* Make sure you start counting hits.
*/
if( ch->num_hits < 0 && victim == ch->fighting )
ch->num_hits = 0;
/*
* Check for disarm, trip, parry, and dodge.
*/
if( dt >= TYPE_HIT || dt == gsn_kick || dt == gsn_race_tail )
{
int leveldiff = ch->level - victim->level - 10;
if( IS_NPC( ch ) && dam == 0
&& number_percent( ) * 2
< ( leveldiff < -5 ? ch->level / 2 : UMAX( 10, leveldiff ) ) )
disarm( ch, victim );
if( IS_NPC( ch ) && dam == 0
&& number_percent( ) * 2 < UMIN( 30, leveldiff ) )
trip( ch, victim );
if( IS_NPC( ch )
&& !IS_SET( race_table[ch->race].race_abilities,
RACE_NO_WEAPON_WIELD )
&& number_percent( ) < UMIN( 25, UMAX( 10, ch->level ) )
&& !IS_NPC( victim ) )
use_magical_item( ch );
if( dam > 0 && !IS_AFFECTED( ch, AFF_NEVERMISS ) )
{
if( check_stumble( ch, victim ) )
return;
if( check_parry( ch, victim ) )
return;
if( check_dodge( ch, victim ) )
return;
if( check_shield_block( ch, victim ) )
return;
if( check_blink( ch, victim ) )
return;
if( check_familiar( ch, victim, dam ) )
return;
}
}
}
/* sleep modifier, has to be after dodge and parry */
if( victim->position == POS_SLEEPING && dam > 0 )
{
dam += ch->level * 2 + dam;
victim->position = POS_GETTING_UP;
}
/*
* We moved dam_message out of the victim != ch if above
* so self damage would show. Other valid type_undefined
* damage is ok to avoid like mortally wounded damage - Kahn
*/
if( dt != TYPE_UNDEFINED && dt != TYPE_MPDAMAGE )
dam_message( ch, victim, dam, dt, wpn );
/*
* Hurt the victim.
* Inform the victim of his new state.
*/
victim->hit -= dam;
if( !IS_NPC( victim )
&& victim->level >= LEVEL_IMMORTAL
&& victim->hit < 1 )
victim->hit = 1;
if( ( ( dt > 0 && dt < MAX_SKILL &&
IS_SET( skill_table[dt].skill_type, SKILL_TYPE_WEAPONSTRIKE ) )
|| ( dt >= TYPE_HIT && dt != TYPE_MPDAMAGE ) ) && dam > 1
&& victim != ch && IS_AFFECTED( victim, AFF_ATTACKSHIELD ) )
{
sntemp = skill_lookup( "flamestrike" );
( *skill_table[sntemp].spell_fun ) ( sntemp, ( victim->level / 4 ),
victim, ch );
}
if( victim->position <= POS_STUNNED
&& IS_AFFECTED( victim, AFF_BERSERK ) )
affect_strip( victim, gsn_berserk );
update_pos( victim );
/*
* Sleep spells and extremely wounded folks.
*/
if( !IS_AWAKE( victim ) && victim->fighting )
stop_fighting( victim, FALSE );
switch( victim->position )
{
case POS_MORTAL:
send_to_char(
"You are mortally wounded, and will die soon, if not aided.\n\r",
victim );
act( "$n is mortally wounded, and will die soon, if not aided.",
victim, NULL, NULL, TO_ROOM );
break;
case POS_INCAP:
send_to_char(
"You are incapacitated and will slowly die, if not aided.\n\r",
victim );
act( "$n is incapacitated and will slowly die, if not aided.",
victim, NULL, NULL, TO_ROOM );
break;
case POS_STUNNED:
send_to_char( "You are stunned, but will probably recover.\n\r",
victim );
act( "$n is stunned, but will probably recover.",
victim, NULL, NULL, TO_ROOM );
break;
case POS_DEAD:
if( IS_AFFECTED( victim, AFF_DEAD ) )
return;
send_to_char( "&RYou have been &fKILLED!!&n\n\r\n\r", victim );
act( "&r$n is DEAD!!", victim, NULL, NULL, TO_ROOM );
if( dt == gsn_hand_of_kaz )
{
OBJ_DATA *obj;
char buf[MAX_INPUT_LENGTH];
act( "&yYou lift $N's still beating heart from $S chest in triumph.",
ch, NULL, victim, TO_CHAR );
obj = create_object( get_obj_index( OBJ_VNUM_TORN_HEART ), victim->level );
sprintf( buf, obj->short_descr,
IS_NPC( victim ) ? victim->short_descr : victim->name );
free_string( obj->short_descr );
obj->short_descr = str_dup( buf );
sprintf( buf, obj->description,
IS_NPC( victim ) ? victim->short_descr : victim->name );
free_string( obj->description );
obj->description = str_dup( buf );
obj->cost /= 20;
obj_to_char( obj, ch );
if( !get_eq_char( ch, WEAR_HOLD_L )
&& !get_eq_char( ch, WEAR_SHIELD )
&& !get_eq_char( ch, WEAR_WIELD_L )
&& !get_eq_char( ch, WEAR_WIELD_DOUBLE ) )
equip_char( ch, obj, WEAR_HOLD_L );
else if( !get_eq_char( ch, WEAR_HOLD_R )
&& !get_eq_char( ch, WEAR_WIELD_R )
&& !get_eq_char( ch, WEAR_WIELD_DOUBLE ) )
equip_char( ch, obj, WEAR_HOLD_R );
}
break;
default:
if( dam > victim->max_hit / 5 )
send_to_char( "&rThat really did &RHURT!&n\n\r", victim );
check_critical( ch, victim, dam, wpn, dt );
if( victim->position != POS_DEAD
&& victim->hit < victim->max_hit / 5 )
{
xSET_BIT( victim->affected_by, AFF_BLEEDING );
send_to_char( "You sure are &rBLEEDING!&n\n\r", victim );
}
break;
}
/*
* Payoff for killing things.
*/
if( victim->position == POS_DEAD )
{
group_gain( ch, victim );
if( !IS_NPC( ch ) )
{
ch->pcdata->killed++;
if( ch->pcdata->clan
&& ( ( ( IS_NPC( victim )
&& ch->pcdata->clan->clan_type != CLAN_NORMAL ) )
|| ( !IS_NPC( victim )
&& ch->pcdata->clan->clan_type == CLAN_NORMAL ) ) )
ch->pcdata->clan->kills++;
}
if( !IS_NPC( victim ) )
{
SysInfo->deaths++;
if( victim->pcdata->clan
&& ( ( IS_NPC( ch )
&& victim->pcdata->clan->clan_type != CLAN_NORMAL )
|| ( !IS_NPC( ch )
&& victim->pcdata->clan->clan_type == CLAN_NORMAL ) ) )
victim->pcdata->clan->deaths++;
victim->pcdata->died++;
sprintf( log_buf, "%s killed by %s at %s",
victim->name,
ch == victim ? "their own stupidity"
: ( IS_NPC( ch ) ? ch->short_descr : ch->name ),
victim->in_room->name );
talk_channel( NULL, log_buf, CHANNEL_INFO, "INFO" );
log_string( "%s (%d)", log_buf, victim->in_room->vnum );
wiznetf( victim, WIZ_DEATHS, get_trust( victim ), "%s (%d)",
log_buf, victim->in_room->vnum );
/*
* Battle stuff.
* Need to keep the battle flag set so they can keep
* all their gear and get a restore.
*/
end_battle_check( victim );
/*
* Dying penalty:
* 1/2 way back to previous 2 levels.
*/
if( IS_NPC( ch ) && !xIS_SET( victim->act, PLR_BATTLE ) )
{
if( victim->exp < get_tnl( victim ) - 4 )
gain_exp( victim, ( victim->exp - get_tnl( victim ) ) / 3 );
else
gain_exp( victim, -4 );
}
}
if( !IS_NPC( victim ) && victim->pcdata->bounty > 0
&& !xIS_SET( victim->act, PLR_BATTLE )
&& !IS_SET( victim->in_room->room_flags, ROOM_ARENA ) )
{
act( "&g$n collect$% the bounty on $O head.",
ch, NULL, victim, TO_ALL );
ch->gold += victim->pcdata->bounty;
victim->pcdata->bounty = 0;
}
if( !IS_NPC( victim ) && !IS_AFFECTED( victim, AFF_DEAD )
&& get_success( victim, gsn_swan_song, 60 ) )
{
xSET_BIT( victim->affected_by, AFF_DEAD );
update_pos( victim );
create_char_event( victim, evn_swan_song,
dice( 4, 24 ) * PULSE_PER_SECOND );
return;
}
raw_kill( ch, victim );
/* Ok, now we want to remove the deleted flag from the
* PC victim.
*/
if( !IS_NPC( victim ) )
victim->deleted = FALSE;
if( !IS_NPC( ch ) && IS_NPC( victim ) )
{
if( ch->pcdata->pc_bits >= PC_BIT_STRANGLE )
ch->pcdata->pc_bits %= PC_BIT_STRANGLE;
/* Autogold by Morpheus */
if( xIS_SET( ch->act, PLR_AUTOGOLD ) )
do_get( ch, "coins corpse" ); /* autogold mod by Canth */
if( xIS_SET( ch->act, PLR_AUTOLOOT ) )
do_get( ch, "all corpse" );
else
do_look( ch, "in corpse" );
if( xIS_SET( ch->act, PLR_AUTOSAC ) )
do_sacrifice( ch, "corpse" );
}
else if( IS_NPC( ch ) && xIS_SET( ch->act, ACT_MERCENARY )
&& IS_NPC( victim ) )
{
do_get( ch, "all corpse" );
sort_inventory( ch );
}
/*
* Remove victims of pk who no longer have exps left
*/
if( !IS_NPC( victim ) && xIS_SET( victim->act, PLR_DENY ) )
{
do_quit( victim, "" );
}
return;
}
else
{
if( dam > 0 && dt == TYPE_HIT && !get_eq_char( ch, WEAR_HOLD_R )
&& !get_eq_char( ch, WEAR_HOLD_L ) && !get_eq_char( ch, WEAR_SHIELD ) )
{
barehand_hit( ch, victim );
if( victim->deleted || victim->position == POS_DEAD )
return;
}
else if( ( dt > 0 && dt < MAX_SKILL &&
IS_SET( skill_table[dt].skill_type, SKILL_TYPE_WEAPONSTRIKE ) )
|| ( dt >= TYPE_HIT && dt != TYPE_MPDAMAGE ) )
{
OBJ_DATA *wield;
if( dam > 0 && ( wield = get_eq_char( ch, wpn ) ) )
{
if( IS_SET( wield->extra_flags, ITEM_POISONED ) )
{
( *skill_table[gsn_poison].spell_fun )
( gsn_poison, ( wield->level / 2 ), ch, victim );
}
if( victim->deleted || victim->position == POS_DEAD )
return;
if( wield->item_type == ITEM_WEAPON && wield->value[0] > 0 )
cast_weapon_spell( ch, victim, wield );
if( victim->deleted || victim->position == POS_DEAD )
return;
}
}
}
if( victim == ch )
return;
/*
* Take care of link dead people.
if( !IS_NPC( victim ) && !victim->desc )
{
if( number_range( 0, victim->wait ) == 0 )
{
do_recall( victim, "" );
return;
}
}
*/
/*
* Wimp out?
*/
if( IS_NPC( victim ) && dam > 0 )
{
if( ( xIS_SET( victim->act, ACT_WIMPY ) && number_bits( 1 ) == 0
&& victim->hit < victim->max_hit / 3 )
|| ( IS_AFFECTED( victim, AFF_CHARM ) && victim->master
&& victim->master->in_room != victim->in_room ) )
do_flee( victim, "" );
}
if( !IS_NPC( victim )
&& victim->hit > 0
&& victim->hit <= victim->wimpy
&& victim->wait == 0 )
do_flee( victim, "" );
return;
}
bool is_safe( CHAR_DATA *ch, CHAR_DATA *victim )
{
if( IS_SET( SysInfo->flags, SYSINFO_NOFIGHT ) )
{
send_to_char( "You can't fight!\n\r", ch );
return TRUE;
}
/* the organised PK options... checked before some of the standard
* 'safe' options such as a safe room.
*/
if( !IS_NPC( ch ) && !IS_NPC( victim ) )
{
if( IS_SET( victim->in_room->room_flags, ROOM_ARENA ) )
return FALSE;
if( xIS_SET( ch->act, PLR_BATTLE )
&& xIS_SET( victim->act, PLR_BATTLE )
&& ( !IS_SET( SysInfo->flags, SYSINFO_HOLYWAR )
|| ch->pcdata->religion != victim->pcdata->religion ) )
return FALSE;
}
if( !IS_NPC( victim ) && xIS_SET( victim->act, PLR_OUTLAW ) )
return FALSE;
if( !IS_NPC( ch ) && IS_AFFECTED( ch, AFF_GHOUL ) )
{
send_to_char(
"You may not participate in combat while in ghoul form.\n\r",
ch );
return TRUE;
}
if( !IS_NPC( victim ) && IS_AFFECTED( victim, AFF_GHOUL ) )
{
act( "&g$o attack passes through $N.", ch, NULL, victim, TO_ALL );
return TRUE;
}
if( IS_SET( victim->in_room->room_flags, ROOM_SAFE ) )
{
act( "&g$N is in a safe room.", ch, NULL, victim, TO_CHAR );
return TRUE;
}
if( is_clan_enemy( victim, ch ) )
return FALSE;
if( !IS_NPC( ch ) && IS_NPC( victim ) && victim->spec_fun
&& IS_SET( spec_table[victim->spec_fun].usage, SPEC_ATTACK ) )
{
return (*spec_table[victim->spec_fun].spec_fun)
( victim, ch, SPEC_ATTACK, NULL );
}
if( IS_NPC( ch ) )
return FALSE;
if( IS_NPC( victim ) )
{
/*
* Mobprog for getting attacked.
* Note that the hard coded prog has precedence, if it exists
* these will be ignored.
*/
if( !victim->fighting
&& xIS_SET( victim->pIndexData->progtypes, ATTACK_PROG ) )
mprog_percent_check( victim, ch,
NULL, NULL, ATTACK_PROG );
if( ch->in_room != victim->in_room
|| ch->in_room->area != victim->in_room->area )
return TRUE;
return FALSE;
}
if( ch != victim && str_cmp( race_table[victim->race].name, "Vampire" ) )
{
if( victim->level > LEVEL_HERO )
{
act( "$N is a &uHERO&n and is automatically safe.",
ch, NULL, victim, TO_CHAR );
return TRUE;
}
return TRUE;
}
return FALSE;
}
void show_opponent( CHAR_DATA *ch )
{
int state;
const char *message;
if( !ch->fighting )
return;
state = ch->fighting->hit * 100 / ch->fighting->max_hit;
if( state < 12 )
message = "&b$N is really buggered, $E looks ready to drop.&n";
else if( state < 25 )
message = "&b$N is wounded badly, $E looks gone for all money.&n";
else if( state < 38 )
message = "&b$N is bleeding freely from many cuts.&n";
else if( state < 51 )
message = "&b$N sways in the breeze, looking dazed.&n";
else if( state < 64 )
message = "&b$N is bruised and sore looking.&n";
else if( state < 77 )
message = "&b$N has a few minor scratches and abrasions.&n";
else if( state < 90 )
message = "&b$N is slightly hurt, but would still pass physical.&n";
else
message = "&b$N has no obvious health problems.&n";
act( message, ch, NULL, ch->fighting, TO_CHAR );
if( ch->in_room != ch->fighting->in_room )
{
stop_fighting( ch->fighting, FALSE );
stop_fighting( ch, FALSE );
}
}
void barehand_hit( CHAR_DATA *ch, CHAR_DATA *victim )
{
struct bh_type
{
int * num;
int cha;
};
const struct bh_type bh_gsns[] =
{
{ &gsn_backhand, 10 },
{ &gsn_jab, 20 },
{ &gsn_knee, 40 },
{ &gsn_elbow, 80 },
{ &gsn_uppercut, 160 },
{ &gsn_roundhouse, 320 },
{ &gsn_headbutt, 500 },
{ NULL, 0 }
};
int i, chance;
int cost = 20;
if( !IS_NPC( ch ) && IS_SET( ch->pcdata->pc_bits, PC_BIT_SURGE ) )
cost *= 3;
/* if you are barehanded you get the chance at the barehanded effects */
if( IS_AFFECTED( ch, AFF_MAGICHANDS ) )
{
if( ch->mana[MAGIC_FIRE] >= cost
&& is_affected( ch, gsn_burning_hands ) )
{
ch->mana[MAGIC_FIRE] -= cost;
spell_power_3( gsn_burn, ch->level, ch, victim );
}
else if( ch->mana[MAGIC_WATER] >= cost
&& is_affected( ch, gsn_chill_touch ) )
{
ch->mana[MAGIC_WATER] -= cost;
spell_freeze( skill_lookup( "freeze" ), ch->level, ch, victim );
}
}
if( ch->fighting != victim || victim->deleted
|| victim->position == POS_DEAD )
return;
if( IS_AFFECTED( ch, AFF_MAGICHANDS ) && ch->mana[MAGIC_AIR] >= cost
&& is_affected( ch, gsn_shocking_grasp ) )
{
ch->mana[MAGIC_AIR] -= cost;
spell_power_5( skill_lookup( "shock" ), ch->level, ch, victim );
if( ch->fighting != victim || victim->deleted
|| victim->position == POS_DEAD )
return;
}
if( IS_AFFECTED( ch, AFF_MAGICHANDS )
&& is_affected( ch, gsn_dark_claws ) )
{
spell_poison( skill_lookup( "poison" ), ch->level * 2 / 3, ch, victim );
if( ch->fighting != victim || victim->deleted
|| victim->position == POS_DEAD )
return;
}
if( IS_NPC( ch ) )
return;
for( i = 0; bh_gsns[i].num; ++i )
{
if( !can_use( ch, *bh_gsns[i].num ) )
continue;
chance = 300 * bh_gsns[i].cha;
chance /= 2 * ch->pcdata->learned[*bh_gsns[i].num]
+ ch->pcdata->learned[gsn_barehand] + 2 * get_curr_dex( ch );
if( number_range( 0, chance ) == 0 )
{
one_hit( ch, victim, *bh_gsns[i].num, WEAR_WIELD_R );
return;
}
}
return;
}
void flashing_blades( CHAR_DATA *ch, CHAR_DATA *victim, int dt, int wpn )
{
int chance;
if( IS_NPC_CLASS( ch ) || ch->fighting != victim || dt < TYPE_HIT
|| ( wpn != WEAR_WIELD_L && wpn != WEAR_WIELD_R )
|| !can_use( ch, gsn_flashing_blades ) )
return;
if( ( wpn == WEAR_WIELD_L && get_eq_char( ch, WEAR_WIELD_R ) )
|| !get_eq_char( ch, wpn ) )
return;
chance = power( get_success( ch, gsn_flashing_blades, 100 ),
-2, ch->carry_weight - 10 );
if( number_bits( 9 ) < chance )
one_hit( ch, victim, dt, wpn );
return;
}
bool get_juggle_attack( CHAR_DATA *ch, int num )
{
OBJ_DATA * obj;
int amount = 0;
int chance;
if( ch->deleted || !ch->fighting || ch->position < POS_FIGHTING )
return FALSE;
for( obj = get_eq_char( ch, WEAR_JUGGLED ); obj; obj = obj->next_content )
{
if( obj->deleted || obj->wear_loc != WEAR_JUGGLED )
continue;
amount++;
}
/* only a small reduction of chance for higher number attacks as these
are less likely to occur due to initiative lossage. */
chance = num * 5 + power( 20, 5, 22 - get_curr_dex( ch ) );
if( amount < num || number_bits( 7 ) < chance )
return FALSE;
return TRUE;
}
void juggle_shuffle( CHAR_DATA *ch )
{
OBJ_DATA *obj;
OBJ_DATA *juggled[MAX_JUGGLED];
int i = 0;
int slot;
if( !get_eq_char( ch, WEAR_JUGGLED ) )
return;
for( obj = ch->carrying; i < MAX_JUGGLED && obj; obj = obj->next_content )
{
if( !obj->deleted && ( obj->wear_loc == WEAR_JUGGLED
|| obj->wear_loc == WEAR_WIELD_L
|| obj->wear_loc == WEAR_WIELD_R ) )
{
juggled[i++] = obj;
obj->wear_loc = WEAR_JUGGLED;
}
}
if( i < 3 )
{
bug( "%s is juggling but not enough.\n\r", ch->name );
return;
}
do /* pick new right wield */
{
slot = number_range( 0, i - 1 );
}
while( juggled[slot] == NULL );
juggled[slot]->wear_loc = WEAR_WIELD_R;
juggled[slot] = NULL;
do /* pick new left wield */
{
slot = number_range( 0, i - 1 );
}
while( juggled[slot] == NULL );
juggled[slot]->wear_loc = WEAR_WIELD_L;
juggled[slot] = NULL;
if( !get_success( ch, gsn_juggle, 130 - i * 10 ) )
{
if( get_success( ch, gsn_juggle, 30 + get_curr_dex( ch ) ) )
{
act( "$n stuff$% up but make$% a hasty recovery.",
ch, NULL, NULL, TO_ALL );
WAIT_STATE( ch, skill_table[gsn_juggle].beats );
return;
}
act( "$n stuffs up $s juggling and drops weapons all over...",
ch, NULL, NULL, TO_ROOM );
act( "You stuff up your juggling routine.", ch, NULL, NULL, TO_CHAR );
while( ( obj = get_eq_char( ch, WEAR_JUGGLED ) ) )
{
if( number_bits( i ) > 10 )
{
slot = obj->level * ( i - 3 );
slot = number_range( slot, slot * 3 / 2 );
damage( ch, ch, slot, gsn_juggle, WEAR_NONE );
}
act( "$p falls to the ground.", ch, obj, NULL, TO_ALL );
obj_from_char( obj );
obj_to_room( obj, ch->in_room );
}
}
return;
}
void check_critical( CHAR_DATA *ch, CHAR_DATA *victim, int dam, int wpn, int dt )
{
OBJ_DATA *obj = NULL;
int chance = 50 - get_curr_con( victim );
int part, i;
if( dam <= victim->level /* rare occurance */
|| victim->level < 10 || number_bits( 5 ) )
return;
if( wpn != WEAR_NONE )
obj = get_eq_char( ch, wpn );
if( !obj )
chance -= 10;
else
{
if( obj->item_type == ITEM_WEAPON )
return; /* can't do any damage without a weapon */
if( IS_SET( obj->extra_flags, ITEM_SHARP ) )
chance += 5;
if( dam > victim->max_hit / 5 )
chance += 12;
else if( dam > victim->max_hit / 10 )
chance += 5;
}
chance += ( get_curr_str( ch ) ) - 20;
part = number_percent( );
for( i = 0; part_loss_table[i].chance > 0; ++i )
{
if( part > part_loss_table[i].chance
|| ( part_loss_table[i].body_parts & victim->body_parts ) == 0 )
continue;
if( IS_SET( victim->damaged_parts, part_loss_table[i].body_parts ) )
chance += 20;
break;
}
chance = UMIN( chance, 25 ) - number_bits( 7 );
if( chance > 15 && part_loss_table[i].chance > 0
&& dt > TYPE_HIT && dt != TYPE_HIT + 2
&& ( dt <= TYPE_HIT + 5 || dt >= TYPE_HIT + 10 )
&& dt != TYPE_HIT + 11 && dt != TYPE_HIT + 12 )
remove_part( ch, victim, i );
else if( chance > 0 )
{
act( "You get a fluke shot on $N and blood spurts from a fresh wound.",
ch, NULL, victim, TO_CHAR );
act( "$n gets a fluke shot on you, opening a new wound.",
ch, NULL, victim, TO_VICT );
act( "$n strikes $N, making an ugly wound appear.",
ch, NULL, victim, TO_NOTVICT );
if( IS_SET( victim->damaged_parts, part_loss_table[i].body_parts ) )
dam += dam / 2;
SET_BIT( victim->damaged_parts, part_loss_table[i].body_parts );
xSET_BIT( victim->affected_by, AFF_BLEEDING );
}
if( chance > 0 && victim->position != POS_DEAD )
{
xSET_BIT( victim->affected_by, AFF_BLEEDING );
chance = UMAX( 10, chance );
dam = number_range( dam * chance / 75, dam * chance / 30 );
damage( ch, victim, dam, gsn_lucky_blow, wpn );
}
return;
}
void remove_part( CHAR_DATA *ch, CHAR_DATA *victim, int part )
{
int smeg;
OBJ_DATA *obj, *obj_next;
char buf[MAX_INPUT_LENGTH];
obj = create_object( get_obj_index( part_loss_table[part].limb_vnum ),
victim->level );
sprintf( buf, obj->short_descr, IS_NPC( victim )
? victim->short_descr : victim->name );
free_string( obj->short_descr );
obj->short_descr = str_dup( buf );
obj->value[0] = victim->body_parts & part_loss_table[part].body_parts;
if( IS_AFFECTED( ch, AFF_POISON ) )
SET_BIT( obj->extra_flags, ITEM_POISONED );
if( IS_AFFECTED( victim, AFF_POISON ) )
obj->value[3] = 1;
obj->cost /= 20;
obj_to_room( obj, victim->in_room );
act( part_loss_table[part].act_char, victim, NULL, ch, TO_CHAR );
act( part_loss_table[part].act_other, victim, NULL, ch, TO_ROOM );
for( obj = victim->carrying; obj; obj = obj_next )
{
obj_next = obj->next_content;
if( obj->wear_loc == WEAR_NONE )
continue;
smeg = part_loss_table[part].body_parts & victim->body_parts;
smeg &= wear_table[obj->wear_loc].body_parts;
if( smeg )
{
act( "&m$p&m drops to the ground.&n",
victim, obj, NULL, TO_ALL );
unequip_char( ch, obj );
obj_from_char( obj );
obj_to_room( obj, victim->in_room );
}
}
victim->body_parts &= ~part_loss_table[part].body_parts;
if( IS_SET( part_loss_table[part].body_parts, BODY_PART_HEAD )
&& victim->position != POS_DEAD )
{
act( "$n die$%.", victim, NULL, ch, TO_ALL );
victim->hit = -10;
victim->position = POS_DEAD;
}
return;
}
void damage_eq( CHAR_DATA *ch, CHAR_DATA *victim )
{
OBJ_DATA *obj;
if( number_bits( 10 ) > 65 - get_curr_dex( victim ) )
return;
for( obj = victim->carrying; obj; obj = obj->next_content )
{
if( obj->deleted || obj->wear_loc == WEAR_NONE
|| number_bits( 10 ) > 40 - get_curr_dex( victim ) )
continue;
mod_item_condition( ch, obj, 1 );
return;
}
return;
}
/*
* Check to see if the battle that is currently raging is over.
*/
void end_battle_check( CHAR_DATA *victim )
{
CHAR_DATA *rch;
int contin = 0;
RELIGION_DATA *pReligion = NULL;
CLAN_DATA *pClan;
if( !xIS_SET( victim->act, PLR_BATTLE ) )
return;
if( IS_SET( SysInfo->flags, SYSINFO_HOLYWAR ) )
{
for( rch = char_list; rch; rch = rch->next )
{
if( rch->deleted || IS_NPC( rch ) || rch == victim
|| !xIS_SET( rch->act, PLR_BATTLE ) )
continue;
if( !pReligion )
pReligion = rch->pcdata->religion;
else if( pReligion != rch->pcdata->religion )
return; /* More than one religion left, no action */
}
for( rch = char_list; rch; rch = rch->next )
if( !IS_NPC( rch ) )
xREMOVE_BIT( rch->act, PLR_BATTLE );
REMOVE_BIT( SysInfo->flags, SYSINFO_HOLYWAR );
if( !pReligion )
{
bug( "In the grasp of a Holy War and there is no winner." );
return;
}
sprintf( log_buf, "%s has won the Holy War for %s!",
pReligion->display_name, pReligion->god_name );
talk_channel( NULL, log_buf, CHANNEL_INFO, "INFO" );
for( pClan = clan_first; pClan; pClan = pClan->next )
if( pClan->clan_type == CLAN_ORDER
&& pClan->religion == pReligion )
add_karma( pClan, 250 );
}
else if( battle_min && battle_max )
{
for( rch = char_list; rch; rch = rch->next )
{
if( !IS_NPC( rch ) && xIS_SET( rch->act, PLR_BATTLE )
&& rch != victim )
contin++;
}
if( contin < 2 )
{
for( rch = char_list; rch; rch = rch->next )
if( !IS_NPC( rch ) && rch != victim &&
xIS_SET( rch->act, PLR_BATTLE ) )
break;
if( rch )
{
xREMOVE_BIT( rch->act, PLR_BATTLE );
sprintf( log_buf, "%s has won the battle!",
rch->name );
talk_channel( NULL, log_buf, CHANNEL_INFO, "INFO" );
do_recall( rch, "auto" );
}
battle_min = 0;
battle_max = 0;
}
}
}
/*
* See if an attack justifies a OUTLAW flag.
*/
void check_killer( CHAR_DATA *ch, CHAR_DATA *victim )
{
OBJ_DATA *obj;
OBJ_DATA *obj_next;
char buf[MAX_STRING_LENGTH];
/*
* NPC's are fair game.
*/
if( IS_NPC( victim ) )
return;
/*
* NPC's are cool of course
* Hitting yourself is cool too (bleeding).
* Hitting immortals are fine, if you can.
*/
if( IS_NPC( ch ) || ch == victim || victim->level > LEVEL_HERO )
return;
/*
* OUTLAWs are fair game.
* OUTLAW aggressors should not be penalized 1000 exps per attack but
* per combat started.
*/
if( xIS_SET( victim->act, PLR_OUTLAW )
|| ( xIS_SET( ch->act, PLR_OUTLAW ) && ch->fighting ) )
return;
if( IS_SET( ch->in_room->room_flags, ROOM_ARENA )
|| ( xIS_SET( ch->act, PLR_BATTLE ) &&
xIS_SET( victim->act, PLR_BATTLE ) ) )
return;
/* Allowing attacks on temporary vampires. */
if( !str_cmp( race_table[victim->race].name, "Vampire" )
&& IS_AFFECTED( victim, AFF_VAMP_BITE ) )
return;
send_to_char( "You are a &ROUTLAW!&n You lose 1000 exps.\n\r", ch );
sprintf( buf, "Help! I'm being attacked by %s!", ch->name );
do_shout( victim, buf );
xSET_BIT( ch->act, PLR_OUTLAW );
gain_exp( ch, -100000 );
demote_level( ch );
for( obj = ch->carrying; obj; obj = obj_next )
{
obj_next = obj->next_content;
if( obj->deleted )
continue;
obj_from_char( obj );
/*
* Remove item inventories
* Includes licenses to kill
*/
if( IS_SET( obj->extra_flags, ITEM_INVENTORY ) )
{
extract_obj( obj );
continue;
}
obj_to_char( obj, ch );
}
save_char_obj( ch );
return;
}
/*
* Check to see if weapon is poisoned.
*/
bool is_wielding_poisoned( CHAR_DATA *ch, int wpn )
{
OBJ_DATA *obj;
if( ( obj = get_eq_char( ch, wpn ) )
&& IS_SET( obj->extra_flags, ITEM_POISONED ) )
return TRUE;
return FALSE;
}
/*
* Check for block.
*/
bool check_shield_block( CHAR_DATA *ch, CHAR_DATA *victim )
{
int chance;
if( IS_NPC_CLASS( victim )
|| IS_SET( victim->extra_bits, CH_EX_SHIELD_BLOCK )
|| !IS_AWAKE( victim ) )
return FALSE;
if( get_eq_char( victim, WEAR_SHIELD ) == NULL )
return FALSE;
chance = get_success( victim, gsn_shield_block, 200 )
* get_curr_str( victim ) / 40;
if( number_percent( ) >= chance + victim->level - ch->level )
return FALSE;
if( victim->desc && !xIS_SET( CH( victim->desc )->act, PLR_BATTLESELF ) )
act( "&gYou block $n's attack with your shield.", ch, NULL, victim,
TO_VICT );
if( ch->desc && !xIS_SET( CH( ch->desc )->act, PLR_BATTLESELF ) )
act( "&g$N blocks your attack with a shield.", ch, NULL, victim,
TO_CHAR );
if( number_bits( 2 ) )
SET_BIT( victim->extra_bits, CH_EX_SHIELD_BLOCK );
return TRUE;
}
/*
* Check for parry.
*/
bool check_parry( CHAR_DATA *ch, CHAR_DATA *victim )
{
int chance;
if( IS_SET( victim->extra_bits, CH_EX_PARRY )
|| !IS_AWAKE( victim ) )
return FALSE;
if( IS_NPC_CLASS( victim ) )
{
/* Tuan was here. :) */
chance = UMIN( 40, victim->level - ch->level / 3 );
if( !get_eq_char( victim, WEAR_WIELD_R ) )
{
if( !get_eq_char( victim, WEAR_WIELD_L ) )
chance /= 2;
else
chance = 3 * chance / 4;
}
}
else
{
if( !get_eq_char( victim, WEAR_WIELD_R ) )
{
if( !get_eq_char( victim, WEAR_WIELD_L ) )
return FALSE;
chance = get_success( victim, gsn_parry, 200 ) / 4;
}
else
chance = get_success( victim, gsn_parry, 200 ) / 2;
if( !IS_NPC( victim ) )
chance -= victim->pcdata->condition[COND_DRUNK] / 10;
}
if( ch->position < POS_FIGHTING )
chance = chance * 3 / 4;
if( number_percent( ) >= chance + victim->level - ch->level )
return FALSE;
if( number_bits( 2 ) )
SET_BIT( victim->extra_bits, CH_EX_PARRY );
if( ch->desc && !xIS_SET( CH( ch->desc )->act, PLR_BATTLESELF ) )
act( "&g$N parries your attack.", ch, NULL, victim, TO_CHAR );
if( victim->desc && !xIS_SET( CH( victim->desc )->act, PLR_BATTLESELF ) )
act( "&gYou parry $n's attack.", ch, NULL, victim, TO_VICT );
return TRUE;
}
/*
* Check for dodge.
*/
bool check_dodge( CHAR_DATA *ch, CHAR_DATA *victim )
{
int chance, which_bit;
if( !IS_AWAKE( victim ) )
return FALSE;
if( IS_SET( victim->extra_bits, CH_EX_DODGE ) )
{
if( IS_SET( victim->extra_bits, CH_EX_DODGE_2 ) )
return FALSE;
else
which_bit = CH_EX_DODGE_2;
}
else
which_bit = CH_EX_DODGE;
if( IS_NPC_CLASS( victim ) )
/* Tuan was here. :) */
chance = UMIN( 25, victim->level - ch->level / 3 );
else
{
if( which_bit == CH_EX_DODGE )
chance = get_success( victim, gsn_dodge, 200 ) / 3;
else
chance = get_success( victim, gsn_avoidance, 200 ) / 3;
chance = power( chance, 2, get_curr_dex( ch ) - 20 );
if( !IS_NPC( victim ) )
chance -= victim->pcdata->condition[COND_DRUNK] / 10;
}
if( chance > 0 )
chance += UMIN( get_speed( victim ), 30 );
if( ch->position < POS_FIGHTING )
chance = chance * 2 / 3;
if( number_percent( ) >= chance + victim->level - ch->level )
return FALSE;
if( number_bits( 2 ) )
SET_BIT( victim->extra_bits, which_bit );
if( ch->desc && !xIS_SET( CH( ch->desc )->act, PLR_BATTLESELF ) )
act( "&g$N dodges your attack.", ch, NULL, victim, TO_CHAR );
if( victim->desc && !xIS_SET( CH( victim->desc )->act, PLR_BATTLESELF ) )
act( "&gYou dodge $n's attack.", ch, NULL, victim, TO_VICT );
return TRUE;
}
/*
* Check for dodge.
*/
bool check_blink( CHAR_DATA *ch, CHAR_DATA *victim )
{
int chance;
if( !IS_AWAKE( victim ) || IS_NPC( victim )
|| victim->pcdata->blink <= 0 )
return FALSE;
chance = get_success( victim, gsn_blink, 100 ) * victim->pcdata->blink / 100;
chance = power( chance, 2, get_curr_int( ch ) - 20 );
chance -= victim->pcdata->condition[COND_DRUNK] / 10;
if( chance > 0 )
chance += UMIN( get_speed( victim ), 30 );
if( ch->position < POS_FIGHTING )
chance = chance * 2 / 3;
if( number_percent( ) >= chance + victim->level - ch->level )
return FALSE;
if( number_bits( 2 ) )
SET_BIT( victim->extra_bits, CH_EX_BLINK );
if( ch->desc && !xIS_SET( CH( ch->desc )->act, PLR_BATTLESELF ) )
act( "&g$N blinks out of existance avoiding your strike.",
ch, NULL, victim, TO_CHAR );
if( victim->desc && !xIS_SET( CH( victim->desc )->act, PLR_BATTLESELF ) )
act( "&gYou blink out of existance, narrowly avoiding $n's attack.",
ch, NULL, victim, TO_VICT );
take_generic_mana( victim, victim->pcdata->blink * MAGIC_MAX );
return TRUE;
}
/*
* One of the advantages to being drunk.
*/
bool check_stumble( CHAR_DATA *ch, CHAR_DATA *victim )
{
if( IS_NPC( victim ) || IS_SET( victim->extra_bits, CH_EX_STUMBLE )
|| !IS_AWAKE( victim ) )
return FALSE;
if( number_percent( ) < ( victim->pcdata->condition[COND_DRUNK] / 10 - 20 ) / 2 )
{
if( ch->desc && !xIS_SET( CH( ch->desc )->act, PLR_BATTLESELF ) )
act( "&g$N stumbles drunkenly and your blow goes wide.",
ch, NULL, victim, TO_CHAR );
if( victim->desc && !xIS_SET( CH( victim->desc )->act, PLR_BATTLESELF ) )
act( "&gYou trip over your weapon and land in a heap on the ground.",
ch, NULL, victim, TO_VICT );
victim->position = POS_GETTING_UP;
WAIT_STATE( victim, PULSE_VIOLENCE / 2 );
SET_BIT( victim->extra_bits, CH_EX_STUMBLE );
return TRUE;
}
return FALSE;
}
#define chance 42
bool check_familiar( CHAR_DATA *ch, CHAR_DATA *victim, int dam )
{
if( IS_NPC( victim ) || IS_SET( victim->extra_bits, CH_EX_FAMILIAR )
|| !IS_AWAKE( victim ) || victim->pcdata->familiar <= 0 )
return FALSE;
if( number_percent( ) > UMIN( 60, chance - victim->level + ch->level ) )
{
if( number_percent( ) > UMIN( 5, ( chance - victim->level + ch->level ) / 12 ) )
return FALSE;
else
{
if( victim->desc && !xIS_SET( CH( victim->desc )->act, PLR_BATTLESELF ) )
act( "&gYour familiar distracts $n from $s attack.",
ch, NULL, victim, TO_VICT );
if( ch->desc && !xIS_SET( CH( ch->desc )->act, PLR_BATTLESELF ) )
act( "&g$N's familiar distracts you and you miss $M, #&&*@!",
ch, NULL, victim, TO_CHAR );
SET_BIT( victim->extra_bits, CH_EX_FAMILIAR );
return TRUE;
}
}
if( victim->desc && !xIS_SET( CH( victim->desc )->act, PLR_BATTLESELF ) )
act( "&gYour familiar bravely takes $n's attack for you.",
ch, NULL, victim, TO_VICT );
if( ch->desc && !xIS_SET( CH( ch->desc )->act, PLR_BATTLESELF ) )
act( "&g$N's familiar gets in the way of your attack.",
ch, NULL, victim, TO_CHAR );
victim->pcdata->familiar -= dam;
if( victim->pcdata->familiar <= 0 )
{
victim->pcdata->familiar = 0;
act( "&y$o familiar screams and disappears.", victim, NULL, ch, TO_ALL );
}
if( number_bits( 2 ) )
SET_BIT( victim->extra_bits, CH_EX_FAMILIAR );
return TRUE;
}
#undef chance
void dirty_fighting( CHAR_DATA *ch )
{
CHAR_DATA *victim = ch->fighting;
AFFECT_DATA af;
OBJ_DATA *obj;
int i;
char buf[MAX_INPUT_LENGTH];
if( !victim )
return;
switch( number_bits( 3 ) )
{
case 0: case 1:
act( "$n fling$% a handful of dirt in $O face, blinding $M!",
ch, NULL, victim, TO_ALL );
af.type = gsn_dirty_fighting;
af.location = APPLY_HITROLL;
af.modifier = -4 - ch->level / 25;
af.duration = 0;
vset( af.bitvector, AFF_BLIND );
affect_to_char( victim, &af, NULL );
break;
case 2:
act( "$n knee$% $N in the groin.", ch, NULL, victim, TO_ALL );
if( victim->sex != SEX_MALE )
{
act( "$n shrugs off the effect of the blow.",
victim, NULL, NULL, TO_ROOM );
send_to_char( "That didn't hurt too much.\n\r", victim );
damage( ch, victim, number_range( ch->level / 2, ch->level ),
gsn_dirty_fighting, WEAR_NONE );
}
else
{
act( "$n groans in agony and doubles over, clutching at $s groin.",
victim, NULL, NULL, TO_ROOM );
send_to_char( "OOOMMPH... you double over in agony!\n\r", victim );
damage( ch, victim, number_range( ch->level, ch->level * 3 ),
gsn_dirty_fighting, WEAR_NONE );
victim->position = POS_GETTING_UP;
WAIT_STATE( victim, PULSE_VIOLENCE );
}
break;
case 3:
if( get_curr_dex( ch ) - get_curr_dex( victim ) + number_range( -10, 10 ) < 0 )
break;
act( "$n grabs $O ears and crushes $S face to a pulp with $s forehead.",
ch, NULL, victim, TO_ROOM );
act( "You grab $N and headbutt him, square between the eyes.",
ch, NULL, victim, TO_CHAR );
if( get_eq_char( victim, WEAR_FACE )
|| !IS_SET( victim->body_parts, BODY_PART_NOSE ) )
damage( ch, victim, number_range( ch->level / 2, ch->level ),
gsn_dirty_fighting, WEAR_NONE );
else
{
act( "$O nose is splattered across $S face.",
ch, NULL, victim, TO_ALL );
damage( ch, victim, number_range( ch->level, ch->level * 3 ),
gsn_dirty_fighting, WEAR_NONE );
REMOVE_BIT( victim->body_parts, BODY_PART_NOSE );
xSET_BIT( victim->affected_by, AFF_BLEEDING );
}
break;
case 4:
act( "$n leap$% for $O eyes, fingers clawed.",
ch, NULL, victim, TO_ALL );
if( number_bits( 3 ) || get_eq_char( victim, WEAR_FACE )
|| !IS_SET( victim->body_parts, BODY_PART_EYES )
|| get_curr_dex( ch ) - get_curr_dex( victim ) + number_range( -10, 10 ) < 0 )
break;
for( i = 0; part_loss_table[i].body_parts != BODY_PART_EYES; )
++i;
obj = create_object( get_obj_index( part_loss_table[i].limb_vnum ),
victim->level );
sprintf( buf, obj->short_descr, IS_NPC( victim )
? victim->short_descr : victim->name );
free_string( obj->short_descr );
obj->short_descr = str_dup( buf );
obj->value[0] = victim->body_parts & part_loss_table[i].body_parts;
if( IS_AFFECTED( victim, AFF_POISON ) )
obj->value[3] = 1;
obj->cost /= 20;
obj_to_char( obj, ch );
act( part_loss_table[i].act_char, victim, NULL, ch, TO_CHAR );
act( part_loss_table[i].act_other, victim, NULL, ch, TO_ROOM );
break;
case 5:
act( "$n deliver$% a swift kick to $O shins.", ch, NULL, victim, TO_ALL );
damage( ch, victim, number_range( ch->level / 2, ch->level ),
gsn_dirty_fighting, WEAR_NONE );
break;
default:
break;
}
}
/* Mobs using magical items by Spitfire from merc mailing list */
/* Modified to give every magical item an equal chance of being used plus
* eating pills -Kahn */
void use_magical_item( CHAR_DATA *ch )
{
OBJ_DATA *obj;
OBJ_DATA *cobj = NULL;
int number = 0;
char buf[MAX_INPUT_LENGTH];
for( obj = ch->carrying; obj; obj = obj->next_content )
{
if( ( obj->item_type == ITEM_SCROLL
|| obj->item_type == ITEM_WAND
|| obj->item_type == ITEM_STAFF
|| obj->item_type == ITEM_PILL )
&& number_range( 0, number ) == 0 )
{
cobj = obj;
number++;
}
}
if( !cobj )
return;
switch( cobj->item_type )
{
case ITEM_SCROLL:
do_recite( ch, "scroll" );
break;
case ITEM_WAND:
if( cobj->wear_loc == WEAR_HOLD_L
|| cobj->wear_loc == WEAR_HOLD_R )
do_zap( ch, "" );
break;
case ITEM_STAFF:
if( cobj->wear_loc == WEAR_HOLD_L
|| cobj->wear_loc == WEAR_HOLD_R )
do_brandish( ch, "" );
break;
case ITEM_POTION:
do_quaff( ch, "potion" );
break;
case ITEM_PILL:
sprintf( buf, "%s", cobj->name );
do_eat( ch, buf );
break;
}
return;
}
/*
* Set position of a victim.
*/
void update_pos( CHAR_DATA *victim )
{
if( victim->hit > 0 || IS_AFFECTED( victim, AFF_DEAD ) )
{
if( victim->position <= POS_STUNNED )
victim->position = POS_STANDING;
return;
}
if( IS_NPC( victim ) || victim->hit <= -11 )
{
victim->position = POS_DEAD;
return;
}
if( victim->hit <= -6 )
victim->position = POS_MORTAL;
else if( victim->hit <= -3 )
victim->position = POS_INCAP;
else
victim->position = POS_STUNNED;
return;
}
/*
* Start fights.
*/
void set_fighting( CHAR_DATA *ch, CHAR_DATA *victim )
{
if( ch->fighting )
{
bug( "Set_fighting: already fighting" );
bug( "...%s attacking %s at %d",
( IS_NPC( ch ) ? ch->short_descr : ch->name ),
( IS_NPC( victim ) ? victim->short_descr : victim->name ),
victim->in_room->vnum );
return;
}
if( IS_AFFECTED( ch, AFF_SLEEP ) )
affect_strip( ch, gsn_sleep );
ch->fighting = victim;
if( ch->position != POS_SMASHED
&& ch->position != POS_SLEEPING
&& ch->position != POS_GETTING_UP )
ch->position = POS_FIGHTING;
if( IS_NPC( ch ) )
add_mob_fight_triggers( ch );
if( get_time_left( ch->in_room->events, evn_room_violence ) < 0 )
create_room_event( ch->in_room, evn_room_violence,
PULSE_VIOLENCE );
return;
}
/*
* Stop fights.
*/
void stop_fighting( CHAR_DATA *ch, bool fBoth )
{
CHAR_DATA *fch;
purge_spam( ch->in_room );
for( fch = char_list; fch; fch = fch->next )
{
if( fch == ch || ( fBoth && fch->fighting == ch ) )
{
fch->fighting = NULL;
if( fch->position != POS_SMASHED )
fch->position = POS_STANDING;
if( IS_AFFECTED( fch, AFF_BERSERK ) )
affect_strip( fch, gsn_berserk );
if( !IS_NPC( fch ) && fch->pcdata->pc_bits >= PC_BIT_STRANGLE )
fch->pcdata->pc_bits %= PC_BIT_STRANGLE;
update_pos( fch );
}
}
return;
}
/*
* Stop fights only in the character's room.
* easier on CPU from mobile_update
*/
void stop_fighting_room( CHAR_DATA *ch, bool fBoth )
{
CHAR_DATA *fch;
if( ch->in_room->area->nplayer )
purge_spam( ch->in_room );
for( fch = ch->in_room->people; fch; fch = fch->next_in_room )
{
if( fch == ch || ( fBoth && fch->fighting == ch ) )
{
fch->fighting = NULL;
if( fch->position != POS_SMASHED )
fch->position = POS_STANDING;
if( IS_AFFECTED( fch, AFF_BERSERK ) )
affect_strip( fch, gsn_berserk );
if( !IS_NPC( fch ) && fch->pcdata->pc_bits >= PC_BIT_STRANGLE )
fch->pcdata->pc_bits %= PC_BIT_STRANGLE;
update_pos( fch );
}
}
return;
}
/*
* Make a corpse out of a character.
*/
void make_corpse( CHAR_DATA *ch )
{
OBJ_DATA *corpse;
OBJ_DATA *obj;
OBJ_DATA *obj_next;
char *name;
char buf[MAX_STRING_LENGTH];
if( IS_NPC( ch ) )
{
/*
* This longwinded corpse creation routine comes about because
* we dont want anything created AFTER a corpse to be placed
* INSIDE a corpse. This had caused crashes from obj_update( )
* in extract_obj( ) when the updating list got shifted from
* object_list to obj_free. --- Thelonius (Monk)
*/
name = ch->short_descr;
if( ch->gold > 0 )
{
OBJ_DATA *coins;
coins = create_money( ch->gold );
corpse = create_object( get_obj_index( OBJ_VNUM_CORPSE_NPC ), 0 );
obj_to_obj( coins, corpse );
ch->gold = 0;
}
else
{
corpse = create_object( get_obj_index( OBJ_VNUM_CORPSE_NPC ), 0 );
}
set_timer_tick( corpse, number_fuzzy( 2 ) );
}
else
{
name = ch->name;
corpse = create_object( get_obj_index( OBJ_VNUM_CORPSE_PC ), 0 );
set_timer_tick( corpse, number_range( 20, 30 ) );
}
corpse->value[0] = ch->race;
sprintf( buf, "%s %s", corpse->name, name );
free_string( corpse->name );
corpse->name = str_dup( buf );
sprintf( buf, corpse->short_descr, name );
free_string( corpse->short_descr );
corpse->short_descr = str_dup( buf );
sprintf( buf, corpse->description, name );
free_string( corpse->description );
corpse->description = str_dup( buf );
corpse->level = ch->level;
for( obj = ch->carrying; obj; obj = obj_next )
{
obj_next = obj->next_content;
if( obj->deleted )
continue;
obj_from_char( obj );
/*
* Remove item inventories from all corpses.
* Includes licenses to kill
*/
if( IS_SET( obj->extra_flags, ITEM_INVENTORY ) )
{
extract_obj( obj );
}
else
{
obj_to_obj( obj, corpse );
}
}
obj_to_room( corpse, ch->in_room );
strip_events( &corpse->events, evn_imp_grab );
return;
}
/*
* Improved Death_cry contributed by Diavolo.
*/
void death_cry( CHAR_DATA *ch )
{
OBJ_DATA *obj;
char buf[MAX_STRING_LENGTH];
const char *msg;
int vnum;
int door;
int i, chance;
vnum = 0;
switch( number_bits( 4 ) )
{
default:
msg = "&yYou hear $n's death cry.";
break;
case 0:
case 1:
case 2:
msg = "&yYou hear $n's death cry.";
vnum = -1; /* take an actual limb */
break;
case 3:
msg = "&y$n hits the ground ... DEAD.";
break;
case 4:
msg = "&y$n splatters blood on your armour.";
break;
case 5:
case 6:
msg = "&yYou smell $n's sphincter releasing in death.";
vnum = OBJ_VNUM_FINAL_TURD;
break;
case 7:
msg = "&y$n crumples to the ground, lifeless.";
break;
case 8:
case 9:
msg = "&yYou lift $o still beating heart from $s chest in triumph.";
vnum = OBJ_VNUM_TORN_HEART;
break;
case 10:
msg = "&y$n screams in agony as $s life leaves $m.";
break;
case 11:
msg = "&yYou slice $n open and spill $s guts on the ground.";
vnum = OBJ_VNUM_SPILLED_GUTS;
break;
case 12:
msg = "&yYour blow smacks $n fair in the mouth and broken teeth fly.";
vnum = OBJ_VNUM_LOOSE_TEETH;
break;
}
if( !IS_NPC( ch ) && number_bits( 1 ) )
vnum = 0;
if( vnum > 0 )
{
char *name;
act( msg, ch, NULL, NULL, TO_ALL );
name = IS_NPC( ch ) ? ch->short_descr : ch->name;
obj = create_object( get_obj_index( vnum ), ch->level );
set_timer_tick( obj, number_range( 4, 7 ) );
if( IS_AFFECTED( ch, AFF_POISON ) )
SET_BIT( obj->extra_flags, ITEM_POISONED );
sprintf( buf, obj->short_descr, name );
free_string( obj->short_descr );
obj->short_descr = str_dup( buf );
sprintf( buf, obj->description, name );
free_string( obj->description );
obj->description = str_dup( buf );
if( IS_AFFECTED( ch, AFF_POISON ) )
obj->value[3] = 1;
obj->cost /= 20;
obj_to_room( obj, ch->in_room );
}
else
{
chance = number_percent( );
for( i = 0; vnum < 0 && part_loss_table[i].chance > 0; ++i )
{
if( chance > part_loss_table[i].chance
|| ( part_loss_table[i].body_parts & ch->body_parts ) == 0 )
continue;
remove_part( ch, ch, i );
break;
}
if( part_loss_table[i].chance <= 0 )
act( msg, ch, NULL, NULL, TO_ALL );
}
if( IS_NPC( ch ) )
msg = "&yYou hear something's death cry.&n";
else
msg = "&yYou hear someone's death cry.&n";
for( door = 0; door <= 5; door++ )
{
EXIT_DATA *pexit;
if( ( pexit = ch->in_room->exit[door] )
&& pexit->to_room
&& pexit->to_room != ch->in_room )
send_to_room( msg, pexit->to_room );
}
return;
}
void raw_kill( CHAR_DATA *ch, CHAR_DATA *victim )
{
AFFECT_DATA *paf;
bool safe_kill = FALSE;
EVENT *e, *e_next;
int can_cry = 1;
if( !IS_NPC( victim ) && ( xIS_SET( victim->act, PLR_BATTLE )
|| IS_SET( victim->in_room->room_flags,
ROOM_ARENA ) ) )
safe_kill = TRUE;
stop_fighting( victim, TRUE );
if( ch != victim && IS_NPC( victim )
&& xIS_SET( victim->pIndexData->progtypes, DEATH_PROG ) )
{
victim->position = POS_STANDING;
can_cry = mprog_percent_check( victim, ch, NULL, NULL, DEATH_PROG );
}
if( ch != victim && can_cry && can_cry != NO_FLAG )
death_cry( victim );
if( victim->spec_fun
&& IS_SET( spec_table[victim->spec_fun].usage, SPEC_DEATH ) )
{
if( !( *spec_table[victim->spec_fun].spec_fun )
( victim, ch, SPEC_DEATH, NULL ) )
make_corpse( victim );
}
else if( safe_kill )
{
OBJ_DATA *obj;
for( obj = victim->carrying; obj; obj = obj->next_content )
{
if( !obj->deleted && obj->wear_loc != WEAR_NONE )
unequip_char( victim, obj );
}
}
else
make_corpse( victim );
if( !IS_NPC( victim ) && IS_AFFECTED( victim, AFF_VAMP_BITE ) )
{
paf = new_affect( );
paf->type = gsn_vampiric_bite;
paf->location = APPLY_RACE;
paf->modifier = race_lookup( "Vampire" ) - victim->race;
paf->duration = 1000;
vset( paf->bitvector, AFF_POLYMORPH );
xSET_BIT( paf->bitvector, AFF_VAMP_BITE );
paf->next = victim->affected;
victim->affected = paf;
}
for( paf = victim->affected; paf; paf = paf->next )
{
if( paf->deleted || xIS_SET( paf->bitvector, AFF_POLYMORPH ) )
continue;
/* Keep the ghoul affect, not
if( !IS_NPC( victim ) && IS_AFFECTED( victim, AFF_GHOUL ) )
continue; */
affect_remove( victim, paf );
}
for( e = ch->events; e; e = e_next )
{
e_next = e->next_local;
if( !IS_SET( e->flags ^ event_table[e->type].flags, EVENT_DEATH_REMOVE ) )
continue;
event_remove_global( e );
event_remove_local( &ch->events, e );
free_event( e );
}
if( IS_NPC( victim ) )
{
if( !IS_NPC( ch ) && ch->pcdata->quest->target == victim->pIndexData )
{
send_to_char( "&BYou have almost completed your QUEST!&n\n\r", ch );
send_to_char( "Better hurry and finish it now.\n\r", ch );
ch->pcdata->quest->type = QUEST_KILL_COMPLETE;
}
SysInfo->mob_deaths++;
victim->pIndexData->killed++;
kill_table[URANGE( 0, victim->level, ( LEVEL_HERO * 2 ) - 1 )].killed++;
extract_char( victim, TRUE );
return;
}
extract_char( victim, FALSE );
victim->position = POS_RESTING;
clean_char( victim );
victim->body_parts &= race_table[victim->race].body_parts;
if( safe_kill )
{
int i;
victim->hit = victim->max_hit;
for( i = 0; i < MAGIC_MAX; ++i )
victim->mana[i] = victim->max_mana[i];
victim->move = victim->max_move;
xREMOVE_BIT( victim->affected_by, AFF_BLEEDING );
xREMOVE_BIT( victim->act, PLR_BATTLE );
victim->pcdata->died--;
victim->body_parts = race_table[victim->race].body_parts;
}
if( !str_cmp( race_table[victim->race].name, "Vampire" ) )
{
victim->pcdata->condition[COND_FULL] = 10;
victim->pcdata->condition[COND_THIRST] = 10;
}
save_char_obj( victim );
return;
}
void group_gain( CHAR_DATA *ch, CHAR_DATA *victim )
{
CHAR_DATA *gch;
char buf[MAX_STRING_LENGTH];
int members, member_levels, highest_member = 0;
int xp;
/*
* Monsters don't get kill xp's or alignment changes.
* Dying of mortal wounds or poison doesn't give xp to anyone!
*/
if( IS_NPC( ch ) || victim == ch )
return;
members = 0;
member_levels = 0;
for( gch = ch->in_room->people; gch; gch = gch->next_in_room )
{
if( is_same_group( gch, ch ) )
{
if( gch->level > highest_member )
highest_member = gch->level;
members++;
member_levels += gch->level;
}
}
if( members == 0 || member_levels == 0 )
{
bug( "Group_gain: members-%d levels-%d", members,
member_levels );
members = 1;
member_levels = ch->level;
}
members--;
for( gch = ch->in_room->people; gch; gch = gch->next_in_room )
{
OBJ_DATA *obj;
OBJ_DATA *obj_next;
if( !is_same_group( gch, ch ) )
continue;
xp = xp_compute( gch, victim, -1 ) * gch->level / member_levels;
xp += xp * members / 10;
/* So high levels can't drag littlies around, xp drops off.
* Once the highest character in the group is 18 levels more
* than gch, gch gets no experience. --Symp
*/
if( gch->level + 5 < highest_member )
xp = 2 * xp - power( xp, 4, highest_member - gch->level );
if( xp < 0 )
xp = 0;
if( xp >= 2500 )
sprintf( buf, "&YYou receive %d experience points.&n\n\r",
xp / 100 );
else
sprintf( buf, "&YYou receive %d.%2.2d experience points.&n\n\r",
xp / 100, xp % 100 );
send_to_char( buf, gch );
gain_exp( gch, xp );
for( obj = gch->carrying; obj; obj = obj_next )
{
obj_next = obj->next_content;
if( obj->deleted || obj->wear_loc == WEAR_NONE
|| number_bits( 1 ) )
continue;
if( ( IS_OBJ_STAT( obj, ITEM_ANTI_EVIL )
&& IS_EVIL( gch ) )
|| ( IS_OBJ_STAT( obj, ITEM_ANTI_GOOD )
&& IS_GOOD( gch ) )
|| ( IS_OBJ_STAT( obj, ITEM_ANTI_NEUTRAL )
&& IS_NEUTRAL( gch ) ) )
{
act( "&m$n is zapped by $p.&n", gch, obj, NULL, TO_ALL );
obj_from_char( obj );
obj_to_room( obj, gch->in_room );
}
}
}
return;
}
/*
* Compute xp for a kill.
* Also adjust alignment of OUTLAW.
* Edit this function to change xp computations.
*/
int xp_compute( CHAR_DATA *gch, CHAR_DATA *victim, int dam )
{
float bonus;
int xp;
int extra;
int level;
int number;
/*
* alignment change if this is a k"ng blow
* this has grown a little to include religious follow-on
* in the event of significant alignment change.
*/
if( dam < 0 )
{
extra = gch->alignment - victim->alignment * 3 / 2;
extra += race_table[gch->race].natural_align / 10;
bonus = log( (double)( abs( extra ) + 1 ) );
extra = bonus * bonus * ( ( extra < 0 ) ? -1 : 1 );
gch->alignment = URANGE( -1000, gch->alignment + extra, 1000 );
if( gch->pcdata->clan && gch->pcdata->clan->clan_type == CLAN_ORDER
&& gch->pcdata->clan_rank != RANK_EXILED
&& victim->alignment >= gch->pcdata->religion->align_min
&& victim->alignment <= gch->pcdata->religion->align_max )
add_karma( gch->pcdata->clan, -3 );
if( !IS_NPC( gch ) && gch->pcdata->religion
&& ( !xIS_SET( gch->act, PLR_OUTLAW ) || number_bits( 1 ) )
&& ( gch->alignment < gch->pcdata->religion->align_min
|| gch->alignment > gch->pcdata->religion->align_max ) )
{
send_to_char( "&ROh NO!&r You have angered your god!\n\r", gch );
act( "$n has angered his god!.", gch, NULL, NULL, TO_ROOM );
act( "A large bolt of lightning strikes $n from the heavens.",
gch, NULL, NULL, TO_ROOM );
act( "A large bolt of lightning strikes you from the heavens.",
gch, NULL, NULL, TO_CHAR );
spell_power_6( skill_lookup( "lightning bolt" ),
gch->level + 5, gch, gch );
xSET_BIT( gch->act, PLR_OUTLAW );
if( gch->pcdata->clan && gch->pcdata->clan->clan_type == CLAN_ORDER
&& gch->pcdata->clan_rank != RANK_EXILED )
{
CLAN_DATA *order = gch->pcdata->clan;
log_string( "Order %s: forcibly exiled from %s",
gch->name, order->name );
wiznetf( gch, WIZ_CLAN, 0, "%s has been forced from the order of %s.",
gch->name, order->name );
talk_channel( gch, "&MINFO: $n has angered $g and has been exiled!",
CHANNEL_INFO, "INFO" );
remove_from_clan( gch ); /* easier this way */
gch->pcdata->clan = order;
gch->pcdata->clan_rank = RANK_EXILED;
order->members++;
add_karma( order, -100 );
}
else if( !xIS_SET( gch->act, PLR_OUTLAW ) )
talk_channel( gch, "&MINFO: $n has angered $g and is now an outlaw",
CHANNEL_INFO, "INFO" );
}
}
bonus = 1.0;
xp = 4000 - URANGE( -10, gch->level - victim->level, 6 ) * 950;
xp = xp - ( xp / 2 ) + ( xp * 500 / get_curr_resil( victim ) );
if( gch->level < 10 )
bonus += (float)1 / 2;
if( xIS_SET( victim->affected_by, AFF_SANCTUARY )
|| IS_SET( race_table[victim->race].race_abilities, RACE_SANCT ) )
bonus += (float)1 / 2;
else if( IS_NPC( victim )
&& xIS_SET( victim->pIndexData->affected_by, AFF_SANCTUARY ) )
bonus += (float)1 / 3;
if( IS_AFFECTED( victim, AFF_ATTACKSHIELD ) )
bonus += (float)2 / 5;
if( get_eq_char( victim, WEAR_WIELD_R ) )
bonus += (float)1 / 4;
if( get_eq_char( victim, WEAR_WIELD_DOUBLE ) )
bonus += (float)1 / 3;
if( get_eq_char( victim, WEAR_WIELD_L ) )
bonus += (float)1 / 5;
if( victim->race == gch->race )
bonus -= (float)1 / 8;
if( IS_NPC( victim ) )
{
if( victim->class >= 0 )
{
if( victim->class < AVAIL_CLASS )
bonus += (float)1/3;
else
bonus += (float)2/5;
}
if( xIS_SET( victim->pIndexData->act, ACT_BURIED ) )
bonus += (float)1 / 7;
if( xIS_SET( victim->pIndexData->affected_by, AFF_WARP_FLESH ) )
bonus -= (float)1 / 3;
if( xIS_SET( victim->pIndexData->affected_by, AFF_MIND_MIST ) )
bonus -= (float)1 / 4;
if( xIS_SET( victim->act, ACT_AGGRESSIVE ) )
bonus += (float)1 / 20;
if( victim->pIndexData->pShop != 0 )
bonus -= (float)1 / 4;
if( victim->spec_fun != 0 && spec_table[victim->spec_fun].prcnt_xp )
bonus += (float)spec_table[victim->spec_fun].prcnt_xp / 100;
}
else
{
if( !xIS_SET( victim->act, PLR_BATTLE ) )
bonus = 0.0;
else
bonus *= (float)2;
}
if( dam >= 0 )
{
dam = UMIN( dam, victim->hit );
bonus *= (float)dam / victim->max_hit;
}
if( gch->class == CLASS_BUILDER )
bonus /= 5;
xp = (int)( xp * bonus );
/*
* Adjust for popularity of target:
* -1/8 for each target over 'par' ( down to - 50% )
* +1/8 for each target under 'par' ( up to + 25% )
*/
if( IS_NPC( victim ) )
{
level = URANGE( 0, victim->level, MAX_LEVEL - 1 );
number = UMAX( 1, kill_table[level].number );
extra = victim->pIndexData->killed - kill_table[level].killed
/ number;
xp -= xp * URANGE( -2, extra, 4 ) / 8;
}
xp = number_range( xp * 3 / 4, xp * 5 / 4 );
xp = UMAX( 0, xp );
if( !IS_NPC( victim ) )
xp = UMIN( xp, 50000 );
return xp;
}
char *xp_str( char *outstr, const int xp )
{
if( xp < 0 )
return strcpy( outstr, "" );
if( xp >= 2500 )
sprintf( outstr, " &g(&c%d&gxp)&n", xp / 100 );
else
sprintf( outstr, " &g(&c%d.%2.2d&gxp)&n", xp / 100, xp % 100 );
return outstr;
}
char *dam_amount( char *outstr, CHAR_DATA *ch, const int dam )
{
if( !IS_IMMORTAL( ch ) )
return strcpy( outstr, "" );
sprintf( outstr, " &r<%d>&n", dam );
return outstr;
}
void dam_message( CHAR_DATA *ch, CHAR_DATA *victim, int dam, int dt,
int wpn )
{
static const char *const him_her[] = { "it", "him", "her" };
const char *vp;
const char *attack;
char *point;
char buf[MAX_STRING_LENGTH];
char buf1[256];
char buf2[256];
char buf3[256];
char buf4[256];
char buf5[256];
char punct;
CHAR_DATA *vch;
int xp;
int members, member_levels;
/* All the bulk is in the dam table now. */
for( xp = 0; dam_table[xp].max_dam >= 0; xp++ )
{
if( dam_table[xp].max_dam >= dam )
break;
}
if( !dam_table[xp].mesg )
{
bug( "Unreal bug, no damage message for %d dam.", dam );
return;
}
if( dt >= 0 && dt <= MAX_SKILL
&& IS_SET( skill_table[dt].skill_type, SKILL_TYPE_FIRE ) )
vp = dam_table[xp].fire_mesg;
else if( dt >= 0 && dt <= MAX_SKILL
&& IS_SET( skill_table[dt].skill_type, SKILL_TYPE_ICE ) )
vp = dam_table[xp].ice_mesg;
else
vp = dam_table[xp].mesg;
punct = ( dam <= UMAX( ch->level * 3 / 2, 25 ) ) ? '.' : '!';
if( dt == TYPE_HIT )
{
if( ch->race > MAX_RACE )
{
bug( "Dam_message: %d invalid race", ch->race );
ch->race = 0;
}
attack = race_table[ch->race].dmg_message;
}
else
{
if( dt >= 0 && dt < MAX_SKILL )
attack = skill_table[dt].noun_damage;
else if( dt >= TYPE_HIT )
{
xp = dt - TYPE_HIT;
attack = flag_string( weapon_flags, &xp );
}
else
{
bug( "Dam_message: bad dt %d caused by %s.", dt,
ch->name );
dt = TYPE_HIT;
attack = "hit";
}
}
sprintf( buf1, "Your %s %s %%s&n%c%%s%%s\n\r", attack, vp, punct );
sprintf( buf2, "%%s&n's %s %s you%c%%s\n\r", attack, vp, punct );
sprintf( buf3, "%%s&n's %s %s %%s&n%c%%s%%s\n\r", attack, vp, punct );
sprintf( buf4, "Your %s %s you%c%%s\n\r", attack, vp, punct );
sprintf( buf5, "%%s&n's %s %s %%s%c%%s%%s\n\r", attack, vp, punct );
members = 0;
member_levels = 0;
if( ch != victim )
{
for( vch = ch->in_room->people; vch; vch = vch->next_in_room )
{
if( !IS_AWAKE( vch ) )
continue;
if( is_same_group( vch, ch ) )
{
members++;
member_levels += vch->level;
}
}
if( members == 0 || member_levels == 0 )
{
bug( "dam_message: members-%d levels-%d",
members, member_levels );
members = 1;
member_levels = ch->level;
}
}
for( vch = ch->in_room->people; vch; vch = vch->next_in_room )
{
char tmpxp[25], tmpdam[25];
if( !IS_AWAKE( vch ) )
continue;
xp = -1;
if( ch != victim )
{
if( !IS_NPC( vch ) && is_same_group( vch, ch ) )
{
xp = xp_compute( vch, victim, dam ) * vch->level / member_levels;
xp += xp * members / 10;
if( vch == ch )
xp += xp / 12;
}
if( xp > 0 )
{
gain_exp( vch, xp );
vch->tot_xp += xp;
}
}
if( ch->fighting == victim && dt != gsn_throw_weapon )
{
if( !vch->desc
|| ( ( vch == ch || vch == victim ) &&
xIS_SET( CH( vch->desc )->act, PLR_BATTLESELF ) )
|| ( ( vch != ch && vch != victim ) &&
xIS_SET( CH( vch->desc )->act, PLR_BATTLEOTHER ) ) )
continue;
}
if( vch == ch )
{
if( ch == victim )
sprintf( buf, buf4, dam_amount( tmpdam, vch, dam ) );
else
sprintf( buf, buf1, PERS( victim, vch ),
xp_str( tmpxp, xp ), dam_amount( tmpdam, vch, dam ) );
}
else if( vch == victim )
sprintf( buf, buf2, PERS( ch, vch ),
dam_amount( tmpdam, vch, dam ) );
else if( ch == victim )
sprintf( buf, buf5, PERS( ch, vch ),
him_her[URANGE( 0, ch->sex, 2 )],
xp_str( tmpxp, xp ), dam_amount( tmpdam, vch, dam ) );
else
sprintf( buf, buf3, PERS( ch, vch ),
PERS( victim, vch ),
xp_str( tmpxp, xp ), dam_amount( tmpdam, vch, dam ) );
point = &buf[0];
while( *point == '&' )
point += 2;
*point = UPPER( *point );
send_to_char( buf, vch );
}
if( ch->fighting == victim && dt != gsn_throw_weapon )
{
if( dam > 0 )
{
ch->num_hits++;
ch->tot_dam += dam;
}
else
ch->num_hits = UMAX( ch->num_hits, 0 );
}
return;
}
/*
* Disarm a creature.
* Caller must check for successful attack.
*/
bool disarm( CHAR_DATA *ch, CHAR_DATA *victim )
{
OBJ_DATA *obj;
if( ( get_size( ch ) - get_size( victim ) )
< -20 + number_range( -40, 40 ) )
return FALSE;
if( !( obj = get_eq_char( victim, WEAR_WIELD_R ) ) )
if( !( obj = get_eq_char( victim, WEAR_WIELD_L ) ) )
if( !( obj = get_eq_char( victim, WEAR_WIELD_DOUBLE ) ) )
return FALSE;
if( IS_SET( obj->extra_flags, ITEM_NOREMOVE )
|| IS_SET( obj->extra_flags, ITEM_NODROP ) )
return FALSE;
if( !get_eq_char( ch, WEAR_WIELD_R )
&& !get_eq_char( ch, WEAR_WIELD_L )
&& !get_eq_char( ch, WEAR_WIELD_DOUBLE )
&& ch->class != CLASS_MARTIAL_ARTIST
&& get_first_class( ch ) != CLASS_MARTIAL_ARTIST
&& get_second_class( ch ) != CLASS_MARTIAL_ARTIST
&& number_bits( 3 ) != 0 )
return FALSE;
act( "You disarm $N!", ch, NULL, victim, TO_CHAR );
act( "$n DISARMS you!", ch, NULL, victim, TO_VICT );
act( "$n DISARMS $N!", ch, NULL, victim, TO_NOTVICT );
obj_from_char( obj );
if( IS_NPC( victim ) )
{
switch( number_bits( 2 ) )
{
case 0:
obj_to_room( obj, ch->in_room );
break;
case 1: case 2:
act( "$n retrieves the weapon.", victim, NULL, NULL, TO_ROOM );
obj_to_char( obj, victim );
break;
default:
act( "$n retrieves the weapon.", victim, NULL, NULL, TO_ROOM );
obj_to_char( obj, victim );
do_wear( victim, obj->name );
break;
}
}
else
obj_to_room( obj, victim->in_room );
return TRUE;
}
/*
* Trip a creature.
* Caller must check for successful attack.
*/
void trip( CHAR_DATA *ch, CHAR_DATA *victim )
{
if( IS_SET( victim->body_parts, BODY_PART_WINGS )
|| xIS_SET( ch->affected_by, AFF_FLYING ) )
return;
if( victim->wait == 0 )
{
act( "$n trips $N and $N goes down!", ch, NULL, victim, TO_ALL );
WAIT_STATE( ch, 2 * PULSE_VIOLENCE );
WAIT_STATE( victim, 2 * PULSE_VIOLENCE );
victim->position = POS_RESTING;
}
return;
}
/*
* At the moment this is for all those trapped in a snare or web (AFF_HOLD)
* they will struggle to free themselves and in the process hurt themselves,
* an NPC who breaks free will flee if it is fighting.
* -- Symposium
*/
void web_update( CHAR_DATA *ch )
{
int dam;
if( !IS_AFFECTED( ch, AFF_HOLD ) )
return;
if( IS_AFFECTED( ch, AFF_PASS_DOOR ) || IS_AFFECTED( ch, AFF_GHOUL )
|| power( number_percent( ), 5, 20 - get_curr_str( ch ) ) < 9 )
{
affect_strip( ch, gsn_snare );
affect_strip( ch, gsn_web );
affect_strip( ch, gsn_strangle );
xREMOVE_BIT( ch->affected_by, AFF_HOLD );
if( IS_AFFECTED( ch, AFF_PASS_DOOR ) || IS_AFFECTED( ch, AFF_GHOUL ) )
act( "$o bonds pass straight through $m as $e move$%.",
ch, NULL, NULL, TO_ALL );
else
act( "$n break$% free of the entanglement.",
ch, NULL, NULL, TO_ALL );
if( IS_NPC( ch ) && xIS_SET( ch->act, ACT_WIMPY )
&& ch->fighting && ch->hit < ch->max_hit / 4 )
do_flee( ch, "" );
}
else
{
dam = 5 + ( ch->level / 2 ) + ( ch->hit / 35 );
dam = number_range( dam / 3, dam * 2 / 3 );
act( "$n struggle$% in the web.", ch, NULL, NULL, TO_ALL );
damage( ch, ch, dam, gsn_web, WEAR_NONE );
create_char_event( ch, evn_web_struggle,
number_range( PULSE_PER_SECOND * 18,
PULSE_PER_SECOND * 27 ) );
}
return;
}